From 0d1a911a7c51c77302e8c73361a23071adad2311 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 11 Jul 2024 18:53:08 -0300 Subject: [PATCH 01/26] chore: Bump version to 4.51.0 (#5785) --- android/app/build.gradle | 2 +- ios/RocketChatRN.xcodeproj/project.pbxproj | 4 ++-- ios/RocketChatRN/Info.plist | 2 +- ios/ShareRocketChatRN/Info.plist | 2 +- package.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 917aa93959..f2976d8064 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -93,7 +93,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode VERSIONCODE as Integer - versionName "4.50.1" + versionName "4.51.0" vectorDrawables.useSupportLibrary = true if (!isFoss) { manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String] diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index b3e98c2a14..b3d3d0c02d 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -3135,7 +3135,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 4.50.1; + MARKETING_VERSION = 4.51.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -3179,7 +3179,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 4.50.1; + MARKETING_VERSION = 4.51.0; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService; diff --git a/ios/RocketChatRN/Info.plist b/ios/RocketChatRN/Info.plist index a45e228c7b..b7325a93c5 100644 --- a/ios/RocketChatRN/Info.plist +++ b/ios/RocketChatRN/Info.plist @@ -28,7 +28,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 4.50.1 + 4.51.0 CFBundleSignature ???? CFBundleURLTypes diff --git a/ios/ShareRocketChatRN/Info.plist b/ios/ShareRocketChatRN/Info.plist index 76dee41473..66c3277239 100644 --- a/ios/ShareRocketChatRN/Info.plist +++ b/ios/ShareRocketChatRN/Info.plist @@ -26,7 +26,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 4.50.1 + 4.51.0 CFBundleVersion 1 KeychainGroup diff --git a/package.json b/package.json index b78e3f9eb6..c05c81786f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rocket-chat-reactnative", - "version": "4.50.1", + "version": "4.51.0", "private": true, "scripts": { "start": "react-native start", From 3fe5e6c85a6d9bccb6fff6de9055d398c49520f7 Mon Sep 17 00:00:00 2001 From: SK JASIMUDDIN <94641150+JASIM0021@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:41:06 -0700 Subject: [PATCH 02/26] fix: Link preview should not enlarge small images (#5789) Co-authored-by: Diego Mello --- app/containers/message/Urls.tsx | 44 +++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/app/containers/message/Urls.tsx b/app/containers/message/Urls.tsx index 3904ff9785..f2b26625d7 100644 --- a/app/containers/message/Urls.tsx +++ b/app/containers/message/Urls.tsx @@ -1,5 +1,5 @@ -import React, { useContext, useState } from 'react'; -import { StyleSheet, Text, View } from 'react-native'; +import React, { useContext, useEffect, useState } from 'react'; +import { Image, StyleSheet, Text, unstable_batchedUpdates, View } from 'react-native'; import Clipboard from '@react-native-clipboard/clipboard'; import FastImage from 'react-native-fast-image'; import { dequal } from 'dequal'; @@ -94,11 +94,27 @@ type TImageLoadedState = 'loading' | 'done' | 'error'; const Url = React.memo( ({ url, index, theme }: { url: IUrl; index: number; theme: TSupportedThemes }) => { const [imageLoadedState, setImageLoadedState] = useState('loading'); + const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 }); const { baseUrl, user } = useContext(MessageContext); - - if (!url || url?.ignoreParse || imageLoadedState === 'error') { - return null; - } + let image = url.image || url.url; + image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`; + + useEffect(() => { + if (image) { + Image.getSize( + image, + (width, height) => { + unstable_batchedUpdates(() => { + setImageDimensions({ width, height }); + setImageLoadedState('done'); + }); + }, + () => { + setImageLoadedState('error'); + } + ); + } + }, [image]); const onPress = () => openLink(url.url, theme); @@ -109,9 +125,8 @@ const Url = React.memo( const hasContent = url.title || url.description; - let image = url.image || url.url; - if (image) { - image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`; + if (!url || url?.ignoreParse || imageLoadedState === 'error' || !imageDimensions.width || !imageDimensions.height) { + return null; } return ( @@ -128,14 +143,17 @@ const Url = React.memo( }, imageLoadedState === 'loading' && styles.loading ]} - background={Touchable.Ripple(themes[theme].surfaceNeutral)} - > + background={Touchable.Ripple(themes[theme].surfaceNeutral)}> <> {image ? ( setImageLoadedState('error')} onLoad={() => setImageLoadedState('done')} /> From 80b9c354e2bd8b940782e4780fef17f3b24343c8 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 23 Jul 2024 16:14:40 -0300 Subject: [PATCH 03/26] fix: Increase random E2EE password security (#5805) --- app/lib/encryption/encryption.ts | 4 +- app/lib/encryption/utils.ts | 7 +- ios/Podfile.lock | 4 +- ios/RocketChatRN.xcodeproj/project.pbxproj | 304 +++++++++--------- package.json | 2 +- .../react-native-simple-crypto+0.6.0.patch | 293 ----------------- yarn.lock | 6 +- 7 files changed, 165 insertions(+), 455 deletions(-) delete mode 100644 patches/react-native-simple-crypto+0.6.0.patch diff --git a/app/lib/encryption/encryption.ts b/app/lib/encryption/encryption.ts index 128233bd1c..01972b2be1 100644 --- a/app/lib/encryption/encryption.ts +++ b/app/lib/encryption/encryption.ts @@ -209,8 +209,8 @@ class Encryption { }; // Create a random password to local created keys - createRandomPassword = (server: string) => { - const password = randomPassword(); + createRandomPassword = async (server: string) => { + const password = await randomPassword(); UserPreferences.setString(`${server}-${E2E_RANDOM_PASSWORD_KEY}`, password); return password; }; diff --git a/app/lib/encryption/utils.ts b/app/lib/encryption/utils.ts index a36e62c4ca..dbc7447f2e 100644 --- a/app/lib/encryption/utils.ts +++ b/app/lib/encryption/utils.ts @@ -1,7 +1,7 @@ import ByteBuffer from 'bytebuffer'; import SimpleCrypto from 'react-native-simple-crypto'; -import { compareServerVersion, random } from '../methods/helpers'; +import { compareServerVersion } from '../methods/helpers'; import { fromByteArray, toByteArray } from './helpers/base64-js'; import { TSubscriptionModel } from '../../definitions'; import { store } from '../store/auxStore'; @@ -59,7 +59,10 @@ export const toString = (thing: string | ByteBuffer | Buffer | ArrayBuffer | Uin // @ts-ignore return new ByteBuffer.wrap(thing).toString('binary'); }; -export const randomPassword = (): string => `${random(3)}-${random(3)}-${random(3)}`.toLowerCase(); +export const randomPassword = async (): Promise => { + const random = await Promise.all(Array.from({ length: 4 }, () => SimpleCrypto.utils.getRandomValues(3))); + return `${random[0]}-${random[1]}-${random[2]}-${random[3]}`; +}; export const generateAESCTRKey = () => SimpleCrypto.utils.randomBytes(32); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d22d9c8eed..a963bbf161 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1053,7 +1053,7 @@ PODS: - React-Core - react-native-safe-area-context (3.2.0): - React-Core - - react-native-simple-crypto (0.6.0): + - react-native-simple-crypto (0.6.1): - OpenSSL-Universal (= 1.1.1100) - React - react-native-slider (4.5.0): @@ -1730,7 +1730,7 @@ SPEC CHECKSUMS: react-native-orientation-locker: f0ca1a8e5031dab6b74bfb4ab33a17ed2c2fcb0d react-native-restart: 733a51ad137f15b0f8dc34c4082e55af7da00979 react-native-safe-area-context: f0906bf8bc9835ac9a9d3f97e8bde2a997d8da79 - react-native-simple-crypto: 83eb246059b5bfce7e6a96bf24569a0a98e92d74 + react-native-simple-crypto: 663609d550ba052dd6ee5eef9954bac274736576 react-native-slider: 09e5a8b7e766d3b5ae24ec15c5c4ec2679ca0f8c react-native-webview: 9f111dfbcfc826084d6c507f569e5e03342ee1c1 React-nativeconfig: b4d4e9901d4cabb57be63053fd2aa6086eb3c85f diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index b3d3d0c02d..09c59dc5fa 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -283,7 +283,9 @@ 1EFEB5982493B6640072EDC0 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFEB5972493B6640072EDC0 /* NotificationService.swift */; }; 1EFEB59C2493B6640072EDC0 /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 1EFEB5952493B6640072EDC0 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 24A2AEF2383D44B586D31C01 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 06BB44DD4855498082A744AD /* libz.tbd */; }; + 31F3B964C6AC566E0EF5AECA /* libPods-defaults-RocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 350CD3DA54F8CDCA236F25BB /* libPods-defaults-RocketChatRN.a */; }; 4C4C8603EF082F0A33A95522 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D5C142B655F8EFD006792C /* ExpoModulesProvider.swift */; }; + 54EE6E5BF0FD714BF1E75A69 /* libPods-defaults-Rocket.Chat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CB24210293E6F5D282BAF268 /* libPods-defaults-Rocket.Chat.a */; }; 65AD38372BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; 65AD38382BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; 65AD38392BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; @@ -292,8 +294,7 @@ 65AD383C2BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; 65B9A71A2AFC24190088956F /* ringtone.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 65B9A7192AFC24190088956F /* ringtone.mp3 */; }; 65B9A71B2AFC24190088956F /* ringtone.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 65B9A7192AFC24190088956F /* ringtone.mp3 */; }; - 6886EA443FB173DAC09A8CC1 /* libPods-defaults-ShareRocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 094A928A54F8F7D6362EBDE8 /* libPods-defaults-ShareRocketChatRN.a */; }; - 6A155BB8D100C38441B462E9 /* libPods-defaults-NotificationService.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5540801CC0131C98DD520BC4 /* libPods-defaults-NotificationService.a */; }; + 77030DD9D7DDAF90EFF935CB /* libPods-defaults-ShareRocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BB540D897AFCDCC5C961EEDD /* libPods-defaults-ShareRocketChatRN.a */; }; 7A006F14229C83B600803143 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7A006F13229C83B600803143 /* GoogleService-Info.plist */; }; 7A0D62D2242AB187006D5C06 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7A0D62D1242AB187006D5C06 /* LaunchScreen.storyboard */; }; 7A14FCED257FEB3A005BDCD4 /* Experimental.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A14FCEC257FEB3A005BDCD4 /* Experimental.xcassets */; }; @@ -356,8 +357,7 @@ 7AE10C0728A59530003593CB /* Inter.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7AE10C0528A59530003593CB /* Inter.ttf */; }; 7AE10C0828A59530003593CB /* Inter.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7AE10C0528A59530003593CB /* Inter.ttf */; }; 85160EB6C143E0493FE5F014 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194D9A8897F4A486C2C6F89A /* ExpoModulesProvider.swift */; }; - 91E821B2387EA7F67457F07F /* libPods-defaults-RocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AF70C4EAFD06BC1061EFEA05 /* libPods-defaults-RocketChatRN.a */; }; - 9F520B3A05B8C19BB3A51B79 /* libPods-defaults-Rocket.Chat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F3D58284790D6A3A4CE28473 /* libPods-defaults-Rocket.Chat.a */; }; + AD9AF13B0FADB6545AE46216 /* libPods-defaults-NotificationService.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F1F9B109DFF41F259095C748 /* libPods-defaults-NotificationService.a */; }; BC404914E86821389EEB543D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391C4F7AA7023CD41EEBD106 /* ExpoModulesProvider.swift */; }; D94D81FB9E10756FAA03F203 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016747EF3B9FED8DE2C9DA14 /* ExpoModulesProvider.swift */; }; DD2BA30A89E64F189C2C24AC /* libWatermelonDB.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BA7E862283664608B3894E34 /* libWatermelonDB.a */; }; @@ -461,13 +461,13 @@ 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; 016747EF3B9FED8DE2C9DA14 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-ShareRocketChatRN/ExpoModulesProvider.swift"; sourceTree = ""; }; 06BB44DD4855498082A744AD /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; - 094A928A54F8F7D6362EBDE8 /* libPods-defaults-ShareRocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-ShareRocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 0D20E5D2078C79720E1DBC03 /* Pods-defaults-Rocket.Chat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.release.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.release.xcconfig"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* Rocket.Chat Experimental.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Rocket.Chat Experimental.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = RocketChatRN/AppDelegate.h; sourceTree = ""; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = RocketChatRN/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = RocketChatRN/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = RocketChatRN/main.m; sourceTree = ""; }; + 1525225C45AF517E68DCDC2D /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-ShareRocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN.debug.xcconfig"; sourceTree = ""; }; + 17115A1EE86DB89DBE1912EB /* Pods-defaults-Rocket.Chat.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.debug.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.debug.xcconfig"; sourceTree = ""; }; 194D9A8897F4A486C2C6F89A /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-NotificationService/ExpoModulesProvider.swift"; sourceTree = ""; }; 1E01C81B2511208400FEF824 /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = ""; }; 1E01C8202511301400FEF824 /* PushResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushResponse.swift; sourceTree = ""; }; @@ -614,11 +614,12 @@ 1EFEB5972493B6640072EDC0 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; 1EFEB5992493B6640072EDC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 1EFEB5A12493B67D0072EDC0 /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = ""; }; + 307B625411539125ECBAA6EA /* Pods-defaults-RocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.debug.xcconfig"; sourceTree = ""; }; + 350CD3DA54F8CDCA236F25BB /* libPods-defaults-RocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-RocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 391C4F7AA7023CD41EEBD106 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-Rocket.Chat/ExpoModulesProvider.swift"; sourceTree = ""; }; - 3AA8F7DEB0B31A5A6329FAF5 /* Pods-defaults-RocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.release.xcconfig"; sourceTree = ""; }; 45D5C142B655F8EFD006792C /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-RocketChatRN/ExpoModulesProvider.swift"; sourceTree = ""; }; - 5540801CC0131C98DD520BC4 /* libPods-defaults-NotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-NotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 60B2A6A31FC4588700BD58E5 /* RocketChatRN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = RocketChatRN.entitlements; path = RocketChatRN/RocketChatRN.entitlements; sourceTree = ""; }; + 628DE8E64E84BCF109FC4969 /* Pods-defaults-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.release.xcconfig"; sourceTree = ""; }; 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 65B9A7192AFC24190088956F /* ringtone.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ringtone.mp3; sourceTree = ""; }; 7A006F13229C83B600803143 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; @@ -633,16 +634,15 @@ 7AAB3E52257E6A6E00707CF6 /* Rocket.Chat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rocket.Chat.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; 7AE10C0528A59530003593CB /* Inter.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Inter.ttf; sourceTree = ""; }; - AF2F39716BB7E569448DF293 /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-ShareRocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN.debug.xcconfig"; sourceTree = ""; }; - AF70C4EAFD06BC1061EFEA05 /* libPods-defaults-RocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-RocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - B0E90AAFBA1A95385AE7C19D /* Pods-defaults-RocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.debug.xcconfig"; sourceTree = ""; }; - B1C2FD3D7D6F056737CCEAE0 /* Pods-defaults-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.release.xcconfig"; sourceTree = ""; }; + 7BEF39A6613B871D0A3326E1 /* Pods-defaults-Rocket.Chat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.release.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.release.xcconfig"; sourceTree = ""; }; B37C79D9BD0742CE936B6982 /* libc++.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; + B41392725C2EA46A36F1FFB1 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-ShareRocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN.release.xcconfig"; sourceTree = ""; }; + B5F46E5C11E602B3F99D4E40 /* Pods-defaults-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.debug.xcconfig"; sourceTree = ""; }; BA7E862283664608B3894E34 /* libWatermelonDB.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libWatermelonDB.a; sourceTree = ""; }; - C2909259346452596AD27AD5 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-ShareRocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN.release.xcconfig"; sourceTree = ""; }; - E2836E70B71C86AAA7EEB0F6 /* Pods-defaults-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.debug.xcconfig"; sourceTree = ""; }; - F3D58284790D6A3A4CE28473 /* libPods-defaults-Rocket.Chat.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-Rocket.Chat.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - FE7279F2D432C9CAB7379496 /* Pods-defaults-Rocket.Chat.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.debug.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.debug.xcconfig"; sourceTree = ""; }; + BB540D897AFCDCC5C961EEDD /* libPods-defaults-ShareRocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-ShareRocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + CB24210293E6F5D282BAF268 /* libPods-defaults-Rocket.Chat.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-Rocket.Chat.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + D944D97B3EF00C39202CC81D /* Pods-defaults-RocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.release.xcconfig"; sourceTree = ""; }; + F1F9B109DFF41F259095C748 /* libPods-defaults-NotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-NotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -663,7 +663,7 @@ 7ACD4897222860DE00442C55 /* JavaScriptCore.framework in Frameworks */, 24A2AEF2383D44B586D31C01 /* libz.tbd in Frameworks */, DD2BA30A89E64F189C2C24AC /* libWatermelonDB.a in Frameworks */, - 91E821B2387EA7F67457F07F /* libPods-defaults-RocketChatRN.a in Frameworks */, + 31F3B964C6AC566E0EF5AECA /* libPods-defaults-RocketChatRN.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -672,7 +672,7 @@ buildActionMask = 2147483647; files = ( 1E25743422CBA2CF005A877F /* JavaScriptCore.framework in Frameworks */, - 6886EA443FB173DAC09A8CC1 /* libPods-defaults-ShareRocketChatRN.a in Frameworks */, + 77030DD9D7DDAF90EFF935CB /* libPods-defaults-ShareRocketChatRN.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -694,7 +694,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 6A155BB8D100C38441B462E9 /* libPods-defaults-NotificationService.a in Frameworks */, + AD9AF13B0FADB6545AE46216 /* libPods-defaults-NotificationService.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -715,7 +715,7 @@ 7AAB3E3D257E6A6E00707CF6 /* JavaScriptCore.framework in Frameworks */, 7AAB3E3E257E6A6E00707CF6 /* libz.tbd in Frameworks */, 7AAB3E3F257E6A6E00707CF6 /* libWatermelonDB.a in Frameworks */, - 9F520B3A05B8C19BB3A51B79 /* libPods-defaults-Rocket.Chat.a in Frameworks */, + 54EE6E5BF0FD714BF1E75A69 /* libPods-defaults-Rocket.Chat.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1148,14 +1148,14 @@ 7AC2B09613AA7C3FEBAC9F57 /* Pods */ = { isa = PBXGroup; children = ( - E2836E70B71C86AAA7EEB0F6 /* Pods-defaults-NotificationService.debug.xcconfig */, - B1C2FD3D7D6F056737CCEAE0 /* Pods-defaults-NotificationService.release.xcconfig */, - FE7279F2D432C9CAB7379496 /* Pods-defaults-Rocket.Chat.debug.xcconfig */, - 0D20E5D2078C79720E1DBC03 /* Pods-defaults-Rocket.Chat.release.xcconfig */, - B0E90AAFBA1A95385AE7C19D /* Pods-defaults-RocketChatRN.debug.xcconfig */, - 3AA8F7DEB0B31A5A6329FAF5 /* Pods-defaults-RocketChatRN.release.xcconfig */, - AF2F39716BB7E569448DF293 /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */, - C2909259346452596AD27AD5 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */, + B5F46E5C11E602B3F99D4E40 /* Pods-defaults-NotificationService.debug.xcconfig */, + 628DE8E64E84BCF109FC4969 /* Pods-defaults-NotificationService.release.xcconfig */, + 17115A1EE86DB89DBE1912EB /* Pods-defaults-Rocket.Chat.debug.xcconfig */, + 7BEF39A6613B871D0A3326E1 /* Pods-defaults-Rocket.Chat.release.xcconfig */, + 307B625411539125ECBAA6EA /* Pods-defaults-RocketChatRN.debug.xcconfig */, + D944D97B3EF00C39202CC81D /* Pods-defaults-RocketChatRN.release.xcconfig */, + 1525225C45AF517E68DCDC2D /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */, + B41392725C2EA46A36F1FFB1 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -1251,10 +1251,10 @@ 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */, B37C79D9BD0742CE936B6982 /* libc++.tbd */, 06BB44DD4855498082A744AD /* libz.tbd */, - 5540801CC0131C98DD520BC4 /* libPods-defaults-NotificationService.a */, - F3D58284790D6A3A4CE28473 /* libPods-defaults-Rocket.Chat.a */, - AF70C4EAFD06BC1061EFEA05 /* libPods-defaults-RocketChatRN.a */, - 094A928A54F8F7D6362EBDE8 /* libPods-defaults-ShareRocketChatRN.a */, + F1F9B109DFF41F259095C748 /* libPods-defaults-NotificationService.a */, + CB24210293E6F5D282BAF268 /* libPods-defaults-Rocket.Chat.a */, + 350CD3DA54F8CDCA236F25BB /* libPods-defaults-RocketChatRN.a */, + BB540D897AFCDCC5C961EEDD /* libPods-defaults-ShareRocketChatRN.a */, ); name = Frameworks; sourceTree = ""; @@ -1274,7 +1274,7 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "RocketChatRN" */; buildPhases = ( - 8C825AFA26B3E74DB80133EA /* [CP] Check Pods Manifest.lock */, + 3253E020357DE5DF3FF75C31 /* [CP] Check Pods Manifest.lock */, 7AA5C63E23E30D110005C4A7 /* Start Packager */, 589729E8381BA997CD19EF19 /* [Expo] Configure project */, 13B07F871A680F5B00A75B9A /* Sources */, @@ -1286,8 +1286,8 @@ 1ED0389C2B507B4F00C007D4 /* Embed Watch Content */, 7AAE9EB32891A0D20024F559 /* Upload source maps to Bugsnag */, 407D3EDE3DABEE15D27BD87D /* ShellScript */, - F973C9D58CCED6F67ADA2E0E /* [CP] Embed Pods Frameworks */, - BACE710E33AE584582B19BD0 /* [CP] Copy Pods Resources */, + 19D140A2EC74F6659DF82620 /* [CP] Embed Pods Frameworks */, + BD50494BAB9A4D228C650E81 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1305,13 +1305,13 @@ isa = PBXNativeTarget; buildConfigurationList = 1EC6ACF322CB9FC300A41C61 /* Build configuration list for PBXNativeTarget "ShareRocketChatRN" */; buildPhases = ( - 277AAB0621E317E8191F8D3D /* [CP] Check Pods Manifest.lock */, + 36BEC951447AB2B3D07D4BCA /* [CP] Check Pods Manifest.lock */, 2C50632AB476A038AFCB1D43 /* [Expo] Configure project */, 1EC6ACAC22CB9FC300A41C61 /* Sources */, 1EC6ACAD22CB9FC300A41C61 /* Frameworks */, 1EC6ACAE22CB9FC300A41C61 /* Resources */, 1EFE4DC322CBF36300B766B7 /* ShellScript */, - E4BD2A0C2451C64208B69644 /* [CP] Copy Pods Resources */, + 9D37CD62B427968B0C843657 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1360,12 +1360,12 @@ isa = PBXNativeTarget; buildConfigurationList = 1EFEB5A02493B6640072EDC0 /* Build configuration list for PBXNativeTarget "NotificationService" */; buildPhases = ( - 149B5E46E002534412D175D7 /* [CP] Check Pods Manifest.lock */, + 39E6E20ED4D99EB6D79F1BCA /* [CP] Check Pods Manifest.lock */, 86A998705576AFA7CE938617 /* [Expo] Configure project */, 1EFEB5912493B6640072EDC0 /* Sources */, 1EFEB5922493B6640072EDC0 /* Frameworks */, 1EFEB5932493B6640072EDC0 /* Resources */, - A0BF2C82F66B14B8F59589FE /* [CP] Copy Pods Resources */, + BA9071ABCBC2655C0EF5124C /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1380,7 +1380,7 @@ isa = PBXNativeTarget; buildConfigurationList = 7AAB3E4F257E6A6E00707CF6 /* Build configuration list for PBXNativeTarget "Rocket.Chat" */; buildPhases = ( - 70428352B0FB6FE6DF9D6032 /* [CP] Check Pods Manifest.lock */, + A7C23C0F63F7E514868AE885 /* [CP] Check Pods Manifest.lock */, 7AAB3E13257E6A6E00707CF6 /* Start Packager */, 6723DBD924B66933E14E7EF7 /* [Expo] Configure project */, 7AAB3E14257E6A6E00707CF6 /* Sources */, @@ -1391,8 +1391,8 @@ 7AAB3E4B257E6A6E00707CF6 /* ShellScript */, 1ED1ECE32B8699DD00F6620C /* Embed Watch Content */, 7A10288726B1D15200E47EF8 /* Upload source maps to Bugsnag */, - 9BE1CEB599E7DFFA7C004B9F /* [CP] Embed Pods Frameworks */, - 9D65115AE1DFF56CD734F04F /* [CP] Copy Pods Resources */, + 81C37052831D3F058FF2B0D4 /* [CP] Embed Pods Frameworks */, + 88DC6B30E9DF0B361032CC1B /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1586,26 +1586,24 @@ shellPath = /bin/sh; shellScript = ". ~/.nvm/nvm.sh\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\"\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; }; - 149B5E46E002534412D175D7 /* [CP] Check Pods Manifest.lock */ = { + 19D140A2EC74F6659DF82620 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); + name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 1E1EA8082326CCE300E22452 /* ShellScript */ = { @@ -1642,7 +1640,48 @@ shellPath = /bin/sh; shellScript = ". ~/.nvm/nvm.sh\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\"\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; }; - 277AAB0621E317E8191F8D3D /* [CP] Check Pods Manifest.lock */ = { + 2C50632AB476A038AFCB1D43 /* [Expo] Configure project */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "[Expo] Configure project"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-ShareRocketChatRN/expo-configure-project.sh\"\n"; + }; + 3253E020357DE5DF3FF75C31 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-defaults-RocketChatRN-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 36BEC951447AB2B3D07D4BCA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1664,24 +1703,27 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 2C50632AB476A038AFCB1D43 /* [Expo] Configure project */ = { + 39E6E20ED4D99EB6D79F1BCA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "[Expo] Configure project"; + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-ShareRocketChatRN/expo-configure-project.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; }; 407D3EDE3DABEE15D27BD87D /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -1739,28 +1781,6 @@ shellPath = /bin/sh; shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-Rocket.Chat/expo-configure-project.sh\"\n"; }; - 70428352B0FB6FE6DF9D6032 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-Rocket.Chat-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 7A10288726B1D15200E47EF8 /* Upload source maps to Bugsnag */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1866,68 +1886,46 @@ shellPath = /bin/sh; shellScript = "SOURCE_MAP=\"$TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\" ../node_modules/@bugsnag/react-native/bugsnag-react-native-xcode.sh\n"; }; - 86A998705576AFA7CE938617 /* [Expo] Configure project */ = { + 81C37052831D3F058FF2B0D4 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); - name = "[Expo] Configure project"; - outputFileListPaths = ( - ); + name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-NotificationService/expo-configure-project.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh\"\n"; + showEnvVarsInLog = 0; }; - 8C825AFA26B3E74DB80133EA /* [CP] Check Pods Manifest.lock */ = { + 86A998705576AFA7CE938617 /* [Expo] Configure project */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", ); - name = "[CP] Check Pods Manifest.lock"; + name = "[Expo] Configure project"; outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-RocketChatRN-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 9BE1CEB599E7DFFA7C004B9F /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-NotificationService/expo-configure-project.sh\"\n"; }; - 9D65115AE1DFF56CD734F04F /* [CP] Copy Pods Resources */ = { + 88DC6B30E9DF0B361032CC1B /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -2003,13 +2001,13 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh\"\n"; showEnvVarsInLog = 0; }; - A0BF2C82F66B14B8F59589FE /* [CP] Copy Pods Resources */ = { + 9D37CD62B427968B0C843657 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", @@ -2076,16 +2074,38 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + A7C23C0F63F7E514868AE885 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-defaults-Rocket.Chat-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - BACE710E33AE584582B19BD0 /* [CP] Copy Pods Resources */ = { + BA9071ABCBC2655C0EF5124C /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", @@ -2152,16 +2172,16 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh\"\n"; showEnvVarsInLog = 0; }; - E4BD2A0C2451C64208B69644 /* [CP] Copy Pods Resources */ = { + BD50494BAB9A4D228C650E81 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", @@ -2228,27 +2248,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - F973C9D58CCED6F67ADA2E0E /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -2628,7 +2628,7 @@ /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B0E90AAFBA1A95385AE7C19D /* Pods-defaults-RocketChatRN.debug.xcconfig */; + baseConfigurationReference = 307B625411539125ECBAA6EA /* Pods-defaults-RocketChatRN.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2689,7 +2689,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3AA8F7DEB0B31A5A6329FAF5 /* Pods-defaults-RocketChatRN.release.xcconfig */; + baseConfigurationReference = D944D97B3EF00C39202CC81D /* Pods-defaults-RocketChatRN.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2750,7 +2750,7 @@ }; 1EC6ACBC22CB9FC300A41C61 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AF2F39716BB7E569448DF293 /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */; + baseConfigurationReference = 1525225C45AF517E68DCDC2D /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; APPLICATION_EXTENSION_API_ONLY = YES; @@ -2826,7 +2826,7 @@ }; 1EC6ACBD22CB9FC300A41C61 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C2909259346452596AD27AD5 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */; + baseConfigurationReference = B41392725C2EA46A36F1FFB1 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; APPLICATION_EXTENSION_API_ONLY = YES; @@ -3110,7 +3110,7 @@ }; 1EFEB59D2493B6640072EDC0 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E2836E70B71C86AAA7EEB0F6 /* Pods-defaults-NotificationService.debug.xcconfig */; + baseConfigurationReference = B5F46E5C11E602B3F99D4E40 /* Pods-defaults-NotificationService.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; CLANG_ANALYZER_NONNULL = YES; @@ -3152,7 +3152,7 @@ }; 1EFEB59E2493B6640072EDC0 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B1C2FD3D7D6F056737CCEAE0 /* Pods-defaults-NotificationService.release.xcconfig */; + baseConfigurationReference = 628DE8E64E84BCF109FC4969 /* Pods-defaults-NotificationService.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; CLANG_ANALYZER_NONNULL = YES; @@ -3195,7 +3195,7 @@ }; 7AAB3E50257E6A6E00707CF6 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FE7279F2D432C9CAB7379496 /* Pods-defaults-Rocket.Chat.debug.xcconfig */; + baseConfigurationReference = 17115A1EE86DB89DBE1912EB /* Pods-defaults-Rocket.Chat.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -3255,7 +3255,7 @@ }; 7AAB3E51257E6A6E00707CF6 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0D20E5D2078C79720E1DBC03 /* Pods-defaults-Rocket.Chat.release.xcconfig */; + baseConfigurationReference = 7BEF39A6613B871D0A3326E1 /* Pods-defaults-Rocket.Chat.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; diff --git a/package.json b/package.json index c05c81786f..ea5c37c2fd 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "react-native-safe-area-context": "3.2.0", "react-native-screens": "3.29.0", "react-native-scrollable-tab-view": "ptomasroos/react-native-scrollable-tab-view", - "react-native-simple-crypto": "RocketChat/react-native-simple-crypto#feat.file-encryption", + "react-native-simple-crypto": "RocketChat/react-native-simple-crypto#fix.6.1.0", "react-native-skeleton-placeholder": "5.2.4", "react-native-slowlog": "1.0.2", "react-native-svg": "13.8.0", diff --git a/patches/react-native-simple-crypto+0.6.0.patch b/patches/react-native-simple-crypto+0.6.0.patch deleted file mode 100644 index 7cf2d18ff3..0000000000 --- a/patches/react-native-simple-crypto+0.6.0.patch +++ /dev/null @@ -1,293 +0,0 @@ -diff --git a/node_modules/react-native-simple-crypto/android/src/main/java/com/pedrouid/crypto/RCTCryptoPackage.java b/node_modules/react-native-simple-crypto/android/src/main/java/com/pedrouid/crypto/RCTCryptoPackage.java -index 2ee3cb4..1288fd6 100644 ---- a/node_modules/react-native-simple-crypto/android/src/main/java/com/pedrouid/crypto/RCTCryptoPackage.java -+++ b/node_modules/react-native-simple-crypto/android/src/main/java/com/pedrouid/crypto/RCTCryptoPackage.java -@@ -21,7 +21,8 @@ public class RCTCryptoPackage implements ReactPackage { - new RCTPbkdf2(reactContext), - new RCTRsa(reactContext), - new RCTRsaUtils(reactContext), -- new RandomBytesModule(reactContext) -+ new RandomBytesModule(reactContext), -+ new Util(reactContext) - ); - } - -diff --git a/node_modules/react-native-simple-crypto/android/src/main/java/com/pedrouid/crypto/Util.java b/node_modules/react-native-simple-crypto/android/src/main/java/com/pedrouid/crypto/Util.java -index caba3b5..32d480d 100644 ---- a/node_modules/react-native-simple-crypto/android/src/main/java/com/pedrouid/crypto/Util.java -+++ b/node_modules/react-native-simple-crypto/android/src/main/java/com/pedrouid/crypto/Util.java -@@ -3,11 +3,37 @@ package com.pedrouid.crypto; - import android.content.Context; - import android.net.Uri; - -+import com.facebook.react.bridge.Promise; -+import com.facebook.react.bridge.ReactApplicationContext; -+import com.facebook.react.bridge.ReactContextBaseJavaModule; -+import com.facebook.react.bridge.ReactMethod; -+ - import java.io.File; - import java.io.FileInputStream; - import java.io.InputStream; -+import java.security.MessageDigest; -+ -+public class Util extends ReactContextBaseJavaModule { -+ -+ public Util(ReactApplicationContext reactContext) { -+ super(reactContext); -+ } -+ -+ @Override -+ public String getName() { -+ return "Shared"; -+ } -+ -+ @ReactMethod -+ public void calculateFileChecksum(String filePath, Promise promise) { -+ try { -+ String result = calculateFileChecksum(getReactApplicationContext(),filePath ); -+ promise.resolve(result); -+ } catch (Exception e) { -+ promise.reject("-1", e.getMessage()); -+ } -+ } - --public class Util { - public static String bytesToHex(byte[] bytes) { - final char[] hexArray = "0123456789abcdef".toCharArray(); - char[] hexChars = new char[bytes.length * 2]; -@@ -39,4 +65,17 @@ public class Util { - return new FileInputStream(new File(inputFile)); // Handle plain file paths - } - } -+ -+ public static String calculateFileChecksum(Context context, String filePath) throws Exception { -+ InputStream inputStream = getInputStream(context, filePath); -+ MessageDigest digest = MessageDigest.getInstance("SHA-256"); -+ byte[] buffer = new byte[4096]; -+ int bytesRead; -+ while ((bytesRead = inputStream.read(buffer)) != -1) { -+ digest.update(buffer, 0, bytesRead); -+ } -+ inputStream.close(); -+ byte[] hash = digest.digest(); -+ return bytesToHex(hash); -+ } - } -diff --git a/node_modules/react-native-simple-crypto/index.d.ts b/node_modules/react-native-simple-crypto/index.d.ts -index 079c397..852c5c9 100644 ---- a/node_modules/react-native-simple-crypto/index.d.ts -+++ b/node_modules/react-native-simple-crypto/index.d.ts -@@ -77,6 +77,7 @@ declare module "react-native-simple-crypto" { - - export namespace utils { - export function randomBytes(bytes: number): Promise; -+ export function calculateFileChecksum(filePath: string): Promise; - export function convertArrayBufferToUtf8(input: ArrayBuffer): string; - export function convertUtf8ToArrayBuffer(input: string): ArrayBuffer; - export function convertArrayBufferToBase64(input: ArrayBuffer): string; -diff --git a/node_modules/react-native-simple-crypto/index.js b/node_modules/react-native-simple-crypto/index.js -index 6d4ed10..fb39cc6 100644 ---- a/node_modules/react-native-simple-crypto/index.js -+++ b/node_modules/react-native-simple-crypto/index.js -@@ -69,6 +69,10 @@ async function randomBytes(length) { - return convertBase64ToArrayBuffer(await NativeModules.RNRandomBytes.randomBytes(length)); - } - -+async function calculateFileChecksum(filePath) { -+ return NativeModules.Shared.calculateFileChecksum(filePath); -+} -+ - async function SHAWrapper(data, algorithm) { - if (typeof data === 'string') { - return NativeModules.Sha.shaUtf8(data, algorithm); -@@ -148,6 +152,7 @@ const RSA = { - - const utils = { - randomBytes, -+ calculateFileChecksum, - convertArrayBufferToUtf8, - convertUtf8ToArrayBuffer, - convertArrayBufferToBase64, -diff --git a/node_modules/react-native-simple-crypto/ios/.DS_Store b/node_modules/react-native-simple-crypto/ios/.DS_Store -new file mode 100644 -index 0000000..371b069 -Binary files /dev/null and b/node_modules/react-native-simple-crypto/ios/.DS_Store differ -diff --git a/node_modules/react-native-simple-crypto/ios/RCTCrypto/RCTShared.h b/node_modules/react-native-simple-crypto/ios/RCTCrypto/RCTShared.h -new file mode 100644 -index 0000000..55b52d1 ---- /dev/null -+++ b/node_modules/react-native-simple-crypto/ios/RCTCrypto/RCTShared.h -@@ -0,0 +1,5 @@ -+#import -+ -+@interface RCTShared : NSObject -+ -+@end -diff --git a/node_modules/react-native-simple-crypto/ios/RCTCrypto/RCTShared.m b/node_modules/react-native-simple-crypto/ios/RCTCrypto/RCTShared.m -new file mode 100644 -index 0000000..c011235 ---- /dev/null -+++ b/node_modules/react-native-simple-crypto/ios/RCTCrypto/RCTShared.m -@@ -0,0 +1,18 @@ -+#import "RCTShared.h" -+#import "Shared.h" -+ -+@implementation RCTShared -+ -+RCT_EXPORT_MODULE() -+ -+RCT_EXPORT_METHOD(calculateFileChecksum:(NSString *)filePath resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { -+ NSError *error = nil; -+ NSString *data = [Shared calculateFileChecksum:filePath]; -+ if (data == nil) { -+ reject(@"shared_checksum_fail", @"Checksum error", error); -+ } else { -+ resolve(data); -+ } -+} -+ -+@end -diff --git a/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Aes.m b/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Aes.m -index 067d962..b24e4dd 100644 ---- a/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Aes.m -+++ b/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Aes.m -@@ -55,7 +55,7 @@ - - NSString *normalizedFilePath = [filePath stringByReplacingOccurrencesOfString:@"file://" withString:@""]; - NSString *outputFileName = [@"processed_" stringByAppendingString:[normalizedFilePath lastPathComponent]]; -- NSString *outputFilePath = [[[normalizedFilePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:outputFileName] stringByDeletingPathExtension]; -+ NSString *outputFilePath = [[normalizedFilePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:outputFileName]; - NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:normalizedFilePath]; - NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:outputFilePath append:NO]; - [inputStream open]; -@@ -67,6 +67,8 @@ - CCCryptorStatus status = CCCryptorCreateWithMode(operation, kCCModeCTR, kCCAlgorithmAES, ccNoPadding, ivData.bytes, keyData.bytes, keyData.length, NULL, 0, 0, kCCModeOptionCTR_BE, &cryptor); - if (status != kCCSuccess) { - NSLog(@"Failed to create cryptor: %d", status); -+ [inputStream close]; -+ [outputStream close]; - return nil; - } - -@@ -79,8 +81,25 @@ - [outputStream write:buffer maxLength:dataOutMoved]; - } else { - NSLog(@"Cryptor update failed: %d", status); -+ return nil; - break; - } -+ } else if (bytesRead < 0) { -+ NSLog(@"Input stream read error"); -+ status = kCCDecodeError; -+ return nil; -+ break; -+ } -+ } -+ -+ if (status == kCCSuccess) { -+ size_t finalBytesOut; -+ status = CCCryptorFinal(cryptor, buffer, bufferSize, &finalBytesOut); -+ if (status == kCCSuccess) { -+ [outputStream write:buffer maxLength:finalBytesOut]; -+ } else { -+ NSLog(@"Cryptor final failed: %d", status); -+ return nil; - } - } - -@@ -89,15 +108,20 @@ - [outputStream close]; - - if (status == kCCSuccess) { -- if (operation == kCCDecrypt) { -- NSFileManager *fileManager = [NSFileManager defaultManager]; -- // Overwrite the input file with the decrypted file -- [fileManager removeItemAtPath:normalizedFilePath error:nil]; -- [fileManager moveItemAtPath:outputFilePath toPath:normalizedFilePath error:nil]; -- return [NSString stringWithFormat:@"file://%@", normalizedFilePath]; -- } else { -- return [NSString stringWithFormat:@"file://%@", outputFilePath]; -+ NSURL *originalFileURL = [NSURL fileURLWithPath:normalizedFilePath]; -+ NSURL *outputFileURL = [NSURL fileURLWithPath:outputFilePath]; -+ NSError *error = nil; -+ [[NSFileManager defaultManager] replaceItemAtURL:originalFileURL -+ withItemAtURL:outputFileURL -+ backupItemName:nil -+ options:NSFileManagerItemReplacementUsingNewMetadataOnly -+ resultingItemURL:nil -+ error:&error]; -+ if (error) { -+ NSLog(@"Failed to replace original file: %@", error); -+ return nil; - } -+ return [NSString stringWithFormat:@"file://%@", normalizedFilePath]; - } else { - // Clean up temp file in case of failure - [[NSFileManager defaultManager] removeItemAtPath:outputFilePath error:nil]; -@@ -105,7 +129,6 @@ - } - } - -- - + (NSString *)encryptFile:(NSString *)filePath key:(NSString *)key iv:(NSString *)iv { - return [self processFile:filePath operation:kCCEncrypt key:key iv:iv]; - } -diff --git a/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Shared.h b/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Shared.h -index 398444b..0e8e078 100644 ---- a/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Shared.h -+++ b/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Shared.h -@@ -4,6 +4,5 @@ - + (NSString *) toHex: (NSData *)nsdata; - + (NSData *) fromHex: (NSString *)string; - + (NSString *)base64FromBase64URL:(NSString *)base64URL; --+ (NSString *)normalizeFilePath:(NSString *)filePath; --+ (NSString *)restoreFilePathSchemeIfNeeded:(NSString *)filePath originalPath:(NSString *)originalPath; -++ (NSString *)calculateFileChecksum:(NSString *)filePath; - @end -diff --git a/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Shared.m b/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Shared.m -index e97098b..a52d03b 100644 ---- a/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Shared.m -+++ b/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Shared.m -@@ -41,4 +41,42 @@ - return base64; - } - -++ (NSString *)calculateFileChecksum:(NSString *)filePath { -+ NSString *normalizedFilePath = [filePath stringByReplacingOccurrencesOfString:@"file://" withString:@""]; -+ NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:normalizedFilePath]; -+ [inputStream open]; -+ -+ if (!inputStream) { -+ NSLog(@"Failed to open file: %@", filePath); -+ return nil; -+ } -+ -+ CC_SHA256_CTX sha256; -+ CC_SHA256_Init(&sha256); -+ -+ uint8_t buffer[4096]; -+ NSInteger bytesRead = 0; -+ -+ while ((bytesRead = [inputStream read:buffer maxLength:sizeof(buffer)]) > 0) { -+ CC_SHA256_Update(&sha256, buffer, (CC_LONG)bytesRead); -+ } -+ -+ [inputStream close]; -+ -+ if (bytesRead < 0) { -+ NSLog(@"File read error: %@", filePath); -+ return nil; -+ } -+ -+ unsigned char hash[CC_SHA256_DIGEST_LENGTH]; -+ CC_SHA256_Final(hash, &sha256); -+ -+ NSMutableString *checksum = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; -+ for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { -+ [checksum appendFormat:@"%02x", hash[i]]; -+ } -+ -+ return checksum; -+} -+ - @end diff --git a/yarn.lock b/yarn.lock index f6558eefe2..35cb65a7ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12182,9 +12182,9 @@ react-native-scrollable-tab-view@ptomasroos/react-native-scrollable-tab-view: prop-types "^15.6.0" react-timer-mixin "^0.13.3" -react-native-simple-crypto@RocketChat/react-native-simple-crypto#feat.file-encryption: - version "0.6.0" - resolved "https://codeload.github.com/RocketChat/react-native-simple-crypto/tar.gz/476f0d2750abc1a9e74879ac3bacc7bec753c476" +react-native-simple-crypto@RocketChat/react-native-simple-crypto#fix.6.1.0: + version "0.6.1" + resolved "https://codeload.github.com/RocketChat/react-native-simple-crypto/tar.gz/467905c61df8132e2257b6408a072521fc5a3d27" dependencies: base64-js "^1.3.0" hex-lite "^1.5.0" From 1b7ab31c73cd4216d75f8d6b8cb18f681ae09d20 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 30 Jul 2024 09:29:59 -0300 Subject: [PATCH 04/26] fix: Media autodownload not working properly (#5807) --- app/containers/UIKit/Image.tsx | 3 +- .../message/Components/Attachments/Audio.tsx | 117 +------- .../message/Components/Attachments/Image.tsx | 251 ------------------ .../Components/Attachments/Image/Button.tsx | 24 ++ .../Attachments/Image/Container.tsx | 45 ++++ .../Components/Attachments/Image/Image.tsx | 45 ++++ .../Attachments/Image/definitions.ts | 21 ++ .../Components/Attachments/Image/index.tsx | 4 + .../message/Components/Attachments/Video.tsx | 180 +++---------- app/containers/message/hooks/useAudioUrl.tsx | 25 -- app/containers/message/hooks/useFile.tsx | 10 +- .../message/hooks/useMediaAutoDownload.tsx | 165 ++++++++++++ app/i18n/locales/en.json | 2 +- app/lib/constants/mediaAutoDownload.ts | 2 +- app/lib/encryption/encryption.ts | 47 ++-- app/lib/methods/autoDownloadPreference.ts | 6 +- app/lib/methods/handleMediaDownload.ts | 58 +++- app/lib/methods/loadThreadMessages.ts | 2 + app/views/MediaAutoDownloadView/index.tsx | 6 +- 19 files changed, 433 insertions(+), 580 deletions(-) delete mode 100644 app/containers/message/Components/Attachments/Image.tsx create mode 100644 app/containers/message/Components/Attachments/Image/Button.tsx create mode 100644 app/containers/message/Components/Attachments/Image/Container.tsx create mode 100644 app/containers/message/Components/Attachments/Image/Image.tsx create mode 100644 app/containers/message/Components/Attachments/Image/definitions.ts create mode 100644 app/containers/message/Components/Attachments/Image/index.tsx delete mode 100644 app/containers/message/hooks/useAudioUrl.tsx create mode 100644 app/containers/message/hooks/useMediaAutoDownload.tsx diff --git a/app/containers/UIKit/Image.tsx b/app/containers/UIKit/Image.tsx index 96c57a9315..6bcc84a6b5 100644 --- a/app/containers/UIKit/Image.tsx +++ b/app/containers/UIKit/Image.tsx @@ -30,8 +30,7 @@ export const Thumb = ({ element, size = 88 }: IThumb) => ( export const Media = ({ element }: IImage) => { const showAttachment = (attachment: IAttachment) => Navigation.navigate('AttachmentView', { attachment }); const imageUrl = element?.imageUrl ?? ''; - - return ; + return ; }; const genericImage = (element: IElement, context?: number) => { diff --git a/app/containers/message/Components/Attachments/Audio.tsx b/app/containers/message/Components/Attachments/Audio.tsx index bdb12fe520..f9f4e8830d 100644 --- a/app/containers/message/Components/Attachments/Audio.tsx +++ b/app/containers/message/Components/Attachments/Audio.tsx @@ -1,16 +1,12 @@ -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React, { useContext } from 'react'; import { StyleProp, TextStyle } from 'react-native'; -import { emitter } from '../../../../lib/methods/helpers'; -import Markdown from '../../../markdown'; -import MessageContext from '../../Context'; -import { TGetCustomEmoji } from '../../../../definitions/IEmoji'; import { IAttachment, IUserMessage } from '../../../../definitions'; -import { TDownloadState, downloadMediaFile, getMediaCache, isDownloadActive } from '../../../../lib/methods/handleMediaDownload'; -import { fetchAutoDownloadEnabled } from '../../../../lib/methods/autoDownloadPreference'; +import { TGetCustomEmoji } from '../../../../definitions/IEmoji'; import AudioPlayer from '../../../AudioPlayer'; -import { useAudioUrl } from '../../hooks/useAudioUrl'; -import { getAudioUrlToCache } from '../../../../lib/methods/getAudioUrl'; +import Markdown from '../../../markdown'; +import MessageContext from '../../Context'; +import { useMediaAutoDownload } from '../../hooks/useMediaAutoDownload'; interface IMessageAudioProps { file: IAttachment; @@ -19,113 +15,16 @@ interface IMessageAudioProps { getCustomEmoji: TGetCustomEmoji; author?: IUserMessage; msg?: string; - cdnPrefix?: string; } const MessageAudio = ({ file, getCustomEmoji, author, isReply, style, msg }: IMessageAudioProps) => { - const [downloadState, setDownloadState] = useState('loading'); - const [fileUri, setFileUri] = useState(''); - const { baseUrl, user, id, rid } = useContext(MessageContext); - const audioUrl = useAudioUrl({ audioUrl: file.audio_url }); - - const onPlayButtonPress = async () => { - if (downloadState === 'to-download') { - const isAudioCached = await handleGetMediaCache(); - if (isAudioCached) { - return; - } - handleDownload(); - } - }; - - const handleDownload = async () => { - setDownloadState('loading'); - try { - if (audioUrl) { - const audioUri = await downloadMediaFile({ - messageId: id, - downloadUrl: getAudioUrlToCache({ token: user.token, userId: user.id, url: audioUrl }), - type: 'audio', - mimeType: file.audio_type, - encryption: file.encryption, - originalChecksum: file.hashes?.sha256 - }); - setFileUri(audioUri); - setDownloadState('downloaded'); - } - } catch { - setDownloadState('to-download'); - } - }; - - const handleAutoDownload = async () => { - try { - if (audioUrl) { - const isCurrentUserAuthor = author?._id === user.id; - const isAutoDownloadEnabled = fetchAutoDownloadEnabled('audioPreferenceDownload'); - if (isAutoDownloadEnabled || isCurrentUserAuthor) { - await handleDownload(); - return; - } - setDownloadState('to-download'); - } - } catch { - // Do nothing - } - }; - - const handleGetMediaCache = async () => { - const cachedAudioResult = await getMediaCache({ - type: 'audio', - mimeType: file.audio_type, - urlToCache: audioUrl - }); - const result = cachedAudioResult?.exists && file.e2e !== 'pending'; - if (result) { - setFileUri(cachedAudioResult.uri); - setDownloadState('downloaded'); - } - return result; - }; - - const handleResumeDownload = () => { - emitter.on(`downloadMedia${id}`, downloadMediaListener); - }; - - useEffect(() => { - const handleCache = async () => { - const isAudioCached = await handleGetMediaCache(); - if (isAudioCached) { - return; - } - if (audioUrl && isDownloadActive(audioUrl)) { - handleResumeDownload(); - return; - } - await handleAutoDownload(); - }; - if (audioUrl) { - handleCache(); - } - }, [audioUrl]); - - const downloadMediaListener = useCallback((uri: string) => { - setFileUri(uri); - setDownloadState('downloaded'); - }, []); - - useEffect(() => () => { - emitter.off(`downloadMedia${id}`, downloadMediaListener); - }); - - if (!baseUrl) { - return null; - } + const { user, id, rid } = useContext(MessageContext); + const { status, onPress, url } = useMediaAutoDownload({ file, author }); return ( <> - + ); }; diff --git a/app/containers/message/Components/Attachments/Image.tsx b/app/containers/message/Components/Attachments/Image.tsx deleted file mode 100644 index 2fc9cf2ff0..0000000000 --- a/app/containers/message/Components/Attachments/Image.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import React, { useCallback, useContext, useEffect, useState } from 'react'; -import { StyleProp, TextStyle, View } from 'react-native'; -import FastImage from 'react-native-fast-image'; - -import { emitter } from '../../../../lib/methods/helpers'; -import { IAttachment, IUserMessage } from '../../../../definitions'; -import { TGetCustomEmoji } from '../../../../definitions/IEmoji'; -import { fetchAutoDownloadEnabled } from '../../../../lib/methods/autoDownloadPreference'; -import { cancelDownload, downloadMediaFile, getMediaCache, isDownloadActive } from '../../../../lib/methods/handleMediaDownload'; -import { formatAttachmentUrl } from '../../../../lib/methods/helpers/formatAttachmentUrl'; -import { useTheme } from '../../../../theme'; -import Markdown from '../../../markdown'; -import BlurComponent from '../OverlayComponent'; -import MessageContext from '../../Context'; -import Touchable from '../../Touchable'; -import styles from '../../styles'; -import { isImageBase64 } from '../../../../lib/methods'; -import { isValidUrl } from '../../../../lib/methods/helpers/isValidUrl'; -import { useFile } from '../../hooks/useFile'; - -interface IMessageButton { - children: React.ReactElement; - disabled?: boolean; - onPress: () => void; -} - -interface IMessageImage { - file: IAttachment; - imageUrl?: string; - showAttachment?: (file: IAttachment) => void; - style?: StyleProp[]; - isReply?: boolean; - getCustomEmoji?: TGetCustomEmoji; - author?: IUserMessage; - msg?: string; -} - -const Button = React.memo(({ children, onPress, disabled }: IMessageButton) => { - const { colors } = useTheme(); - return ( - - {children} - - ); -}); - -export const MessageImage = React.memo( - ({ imgUri, cached, loading, encrypted = false }: { imgUri: string; cached: boolean; loading: boolean; encrypted: boolean }) => { - const { colors } = useTheme(); - const valid = isValidUrl(imgUri); - - if (encrypted && !loading && cached) { - return ( - <> - - - - ); - } - - return ( - <> - {valid ? ( - - ) : ( - - )} - {!cached ? ( - - ) : null} - - ); - } -); - -const ImageContainer = ({ - file, - imageUrl, - showAttachment, - getCustomEmoji, - style, - isReply, - author, - msg -}: IMessageImage): React.ReactElement | null => { - const { id, baseUrl, user } = useContext(MessageContext); - const [imageCached, setImageCached] = useFile(file, id); - const [cached, setCached] = useState(false); - const [loading, setLoading] = useState(true); - const { theme } = useTheme(); - const getUrl = (link?: string) => imageUrl || formatAttachmentUrl(link, user.id, user.token, baseUrl); - const img = getUrl(file.image_url); - // The param file.title_link is the one that point to image with best quality, however we still need to test the imageUrl - // And we cannot be certain whether the file.title_link actually exists. - const imgUrlToCache = getUrl(imageCached.title_link || imageCached.image_url); - - useEffect(() => { - const handleCache = async () => { - if (img) { - const isImageCached = await handleGetMediaCache(); - if (isImageCached) { - return; - } - if (isDownloadActive(imgUrlToCache)) { - handleResumeDownload(); - return; - } - await handleAutoDownload(); - setLoading(false); - } - }; - if (isImageBase64(imgUrlToCache)) { - setLoading(false); - setCached(true); - } else { - handleCache(); - } - - return () => { - emitter.off(`downloadMedia${id}`, downloadMediaListener); - }; - }, []); - - const downloadMediaListener = useCallback((imageUri: string) => { - updateImageCached(imageUri); - }, []); - - if (!img) { - return null; - } - - const handleAutoDownload = async () => { - const isCurrentUserAuthor = author?._id === user.id; - const isAutoDownloadEnabled = fetchAutoDownloadEnabled('imagesPreferenceDownload'); - if (isAutoDownloadEnabled || isCurrentUserAuthor) { - await handleDownload(); - } - }; - - const updateImageCached = (imgUri: string) => { - setImageCached({ - title_link: imgUri - }); - setCached(true); - }; - - const setDecrypted = () => { - if (imageCached.e2e === 'pending') { - setImageCached({ - e2e: 'done' - }); - } - }; - - const handleGetMediaCache = async () => { - const cachedImageResult = await getMediaCache({ - type: 'image', - mimeType: imageCached.image_type, - urlToCache: imgUrlToCache - }); - const result = !!cachedImageResult?.exists && imageCached.e2e !== 'pending'; - if (result) { - updateImageCached(cachedImageResult.uri); - } - return result; - }; - - const handleResumeDownload = () => { - emitter.on(`downloadMedia${id}`, downloadMediaListener); - }; - - const handleDownload = async () => { - try { - const imageUri = await downloadMediaFile({ - messageId: id, - downloadUrl: imgUrlToCache, - type: 'image', - mimeType: imageCached.image_type, - encryption: file.encryption, - originalChecksum: file.hashes?.sha256 - }); - setDecrypted(); - updateImageCached(imageUri); - } catch (e) { - setCached(false); - setLoading(false); - } - }; - - const onPress = async () => { - if (loading && isDownloadActive(imgUrlToCache)) { - cancelDownload(imgUrlToCache); - setLoading(false); - setCached(false); - return; - } - if (!cached && !loading) { - const isImageCached = await handleGetMediaCache(); - if (isImageCached && showAttachment) { - showAttachment(imageCached); - return; - } - if (isDownloadActive(imgUrlToCache)) { - handleResumeDownload(); - return; - } - setLoading(true); - handleDownload(); - return; - } - if (!showAttachment || !imageCached.title_link) { - return; - } - showAttachment(imageCached); - }; - - const image = ( - - ); - - if (msg) { - return ( - - - {image} - - ); - } - - return image; -}; - -ImageContainer.displayName = 'MessageImageContainer'; -MessageImage.displayName = 'MessageImage'; - -export default ImageContainer; diff --git a/app/containers/message/Components/Attachments/Image/Button.tsx b/app/containers/message/Components/Attachments/Image/Button.tsx new file mode 100644 index 0000000000..452bfaf15d --- /dev/null +++ b/app/containers/message/Components/Attachments/Image/Button.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +import { useTheme } from '../../../../../theme'; +import Touchable from '../../../Touchable'; +import styles from '../../../styles'; + +interface IMessageButton { + children: React.ReactElement; + disabled?: boolean; + onPress: () => void; +} + +export const Button = ({ children, onPress, disabled }: IMessageButton) => { + const { colors } = useTheme(); + return ( + + {children} + + ); +}; diff --git a/app/containers/message/Components/Attachments/Image/Container.tsx b/app/containers/message/Components/Attachments/Image/Container.tsx new file mode 100644 index 0000000000..abee3b5969 --- /dev/null +++ b/app/containers/message/Components/Attachments/Image/Container.tsx @@ -0,0 +1,45 @@ +import React, { useContext } from 'react'; +import { View } from 'react-native'; + +import { useTheme } from '../../../../../theme'; +import Markdown from '../../../../markdown'; +import { useMediaAutoDownload } from '../../../hooks/useMediaAutoDownload'; +import { Button } from './Button'; +import { MessageImage } from './Image'; +import { IImageContainer } from './definitions'; +import MessageContext from '../../../Context'; + +const ImageContainer = ({ + file, + showAttachment, + getCustomEmoji, + style, + isReply, + author, + msg +}: IImageContainer): React.ReactElement | null => { + const { user } = useContext(MessageContext); + const { theme } = useTheme(); + const { status, onPress, url, isEncrypted } = useMediaAutoDownload({ file, author, showAttachment }); + + const image = ( + + ); + + if (msg) { + return ( + + + {image} + + ); + } + + return image; +}; + +ImageContainer.displayName = 'MessageImageContainer'; + +export default ImageContainer; diff --git a/app/containers/message/Components/Attachments/Image/Image.tsx b/app/containers/message/Components/Attachments/Image/Image.tsx new file mode 100644 index 0000000000..45518908ee --- /dev/null +++ b/app/containers/message/Components/Attachments/Image/Image.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { View } from 'react-native'; +import FastImage from 'react-native-fast-image'; + +import { isValidUrl } from '../../../../../lib/methods/helpers/isValidUrl'; +import { useTheme } from '../../../../../theme'; +import styles from '../../../styles'; +import OverlayComponent from '../../OverlayComponent'; +import { IMessageImage } from './definitions'; + +export const MessageImage = React.memo(({ uri, status, encrypted = false }: IMessageImage) => { + const { colors } = useTheme(); + + if (encrypted && status === 'downloaded') { + return ( + <> + + + + ); + } + + return ( + <> + {isValidUrl(uri) && status === 'downloaded' ? ( + + ) : ( + + )} + {['loading', 'to-download'].includes(status) ? ( + + ) : null} + + ); +}); + +MessageImage.displayName = 'MessageImage'; diff --git a/app/containers/message/Components/Attachments/Image/definitions.ts b/app/containers/message/Components/Attachments/Image/definitions.ts new file mode 100644 index 0000000000..7dbb6ac4c8 --- /dev/null +++ b/app/containers/message/Components/Attachments/Image/definitions.ts @@ -0,0 +1,21 @@ +import { StyleProp, TextStyle } from 'react-native'; + +import { IAttachment, IUserMessage } from '../../../../../definitions'; +import { TGetCustomEmoji } from '../../../../../definitions/IEmoji'; +import { TDownloadState } from '../../../../../lib/methods/handleMediaDownload'; + +export interface IImageContainer { + file: IAttachment; + showAttachment?: (file: IAttachment) => void; + style?: StyleProp[]; + isReply?: boolean; + getCustomEmoji?: TGetCustomEmoji; + author?: IUserMessage; + msg?: string; +} + +export interface IMessageImage { + uri: string; + status: TDownloadState; + encrypted: boolean; +} diff --git a/app/containers/message/Components/Attachments/Image/index.tsx b/app/containers/message/Components/Attachments/Image/index.tsx new file mode 100644 index 0000000000..c1eb3a8441 --- /dev/null +++ b/app/containers/message/Components/Attachments/Image/index.tsx @@ -0,0 +1,4 @@ +import Container from './Container'; + +export * from './Image'; +export default Container; diff --git a/app/containers/message/Components/Attachments/Video.tsx b/app/containers/message/Components/Attachments/Video.tsx index 42c432db83..c114ae6885 100644 --- a/app/containers/message/Components/Attachments/Video.tsx +++ b/app/containers/message/Components/Attachments/Video.tsx @@ -1,26 +1,24 @@ -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React, { useContext } from 'react'; import { StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native'; +import { IUserMessage } from '../../../../definitions'; import { IAttachment } from '../../../../definitions/IAttachment'; import { TGetCustomEmoji } from '../../../../definitions/IEmoji'; import I18n from '../../../../i18n'; import { themes } from '../../../../lib/constants'; -import { fetchAutoDownloadEnabled } from '../../../../lib/methods/autoDownloadPreference'; -import { cancelDownload, downloadMediaFile, getMediaCache, isDownloadActive } from '../../../../lib/methods/handleMediaDownload'; -import { emitter, fileDownload, isIOS } from '../../../../lib/methods/helpers'; +import { fileDownload, isIOS } from '../../../../lib/methods/helpers'; import EventEmitter from '../../../../lib/methods/helpers/events'; -import { formatAttachmentUrl } from '../../../../lib/methods/helpers/formatAttachmentUrl'; import { useTheme } from '../../../../theme'; import sharedStyles from '../../../../views/Styles'; +import { TIconsName } from '../../../CustomIcon'; import { LISTENER } from '../../../Toast'; import Markdown from '../../../markdown'; -import BlurComponent from '../OverlayComponent'; import MessageContext from '../../Context'; import Touchable from '../../Touchable'; +import { useMediaAutoDownload } from '../../hooks/useMediaAutoDownload'; import { DEFAULT_MESSAGE_HEIGHT } from '../../utils'; -import { TIconsName } from '../../../CustomIcon'; -import { useFile } from '../../hooks/useFile'; -import { IUserMessage } from '../../../../definitions'; +import BlurComponent from '../OverlayComponent'; +import { TDownloadState } from '../../../../lib/methods/handleMediaDownload'; const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])]; const isTypeSupported = (type: string) => SUPPORTED_TYPES.indexOf(type) !== -1; @@ -64,16 +62,16 @@ const CancelIndicator = () => { ); }; -const Thumbnail = ({ loading, cached, encrypted = false }: { loading: boolean; cached: boolean; encrypted: boolean }) => { - let icon: TIconsName = cached ? 'play-filled' : 'arrow-down-circle'; - if (encrypted && !loading && cached) { +const Thumbnail = ({ status, encrypted = false }: { status: TDownloadState; encrypted: boolean }) => { + let icon: TIconsName = status === 'downloaded' ? 'play-filled' : 'arrow-down-circle'; + if (encrypted && status === 'downloaded') { icon = 'encrypted'; } return ( <> - - {loading ? : null} + + {status === 'loading' ? : null} ); }; @@ -87,159 +85,41 @@ const Video = ({ isReply, msg }: IMessageVideo): React.ReactElement | null => { - const { id, baseUrl, user } = useContext(MessageContext); - const [videoCached, setVideoCached] = useFile(file, id); - const [loading, setLoading] = useState(true); - const [cached, setCached] = useState(false); + const { user } = useContext(MessageContext); const { theme } = useTheme(); - const video = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl); - - useEffect(() => { - const handleVideoSearchAndDownload = async () => { - if (video) { - const isVideoCached = await handleGetMediaCache(); - if (isVideoCached) { - return; - } - if (isDownloadActive(video)) { - handleResumeDownload(); - return; - } - await handleAutoDownload(); - setLoading(false); + const { status, onPress, url, isEncrypted, currentFile } = useMediaAutoDownload({ file, author, showAttachment }); + + const _onPress = async () => { + if (currentFile.video_type && !isTypeSupported(currentFile.video_type)) { + if (isIOS) { + EventEmitter.emit(LISTENER, { message: I18n.t('Unsupported_format') }); + } else { + await downloadVideoToGallery(url); } - }; - handleVideoSearchAndDownload(); - - return () => { - emitter.off(`downloadMedia${id}`, downloadMediaListener); - }; - }, []); - - const downloadMediaListener = useCallback((uri: string) => { - updateVideoCached(uri); - setLoading(false); - }, []); - - if (!baseUrl) { - return null; - } - - const handleAutoDownload = async () => { - const isCurrentUserAuthor = author?._id === user.id; - const isAutoDownloadEnabled = fetchAutoDownloadEnabled('videoPreferenceDownload'); - if ((isAutoDownloadEnabled || isCurrentUserAuthor) && file.video_type && isTypeSupported(file.video_type)) { - await handleDownload(); return; } - setLoading(false); - }; - - const updateVideoCached = (videoUri: string) => { - setVideoCached({ video_url: videoUri }); - setCached(true); - setLoading(false); - }; - - const setDecrypted = () => { - if (videoCached.e2e === 'pending') { - setVideoCached({ - e2e: 'done' - }); - } - }; - - const handleGetMediaCache = async () => { - const cachedVideoResult = await getMediaCache({ - type: 'video', - mimeType: file.video_type, - urlToCache: video - }); - const result = !!cachedVideoResult?.exists && videoCached.e2e !== 'pending'; - if (result) { - updateVideoCached(cachedVideoResult.uri); - } - return result; - }; - - const handleResumeDownload = () => { - emitter.on(`downloadMedia${id}`, downloadMediaListener); + onPress(); }; - const handleDownload = async () => { + const downloadVideoToGallery = async (uri: string) => { try { - const videoUri = await downloadMediaFile({ - messageId: id, - downloadUrl: video, - type: 'video', - mimeType: file.video_type, - encryption: file.encryption, - originalChecksum: file.hashes?.sha256 - }); - setDecrypted(); - updateVideoCached(videoUri); - } catch { - setCached(false); - } - }; - - const onPress = async () => { - if (file.video_type && cached && isTypeSupported(file.video_type) && showAttachment && videoCached.video_url) { - showAttachment(videoCached); - return; - } - if (!loading && !cached && file.video_type && isTypeSupported(file.video_type)) { - const isVideoCached = await handleGetMediaCache(); - if (isVideoCached && showAttachment && videoCached.video_url) { - showAttachment(videoCached); - return; + const fileDownloaded = await fileDownload(uri, file); + if (fileDownloaded) { + EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') }); } - if (isDownloadActive(video)) { - handleResumeDownload(); - return; - } - setLoading(true); - handleDownload(); - return; - } - if (loading && !cached) { - handleCancelDownload(); - return; - } - if (!isIOS && file.video_url) { - await downloadVideoToGallery(video); - return; - } - EventEmitter.emit(LISTENER, { message: I18n.t('Unsupported_format') }); - }; - - const handleCancelDownload = () => { - if (loading) { - cancelDownload(video); - setLoading(false); - } - }; - - const downloadVideoToGallery = async (uri: string) => { - setLoading(true); - const fileDownloaded = await fileDownload(uri, file); - setLoading(false); - - if (fileDownloaded) { - EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') }); - return; + } catch (error) { + EventEmitter.emit(LISTENER, { message: I18n.t('error-save-video') }); } - EventEmitter.emit(LISTENER, { message: I18n.t('error-save-video') }); }; return ( <> - + ); diff --git a/app/containers/message/hooks/useAudioUrl.tsx b/app/containers/message/hooks/useAudioUrl.tsx deleted file mode 100644 index dc20475c30..0000000000 --- a/app/containers/message/hooks/useAudioUrl.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useState, useEffect } from 'react'; - -import { useAppSelector } from '../../../lib/hooks'; -import { getAudioUrl } from '../../../lib/methods/getAudioUrl'; - -export const useAudioUrl = ({ audioUrl }: { audioUrl?: string }): string => { - const [filePath, setFilePath] = useState(''); - - const { cdnPrefix, baseUrl } = useAppSelector(state => ({ - cdnPrefix: state.settings.CDN_PREFIX as string, - baseUrl: state.server.server - })); - - useEffect(() => { - if (!audioUrl) { - return; - } - const url = getAudioUrl({ baseUrl, cdnPrefix, audioUrl }); - if (url) { - setFilePath(url); - } - }, [audioUrl, baseUrl, cdnPrefix]); - - return filePath; -}; diff --git a/app/containers/message/hooks/useFile.tsx b/app/containers/message/hooks/useFile.tsx index 2e5d34eb32..525b8b3eba 100644 --- a/app/containers/message/hooks/useFile.tsx +++ b/app/containers/message/hooks/useFile.tsx @@ -2,15 +2,19 @@ import { useEffect, useState } from 'react'; import { IAttachment } from '../../../definitions'; import { getMessageById } from '../../../lib/database/services/Message'; +import { getThreadMessageById } from '../../../lib/database/services/ThreadMessage'; export const useFile = (file: IAttachment, messageId: string) => { const [localFile, setLocalFile] = useState(file); const [isMessagePersisted, setIsMessagePersisted] = useState(!!messageId); useEffect(() => { const checkMessage = async () => { - const message = await getMessageById(messageId); - if (!message) { - setIsMessagePersisted(false); + const threadMessage = await getThreadMessageById(messageId); + if (!threadMessage) { + const message = await getMessageById(messageId); + if (!message) { + setIsMessagePersisted(false); + } } }; checkMessage(); diff --git a/app/containers/message/hooks/useMediaAutoDownload.tsx b/app/containers/message/hooks/useMediaAutoDownload.tsx new file mode 100644 index 0000000000..2d1aa5e25c --- /dev/null +++ b/app/containers/message/hooks/useMediaAutoDownload.tsx @@ -0,0 +1,165 @@ +import { useCallback, useContext, useEffect, useState } from 'react'; + +import { IAttachment, IUserMessage } from '../../../definitions'; +import { isImageBase64 } from '../../../lib/methods'; +import { fetchAutoDownloadEnabled } from '../../../lib/methods/autoDownloadPreference'; +import { + cancelDownload, + downloadMediaFile, + getMediaCache, + isDownloadActive, + MediaTypes, + TDownloadState +} from '../../../lib/methods/handleMediaDownload'; +import { emitter } from '../../../lib/methods/helpers'; +import { formatAttachmentUrl } from '../../../lib/methods/helpers/formatAttachmentUrl'; +import MessageContext from '../Context'; +import { useFile } from './useFile'; + +const getFileType = (file: IAttachment): MediaTypes | null => { + if (file.image_url) { + return 'image'; + } + if (file.video_url) { + return 'video'; + } + if (file.audio_url) { + return 'audio'; + } + return null; +}; + +const getFileProperty = (file: IAttachment, fileType: MediaTypes, property: 'url' | 'type') => { + if (fileType && file[`${fileType}_${property}`]) { + return file[`${fileType}_${property}`]; + } +}; + +export const useMediaAutoDownload = ({ + file, + author, + showAttachment +}: { + file: IAttachment; + author?: IUserMessage; + showAttachment?: Function; +}) => { + const fileType = getFileType(file) ?? 'image'; + const { id, baseUrl, user } = useContext(MessageContext); + const [status, setStatus] = useState('to-download'); + const [currentFile, setCurrentFile] = useFile(file, id); + const url = formatAttachmentUrl(file.title_link || getFileProperty(currentFile, fileType, 'url'), user.id, user.token, baseUrl); + const isEncrypted = currentFile.e2e === 'pending'; + + useEffect(() => { + const handleCache = async () => { + if (url) { + const isCached = await checkCache(); + if (isCached) { + return; + } + if (isDownloadActive(url)) { + resumeDownload(); + return; + } + await tryAutoDownload(); + } + }; + if (fileType === 'image' && isImageBase64(url)) { + setStatus('downloaded'); + } else { + handleCache(); + } + + return () => { + emitter.off(`downloadMedia${id}`, downloadMediaListener); + }; + }, []); + + const downloadMediaListener = useCallback((uri: string) => { + updateCurrentFile(uri); + }, []); + + const resumeDownload = () => { + emitter.on(`downloadMedia${id}`, downloadMediaListener); + }; + + const tryAutoDownload = async () => { + const isCurrentUserAuthor = author?._id === user.id; + const isAutoDownloadEnabled = fetchAutoDownloadEnabled(`${fileType}PreferenceDownload`); + if (isAutoDownloadEnabled || isCurrentUserAuthor) { + await download(); + } else { + setStatus('to-download'); + } + }; + + const download = async () => { + try { + setStatus('loading'); + const uri = await downloadMediaFile({ + messageId: id, + downloadUrl: url, + type: fileType, + mimeType: getFileProperty(currentFile, fileType, 'type'), + encryption: file.encryption, + originalChecksum: file.hashes?.sha256 + }); + setDecrypted(); + updateCurrentFile(uri); + } catch (e) { + setStatus('to-download'); + } + }; + + const updateCurrentFile = (uri: string) => { + setCurrentFile({ + title_link: uri + }); + setStatus('downloaded'); + }; + + const setDecrypted = () => { + if (isEncrypted) { + setCurrentFile({ + e2e: 'done' + }); + } + }; + + const checkCache = async () => { + const result = await getMediaCache({ + type: fileType, + mimeType: getFileProperty(currentFile, fileType, 'type'), + urlToCache: url + }); + if (result?.exists && !isEncrypted) { + updateCurrentFile(result.uri); + } + return result?.exists; + }; + + const onPress = () => { + if (status === 'loading') { + cancelDownload(url); + setStatus('to-download'); + return; + } + if (status === 'to-download') { + download(); + return; + } + if (!showAttachment || !currentFile.title_link || isEncrypted) { + return; + } + showAttachment(currentFile); + }; + + return { + status, + url, + onPress, + currentFile, + isEncrypted + }; +}; diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 46b2cde2f0..67f4d083c8 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -317,7 +317,7 @@ "How_It_Works": "How it works", "I_Saved_My_E2E_Password": "I saved my E2E password", "Ignore": "Ignore", - "Images": "Images", + "Image": "Image", "Images_uploaded": "Images uploaded", "In_app": "In-app", "In_App_And_Desktop": "In-app and desktop", diff --git a/app/lib/constants/mediaAutoDownload.ts b/app/lib/constants/mediaAutoDownload.ts index f80a5dcd9c..f51f14a647 100644 --- a/app/lib/constants/mediaAutoDownload.ts +++ b/app/lib/constants/mediaAutoDownload.ts @@ -1,5 +1,5 @@ export type MediaDownloadOption = 'never' | 'wifi_mobile_data' | 'wifi'; -export const IMAGES_PREFERENCE_DOWNLOAD = 'imagesPreferenceDownload'; +export const IMAGE_PREFERENCE_DOWNLOAD = 'imagePreferenceDownload'; export const VIDEO_PREFERENCE_DOWNLOAD = 'videoPreferenceDownload'; export const AUDIO_PREFERENCE_DOWNLOAD = 'audioPreferenceDownload'; diff --git a/app/lib/encryption/encryption.ts b/app/lib/encryption/encryption.ts index 01972b2be1..7ccadbfe31 100644 --- a/app/lib/encryption/encryption.ts +++ b/app/lib/encryption/encryption.ts @@ -1,29 +1,19 @@ -import EJSON from 'ejson'; -import SimpleCrypto from 'react-native-simple-crypto'; +import { Model, Q } from '@nozbe/watermelondb'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; -import { Q, Model } from '@nozbe/watermelondb'; +import EJSON from 'ejson'; import { deleteAsync } from 'expo-file-system'; +import SimpleCrypto from 'react-native-simple-crypto'; -import UserPreferences from '../methods/userPreferences'; -import { getMessageById } from '../database/services/Message'; -import { getSubscriptionByRoomId } from '../database/services/Subscription'; -import database from '../database'; -import protectedFunction from '../methods/helpers/protectedFunction'; -import Deferred from './helpers/deferred'; -import log from '../methods/helpers/log'; -import { store } from '../store/auxStore'; -import { decryptAESCTR, joinVectorData, randomPassword, splitVectorData, toString, utf8ToBuffer } from './utils'; import { IMessage, + IServerAttachment, ISubscription, - TSendFileMessageFileInfo, TMessageModel, + TSendFileMessageFileInfo, TSubscriptionModel, TThreadMessageModel, - TThreadModel, - IServerAttachment + TThreadModel } from '../../definitions'; -import EncryptionRoom from './room'; import { E2E_BANNER_TYPE, E2E_MESSAGE_TYPE, @@ -32,9 +22,18 @@ import { E2E_RANDOM_PASSWORD_KEY, E2E_STATUS } from '../constants'; +import database from '../database'; +import { getSubscriptionByRoomId } from '../database/services/Subscription'; +import log from '../methods/helpers/log'; +import protectedFunction from '../methods/helpers/protectedFunction'; +import UserPreferences from '../methods/userPreferences'; import { Services } from '../services'; -import { IDecryptionFileQueue, TDecryptFile, TEncryptFile } from './definitions'; +import { store } from '../store/auxStore'; import { MAX_CONCURRENT_QUEUE } from './constants'; +import { IDecryptionFileQueue, TDecryptFile, TEncryptFile } from './definitions'; +import Deferred from './helpers/deferred'; +import EncryptionRoom from './room'; +import { decryptAESCTR, joinVectorData, randomPassword, splitVectorData, toString, utf8ToBuffer } from './utils'; class Encryption { ready: boolean; @@ -548,7 +547,6 @@ class Encryption { }; decryptFile: TDecryptFile = async (messageId, path, encryption, originalChecksum) => { - const messageRecord = await getMessageById(messageId); const decryptedFile = await decryptAESCTR(path, encryption.key.k, encryption.iv); if (decryptedFile) { const checksum = await SimpleCrypto.utils.calculateFileChecksum(decryptedFile); @@ -556,19 +554,6 @@ class Encryption { await deleteAsync(decryptedFile); return null; } - - if (messageRecord) { - const db = database.active; - await db.write(async () => { - await messageRecord.update(m => { - m.attachments = m.attachments?.map(att => ({ - ...att, - title_link: decryptedFile, - e2e: 'done' - })); - }); - }); - } } return decryptedFile; }; diff --git a/app/lib/methods/autoDownloadPreference.ts b/app/lib/methods/autoDownloadPreference.ts index 71078b161b..340114fdca 100644 --- a/app/lib/methods/autoDownloadPreference.ts +++ b/app/lib/methods/autoDownloadPreference.ts @@ -1,7 +1,7 @@ import { NetInfoStateType } from '@react-native-community/netinfo'; import { - IMAGES_PREFERENCE_DOWNLOAD, + IMAGE_PREFERENCE_DOWNLOAD, AUDIO_PREFERENCE_DOWNLOAD, VIDEO_PREFERENCE_DOWNLOAD, MediaDownloadOption @@ -9,7 +9,7 @@ import { import userPreferences from './userPreferences'; import { store } from '../store/auxStore'; -type TMediaType = typeof IMAGES_PREFERENCE_DOWNLOAD | typeof AUDIO_PREFERENCE_DOWNLOAD | typeof VIDEO_PREFERENCE_DOWNLOAD; +type TMediaType = typeof IMAGE_PREFERENCE_DOWNLOAD | typeof AUDIO_PREFERENCE_DOWNLOAD | typeof VIDEO_PREFERENCE_DOWNLOAD; export const fetchAutoDownloadEnabled = (mediaType: TMediaType) => { const { netInfoState } = store.getState().app; @@ -24,7 +24,7 @@ export const fetchAutoDownloadEnabled = (mediaType: TMediaType) => { } if (mediaDownloadPreference === null) { - if (mediaType === 'imagesPreferenceDownload') { + if (mediaType === 'imagePreferenceDownload') { return true; } if (mediaType === 'audioPreferenceDownload' || mediaType === 'videoPreferenceDownload') { diff --git a/app/lib/methods/handleMediaDownload.ts b/app/lib/methods/handleMediaDownload.ts index 7a31f7a1d9..367abf9832 100644 --- a/app/lib/methods/handleMediaDownload.ts +++ b/app/lib/methods/handleMediaDownload.ts @@ -1,13 +1,18 @@ import * as FileSystem from 'expo-file-system'; import * as mime from 'react-native-mime-types'; import { isEmpty } from 'lodash'; +import { Model } from '@nozbe/watermelondb'; -import { TAttachmentEncryption } from '../../definitions'; +import { IAttachment, TAttachmentEncryption, TMessageModel } from '../../definitions'; import { sanitizeLikeString } from '../database/utils'; import { store } from '../store/auxStore'; import log from './helpers/log'; import { emitter } from './helpers'; import { Encryption } from '../encryption'; +import { getMessageById } from '../database/services/Message'; +import { getThreadMessageById } from '../database/services/ThreadMessage'; +import database from '../database'; +import { getThreadById } from '../database/services/Thread'; export type MediaTypes = 'audio' | 'image' | 'video'; export type TDownloadState = 'to-download' | 'loading' | 'downloaded'; @@ -194,6 +199,55 @@ export async function cancelDownload(messageUrl: string): Promise { } } +const mapAttachments = ({ + attachments, + uri, + encryption +}: { + attachments?: IAttachment[]; + uri: string; + encryption: boolean; +}): TMessageModel['attachments'] => + attachments?.map(att => ({ + ...att, + title_link: uri, + e2e: encryption ? 'done' : undefined + })); + +const persistMessage = async (messageId: string, uri: string, encryption: boolean) => { + const db = database.active; + const batch: Model[] = []; + const messageRecord = await getMessageById(messageId); + if (messageRecord) { + batch.push( + messageRecord.prepareUpdate(m => { + m.attachments = mapAttachments({ attachments: m.attachments, uri, encryption }); + }) + ); + } + const threadRecord = await getThreadById(messageId); + if (threadRecord) { + batch.push( + threadRecord.prepareUpdate(m => { + m.attachments = mapAttachments({ attachments: m.attachments, uri, encryption }); + }) + ); + } + const threadMessageRecord = await getThreadMessageById(messageId); + if (threadMessageRecord) { + batch.push( + threadMessageRecord.prepareUpdate(m => { + m.attachments = mapAttachments({ attachments: m.attachments, uri, encryption }); + }) + ); + } + if (batch.length) { + await db.write(async () => { + await db.batch(...batch); + }); + } +}; + export function downloadMediaFile({ messageId, type, @@ -228,6 +282,8 @@ export function downloadMediaFile({ await Encryption.addFileToDecryptFileQueue(messageId, result.uri, encryption, originalChecksum); } + await persistMessage(messageId, result.uri, !!encryption); + emitter.emit(`downloadMedia${messageId}`, result.uri); return resolve(result.uri); } catch (e) { diff --git a/app/lib/methods/loadThreadMessages.ts b/app/lib/methods/loadThreadMessages.ts index 6a73891bab..9462fdea61 100644 --- a/app/lib/methods/loadThreadMessages.ts +++ b/app/lib/methods/loadThreadMessages.ts @@ -62,7 +62,9 @@ export function loadThreadMessages({ tmid, rid }: { tmid: string; rid: string }) const newThreadMessage = data.find((t: TThreadMessageModel) => t._id === threadMessage.id); return threadMessage.prepareUpdate( protectedFunction((tm: TThreadMessageModel) => { + const { attachments } = tm; Object.assign(tm, newThreadMessage); + tm.attachments = attachments; if (threadMessage.tmid) { tm.rid = threadMessage.tmid; } diff --git a/app/views/MediaAutoDownloadView/index.tsx b/app/views/MediaAutoDownloadView/index.tsx index 9ea3befab4..5dae262e26 100644 --- a/app/views/MediaAutoDownloadView/index.tsx +++ b/app/views/MediaAutoDownloadView/index.tsx @@ -9,7 +9,7 @@ import ListPicker from './ListPicker'; import { useUserPreferences } from '../../lib/methods/userPreferences'; import { AUDIO_PREFERENCE_DOWNLOAD, - IMAGES_PREFERENCE_DOWNLOAD, + IMAGE_PREFERENCE_DOWNLOAD, MediaDownloadOption, VIDEO_PREFERENCE_DOWNLOAD } from '../../lib/constants'; @@ -18,7 +18,7 @@ import { SettingsStackParamList } from '../../stacks/types'; const MediaAutoDownload = () => { const [imagesPreference, setImagesPreference] = useUserPreferences( - IMAGES_PREFERENCE_DOWNLOAD, + IMAGE_PREFERENCE_DOWNLOAD, 'wifi_mobile_data' ); const [videoPreference, setVideoPreference] = useUserPreferences(VIDEO_PREFERENCE_DOWNLOAD, 'wifi'); @@ -36,7 +36,7 @@ const MediaAutoDownload = () => { - + From 3933932dff922f29b51e9060ab1b6efd55138b92 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 31 Jul 2024 14:43:42 -0300 Subject: [PATCH 05/26] fix: image preview size (#5813) --- .../Attachments/Image/Container.tsx | 5 +- .../Components/Attachments/Image/Image.tsx | 57 +++- .../message/Components/Attachments/Reply.tsx | 6 +- .../message/Components/Attachments/Video.tsx | 25 +- .../message/Components/WidthAwareView.tsx | 26 ++ app/containers/message/Urls.tsx | 248 +++++++++--------- app/containers/message/styles.ts | 7 +- 7 files changed, 211 insertions(+), 163 deletions(-) create mode 100644 app/containers/message/Components/WidthAwareView.tsx diff --git a/app/containers/message/Components/Attachments/Image/Container.tsx b/app/containers/message/Components/Attachments/Image/Container.tsx index abee3b5969..12209c8eb5 100644 --- a/app/containers/message/Components/Attachments/Image/Container.tsx +++ b/app/containers/message/Components/Attachments/Image/Container.tsx @@ -8,6 +8,7 @@ import { Button } from './Button'; import { MessageImage } from './Image'; import { IImageContainer } from './definitions'; import MessageContext from '../../../Context'; +import { WidthAwareView } from '../../WidthAwareView'; const ImageContainer = ({ file, @@ -24,7 +25,9 @@ const ImageContainer = ({ const image = ( ); diff --git a/app/containers/message/Components/Attachments/Image/Image.tsx b/app/containers/message/Components/Attachments/Image/Image.tsx index 45518908ee..4bad3c143b 100644 --- a/app/containers/message/Components/Attachments/Image/Image.tsx +++ b/app/containers/message/Components/Attachments/Image/Image.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { View } from 'react-native'; +import React, { useContext, useEffect, useState } from 'react'; +import { View, ViewStyle, Image } from 'react-native'; import FastImage from 'react-native-fast-image'; import { isValidUrl } from '../../../../../lib/methods/helpers/isValidUrl'; @@ -7,34 +7,65 @@ import { useTheme } from '../../../../../theme'; import styles from '../../../styles'; import OverlayComponent from '../../OverlayComponent'; import { IMessageImage } from './definitions'; +import { WidthAwareContext } from '../../WidthAwareView'; export const MessageImage = React.memo(({ uri, status, encrypted = false }: IMessageImage) => { const { colors } = useTheme(); + const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 }); + const maxSize = useContext(WidthAwareContext); + const showImage = isValidUrl(uri) && imageDimensions.width && status === 'downloaded'; + + useEffect(() => { + if (status === 'downloaded') { + Image.getSize(uri, (width, height) => { + setImageDimensions({ width, height }); + }); + } + }, [uri, status]); + + const width = Math.min(imageDimensions.width, maxSize) || 0; + const height = Math.min((imageDimensions.height * ((width * 100) / imageDimensions.width)) / 100, maxSize) || 0; + const imageStyle = { + width, + height + }; + + const containerStyle: ViewStyle = { + alignItems: 'center', + justifyContent: 'center', + ...(imageDimensions.width <= 64 && { width: 64 }), + ...(imageDimensions.height <= 64 && { height: 64 }) + }; + + const borderStyle: ViewStyle = { + borderColor: colors.strokeLight, + borderWidth: 1, + borderRadius: 4, + overflow: 'hidden' + }; if (encrypted && status === 'downloaded') { return ( <> - + ); } return ( <> - {isValidUrl(uri) && status === 'downloaded' ? ( - + {showImage ? ( + + + ) : ( - + )} - {['loading', 'to-download'].includes(status) ? ( + {['loading', 'to-download'].includes(status) || (status === 'downloaded' && !showImage) ? ( ) : null} diff --git a/app/containers/message/Components/Attachments/Reply.tsx b/app/containers/message/Components/Attachments/Reply.tsx index 822a2c30e1..eb374a5beb 100644 --- a/app/containers/message/Components/Attachments/Reply.tsx +++ b/app/containers/message/Components/Attachments/Reply.tsx @@ -223,9 +223,9 @@ const Reply = React.memo( openLink(url, theme); }; - let { strokeExtraLight } = themes[theme]; + let { strokeLight } = themes[theme]; if (attachment.color) { - strokeExtraLight = attachment.color; + strokeLight = attachment.color; } return ( @@ -239,7 +239,7 @@ const Reply = React.memo( index > 0 && styles.marginTop, msg && styles.marginBottom, { - borderColor: strokeExtraLight + borderColor: strokeLight } ]} background={Touchable.Ripple(themes[theme].surfaceNeutral)} diff --git a/app/containers/message/Components/Attachments/Video.tsx b/app/containers/message/Components/Attachments/Video.tsx index c114ae6885..23b72e6e46 100644 --- a/app/containers/message/Components/Attachments/Video.tsx +++ b/app/containers/message/Components/Attachments/Video.tsx @@ -5,7 +5,6 @@ import { IUserMessage } from '../../../../definitions'; import { IAttachment } from '../../../../definitions/IAttachment'; import { TGetCustomEmoji } from '../../../../definitions/IEmoji'; import I18n from '../../../../i18n'; -import { themes } from '../../../../lib/constants'; import { fileDownload, isIOS } from '../../../../lib/methods/helpers'; import EventEmitter from '../../../../lib/methods/helpers/events'; import { useTheme } from '../../../../theme'; @@ -16,22 +15,14 @@ import Markdown from '../../../markdown'; import MessageContext from '../../Context'; import Touchable from '../../Touchable'; import { useMediaAutoDownload } from '../../hooks/useMediaAutoDownload'; -import { DEFAULT_MESSAGE_HEIGHT } from '../../utils'; import BlurComponent from '../OverlayComponent'; import { TDownloadState } from '../../../../lib/methods/handleMediaDownload'; +import messageStyles from '../../styles'; const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])]; const isTypeSupported = (type: string) => SUPPORTED_TYPES.indexOf(type) !== -1; const styles = StyleSheet.create({ - button: { - flex: 1, - borderRadius: 4, - height: DEFAULT_MESSAGE_HEIGHT, - marginBottom: 6, - alignItems: 'center', - justifyContent: 'center' - }, cancelContainer: { position: 'absolute', top: 8, @@ -63,6 +54,7 @@ const CancelIndicator = () => { }; const Thumbnail = ({ status, encrypted = false }: { status: TDownloadState; encrypted: boolean }) => { + const { colors } = useTheme(); let icon: TIconsName = status === 'downloaded' ? 'play-filled' : 'arrow-down-circle'; if (encrypted && status === 'downloaded') { icon = 'encrypted'; @@ -70,7 +62,11 @@ const Thumbnail = ({ status, encrypted = false }: { status: TDownloadState; encr return ( <> - + {status === 'loading' ? : null} ); @@ -86,7 +82,7 @@ const Video = ({ msg }: IMessageVideo): React.ReactElement | null => { const { user } = useContext(MessageContext); - const { theme } = useTheme(); + const { theme, colors } = useTheme(); const { status, onPress, url, isEncrypted, currentFile } = useMediaAutoDownload({ file, author, showAttachment }); const _onPress = async () => { @@ -115,10 +111,7 @@ const Video = ({ return ( <> - + diff --git a/app/containers/message/Components/WidthAwareView.tsx b/app/containers/message/Components/WidthAwareView.tsx new file mode 100644 index 0000000000..82103fd7be --- /dev/null +++ b/app/containers/message/Components/WidthAwareView.tsx @@ -0,0 +1,26 @@ +import { createContext, ReactElement, useState } from 'react'; +import { View, StyleSheet } from 'react-native'; + +export const WidthAwareContext = createContext(0); + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row' + } +}); + +export const WidthAwareView = ({ children }: { children: ReactElement }) => { + const [width, setWidth] = useState(0); + + return ( + { + if (ev.nativeEvent.layout.width) { + setWidth(ev.nativeEvent.layout.width); + } + }}> + {children} + + ); +}; diff --git a/app/containers/message/Urls.tsx b/app/containers/message/Urls.tsx index f2b26625d7..7e1ecaf5cd 100644 --- a/app/containers/message/Urls.tsx +++ b/app/containers/message/Urls.tsx @@ -1,5 +1,5 @@ -import React, { useContext, useEffect, useState } from 'react'; -import { Image, StyleSheet, Text, unstable_batchedUpdates, View } from 'react-native'; +import React, { ReactElement, useContext, useEffect, useState } from 'react'; +import { Image, StyleSheet, Text, View, ViewStyle } from 'react-native'; import Clipboard from '@react-native-clipboard/clipboard'; import FastImage from 'react-native-fast-image'; import { dequal } from 'dequal'; @@ -7,24 +7,20 @@ import { dequal } from 'dequal'; import Touchable from './Touchable'; import openLink from '../../lib/methods/helpers/openLink'; import sharedStyles from '../../views/Styles'; -import { themes } from '../../lib/constants'; -import { TSupportedThemes, useTheme, withTheme } from '../../theme'; +import { useTheme } from '../../theme'; import { LISTENER } from '../Toast'; import EventEmitter from '../../lib/methods/helpers/events'; import I18n from '../../i18n'; import MessageContext from './Context'; import { IUrl } from '../../definitions'; -import { DEFAULT_MESSAGE_HEIGHT } from './utils'; +import { WidthAwareContext, WidthAwareView } from './Components/WidthAwareView'; const styles = StyleSheet.create({ - button: { - marginTop: 6 - }, container: { flex: 1, flexDirection: 'column', - borderRadius: 4, - borderWidth: 1 + marginTop: 4, + gap: 4 }, textContainer: { flex: 1, @@ -41,18 +37,6 @@ const styles = StyleSheet.create({ fontSize: 16, ...sharedStyles.textRegular }, - marginTop: { - marginTop: 4 - }, - image: { - width: '100%', - height: DEFAULT_MESSAGE_HEIGHT, - borderTopLeftRadius: 4, - borderTopRightRadius: 4 - }, - imageWithoutContent: { - borderRadius: 4 - }, loading: { height: 0, borderWidth: 0, @@ -60,128 +44,144 @@ const styles = StyleSheet.create({ } }); -const UrlContent = React.memo( - ({ title, description }: { title: string; description: string }) => { - const { colors } = useTheme(); - return ( - - {title ? ( - - {title} - - ) : null} - {description ? ( - - {description} - - ) : null} - - ); - }, - (prevProps, nextProps) => { - if (prevProps.title !== nextProps.title) { - return false; +const UrlContent = ({ title, description }: { title: string; description: string }) => { + const { colors } = useTheme(); + return ( + + {title ? ( + + {title} + + ) : null} + {description ? ( + + {description} + + ) : null} + + ); +}; +const UrlImage = ({ image, hasContent }: { image: string; hasContent: boolean }) => { + const { colors } = useTheme(); + const [imageLoadedState, setImageLoadedState] = useState('loading'); + const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 }); + const maxSize = useContext(WidthAwareContext); + + useEffect(() => { + if (image) { + Image.getSize( + image, + (width, height) => { + setImageDimensions({ width, height }); + }, + () => { + setImageLoadedState('error'); + } + ); } - if (prevProps.description !== nextProps.description) { - return false; + }, [image]); + + let imageStyle = {}; + let containerStyle: ViewStyle = {}; + + if (imageLoadedState === 'done') { + const width = Math.min(imageDimensions.width, maxSize) || 0; + const height = Math.min((imageDimensions.height * ((width * 100) / imageDimensions.width)) / 100, maxSize) || 0; + imageStyle = { + width, + height + }; + containerStyle = { + overflow: 'hidden', + alignItems: 'center', + justifyContent: 'center', + ...(imageDimensions.width <= 64 && { width: 64 }), + ...(imageDimensions.height <= 64 && { height: 64 }) + }; + if (!hasContent) { + containerStyle = { + ...containerStyle, + borderColor: colors.strokeLight, + borderWidth: 1, + borderRadius: 4 + }; } - return true; } -); + + return ( + + setImageLoadedState('error')} + onLoad={() => setImageLoadedState('done')} + /> + + ); +}; type TImageLoadedState = 'loading' | 'done' | 'error'; -const Url = React.memo( - ({ url, index, theme }: { url: IUrl; index: number; theme: TSupportedThemes }) => { - const [imageLoadedState, setImageLoadedState] = useState('loading'); - const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 }); - const { baseUrl, user } = useContext(MessageContext); - let image = url.image || url.url; - image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`; - - useEffect(() => { - if (image) { - Image.getSize( - image, - (width, height) => { - unstable_batchedUpdates(() => { - setImageDimensions({ width, height }); - setImageLoadedState('done'); - }); - }, - () => { - setImageLoadedState('error'); - } - ); - } - }, [image]); - - const onPress = () => openLink(url.url, theme); - - const onLongPress = () => { - Clipboard.setString(url.url); - EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') }); - }; +const Url = ({ url }: { url: IUrl }) => { + const { colors, theme } = useTheme(); + const { baseUrl, user } = useContext(MessageContext); + let image = url.image || url.url; + image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`; - const hasContent = url.title || url.description; + const onPress = () => openLink(url.url, theme); - if (!url || url?.ignoreParse || imageLoadedState === 'error' || !imageDimensions.width || !imageDimensions.height) { - return null; - } + const onLongPress = () => { + Clipboard.setString(url.url); + EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') }); + }; - return ( - 0 && styles.marginTop, - styles.container, - { - backgroundColor: themes[theme].surfaceTint, - borderColor: themes[theme].strokeLight - }, - imageLoadedState === 'loading' && styles.loading - ]} - background={Touchable.Ripple(themes[theme].surfaceNeutral)}> - <> - {image ? ( - setImageLoadedState('error')} - onLoad={() => setImageLoadedState('done')} - /> - ) : null} - {hasContent ? : null} - - - ); - }, - (oldProps, newProps) => dequal(oldProps.url, newProps.url) && oldProps.theme === newProps.theme -); + const hasContent = !!(url.title || url.description); -const Urls = React.memo( - // TODO - didn't work - (React.ReactElement | null)[] | React.ReactElement | null - ({ urls }: { urls?: IUrl[] }): any => { - const { theme } = useTheme(); + if (!url || url?.ignoreParse) { + return null; + } + return ( + + <> + {image ? ( + + + + ) : null} + {hasContent ? : null} + + + ); +}; +const Urls = React.memo( + ({ urls }: { urls?: IUrl[] }): ReactElement[] | null => { if (!urls || urls.length === 0) { return null; } - return urls.map((url: IUrl, index: number) => ); + return urls.map((url: IUrl) => ); }, (oldProps, newProps) => dequal(oldProps.urls, newProps.urls) ); UrlContent.displayName = 'MessageUrlContent'; +UrlImage.displayName = 'MessageUrlImage'; Url.displayName = 'MessageUrl'; Urls.displayName = 'MessageUrls'; -export default withTheme(Urls); +export default Urls; diff --git a/app/containers/message/styles.ts b/app/containers/message/styles.ts index 12ae4f9604..c5c42d2030 100644 --- a/app/containers/message/styles.ts +++ b/app/containers/message/styles.ts @@ -102,12 +102,7 @@ export default StyleSheet.create({ width: '100%', minHeight: isTablet ? 300 : 200, borderRadius: 4, - borderWidth: 1, - overflow: 'hidden', - borderColor: 'rgba(0, 0, 0, 0.1)' - }, - imageBlurContainer: { - height: '100%' + overflow: 'hidden' }, imagePressed: { opacity: 0.5 From d0548a04290189e60be84b31a5507a63d243633e Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Wed, 31 Jul 2024 16:23:48 -0300 Subject: [PATCH 06/26] feat(a11y): add accessibilityLabel (#5793) --- .../chat/rocket/reactnative/MainActivity.kt | 8 - app/actions/actionsTypes.ts | 2 - app/actions/rooms.ts | 12 - app/containers/Button/index.tsx | 8 +- app/containers/DirectoryItem/index.tsx | 40 +- .../HeaderButton/HeaderButtonItem.tsx | 16 +- app/containers/List/ListHeader.tsx | 5 +- app/containers/List/ListItem.tsx | 34 +- .../LoginServices/ButtonService.tsx | 4 +- app/containers/LoginServices/Service.tsx | 1 + app/containers/LoginServices/interfaces.ts | 1 + app/containers/RoomHeader/RoomHeader.tsx | 14 +- app/containers/RoomItem/LastMessage.tsx | 56 +- app/containers/RoomItem/RoomItem.tsx | 204 +++---- app/containers/RoomItem/Wrapper.tsx | 4 +- app/containers/RoomItem/index.tsx | 15 - app/containers/RoomItem/interfaces.ts | 1 - app/containers/SearchBox/index.tsx | 2 +- app/containers/TextInput/FormTextInput.tsx | 1 + app/containers/Touch.tsx | 36 +- app/containers/UserItem.tsx | 12 +- app/containers/message/Message.tsx | 28 +- .../containers/OmnichannelHeader/index.tsx | 1 + app/i18n/locales/en.json | 9 +- app/i18n/locales/pt-BR.json | 6 + app/index.tsx | 10 +- app/lib/methods/formatLastMessage.ts | 53 ++ app/lib/methods/helpers/log/events.ts | 1 - app/lib/methods/helpers/navigation/index.ts | 4 - app/reducers/rooms.test.ts | 16 +- app/reducers/rooms.ts | 14 - app/views/AddExistingChannelView/index.tsx | 2 + app/views/AutoTranslateView/index.tsx | 3 + .../DepartmentFilter/DepartmentItemFilter.tsx | 47 ++ .../DepartmentFilter/index.tsx | 42 ++ .../Dropdown/DropdownItem.tsx | 46 -- .../Dropdown/DropdownItemFilter.tsx | 20 - .../Dropdown/DropdownItemHeader.tsx | 15 - .../Dropdown/index.tsx | 94 ---- app/views/CannedResponsesListView/index.tsx | 58 +- app/views/CannedResponsesListView/styles.ts | 9 - .../RoomSettings/SwitchItem.tsx | 25 +- app/views/CreateDiscussionView/index.tsx | 1 + app/views/DefaultBrowserView/Item.tsx | 2 + app/views/DefaultBrowserView/index.tsx | 2 +- app/views/DirectoryView/Options.tsx | 105 ++-- app/views/DirectoryView/index.tsx | 104 ++-- app/views/DirectoryView/styles.ts | 59 +- app/views/DisplayPrefsView.tsx | 8 + app/views/LanguageView/LanguageItem.tsx | 2 + .../MediaAutoDownloadView/ListPicker.tsx | 12 +- app/views/NewServerView/ServerInput/Item.tsx | 14 +- app/views/NewServerView/index.tsx | 63 +-- app/views/NewServerView/scaling.ts | 16 - .../NotificationPreferencesView/index.tsx | 14 +- app/views/PickerView.tsx | 2 + .../components/CommunityEditionPushQuota.tsx | 1 + app/views/RoomActionsView/index.tsx | 1 + app/views/RoomsListView/Header/Header.tsx | 20 +- app/views/RoomsListView/Header/index.tsx | 18 +- .../{ServerDropdown.tsx => ServersList.tsx} | 151 ++--- app/views/RoomsListView/index.tsx | 17 +- app/views/RoomsListView/styles.ts | 11 +- app/views/ScreenLockConfigView.tsx | 9 +- app/views/SecurityPrivacyView.tsx | 2 + app/views/SelectListView.tsx | 11 + app/views/SelectedUsersView/index.tsx | 1 + app/views/ShareView/Header.tsx | 2 +- app/views/SidebarView/SidebarItem.tsx | 9 +- app/views/StatusView/index.tsx | 2 +- app/views/ThemeView.tsx | 2 + .../Dropdown/DropdownItem.tsx | 42 -- .../Dropdown/DropdownItemFilter.tsx | 17 - .../Dropdown/DropdownItemHeader.tsx | 28 - .../ThreadMessagesView/Dropdown/index.tsx | 92 ---- app/views/ThreadMessagesView/index.tsx | 64 +-- app/views/ThreadMessagesView/styles.ts | 9 - .../ListPicker.tsx | 9 +- app/views/UserPreferencesView/ListPicker.tsx | 9 +- e2e/tests/assorted/01-e2eencryption.spec.ts | 8 +- e2e/tests/assorted/06-status.spec.ts | 2 +- e2e/tests/assorted/07-changeserver.spec.ts | 17 +- .../assorted/09-joinfromdirectory.spec.ts | 4 +- e2e/tests/assorted/10-deleteserver.spec.ts | 12 +- e2e/tests/assorted/11-deeplinking.spec.ts | 4 +- ios/Podfile.lock | 6 - ios/RocketChatRN.xcodeproj/project.pbxproj | 516 +++++++++--------- ios/RocketChatRN/AppDelegate.mm | 7 +- package.json | 1 - ...eact-native-orientation-locker+1.1.8.patch | 25 - yarn.lock | 5 - 91 files changed, 1008 insertions(+), 1509 deletions(-) create mode 100644 app/lib/methods/formatLastMessage.ts create mode 100644 app/views/CannedResponsesListView/DepartmentFilter/DepartmentItemFilter.tsx create mode 100644 app/views/CannedResponsesListView/DepartmentFilter/index.tsx delete mode 100644 app/views/CannedResponsesListView/Dropdown/DropdownItem.tsx delete mode 100644 app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.tsx delete mode 100644 app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.tsx delete mode 100644 app/views/CannedResponsesListView/Dropdown/index.tsx delete mode 100644 app/views/NewServerView/scaling.ts rename app/views/RoomsListView/{ServerDropdown.tsx => ServersList.tsx} (50%) delete mode 100644 app/views/ThreadMessagesView/Dropdown/DropdownItem.tsx delete mode 100644 app/views/ThreadMessagesView/Dropdown/DropdownItemFilter.tsx delete mode 100644 app/views/ThreadMessagesView/Dropdown/DropdownItemHeader.tsx delete mode 100644 app/views/ThreadMessagesView/Dropdown/index.tsx delete mode 100644 patches/react-native-orientation-locker+1.1.8.patch diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt index 98e3feb22c..ad73afd565 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt +++ b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt @@ -33,12 +33,4 @@ class MainActivity : ReactActivity() { override fun invokeDefaultOnBackPressed() { moveTaskToBack(true) } - - // from react-native-orientation - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - val intent = Intent("onConfigurationChanged") - intent.putExtra("newConfig", newConfig) - sendBroadcast(intent) - } } \ No newline at end of file diff --git a/app/actions/actionsTypes.ts b/app/actions/actionsTypes.ts index d5399d4577..fe6fb521e8 100644 --- a/app/actions/actionsTypes.ts +++ b/app/actions/actionsTypes.ts @@ -16,8 +16,6 @@ export const ROOMS = createRequestTypes('ROOMS', [ ...defaultTypes, 'REFRESH', 'SET_SEARCH', - 'CLOSE_SERVER_DROPDOWN', - 'TOGGLE_SERVER_DROPDOWN', 'OPEN_SEARCH_HEADER', 'CLOSE_SEARCH_HEADER' ]); diff --git a/app/actions/rooms.ts b/app/actions/rooms.ts index 514582ff62..7956f90870 100644 --- a/app/actions/rooms.ts +++ b/app/actions/rooms.ts @@ -53,18 +53,6 @@ export function setSearch(searchText: string): ISetSearch { }; } -export function closeServerDropdown(): Action { - return { - type: ROOMS.CLOSE_SERVER_DROPDOWN - }; -} - -export function toggleServerDropdown(): Action { - return { - type: ROOMS.TOGGLE_SERVER_DROPDOWN - }; -} - export function openSearchHeader(): Action { return { type: ROOMS.OPEN_SEARCH_HEADER diff --git a/app/containers/Button/index.tsx b/app/containers/Button/index.tsx index 396426b613..1632cded4a 100644 --- a/app/containers/Button/index.tsx +++ b/app/containers/Button/index.tsx @@ -67,7 +67,13 @@ const Button: React.FC = ({ const textStyle = [styles.text, { color: isDisabled ? colors.fontDisabled : resolvedTextColor, fontSize }, styleText]; return ( - + {loading ? : {title}} ); diff --git a/app/containers/DirectoryItem/index.tsx b/app/containers/DirectoryItem/index.tsx index 785dc90293..86fcd5725a 100644 --- a/app/containers/DirectoryItem/index.tsx +++ b/app/containers/DirectoryItem/index.tsx @@ -50,27 +50,29 @@ const DirectoryItem = ({ }: IDirectoryItem): React.ReactElement => { const { theme } = useTheme(); return ( - - - - - - {type !== 'd' ? : null} - - {title} - + + + + + + + {type !== 'd' ? : null} + + {title} + + + {description ? ( + + ) : null} - {description ? ( - - ) : null} + - - - + + ); }; diff --git a/app/containers/HeaderButton/HeaderButtonItem.tsx b/app/containers/HeaderButton/HeaderButtonItem.tsx index c4b51452ad..67c2b44698 100644 --- a/app/containers/HeaderButton/HeaderButtonItem.tsx +++ b/app/containers/HeaderButton/HeaderButtonItem.tsx @@ -14,6 +14,7 @@ export interface IHeaderButtonItem { badge?(): void; color?: string; disabled?: boolean; + accessibilityLabel?: string; } export const BUTTON_HIT_SLOP = { @@ -40,7 +41,17 @@ const styles = StyleSheet.create({ } }); -const Item = ({ title, iconName, onPress, testID, badge, color, disabled, ...props }: IHeaderButtonItem): React.ReactElement => { +const Item = ({ + title, + iconName, + onPress, + testID, + badge, + color, + disabled, + accessibilityLabel, + ...props +}: IHeaderButtonItem): React.ReactElement => { const { colors } = useTheme(); return ( + ]} + accessibilityLabel={accessibilityLabel}> <> {iconName ? ( diff --git a/app/containers/List/ListHeader.tsx b/app/containers/List/ListHeader.tsx index 5d22869c52..05bdce7df8 100644 --- a/app/containers/List/ListHeader.tsx +++ b/app/containers/List/ListHeader.tsx @@ -21,14 +21,15 @@ const styles = StyleSheet.create({ interface IListHeader { title: string; translateTitle?: boolean; + numberOfLines?: number; } -const ListHeader = React.memo(({ title, translateTitle = true }: IListHeader) => { +const ListHeader = React.memo(({ title, translateTitle = true, numberOfLines = 1 }: IListHeader) => { const { theme } = useTheme(); return ( - + {translateTitle ? I18n.t(title) : title} diff --git a/app/containers/List/ListItem.tsx b/app/containers/List/ListItem.tsx index 79538736ff..8c8a4013ac 100644 --- a/app/containers/List/ListItem.tsx +++ b/app/containers/List/ListItem.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { I18nManager, StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native'; import Touch from '../Touch'; @@ -68,6 +68,8 @@ interface IListItemContent { alert?: boolean; heightContainer?: number; styleTitle?: StyleProp; + additionalAcessibilityLabel?: string | boolean; + additionalAcessibilityLabelCheck?: boolean; } const Content = React.memo( @@ -85,14 +87,40 @@ const Content = React.memo( showActionIndicator = false, theme, heightContainer, - styleTitle + styleTitle, + additionalAcessibilityLabel, + additionalAcessibilityLabelCheck }: IListItemContent) => { const { fontScale } = useDimensions(); + const handleAcessibilityLabel = useMemo(() => { + let label = ''; + if (title) { + label = translateTitle ? I18n.t(title) : title; + } + if (subtitle) { + label = translateSubtitle ? `${label} ${I18n.t(subtitle)}` : `${label} ${subtitle}`; + } + if (typeof additionalAcessibilityLabel === 'string') { + label = `${label} ${additionalAcessibilityLabel}`; + } + if (typeof additionalAcessibilityLabel === 'boolean') { + if (additionalAcessibilityLabelCheck) { + label = `${label} ${additionalAcessibilityLabel ? I18n.t('Checked') : I18n.t('Unchecked')}`; + } else { + label = `${label} ${additionalAcessibilityLabel ? I18n.t('Enabled') : I18n.t('Disabled')}`; + } + } + return label; + }, [title, subtitle, translateTitle, translateSubtitle, additionalAcessibilityLabel, additionalAcessibilityLabelCheck]); + return ( + testID={testID} + accessible + accessibilityLabel={handleAcessibilityLabel} + accessibilityRole='button'> {left ? {left()} : null} diff --git a/app/containers/LoginServices/ButtonService.tsx b/app/containers/LoginServices/ButtonService.tsx index cdcb7b5def..2e6e581901 100644 --- a/app/containers/LoginServices/ButtonService.tsx +++ b/app/containers/LoginServices/ButtonService.tsx @@ -7,7 +7,7 @@ import { CustomIcon } from '../CustomIcon'; import { IButtonService } from './interfaces'; import styles from './styles'; -const ButtonService = ({ name, authType, onPress, backgroundColor, buttonText, icon }: IButtonService) => { +const ButtonService = ({ name, authType, onPress, backgroundColor, buttonText, icon, accessibilityLabel }: IButtonService) => { const { colors } = useTheme(); return ( @@ -17,6 +17,8 @@ const ButtonService = ({ name, authType, onPress, backgroundColor, buttonText, i style={[styles.serviceButton, { backgroundColor }]} activeOpacity={0.5} underlayColor={colors.fontWhite} + accessible + accessibilityLabel={accessibilityLabel} > {authType === 'oauth' || authType === 'apple' ? : null} diff --git a/app/containers/LoginServices/Service.tsx b/app/containers/LoginServices/Service.tsx index 028bb77365..62a628bbc4 100644 --- a/app/containers/LoginServices/Service.tsx +++ b/app/containers/LoginServices/Service.tsx @@ -96,6 +96,7 @@ const Service = React.memo( icon={icon} name={service.name} authType={service.authType} + accessibilityLabel={`${I18n.t('Continue_with')} ${modifiedName.current}`} /> ); } diff --git a/app/containers/LoginServices/interfaces.ts b/app/containers/LoginServices/interfaces.ts index b73a30c090..d7169145c4 100644 --- a/app/containers/LoginServices/interfaces.ts +++ b/app/containers/LoginServices/interfaces.ts @@ -66,4 +66,5 @@ export interface IButtonService { backgroundColor: string; buttonText: ReactElement; icon: TIconsName; + accessibilityLabel?: string; } diff --git a/app/containers/RoomHeader/RoomHeader.tsx b/app/containers/RoomHeader/RoomHeader.tsx index bdcdddafeb..72741eec3d 100644 --- a/app/containers/RoomHeader/RoomHeader.tsx +++ b/app/containers/RoomHeader/RoomHeader.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import I18n from '../../i18n'; @@ -174,10 +174,17 @@ const Header = React.memo( const handleOnPress = useCallback(() => onPress(), []); + const accessibilityLabel = useMemo(() => { + if (tmid) { + return `${title} ${parentTitle}`; + } + return title; + }, [title, parentTitle, tmid]); + return ( + hitSlop={HIT_SLOP} + accessibilityRole='header'> {tmid ? null : ( ) => { - if (!showLastMessage) { - return ''; - } - if (!lastMessage || !lastMessage.u) { - return I18n.t('No_Message'); - } - if (lastMessage.t === 'jitsi_call_started') { - const { u } = lastMessage; - return I18n.t('Started_call', { userBy: u.username }); - } - - let prefix = ''; - const isLastMessageSentByMe = lastMessage.u.username === username; - - if (!lastMessage.msg && lastMessage.attachments && Object.keys(lastMessage.attachments).length) { - const userAttachment = () => { - if (isLastMessageSentByMe) { - return I18n.t('You'); - } - if (useRealName && lastMessage.u.name) { - return lastMessage.u.name; - } - return lastMessage.u.username; - }; - return I18n.t('User_sent_an_attachment', { user: userAttachment() }); - } - - // Encrypted message pending decrypt - if (lastMessage.t === E2E_MESSAGE_TYPE && lastMessage.e2e !== E2E_STATUS.DONE) { - lastMessage.msg = I18n.t('Encrypted_message'); - } - - if (isLastMessageSentByMe) { - prefix = I18n.t('You_colon'); - } else if (type !== 'd') { - const { - u: { name } - } = lastMessage; - prefix = `${useRealName ? name : lastMessage.u.username}: `; - } - - if (lastMessage.t === 'videoconf') { - prefix = ''; - lastMessage.msg = I18n.t('Call_started'); - } - - return `${prefix}${lastMessage.msg}`; -}; - const arePropsEqual = (oldProps: any, newProps: any) => dequal(oldProps, newProps); const LastMessage = React.memo(({ lastMessage, type, showLastMessage, username, alert, useRealName }: ILastMessageProps) => { const { colors } = useTheme(); // Android has a bug with the text align on the markdown preview const alignSelf: TextStyle = isAndroid ? { alignSelf: 'stretch' } : {}; + return ( ( - - { + const memoizedMessage = useMemo( + () => formatLastMessage({ lastMessage, username, useRealName, showLastMessage, alert, type }), + [lastMessage, username, useRealName, showLastMessage, alert, type] + ); + const accessibilityLabel = useMemo(() => `${name} ${capitalize(date)} ${memoizedMessage}`, [name, date, memoizedMessage]); + return ( + - {showLastMessage && displayMode === DisplayMode.Expanded ? ( - <> - - {showAvatar ? ( - + + {showLastMessage && displayMode === DisplayMode.Expanded ? ( + <> + + {showAvatar ? ( + + ) : null} + + {autoJoin ? <Tag testID='auto-join-tag' name={I18n.t('Auto-join')} /> : null} + <UpdatedAt date={date} hideUnreadStatus={hideUnreadStatus} alert={alert} /> + </View> + <View style={styles.row} testID='room-item-last-message-container'> + <LastMessage + lastMessage={lastMessage} type={type} - prid={prid} - status={status} - isGroupChat={isGroupChat} - teamMain={teamMain} - sourceType={sourceType} + showLastMessage={showLastMessage} + username={username || ''} + alert={alert && !hideUnreadStatus} + useRealName={useRealName} /> - ) : null} - <Title name={name} hideUnreadStatus={hideUnreadStatus} alert={alert} /> - {autoJoin ? <Tag testID='auto-join-tag' name={I18n.t('Auto-join')} /> : null} - <UpdatedAt date={date} hideUnreadStatus={hideUnreadStatus} alert={alert} /> - </View> - <View style={styles.row} testID='room-item-last-message-container'> - <LastMessage - lastMessage={lastMessage} + <UnreadBadge + unread={unread} + userMentions={userMentions} + groupMentions={groupMentions} + tunread={tunread} + tunreadUser={tunreadUser} + tunreadGroup={tunreadGroup} + hideMentionStatus={hideMentionStatus} + hideUnreadStatus={hideUnreadStatus} + /> + </View> + </> + ) : ( + <View style={[styles.titleContainer, styles.flex]}> + <TypeIcon + userId={userId} type={type} - showLastMessage={showLastMessage} - username={username || ''} - alert={alert && !hideUnreadStatus} - useRealName={useRealName} - /> - <UnreadBadge - unread={unread} - userMentions={userMentions} - groupMentions={groupMentions} - tunread={tunread} - tunreadUser={tunreadUser} - tunreadGroup={tunreadGroup} - hideMentionStatus={hideMentionStatus} - hideUnreadStatus={hideUnreadStatus} - /> - </View> - </> - ) : ( - <View style={[styles.titleContainer, styles.flex]}> - <TypeIcon - userId={userId} - type={type} - prid={prid} - status={status} - isGroupChat={isGroupChat} - teamMain={teamMain} - size={22} - style={{ marginRight: 8 }} - sourceType={sourceType} - /> - <Title name={name} hideUnreadStatus={hideUnreadStatus} alert={alert} /> - {autoJoin ? <Tag name={I18n.t('Auto-join')} /> : null} - <View style={styles.wrapUpdatedAndBadge}> - <UpdatedAt date={date} hideUnreadStatus={hideUnreadStatus} alert={alert} /> - <UnreadBadge - unread={unread} - userMentions={userMentions} - groupMentions={groupMentions} - tunread={tunread} - tunreadUser={tunreadUser} - tunreadGroup={tunreadGroup} - hideMentionStatus={hideMentionStatus} - hideUnreadStatus={hideUnreadStatus} + prid={prid} + status={status} + isGroupChat={isGroupChat} + teamMain={teamMain} + size={22} + style={{ marginRight: 8 }} + sourceType={sourceType} /> + <Title name={name} hideUnreadStatus={hideUnreadStatus} alert={alert} /> + {autoJoin ? <Tag name={I18n.t('Auto-join')} /> : null} + <View style={styles.wrapUpdatedAndBadge}> + <UpdatedAt date={date} hideUnreadStatus={hideUnreadStatus} alert={alert} /> + <UnreadBadge + unread={unread} + userMentions={userMentions} + groupMentions={groupMentions} + tunread={tunread} + tunreadUser={tunreadUser} + tunreadGroup={tunreadGroup} + hideMentionStatus={hideMentionStatus} + hideUnreadStatus={hideUnreadStatus} + /> + </View> </View> - </View> - )} - </Wrapper> - </Touchable> -); + )} + </Wrapper> + </Touchable> + ); +}; export default RoomItem; diff --git a/app/containers/RoomItem/Wrapper.tsx b/app/containers/RoomItem/Wrapper.tsx index 9affa8da39..d0d49136b1 100644 --- a/app/containers/RoomItem/Wrapper.tsx +++ b/app/containers/RoomItem/Wrapper.tsx @@ -12,7 +12,9 @@ const Wrapper = ({ accessibilityLabel, children, displayMode, ...props }: IWrapp return ( <View style={[styles.container, displayMode === DisplayMode.Condensed && styles.containerCondensed]} - accessibilityLabel={accessibilityLabel}> + accessibilityLabel={accessibilityLabel} + accessible + accessibilityRole='button'> <IconOrAvatar displayMode={displayMode} {...props} /> <View style={[ diff --git a/app/containers/RoomItem/index.tsx b/app/containers/RoomItem/index.tsx index 19cdb7facd..40f7371ed0 100644 --- a/app/containers/RoomItem/index.tsx +++ b/app/containers/RoomItem/index.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useReducer, useRef } from 'react'; import { Subscription } from 'rxjs'; -import I18n from '../../i18n'; import { isGroupChat } from '../../lib/methods/helpers'; import { formatDate } from '../../lib/methods/helpers/room'; import { IRoomItemContainerProps } from './interfaces'; @@ -62,19 +61,6 @@ const RoomItemContainer = React.memo( const handleOnLongPress = () => onLongPress && onLongPress(item); - let accessibilityLabel = ''; - if (item.unread === 1) { - accessibilityLabel = `, ${item.unread} ${I18n.t('alert')}`; - } else if (item.unread > 1) { - accessibilityLabel = `, ${item.unread} ${I18n.t('alerts')}`; - } - if (item.userMentions > 0) { - accessibilityLabel = `, ${I18n.t('you_were_mentioned')}`; - } - if (date) { - accessibilityLabel = `, ${I18n.t('last_message')} ${date}`; - } - return ( <RoomItem name={name} @@ -84,7 +70,6 @@ const RoomItemContainer = React.memo( onPress={handleOnPress} onLongPress={handleOnLongPress} date={date} - accessibilityLabel={accessibilityLabel} width={width} favorite={item.f} rid={item.rid} diff --git a/app/containers/RoomItem/interfaces.ts b/app/containers/RoomItem/interfaces.ts index e56ef4af61..ed2f74429e 100644 --- a/app/containers/RoomItem/interfaces.ts +++ b/app/containers/RoomItem/interfaces.ts @@ -104,7 +104,6 @@ export interface IRoomItemProps extends IBaseRoomItem { isRead: boolean; teamMain: boolean; date: string; - accessibilityLabel: string; lastMessage: ILastMessage; favorite: boolean; alert: boolean; diff --git a/app/containers/SearchBox/index.tsx b/app/containers/SearchBox/index.tsx index baf6c51bdb..62c447efaf 100644 --- a/app/containers/SearchBox/index.tsx +++ b/app/containers/SearchBox/index.tsx @@ -23,7 +23,7 @@ const SearchBox = ({ onChangeText, onSubmitEditing, testID }: TextInputProps): J }, []); return ( - <View testID='searchbox' style={{ backgroundColor: colors.surfaceRoom }}> + <View testID='searchbox' style={{ backgroundColor: colors.surfaceRoom }} > <FormTextInput autoCapitalize='none' autoCorrect={false} diff --git a/app/containers/TextInput/FormTextInput.tsx b/app/containers/TextInput/FormTextInput.tsx index df1922576e..8cd82f6d8e 100644 --- a/app/containers/TextInput/FormTextInput.tsx +++ b/app/containers/TextInput/FormTextInput.tsx @@ -150,6 +150,7 @@ export const FormTextInput = ({ size={20} color={colors.fontDefault} style={[styles.iconContainer, styles.iconRight]} + accessible={false} /> ) : null} diff --git a/app/containers/Touch.tsx b/app/containers/Touch.tsx index aca34be8b2..40cc5046db 100644 --- a/app/containers/Touch.tsx +++ b/app/containers/Touch.tsx @@ -1,29 +1,35 @@ import React from 'react'; import { RectButton, RectButtonProps } from 'react-native-gesture-handler'; +import { View } from 'react-native'; import { useTheme } from '../theme'; export interface ITouchProps extends RectButtonProps { children: React.ReactNode; + accessible?: boolean; accessibilityLabel?: string; testID?: string; } -const Touch = React.forwardRef<RectButton, ITouchProps>(({ children, onPress, underlayColor, ...props }, ref) => { - const { colors } = useTheme(); +const Touch = React.forwardRef<RectButton, ITouchProps>( + ({ children, onPress, underlayColor, accessible, accessibilityLabel, ...props }, ref) => { + const { colors } = useTheme(); - return ( - <RectButton - ref={ref} - onPress={onPress} - activeOpacity={1} - underlayColor={underlayColor || colors.surfaceNeutral} - rippleColor={colors.surfaceNeutral} - {...props} - > - {children} - </RectButton> - ); -}); + return ( + // container for accessibility + <View accessible={accessible} accessibilityLabel={accessibilityLabel} accessibilityRole='button'> + <RectButton + ref={ref} + onPress={onPress} + activeOpacity={1} + underlayColor={underlayColor || colors.surfaceNeutral} + rippleColor={colors.surfaceNeutral} + {...props}> + {children} + </RectButton> + </View> + ); + } +); export default Touch; diff --git a/app/containers/UserItem.tsx b/app/containers/UserItem.tsx index 6fe87244c7..2887fe3fe5 100644 --- a/app/containers/UserItem.tsx +++ b/app/containers/UserItem.tsx @@ -6,6 +6,7 @@ import { CustomIcon, TIconsName } from './CustomIcon'; import sharedStyles from '../views/Styles'; import { isIOS } from '../lib/methods/helpers'; import { useTheme } from '../theme'; +import i18n from '../i18n'; const styles = StyleSheet.create({ button: { @@ -43,11 +44,15 @@ interface IUserItem { style?: StyleProp<ViewStyle>; icon?: TIconsName | null; iconColor?: string; + isChecked?: boolean; } -const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon, iconColor }: IUserItem) => { +const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon, iconColor, isChecked }: IUserItem) => { const { colors } = useTheme(); - + let label = `${name}`; + if (icon) { + label = `${name} ${isChecked ? i18n.t('Selected') : i18n.t('Unselected')}`; + } return ( <Pressable onPress={onPress} @@ -59,7 +64,8 @@ const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon, i style={({ pressed }: any) => ({ backgroundColor: isIOS && pressed ? colors.surfaceNeutral : 'transparent' })} - > + accessibilityLabel={label} + accessibilityRole='button'> <View style={[styles.container, styles.button, style]}> <Avatar text={username} size={30} style={styles.avatar} /> <View style={styles.textContainer}> diff --git a/app/containers/message/Message.tsx b/app/containers/message/Message.tsx index 93efa8237e..a1dbab8a11 100644 --- a/app/containers/message/Message.tsx +++ b/app/containers/message/Message.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +import React, { useContext, useMemo } from 'react'; import { View } from 'react-native'; import Touchable from 'react-native-platform-touchable'; @@ -20,6 +20,8 @@ import { themes } from '../../lib/constants'; import { IMessage, IMessageInner, IMessageTouchable } from './interfaces'; import { useTheme } from '../../theme'; import RightIcons from './Components/RightIcons'; +import i18n from '../../i18n'; +import { getInfoMessage } from './utils'; const MessageInner = React.memo((props: IMessageInner) => { if (props.isPreview) { @@ -139,6 +141,28 @@ const MessageTouchable = React.memo((props: IMessageTouchable & IMessage) => { backgroundColor = themes[theme].surfaceNeutral; } + // temp accessibilityLabel + const accessibilityLabel = useMemo(() => { + let label = ''; + label = props.isInfo ? (props.msg as string) : `${props.tmid ? `thread message ${props.msg}` : props.msg}`; + if (props.isThreadReply) { + label = `replying to ${props.tmid ? `thread message ${props.msg}` : props}`; + } + if (props.isThreadSequential) { + label = `thread message ${props.msg}`; + } + if (props.isEncrypted) { + label = i18n.t('Encrypted_message'); + } + if (props.isInfo) { + // @ts-ignore + label = getInfoMessage({ ...props }); + } + const hour = props.ts ? new Date(props.ts).toLocaleTimeString() : ''; + const user = props.useRealName ? props.author?.name : props.author?.username || ''; + return `${user} ${hour} ${label}`; + }, []); + if (props.hasError) { return ( <View> @@ -153,7 +177,7 @@ const MessageTouchable = React.memo((props: IMessageTouchable & IMessage) => { onPress={onPress} disabled={(props.isInfo && !props.isThreadReply) || props.archived || props.isTemp || props.type === 'jitsi_call_started'} style={{ backgroundColor }}> - <View> + <View accessible accessibilityLabel={accessibilityLabel}> <Message {...props} /> </View> </Touchable> diff --git a/app/ee/omnichannel/containers/OmnichannelHeader/index.tsx b/app/ee/omnichannel/containers/OmnichannelHeader/index.tsx index dc570346c2..bbbda4136b 100644 --- a/app/ee/omnichannel/containers/OmnichannelHeader/index.tsx +++ b/app/ee/omnichannel/containers/OmnichannelHeader/index.tsx @@ -63,6 +63,7 @@ const OmnichannelStatus = memo(({ searching, goQueue, queueSize, user }: IOmnich title='Omnichannel' color={themes[theme].fontDefault} onPress={toggleLivechat} + additionalAcessibilityLabel={status} right={() => ( <View style={styles.omnichannelRightContainer}> <Switch value={status} onValueChange={toggleLivechat} /> diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 67f4d083c8..ef334c408d 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -106,6 +106,7 @@ "Chats": "Chats", "Check_again": "Check again", "Check_canned_responses": "Check on canned responses.", + "Checked": "Checked", "Choose": "Choose", "Choose_file": "Choose file", "Choose_from_library": "Choose from library", @@ -203,6 +204,7 @@ "Disable_encryption_description": "Disabling E2EE compromises privacy. You can re-enable it later if needed. Proceed with caution.", "Disable_encryption_title": "Disable encryption", "Disable_writing_in_room": "Disable writing in room", + "Disabled": "Disabled", "Disabled_E2E_Encryption_for_this_room": "disabled E2E encryption for this room", "Discard": "Discard", "Discard_changes": "Discard changes?", @@ -252,6 +254,7 @@ "Enable_encryption_title": "Enable encryption", "Enable_Message_Parser": "Enable message parser", "Enable_writing_in_room": "Enable writing in room", + "Enabled": "Enabled", "Enabled_E2E_Encryption_for_this_room": "enabled E2E encryption for this room", "Encrypted": "Encrypted", "Encrypted_file": "Encrypted file", @@ -658,6 +661,7 @@ "Select_Team_Channels": "Select the team's channels you would like to leave.", "Select_Team_Channels_To_Delete": "Select the team’s channels you would like to delete, the ones you do not select will be moved to the workspace. \n\nNotice that public channels will be public and visible to everyone.", "Select_Users": "Select users", + "Selected": "Selected", "Send": "Send", "Send_audio_message": "Send audio message", "Send_crash_report": "Send crash report", @@ -736,9 +740,6 @@ "This_will_remove_all_data_from_this_server": "This will remove all data from this workspace.", "Thread": "Thread", "Threads": "Threads", - "Threads_displaying_all": "Displaying all", - "Threads_displaying_following": "Displaying following", - "Threads_displaying_unread": "Displaying unread", "Timezone": "Timezone", "To_continue_using_RocketChat": "To continue using the mobile app, you need to change your password.", "Token_expired": "Your session has expired. Please log in again.", @@ -755,6 +756,7 @@ "unarchive": "unarchive", "unauthorized": "Unauthorized", "Unblock": "Unblock", + "Unchecked": "Unchecked", "Unfollowed_thread": "Unfollowed thread", "Unignore": "Unignore", "Unmute": "Unmute", @@ -764,6 +766,7 @@ "Unread": "Unread", "unread_messages": "unread", "Unread_on_top": "Unread on top", + "Unselected": "Unselected", "Unstar": "Unstar", "Unsupported_format": "Unsupported format", "Unsupported_system_message": "Unsupported system message", diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json index 7af4a558ed..568349fc1e 100644 --- a/app/i18n/locales/pt-BR.json +++ b/app/i18n/locales/pt-BR.json @@ -104,6 +104,7 @@ "Chats": "Conversas", "Check_again": "Verificar novamente", "Check_canned_responses": "Verifique nas respostas predefinidas", + "Checked": "Marcado", "Choose": "Escolher", "Choose_file": "Enviar arquivo", "Choose_from_library": "Escolha da biblioteca", @@ -200,6 +201,7 @@ "Disable_encryption_description": "Desabilitar a criptografia compromete sua privacidade. Você pode reabilitá-la depois se precisar. Continue com cautela.", "Disable_encryption_title": "Desabilitar criptografia", "Disable_writing_in_room": "Desabilitar escrita na sala", + "Disabled": "Desabilitado", "Disabled_E2E_Encryption_for_this_room": "desabilitou criptografia para essa sala", "Discard": "Descartar", "Discard_changes": "Descartar alterações?", @@ -247,6 +249,7 @@ "Enable_encryption_title": "Habilitar criptografia", "Enable_Message_Parser": "Habilita analisador de mensagem", "Enable_writing_in_room": "Permitir escrita na sala", + "Enabled": "Habilitado", "Enabled_E2E_Encryption_for_this_room": "habilitou criptografia para essa sala", "Encrypted": "Criptografado", "Encrypted_file": "Arquivo criptografado", @@ -646,6 +649,7 @@ "Select_Team_Channels": "Selecione os canais do time que você gostaria de deixar.", "Select_Team_Channels_To_Delete": "Selecione os canais do time que você gostaria de excluir. Aqueles que você não selecionar serão movidos para a workspace.\n\nNote que os canais públicos serão públicos e visíveis para todos.", "Select_Users": "Selecionar usuários", + "Selected": "Selecionado", "Send": "Enviar", "Send_audio_message": "Enviar mensagem de áudio", "Send_crash_report": "Enviar relatório de erros", @@ -741,6 +745,7 @@ "unarchive": "desarquivar", "unauthorized": "Não autorizado", "Unblock": "Desbloquear", + "Unchecked": "Desmarcado", "Unfollowed_thread": "Parou de seguir tópico", "Unignore": "Deixar de ignorar", "Unmute": "Permitir que o usuário fale", @@ -750,6 +755,7 @@ "Unread": "Não lidas", "unread_messages": "não lidas", "Unread_on_top": "Não lidas no topo", + "Unselected": "Não selecionado", "Unstar": "Remover favorito", "Unsupported_format": "Formato não suportado", "Unsupported_system_message": "Mensagem de sistema não suportada", diff --git a/app/index.tsx b/app/index.tsx index 8131fdac8f..338fb9c01d 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Dimensions, EmitterSubscription, Linking } from 'react-native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; -import Orientation from 'react-native-orientation-locker'; import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context'; import RNScreens from 'react-native-screens'; import { Provider } from 'react-redux'; @@ -93,9 +92,6 @@ export default class Root extends React.Component<{}, IState> { }; if (isTablet) { this.initTablet(); - Orientation.unlockAllOrientations(); - } else { - Orientation.lockToPortrait(); } setNativeTheme(theme); } @@ -208,8 +204,7 @@ export default class Root extends React.Component<{}, IState> { themePreferences, setTheme: this.setTheme, colors: colors[theme] - }} - > + }}> <DimensionsContext.Provider value={{ width, @@ -217,8 +212,7 @@ export default class Root extends React.Component<{}, IState> { scale, fontScale, setDimensions: this.setDimensions - }} - > + }}> <GestureHandlerRootView> <ActionSheetProvider> <AppContainer /> diff --git a/app/lib/methods/formatLastMessage.ts b/app/lib/methods/formatLastMessage.ts new file mode 100644 index 0000000000..8491dc0159 --- /dev/null +++ b/app/lib/methods/formatLastMessage.ts @@ -0,0 +1,53 @@ +import { ILastMessageProps } from '../../containers/RoomItem/interfaces'; +import I18n from '../../i18n'; +import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../constants'; + +export const formatLastMessage = ({ lastMessage, type, showLastMessage, username, useRealName }: Partial<ILastMessageProps>) => { + if (!showLastMessage) { + return ''; + } + if (!lastMessage || !lastMessage.u) { + return I18n.t('No_Message'); + } + if (lastMessage.t === 'jitsi_call_started') { + const { u } = lastMessage; + return I18n.t('Started_call', { userBy: u.username }); + } + + let prefix = ''; + const isLastMessageSentByMe = lastMessage.u.username === username; + + if (!lastMessage.msg && lastMessage.attachments && Object.keys(lastMessage.attachments).length) { + const userAttachment = () => { + if (isLastMessageSentByMe) { + return I18n.t('You'); + } + if (useRealName && lastMessage.u.name) { + return lastMessage.u.name; + } + return lastMessage.u.username; + }; + return I18n.t('User_sent_an_attachment', { user: userAttachment() }); + } + + // Encrypted message pending decrypt + if (lastMessage.t === E2E_MESSAGE_TYPE && lastMessage.e2e !== E2E_STATUS.DONE) { + lastMessage.msg = I18n.t('Encrypted_message'); + } + + if (isLastMessageSentByMe) { + prefix = I18n.t('You_colon'); + } else if (type !== 'd') { + const { + u: { name } + } = lastMessage; + prefix = `${useRealName ? name : lastMessage.u.username}: `; + } + + if (lastMessage.t === 'videoconf') { + prefix = ''; + lastMessage.msg = I18n.t('Call_started'); + } + + return `${prefix}${lastMessage.msg}`; +}; diff --git a/app/lib/methods/helpers/log/events.ts b/app/lib/methods/helpers/log/events.ts index f028d0e41b..2b7884334c 100644 --- a/app/lib/methods/helpers/log/events.ts +++ b/app/lib/methods/helpers/log/events.ts @@ -54,7 +54,6 @@ export default { SET_STATUS_FAIL: 'set_status_fail', // ROOMS LIST VIEW - RL_TOGGLE_SERVER_DROPDOWN: 'rl_toggle_server_dropdown', RL_ADD_SERVER: 'rl_add_server', RL_CHANGE_SERVER: 'rl_change_server', RL_GO_NEW_MSG: 'rl_go_new_msg', diff --git a/app/lib/methods/helpers/navigation/index.ts b/app/lib/methods/helpers/navigation/index.ts index 676b4fb4f4..e20101c47f 100644 --- a/app/lib/methods/helpers/navigation/index.ts +++ b/app/lib/methods/helpers/navigation/index.ts @@ -3,7 +3,6 @@ import { DarkTheme, DefaultTheme } from '@react-navigation/native'; import { themes } from '../../../constants'; import { TSupportedThemes } from '../../../../theme'; -import { isIOS } from '../deviceInfo'; import sharedStyles from '../../../../views/Styles'; export * from './animations'; @@ -29,9 +28,6 @@ export const drawerStyle = { width: 320 }; -// TODO: Remove it once we migrate dropdowns to action sheet -export const headerHeight = isIOS ? 50 : 56; - export const themedHeader = (theme: TSupportedThemes) => ({ headerStyle: { ...borderBottom(theme), diff --git a/app/reducers/rooms.test.ts b/app/reducers/rooms.test.ts index 4bf1312ec8..7e2945bd1a 100644 --- a/app/reducers/rooms.test.ts +++ b/app/reducers/rooms.test.ts @@ -1,13 +1,11 @@ import { closeSearchHeader, - closeServerDropdown, openSearchHeader, roomsFailure, roomsRefresh, roomsRequest, roomsSuccess, - setSearch, - toggleServerDropdown + setSearch } from '../actions/rooms'; import { mockedStore } from './mockedStore'; import { initialState } from './rooms'; @@ -45,18 +43,6 @@ describe('test selectedUsers reducer', () => { expect(state.searchText).toEqual('dog'); }); - it('should return modified store after call closeServerDropdown', () => { - mockedStore.dispatch(closeServerDropdown()); - const state = mockedStore.getState().rooms; - expect(state.closeServerDropdown).toEqual(!initialState.closeServerDropdown); - }); - - it('should return modified store after call toggleServerDropdown', () => { - mockedStore.dispatch(toggleServerDropdown()); - const state = mockedStore.getState().rooms; - expect(state.showServerDropdown).toEqual(!initialState.showServerDropdown); - }); - it('should return modified store after call openSearchHeader', () => { mockedStore.dispatch(openSearchHeader()); const state = mockedStore.getState().rooms; diff --git a/app/reducers/rooms.ts b/app/reducers/rooms.ts index 1a67050865..6b309c2ac4 100644 --- a/app/reducers/rooms.ts +++ b/app/reducers/rooms.ts @@ -7,8 +7,6 @@ export interface IRooms { failure: boolean; errorMessage: Record<string, any> | string; searchText: string; - showServerDropdown: boolean; - closeServerDropdown: boolean; showSearchHeader: boolean; } @@ -18,8 +16,6 @@ export const initialState: IRooms = { failure: false, errorMessage: {}, searchText: '', - showServerDropdown: false, - closeServerDropdown: false, showSearchHeader: false }; @@ -57,16 +53,6 @@ export default function rooms(state = initialState, action: IRoomsAction): IRoom ...state, searchText: action.searchText }; - case ROOMS.CLOSE_SERVER_DROPDOWN: - return { - ...state, - closeServerDropdown: !state.closeServerDropdown - }; - case ROOMS.TOGGLE_SERVER_DROPDOWN: - return { - ...state, - showServerDropdown: !state.showServerDropdown - }; case ROOMS.OPEN_SEARCH_HEADER: return { ...state, diff --git a/app/views/AddExistingChannelView/index.tsx b/app/views/AddExistingChannelView/index.tsx index c5499eacbb..7b8d7be32b 100644 --- a/app/views/AddExistingChannelView/index.tsx +++ b/app/views/AddExistingChannelView/index.tsx @@ -163,6 +163,8 @@ const AddExistingChannelView = () => { testID={`add-existing-channel-view-item-${item.name}`} left={() => <List.Icon name={icon} />} right={() => (isChecked(item.rid) ? <List.Icon name='check' color={colors.fontHint} /> : null)} + additionalAcessibilityLabel={isChecked(item.rid)} + additionalAcessibilityLabelCheck /> ); }} diff --git a/app/views/AutoTranslateView/index.tsx b/app/views/AutoTranslateView/index.tsx index cf93776c63..38a75625bf 100644 --- a/app/views/AutoTranslateView/index.tsx +++ b/app/views/AutoTranslateView/index.tsx @@ -105,6 +105,8 @@ const AutoTranslateView = (): React.ReactElement => { ) : null } translateTitle={false} + additionalAcessibilityLabel={selectedLanguage === language} + additionalAcessibilityLabelCheck /> )); @@ -124,6 +126,7 @@ const AutoTranslateView = (): React.ReactElement => { right={() => ( <Switch testID='auto-translate-view-switch' value={enableAutoTranslate} onValueChange={toggleAutoTranslate} /> )} + additionalAcessibilityLabel={enableAutoTranslate} /> <List.Separator /> </> diff --git a/app/views/CannedResponsesListView/DepartmentFilter/DepartmentItemFilter.tsx b/app/views/CannedResponsesListView/DepartmentFilter/DepartmentItemFilter.tsx new file mode 100644 index 0000000000..2b67684907 --- /dev/null +++ b/app/views/CannedResponsesListView/DepartmentFilter/DepartmentItemFilter.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { StyleSheet, Text, View } from 'react-native'; + +import { ILivechatDepartment } from '../../../definitions/ILivechatDepartment'; +import { useTheme } from '../../../theme'; +import Touch from '../../../containers/Touch'; +import { CustomIcon } from '../../../containers/CustomIcon'; +import sharedStyles from '../../Styles'; + +interface IDepartmentItemFilter { + currentDepartment: ILivechatDepartment; + value: ILivechatDepartment; + onPress: (value: ILivechatDepartment) => void; +} + +export const ROW_HEIGHT = 44; + +const styles = StyleSheet.create({ + container: { + paddingVertical: 11, + height: ROW_HEIGHT, + paddingHorizontal: 16, + flexDirection: 'row', + alignItems: 'center' + }, + text: { + flex: 1, + fontSize: 16, + ...sharedStyles.textRegular + } +}); + +const DepartmentItemFilter = ({ currentDepartment, value, onPress }: IDepartmentItemFilter): JSX.Element => { + const { colors } = useTheme(); + const iconName = currentDepartment?._id === value?._id ? 'check' : null; + + return ( + <Touch onPress={() => onPress(value)} style={{ backgroundColor: colors.surfaceRoom }}> + <View style={styles.container}> + <Text style={[styles.text, { color: colors.fontSecondaryInfo }]}>{value?.name}</Text> + {iconName ? <CustomIcon name={iconName} size={22} color={colors.fontSecondaryInfo} /> : null} + </View> + </Touch> + ); +}; + +export default DepartmentItemFilter; diff --git a/app/views/CannedResponsesListView/DepartmentFilter/index.tsx b/app/views/CannedResponsesListView/DepartmentFilter/index.tsx new file mode 100644 index 0000000000..00eb33f693 --- /dev/null +++ b/app/views/CannedResponsesListView/DepartmentFilter/index.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { FlatList, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { useTheme } from '../../../theme'; +import * as List from '../../../containers/List'; +import DepartmentItemFilter, { ROW_HEIGHT } from './DepartmentItemFilter'; +import { ILivechatDepartment } from '../../../definitions/ILivechatDepartment'; + +const MAX_ROWS = 5; + +interface IDepartmentFilterProps { + currentDepartment: ILivechatDepartment; + onDepartmentSelected: (value: ILivechatDepartment) => void; + departments: ILivechatDepartment[]; +} + +const DepartmentFilter = ({ currentDepartment, onDepartmentSelected, departments }: IDepartmentFilterProps) => { + const { colors } = useTheme(); + const insets = useSafeAreaInsets(); + + return ( + <View + style={{ + backgroundColor: colors.surfaceRoom, + borderColor: colors.strokeLight, + marginBottom: insets.bottom + }}> + <FlatList + style={{ maxHeight: MAX_ROWS * ROW_HEIGHT }} + data={departments} + keyExtractor={item => item._id} + renderItem={({ item }) => ( + <DepartmentItemFilter onPress={onDepartmentSelected} currentDepartment={currentDepartment} value={item} /> + )} + ItemSeparatorComponent={List.Separator} + /> + </View> + ); +}; + +export default DepartmentFilter; diff --git a/app/views/CannedResponsesListView/Dropdown/DropdownItem.tsx b/app/views/CannedResponsesListView/Dropdown/DropdownItem.tsx deleted file mode 100644 index 840bd07ea3..0000000000 --- a/app/views/CannedResponsesListView/Dropdown/DropdownItem.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import { StyleSheet, Text, View } from 'react-native'; - -import { themes } from '../../../lib/constants'; -import { useTheme } from '../../../theme'; -import Touch from '../../../containers/Touch'; -import { CustomIcon, TIconsName } from '../../../containers/CustomIcon'; -import sharedStyles from '../../Styles'; - -export const ROW_HEIGHT = 44; - -const styles = StyleSheet.create({ - container: { - paddingVertical: 11, - height: ROW_HEIGHT, - paddingHorizontal: 16, - flexDirection: 'row', - alignItems: 'center' - }, - text: { - flex: 1, - fontSize: 16, - ...sharedStyles.textRegular - } -}); - -interface IDropdownItem { - text: string; - iconName: TIconsName | null; - onPress: () => void; -} - -const DropdownItem = React.memo(({ onPress, iconName, text }: IDropdownItem) => { - const { theme } = useTheme(); - - return ( - <Touch onPress={onPress} style={{ backgroundColor: themes[theme].surfaceRoom }}> - <View style={styles.container}> - <Text style={[styles.text, { color: themes[theme].fontSecondaryInfo }]}>{text}</Text> - {iconName ? <CustomIcon name={iconName} size={22} color={themes[theme].fontSecondaryInfo} /> : null} - </View> - </Touch> - ); -}); - -export default DropdownItem; diff --git a/app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.tsx b/app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.tsx deleted file mode 100644 index fa165c67c5..0000000000 --- a/app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; - -import { ILivechatDepartment } from '../../../definitions/ILivechatDepartment'; -import DropdownItem from './DropdownItem'; - -interface IDropdownItemFilter { - currentDepartment: ILivechatDepartment; - value: ILivechatDepartment; - onPress: (value: ILivechatDepartment) => void; -} - -const DropdownItemFilter = ({ currentDepartment, value, onPress }: IDropdownItemFilter): JSX.Element => ( - <DropdownItem - text={value?.name} - iconName={currentDepartment?._id === value?._id ? 'check' : null} - onPress={() => onPress(value)} - /> -); - -export default DropdownItemFilter; diff --git a/app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.tsx b/app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.tsx deleted file mode 100644 index 5ebcaf8601..0000000000 --- a/app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; - -import { ILivechatDepartment } from '../../../definitions/ILivechatDepartment'; -import DropdownItem from './DropdownItem'; - -interface IDropdownItemHeader { - department: ILivechatDepartment; - onPress: () => void; -} - -const DropdownItemHeader = ({ department, onPress }: IDropdownItemHeader): React.ReactElement => ( - <DropdownItem text={department?.name} iconName='filter' onPress={onPress} /> -); - -export default DropdownItemHeader; diff --git a/app/views/CannedResponsesListView/Dropdown/index.tsx b/app/views/CannedResponsesListView/Dropdown/index.tsx deleted file mode 100644 index b50c394ed7..0000000000 --- a/app/views/CannedResponsesListView/Dropdown/index.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import { Animated, Easing, FlatList, TouchableWithoutFeedback } from 'react-native'; - -import styles from '../styles'; -import { useTheme } from '../../../theme'; -import * as List from '../../../containers/List'; -import DropdownItemFilter from './DropdownItemFilter'; -import DropdownItemHeader from './DropdownItemHeader'; -import { ROW_HEIGHT } from './DropdownItem'; -import { ILivechatDepartment } from '../../../definitions/ILivechatDepartment'; - -const ANIMATION_DURATION = 200; -const HEIGHT_DESTINATION = 0; -const MAX_ROWS = 5; - -interface IDropdownProps { - currentDepartment: ILivechatDepartment; - onClose: () => void; - onDepartmentSelected: (value: ILivechatDepartment) => void; - departments: ILivechatDepartment[]; -} - -const Dropdown = ({ currentDepartment, onClose, onDepartmentSelected, departments }: IDropdownProps) => { - const animatedValue = useRef(new Animated.Value(0)).current; - const { colors } = useTheme(); - - useEffect(() => { - Animated.timing(animatedValue, { - toValue: 1, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - }).start(); - }, [animatedValue]); - - const close = () => { - Animated.timing(animatedValue, { - toValue: 0, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - }).start(() => onClose()); - }; - - const translateY = animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [-300, HEIGHT_DESTINATION] // approximated height of the component when closed/open - }); - - const backdropOpacity = animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [0, colors.backdropOpacity] - }); - - return ( - <> - <TouchableWithoutFeedback onPress={close}> - <Animated.View - style={[ - styles.backdrop, - { - backgroundColor: colors.backdropColor, - opacity: backdropOpacity, - top: HEIGHT_DESTINATION - } - ]} - /> - </TouchableWithoutFeedback> - <Animated.View - style={[ - styles.dropdownContainer, - { - transform: [{ translateY }], - backgroundColor: colors.surfaceRoom, - borderColor: colors.strokeLight - } - ]}> - <DropdownItemHeader department={currentDepartment} onPress={close} /> - <List.Separator /> - <FlatList - style={{ maxHeight: MAX_ROWS * ROW_HEIGHT }} - data={departments} - keyExtractor={item => item._id} - renderItem={({ item }) => ( - <DropdownItemFilter onPress={onDepartmentSelected} currentDepartment={currentDepartment} value={item} /> - )} - keyboardShouldPersistTaps='always' - /> - </Animated.View> - </> - ); -}; - -export default Dropdown; diff --git a/app/views/CannedResponsesListView/index.tsx b/app/views/CannedResponsesListView/index.tsx index 7815a25e6e..ebf3886c5a 100644 --- a/app/views/CannedResponsesListView/index.tsx +++ b/app/views/CannedResponsesListView/index.tsx @@ -6,6 +6,7 @@ import { HeaderBackButton } from '@react-navigation/elements'; import database from '../../lib/database'; import I18n from '../../i18n'; +import { hideActionSheetRef, showActionSheetRef } from '../../containers/ActionSheet'; import SafeAreaView from '../../containers/SafeAreaView'; import StatusBar from '../../containers/StatusBar'; import ActivityIndicator from '../../containers/ActivityIndicator'; @@ -18,8 +19,7 @@ import * as List from '../../containers/List'; import { themes } from '../../lib/constants'; import log from '../../lib/methods/helpers/log'; import CannedResponseItem from './CannedResponseItem'; -import Dropdown from './Dropdown'; -import DropdownItemHeader from './Dropdown/DropdownItemHeader'; +import DepartmentFilter from './DepartmentFilter'; import styles from './styles'; import { ICannedResponse } from '../../definitions/ICannedResponse'; import { ChatsStackParamList } from '../../stacks/types'; @@ -57,11 +57,8 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView const [cannedResponses, setCannedResponses] = useState<ICannedResponse[]>([]); const [cannedResponsesScopeName, setCannedResponsesScopeName] = useState<ICannedResponse[]>([]); const [departments, setDepartments] = useState<ILivechatDepartment[]>([]); - - // states used by the filter in Header and Dropdown const [isSearching, setIsSearching] = useState(false); const [currentDepartment, setCurrentDepartment] = useState(fixedScopes[0]); - const [showFilterDropdown, setShowFilterDropDown] = useState(false); // states used to do a fetch by onChangeText, onDepartmentSelect and onEndReached const [searchText, setSearchText] = useState(''); @@ -200,8 +197,8 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView setCurrentDepartment(value); setScope(department); setDepartmentId(depId); - setShowFilterDropDown(false); searchCallback(searchText, department, depId); + hideActionSheetRef(); }; const onEndReached = async () => { @@ -249,6 +246,7 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView ), headerRight: () => ( <HeaderButton.Container> + <HeaderButton.Item iconName='filter' onPress={showFilters} /> <HeaderButton.Item iconName='search' onPress={() => setIsSearching(true)} /> </HeaderButton.Container> ) @@ -268,36 +266,24 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView useEffect(() => { setHeader(); - }, [isSearching]); - - const showDropdown = () => { - if (isSearching) { - setSearchText(''); - setIsSearching(false); - } - setShowFilterDropDown(true); - }; + }, [isSearching, departments, currentDepartment]); - const renderFlatListHeader = () => { - if (!departments.length) { - return null; - } - return ( - <> - <DropdownItemHeader department={currentDepartment} onPress={showDropdown} /> - <List.Separator /> - </> - ); + const showFilters = () => { + showActionSheetRef({ + children: ( + <DepartmentFilter + departments={departments} + currentDepartment={currentDepartment} + onDepartmentSelected={onDepartmentSelect} + /> + ), + enableContentPanningGesture: false + }); }; const renderContent = () => { if (!cannedResponsesScopeName.length && !loading) { - return ( - <> - {renderFlatListHeader()} - <BackgroundContainer text={I18n.t('No_canned_responses')} /> - </> - ); + return <BackgroundContainer text={I18n.t('No_canned_responses')} />; } return ( <FlatList @@ -316,8 +302,6 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView /> )} keyExtractor={item => item._id || item.shortcut} - ListHeaderComponent={renderFlatListHeader} - stickyHeaderIndices={[0]} onEndReached={onEndReached} onEndReachedThreshold={0.5} ItemSeparatorComponent={List.Separator} @@ -330,14 +314,6 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView <SafeAreaView> <StatusBar /> {renderContent()} - {showFilterDropdown ? ( - <Dropdown - departments={departments} - currentDepartment={currentDepartment} - onDepartmentSelected={onDepartmentSelect} - onClose={() => setShowFilterDropDown(false)} - /> - ) : null} </SafeAreaView> ); }; diff --git a/app/views/CannedResponsesListView/styles.ts b/app/views/CannedResponsesListView/styles.ts index f0e9718671..c148743605 100644 --- a/app/views/CannedResponsesListView/styles.ts +++ b/app/views/CannedResponsesListView/styles.ts @@ -6,15 +6,6 @@ export default StyleSheet.create({ list: { flex: 1 }, - dropdownContainer: { - width: '100%', - position: 'absolute', - top: 0, - borderBottomWidth: StyleSheet.hairlineWidth - }, - backdrop: { - ...StyleSheet.absoluteFillObject - }, wrapCannedItem: { minHeight: 117, maxHeight: 141, diff --git a/app/views/CreateChannelView/RoomSettings/SwitchItem.tsx b/app/views/CreateChannelView/RoomSettings/SwitchItem.tsx index 82fadd5e6d..14de43d2b5 100644 --- a/app/views/CreateChannelView/RoomSettings/SwitchItem.tsx +++ b/app/views/CreateChannelView/RoomSettings/SwitchItem.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { StyleSheet, Text, View, SwitchProps } from 'react-native'; +import { StyleSheet, Text, View, SwitchProps, Pressable } from 'react-native'; import I18n from '../../../i18n'; import { useTheme } from '../../../theme'; @@ -40,14 +40,21 @@ export const SwitchItem = ({ id, value, label, hint, onValueChange, disabled = f const { colors } = useTheme(); return ( - <View style={[styles.switchContainer, { backgroundColor: colors.surfaceRoom }]}> - <View style={styles.switchTextContainer}> - <Text style={[styles.label, { color: colors.fontTitlesLabels }]}>{I18n.t(label)}</Text> - <Text testID={`create-channel-${id}-hint`} style={[styles.hint, { color: colors.fontSecondaryInfo }]}> - {I18n.t(hint)} - </Text> + <Pressable + disabled={disabled} + onPress={() => onValueChange(!value)} + accessible + accessibilityLabel={`${I18n.t(label)}, ${value ? I18n.t('Enabled') : I18n.t('Disabled')}, ${I18n.t(hint)}`} + accessibilityRole='switch'> + <View style={[styles.switchContainer, { backgroundColor: colors.surfaceRoom }]}> + <View style={styles.switchTextContainer}> + <Text style={[styles.label, { color: colors.fontTitlesLabels }]}>{I18n.t(label)}</Text> + <Text testID={`create-channel-${id}-hint`} style={[styles.hint, { color: colors.fontSecondaryInfo }]}> + {I18n.t(hint)} + </Text> + </View> + <Switch value={value} onValueChange={onValueChange} testID={`create-channel-${id}`} disabled={disabled} /> </View> - <Switch value={value} onValueChange={onValueChange} testID={`create-channel-${id}`} disabled={disabled} /> - </View> + </Pressable> ); }; diff --git a/app/views/CreateDiscussionView/index.tsx b/app/views/CreateDiscussionView/index.tsx index b337dc6fd1..541920a8db 100644 --- a/app/views/CreateDiscussionView/index.tsx +++ b/app/views/CreateDiscussionView/index.tsx @@ -185,6 +185,7 @@ class CreateChannelView extends React.Component<ICreateChannelViewProps, ICreate title='Encrypted' testID='room-actions-encrypt' right={() => <Switch value={encrypted} onValueChange={this.onEncryptedChange} />} + additionalAcessibilityLabel={encrypted} /> </> ) : null} diff --git a/app/views/DefaultBrowserView/Item.tsx b/app/views/DefaultBrowserView/Item.tsx index 2cc0e3bd6f..471896d880 100644 --- a/app/views/DefaultBrowserView/Item.tsx +++ b/app/views/DefaultBrowserView/Item.tsx @@ -27,6 +27,8 @@ const Item = ({ title, value, browser, changeDefaultBrowser }: IRenderItem) => { testID={`default-browser-view-${title}`} right={() => (isSelected ? <List.Icon name='check' color={colors.badgeBackgroundLevel2} /> : null)} translateTitle={false} + additionalAcessibilityLabel={isSelected} + additionalAcessibilityLabelCheck /> ); }; diff --git a/app/views/DefaultBrowserView/index.tsx b/app/views/DefaultBrowserView/index.tsx index 0e83791fa1..02669ed15e 100644 --- a/app/views/DefaultBrowserView/index.tsx +++ b/app/views/DefaultBrowserView/index.tsx @@ -96,7 +96,7 @@ const DefaultBrowserView = () => { )} ListHeaderComponent={ <> - <List.Header title='Choose_where_you_want_links_be_opened' /> + <List.Header title='Choose_where_you_want_links_be_opened' numberOfLines={2} /> <List.Separator /> </> } diff --git a/app/views/DirectoryView/Options.tsx b/app/views/DirectoryView/Options.tsx index f09f8c92a6..53530f95e3 100644 --- a/app/views/DirectoryView/Options.tsx +++ b/app/views/DirectoryView/Options.tsx @@ -1,26 +1,20 @@ -import React, { useEffect, useRef } from 'react'; -import { Animated, Easing, Text, TouchableWithoutFeedback, View } from 'react-native'; +import React from 'react'; +import { Text, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import Touch from '../../containers/Touch'; import { CustomIcon, TIconsName } from '../../containers/CustomIcon'; import Check from '../../containers/Check'; +import * as List from '../../containers/List'; import I18n from '../../i18n'; import styles from './styles'; import Switch from '../../containers/Switch'; import { useTheme } from '../../theme'; -const ANIMATION_DURATION = 200; -const ANIMATION_PROPS = { - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true -}; - interface IDirectoryOptionsProps { type: string; globalUsers: boolean; isFederationEnabled: boolean; - close: Function; changeType: Function; toggleWorkspace(): void; } @@ -29,26 +23,11 @@ const DirectoryOptions = ({ type: propType, globalUsers, isFederationEnabled, - close: onClose, changeType, toggleWorkspace }: IDirectoryOptionsProps) => { - const animatedValue = useRef(new Animated.Value(0)).current; const { colors } = useTheme(); - - useEffect(() => { - Animated.timing(animatedValue, { - toValue: 1, - ...ANIMATION_PROPS - }).start(); - }, [animatedValue]); - - const close = () => { - Animated.timing(animatedValue, { - toValue: 0, - ...ANIMATION_PROPS - }).start(() => onClose()); - }; + const insets = useSafeAreaInsets(); const renderItem = (itemType: string) => { let text = 'Users'; @@ -64,62 +43,40 @@ const DirectoryOptions = ({ } return ( - <Touch onPress={() => changeType(itemType)} style={styles.dropdownItemButton} accessibilityLabel={I18n.t(text)}> - <View style={styles.dropdownItemContainer}> - <CustomIcon name={icon} size={22} color={colors.fontDefault} style={styles.dropdownItemIcon} /> - <Text style={[styles.dropdownItemText, { color: colors.fontDefault }]}>{I18n.t(text)}</Text> + <Touch onPress={() => changeType(itemType)} style={styles.filterItemButton} accessibilityLabel={I18n.t(text)} accessible> + <View style={styles.filterItemContainer}> + <CustomIcon name={icon} size={22} color={colors.fontDefault} style={styles.filterItemIcon} /> + <Text style={[styles.filterItemText, { color: colors.fontDefault }]}>{I18n.t(text)}</Text> {propType === itemType ? <Check /> : null} </View> </Touch> ); }; - const translateY = animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [-326, 0] - }); - - const backdropOpacity = animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [0, colors.backdropOpacity] - }); - return ( - <> - <TouchableWithoutFeedback onPress={close}> - <Animated.View style={[styles.backdrop, { backgroundColor: colors.backdropColor, opacity: backdropOpacity }]} /> - </TouchableWithoutFeedback> - <Animated.View style={[styles.dropdownContainer, { transform: [{ translateY }], backgroundColor: colors.surfaceRoom }]}> - <Touch onPress={close} accessibilityLabel={I18n.t('Search_by')}> - <View style={[styles.dropdownContainerHeader, styles.dropdownItemContainer, { borderColor: colors.strokeLight }]}> - <Text style={[styles.dropdownToggleText, { color: colors.fontSecondaryInfo }]}>{I18n.t('Search_by')}</Text> - <CustomIcon - style={[styles.dropdownItemIcon, styles.inverted]} - size={22} - name='chevron-down' - color={colors.fontHint} - /> - </View> - </Touch> - {renderItem('channels')} - {renderItem('users')} - {renderItem('teams')} - {isFederationEnabled ? ( - <> - <View style={[styles.dropdownSeparator, { backgroundColor: colors.strokeLight }]} /> - <View style={[styles.dropdownItemContainer, styles.globalUsersContainer]}> - <View style={styles.globalUsersTextContainer}> - <Text style={[styles.dropdownItemText, { color: colors.fontHint }]}>{I18n.t('Search_global_users')}</Text> - <Text style={[styles.dropdownItemDescription, { color: colors.fontHint }]}> - {I18n.t('Search_global_users_description')} - </Text> - </View> - <Switch value={globalUsers} onValueChange={toggleWorkspace} /> + <View style={{ backgroundColor: colors.surfaceRoom, marginBottom: insets.bottom }}> + <List.Separator /> + {renderItem('channels')} + <List.Separator /> + {renderItem('users')} + <List.Separator /> + {renderItem('teams')} + <List.Separator /> + {isFederationEnabled ? ( + <> + <List.Separator /> + <View style={[styles.filterItemContainer, styles.globalUsersContainer]}> + <View style={styles.globalUsersTextContainer}> + <Text style={[styles.filterItemText, { color: colors.fontHint }]}>{I18n.t('Search_global_users')}</Text> + <Text style={[styles.filterItemDescription, { color: colors.fontHint }]}> + {I18n.t('Search_global_users_description')} + </Text> </View> - </> - ) : null} - </Animated.View> - </> + <Switch value={globalUsers} onValueChange={toggleWorkspace} /> + </View> + </> + ) : null} + </View> ); }; diff --git a/app/views/DirectoryView/index.tsx b/app/views/DirectoryView/index.tsx index 4902a990c3..7c872a363b 100644 --- a/app/views/DirectoryView/index.tsx +++ b/app/views/DirectoryView/index.tsx @@ -1,18 +1,17 @@ import React from 'react'; -import { FlatList, ListRenderItem, Text, View } from 'react-native'; +import { FlatList, ListRenderItem } from 'react-native'; import { connect } from 'react-redux'; import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack'; import { CompositeNavigationProp } from '@react-navigation/native'; +import { hideActionSheetRef, showActionSheetRef } from '../../containers/ActionSheet'; import { ChatsStackParamList } from '../../stacks/types'; import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types'; import * as List from '../../containers/List'; -import Touch from '../../containers/Touch'; import DirectoryItem from '../../containers/DirectoryItem'; import sharedStyles from '../Styles'; import I18n from '../../i18n'; import SearchBox from '../../containers/SearchBox'; -import { CustomIcon, TIconsName } from '../../containers/CustomIcon'; import StatusBar from '../../containers/StatusBar'; import ActivityIndicator from '../../containers/ActivityIndicator'; import * as HeaderButton from '../../containers/HeaderButton'; @@ -47,22 +46,11 @@ interface IDirectoryViewState { loading: boolean; text: string; total: number; - showOptionsDropdown: boolean; globalUsers: boolean; type: string; } class DirectoryView extends React.Component<IDirectoryViewProps, IDirectoryViewState> { - static navigationOptions = ({ navigation, isMasterDetail }: IDirectoryViewProps) => { - const options: StackNavigationOptions = { - title: I18n.t('Directory') - }; - if (isMasterDetail) { - options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} testID='directory-view-close' />; - } - return options; - }; - constructor(props: IDirectoryViewProps) { super(props); this.state = { @@ -70,16 +58,33 @@ class DirectoryView extends React.Component<IDirectoryViewProps, IDirectoryViewS loading: false, text: '', total: -1, - showOptionsDropdown: false, globalUsers: true, type: props.directoryDefaultView }; + this.setHeader(); } componentDidMount() { this.load({}); } + setHeader = () => { + const { navigation, isMasterDetail } = this.props; + const options: StackNavigationOptions = { + title: I18n.t('Directory'), + headerRight: () => ( + <HeaderButton.Container> + <HeaderButton.Item iconName='filter' onPress={this.showFilters} testID='directory-view-filter' /> + </HeaderButton.Container> + ) + }; + if (isMasterDetail) { + options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} testID='directory-view-close' />; + } + + navigation.setOptions(options); + }; + onSearchChangeText = (text: string) => { this.setState({ text }, this.search); }; @@ -139,7 +144,7 @@ class DirectoryView extends React.Component<IDirectoryViewProps, IDirectoryViewS } else if (type === 'teams') { logEvent(events.DIRECTORY_SEARCH_TEAMS); } - this.toggleDropdown(); + hideActionSheetRef(); }; toggleWorkspace = () => { @@ -149,8 +154,20 @@ class DirectoryView extends React.Component<IDirectoryViewProps, IDirectoryViewS ); }; - toggleDropdown = () => { - this.setState(({ showOptionsDropdown }) => ({ showOptionsDropdown: !showOptionsDropdown })); + showFilters = () => { + const { type, globalUsers } = this.state; + const { isFederationEnabled } = this.props; + showActionSheetRef({ + children: ( + <Options + type={type} + globalUsers={globalUsers} + changeType={this.changeType} + toggleWorkspace={this.toggleWorkspace} + isFederationEnabled={isFederationEnabled} + /> + ) + }); }; goRoom = (item: TGoRoomItem) => { @@ -195,37 +212,12 @@ class DirectoryView extends React.Component<IDirectoryViewProps, IDirectoryViewS } }; - renderHeader = () => { - const { type } = this.state; - const { theme } = this.props; - let text = 'Users'; - let icon: TIconsName = 'user'; - - if (type === 'channels') { - text = 'Channels'; - icon = 'channel-public'; - } - - if (type === 'teams') { - text = 'Teams'; - icon = 'teams'; - } - - return ( - <> - <SearchBox onChangeText={this.onSearchChangeText} onSubmitEditing={this.search} testID='directory-view-search' /> - <Touch onPress={this.toggleDropdown} style={styles.dropdownItemButton} testID='directory-view-dropdown'> - <View - style={[sharedStyles.separatorVertical, styles.toggleDropdownContainer, { borderColor: themes[theme].strokeLight }]} - > - <CustomIcon name={icon} size={20} color={themes[theme].badgeBackgroundLevel2} style={styles.toggleDropdownIcon} /> - <Text style={[styles.toggleDropdownText, { color: themes[theme].badgeBackgroundLevel2 }]}>{I18n.t(text)}</Text> - <CustomIcon name='chevron-down' size={20} color={themes[theme].fontHint} style={styles.toggleDropdownArrow} /> - </View> - </Touch> - </> - ); - }; + renderHeader = () => ( + <> + <SearchBox onChangeText={this.onSearchChangeText} onSubmitEditing={this.search} testID='directory-view-search' /> + <List.Separator /> + </> + ); renderItem: ListRenderItem<IServerRoom> = ({ item, index }) => { const { data, type } = this.state; @@ -286,8 +278,8 @@ class DirectoryView extends React.Component<IDirectoryViewProps, IDirectoryViewS }; render = () => { - const { data, loading, showOptionsDropdown, type, globalUsers } = this.state; - const { isFederationEnabled, theme } = this.props; + const { data, loading } = this.state; + const { theme } = this.props; return ( <SafeAreaView style={{ backgroundColor: themes[theme].surfaceRoom }} testID='directory-view'> <StatusBar /> @@ -304,16 +296,6 @@ class DirectoryView extends React.Component<IDirectoryViewProps, IDirectoryViewS ListFooterComponent={loading ? <ActivityIndicator /> : null} onEndReached={() => this.load({})} /> - {showOptionsDropdown ? ( - <Options - type={type} - globalUsers={globalUsers} - close={this.toggleDropdown} - changeType={this.changeType} - toggleWorkspace={this.toggleWorkspace} - isFederationEnabled={isFederationEnabled} - /> - ) : null} </SafeAreaView> ); }; diff --git a/app/views/DirectoryView/styles.ts b/app/views/DirectoryView/styles.ts index 543e6babba..d9e998d0c7 100644 --- a/app/views/DirectoryView/styles.ts +++ b/app/views/DirectoryView/styles.ts @@ -9,80 +9,31 @@ export default StyleSheet.create({ listContainer: { paddingBottom: 30 }, - separator: { - marginLeft: 60 - }, - toggleDropdownContainer: { - height: 46, - flexDirection: 'row', - alignItems: 'center' - }, - toggleDropdownIcon: { - marginLeft: 20, - marginRight: 17 - }, - toggleDropdownText: { - flex: 1, - fontSize: 17, - ...sharedStyles.textRegular - }, - toggleDropdownArrow: { - marginRight: 15 - }, - dropdownContainer: { - width: '100%', - position: 'absolute', - top: 0 - }, - backdrop: { - // @ts-ignore - ...StyleSheet.absoluteFill - }, - dropdownContainerHeader: { - height: 46, - borderBottomWidth: StyleSheet.hairlineWidth, - alignItems: 'center', - flexDirection: 'row' - }, - dropdownItemButton: { + filterItemButton: { height: 46, justifyContent: 'center' }, - dropdownItemContainer: { + filterItemContainer: { flex: 1, flexDirection: 'row', alignItems: 'center' }, - dropdownItemText: { + filterItemText: { fontSize: 18, flex: 1, ...sharedStyles.textRegular }, - dropdownItemDescription: { + filterItemDescription: { fontSize: 14, flex: 1, marginTop: 2, ...sharedStyles.textRegular }, - dropdownToggleText: { - fontSize: 15, - flex: 1, - marginLeft: 15, - ...sharedStyles.textRegular - }, - dropdownItemIcon: { + filterItemIcon: { width: 22, height: 22, marginHorizontal: 15 }, - dropdownSeparator: { - height: StyleSheet.hairlineWidth, - marginHorizontal: 15, - flex: 1 - }, - inverted: { - transform: [{ scaleY: -1 }] - }, globalUsersContainer: { padding: 15 }, diff --git a/app/views/DisplayPrefsView.tsx b/app/views/DisplayPrefsView.tsx index 9e2e1970a6..8e7f8ec697 100644 --- a/app/views/DisplayPrefsView.tsx +++ b/app/views/DisplayPrefsView.tsx @@ -110,6 +110,7 @@ const DisplayPrefsView = (): React.ReactElement => { testID='display-pref-view-expanded' right={() => renderRadio(displayMode === DisplayMode.Expanded)} onPress={displayExpanded} + additionalAcessibilityLabel={displayMode === DisplayMode.Expanded} /> <List.Separator /> <List.Item @@ -118,6 +119,7 @@ const DisplayPrefsView = (): React.ReactElement => { testID='display-pref-view-condensed' right={() => renderRadio(displayMode === DisplayMode.Condensed)} onPress={displayCondensed} + additionalAcessibilityLabel={displayMode === DisplayMode.Condensed} /> <List.Separator /> <List.Item @@ -125,6 +127,7 @@ const DisplayPrefsView = (): React.ReactElement => { title='Avatars' testID='display-pref-view-avatars' right={() => renderAvatarSwitch(showAvatar)} + additionalAcessibilityLabel={showAvatar} /> <List.Separator /> </List.Section> @@ -137,6 +140,7 @@ const DisplayPrefsView = (): React.ReactElement => { left={() => <List.Icon name='clock' />} onPress={sortByActivity} right={() => renderRadio(sortBy === SortBy.Activity)} + additionalAcessibilityLabel={sortBy === SortBy.Activity} /> <List.Separator /> <List.Item @@ -145,6 +149,7 @@ const DisplayPrefsView = (): React.ReactElement => { left={() => <List.Icon name='sort-az' />} onPress={sortByName} right={() => renderRadio(sortBy === SortBy.Alphabetical)} + additionalAcessibilityLabel={sortBy === SortBy.Alphabetical} /> <List.Separator /> </List.Section> @@ -157,6 +162,7 @@ const DisplayPrefsView = (): React.ReactElement => { left={() => <List.Icon name='flag' />} onPress={toggleUnread} right={() => renderCheckBox(showUnread)} + additionalAcessibilityLabel={showUnread} /> <List.Separator /> <List.Item @@ -165,6 +171,7 @@ const DisplayPrefsView = (): React.ReactElement => { left={() => <List.Icon name='star' />} onPress={toggleGroupByFavorites} right={() => renderCheckBox(showFavorites)} + additionalAcessibilityLabel={showFavorites} /> <List.Separator /> <List.Item @@ -173,6 +180,7 @@ const DisplayPrefsView = (): React.ReactElement => { left={() => <List.Icon name='group-by-type' />} onPress={toggleGroupByType} right={() => renderCheckBox(groupByType)} + additionalAcessibilityLabel={groupByType} /> <List.Separator /> </List.Section> diff --git a/app/views/LanguageView/LanguageItem.tsx b/app/views/LanguageView/LanguageItem.tsx index 637b4dc8f2..ca5a45e53e 100644 --- a/app/views/LanguageView/LanguageItem.tsx +++ b/app/views/LanguageView/LanguageItem.tsx @@ -2,6 +2,7 @@ import React from 'react'; import * as List from '../../containers/List'; import { useTheme } from '../../theme'; +import i18n from '../../i18n'; const LanguageItem = ({ item, @@ -24,6 +25,7 @@ const LanguageItem = ({ testID={`language-view-${value}`} right={() => (isSelected ? <List.Icon name='check' color={colors.badgeBackgroundLevel2} /> : null)} translateTitle={false} + additionalAcessibilityLabel={isSelected ? i18n.t('Checked') : i18n.t('Unchecked')} /> ); }; diff --git a/app/views/MediaAutoDownloadView/ListPicker.tsx b/app/views/MediaAutoDownloadView/ListPicker.tsx index 327fd0f318..666671dd39 100644 --- a/app/views/MediaAutoDownloadView/ListPicker.tsx +++ b/app/views/MediaAutoDownloadView/ListPicker.tsx @@ -56,17 +56,15 @@ const ListPicker = ({ right: option.value === i.value ? () => <CustomIcon name={'check'} size={20} color={colors.strokeHighlight} /> : undefined })); + /* when picking an option the label should be Never but when showing among the other settings the label should be Off */ + const label = option.label === 'Never' ? I18n.t('Off') : I18n.t(option.label); + return ( <List.Item title={title} onPress={() => showActionSheet({ options: getOptions() })} - right={() => ( - <Text style={[styles.title, { color: colors.fontHint }]}> - {/* when picking an option the label should be Never - but when showing among the other settings the label should be Off */} - {option.label === 'Never' ? I18n.t('Off') : I18n.t(option.label)} - </Text> - )} + right={() => <Text style={[styles.title, { color: colors.fontHint }]}>{label}</Text>} + additionalAcessibilityLabel={label} /> ); }; diff --git a/app/views/NewServerView/ServerInput/Item.tsx b/app/views/NewServerView/ServerInput/Item.tsx index 32b7305e04..852f4d70a5 100644 --- a/app/views/NewServerView/ServerInput/Item.tsx +++ b/app/views/NewServerView/ServerInput/Item.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; -import { BorderlessButton } from 'react-native-gesture-handler'; import { themes } from '../../../lib/constants'; import { CustomIcon } from '../../../containers/CustomIcon'; @@ -36,8 +35,13 @@ interface IItem { } const Item = ({ item, theme, onPress, onDelete }: IItem): JSX.Element => ( - <Touch style={styles.container} onPress={() => onPress(item.url)} testID={`server-history-${item.url}`}> - <View style={styles.content}> + <Touch + style={styles.container} + onPress={() => onPress(item.url)} + testID={`server-history-${item.url}`} + accessible + accessibilityLabel={`${item.url} ${item.username}`}> + <View style={styles.content} accessible={false} accessibilityElementsHidden> <Text numberOfLines={1} style={[styles.server, { color: themes[theme].fontDefault }]}> {item.url} </Text> @@ -45,9 +49,9 @@ const Item = ({ item, theme, onPress, onDelete }: IItem): JSX.Element => ( {item.username} </Text> </View> - <BorderlessButton onPress={() => onDelete(item)} testID={`server-history-delete-${item.url}`}> + <Touch onPress={() => onDelete(item)} testID={`server-history-delete-${item.url}`}> <CustomIcon name='delete' size={24} color={themes[theme].fontSecondaryInfo} /> - </BorderlessButton> + </Touch> </Touch> ); diff --git a/app/views/NewServerView/index.tsx b/app/views/NewServerView/index.tsx index 23711be99c..32a00a6c63 100644 --- a/app/views/NewServerView/index.tsx +++ b/app/views/NewServerView/index.tsx @@ -14,19 +14,17 @@ import FormContainer, { FormContainerInner } from '../../containers/FormContaine import * as HeaderButton from '../../containers/HeaderButton'; import OrSeparator from '../../containers/OrSeparator'; import { IApplicationState, IBaseScreen, TServerHistoryModel } from '../../definitions'; -import { withDimensions } from '../../dimensions'; import I18n from '../../i18n'; import database from '../../lib/database'; import { sanitizeLikeString } from '../../lib/database/utils'; import UserPreferences from '../../lib/methods/userPreferences'; import { OutsideParamList } from '../../stacks/types'; import { withTheme } from '../../theme'; -import { isIOS, isTablet } from '../../lib/methods/helpers'; +import { isAndroid, isIOS, isTablet } from '../../lib/methods/helpers'; import EventEmitter from '../../lib/methods/helpers/events'; import { BASIC_AUTH_KEY, setBasicAuth } from '../../lib/methods/helpers/fetch'; import { showConfirmationAlert } from '../../lib/methods/helpers/info'; import { events, logEvent } from '../../lib/methods/helpers/log'; -import { moderateScale, verticalScale } from './scaling'; import SSLPinning from '../../lib/methods/helpers/sslPinning'; import sharedStyles from '../Styles'; import ServerInput from './ServerInput'; @@ -68,8 +66,6 @@ const styles = StyleSheet.create({ interface INewServerViewProps extends IBaseScreen<OutsideParamList, 'NewServerView'> { connecting: boolean; previousServer: string | null; - width: number; - height: number; } interface INewServerViewState { @@ -293,31 +289,23 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS renderCertificatePicker = () => { const { certificate } = this.state; - const { theme, width, height, previousServer } = this.props; + const { theme, previousServer } = this.props; return ( <View style={[ styles.certificatePicker, { - marginBottom: verticalScale({ size: previousServer && !isTablet ? 10 : 30, height }) + marginTop: isAndroid ? 20 : 0, + marginBottom: previousServer && !isTablet ? 10 : 30 } - ]} - > - <Text - style={[ - styles.chooseCertificateTitle, - { color: themes[theme].fontSecondaryInfo, fontSize: moderateScale({ size: 13, width }) } - ]} - > + ]}> + <Text style={[styles.chooseCertificateTitle, { color: themes[theme].fontSecondaryInfo, fontSize: 13 }]}> {certificate ? I18n.t('Your_certificate') : I18n.t('Do_you_have_a_certificate')} </Text> <TouchableOpacity onPress={certificate ? this.handleRemove : this.chooseCertificate} - testID='new-server-choose-certificate' - > - <Text - style={[styles.chooseCertificate, { color: themes[theme].fontInfo, fontSize: moderateScale({ size: 13, width }) }]} - > + testID='new-server-choose-certificate'> + <Text style={[styles.chooseCertificate, { color: themes[theme].fontInfo, fontSize: 13 }]}> {certificate ?? I18n.t('Apply_Your_Certificate')} </Text> </TouchableOpacity> @@ -326,7 +314,7 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS }; render() { - const { connecting, theme, previousServer, width, height } = this.props; + const { connecting, theme, previousServer } = this.props; const { text, connectingOpen, serversHistory } = this.state; const marginTop = previousServer ? 0 : 35; @@ -337,10 +325,10 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS style={[ styles.onboardingImage, { - marginBottom: verticalScale({ size: 10, height }), - marginTop: isTablet ? 0 : verticalScale({ size: marginTop, height }), - width: verticalScale({ size: 100, height }), - height: verticalScale({ size: 100, height }) + marginBottom: 10, + marginTop: isTablet ? 0 : marginTop, + width: 100, + height: 100 } ]} source={require('../../static/images/logo.png')} @@ -351,11 +339,10 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS styles.title, { color: themes[theme].fontTitlesLabels, - fontSize: moderateScale({ size: 22, width }), - marginBottom: verticalScale({ size: 8, height }) + fontSize: 22, + marginBottom: 8 } - ]} - > + ]}> Rocket.Chat </Text> <Text @@ -363,11 +350,10 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS styles.subtitle, { color: themes[theme].fontHint, - fontSize: moderateScale({ size: 16, width }), - marginBottom: verticalScale({ size: 30, height }) + fontSize: 16, + marginBottom: 30 } - ]} - > + ]}> {I18n.t('Onboarding_subtitle')} </Text> <ServerInput @@ -385,7 +371,7 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS onPress={this.submit} disabled={!text || connecting} loading={!connectingOpen && connecting} - style={[styles.connectButton, { marginTop: verticalScale({ size: 16, height }) }]} + style={[styles.connectButton, { marginTop: 16 }]} testID='new-server-view-button' /> {isIOS ? ( @@ -396,11 +382,10 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS styles.description, { color: themes[theme].fontSecondaryInfo, - fontSize: moderateScale({ size: 14, width }), - marginBottom: verticalScale({ size: 16, height }) + fontSize: 14, + marginBottom: 16 } - ]} - > + ]}> {I18n.t('Onboarding_join_open_description')} </Text> <Button @@ -425,4 +410,4 @@ const mapStateToProps = (state: IApplicationState) => ({ previousServer: state.server.previousServer }); -export default connect(mapStateToProps)(withDimensions(withTheme(NewServerView))); +export default connect(mapStateToProps)(withTheme(NewServerView)); diff --git a/app/views/NewServerView/scaling.ts b/app/views/NewServerView/scaling.ts deleted file mode 100644 index 4d49bc1beb..0000000000 --- a/app/views/NewServerView/scaling.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { isTablet } from '../../lib/methods/helpers'; - -const guidelineBaseWidth = isTablet ? 600 : 375; -const guidelineBaseHeight = isTablet ? 800 : 667; - -function scale({ size, width }: { size: number; width: number }): number { - return (width / guidelineBaseWidth) * size; -} -function verticalScale({ size, height }: { size: number; height: number }): number { - return (height / guidelineBaseHeight) * size; -} -function moderateScale({ size, factor = 0.5, width }: { size: number; factor?: number; width: number }): number { - return size + (scale({ size, width }) - size) * factor; -} - -export { scale, verticalScale, moderateScale }; diff --git a/app/views/NotificationPreferencesView/index.tsx b/app/views/NotificationPreferencesView/index.tsx index a3680d04aa..ed09a4883d 100644 --- a/app/views/NotificationPreferencesView/index.tsx +++ b/app/views/NotificationPreferencesView/index.tsx @@ -60,16 +60,15 @@ const RenderListPicker = ({ right: option?.value === i.value ? () => <CustomIcon name={'check'} size={20} color={colors.strokeHighlight} /> : undefined })); + const label = option?.label ? I18n.t(option?.label, { defaultValue: option?.label, second: option?.second }) : option?.label; + return ( <List.Item title={title} testID={testID} onPress={() => showActionSheet({ options })} - right={() => ( - <Text style={[{ ...sharedStyles.textRegular, fontSize: 16 }, { color: colors.fontHint }]}> - {option?.label ? I18n.t(option?.label, { defaultValue: option?.label, second: option?.second }) : option?.label} - </Text> - )} + right={() => <Text style={[{ ...sharedStyles.textRegular, fontSize: 16 }, { color: colors.fontHint }]}>{label}</Text>} + additionalAcessibilityLabel={label} /> ); }; @@ -143,6 +142,7 @@ const NotificationPreferencesView = (): React.ReactElement => { title='Receive_Notification' testID='notification-preference-view-receive-notification' right={() => <RenderSwitch preference='disableNotifications' room={room} onChangeValue={saveNotificationSettings} />} + additionalAcessibilityLabel={!room.disableNotifications} /> <List.Separator /> <List.Info info={I18n.t('Receive_notifications_from', { name: room.name })} translateInfo={false} /> @@ -154,6 +154,8 @@ const NotificationPreferencesView = (): React.ReactElement => { title='Receive_Group_Mentions' testID='notification-preference-view-group-mentions' right={() => <RenderSwitch preference='muteGroupMentions' room={room} onChangeValue={saveNotificationSettings} />} + // @ts-ignore + additionalAcessibilityLabel={!room.muteGroupMentions} /> <List.Separator /> <List.Info info='Receive_Group_Mentions_Info' /> @@ -165,6 +167,7 @@ const NotificationPreferencesView = (): React.ReactElement => { title='Mark_as_unread' testID='notification-preference-view-mark-as-unread' right={() => <RenderSwitch preference='hideUnreadStatus' room={room} onChangeValue={saveNotificationSettings} />} + additionalAcessibilityLabel={!room.hideUnreadStatus} /> <List.Separator /> <List.Info info='Mark_as_unread_Info' /> @@ -177,6 +180,7 @@ const NotificationPreferencesView = (): React.ReactElement => { title='Show_badge_for_mentions' testID='notification-preference-view-badge-for-mentions' right={() => <RenderSwitch preference='hideMentionStatus' room={room} onChangeValue={saveNotificationSettings} />} + additionalAcessibilityLabel={!room.hideMentionStatus} /> <List.Separator /> <List.Info info='Show_badge_for_mentions_Info' /> diff --git a/app/views/PickerView.tsx b/app/views/PickerView.tsx index 18170cb23a..208a693c35 100644 --- a/app/views/PickerView.tsx +++ b/app/views/PickerView.tsx @@ -35,6 +35,8 @@ const Item = ({ item, selected, onItemPress }: IItem) => { right={() => (selected ? <List.Icon name='check' color={colors.badgeBackgroundLevel2} /> : null)} onPress={onItemPress} translateTitle={false} + additionalAcessibilityLabel={selected} + additionalAcessibilityLabelCheck /> ); }; diff --git a/app/views/PushTroubleshootView/components/CommunityEditionPushQuota.tsx b/app/views/PushTroubleshootView/components/CommunityEditionPushQuota.tsx index f513e52dc3..b40d3cac06 100644 --- a/app/views/PushTroubleshootView/components/CommunityEditionPushQuota.tsx +++ b/app/views/PushTroubleshootView/components/CommunityEditionPushQuota.tsx @@ -41,6 +41,7 @@ export default function CommunityEditionPushQuota(): React.ReactElement | null { testID='push-troubleshoot-view-workspace-consumption' onPress={alertWorkspaceConsumption} right={() => <Text style={[styles.pickerText, { color: percentageColor }]}>{percentage}</Text>} + additionalAcessibilityLabel={percentage} /> <List.Separator /> <List.Info info='Workspace_consumption_description' /> diff --git a/app/views/RoomActionsView/index.tsx b/app/views/RoomActionsView/index.tsx index 6b91809d76..a0403b769c 100644 --- a/app/views/RoomActionsView/index.tsx +++ b/app/views/RoomActionsView/index.tsx @@ -831,6 +831,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction testID='room-actions-encrypt' left={() => <List.Icon name='encrypted' />} right={this.renderEncryptedSwitch} + additionalAcessibilityLabel={!!room.encrypted} /> <List.Separator /> </List.Section> diff --git a/app/views/RoomsListView/Header/Header.tsx b/app/views/RoomsListView/Header/Header.tsx index e518a635d1..d09be58c57 100644 --- a/app/views/RoomsListView/Header/Header.tsx +++ b/app/views/RoomsListView/Header/Header.tsx @@ -3,7 +3,6 @@ import { StyleSheet, Text, TextInputProps, TouchableOpacity, TouchableOpacityPro import I18n from '../../../i18n'; import sharedStyles from '../../Styles'; -import { CustomIcon } from '../../../containers/CustomIcon'; import { useTheme } from '../../../theme'; import SearchHeader from '../../../containers/SearchHeader'; import { useAppSelector } from '../../../lib/hooks'; @@ -25,9 +24,6 @@ const styles = StyleSheet.create({ subtitle: { fontSize: 14, ...sharedStyles.textRegular - }, - upsideDown: { - transform: [{ scaleY: -1 }] } }); @@ -37,7 +33,6 @@ interface IRoomHeader { isFetching: boolean; serverName: string; server: string; - showServerDropdown: boolean; showSearchHeader: boolean; onSearchChangeText: TextInputProps['onChangeText']; onPress: TouchableOpacityProps['onPress']; @@ -50,7 +45,6 @@ const Header = React.memo( isFetching, serverName = 'Rocket.Chat', server, - showServerDropdown, showSearchHeader, onSearchChangeText, onPress @@ -73,26 +67,20 @@ const Header = React.memo( } else { subtitle = server?.replace(/(^\w+:|^)\/\//, ''); } + // improve copy return ( - <View style={styles.container}> - <TouchableOpacity onPress={onPress} testID='rooms-list-header-server-dropdown-button'> + <View style={styles.container} accessibilityLabel={`${serverName} ${subtitle}`} accessibilityRole='header' accessible> + <TouchableOpacity onPress={onPress} testID='rooms-list-header-servers-list-button'> <View style={styles.button}> <Text style={[styles.title, { color: colors.fontTitlesLabels }]} numberOfLines={1}> {serverName} </Text> - <CustomIcon - name='chevron-down' - color={colors.fontSecondaryInfo} - style={[showServerDropdown && styles.upsideDown]} - size={18} - /> </View> {subtitle ? ( <Text testID='rooms-list-header-server-subtitle' style={[styles.subtitle, { color: colors.fontSecondaryInfo }]} - numberOfLines={1} - > + numberOfLines={1}> {subtitle} </Text> ) : null} diff --git a/app/views/RoomsListView/Header/index.tsx b/app/views/RoomsListView/Header/index.tsx index 762f3f43fe..bc27d35c1e 100644 --- a/app/views/RoomsListView/Header/index.tsx +++ b/app/views/RoomsListView/Header/index.tsx @@ -2,13 +2,13 @@ import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; -import { toggleServerDropdown, closeServerDropdown, setSearch } from '../../../actions/rooms'; -import { events, logEvent } from '../../../lib/methods/helpers/log'; +import { setSearch } from '../../../actions/rooms'; import Header from './Header'; import { IApplicationState } from '../../../definitions'; +import { showActionSheetRef } from '../../../containers/ActionSheet'; +import ServersList from '../ServersList'; interface IRoomsListHeaderViewProps { - showServerDropdown: boolean; showSearchHeader: boolean; serverName: string; connecting: boolean; @@ -25,23 +25,16 @@ class RoomsListHeaderView extends PureComponent<IRoomsListHeaderViewProps, any> }; onPress = () => { - logEvent(events.RL_TOGGLE_SERVER_DROPDOWN); - const { showServerDropdown, dispatch } = this.props; - if (showServerDropdown) { - dispatch(closeServerDropdown()); - } else { - dispatch(toggleServerDropdown()); - } + showActionSheetRef({ children: <ServersList />, enableContentPanningGesture: false }); }; render() { - const { serverName, showServerDropdown, showSearchHeader, connecting, connected, isFetching, server } = this.props; + const { serverName, showSearchHeader, connecting, connected, isFetching, server } = this.props; return ( <Header serverName={serverName} server={server} - showServerDropdown={showServerDropdown} showSearchHeader={showSearchHeader} connecting={connecting} connected={connected} @@ -54,7 +47,6 @@ class RoomsListHeaderView extends PureComponent<IRoomsListHeaderViewProps, any> } const mapStateToProps = (state: IApplicationState) => ({ - showServerDropdown: state.rooms.showServerDropdown, showSearchHeader: state.rooms.showSearchHeader, connecting: state.meteor.connecting || state.server.loading, connected: state.meteor.connected, diff --git a/app/views/RoomsListView/ServerDropdown.tsx b/app/views/RoomsListView/ServersList.tsx similarity index 50% rename from app/views/RoomsListView/ServerDropdown.tsx rename to app/views/RoomsListView/ServersList.tsx index 0527d84a9a..b98eb41a78 100644 --- a/app/views/RoomsListView/ServerDropdown.tsx +++ b/app/views/RoomsListView/ServersList.tsx @@ -1,12 +1,12 @@ import React, { useEffect, useRef, useState } from 'react'; -import { View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Linking } from 'react-native'; +import { View, Text, TouchableOpacity, FlatList, Linking } from 'react-native'; import { batch, useDispatch } from 'react-redux'; import { Subscription } from 'rxjs'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import * as List from '../../containers/List'; import Button from '../../containers/Button'; -import { toggleServerDropdown } from '../../actions/rooms'; +import { hideActionSheetRef } from '../../containers/ActionSheet'; import { selectServerRequest, serverInitAdd } from '../../actions/server'; import { appStart } from '../../actions/app'; import I18n from '../../i18n'; @@ -18,7 +18,6 @@ import { useTheme } from '../../theme'; import { localAuthenticate } from '../../lib/methods/helpers/localAuthentication'; import { showConfirmationAlert } from '../../lib/methods/helpers/info'; import log, { events, logEvent } from '../../lib/methods/helpers/log'; -import { headerHeight } from '../../lib/methods/helpers/navigation'; import { goRoom } from '../../lib/methods/helpers/goRoom'; import UserPreferences from '../../lib/methods/userPreferences'; import { RootEnum, TServerModel } from '../../definitions'; @@ -27,29 +26,18 @@ import { removeServer } from '../../lib/methods'; import { useAppSelector } from '../../lib/hooks'; const ROW_HEIGHT = 68; -const ANIMATION_DURATION = 200; -const MAX_ROWS = 4; +const MAX_ROWS = 4.5; -const ServerDropdown = () => { - const animatedValue = useRef(new Animated.Value(0)).current; +const ServersList = () => { const subscription = useRef<Subscription>(); - const newServerTimeout = useRef<ReturnType<typeof setTimeout> | false>(); - const isMounted = useRef(false); const [servers, setServers] = useState<TServerModel[]>([]); const dispatch = useDispatch(); - const closeServerDropdown = useAppSelector(state => state.rooms.closeServerDropdown); const server = useAppSelector(state => state.server.server); const isMasterDetail = useAppSelector(state => state.app.isMasterDetail); const { colors } = useTheme(); const insets = useSafeAreaInsets(); useEffect(() => { - if (isMounted.current) close(); - }, [closeServerDropdown]); - - useEffect(() => { - isMounted.current = true; - const init = async () => { const serversDB = database.servers; const observable = await serversDB.get('servers').query().observeWithColumns(['name']); @@ -57,21 +45,10 @@ const ServerDropdown = () => { subscription.current = observable.subscribe(data => { setServers(data); }); - - Animated.timing(animatedValue, { - toValue: 1, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - }).start(); }; init(); return () => { - if (newServerTimeout.current) { - clearTimeout(newServerTimeout.current); - newServerTimeout.current = false; - } if (subscription.current && subscription.current.unsubscribe) { subscription.current.unsubscribe(); } @@ -79,12 +56,7 @@ const ServerDropdown = () => { }, []); const close = () => { - Animated.timing(animatedValue, { - toValue: 0, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - }).start(() => dispatch(toggleServerDropdown())); + hideActionSheetRef(); }; const createWorkspace = async () => { @@ -106,9 +78,7 @@ const ServerDropdown = () => { const addServer = () => { logEvent(events.RL_ADD_SERVER); close(); - setTimeout(() => { - navToNewServer(server); - }, ANIMATION_DURATION); + navToNewServer(server); }; const select = async (serverParam: string, version?: string) => { @@ -120,12 +90,11 @@ const ServerDropdown = () => { goRoom({ item: {}, isMasterDetail }); } if (!userId) { + navToNewServer(server); + // Intentionally not cleared, because it needs to trigger the emitter even after unmount setTimeout(() => { - navToNewServer(server); - newServerTimeout.current = setTimeout(() => { - EventEmitter.emit('NewServer', { server: serverParam }); - }, ANIMATION_DURATION); - }, ANIMATION_DURATION); + EventEmitter.emit('NewServer', { server: serverParam }); + }, 300); } else { await localAuthenticate(serverParam); dispatch(selectServerRequest(serverParam, version, true, true)); @@ -156,73 +125,41 @@ const ServerDropdown = () => { /> ); - const initialTop = 87 + Math.min(servers.length, MAX_ROWS) * ROW_HEIGHT; - const statusBarHeight = insets?.top ?? 0; - const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0; - - const translateY = animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [-initialTop, heightDestination] - }); - - const backdropOpacity = animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [0, colors.backdropOpacity] - }); - return ( - <> - <TouchableWithoutFeedback onPress={close}> - <Animated.View - style={[ - styles.backdrop, - { - backgroundColor: colors.backdropColor, - opacity: backdropOpacity, - top: heightDestination - } - ]} - /> - </TouchableWithoutFeedback> - <Animated.View - style={[ - styles.dropdownContainer, - { - transform: [{ translateY }], - backgroundColor: colors.surfaceRoom, - borderColor: colors.strokeLight - } - ]} - testID='rooms-list-header-server-dropdown' - > - <View style={[styles.dropdownContainerHeader, styles.serverHeader, { borderColor: colors.strokeLight }]}> - <Text style={[styles.serverHeaderText, { color: colors.fontSecondaryInfo }]}>{I18n.t('Server')}</Text> - <TouchableOpacity onPress={addServer} testID='rooms-list-header-server-add'> - <Text style={[styles.serverHeaderAdd, { color: colors.badgeBackgroundLevel2 }]}>{I18n.t('Add_Server')}</Text> - </TouchableOpacity> - </View> - <FlatList - style={{ maxHeight: MAX_ROWS * ROW_HEIGHT }} - data={servers} - keyExtractor={item => item.id} - renderItem={renderItem} - ItemSeparatorComponent={List.Separator} - keyboardShouldPersistTaps='always' - /> - <List.Separator /> - <Button - title={I18n.t('Create_a_new_workspace')} - type='secondary' - onPress={createWorkspace} - testID='rooms-list-header-create-workspace-button' - style={styles.buttonCreateWorkspace} - color={colors.badgeBackgroundLevel2} - backgroundColor={colors.surfaceRoom} - styleText={[styles.serverHeaderAdd, { textAlign: 'center' }]} - /> - </Animated.View> - </> + <View + style={{ + backgroundColor: colors.surfaceRoom, + borderColor: colors.strokeLight, + marginBottom: insets.bottom + }} + testID='rooms-list-header-servers-list'> + <View style={[styles.serversListContainerHeader, styles.serverHeader, { borderColor: colors.strokeLight }]}> + <Text style={[styles.serverHeaderText, { color: colors.fontSecondaryInfo }]}>{I18n.t('Server')}</Text> + <TouchableOpacity onPress={addServer} testID='rooms-list-header-server-add'> + <Text style={[styles.serverHeaderAdd, { color: colors.badgeBackgroundLevel2 }]}>{I18n.t('Add_Server')}</Text> + </TouchableOpacity> + </View> + <FlatList + style={{ maxHeight: MAX_ROWS * ROW_HEIGHT }} + data={servers} + keyExtractor={item => item.id} + renderItem={renderItem} + ItemSeparatorComponent={List.Separator} + keyboardShouldPersistTaps='always' + /> + <List.Separator /> + <Button + title={I18n.t('Create_a_new_workspace')} + type='secondary' + onPress={createWorkspace} + testID='rooms-list-header-create-workspace-button' + style={styles.buttonCreateWorkspace} + color={colors.badgeBackgroundLevel2} + backgroundColor={colors.surfaceRoom} + styleText={[styles.serverHeaderAdd, { textAlign: 'center' }]} + /> + </View> ); }; -export default ServerDropdown; +export default ServersList; diff --git a/app/views/RoomsListView/index.tsx b/app/views/RoomsListView/index.tsx index 343fa50c8b..c8cc39e6b8 100644 --- a/app/views/RoomsListView/index.tsx +++ b/app/views/RoomsListView/index.tsx @@ -14,7 +14,7 @@ import database from '../../lib/database'; import RoomItem, { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from '../../containers/RoomItem'; import log, { logEvent, events } from '../../lib/methods/helpers/log'; import I18n from '../../i18n'; -import { closeSearchHeader, closeServerDropdown, openSearchHeader, roomsRequest } from '../../actions/rooms'; +import { closeSearchHeader, openSearchHeader, roomsRequest } from '../../actions/rooms'; import * as HeaderButton from '../../containers/HeaderButton'; import StatusBar from '../../containers/StatusBar'; import ActivityIndicator from '../../containers/ActivityIndicator'; @@ -28,7 +28,6 @@ import { withDimensions } from '../../dimensions'; import { getInquiryQueueSelector } from '../../ee/omnichannel/selectors/inquiry'; import { IApplicationState, ISubscription, IUser, TSVStatus, SubscriptionType, TSubscriptionModel } from '../../definitions'; import styles from './styles'; -import ServerDropdown from './ServerDropdown'; import ListHeader, { TEncryptionBanner } from './ListHeader'; import RoomsListHeaderView from './Header'; import { ChatsStackParamList, DrawerParamList } from '../../stacks/types'; @@ -65,7 +64,6 @@ interface IRoomsListViewProps { searchText: string; changingServer: boolean; loadingServer: boolean; - showServerDropdown: boolean; sortBy: string; groupByType: boolean; showFavorites: boolean; @@ -133,7 +131,6 @@ const filterIsDiscussion = (s: TSubscriptionModel) => s.prid; const shouldUpdateProps = [ 'searchText', 'loadingServer', - 'showServerDropdown', 'useRealName', 'StoreLastMessage', 'theme', @@ -200,7 +197,7 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS } componentDidMount() { - const { navigation, dispatch } = this.props; + const { navigation } = this.props; this.handleHasPermission(); this.mounted = true; this.unsubscribeFocus = navigation.addListener('focus', () => { @@ -219,7 +216,6 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS }); this.unsubscribeBlur = navigation.addListener('blur', () => { this.animated = false; - dispatch(closeServerDropdown()); this.cancelSearch(); if (this.backHandler && this.backHandler.remove) { this.backHandler.remove(); @@ -626,11 +622,8 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS initSearching = () => { logEvent(events.RL_SEARCH); - const { dispatch, showServerDropdown } = this.props; + const { dispatch } = this.props; this.internalSetState({ searching: true }, () => { - if (showServerDropdown) { - dispatch(closeServerDropdown()); - } dispatch(openSearchHeader()); this.handleSearch(''); this.setHeader(); @@ -995,14 +988,13 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS render = () => { console.count(`${this.constructor.name}.render calls`); - const { showServerDropdown, theme } = this.props; + const { theme } = this.props; return ( <SafeAreaView testID='rooms-list-view' style={{ backgroundColor: themes[theme].surfaceRoom }}> <StatusBar /> {this.renderHeader()} {this.renderScroll()} - {showServerDropdown ? <ServerDropdown /> : null} </SafeAreaView> ); }; @@ -1017,7 +1009,6 @@ const mapStateToProps = (state: IApplicationState) => ({ changingServer: state.server.changingServer, searchText: state.rooms.searchText, loadingServer: state.server.loading, - showServerDropdown: state.rooms.showServerDropdown, refreshing: state.rooms.refreshing, sortBy: state.sortPreferences.sortBy, groupByType: state.sortPreferences.groupByType, diff --git a/app/views/RoomsListView/styles.ts b/app/views/RoomsListView/styles.ts index 80b19c89a7..26709a3ecf 100644 --- a/app/views/RoomsListView/styles.ts +++ b/app/views/RoomsListView/styles.ts @@ -9,21 +9,12 @@ export default StyleSheet.create({ list: { width: '100%' }, - dropdownContainerHeader: { + serversListContainerHeader: { height: 41, borderBottomWidth: StyleSheet.hairlineWidth, alignItems: 'center', flexDirection: 'row' }, - dropdownContainer: { - width: '100%', - position: 'absolute', - top: 0, - borderBottomWidth: StyleSheet.hairlineWidth - }, - backdrop: { - ...StyleSheet.absoluteFillObject - }, groupTitleContainer: { paddingHorizontal: 12, paddingTop: 17, diff --git a/app/views/ScreenLockConfigView.tsx b/app/views/ScreenLockConfigView.tsx index 92a15eb72f..4c07d1b52b 100644 --- a/app/views/ScreenLockConfigView.tsx +++ b/app/views/ScreenLockConfigView.tsx @@ -185,6 +185,8 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I right={() => (this.isSelected(value) ? this.renderIcon() : null)} disabled={disabled} translateTitle={false} + additionalAcessibilityLabel={this.isSelected(value)} + additionalAcessibilityLabelCheck /> <List.Separator /> </> @@ -244,6 +246,7 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I title={I18n.t('Local_authentication_unlock_with_label', { label: biometryLabel })} right={() => this.renderBiometrySwitch()} translateTitle={false} + additionalAcessibilityLabel={this.state.biometry ? I18n.t('Enabled') : I18n.t('Disabled')} /> <List.Separator /> </List.Section> @@ -258,7 +261,11 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I <List.Container> <List.Section> <List.Separator /> - <List.Item title='Local_authentication_unlock_option' right={() => this.renderAutoLockSwitch()} /> + <List.Item + title='Local_authentication_unlock_option' + right={() => this.renderAutoLockSwitch()} + additionalAcessibilityLabel={autoLock} + /> {autoLock ? ( <> <List.Separator /> diff --git a/app/views/SecurityPrivacyView.tsx b/app/views/SecurityPrivacyView.tsx index db7bdec3dc..9480a3ec31 100644 --- a/app/views/SecurityPrivacyView.tsx +++ b/app/views/SecurityPrivacyView.tsx @@ -99,12 +99,14 @@ const SecurityPrivacyView = ({ navigation }: ISecurityPrivacyViewProps): JSX.Ele title='Log_analytics_events' testID='security-privacy-view-analytics-events' right={() => <Switch value={analyticsEventsState} onValueChange={toggleAnalyticsEvents} />} + additionalAcessibilityLabel={analyticsEventsState} /> <List.Separator /> <List.Item title='Send_crash_report' testID='security-privacy-view-crash-report' right={() => <Switch value={crashReportState} onValueChange={toggleCrashReport} />} + additionalAcessibilityLabel={analyticsEventsState} /> <List.Separator /> <List.Info info='Crash_report_disclaimer' /> diff --git a/app/views/SelectListView.tsx b/app/views/SelectListView.tsx index dcdbed975d..da0a3202e5 100644 --- a/app/views/SelectListView.tsx +++ b/app/views/SelectListView.tsx @@ -166,6 +166,16 @@ class SelectListView extends React.Component<ISelectListViewProps, ISelectListVi /> ) : null; + const handleAcessibilityLabel = (rid: string) => { + let label = ''; + if (this.isRadio) { + label = this.isChecked(rid) ? I18n.t('Selected') : I18n.t('Unselected'); + } else { + label = this.isChecked(rid) ? I18n.t('Checked') : I18n.t('Unchecked'); + } + return label; + }; + return ( <> <List.Separator /> @@ -177,6 +187,7 @@ class SelectListView extends React.Component<ISelectListViewProps, ISelectListVi alert={item.alert} left={() => <List.Icon name={icon} color={themes[theme].fontHint} />} right={() => (this.isRadio ? showRadio() : showCheck())} + additionalAcessibilityLabel={handleAcessibilityLabel(item.rid)} /> </> ); diff --git a/app/views/SelectedUsersView/index.tsx b/app/views/SelectedUsersView/index.tsx index 622b8fa003..68d55cd068 100644 --- a/app/views/SelectedUsersView/index.tsx +++ b/app/views/SelectedUsersView/index.tsx @@ -166,6 +166,7 @@ const SelectedUsersView = () => { testID={`select-users-view-item-${item.name}`} icon={isChecked(username) ? 'checkbox-checked' : 'checkbox-unchecked'} iconColor={isChecked(username) ? colors.fontHint : colors.strokeLight} + isChecked={isChecked(username)} /> ); }} diff --git a/app/views/ShareView/Header.tsx b/app/views/ShareView/Header.tsx index 63cf0cb43f..0b935d4e6a 100644 --- a/app/views/ShareView/Header.tsx +++ b/app/views/ShareView/Header.tsx @@ -96,7 +96,7 @@ const Header = React.memo(({ room, thread }: IHeader) => { }, []); return ( - <View style={styles.container}> + <View style={styles.container} accessible accessibilityLabel={`${I18n.t('Sending_to')} ${title}`} accessibilityRole='header'> <View style={styles.inner}> <Text numberOfLines={1} style={styles.text}> <Text style={[styles.text, { color: textColor }]} numberOfLines={1}> diff --git a/app/views/SidebarView/SidebarItem.tsx b/app/views/SidebarView/SidebarItem.tsx index 849d3b6fbd..e0b70ff131 100644 --- a/app/views/SidebarView/SidebarItem.tsx +++ b/app/views/SidebarView/SidebarItem.tsx @@ -23,14 +23,11 @@ const Item = React.memo(({ left, right, text, onPress, testID, current, theme, t testID={testID} onPress={onPress} style={[styles.item, current && { backgroundColor: themes[theme].strokeLight }]} - > + accessible + accessibilityLabel={text}> <View style={styles.itemHorizontal}>{left}</View> <View style={styles.itemCenter}> - <Text - style={[styles.itemText, { color: textColor || themes[theme].fontTitlesLabels }]} - numberOfLines={1} - accessibilityLabel={text} - > + <Text testID={`sidebar-custom-status-text-${text}`} style={[styles.itemText, { color: textColor || themes[theme].fontTitlesLabels }]} numberOfLines={1}> {text} </Text> </View> diff --git a/app/views/StatusView/index.tsx b/app/views/StatusView/index.tsx index 02b42ead8a..9e569bbb57 100644 --- a/app/views/StatusView/index.tsx +++ b/app/views/StatusView/index.tsx @@ -161,7 +161,7 @@ const StatusView = (): React.ReactElement => { value={statusText} containerStyle={styles.inputContainer} onChangeText={text => setStatusText(text)} - left={<StatusIcon testID={`status-view-current-${status}`} style={styles.inputLeft} status={status} size={24} />} + left={<StatusIcon accessible={false} testID={`status-view-current-${status}`} style={styles.inputLeft} status={status} size={24} />} inputStyle={styles.inputStyle} placeholder={I18n.t('What_are_you_doing_right_now')} testID='status-view-input' diff --git a/app/views/ThemeView.tsx b/app/views/ThemeView.tsx index 7f72e4fd87..5ae9c6b5a5 100644 --- a/app/views/ThemeView.tsx +++ b/app/views/ThemeView.tsx @@ -76,6 +76,8 @@ const Item = ({ onPress={onPress} testID={`theme-view-${value}`} right={() => (isSelected ? <List.Icon name='check' color={colors.badgeBackgroundLevel2} /> : null)} + additionalAcessibilityLabel={isSelected} + additionalAcessibilityLabelCheck /> <List.Separator /> </> diff --git a/app/views/ThreadMessagesView/Dropdown/DropdownItem.tsx b/app/views/ThreadMessagesView/Dropdown/DropdownItem.tsx deleted file mode 100644 index 4e13380792..0000000000 --- a/app/views/ThreadMessagesView/Dropdown/DropdownItem.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import { StyleSheet, Text, View } from 'react-native'; - -import { useTheme } from '../../../theme'; -import Touch from '../../../containers/Touch'; -import { CustomIcon, TIconsName } from '../../../containers/CustomIcon'; -import sharedStyles from '../../Styles'; - -const styles = StyleSheet.create({ - container: { - paddingVertical: 8, - minHeight: 40, - paddingHorizontal: 16, - flexDirection: 'row', - alignItems: 'center' - }, - text: { - flex: 1, - fontSize: 16, - ...sharedStyles.textRegular - } -}); - -interface IDropdownItem { - text: string; - iconName: TIconsName | null; - onPress: () => void; -} - -const DropdownItem = React.memo(({ onPress, iconName, text }: IDropdownItem) => { - const { colors } = useTheme(); - return ( - <Touch onPress={onPress} style={{ backgroundColor: colors.surfaceRoom }}> - <View style={styles.container}> - <Text style={[styles.text, { color: colors.fontSecondaryInfo }]}>{text}</Text> - {iconName ? <CustomIcon name={iconName} size={22} color={colors.fontSecondaryInfo} /> : null} - </View> - </Touch> - ); -}); - -export default DropdownItem; diff --git a/app/views/ThreadMessagesView/Dropdown/DropdownItemFilter.tsx b/app/views/ThreadMessagesView/Dropdown/DropdownItemFilter.tsx deleted file mode 100644 index 6beaa2e77a..0000000000 --- a/app/views/ThreadMessagesView/Dropdown/DropdownItemFilter.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; - -import I18n from '../../../i18n'; -import { Filter } from '../filters'; -import DropdownItem from './DropdownItem'; - -interface IDropdownItemFilter { - currentFilter: string; - value: Filter; - onPress: (value: Filter) => void; -} - -const DropdownItemFilter = ({ currentFilter, value, onPress }: IDropdownItemFilter): JSX.Element => ( - <DropdownItem text={I18n.t(value)} iconName={currentFilter === value ? 'check' : null} onPress={() => onPress(value)} /> -); - -export default DropdownItemFilter; diff --git a/app/views/ThreadMessagesView/Dropdown/DropdownItemHeader.tsx b/app/views/ThreadMessagesView/Dropdown/DropdownItemHeader.tsx deleted file mode 100644 index 9ffe4c89e0..0000000000 --- a/app/views/ThreadMessagesView/Dropdown/DropdownItemHeader.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; - -import { Filter } from '../filters'; -import I18n from '../../../i18n'; -import DropdownItem from './DropdownItem'; - -interface IDropdownItemHeader { - currentFilter: Filter; - onPress: () => void; -} - -const DropdownItemHeader = ({ currentFilter, onPress }: IDropdownItemHeader): JSX.Element => { - let text; - switch (currentFilter) { - case Filter.Following: - text = I18n.t('Threads_displaying_following'); - break; - case Filter.Unread: - text = I18n.t('Threads_displaying_unread'); - break; - default: - text = I18n.t('Threads_displaying_all'); - break; - } - return <DropdownItem text={text} iconName='filter' onPress={onPress} />; -}; - -export default DropdownItemHeader; diff --git a/app/views/ThreadMessagesView/Dropdown/index.tsx b/app/views/ThreadMessagesView/Dropdown/index.tsx deleted file mode 100644 index f7d56c952a..0000000000 --- a/app/views/ThreadMessagesView/Dropdown/index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import { Animated, Easing, TouchableWithoutFeedback } from 'react-native'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; - -import styles from '../styles'; -import { headerHeight } from '../../../lib/methods/helpers/navigation'; -import * as List from '../../../containers/List'; -import { Filter } from '../filters'; -import DropdownItemFilter from './DropdownItemFilter'; -import DropdownItemHeader from './DropdownItemHeader'; -import { useTheme } from '../../../theme'; - -const ANIMATION_DURATION = 200; - -interface IDropdownProps { - isMasterDetail?: boolean; - currentFilter: Filter; - onClose: () => void; - onFilterSelected: (value: Filter) => void; -} - -const Dropdown = ({ isMasterDetail, currentFilter, onClose, onFilterSelected }: IDropdownProps) => { - const animatedValue = useRef(new Animated.Value(0)).current; - const { colors } = useTheme(); - const insets = useSafeAreaInsets(); - - useEffect(() => { - Animated.timing(animatedValue, { - toValue: 1, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - }).start(); - }, [animatedValue]); - - const close = () => { - Animated.timing(animatedValue, { - toValue: 0, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - }).start(() => onClose()); - }; - - const heightDestination = isMasterDetail ? headerHeight + insets.top : 0; - - const translateY = animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [-300, heightDestination] // approximated height of the component when closed/open - }); - - const backdropOpacity = animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [0, colors.backdropOpacity] - }); - - return ( - <> - <TouchableWithoutFeedback onPress={close}> - <Animated.View - style={[ - styles.backdrop, - { - transform: [{ translateY }], - backgroundColor: colors.surfaceRoom, - borderColor: colors.strokeLight, - opacity: backdropOpacity, - top: heightDestination - } - ]} - /> - </TouchableWithoutFeedback> - <Animated.View - style={[ - styles.dropdownContainer, - { - transform: [{ translateY }], - backgroundColor: colors.surfaceRoom, - borderColor: colors.surfaceSelected - } - ]}> - <DropdownItemHeader currentFilter={currentFilter} onPress={close} /> - <List.Separator /> - <DropdownItemFilter currentFilter={currentFilter} value={Filter.All} onPress={onFilterSelected} /> - <DropdownItemFilter currentFilter={currentFilter} value={Filter.Following} onPress={onFilterSelected} /> - <DropdownItemFilter currentFilter={currentFilter} value={Filter.Unread} onPress={onFilterSelected} /> - </Animated.View> - </> - ); -}; - -export default Dropdown; diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index 268758589a..1ef8af54c5 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -7,6 +7,8 @@ import { StackNavigationOptions } from '@react-navigation/stack'; import { HeaderBackButton } from '@react-navigation/elements'; import { Observable, Subscription } from 'rxjs'; +import { showActionSheetRef } from '../../containers/ActionSheet'; +import { CustomIcon } from '../../containers/CustomIcon'; import ActivityIndicator from '../../containers/ActivityIndicator'; import I18n from '../../i18n'; import database from '../../lib/database'; @@ -28,8 +30,6 @@ import { LISTENER } from '../../containers/Toast'; import SearchHeader from '../../containers/SearchHeader'; import { ChatsStackParamList } from '../../stacks/types'; import { Filter } from './filters'; -import DropdownItemHeader from './Dropdown/DropdownItemHeader'; -import Dropdown from './Dropdown'; import Item from './Item'; import styles from './styles'; import { IApplicationState, IBaseScreen, IMessage, SubscriptionType, TSubscriptionModel, TThreadModel } from '../../definitions'; @@ -46,7 +46,6 @@ interface IThreadMessagesViewState { messages: any[]; displayingThreads: TThreadModel[]; subscription: TSubscriptionModel; - showFilterDropdown: boolean; currentFilter: Filter; isSearching: boolean; searchText: string; @@ -66,8 +65,6 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre private rid: string; - private t: string; - private subSubscription?: Subscription; private messagesSubscription?: Subscription; @@ -78,14 +75,12 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre super(props); this.mounted = false; this.rid = props.route.params?.rid; - this.t = props.route.params?.t; this.state = { loading: false, end: false, messages: [], displayingThreads: [], subscription: {} as TSubscriptionModel, - showFilterDropdown: false, currentFilter: Filter.All, isSearching: false, searchText: '', @@ -147,6 +142,7 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre ), headerRight: () => ( <HeaderButton.Container> + <HeaderButton.Item iconName='filter' onPress={this.showFilters} /> <HeaderButton.Item iconName='search' onPress={this.onSearchPress} testID='thread-messages-view-search-icon' /> </HeaderButton.Container> ) @@ -426,14 +422,33 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre return messages; }; - showFilterDropdown = () => this.setState({ showFilterDropdown: true }); - - closeFilterDropdown = () => this.setState({ showFilterDropdown: false }); + showFilters = () => { + const { currentFilter } = this.state; + showActionSheetRef({ + options: [ + { + title: I18n.t(Filter.All), + right: currentFilter === Filter.All ? () => <CustomIcon name='check' size={24} /> : undefined, + onPress: () => this.onFilterSelected(Filter.All) + }, + { + title: I18n.t(Filter.Following), + right: currentFilter === Filter.Following ? () => <CustomIcon name='check' size={24} /> : undefined, + onPress: () => this.onFilterSelected(Filter.Following) + }, + { + title: I18n.t(Filter.Unread), + right: currentFilter === Filter.Unread ? () => <CustomIcon name='check' size={24} /> : undefined, + onPress: () => this.onFilterSelected(Filter.Unread) + } + ] + }); + }; onFilterSelected = (filter: Filter) => { const { messages, subscription } = this.state; const displayingThreads = this.getFilteredThreads(messages, subscription, filter); - this.setState({ currentFilter: filter, displayingThreads, showFilterDropdown: false }); + this.setState({ currentFilter: filter, displayingThreads }); UserPreferences.setString(THREADS_FILTER, filter); }; @@ -464,20 +479,6 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre ); }; - renderHeader = () => { - const { messages, currentFilter } = this.state; - if (!messages.length) { - return null; - } - - return ( - <> - <DropdownItemHeader currentFilter={currentFilter} onPress={this.showFilterDropdown} /> - <List.Separator /> - </> - ); - }; - renderContent = () => { const { loading, messages, displayingThreads, currentFilter } = this.state; const { theme } = this.props; @@ -490,12 +491,7 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre } else { text = I18n.t('No_threads'); } - return ( - <> - {this.renderHeader()} - <BackgroundContainer text={text} /> - </> - ); + return <BackgroundContainer text={text} />; } return ( @@ -512,7 +508,6 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre initialNumToRender={7} removeClippedSubviews={isIOS} ItemSeparatorComponent={List.Separator} - ListHeaderComponent={this.renderHeader} ListFooterComponent={loading ? <ActivityIndicator /> : null} scrollIndicatorInsets={{ right: 1 }} // https://github.com/facebook/react-native/issues/26610#issuecomment-539843444 /> @@ -521,15 +516,10 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre render() { console.count(`${this.constructor.name}.render calls`); - const { showFilterDropdown, currentFilter } = this.state; - return ( <SafeAreaView testID='thread-messages-view'> <StatusBar /> {this.renderContent()} - {showFilterDropdown ? ( - <Dropdown currentFilter={currentFilter} onFilterSelected={this.onFilterSelected} onClose={this.closeFilterDropdown} /> - ) : null} </SafeAreaView> ); } diff --git a/app/views/ThreadMessagesView/styles.ts b/app/views/ThreadMessagesView/styles.ts index a6b30bbac7..4103e720c1 100644 --- a/app/views/ThreadMessagesView/styles.ts +++ b/app/views/ThreadMessagesView/styles.ts @@ -17,14 +17,5 @@ export default StyleSheet.create({ }, contentContainer: { paddingBottom: 30 - }, - dropdownContainer: { - width: '100%', - position: 'absolute', - top: 0, - borderBottomWidth: StyleSheet.hairlineWidth - }, - backdrop: { - ...StyleSheet.absoluteFillObject } }); diff --git a/app/views/UserNotificationPreferencesView/ListPicker.tsx b/app/views/UserNotificationPreferencesView/ListPicker.tsx index 27ae16b22d..58adaceff8 100644 --- a/app/views/UserNotificationPreferencesView/ListPicker.tsx +++ b/app/views/UserNotificationPreferencesView/ListPicker.tsx @@ -48,16 +48,15 @@ const ListPicker = ({ right: option?.value === i.value ? () => <CustomIcon name={'check'} size={20} color={colors.fontHint} /> : undefined })); + const label = option?.label ? I18n.t(option?.label, { defaultValue: option?.label }) : option?.label; + return ( <List.Item title={title} testID={testID} onPress={() => showActionSheet({ options: getOptions() })} - right={() => ( - <Text style={[styles.pickerText, { color: colors.fontHint }]}> - {option?.label ? I18n.t(option?.label, { defaultValue: option?.label }) : option?.label} - </Text> - )} + right={() => <Text style={[styles.pickerText, { color: colors.fontHint }]}>{label}</Text>} + additionalAcessibilityLabel={label} /> ); }; diff --git a/app/views/UserPreferencesView/ListPicker.tsx b/app/views/UserPreferencesView/ListPicker.tsx index 3190d29f56..0f553df430 100644 --- a/app/views/UserPreferencesView/ListPicker.tsx +++ b/app/views/UserPreferencesView/ListPicker.tsx @@ -64,16 +64,15 @@ const ListPicker = ({ right: option?.value === i.value ? () => <CustomIcon name={'check'} size={20} color={colors.fontHint} /> : undefined })); + const label = option?.label ? I18n.t(option?.label, { defaultValue: option?.label }) : option?.label; + return ( <List.Item title={title} testID={testID} onPress={() => showActionSheet({ options: getOptions() })} - right={() => ( - <Text style={[styles.title, { color: colors.fontHint }]}> - {option?.label ? I18n.t(option?.label, { defaultValue: option?.label }) : option?.label} - </Text> - )} + right={() => <Text style={[styles.title, { color: colors.fontHint }]}>{label}</Text>} + additionalAcessibilityLabel={label} /> ); }; diff --git a/e2e/tests/assorted/01-e2eencryption.spec.ts b/e2e/tests/assorted/01-e2eencryption.spec.ts index a82b79a08e..090021efe2 100644 --- a/e2e/tests/assorted/01-e2eencryption.spec.ts +++ b/e2e/tests/assorted/01-e2eencryption.spec.ts @@ -369,8 +369,8 @@ describe('E2E Encryption', () => { it('should add server and create new user', async () => { await sleep(5000); - await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + await element(by.id('rooms-list-header-servers-list-button')).tap(); + await waitFor(element(by.id('rooms-list-header-servers-list'))) .toBeVisible() .withTimeout(5000); await element(by.id('rooms-list-header-server-add')).tap(); @@ -403,8 +403,8 @@ describe('E2E Encryption', () => { }); it('should change back', async () => { - await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + await element(by.id('rooms-list-header-servers-list-button')).tap(); + await waitFor(element(by.id('rooms-list-header-servers-list'))) .toBeVisible() .withTimeout(5000); await element(by.id(`rooms-list-header-server-${data.server}`)).tap(); diff --git a/e2e/tests/assorted/06-status.spec.ts b/e2e/tests/assorted/06-status.spec.ts index 34e3afe6a4..b77a70895f 100644 --- a/e2e/tests/assorted/06-status.spec.ts +++ b/e2e/tests/assorted/06-status.spec.ts @@ -57,7 +57,7 @@ describe('Status screen', () => { await element(by.id('status-view-input')).replaceText('status-text-new'); await element(by.id('status-view-submit')).tap(); await sleep(3000); // Wait until the loading hide - await waitFor(element(by.label('status-text-new'))) + await waitFor(element(by.id(`sidebar-custom-status-text-status-text-new`))) .toExist() .withTimeout(5000); }); diff --git a/e2e/tests/assorted/07-changeserver.spec.ts b/e2e/tests/assorted/07-changeserver.spec.ts index bef8fc836f..3fb0d11b64 100644 --- a/e2e/tests/assorted/07-changeserver.spec.ts +++ b/e2e/tests/assorted/07-changeserver.spec.ts @@ -30,9 +30,12 @@ describe('Change server', () => { await deleteCreatedUsers(deleteUsersAfterAll); }); - it('should open the dropdown button, have the server add button and create workspace button', async () => { - await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + it('should open the servers list, have the server add button and create workspace button', async () => { + await waitFor(element(by.id('rooms-list-header-servers-list-button'))) + .toBeVisible() + .withTimeout(2000); + await element(by.id('rooms-list-header-servers-list-button')).tap(); + await waitFor(element(by.id('rooms-list-header-servers-list'))) .toBeVisible() .withTimeout(5000); await waitFor(element(by.id('rooms-list-header-server-add'))) @@ -57,8 +60,8 @@ describe('Change server', () => { }); it('should add server and create new user', async () => { - await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + await element(by.id('rooms-list-header-servers-list-button')).tap(); + await waitFor(element(by.id('rooms-list-header-servers-list'))) .toBeVisible() .withTimeout(5000); await element(by.id(`rooms-list-header-server-${data.alternateServer}`)).tap(); @@ -91,8 +94,8 @@ describe('Change server', () => { }); it('should change back to main server', async () => { - await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + await element(by.id('rooms-list-header-servers-list-button')).tap(); + await waitFor(element(by.id('rooms-list-header-servers-list'))) .toBeVisible() .withTimeout(5000); await element(by.id(`rooms-list-header-server-${data.server}`)).tap(); diff --git a/e2e/tests/assorted/09-joinfromdirectory.spec.ts b/e2e/tests/assorted/09-joinfromdirectory.spec.ts index 5eb6e89758..9fb6f91407 100644 --- a/e2e/tests/assorted/09-joinfromdirectory.spec.ts +++ b/e2e/tests/assorted/09-joinfromdirectory.spec.ts @@ -74,7 +74,7 @@ describe('Join room from directory', () => { await waitFor(element(by.id('directory-view'))) .toExist() .withTimeout(2000); - await element(by.id('directory-view-dropdown')).tap(); + await element(by.id('directory-view-filter')).tap(); await element(by.label('Users')).atIndex(0).tap(); await navigateToRoom(otherUser.username); }); @@ -85,7 +85,7 @@ describe('Join room from directory', () => { await waitFor(element(by.id('directory-view'))) .toExist() .withTimeout(2000); - await element(by.id('directory-view-dropdown')).tap(); + await element(by.id('directory-view-filter')).tap(); await element(by.label('Teams')).atIndex(0).tap(); await navigateToRoom(team); }); diff --git a/e2e/tests/assorted/10-deleteserver.spec.ts b/e2e/tests/assorted/10-deleteserver.spec.ts index 22d601f1cb..d3ef2d7954 100644 --- a/e2e/tests/assorted/10-deleteserver.spec.ts +++ b/e2e/tests/assorted/10-deleteserver.spec.ts @@ -36,8 +36,8 @@ describe('Delete server', () => { it('should add server', async () => { await sleep(5000); - await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + await element(by.id('rooms-list-header-servers-list-button')).tap(); + await waitFor(element(by.id('rooms-list-header-servers-list'))) .toBeVisible() .withTimeout(5000); await element(by.id('rooms-list-header-server-add')).tap(); @@ -69,14 +69,14 @@ describe('Delete server', () => { }); it('should delete server', async () => { - await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + await element(by.id('rooms-list-header-servers-list-button')).tap(); + await waitFor(element(by.id('rooms-list-header-servers-list'))) .toBeVisible() .withTimeout(5000); await element(by.id(`rooms-list-header-server-${data.server}`)).longPress(1500); await element(by[textMatcher]('Delete').and(by.type(alertButtonType))).tap(); - await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + await element(by.id('rooms-list-header-servers-list-button')).tap(); + await waitFor(element(by.id('rooms-list-header-servers-list'))) .toBeVisible() .withTimeout(5000); await waitFor(element(by.id(`rooms-list-header-server-${data.server}`))) diff --git a/e2e/tests/assorted/11-deeplinking.spec.ts b/e2e/tests/assorted/11-deeplinking.spec.ts index 0d3bb97830..bd4dcdf82a 100644 --- a/e2e/tests/assorted/11-deeplinking.spec.ts +++ b/e2e/tests/assorted/11-deeplinking.spec.ts @@ -213,8 +213,8 @@ describe('Deep linking', () => { await waitFor(element(by.id('rooms-list-view'))) .toBeVisible() .withTimeout(2000); - await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + await element(by.id('rooms-list-header-servers-list-button')).tap(); + await waitFor(element(by.id('rooms-list-header-servers-list'))) .toBeVisible() .withTimeout(5000); await element(by.id(`rooms-list-header-server-${data.alternateServer}`)).tap(); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a963bbf161..18d448c898 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1047,8 +1047,6 @@ PODS: - React-Core - react-native-notifications (5.1.0): - React-Core - - react-native-orientation-locker (1.1.8): - - React - react-native-restart (0.0.22): - React-Core - react-native-safe-area-context (3.2.0): @@ -1372,7 +1370,6 @@ DEPENDENCIES: - react-native-mmkv-storage (from `../node_modules/react-native-mmkv-storage`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-notifications (from `../node_modules/react-native-notifications`) - - react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`) - react-native-restart (from `../node_modules/react-native-restart`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - react-native-simple-crypto (from `../node_modules/react-native-simple-crypto`) @@ -1551,8 +1548,6 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-community/netinfo" react-native-notifications: :path: "../node_modules/react-native-notifications" - react-native-orientation-locker: - :path: "../node_modules/react-native-orientation-locker" react-native-restart: :path: "../node_modules/react-native-restart" react-native-safe-area-context: @@ -1727,7 +1722,6 @@ SPEC CHECKSUMS: react-native-mmkv-storage: cfb6854594cfdc5f7383a9e464bb025417d1721c react-native-netinfo: bdb108d340cdb41875c9ced535977cac6d2ff321 react-native-notifications: 4601a5a8db4ced6ae7cfc43b44d35fe437ac50c4 - react-native-orientation-locker: f0ca1a8e5031dab6b74bfb4ab33a17ed2c2fcb0d react-native-restart: 733a51ad137f15b0f8dc34c4082e55af7da00979 react-native-safe-area-context: f0906bf8bc9835ac9a9d3f97e8bde2a997d8da79 react-native-simple-crypto: 663609d550ba052dd6ee5eef9954bac274736576 diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index 09c59dc5fa..3c4f0becb2 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 0C6E2DE448364EA896869ADF /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B37C79D9BD0742CE936B6982 /* libc++.tbd */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 1C77205B81359264AD42BBF3 /* libPods-defaults-RocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DC1C73C9C5E285F42018A7B /* libPods-defaults-RocketChatRN.a */; }; 1E01C81C2511208400FEF824 /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E01C81B2511208400FEF824 /* URL+Extensions.swift */; }; 1E01C8212511301400FEF824 /* PushResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E01C8202511301400FEF824 /* PushResponse.swift */; }; 1E01C8252511303100FEF824 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E01C8242511303100FEF824 /* Notification.swift */; }; @@ -283,9 +284,7 @@ 1EFEB5982493B6640072EDC0 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFEB5972493B6640072EDC0 /* NotificationService.swift */; }; 1EFEB59C2493B6640072EDC0 /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 1EFEB5952493B6640072EDC0 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 24A2AEF2383D44B586D31C01 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 06BB44DD4855498082A744AD /* libz.tbd */; }; - 31F3B964C6AC566E0EF5AECA /* libPods-defaults-RocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 350CD3DA54F8CDCA236F25BB /* libPods-defaults-RocketChatRN.a */; }; 4C4C8603EF082F0A33A95522 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D5C142B655F8EFD006792C /* ExpoModulesProvider.swift */; }; - 54EE6E5BF0FD714BF1E75A69 /* libPods-defaults-Rocket.Chat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CB24210293E6F5D282BAF268 /* libPods-defaults-Rocket.Chat.a */; }; 65AD38372BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; 65AD38382BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; 65AD38392BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; @@ -294,7 +293,6 @@ 65AD383C2BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; 65B9A71A2AFC24190088956F /* ringtone.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 65B9A7192AFC24190088956F /* ringtone.mp3 */; }; 65B9A71B2AFC24190088956F /* ringtone.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 65B9A7192AFC24190088956F /* ringtone.mp3 */; }; - 77030DD9D7DDAF90EFF935CB /* libPods-defaults-ShareRocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BB540D897AFCDCC5C961EEDD /* libPods-defaults-ShareRocketChatRN.a */; }; 7A006F14229C83B600803143 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7A006F13229C83B600803143 /* GoogleService-Info.plist */; }; 7A0D62D2242AB187006D5C06 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7A0D62D1242AB187006D5C06 /* LaunchScreen.storyboard */; }; 7A14FCED257FEB3A005BDCD4 /* Experimental.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A14FCEC257FEB3A005BDCD4 /* Experimental.xcassets */; }; @@ -357,8 +355,10 @@ 7AE10C0728A59530003593CB /* Inter.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7AE10C0528A59530003593CB /* Inter.ttf */; }; 7AE10C0828A59530003593CB /* Inter.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7AE10C0528A59530003593CB /* Inter.ttf */; }; 85160EB6C143E0493FE5F014 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194D9A8897F4A486C2C6F89A /* ExpoModulesProvider.swift */; }; - AD9AF13B0FADB6545AE46216 /* libPods-defaults-NotificationService.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F1F9B109DFF41F259095C748 /* libPods-defaults-NotificationService.a */; }; + 864F9F60CC1C1301AB392586 /* libPods-defaults-NotificationService.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C3A803D7C0F33FAC94992D06 /* libPods-defaults-NotificationService.a */; }; + 945283F4E4DE01B83BB22420 /* libPods-defaults-Rocket.Chat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AF27585043417AE1F4BFF5F9 /* libPods-defaults-Rocket.Chat.a */; }; BC404914E86821389EEB543D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391C4F7AA7023CD41EEBD106 /* ExpoModulesProvider.swift */; }; + C2DB3AB18736E4829C709762 /* libPods-defaults-ShareRocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B8B55F1CCE1B454F23C46EC4 /* libPods-defaults-ShareRocketChatRN.a */; }; D94D81FB9E10756FAA03F203 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016747EF3B9FED8DE2C9DA14 /* ExpoModulesProvider.swift */; }; DD2BA30A89E64F189C2C24AC /* libWatermelonDB.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BA7E862283664608B3894E34 /* libWatermelonDB.a */; }; /* End PBXBuildFile section */ @@ -466,8 +466,7 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = RocketChatRN/Images.xcassets; sourceTree = "<group>"; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = RocketChatRN/Info.plist; sourceTree = "<group>"; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = RocketChatRN/main.m; sourceTree = "<group>"; }; - 1525225C45AF517E68DCDC2D /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-ShareRocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN.debug.xcconfig"; sourceTree = "<group>"; }; - 17115A1EE86DB89DBE1912EB /* Pods-defaults-Rocket.Chat.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.debug.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.debug.xcconfig"; sourceTree = "<group>"; }; + 1610999420E746E4299F1E30 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-ShareRocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN.release.xcconfig"; sourceTree = "<group>"; }; 194D9A8897F4A486C2C6F89A /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-NotificationService/ExpoModulesProvider.swift"; sourceTree = "<group>"; }; 1E01C81B2511208400FEF824 /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = "<group>"; }; 1E01C8202511301400FEF824 /* PushResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushResponse.swift; sourceTree = "<group>"; }; @@ -614,12 +613,12 @@ 1EFEB5972493B6640072EDC0 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; }; 1EFEB5992493B6640072EDC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 1EFEB5A12493B67D0072EDC0 /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = "<group>"; }; - 307B625411539125ECBAA6EA /* Pods-defaults-RocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.debug.xcconfig"; sourceTree = "<group>"; }; - 350CD3DA54F8CDCA236F25BB /* libPods-defaults-RocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-RocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 2CF5E810829371C4A8189771 /* Pods-defaults-RocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.release.xcconfig"; sourceTree = "<group>"; }; 391C4F7AA7023CD41EEBD106 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-Rocket.Chat/ExpoModulesProvider.swift"; sourceTree = "<group>"; }; 45D5C142B655F8EFD006792C /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-RocketChatRN/ExpoModulesProvider.swift"; sourceTree = "<group>"; }; + 50B2AC15B2AAC327B74A8BBB /* Pods-defaults-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.debug.xcconfig"; sourceTree = "<group>"; }; + 5DC1C73C9C5E285F42018A7B /* libPods-defaults-RocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-RocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 60B2A6A31FC4588700BD58E5 /* RocketChatRN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = RocketChatRN.entitlements; path = RocketChatRN/RocketChatRN.entitlements; sourceTree = "<group>"; }; - 628DE8E64E84BCF109FC4969 /* Pods-defaults-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.release.xcconfig"; sourceTree = "<group>"; }; 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; }; 65B9A7192AFC24190088956F /* ringtone.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ringtone.mp3; sourceTree = "<group>"; }; 7A006F13229C83B600803143 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; }; @@ -634,15 +633,16 @@ 7AAB3E52257E6A6E00707CF6 /* Rocket.Chat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rocket.Chat.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; 7AE10C0528A59530003593CB /* Inter.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Inter.ttf; sourceTree = "<group>"; }; - 7BEF39A6613B871D0A3326E1 /* Pods-defaults-Rocket.Chat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.release.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.release.xcconfig"; sourceTree = "<group>"; }; + 9F795E0928F3C7FADAD6BB77 /* Pods-defaults-Rocket.Chat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.release.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.release.xcconfig"; sourceTree = "<group>"; }; + A4CADE732CC8A42A8B30EA03 /* Pods-defaults-RocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.debug.xcconfig"; sourceTree = "<group>"; }; + AF27585043417AE1F4BFF5F9 /* libPods-defaults-Rocket.Chat.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-Rocket.Chat.a"; sourceTree = BUILT_PRODUCTS_DIR; }; B37C79D9BD0742CE936B6982 /* libc++.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; - B41392725C2EA46A36F1FFB1 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-ShareRocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN.release.xcconfig"; sourceTree = "<group>"; }; - B5F46E5C11E602B3F99D4E40 /* Pods-defaults-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.debug.xcconfig"; sourceTree = "<group>"; }; + B8B55F1CCE1B454F23C46EC4 /* libPods-defaults-ShareRocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-ShareRocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + B99AD0CC4154722C23BACE13 /* Pods-defaults-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.release.xcconfig"; sourceTree = "<group>"; }; BA7E862283664608B3894E34 /* libWatermelonDB.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libWatermelonDB.a; sourceTree = "<group>"; }; - BB540D897AFCDCC5C961EEDD /* libPods-defaults-ShareRocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-ShareRocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - CB24210293E6F5D282BAF268 /* libPods-defaults-Rocket.Chat.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-Rocket.Chat.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - D944D97B3EF00C39202CC81D /* Pods-defaults-RocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.release.xcconfig"; sourceTree = "<group>"; }; - F1F9B109DFF41F259095C748 /* libPods-defaults-NotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-NotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + BD0E5505861320CE3592D4BF /* Pods-defaults-Rocket.Chat.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.debug.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.debug.xcconfig"; sourceTree = "<group>"; }; + C3A803D7C0F33FAC94992D06 /* libPods-defaults-NotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-NotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + C8FC44D9D09F4A537E3407C2 /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-ShareRocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN.debug.xcconfig"; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -663,7 +663,7 @@ 7ACD4897222860DE00442C55 /* JavaScriptCore.framework in Frameworks */, 24A2AEF2383D44B586D31C01 /* libz.tbd in Frameworks */, DD2BA30A89E64F189C2C24AC /* libWatermelonDB.a in Frameworks */, - 31F3B964C6AC566E0EF5AECA /* libPods-defaults-RocketChatRN.a in Frameworks */, + 1C77205B81359264AD42BBF3 /* libPods-defaults-RocketChatRN.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -672,7 +672,7 @@ buildActionMask = 2147483647; files = ( 1E25743422CBA2CF005A877F /* JavaScriptCore.framework in Frameworks */, - 77030DD9D7DDAF90EFF935CB /* libPods-defaults-ShareRocketChatRN.a in Frameworks */, + C2DB3AB18736E4829C709762 /* libPods-defaults-ShareRocketChatRN.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -694,7 +694,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - AD9AF13B0FADB6545AE46216 /* libPods-defaults-NotificationService.a in Frameworks */, + 864F9F60CC1C1301AB392586 /* libPods-defaults-NotificationService.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -715,7 +715,7 @@ 7AAB3E3D257E6A6E00707CF6 /* JavaScriptCore.framework in Frameworks */, 7AAB3E3E257E6A6E00707CF6 /* libz.tbd in Frameworks */, 7AAB3E3F257E6A6E00707CF6 /* libWatermelonDB.a in Frameworks */, - 54EE6E5BF0FD714BF1E75A69 /* libPods-defaults-Rocket.Chat.a in Frameworks */, + 945283F4E4DE01B83BB22420 /* libPods-defaults-Rocket.Chat.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1148,14 +1148,14 @@ 7AC2B09613AA7C3FEBAC9F57 /* Pods */ = { isa = PBXGroup; children = ( - B5F46E5C11E602B3F99D4E40 /* Pods-defaults-NotificationService.debug.xcconfig */, - 628DE8E64E84BCF109FC4969 /* Pods-defaults-NotificationService.release.xcconfig */, - 17115A1EE86DB89DBE1912EB /* Pods-defaults-Rocket.Chat.debug.xcconfig */, - 7BEF39A6613B871D0A3326E1 /* Pods-defaults-Rocket.Chat.release.xcconfig */, - 307B625411539125ECBAA6EA /* Pods-defaults-RocketChatRN.debug.xcconfig */, - D944D97B3EF00C39202CC81D /* Pods-defaults-RocketChatRN.release.xcconfig */, - 1525225C45AF517E68DCDC2D /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */, - B41392725C2EA46A36F1FFB1 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */, + 50B2AC15B2AAC327B74A8BBB /* Pods-defaults-NotificationService.debug.xcconfig */, + B99AD0CC4154722C23BACE13 /* Pods-defaults-NotificationService.release.xcconfig */, + BD0E5505861320CE3592D4BF /* Pods-defaults-Rocket.Chat.debug.xcconfig */, + 9F795E0928F3C7FADAD6BB77 /* Pods-defaults-Rocket.Chat.release.xcconfig */, + A4CADE732CC8A42A8B30EA03 /* Pods-defaults-RocketChatRN.debug.xcconfig */, + 2CF5E810829371C4A8189771 /* Pods-defaults-RocketChatRN.release.xcconfig */, + C8FC44D9D09F4A537E3407C2 /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */, + 1610999420E746E4299F1E30 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */, ); path = Pods; sourceTree = "<group>"; @@ -1251,10 +1251,10 @@ 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */, B37C79D9BD0742CE936B6982 /* libc++.tbd */, 06BB44DD4855498082A744AD /* libz.tbd */, - F1F9B109DFF41F259095C748 /* libPods-defaults-NotificationService.a */, - CB24210293E6F5D282BAF268 /* libPods-defaults-Rocket.Chat.a */, - 350CD3DA54F8CDCA236F25BB /* libPods-defaults-RocketChatRN.a */, - BB540D897AFCDCC5C961EEDD /* libPods-defaults-ShareRocketChatRN.a */, + C3A803D7C0F33FAC94992D06 /* libPods-defaults-NotificationService.a */, + AF27585043417AE1F4BFF5F9 /* libPods-defaults-Rocket.Chat.a */, + 5DC1C73C9C5E285F42018A7B /* libPods-defaults-RocketChatRN.a */, + B8B55F1CCE1B454F23C46EC4 /* libPods-defaults-ShareRocketChatRN.a */, ); name = Frameworks; sourceTree = "<group>"; @@ -1274,7 +1274,7 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "RocketChatRN" */; buildPhases = ( - 3253E020357DE5DF3FF75C31 /* [CP] Check Pods Manifest.lock */, + 3C73B005472EB6FEED6DBBF1 /* [CP] Check Pods Manifest.lock */, 7AA5C63E23E30D110005C4A7 /* Start Packager */, 589729E8381BA997CD19EF19 /* [Expo] Configure project */, 13B07F871A680F5B00A75B9A /* Sources */, @@ -1286,8 +1286,8 @@ 1ED0389C2B507B4F00C007D4 /* Embed Watch Content */, 7AAE9EB32891A0D20024F559 /* Upload source maps to Bugsnag */, 407D3EDE3DABEE15D27BD87D /* ShellScript */, - 19D140A2EC74F6659DF82620 /* [CP] Embed Pods Frameworks */, - BD50494BAB9A4D228C650E81 /* [CP] Copy Pods Resources */, + 581F472335FB8D3145D6165C /* [CP] Embed Pods Frameworks */, + C0D0DC8D75DD64F4BA9F8198 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1305,13 +1305,13 @@ isa = PBXNativeTarget; buildConfigurationList = 1EC6ACF322CB9FC300A41C61 /* Build configuration list for PBXNativeTarget "ShareRocketChatRN" */; buildPhases = ( - 36BEC951447AB2B3D07D4BCA /* [CP] Check Pods Manifest.lock */, + A973A5C2F88ACE42FDC5A402 /* [CP] Check Pods Manifest.lock */, 2C50632AB476A038AFCB1D43 /* [Expo] Configure project */, 1EC6ACAC22CB9FC300A41C61 /* Sources */, 1EC6ACAD22CB9FC300A41C61 /* Frameworks */, 1EC6ACAE22CB9FC300A41C61 /* Resources */, 1EFE4DC322CBF36300B766B7 /* ShellScript */, - 9D37CD62B427968B0C843657 /* [CP] Copy Pods Resources */, + C3DC4216188B500AE69A8DEA /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1360,12 +1360,12 @@ isa = PBXNativeTarget; buildConfigurationList = 1EFEB5A02493B6640072EDC0 /* Build configuration list for PBXNativeTarget "NotificationService" */; buildPhases = ( - 39E6E20ED4D99EB6D79F1BCA /* [CP] Check Pods Manifest.lock */, + BA8CA2DB368A7DFA6F221A05 /* [CP] Check Pods Manifest.lock */, 86A998705576AFA7CE938617 /* [Expo] Configure project */, 1EFEB5912493B6640072EDC0 /* Sources */, 1EFEB5922493B6640072EDC0 /* Frameworks */, 1EFEB5932493B6640072EDC0 /* Resources */, - BA9071ABCBC2655C0EF5124C /* [CP] Copy Pods Resources */, + 36CB40F595F84A5786EE6F85 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1380,7 +1380,7 @@ isa = PBXNativeTarget; buildConfigurationList = 7AAB3E4F257E6A6E00707CF6 /* Build configuration list for PBXNativeTarget "Rocket.Chat" */; buildPhases = ( - A7C23C0F63F7E514868AE885 /* [CP] Check Pods Manifest.lock */, + FF48CCDFCE7D3F572702FB70 /* [CP] Check Pods Manifest.lock */, 7AAB3E13257E6A6E00707CF6 /* Start Packager */, 6723DBD924B66933E14E7EF7 /* [Expo] Configure project */, 7AAB3E14257E6A6E00707CF6 /* Sources */, @@ -1391,8 +1391,8 @@ 7AAB3E4B257E6A6E00707CF6 /* ShellScript */, 1ED1ECE32B8699DD00F6620C /* Embed Watch Content */, 7A10288726B1D15200E47EF8 /* Upload source maps to Bugsnag */, - 81C37052831D3F058FF2B0D4 /* [CP] Embed Pods Frameworks */, - 88DC6B30E9DF0B361032CC1B /* [CP] Copy Pods Resources */, + CBD4174E0909B1B62ED55A6E /* [CP] Embed Pods Frameworks */, + 6DBC49C0F170C83D191D0450 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1586,26 +1586,6 @@ shellPath = /bin/sh; shellScript = ". ~/.nvm/nvm.sh\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\"\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; }; - 19D140A2EC74F6659DF82620 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 1E1EA8082326CCE300E22452 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1659,29 +1639,83 @@ shellPath = /bin/sh; shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-ShareRocketChatRN/expo-configure-project.sh\"\n"; }; - 3253E020357DE5DF3FF75C31 /* [CP] Check Pods Manifest.lock */ = { + 36CB40F595F84A5786EE6F85 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics/FirebaseCrashlytics_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNDeviceInfo/RNDeviceInfoPrivacyInfo.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", ); + name = "[CP] Copy Pods Resources"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-RocketChatRN-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCrashlytics_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNDeviceInfoPrivacyInfo.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 36BEC951447AB2B3D07D4BCA /* [CP] Check Pods Manifest.lock */ = { + 3C73B005472EB6FEED6DBBF1 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1696,36 +1730,32 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-ShareRocketChatRN-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-defaults-RocketChatRN-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 39E6E20ED4D99EB6D79F1BCA /* [CP] Check Pods Manifest.lock */ = { + 407D3EDE3DABEE15D27BD87D /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 407D3EDE3DABEE15D27BD87D /* ShellScript */ = { + 581F472335FB8D3145D6165C /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1735,8 +1765,10 @@ "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); + name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -1781,6 +1813,82 @@ shellPath = /bin/sh; shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-Rocket.Chat/expo-configure-project.sh\"\n"; }; + 6DBC49C0F170C83D191D0450 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics/FirebaseCrashlytics_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNDeviceInfo/RNDeviceInfoPrivacyInfo.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf", + "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCrashlytics_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNDeviceInfoPrivacyInfo.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 7A10288726B1D15200E47EF8 /* Upload source maps to Bugsnag */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1886,52 +1994,76 @@ shellPath = /bin/sh; shellScript = "SOURCE_MAP=\"$TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\" ../node_modules/@bugsnag/react-native/bugsnag-react-native-xcode.sh\n"; }; - 81C37052831D3F058FF2B0D4 /* [CP] Embed Pods Frameworks */ = { + 86A998705576AFA7CE938617 /* [Expo] Configure project */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); - name = "[CP] Embed Pods Frameworks"; + name = "[Expo] Configure project"; + outputFileListPaths = ( + ); outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh\"\n"; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-NotificationService/expo-configure-project.sh\"\n"; + }; + A973A5C2F88ACE42FDC5A402 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-defaults-ShareRocketChatRN-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 86A998705576AFA7CE938617 /* [Expo] Configure project */ = { + BA8CA2DB368A7DFA6F221A05 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "[Expo] Configure project"; + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-NotificationService/expo-configure-project.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; }; - 88DC6B30E9DF0B361032CC1B /* [CP] Copy Pods Resources */ = { + C0D0DC8D75DD64F4BA9F8198 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", @@ -1998,10 +2130,10 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 9D37CD62B427968B0C843657 /* [CP] Copy Pods Resources */ = { + C3DC4216188B500AE69A8DEA /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -2077,178 +2209,46 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN-resources.sh\"\n"; showEnvVarsInLog = 0; }; - A7C23C0F63F7E514868AE885 /* [CP] Check Pods Manifest.lock */ = { + CBD4174E0909B1B62ED55A6E /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); + name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-Rocket.Chat-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - BA9071ABCBC2655C0EF5124C /* [CP] Copy Pods Resources */ = { + FF48CCDFCE7D3F572702FB70 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics/FirebaseCrashlytics_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNDeviceInfo/RNDeviceInfoPrivacyInfo.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCrashlytics_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNDeviceInfoPrivacyInfo.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - BD50494BAB9A4D228C650E81 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( + inputFileListPaths = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics/FirebaseCrashlytics_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNDeviceInfo/RNDeviceInfoPrivacyInfo.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "[CP] Copy Pods Resources"; outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCrashlytics_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNDeviceInfoPrivacyInfo.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", + "$(DERIVED_FILE_DIR)/Pods-defaults-Rocket.Chat-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -2628,7 +2628,7 @@ /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 307B625411539125ECBAA6EA /* Pods-defaults-RocketChatRN.debug.xcconfig */; + baseConfigurationReference = A4CADE732CC8A42A8B30EA03 /* Pods-defaults-RocketChatRN.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2689,7 +2689,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D944D97B3EF00C39202CC81D /* Pods-defaults-RocketChatRN.release.xcconfig */; + baseConfigurationReference = 2CF5E810829371C4A8189771 /* Pods-defaults-RocketChatRN.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2750,7 +2750,7 @@ }; 1EC6ACBC22CB9FC300A41C61 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 1525225C45AF517E68DCDC2D /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */; + baseConfigurationReference = C8FC44D9D09F4A537E3407C2 /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; APPLICATION_EXTENSION_API_ONLY = YES; @@ -2826,7 +2826,7 @@ }; 1EC6ACBD22CB9FC300A41C61 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B41392725C2EA46A36F1FFB1 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */; + baseConfigurationReference = 1610999420E746E4299F1E30 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; APPLICATION_EXTENSION_API_ONLY = YES; @@ -3110,7 +3110,7 @@ }; 1EFEB59D2493B6640072EDC0 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B5F46E5C11E602B3F99D4E40 /* Pods-defaults-NotificationService.debug.xcconfig */; + baseConfigurationReference = 50B2AC15B2AAC327B74A8BBB /* Pods-defaults-NotificationService.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; CLANG_ANALYZER_NONNULL = YES; @@ -3152,7 +3152,7 @@ }; 1EFEB59E2493B6640072EDC0 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 628DE8E64E84BCF109FC4969 /* Pods-defaults-NotificationService.release.xcconfig */; + baseConfigurationReference = B99AD0CC4154722C23BACE13 /* Pods-defaults-NotificationService.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; CLANG_ANALYZER_NONNULL = YES; @@ -3195,7 +3195,7 @@ }; 7AAB3E50257E6A6E00707CF6 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 17115A1EE86DB89DBE1912EB /* Pods-defaults-Rocket.Chat.debug.xcconfig */; + baseConfigurationReference = BD0E5505861320CE3592D4BF /* Pods-defaults-Rocket.Chat.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -3255,7 +3255,7 @@ }; 7AAB3E51257E6A6E00707CF6 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7BEF39A6613B871D0A3326E1 /* Pods-defaults-Rocket.Chat.release.xcconfig */; + baseConfigurationReference = 9F795E0928F3C7FADAD6BB77 /* Pods-defaults-Rocket.Chat.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; diff --git a/ios/RocketChatRN/AppDelegate.mm b/ios/RocketChatRN/AppDelegate.mm index 030274a1fa..07e098ec9b 100644 --- a/ios/RocketChatRN/AppDelegate.mm +++ b/ios/RocketChatRN/AppDelegate.mm @@ -3,7 +3,6 @@ #import <React/RCTLinkingManager.h> #import "RNNotifications.h" #import "RNBootSplash.h" -#import "Orientation.h" #import <Firebase.h> #import <Bugsnag/Bugsnag.h> #import <MMKV/MMKV.h> @@ -79,8 +78,4 @@ - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull N restorationHandler:restorationHandler]; } -- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window -{ - return [Orientation getOrientation]; -} -@end \ No newline at end of file +@end diff --git a/package.json b/package.json index ea5c37c2fd..9bfb61e776 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,6 @@ "react-native-navigation-bar-color": "2.0.1", "react-native-notifications": "5.1.0", "react-native-notifier": "1.6.1", - "react-native-orientation-locker": "1.1.8", "react-native-picker-select": "9.0.1", "react-native-platform-touchable": "1.1.1", "react-native-popover-view": "5.1.7", diff --git a/patches/react-native-orientation-locker+1.1.8.patch b/patches/react-native-orientation-locker+1.1.8.patch deleted file mode 100644 index 3da5262fcf..0000000000 --- a/patches/react-native-orientation-locker+1.1.8.patch +++ /dev/null @@ -1,25 +0,0 @@ -diff --git a/node_modules/react-native-orientation-locker/android/src/main/java/org/wonday/orientation/OrientationModule.java b/node_modules/react-native-orientation-locker/android/src/main/java/org/wonday/orientation/OrientationModule.java -index af1d952..ba056e4 100644 ---- a/node_modules/react-native-orientation-locker/android/src/main/java/org/wonday/orientation/OrientationModule.java -+++ b/node_modules/react-native-orientation-locker/android/src/main/java/org/wonday/orientation/OrientationModule.java -@@ -22,6 +22,7 @@ import android.view.Surface; - import android.view.WindowManager; - import android.util.DisplayMetrics; - import android.hardware.SensorManager; -+import android.os.Build; - - import com.facebook.common.logging.FLog; - import com.facebook.react.bridge.Arguments; -@@ -345,7 +346,11 @@ public class OrientationModule extends ReactContextBaseJavaModule implements Lif - - final Activity activity = getCurrentActivity(); - if (activity == null) return; -- activity.registerReceiver(mReceiver, new IntentFilter("onConfigurationChanged")); -+ if (Build.VERSION.SDK_INT >= 34 && ctx.getApplicationInfo().targetSdkVersion >= 34) { -+ ctx.registerReceiver(mReceiver, new IntentFilter("onConfigurationChanged"), Context.RECEIVER_NOT_EXPORTED); -+ }else{ -+ ctx.registerReceiver(mReceiver, new IntentFilter("onConfigurationChanged")); -+ } - } - @Override - public void onHostPause() { diff --git a/yarn.lock b/yarn.lock index 35cb65a7ae..f388017695 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12093,11 +12093,6 @@ react-native-notifier@1.6.1: resolved "https://registry.yarnpkg.com/react-native-notifier/-/react-native-notifier-1.6.1.tgz#eec07bdebed6c22cd22f5167555b7762e4119552" integrity sha512-uq58vefWCmgk9B/GU0Z9P6M8Su5IuRBtKmroSX7luISEdh3r8QvJo/G6jgK2sO0QhM5QvJOGWBDm1JGEkYL4IQ== -react-native-orientation-locker@1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/react-native-orientation-locker/-/react-native-orientation-locker-1.1.8.tgz#45d1c9e002496b8d286ec8932d6e3e7d341f9c85" - integrity sha512-+Vd7x6O/3zGqYIMXpeDlaw3ma074Dtnocm8ryT9v5SvaiEcWSzII4frPgXaUcc/MiCq4OWZ1JtVoyw75mdomQw== - react-native-picker-select@9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/react-native-picker-select/-/react-native-picker-select-9.0.1.tgz#af454433061a18b7307a725fc045c05ad494e300" From bb59d980b23484a7897611de77d6847bcfad77f4 Mon Sep 17 00:00:00 2001 From: Masoud Tavakkoli <mtg1376@outlook.com> Date: Mon, 5 Aug 2024 17:13:48 +0330 Subject: [PATCH 07/26] chore: Platform with type "Windows_NT" and architecture "x64" is not supported by @bugsnag/cli. (#5790) Co-authored-by: Masoud Tavakkoli <promasoud@icloud.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9bfb61e776..1080a470a5 100644 --- a/package.json +++ b/package.json @@ -160,7 +160,7 @@ "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", "@babel/preset-env": "^7.24.3", "@babel/runtime": "^7.24.1", - "@bugsnag/cli": "^2.1.1", + "@bugsnag/cli": "^2.2.0", "@bugsnag/source-maps": "^2.2.0", "@react-native/babel-preset": "0.73.21", "@react-native/eslint-config": "0.73.2", diff --git a/yarn.lock b/yarn.lock index f388017695..ac0bf7225b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2206,10 +2206,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@bugsnag/cli@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@bugsnag/cli/-/cli-2.1.1.tgz#a1c0b879525ce74e017233b2cb50f3b6083279a0" - integrity sha512-N5Ubpaz+g4sbxsM0tWgE3HQOvg73Hmmywj8lzP9deEgRcYG9/iLj1Nn5CFxzK+i516iTF6TBe26bIW7ItMHY0w== +"@bugsnag/cli@^2.2.0": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@bugsnag/cli/-/cli-2.4.1.tgz#e11533f301f6d7fe08899658b40c659d3032dfd4" + integrity sha512-4Ey2NLnD6KnZ6qOXUZkRAc/LjUp8Clw07tThKZYTfvX0st0EZzZ5y4gKu5igYFCEF/sGnNhjSUxx0liWCTtcTg== dependencies: axios "^1.6.2" js-yaml "^4.1.0" From 6f8fb6b3067b62d76aa5d3321e9a5eb4a001dcd7 Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Thu, 15 Aug 2024 13:29:31 -0300 Subject: [PATCH 08/26] fix: draft keeping content incorrectly sometimes (#5827) --- .../components/ComposerInput.tsx | 14 +++++---- .../MessageComposer/hooks/useAutoSaveDraft.ts | 30 +++++++++++++------ app/containers/MessageComposer/interfaces.ts | 2 +- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/app/containers/MessageComposer/components/ComposerInput.tsx b/app/containers/MessageComposer/components/ComposerInput.tsx index 096f799a84..f5e672c1e0 100644 --- a/app/containers/MessageComposer/components/ComposerInput.tsx +++ b/app/containers/MessageComposer/components/ComposerInput.tsx @@ -58,7 +58,7 @@ export const ComposerInput = memo( const usedCannedResponse = route.params?.usedCannedResponse; const prevAction = usePrevious(action); - useAutoSaveDraft(textRef.current); + const { saveMessageDraft } = useAutoSaveDraft(textRef.current); // Draft/Canned Responses useEffect(() => { @@ -142,7 +142,7 @@ export const ComposerInput = memo( useImperativeHandle(ref, () => ({ getTextAndClear: () => { const text = textRef.current; - setInput(''); + setInput('', undefined, true); return text; }, getText: () => textRef.current, @@ -151,12 +151,16 @@ export const ComposerInput = memo( onAutocompleteItemSelected })); - const setInput: TSetInput = (text, selection) => { + const setInput: TSetInput = (text, selection, forceUpdateDraftMessage) => { const message = text.trim(); textRef.current = message; - if (inputRef.current) { - inputRef.current.setNativeProps({ text }); + + if (forceUpdateDraftMessage) { + saveMessageDraft(''); } + + inputRef.current?.setNativeProps?.({ text }); + if (selection) { // setSelection won't trigger onSelectionChange, so we need it to be ran after new text is set setTimeout(() => { diff --git a/app/containers/MessageComposer/hooks/useAutoSaveDraft.ts b/app/containers/MessageComposer/hooks/useAutoSaveDraft.ts index a846237f9c..a0df0aebeb 100644 --- a/app/containers/MessageComposer/hooks/useAutoSaveDraft.ts +++ b/app/containers/MessageComposer/hooks/useAutoSaveDraft.ts @@ -14,16 +14,26 @@ export const useAutoSaveDraft = (text = '') => { const mounted = useRef(true); - const saveMessageDraft = useCallback(() => { - if (route.name === 'ShareView') return; - if (action === 'edit') return; - const draftMessage = selectedMessages?.length ? JSON.stringify({ quotes: selectedMessages, msg: text }) : text; - if (oldText.current !== draftMessage || (oldText.current === '' && draftMessage === '')) { - oldText.current = draftMessage; - saveDraftMessage({ rid, tmid, draftMessage }); - } - }, [action, rid, tmid, text, selectedMessages?.length, route.name]); + const saveMessageDraft = useCallback( + (m?: string) => { + if (route.name === 'ShareView') return; + if (action === 'edit') return; + let draftMessage = ''; + if (selectedMessages?.length) { + draftMessage = JSON.stringify({ quotes: selectedMessages, msg: text }); + } else { + draftMessage = m ?? text; + } + if (oldText.current !== draftMessage || (oldText.current === '' && draftMessage === '') || m !== undefined) { + oldText.current = draftMessage; + saveDraftMessage({ rid, tmid, draftMessage }); + } + }, + [action, rid, tmid, text, selectedMessages?.length, route.name] + ); + + // if focused on composer input, saves every N seconds useEffect(() => { if (focused) { intervalRef.current = setInterval(saveMessageDraft, 3000) as any; @@ -52,4 +62,6 @@ export const useAutoSaveDraft = (text = '') => { }, [saveMessageDraft] ); + + return { saveMessageDraft }; }; diff --git a/app/containers/MessageComposer/interfaces.ts b/app/containers/MessageComposer/interfaces.ts index ac68e05fef..8f6d05fcdb 100644 --- a/app/containers/MessageComposer/interfaces.ts +++ b/app/containers/MessageComposer/interfaces.ts @@ -17,7 +17,7 @@ export interface IInputSelection { end: number; } -export type TSetInput = (text: string, selection?: IInputSelection) => void; +export type TSetInput = (text: string, selection?: IInputSelection, forceUpdateDraftMessage?: boolean) => void; export type TMicOrSend = 'mic' | 'send'; From 0437cde832906a7987842233fdfa43891b1fa1bf Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Mon, 26 Aug 2024 15:44:59 -0300 Subject: [PATCH 09/26] feat: Add text to collapsible quote (#5835) --- .../CollapsibleQuote.test.tsx | 17 ++++++++++++ .../Attachments/CollapsibleQuote/index.tsx | 27 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/app/containers/message/Components/Attachments/CollapsibleQuote/CollapsibleQuote.test.tsx b/app/containers/message/Components/Attachments/CollapsibleQuote/CollapsibleQuote.test.tsx index bbe9a27354..45c52ca973 100644 --- a/app/containers/message/Components/Attachments/CollapsibleQuote/CollapsibleQuote.test.tsx +++ b/app/containers/message/Components/Attachments/CollapsibleQuote/CollapsibleQuote.test.tsx @@ -7,6 +7,7 @@ import CollapsibleQuote from '.'; const testAttachment = { ts: '1970-01-01T00:00:00.000Z', title: 'Engineering (9 today)', + text: 'Test title', fields: [ { title: 'Out Today:\n', @@ -46,6 +47,22 @@ describe('CollapsibleQuote', () => { expect(collapsibleQuoteTitle.props.children).toEqual(testAttachment.title); }); + test('text exists and is correct', async () => { + const collapsibleQuote = render(<Render />); + const collapsibleQuoteTouchable = await collapsibleQuote.findByTestId(touchableTestID); + // open + fireEvent.press(collapsibleQuoteTouchable); + const open = within(collapsibleQuoteTouchable); + const textOpen = open.getByLabelText(testAttachment.text); + expect(textOpen).toBeTruthy(); + // close + fireEvent.press(collapsibleQuoteTouchable); + collapsibleQuote.rerender(<Render />); + const close = within(collapsibleQuoteTouchable); + const textClosed = close.queryByText(testAttachment.text); + expect(textClosed).toBeNull(); + }); + test('fields render title correctly', async () => { const collapsibleQuote = render(<Render />); const collapsibleQuoteTouchable = await collapsibleQuote.findByTestId(touchableTestID); diff --git a/app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx b/app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx index 20d9cb8b4b..b492e3fe4a 100644 --- a/app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx +++ b/app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx @@ -37,6 +37,11 @@ const styles = StyleSheet.create({ paddingTop: 10, paddingBottom: 10 }, + fieldText: { + fontSize: 15, + padding: 10, + ...sharedStyles.textRegular + }, fieldTitle: { fontSize: 15, ...sharedStyles.textBold @@ -67,6 +72,11 @@ const styles = StyleSheet.create({ } }); +interface IMessageAttText { + text?: string; + getCustomEmoji: TGetCustomEmoji; +} + interface IMessageFields { attachment: IAttachment; getCustomEmoji: TGetCustomEmoji; @@ -79,6 +89,22 @@ interface IMessageReply { getCustomEmoji: TGetCustomEmoji; } +const AttText = React.memo( + ({ text, getCustomEmoji }: IMessageAttText) => { + const { theme } = useTheme(); + const { user } = useContext(MessageContext); + + if (!text) { + return null; + } + + return ( + <Markdown msg={text} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} style={[styles.fieldText]} /> + ); + }, + (prevProps, nextProps) => prevProps.text === nextProps.text +); + const Fields = React.memo( ({ attachment, getCustomEmoji }: IMessageFields) => { const { theme } = useTheme(); @@ -162,6 +188,7 @@ const CollapsibleQuote = React.memo( <View style={styles.authorContainer}> <Text style={[styles.title, { color: fontSecondaryInfo }]}>{attachment.title}</Text> </View> + {!collapsed && <AttText text={attachment.text} getCustomEmoji={getCustomEmoji} />} {!collapsed && <Fields attachment={attachment} getCustomEmoji={getCustomEmoji} />} </View> <View style={styles.iconContainer}> From fc7c05078f5580a200385f49edca7d0b7c6ab050 Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Mon, 26 Aug 2024 15:52:47 -0300 Subject: [PATCH 10/26] fix: Room encrypted with E2E_Enabled setting disabled (#5834) --- app/lib/encryption/utils.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/lib/encryption/utils.ts b/app/lib/encryption/utils.ts index dbc7447f2e..a79a55a6cd 100644 --- a/app/lib/encryption/utils.ts +++ b/app/lib/encryption/utils.ts @@ -108,6 +108,10 @@ export const isMissingRoomE2EEKey = ({ E2EKey: TSubscriptionModel['E2EKey']; }): boolean => { const serverVersion = store.getState().server.version; + const e2eeEnabled = store.getState().settings.E2E_Enable; + if (!e2eeEnabled) { + return false; + } if (compareServerVersion(serverVersion, 'lowerThan', '6.10.0')) { return false; } @@ -123,6 +127,10 @@ export const isE2EEDisabledEncryptedRoom = ({ roomEncrypted: TSubscriptionModel['encrypted']; }): boolean => { const serverVersion = store.getState().server.version; + const e2eeEnabled = store.getState().settings.E2E_Enable; + if (!e2eeEnabled) { + return false; + } if (compareServerVersion(serverVersion, 'lowerThan', '6.10.0')) { return false; } From 3ba09b4b09495f78166e23f05d8bd40733150a22 Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Wed, 28 Aug 2024 11:14:10 -0300 Subject: [PATCH 11/26] fix: Action sheet closing whenever an input is focused (#5842) --- package.json | 2 +- patches/@discord+bottom-sheet+4.6.1.patch | 14 ++++++++++++++ yarn.lock | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1080a470a5..1f8f0ecd2d 100644 --- a/package.json +++ b/package.json @@ -174,7 +174,7 @@ "@types/bytebuffer": "^5.0.44", "@types/ejson": "^2.1.3", "@types/i18n-js": "^3.8.3", - "@types/invariant": "^2.2.37", + "@types/invariant": "2.2.37", "@types/jest": "^29.5.12", "@types/jsrsasign": "^10.5.8", "@types/lodash": "^4.14.188", diff --git a/patches/@discord+bottom-sheet+4.6.1.patch b/patches/@discord+bottom-sheet+4.6.1.patch index 66621ae989..3a2e2e3c01 100644 --- a/patches/@discord+bottom-sheet+4.6.1.patch +++ b/patches/@discord+bottom-sheet+4.6.1.patch @@ -1,3 +1,17 @@ +diff --git a/node_modules/@discord/bottom-sheet/src/components/bottomSheet/BottomSheet.tsx b/node_modules/@discord/bottom-sheet/src/components/bottomSheet/BottomSheet.tsx +index 2897fef..9a8505e 100644 +--- a/node_modules/@discord/bottom-sheet/src/components/bottomSheet/BottomSheet.tsx ++++ b/node_modules/@discord/bottom-sheet/src/components/bottomSheet/BottomSheet.tsx +@@ -1382,7 +1382,8 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>( + if (containerHeight !== _previousContainerHeight) { + animationSource = ANIMATION_SOURCE.CONTAINER_RESIZE; + animationConfig = { +- duration: 0, ++ // https://github.com/gorhom/react-native-bottom-sheet/pull/1497 ++ duration: 1, + }; + } + } diff --git a/node_modules/@discord/bottom-sheet/src/components/bottomSheetHandleContainer/BottomSheetHandleContainer.tsx b/node_modules/@discord/bottom-sheet/src/components/bottomSheetHandleContainer/BottomSheetHandleContainer.tsx index 2219e0f..59f90ba 100644 --- a/node_modules/@discord/bottom-sheet/src/components/bottomSheetHandleContainer/BottomSheetHandleContainer.tsx diff --git a/yarn.lock b/yarn.lock index ac0bf7225b..71bc3e9138 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4317,7 +4317,7 @@ resolved "https://registry.yarnpkg.com/@types/i18n-js/-/i18n-js-3.8.9.tgz#074d1389539d2db992e6afd7eb379aa02929ef93" integrity sha512-bSxgya4x5O+x+QhfCGckiDDE+17XGPp1TNBgBA/vfF5EwdiZC70F4cKG5QK2v44+v62oY7/t/InreRhxskulcA== -"@types/invariant@^2.2.37": +"@types/invariant@2.2.37": version "2.2.37" resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.37.tgz#1709741e534364d653c87dff22fc76fa94aa7bc0" integrity sha512-IwpIMieE55oGWiXkQPSBY1nw1nFs6bsKXTFskNY8sdS17K24vyEBRQZEwlRS7ZmXCWnJcQtbxWzly+cODWGs2A== From d42a338a8420159dbe1f88cdc9616303679db8b5 Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Wed, 28 Aug 2024 11:15:52 -0300 Subject: [PATCH 12/26] feat: Setting to enable file encryption (#5841) --- app/lib/constants/defaultSettings.ts | 7 +++++-- app/lib/encryption/encryption.ts | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/lib/constants/defaultSettings.ts b/app/lib/constants/defaultSettings.ts index dc51e79c1a..ab1b4ea150 100644 --- a/app/lib/constants/defaultSettings.ts +++ b/app/lib/constants/defaultSettings.ts @@ -105,6 +105,9 @@ export const defaultSettings = { E2E_Enabled_Default_PrivateRooms: { type: 'valueAsBoolean' }, + E2E_Enable_Encrypt_Files: { + type: 'valueAsBoolean' + }, Accounts_Directory_DefaultView: { type: 'valueAsString' }, @@ -249,10 +252,10 @@ export const defaultSettings = { CDN_PREFIX: { type: 'valueAsString' }, - Accounts_RequirePasswordConfirmation:{ + Accounts_RequirePasswordConfirmation: { type: 'valueAsBoolean' }, - Accounts_ConfirmPasswordPlaceholder:{ + Accounts_ConfirmPasswordPlaceholder: { type: 'valueAsString' }, ...deprecatedSettings diff --git a/app/lib/encryption/encryption.ts b/app/lib/encryption/encryption.ts index 7ccadbfe31..12c601c8ff 100644 --- a/app/lib/encryption/encryption.ts +++ b/app/lib/encryption/encryption.ts @@ -531,7 +531,8 @@ class Encryption { throw new Error('Subscription not found'); } - if (!subscription.encrypted) { + const { E2E_Enable_Encrypt_Files } = store.getState().settings; + if (!subscription.encrypted || !E2E_Enable_Encrypt_Files) { // Send a non encrypted message return { file }; } From 7d38ea0515564a20565ebddd095f05345f1e292b Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Thu, 29 Aug 2024 16:26:35 -0300 Subject: [PATCH 13/26] fix: Change E2EE key password (#5844) --- app/definitions/rest/v1/e2e.ts | 2 +- app/lib/encryption/encryption.ts | 10 +++++++++- app/lib/services/restApi.ts | 4 ++-- e2e/tests/assorted/01-e2eencryption.spec.ts | 14 +++++++------- e2e/tests/assorted/10-deleteserver.spec.ts | 3 +++ e2e/tests/assorted/11-deeplinking.spec.ts | 3 +++ 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/app/definitions/rest/v1/e2e.ts b/app/definitions/rest/v1/e2e.ts index 6c4ef7c8f7..bf4db5b117 100644 --- a/app/definitions/rest/v1/e2e.ts +++ b/app/definitions/rest/v1/e2e.ts @@ -2,7 +2,7 @@ import { IUser } from '../../IUser'; export type E2eEndpoints = { 'e2e.setUserPublicAndPrivateKeys': { - POST: (params: { public_key: string; private_key: string }) => void; + POST: (params: { public_key: string; private_key: string; force?: boolean }) => void; }; 'e2e.getUsersOfRoomWithoutKey': { GET: (params: { rid: string }) => { diff --git a/app/lib/encryption/encryption.ts b/app/lib/encryption/encryption.ts index 12c601c8ff..de401c64d0 100644 --- a/app/lib/encryption/encryption.ts +++ b/app/lib/encryption/encryption.ts @@ -27,6 +27,7 @@ import { getSubscriptionByRoomId } from '../database/services/Subscription'; import log from '../methods/helpers/log'; import protectedFunction from '../methods/helpers/protectedFunction'; import UserPreferences from '../methods/userPreferences'; +import { compareServerVersion } from '../methods/helpers'; import { Services } from '../services'; import { store } from '../store/auxStore'; import { MAX_CONCURRENT_QUEUE } from './constants'; @@ -228,8 +229,15 @@ class Encryption { throw new Error('Public key not found in local storage, password not changed'); } + // Only send force param for newer worspace versions + const { version } = store.getState().server; + let force = false; + if (compareServerVersion(version, 'greaterThanOrEqualTo', '6.10.0')) { + force = true; + } + // Send the new keys to the server - await Services.e2eSetUserPublicAndPrivateKeys(publicKey, encodedPrivateKey); + await Services.e2eSetUserPublicAndPrivateKeys(publicKey, encodedPrivateKey, force); }; // get a encryption room instance diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index ce42c92986..e201628ac6 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -54,9 +54,9 @@ export const createChannel = ({ return sdk.post(type ? 'groups.create' : 'channels.create', params); }; -export const e2eSetUserPublicAndPrivateKeys = (public_key: string, private_key: string) => +export const e2eSetUserPublicAndPrivateKeys = (public_key: string, private_key: string, force: boolean = false) => // RC 2.2.0 - sdk.post('e2e.setUserPublicAndPrivateKeys', { public_key, private_key }); + sdk.post('e2e.setUserPublicAndPrivateKeys', { public_key, private_key, ...(force && { force: true }) }); export const e2eRequestSubscriptionKeys = (): Promise<boolean> => // RC 0.72.0 diff --git a/e2e/tests/assorted/01-e2eencryption.spec.ts b/e2e/tests/assorted/01-e2eencryption.spec.ts index 090021efe2..9e84e9e876 100644 --- a/e2e/tests/assorted/01-e2eencryption.spec.ts +++ b/e2e/tests/assorted/01-e2eencryption.spec.ts @@ -290,10 +290,9 @@ describe('E2E Encryption', () => { await waitFor(element(by[textMatcher](mockedMessageText)).atIndex(0)) .not.toExist() .withTimeout(2000); - // await waitFor(element(by.id('room-view-encrypted-room'))) - // .toBeVisible() - // .withTimeout(2000); - await expect(element(by.label('Encrypted message')).atIndex(0)).toExist(); + await waitFor(element(by.id('room-view-encrypted-room'))) + .toBeVisible() + .withTimeout(2000); }); it('should enter new e2e password and messages should be decrypted', async () => { @@ -391,11 +390,12 @@ describe('E2E Encryption', () => { // Register new user const randomUser = data.randomUser(); - await element(by.id('register-view-name')).replaceText(randomUser.username); + await element(by.id('register-view-name')).replaceText(randomUser.name); + await element(by.id('register-view-name')).tapReturnKey(); await element(by.id('register-view-username')).replaceText(randomUser.username); + await element(by.id('register-view-username')).tapReturnKey(); await element(by.id('register-view-email')).replaceText(randomUser.email); - await element(by.id('register-view-password')).replaceText(randomUser.password); - await element(by.id('register-view-password')).tapReturnKey(); + await element(by.id('register-view-email')).tapReturnKey(); await expectValidRegisterOrRetry(device.getPlatform()); deleteUsersAfterAll.push({ server: data.alternateServer, username: randomUser.username }); diff --git a/e2e/tests/assorted/10-deleteserver.spec.ts b/e2e/tests/assorted/10-deleteserver.spec.ts index d3ef2d7954..49f020db37 100644 --- a/e2e/tests/assorted/10-deleteserver.spec.ts +++ b/e2e/tests/assorted/10-deleteserver.spec.ts @@ -58,8 +58,11 @@ describe('Delete server', () => { // Register new user const randomUser = data.randomUser(); await element(by.id('register-view-name')).replaceText(randomUser.name); + await element(by.id('register-view-name')).tapReturnKey(); await element(by.id('register-view-username')).replaceText(randomUser.username); + await element(by.id('register-view-username')).tapReturnKey(); await element(by.id('register-view-email')).replaceText(randomUser.email); + await element(by.id('register-view-email')).tapReturnKey(); await element(by.id('register-view-password')).replaceText(randomUser.password); await element(by.id('register-view-password')).tapReturnKey(); await expectValidRegisterOrRetry(device.getPlatform()); diff --git a/e2e/tests/assorted/11-deeplinking.spec.ts b/e2e/tests/assorted/11-deeplinking.spec.ts index bd4dcdf82a..5a5c78668b 100644 --- a/e2e/tests/assorted/11-deeplinking.spec.ts +++ b/e2e/tests/assorted/11-deeplinking.spec.ts @@ -98,8 +98,11 @@ describe('Deep linking', () => { await navigateToRegister(data.alternateServer); const randomUser = data.randomUser(); await element(by.id('register-view-name')).replaceText(randomUser.name); + await element(by.id('register-view-name')).tapReturnKey(); await element(by.id('register-view-username')).replaceText(randomUser.username); + await element(by.id('register-view-username')).tapReturnKey(); await element(by.id('register-view-email')).replaceText(randomUser.email); + await element(by.id('register-view-email')).tapReturnKey(); await element(by.id('register-view-password')).replaceText(randomUser.password); await element(by.id('register-view-password')).tapReturnKey(); await expectValidRegisterOrRetry(device.getPlatform()); From 3f38926bf48e73ee0c54bfb764c62a79f5d3cb70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:28:00 -0300 Subject: [PATCH 14/26] chore(deps): bump the npm_and_yarn group with 8 updates (#5843) --- package.json | 8 ++--- yarn.lock | 98 +++++++++++++++++++++------------------------------- 2 files changed, 43 insertions(+), 63 deletions(-) diff --git a/package.json b/package.json index 1f8f0ecd2d..c2e34a64ee 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "i18n-js": "3.9.2", "js-base64": "3.6.1", "js-sha256": "0.9.0", - "jsrsasign": "10.8.6", + "jsrsasign": "11.0.0", "lint-staged": "11.1.0", "lodash": "4.17.21", "mitt": "3.0.1", @@ -136,10 +136,10 @@ "reselect": "4.0.0", "rn-extensions-share": "RocketChat/rn-extensions-share", "rn-root-view": "RocketChat/rn-root-view", - "semver": "7.3.8", + "semver": "7.5.2", "transliteration": "2.3.5", "typed-redux-saga": "1.5.0", - "ua-parser-js": "1.0.32", + "ua-parser-js": "1.0.33", "uri-js": "4.4.1", "url-parse": "1.5.10", "use-debounce": "9.0.4", @@ -190,7 +190,7 @@ "@types/url-parse": "^1.4.8", "@typescript-eslint/eslint-plugin": "^7.4.0", "@typescript-eslint/parser": "^7.4.0", - "axios": "0.27.2", + "axios": "0.28.0", "babel-jest": "^29.7.0", "babel-loader": "^9.1.3", "babel-plugin-transform-remove-console": "^6.9.4", diff --git a/yarn.lock b/yarn.lock index 71bc3e9138..4b5010a1f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5300,13 +5300,14 @@ axe-core@=4.7.0: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf" integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== -axios@0.27.2: - version "0.27.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== +axios@0.28.0: + version "0.28.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.28.0.tgz#801a4d991d0404961bccef46800e1170f8278c89" + integrity sha512-Tu7NYoGY4Yoc7I+Npf9HhUMtEEpV7ZiLH9yndTCoNhcpBH0kwcvFbzYN9/u5QKI5A6uefjsNNWaz5olJVYS62Q== dependencies: - follow-redirects "^1.14.9" + follow-redirects "^1.15.0" form-data "^4.0.0" + proxy-from-env "^1.1.0" axios@^1.6.2: version "1.6.8" @@ -5659,12 +5660,12 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" browser-process-hrtime@^1.0.0: version "1.0.0" @@ -7823,9 +7824,9 @@ fast-url-parser@^1.1.3: punycode "^1.3.2" fast-xml-parser@^4.0.12, fast-xml-parser@^4.2.4: - version "4.3.5" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.3.5.tgz#e2f2a2ae8377e9c3dc321b151e58f420ca7e5ccc" - integrity sha512-sWvP1Pl8H03B8oFJpFR3HE31HUfwtX7Rlf9BNsvdpujD4n7WMhfmu8h9wOV2u+c1k0ZilTADhPqypzx2J690ZQ== + version "4.4.1" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f" + integrity sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw== dependencies: strnum "^1.0.5" @@ -7908,10 +7909,10 @@ file-system-cache@2.3.0: fs-extra "11.1.1" ramda "0.29.0" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -8046,7 +8047,7 @@ flow-parser@^0.206.0: resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.206.0.tgz#f4f794f8026535278393308e01ea72f31000bfef" integrity sha512-HVzoK3r6Vsg+lKvlIZzaWNBVai+FXTX1wdYhz/wVlH13tb/gOdLXmlTqy6odmTBhT5UoWUbq0k8263Qhr9d88w== -follow-redirects@^1.14.9, follow-redirects@^1.15.6: +follow-redirects@^1.15.0, follow-redirects@^1.15.6: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== @@ -9935,10 +9936,10 @@ jsonify@^0.0.1: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== -jsrsasign@10.8.6: - version "10.8.6" - resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-10.8.6.tgz#ebf7f3c812c6517af84f0d8a10115e0dbfabe145" - integrity sha512-bQmbVtsfbgaKBTWCKiDCPlUPbdlRIK/FzSwT3BzIgZl/cU6TqXu6pZJsCI/dJVrZ9Gir5GC4woqw9shH/v7MBw== +jsrsasign@11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-11.0.0.tgz#766570c21f87d68075a142f5188f7e583cee9d70" + integrity sha512-BtRwVKS+5dsgPpAtzJcpo5OoWjSs1/zllSBG0+8o8/aV0Ki76m6iZwHnwnsqoTdhfFZDN1XIdcaZr5ZkP+H2gg== "jsx-ast-utils@^2.4.1 || ^3.0.0": version "3.3.0" @@ -10616,11 +10617,11 @@ mhchemparser@^4.1.0: integrity sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ== micromatch@^4.0.2, micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": @@ -12844,10 +12845,10 @@ semver@7.3.2: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== -semver@7.3.8: - version "7.3.8" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" - integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== +semver@7.5.2: + version "7.5.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.2.tgz#5b851e66d1be07c1cdaf37dfc856f543325a2beb" + integrity sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ== dependencies: lru-cache "^6.0.0" @@ -12863,34 +12864,13 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.0.0, semver@^7.5.2, semver@^7.5.4: +semver@^7.0.0, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4: version "7.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== dependencies: lru-cache "^6.0.0" -semver@^7.3.5: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== - dependencies: - lru-cache "^6.0.0" - -semver@^7.3.7: - version "7.3.7" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== - dependencies: - lru-cache "^6.0.0" - -semver@^7.5.3: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - send@0.18.0, send@^0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -13716,9 +13696,9 @@ tar-stream@^3.1.5: streamx "^2.15.0" tar@^6.0.2, tar@^6.0.5: - version "6.2.0" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73" - integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ== + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -14216,10 +14196,10 @@ ua-parser-js@1.0.2, ua-parser-js@^0.7.30, ua-parser-js@^1.0.35: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.2.tgz#e2976c34dbfb30b15d2c300b2a53eac87c57a775" integrity sha512-00y/AXhx0/SsnI51fTc0rLRmafiGOM4/O+ny10Ps7f+j/b8p/ZY11ytMgznXkOVo4GQ+KwQG5UQLkLGirsACRg== -ua-parser-js@1.0.32: - version "1.0.32" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.32.tgz#786bf17df97de159d5b1c9d5e8e9e89806f8a030" - integrity sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA== +ua-parser-js@1.0.33: + version "1.0.33" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.33.tgz#f21f01233e90e7ed0f059ceab46eb190ff17f8f4" + integrity sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ== uglify-js@^3.1.4: version "3.17.4" From 262a74ba6e0940d293d4113c2fe06dce84b6b7dd Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Mon, 2 Sep 2024 14:14:26 -0300 Subject: [PATCH 15/26] feat: Adjust markdown line height (#5845) --- .../__snapshots__/MessageComposer.test.tsx.snap | 10 ++++++++++ app/containers/markdown/Preview.tsx | 2 +- app/containers/markdown/styles.ts | 6 +++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap b/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap index 37b42b978f..e6e8b47ee2 100644 --- a/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap +++ b/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap @@ -626,10 +626,12 @@ exports[`MessageComposer Quote Add quote \`abc\` 1`] = ` "fontFamily": "Inter", "fontSize": 16, "fontWeight": "400", + "lineHeight": 22, "textAlign": "left", }, { "color": "#2F343D", + "lineHeight": undefined, }, { "backgroundColor": "transparent", @@ -1044,10 +1046,12 @@ exports[`MessageComposer Quote Add quote \`def\` 1`] = ` "fontFamily": "Inter", "fontSize": 16, "fontWeight": "400", + "lineHeight": 22, "textAlign": "left", }, { "color": "#2F343D", + "lineHeight": undefined, }, { "backgroundColor": "transparent", @@ -1219,10 +1223,12 @@ exports[`MessageComposer Quote Add quote \`def\` 1`] = ` "fontFamily": "Inter", "fontSize": 16, "fontWeight": "400", + "lineHeight": 22, "textAlign": "left", }, { "color": "#2F343D", + "lineHeight": undefined, }, { "backgroundColor": "transparent", @@ -1637,10 +1643,12 @@ exports[`MessageComposer Quote Remove a quote 1`] = ` "fontFamily": "Inter", "fontSize": 16, "fontWeight": "400", + "lineHeight": 22, "textAlign": "left", }, { "color": "#2F343D", + "lineHeight": undefined, }, { "backgroundColor": "transparent", @@ -1812,10 +1820,12 @@ exports[`MessageComposer Quote Remove a quote 1`] = ` "fontFamily": "Inter", "fontSize": 16, "fontWeight": "400", + "lineHeight": 22, "textAlign": "left", }, { "color": "#2F343D", + "lineHeight": undefined, }, { "backgroundColor": "transparent", diff --git a/app/containers/markdown/Preview.tsx b/app/containers/markdown/Preview.tsx index f52a0ebb44..bb6bc1b583 100644 --- a/app/containers/markdown/Preview.tsx +++ b/app/containers/markdown/Preview.tsx @@ -24,7 +24,7 @@ const MarkdownPreview = ({ msg, numberOfLines = 1, style = [], testID }: IMarkdo return ( <Text accessibilityLabel={m} - style={[styles.text, { color: themes[theme].fontDefault }, ...style]} + style={[styles.text, { color: themes[theme].fontDefault, lineHeight: undefined }, ...style]} numberOfLines={numberOfLines} testID={testID || `markdown-preview-${m}`}> {m} diff --git a/app/containers/markdown/styles.ts b/app/containers/markdown/styles.ts index a0fcba0a48..d558ef4146 100644 --- a/app/containers/markdown/styles.ts +++ b/app/containers/markdown/styles.ts @@ -32,9 +32,11 @@ export default StyleSheet.create({ }, plainText: { fontSize: 16, - flexShrink: 1 + flexShrink: 1, + lineHeight: 22 }, text: { + lineHeight: 22, fontSize: 16, ...sharedStyles.textRegular }, @@ -75,6 +77,7 @@ export default StyleSheet.create({ }, codeInline: { fontSize: 16, + lineHeight: 22, ...sharedStyles.textRegular, ...codeFontFamily, borderWidth: 1, @@ -89,6 +92,7 @@ export default StyleSheet.create({ }, codeBlockText: { fontSize: 16, + lineHeight: 22, ...sharedStyles.textRegular, ...codeFontFamily }, From 083e02218a524e00b5732ab26d5f6a5d686ba182 Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Fri, 6 Sep 2024 07:35:29 -0300 Subject: [PATCH 16/26] feat(a11y): Menus (use of color) (#5846) --- app/containers/ActionSheet/Item.tsx | 2 +- app/containers/ActionSheet/styles.ts | 2 +- app/containers/List/ListIcon.tsx | 14 ++- app/containers/List/ListItem.tsx | 4 +- app/containers/List/constants.ts | 2 +- app/i18n/locales/ar.json | 2 +- app/i18n/locales/bn-IN.json | 2 +- app/i18n/locales/cs.json | 2 +- app/i18n/locales/de.json | 2 +- app/i18n/locales/en.json | 2 +- app/i18n/locales/es.json | 2 +- app/i18n/locales/fi.json | 2 +- app/i18n/locales/fr.json | 2 +- app/i18n/locales/hi-IN.json | 2 +- app/i18n/locales/hu.json | 2 +- app/i18n/locales/it.json | 2 +- app/i18n/locales/ja.json | 2 +- app/i18n/locales/nl.json | 2 +- app/i18n/locales/pt-BR.json | 2 +- app/i18n/locales/ru.json | 2 +- app/i18n/locales/sl-SI.json | 2 +- app/i18n/locales/sv.json | 2 +- app/i18n/locales/ta-IN.json | 2 +- app/i18n/locales/te-IN.json | 2 +- app/i18n/locales/tr.json | 2 +- app/i18n/locales/zh-CN.json | 2 +- app/i18n/locales/zh-TW.json | 2 +- app/views/MediaAutoDownloadView/index.tsx | 2 + app/views/SettingsView/index.tsx | 70 ++++++++++---- app/views/SidebarView/SidebarItem.tsx | 38 -------- app/views/SidebarView/index.tsx | 100 +++++++++----------- app/views/SidebarView/styles.ts | 33 ------- e2e/helpers/app.ts | 3 + e2e/tests/assorted/01-e2eencryption.spec.ts | 5 + e2e/tests/assorted/06-status.spec.ts | 2 +- 35 files changed, 147 insertions(+), 174 deletions(-) delete mode 100644 app/views/SidebarView/SidebarItem.tsx diff --git a/app/containers/ActionSheet/Item.tsx b/app/containers/ActionSheet/Item.tsx index 389f204011..d2f44f6603 100644 --- a/app/containers/ActionSheet/Item.tsx +++ b/app/containers/ActionSheet/Item.tsx @@ -38,7 +38,7 @@ export const Item = React.memo(({ item, hide }: IActionSheetItem) => { return ( <View accessible accessibilityLabel={item.title}> <Touch onPress={onPress} style={[styles.item, { backgroundColor: colors.surfaceLight }]} testID={item.testID}> - {item.icon ? <CustomIcon name={item.icon} size={20} color={color} /> : null} + {item.icon ? <CustomIcon name={item.icon} size={24} color={color} /> : null} <View style={styles.titleContainer}> <Text numberOfLines={1} style={[styles.title, { color, marginLeft: item.icon ? 16 : 0 }]}> {item.title} diff --git a/app/containers/ActionSheet/styles.ts b/app/containers/ActionSheet/styles.ts index 00d34935a7..7bd1fb3d1c 100644 --- a/app/containers/ActionSheet/styles.ts +++ b/app/containers/ActionSheet/styles.ts @@ -24,7 +24,7 @@ export default StyleSheet.create({ }, title: { fontSize: 16, - ...sharedStyles.textRegular + ...sharedStyles.textMedium }, handle: { justifyContent: 'center', diff --git a/app/containers/List/ListIcon.tsx b/app/containers/List/ListIcon.tsx index 3e88f4be92..df22f38719 100644 --- a/app/containers/List/ListIcon.tsx +++ b/app/containers/List/ListIcon.tsx @@ -3,6 +3,7 @@ import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; import { CustomIcon, TIconsName } from '../CustomIcon'; import { ICON_SIZE } from './constants'; +import { useTheme } from '../../theme'; interface IListIcon { name: TIconsName; @@ -19,11 +20,14 @@ const styles = StyleSheet.create({ } }); -const ListIcon = ({ name, color, style, testID, size }: IListIcon): React.ReactElement => ( - <View style={[styles.icon, style]}> - <CustomIcon name={name} color={color} size={size ?? ICON_SIZE} testID={testID} /> - </View> -); +const ListIcon = ({ name, color, style, testID, size }: IListIcon): React.ReactElement => { + const { colors } = useTheme(); + return ( + <View style={[styles.icon, style]}> + <CustomIcon name={name} color={color || colors.fontDefault} size={size ?? ICON_SIZE} testID={testID} /> + </View> + ); +}; ListIcon.displayName = 'List.Icon'; diff --git a/app/containers/List/ListItem.tsx b/app/containers/List/ListItem.tsx index 8c8a4013ac..a39df4a0e5 100644 --- a/app/containers/List/ListItem.tsx +++ b/app/containers/List/ListItem.tsx @@ -42,7 +42,7 @@ const styles = StyleSheet.create({ title: { flexShrink: 1, fontSize: 16, - ...sharedStyles.textRegular + ...sharedStyles.textMedium }, subtitle: { fontSize: 14, @@ -124,7 +124,7 @@ const Content = React.memo( {left ? <View style={styles.leftContainer}>{left()}</View> : null} <View style={styles.textContainer}> <View style={styles.textAlertContainer}> - <Text style={[styles.title, styleTitle, { color: color || themes[theme].fontTitlesLabels }]} numberOfLines={1}> + <Text style={[styles.title, styleTitle, { color: color || themes[theme].fontDefault }]} numberOfLines={1}> {translateTitle && title ? I18n.t(title) : title} </Text> {alert ? ( diff --git a/app/containers/List/constants.ts b/app/containers/List/constants.ts index 8144096d39..71d877c633 100644 --- a/app/containers/List/constants.ts +++ b/app/containers/List/constants.ts @@ -1,3 +1,3 @@ export const PADDING_HORIZONTAL = 12; export const BASE_HEIGHT = 46; -export const ICON_SIZE = 20; +export const ICON_SIZE = 24; diff --git a/app/i18n/locales/ar.json b/app/i18n/locales/ar.json index e360ee137c..c3bac972bf 100644 --- a/app/i18n/locales/ar.json +++ b/app/i18n/locales/ar.json @@ -459,7 +459,7 @@ "Users": "مستخدمين", "Uses_server_configuration": "يستخدم إعداد الخادم", "Verify_email_desc": "لقد أرسلنا إليك بريداً إلكترونياً لتأكيد تسجيلك. إذا لم تتلق البريد الإلكتروني قريباً، فيرجى العودة والمحاولة مرة أخرى", - "Version_no": "النسخة: {{version}}", + "Version_no": "إصدار التطبيق: {{version}}", "View_Original": "عرض المحتوى الأصلي", "Wait_activation_warning": "يحب تفعيل حسابك من المشرف قبل تسجيل الدخول", "Waiting_for_network": "بانتظار توفر شبكة...", diff --git a/app/i18n/locales/bn-IN.json b/app/i18n/locales/bn-IN.json index 14ed0f168d..06719eb495 100644 --- a/app/i18n/locales/bn-IN.json +++ b/app/i18n/locales/bn-IN.json @@ -712,7 +712,7 @@ "Users": "ব্যবহারকারীগণ", "Uses_server_configuration": "ওয়ার্কস্পেস কনফিগারেশন ব্যবহার করে", "Verify_email_desc": "আমরা আপনার নিবন্ধন নিশ্চিত করতে একটি ইমেল পাঠিয়েছি। যদি আপনি শীঘ্রই একটি ইমেল পাননি, তবে দয়া করে ফিরে এসে আবার চেষ্টা করুন।", - "Version_no": "সংস্করণ: {{version}}", + "Version_no": "অ্যাপ সংস্করণ: {{version}}", "Video": "ভিডিও", "video-conf-provider-not-configured-body": "একটি কার্যক্ষম প্রশাসককে প্রথমে কনফারেন্স কল বৈশিষ্ট্যটি চালু করতে হবে।", "video-conf-provider-not-configured-header": "কনফারেন্স কল সক্ষম হয়নি", diff --git a/app/i18n/locales/cs.json b/app/i18n/locales/cs.json index 95405c892d..a5334a0ec1 100644 --- a/app/i18n/locales/cs.json +++ b/app/i18n/locales/cs.json @@ -783,7 +783,7 @@ "Uses_server_configuration": "Používá konfiguraci pracovního prostoru", "Verify": "Ověřit", "Verify_email_desc": "Poslali jsme vám e-mail pro potvrzení vaší registrace. Pokud e-mail brzy neobdržíte, vraťte se a zkuste to znovu.", - "Version_no": "Verze: {{version}}", + "Version_no": "Verze aplikace: {{version}}", "Vibrate": "Vibrovat", "Video": "Video", "video-conf-provider-not-configured-body": "Správce pracovního prostoru musí nejprve povolit funkci konferenčních hovorů.", diff --git a/app/i18n/locales/de.json b/app/i18n/locales/de.json index 5dfca2a5dc..bf58637a52 100644 --- a/app/i18n/locales/de.json +++ b/app/i18n/locales/de.json @@ -706,7 +706,7 @@ "Users": "Benutzer", "Uses_server_configuration": "Nutzt Servereinstellungen", "Verify_email_desc": "Wir haben Ihnen eine Email geschickt um Ihre Anmeldung zu bestätigen. Wenn Sie keine Email erhalten, versuchen Sie es später noch einmal.", - "Version_no": "Version: {{version}}", + "Version_no": "App-Version: {{version}}", "video-conf-provider-not-configured-body": "Ein Arbeitsbereich-Administrator muss die Funktion für Telefonkonferenzen zuerst aktivieren.", "video-conf-provider-not-configured-header": "Telefonkonferenz nicht aktiviert", "View_Original": "Original anzeigen", diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index ef334c408d..ab49887112 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -803,7 +803,7 @@ "Uses_server_configuration": "Uses workspace configuration", "Verify": "Verify", "Verify_email_desc": "We have sent you an email to confirm your registration. If you do not receive an email shortly, please come back and try again.", - "Version_no": "Version: {{version}}", + "Version_no": "App version: {{version}}", "Vibrate": "Vibrate", "Video": "Video", "video-conf-provider-not-configured-body": "A workspace admin needs to enable the conference calls feature first.", diff --git a/app/i18n/locales/es.json b/app/i18n/locales/es.json index 06993033f6..6273ba700c 100644 --- a/app/i18n/locales/es.json +++ b/app/i18n/locales/es.json @@ -283,7 +283,7 @@ "Username": "Nombre de usuario", "Username_or_email": "Nombre de usuario o email", "Users": "Usuarios", - "Version_no": "Versión: {{version}}", + "Version_no": "Versión de la aplicación: {{version}}", "View_Original": "Ver original", "Websocket_disabled": "Websocket está deshabilitado para este servidor.\n{{contact}}", "Whats_the_password_for_your_certificate": "¿Cuál es la contraseña de tu certificado?", diff --git a/app/i18n/locales/fi.json b/app/i18n/locales/fi.json index 768cf6e09f..97f5504a4c 100644 --- a/app/i18n/locales/fi.json +++ b/app/i18n/locales/fi.json @@ -680,7 +680,7 @@ "Users": "Käyttäjät", "Uses_server_configuration": "Käyttää palvelimen määrityksiä", "Verify_email_desc": "Lähetimme rekisteröitymisvahvistuksen sähköpostiisi. Jos et saa sähköpostia pian, yritä uudelleen.", - "Version_no": "Versio: {{version}}", + "Version_no": "Sovelluksen versio: {{version}}", "View_Original": "Näytä alkuperäinen", "Wait_activation_warning": "Ennen kuin voit kirjautua, järjestelmänvalvojan on aktivoitava tilisi manuaalisesti.", "Waiting_for_answer": "Odotetaan vastausta", diff --git a/app/i18n/locales/fr.json b/app/i18n/locales/fr.json index ef66bd48fd..0458384c79 100644 --- a/app/i18n/locales/fr.json +++ b/app/i18n/locales/fr.json @@ -600,7 +600,7 @@ "Users": "Utilisateurs", "Uses_server_configuration": "Utilise la configuration du serveur", "Verify_email_desc": "Nous vous avons envoyé un e-mail pour confirmer votre inscription. Si vous ne recevez pas d'e-mail sous peu, veuillez revenir et réessayer.", - "Version_no": "Version : {{version}}", + "Version_no": "Version de l'application : {{version}}", "View_Original": "Voir l'original", "Wait_activation_warning": "Avant de pouvoir vous connecter, votre compte doit être activé manuellement par un administrateur.", "Waiting_for_network": "En attente du réseau...", diff --git a/app/i18n/locales/hi-IN.json b/app/i18n/locales/hi-IN.json index 52a39d5f24..a3b9d1b525 100644 --- a/app/i18n/locales/hi-IN.json +++ b/app/i18n/locales/hi-IN.json @@ -712,7 +712,7 @@ "Users": "उपयोगकर्ताएँ", "Uses_server_configuration": "कार्यस्थान समरूपण का उपयोग करता है", "Verify_email_desc": "हमने आपको आपके पंजीकरण की पुष्टि के लिए एक ईमेल भेजा है। यदि आपको शीघ्र एक ईमेल प्राप्त नहीं होता है, तो कृपया वापस आकर पुनः प्रयास करें।", - "Version_no": "संस्करण: {{version}}", + "Version_no": "ऐप संस्करण: {{version}}", "Video": "वीडियो", "video-conf-provider-not-configured-body": "कार्यक्षेत्र प्रबंधक को पहले कॉन्फ़्रेंस कॉल सुविधा सक्षम करनी होगी।", "video-conf-provider-not-configured-header": "कॉन्फ़्रेंस कॉल सक्षम नहीं है", diff --git a/app/i18n/locales/hu.json b/app/i18n/locales/hu.json index ff4bdd2991..1964200302 100644 --- a/app/i18n/locales/hu.json +++ b/app/i18n/locales/hu.json @@ -714,7 +714,7 @@ "Users": "Felhasználók", "Uses_server_configuration": "Használja a munkaterület konfigurációját", "Verify_email_desc": "Küldtünk Önnek egy e-mailt a regisztrációja megerősítéséhez. Ha nem kap rövidesen e-mailt, akkor térjen vissza, és próbálja meg újra.", - "Version_no": "Verzió: {{version}}", + "Version_no": "Alkalmazás verzió: {{version}}", "Video": "Videó", "video-conf-provider-not-configured-body": "A munkaterület adminisztrátorának először engedélyeznie kell a konferenciahívások funkciót.", "video-conf-provider-not-configured-header": "Konferenciahívás nem engedélyezett", diff --git a/app/i18n/locales/it.json b/app/i18n/locales/it.json index dd13c20f87..d894666f8f 100644 --- a/app/i18n/locales/it.json +++ b/app/i18n/locales/it.json @@ -507,7 +507,7 @@ "Users": "Utenti", "Uses_server_configuration": "Usa la configurazione del server", "Verify_email_desc": "Ti abbiamo inviato una e-mail per confermare la tua registrazione. Se non la ricevi, ritorna qui e riprova", - "Version_no": "Versione: {{version}}", + "Version_no": "Versione dell'app: {{version}}", "View_Original": "Mostra originale", "Wait_activation_warning": "Prima di poter accedere, il tuo account deve essere attivato manualmente da un amministratore.", "Waiting_for_network": "In attesa di connessione ...", diff --git a/app/i18n/locales/ja.json b/app/i18n/locales/ja.json index 37593ad945..4641bf86aa 100644 --- a/app/i18n/locales/ja.json +++ b/app/i18n/locales/ja.json @@ -387,7 +387,7 @@ "Username_or_email": "ユーザー名かメールアドレス", "Users": "ユーザー", "Uses_server_configuration": "サーバー構成を使用する", - "Version_no": "バージョン: {{version}}", + "Version_no": "アプリバージョン: {{version}}", "View_Original": "オリジナルを見る", "Websocket_disabled": "Websocketはこのサーバーでは無効化されています。\n{{contact}}", "Whats_the_password_for_your_certificate": "証明書のパスワードはなんですか?", diff --git a/app/i18n/locales/nl.json b/app/i18n/locales/nl.json index 5503286e08..8a003fd2b7 100644 --- a/app/i18n/locales/nl.json +++ b/app/i18n/locales/nl.json @@ -600,7 +600,7 @@ "Users": "Gebruikers", "Uses_server_configuration": "Gebruikt serverconfiguratie", "Verify_email_desc": "We hebben je een e-mail gestuurd om je inschrijving te bevestigen. Als je binnenkort geen e-mail ontvangt, gelieve terug te komen en het opnieuw te proberen.", - "Version_no": "Versie: {{version}}", + "Version_no": "App-versie: {{version}}", "View_Original": "Bekijk origineel", "Wait_activation_warning": "Voordat u kunt inloggen, moet uw account handmatig worden geactiveerd door een beheerder.", "Waiting_for_network": "Wachten op netwerk...", diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json index 568349fc1e..54fb56cf32 100644 --- a/app/i18n/locales/pt-BR.json +++ b/app/i18n/locales/pt-BR.json @@ -792,7 +792,7 @@ "Uses_server_configuration": "Usar configuração da workspace", "Verify": "Verificar", "Verify_email_desc": "Nós lhe enviamos um e-mail para confirmar o seu registro. Se você não receber um e-mail em breve, por favor retorne e tente novamente.", - "Version_no": "Versão: {{version}}", + "Version_no": "Versão do aplicativo: {{version}}", "Vibrate": "Vibrar", "Video": "Vídeo", "video-conf-provider-not-configured-body": "Um administrador do workspace precisa ativar o recurso de chamadas de conferência primeiro.", diff --git a/app/i18n/locales/ru.json b/app/i18n/locales/ru.json index e2cc7379c9..96a69220b3 100644 --- a/app/i18n/locales/ru.json +++ b/app/i18n/locales/ru.json @@ -648,7 +648,7 @@ "Users": "Пользователи", "Uses_server_configuration": "Используется конфигурация сервера", "Verify_email_desc": "Вам был отправлен email для подтверждения регистрации. Если вы не получили этого сообщения, пожалуйста, попробуйте еще раз.", - "Version_no": "Версия: {{version}}", + "Version_no": "Версия приложения: {{version}}", "View_Original": "Посмотреть оригинал", "Wait_activation_warning": "До того как вы сможете войти, ваш аккаунт должен быть вручную активирован администратором сервера.", "Waiting_for_answer": "Ожидание ответа", diff --git a/app/i18n/locales/sl-SI.json b/app/i18n/locales/sl-SI.json index d07e948eb6..9d3fed05d7 100644 --- a/app/i18n/locales/sl-SI.json +++ b/app/i18n/locales/sl-SI.json @@ -615,7 +615,7 @@ "Users": "Uporabniki", "Uses_server_configuration": "Uporablja konfiguracijo strežnika", "Verify_email_desc": "Poslali smo vam e -poštno sporočilo za potrditev vaše registracije. Če v kratkem ne prejmete e -pošte, se vrnite in poskusite znova.", - "Version_no": "Različica: {{version}}", + "Version_no": "Različica aplikacije: {{version}}", "View_Original": "Pogled original", "Wait_activation_warning": "Preden se lahko prijavite, mora skrbnik ročno aktivirati vaš račun.", "Waiting_for_network": "Čakanje na omrežje ...", diff --git a/app/i18n/locales/sv.json b/app/i18n/locales/sv.json index d779af5573..376b7e61c0 100644 --- a/app/i18n/locales/sv.json +++ b/app/i18n/locales/sv.json @@ -678,7 +678,7 @@ "Users": "Användare", "Uses_server_configuration": "Använder serverkonfiguration", "Verify_email_desc": "Vi har skickat ett e-postmeddelande för att bekräfta din registrering. Om du inte får e-postmeddelandet försöker du igen.", - "Version_no": "Version: {{version}}", + "Version_no": "Appversion: {{version}}", "View_Original": "Visa original", "Wait_activation_warning": "Innan du kan logga in måste ditt konto aktiveras manuellt av en administratör.", "Waiting_for_answer": "Väntar på svar", diff --git a/app/i18n/locales/ta-IN.json b/app/i18n/locales/ta-IN.json index d574c8e2c4..8923e20e4a 100644 --- a/app/i18n/locales/ta-IN.json +++ b/app/i18n/locales/ta-IN.json @@ -712,7 +712,7 @@ "Users": "பயனர்கள்", "Uses_server_configuration": "பணியில் உள்ளேயே உபயோகிக்குகின்றது", "Verify_email_desc": "உங்களுக்கு உங்கள் பதிவுக்கு உறுதிப்படுத்த ஒரு மின்னஞ்சல் அனுப்பினோம். உங்களுக்கு விரைவில் ஒரு மின்னஞ்சல் பெறாதிருக்கின்றார்கள், தயவுசெய்து மீண்டும் வாருங்கள் மற்றும் முயற்சிக்கவும்.", - "Version_no": "பதிப்பு: {{version}}", + "Version_no": "நிரல் பதிப்பு: {{version}}", "Video": "காணொளி", "video-conf-provider-not-configured-body": "காரிகள் ஆட்சேர்க்கலாம் என்பதற்கு முகப்பு நிர்வாகி முதலாளியின் அனுமதி தேவை.", "video-conf-provider-not-configured-header": "காந்ஃபரன்ஸ் கால் கொள்ளை இயக்கப்படவில்லை", diff --git a/app/i18n/locales/te-IN.json b/app/i18n/locales/te-IN.json index 1d8a195319..1af32b3aa1 100644 --- a/app/i18n/locales/te-IN.json +++ b/app/i18n/locales/te-IN.json @@ -712,7 +712,7 @@ "Users": "వాడుకరులు", "Uses_server_configuration": "పనితనం ఆకృతి ఉపయోగిస్తుంది", "Verify_email_desc": "మేము మీ నమోదుని ధ్యానంలోకి పెంపొందాం. మీరు తక్షణం ఒక ఇమెయిల్ పొందరాక, దయచేసి మళ్ళీ ప్రయత్నించండి.", - "Version_no": "పరిస్థితి: {{version}}", + "Version_no": "అనువర్తనం సంచిక: {{version}}", "Video": "వీడియో", "video-conf-provider-not-configured-body": "వార్క్‌స్పేస్ యాడ్మిన్ మొదలు కాన్ఫరెన్స్ కాల్స్ విశేషాలు ఏర్పాట్లో ఉన్నాయి.", "video-conf-provider-not-configured-header": "కాన్ఫరెన్స్ కాల్ అనేకంగా లేదు", diff --git a/app/i18n/locales/tr.json b/app/i18n/locales/tr.json index 79d28e1941..e28f4727b4 100644 --- a/app/i18n/locales/tr.json +++ b/app/i18n/locales/tr.json @@ -490,7 +490,7 @@ "Users": "Kullanıcılar", "Uses_server_configuration": "Sunucu yapılandırmasını kullanır", "Verify_email_desc": "Kaydınızı onaylamak için size bir e-posta gönderdik. Kısa süre içinde bir e-posta almazsanız, lütfen geri gelin ve tekrar deneyin.", - "Version_no": "Versiyon: {{version}}", + "Version_no": "Uygulama sürümü: {{version}}", "View_Original": "Orijinali Görüntüle", "Wait_activation_warning": "Giriş yapmadan önce, hesabınız bir yönetici tarafından manuel olarak etkinleştirilmelidir.", "Waiting_for_network": "Ağ bağlantısı bekleniyor ...", diff --git a/app/i18n/locales/zh-CN.json b/app/i18n/locales/zh-CN.json index def1f53deb..8f434a0cfd 100644 --- a/app/i18n/locales/zh-CN.json +++ b/app/i18n/locales/zh-CN.json @@ -450,7 +450,7 @@ "Users": "用戶", "Uses_server_configuration": "使用服务器设置", "Verify_email_desc": "我们已经送出一封电子邮件,以确认您的注册。如果您没有很快收到,请再试一次。", - "Version_no": "版本: {{version}}", + "Version_no": "应用版本: {{version}}", "View_Original": "检视原文", "Wait_activation_warning": "您的帐号必须由管理员手动启用后才能登入。", "Waiting_for_network": "等待网路连接", diff --git a/app/i18n/locales/zh-TW.json b/app/i18n/locales/zh-TW.json index 773f8bc3d4..f9a9114e10 100644 --- a/app/i18n/locales/zh-TW.json +++ b/app/i18n/locales/zh-TW.json @@ -479,7 +479,7 @@ "Users": "使用者", "Uses_server_configuration": "使用伺服器設定", "Verify_email_desc": "我們已經送出一封電子郵件,以確認您的註冊。如果您沒有很快收到,請再試一次。", - "Version_no": "版本: {{version}}", + "Version_no": "應用程式版本: {{version}}", "View_Original": "檢視原文", "Wait_activation_warning": "您的帳號必須由管理員手動啟用後才能登入。", "Waiting_for_network": "等待網路連線", diff --git a/app/views/MediaAutoDownloadView/index.tsx b/app/views/MediaAutoDownloadView/index.tsx index 5dae262e26..e9b3910386 100644 --- a/app/views/MediaAutoDownloadView/index.tsx +++ b/app/views/MediaAutoDownloadView/index.tsx @@ -36,11 +36,13 @@ const MediaAutoDownload = () => { <StatusBar /> <List.Container> <List.Section> + <List.Separator /> <ListPicker onChangeValue={setImagesPreference} value={imagesPreference} title='Image' /> <List.Separator /> <ListPicker onChangeValue={setVideoPreference} value={videoPreference} title='Video' /> <List.Separator /> <ListPicker onChangeValue={setAudioPreference} value={audioPreference} title='Audio' /> + <List.Separator /> </List.Section> </List.Container> </SafeAreaView> diff --git a/app/views/SettingsView/index.tsx b/app/views/SettingsView/index.tsx index 708db42c07..c8dc406e3e 100644 --- a/app/views/SettingsView/index.tsx +++ b/app/views/SettingsView/index.tsx @@ -178,13 +178,19 @@ const SettingsView = (): React.ReactElement => { </List.Section> <List.Section> <List.Separator /> - <List.Item title='Display' onPress={() => navigateToScreen('DisplayPrefsView')} showActionIndicator /> + <List.Item + title='Display' + onPress={() => navigateToScreen('DisplayPrefsView')} + showActionIndicator + left={() => <List.Icon name='sort' />} + /> <List.Separator /> <List.Item title='Profile' onPress={() => navigateToScreen('ProfileView')} showActionIndicator testID='settings-profile' + left={() => <List.Icon name='user' />} /> <List.Separator /> </List.Section> @@ -192,29 +198,21 @@ const SettingsView = (): React.ReactElement => { ) : null} <List.Section> - <List.Separator /> - <List.Item title='Contact_us' onPress={sendEmail} showActionIndicator testID='settings-view-contact' /> <List.Separator /> <List.Item title='Language' onPress={() => navigateToScreen('LanguageView')} showActionIndicator testID='settings-view-language' + left={() => <List.Icon name='language' />} /> <List.Separator /> - {!isFDroidBuild ? ( - <> - <List.Item title='Review_this_app' showActionIndicator onPress={onReviewPress} testID='settings-view-review-app' /> - </> - ) : null} - <List.Separator /> - <List.Item title='Share_this_app' showActionIndicator onPress={shareApp} testID='settings-view-share-app' /> - <List.Separator /> <List.Item title='Default_browser' showActionIndicator onPress={() => navigateToScreen('DefaultBrowserView')} testID='settings-view-default-browser' + left={() => <List.Icon name='federation' />} /> <List.Separator /> <List.Item @@ -222,6 +220,7 @@ const SettingsView = (): React.ReactElement => { showActionIndicator onPress={() => navigateToScreen('ThemeView')} testID='settings-view-theme' + left={() => <List.Icon name='moon' />} /> <List.Separator /> <List.Item @@ -229,6 +228,7 @@ const SettingsView = (): React.ReactElement => { showActionIndicator onPress={() => navigateToScreen('MediaAutoDownloadView')} testID='settings-view-media-auto-download' + left={() => <List.Icon name='download' />} /> <List.Separator /> <List.Item @@ -236,19 +236,54 @@ const SettingsView = (): React.ReactElement => { showActionIndicator onPress={() => navigateToScreen('SecurityPrivacyView')} testID='settings-view-security-privacy' + left={() => <List.Icon name='locker' />} /> - <List.Separator /> </List.Section> <List.Section> <List.Separator /> - <List.Item title='License' onPress={onPressLicense} showActionIndicator testID='settings-view-license' /> + <List.Item + title='Share_this_app' + showActionIndicator + onPress={shareApp} + testID='settings-view-share-app' + left={() => <List.Icon name='arrow-forward' />} + /> + <List.Separator /> + <List.Item + title='Contact_us' + onPress={sendEmail} + testID='settings-view-contact' + left={() => <List.Icon name='mail' />} + right={() => <List.Icon name='new-window' />} + /> + <List.Separator /> + {!isFDroidBuild ? ( + <> + <List.Item + title='Review_this_app' + onPress={onReviewPress} + testID='settings-view-review-app' + left={() => <List.Icon name='star' />} + right={() => <List.Icon name='new-window' />} + /> + </> + ) : null} + <List.Separator /> + <List.Item + title='License' + onPress={onPressLicense} + testID='settings-view-license' + left={() => <List.Icon name='file-document' />} + right={() => <List.Icon name='new-window' />} + /> <List.Separator /> <List.Item title={I18n.t('Version_no', { version: getReadableVersion })} onPress={copyAppVersion} testID='settings-view-version' translateTitle={false} + left={() => <List.Icon name='mobile' />} /> <List.Separator /> <List.Item @@ -258,6 +293,7 @@ const SettingsView = (): React.ReactElement => { testID='settings-view-server-version' translateTitle={false} translateSubtitle={false} + left={() => <List.Icon name='desktop' />} /> <List.Separator /> </List.Section> @@ -268,16 +304,16 @@ const SettingsView = (): React.ReactElement => { title='Clear_cache' testID='settings-view-clear-cache' onPress={handleClearCache} - showActionIndicator - color={colors.buttonBackgroundDangerDefault} + color={colors.fontDanger} + left={() => <List.Icon name='prune' color={colors.fontDanger} />} /> <List.Separator /> <List.Item title='Logout' testID='settings-logout' onPress={handleLogout} - showActionIndicator - color={colors.buttonBackgroundDangerDefault} + color={colors.fontDanger} + left={() => <List.Icon name='logout' color={colors.fontDanger} />} /> <List.Separator /> </List.Section> diff --git a/app/views/SidebarView/SidebarItem.tsx b/app/views/SidebarView/SidebarItem.tsx deleted file mode 100644 index e0b70ff131..0000000000 --- a/app/views/SidebarView/SidebarItem.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import { Text, View } from 'react-native'; - -import Touch from '../../containers/Touch'; -import { themes } from '../../lib/constants'; -import { TSupportedThemes, withTheme } from '../../theme'; -import styles from './styles'; - -interface SidebarItemProps { - left: JSX.Element; - right?: JSX.Element; - text: string; - textColor?: string; - current?: boolean; - onPress(): void; - testID: string; - theme: TSupportedThemes; -} - -const Item = React.memo(({ left, right, text, onPress, testID, current, theme, textColor }: SidebarItemProps) => ( - <Touch - key={testID} - testID={testID} - onPress={onPress} - style={[styles.item, current && { backgroundColor: themes[theme].strokeLight }]} - accessible - accessibilityLabel={text}> - <View style={styles.itemHorizontal}>{left}</View> - <View style={styles.itemCenter}> - <Text testID={`sidebar-custom-status-text-${text}`} style={[styles.itemText, { color: textColor || themes[theme].fontTitlesLabels }]} numberOfLines={1}> - {text} - </Text> - </View> - <View style={styles.itemHorizontal}>{right}</View> - </Touch> -)); - -export default withTheme(Item); diff --git a/app/views/SidebarView/index.tsx b/app/views/SidebarView/index.tsx index 80feac5138..113bf0b64f 100644 --- a/app/views/SidebarView/index.tsx +++ b/app/views/SidebarView/index.tsx @@ -18,7 +18,6 @@ import { TSupportedThemes, withTheme } from '../../theme'; import { getUserSelector } from '../../selectors/login'; import SafeAreaView from '../../containers/SafeAreaView'; import Navigation from '../../lib/navigation/appNavigation'; -import SidebarItem from './SidebarItem'; import styles from './styles'; import { DrawerParamList } from '../../stacks/types'; import { IApplicationState, IUser, TSVStatus } from '../../definitions'; @@ -223,13 +222,11 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> { return ( <> <List.Separator /> - <SidebarItem - text={I18n.t('Admin_Panel')} - left={<CustomIcon name='settings' size={20} color={themes[theme!].fontTitlesLabels} />} + <List.Item + title={'Admin_Panel'} + left={() => <List.Icon name='settings' />} onPress={() => this.sidebarNavigate(routeName)} - testID='sidebar-admin' - theme={theme!} - current={this.currentItemKey === routeName} + backgroundColor={this.currentItemKey === routeName ? themes[theme!].strokeLight : undefined} /> </> ); @@ -239,37 +236,36 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> { const { theme } = this.props; return ( <> - <SidebarItem - text={I18n.t('Chats')} - left={<CustomIcon name='message' size={20} color={themes[theme!].fontTitlesLabels} />} + <List.Item + title={'Chats'} + left={() => <List.Icon name='message' />} onPress={() => this.sidebarNavigate('ChatsStackNavigator')} + backgroundColor={this.currentItemKey === 'ChatsStackNavigator' ? themes[theme!].strokeLight : undefined} testID='sidebar-chats' - theme={theme!} - current={this.currentItemKey === 'ChatsStackNavigator'} /> - <SidebarItem - text={I18n.t('Profile')} - left={<CustomIcon name='user' size={20} color={themes[theme!].fontTitlesLabels} />} + <List.Separator /> + <List.Item + title={'Profile'} + left={() => <List.Icon name='user' />} onPress={() => this.sidebarNavigate('ProfileStackNavigator')} + backgroundColor={this.currentItemKey === 'ProfileStackNavigator' ? themes[theme!].strokeLight : undefined} testID='sidebar-profile' - theme={theme!} - current={this.currentItemKey === 'ProfileStackNavigator'} /> - <SidebarItem - text={I18n.t('Display')} - left={<CustomIcon name='sort' size={20} color={themes[theme!].fontTitlesLabels} />} + <List.Separator /> + <List.Item + title={'Display'} + left={() => <List.Icon name='sort' />} onPress={() => this.sidebarNavigate('DisplayPrefStackNavigator')} + backgroundColor={this.currentItemKey === 'DisplayPrefStackNavigator' ? themes[theme!].strokeLight : undefined} testID='sidebar-display' - theme={theme!} - current={this.currentItemKey === 'DisplayPrefStackNavigator'} /> - <SidebarItem - text={I18n.t('Settings')} - left={<CustomIcon name='administration' size={20} color={themes[theme!].fontTitlesLabels} />} + <List.Separator /> + <List.Item + title={'Settings'} + left={() => <List.Icon name='administration' />} onPress={() => this.sidebarNavigate('SettingsStackNavigator')} + backgroundColor={this.currentItemKey === 'SettingsStackNavigator' ? themes[theme!].strokeLight : undefined} testID='sidebar-settings' - theme={theme!} - current={this.currentItemKey === 'SettingsStackNavigator'} /> {this.renderAdmin()} </> @@ -284,20 +280,22 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> { status = 'disabled'; } - let right: React.ReactElement | undefined = <CustomIcon name='edit' size={20} color={themes[theme!].fontTitlesLabels} />; + let right: (() => JSX.Element | null) | undefined = () => ( + <CustomIcon name='edit' size={20} color={themes[theme!].fontTitlesLabels} /> + ); if (notificationPresenceCap) { - right = <View style={[styles.customStatusDisabled, { backgroundColor: themes[theme!].userPresenceDisabled }]} />; + right = () => <View style={[styles.customStatusDisabled, { backgroundColor: themes[theme!].userPresenceDisabled }]} />; } else if (Presence_broadcast_disabled) { right = undefined; } return ( - <SidebarItem - text={user.statusText || I18n.t('Edit_Status')} - left={<Status size={24} status={status} />} - theme={theme!} + <List.Item + title={user.statusText || 'Edit_Status'} + left={() => <Status size={24} status={status} />} right={right} onPress={() => (Presence_broadcast_disabled ? this.onPressPresenceLearnMore() : this.sidebarNavigate('StatusView'))} + translateTitle={!user.statusText} testID={`sidebar-custom-status-${user.status}`} /> ); @@ -307,14 +305,16 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> { const { theme, supportedVersionsStatus } = this.props; if (supportedVersionsStatus === 'warn') { return ( - <SidebarItem - text={I18n.t('Supported_versions_warning_update_required')} - textColor={themes[theme!].fontDanger} - left={<CustomIcon name='warning' size={20} color={themes[theme!].buttonBackgroundDangerDefault} />} - theme={theme!} - onPress={() => this.onPressSupportedVersionsWarning()} - testID={`sidebar-supported-versions-warn`} - /> + <> + <List.Separator /> + <List.Item + title={'Supported_versions_warning_update_required'} + color={themes[theme!].fontDanger} + left={() => <CustomIcon name='warning' size={20} color={themes[theme!].buttonBackgroundDangerDefault} />} + onPress={() => this.onPressSupportedVersionsWarning()} + testID={`sidebar-supported-versions-warn`} + /> + </> ); } return null; @@ -327,17 +327,11 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> { return null; } return ( - <SafeAreaView testID='sidebar-view' style={{ backgroundColor: themes[theme!].surfaceLight }} vertical={isMasterDetail}> - <ScrollView - style={[ - styles.container, - { - backgroundColor: isMasterDetail ? themes[theme!].surfaceRoom : themes[theme!].surfaceLight - } - ]} - {...scrollPersistTaps}> + <SafeAreaView testID='sidebar-view' vertical={isMasterDetail}> + <ScrollView style={styles.container} {...scrollPersistTaps}> + <List.Separator /> <TouchableWithoutFeedback onPress={this.onPressUser} testID='sidebar-close-drawer'> - <View style={styles.header}> + <View style={[styles.header, { backgroundColor: themes[theme!].surfaceRoom }]}> <Avatar text={user.username} style={styles.avatar} size={30} /> <View style={styles.headerTextContainer}> <View style={styles.headerUsername}> @@ -355,7 +349,6 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> { </View> </TouchableWithoutFeedback> - <List.Separator /> {this.renderSupportedVersionsWarn()} <List.Separator /> @@ -365,11 +358,12 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> { <> <List.Separator /> {this.renderNavigation()} - <List.Separator /> </> ) : ( <>{this.renderAdmin()}</> )} + + <List.Separator /> </ScrollView> </SafeAreaView> ); diff --git a/app/views/SidebarView/styles.ts b/app/views/SidebarView/styles.ts index 63d4c290a7..e06eb5f285 100644 --- a/app/views/SidebarView/styles.ts +++ b/app/views/SidebarView/styles.ts @@ -6,30 +6,6 @@ export default StyleSheet.create({ container: { flex: 1 }, - item: { - flexDirection: 'row', - alignItems: 'center' - }, - itemCurrent: { - backgroundColor: '#E1E5E8' - }, - itemHorizontal: { - marginHorizontal: 10, - width: 30, - alignItems: 'center' - }, - itemCenter: { - flex: 1 - }, - itemText: { - marginVertical: 16, - fontSize: 14, - ...sharedStyles.textSemibold - }, - separator: { - borderBottomWidth: StyleSheet.hairlineWidth, - marginVertical: 4 - }, header: { paddingVertical: 16, flexDirection: 'row', @@ -55,15 +31,6 @@ export default StyleSheet.create({ fontSize: 14, ...sharedStyles.textSemibold }, - version: { - marginHorizontal: 10, - marginBottom: 10, - fontSize: 13, - ...sharedStyles.textSemibold - }, - inverted: { - transform: [{ scaleY: -1 }] - }, customStatusDisabled: { width: 10, height: 10, diff --git a/e2e/helpers/app.ts b/e2e/helpers/app.ts index abd2f39089..6ff9178aea 100644 --- a/e2e/helpers/app.ts +++ b/e2e/helpers/app.ts @@ -215,6 +215,9 @@ async function checkRoomTitle(room: string) { const checkServer = async (server: string) => { const label = `Connected to ${server}`; + await waitFor(element(by.id('rooms-list-view-sidebar'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('rooms-list-view-sidebar')).tap(); await waitFor(element(by.id('sidebar-view'))) .toBeVisible() diff --git a/e2e/tests/assorted/01-e2eencryption.spec.ts b/e2e/tests/assorted/01-e2eencryption.spec.ts index 9e84e9e876..b4177d844b 100644 --- a/e2e/tests/assorted/01-e2eencryption.spec.ts +++ b/e2e/tests/assorted/01-e2eencryption.spec.ts @@ -396,6 +396,8 @@ describe('E2E Encryption', () => { await element(by.id('register-view-username')).tapReturnKey(); await element(by.id('register-view-email')).replaceText(randomUser.email); await element(by.id('register-view-email')).tapReturnKey(); + await element(by.id('register-view-password')).replaceText(randomUser.password); + await element(by.id('register-view-password')).tapReturnKey(); await expectValidRegisterOrRetry(device.getPlatform()); deleteUsersAfterAll.push({ server: data.alternateServer, username: randomUser.username }); @@ -403,6 +405,9 @@ describe('E2E Encryption', () => { }); it('should change back', async () => { + await waitFor(element(by.id('rooms-list-header-servers-list-button'))) + .toExist() + .withTimeout(2000); await element(by.id('rooms-list-header-servers-list-button')).tap(); await waitFor(element(by.id('rooms-list-header-servers-list'))) .toBeVisible() diff --git a/e2e/tests/assorted/06-status.spec.ts b/e2e/tests/assorted/06-status.spec.ts index b77a70895f..464c92e1ea 100644 --- a/e2e/tests/assorted/06-status.spec.ts +++ b/e2e/tests/assorted/06-status.spec.ts @@ -57,7 +57,7 @@ describe('Status screen', () => { await element(by.id('status-view-input')).replaceText('status-text-new'); await element(by.id('status-view-submit')).tap(); await sleep(3000); // Wait until the loading hide - await waitFor(element(by.id(`sidebar-custom-status-text-status-text-new`))) + await waitFor(element(by.text('status-text-new'))) .toExist() .withTimeout(5000); }); From 61dff3586aaaa8bf3204c4247b28ea9a5a4ea811 Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Fri, 6 Sep 2024 14:07:54 -0300 Subject: [PATCH 17/26] regression: Fix E2E_Enable_Encrypt_Files backwards compatibility (#5858) --- app/lib/encryption/encryption.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/encryption/encryption.ts b/app/lib/encryption/encryption.ts index de401c64d0..4b7dee5c61 100644 --- a/app/lib/encryption/encryption.ts +++ b/app/lib/encryption/encryption.ts @@ -540,7 +540,7 @@ class Encryption { } const { E2E_Enable_Encrypt_Files } = store.getState().settings; - if (!subscription.encrypted || !E2E_Enable_Encrypt_Files) { + if (!subscription.encrypted || (E2E_Enable_Encrypt_Files !== undefined && !E2E_Enable_Encrypt_Files)) { // Send a non encrypted message return { file }; } From c3795ed6090f8b5f586cf9517a17fac89f5947d6 Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Mon, 9 Sep 2024 15:40:11 -0300 Subject: [PATCH 18/26] chore: Bump version to 4.52.0 (#5862) --- android/app/build.gradle | 2 +- ios/RocketChatRN.xcodeproj/project.pbxproj | 4 ++-- ios/RocketChatRN/Info.plist | 2 +- ios/ShareRocketChatRN/Info.plist | 2 +- package.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index f2976d8064..b634e7607f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -93,7 +93,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode VERSIONCODE as Integer - versionName "4.51.0" + versionName "4.52.0" vectorDrawables.useSupportLibrary = true if (!isFoss) { manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String] diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index 3c4f0becb2..efe4a8f292 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -3135,7 +3135,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 4.51.0; + MARKETING_VERSION = 4.52.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -3179,7 +3179,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 4.51.0; + MARKETING_VERSION = 4.52.0; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService; diff --git a/ios/RocketChatRN/Info.plist b/ios/RocketChatRN/Info.plist index b7325a93c5..64e5d24cd4 100644 --- a/ios/RocketChatRN/Info.plist +++ b/ios/RocketChatRN/Info.plist @@ -28,7 +28,7 @@ <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> - <string>4.51.0</string> + <string>4.52.0</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleURLTypes</key> diff --git a/ios/ShareRocketChatRN/Info.plist b/ios/ShareRocketChatRN/Info.plist index 66c3277239..6c60a7052e 100644 --- a/ios/ShareRocketChatRN/Info.plist +++ b/ios/ShareRocketChatRN/Info.plist @@ -26,7 +26,7 @@ <key>CFBundlePackageType</key> <string>XPC!</string> <key>CFBundleShortVersionString</key> - <string>4.51.0</string> + <string>4.52.0</string> <key>CFBundleVersion</key> <string>1</string> <key>KeychainGroup</key> diff --git a/package.json b/package.json index c2e34a64ee..6104b0b6ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rocket-chat-reactnative", - "version": "4.51.0", + "version": "4.52.0", "private": true, "scripts": { "start": "react-native start", From cda330081a3172a7d5dd3d9842bd0720d11b0ff4 Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Fri, 13 Sep 2024 17:40:34 -0300 Subject: [PATCH 19/26] fix: share extension ios memory (#5831) --- .../reactnative/share/ShareActivity.java | 10 - .../rocket/reactnative/share/ShareActivity.kt | 137 +++++ .../reactnative/share/ShareApplication.java | 38 -- app.json | 4 +- app/AppContainer.tsx | 8 +- app/actions/actionsTypes.ts | 2 +- app/actions/share.ts | 36 +- app/containers/Avatar/AvatarContainer.tsx | 11 +- app/containers/EmojiPicker/CustomEmoji.tsx | 2 +- app/containers/ServerItem/index.tsx | 5 +- app/definitions/navigationTypes.ts | 11 +- app/definitions/redux/TRootEnum.ts | 4 +- app/index.tsx | 21 +- app/lib/database/index.ts | 40 +- .../helpers/askAndroidMediaPermissions.ts | 54 -- app/lib/methods/helpers/helpers.ts | 7 +- app/lib/methods/helpers/index.ts | 1 - app/lib/methods/helpers/parseQuery.ts | 4 + app/lib/methods/shareExtension.ts | 95 ---- app/lib/methods/useServer.ts | 3 +- app/lib/services/sdk.ts | 12 +- app/reducers/share.test.ts | 42 +- app/reducers/share.ts | 36 +- app/sagas/deepLinking.js | 26 + app/sagas/encryption.js | 2 +- app/sagas/init.js | 9 +- app/sagas/login.js | 5 +- app/sagas/selectServer.ts | 5 +- app/selectors/login.ts | 8 +- app/share.tsx | 144 ------ app/stacks/ShareExtensionStack.tsx | 26 + app/views/SelectServerView.tsx | 14 +- app/views/ShareListView/Header/Header.ios.tsx | 62 --- app/views/ShareListView/Header/Header.tsx | 45 -- app/views/ShareListView/Header/SearchBox.tsx | 87 ---- app/views/ShareListView/Header/index.tsx | 22 - app/views/ShareListView/Header/interface.ts | 15 - app/views/ShareListView/index.tsx | 217 ++++---- app/views/ShareView/Preview.tsx | 49 +- app/views/ShareView/Thumbs.tsx | 43 +- app/views/ShareView/index.tsx | 21 +- app/views/ShareView/utils.ts | 5 - app/views/WithoutServersView.tsx | 50 -- e2e/helpers/app.ts | 13 +- e2e/tests/assorted/01-e2eencryption.spec.ts | 2 +- e2e/tests/assorted/07-changeserver.spec.ts | 7 +- e2e/tests/assorted/10-deleteserver.spec.ts | 4 +- e2e/tests/assorted/11-deeplinking.spec.ts | 141 ++++- index.js | 1 - ios/Podfile | 1 - ios/Podfile.lock | 8 +- ios/RocketChatRN.xcodeproj/project.pbxproj | 486 ++++++------------ .../Base.lproj/MainInterface.storyboard | 15 +- ios/ShareRocketChatRN/ShareRocketChatRN.m | 61 --- ios/ShareRocketChatRN/ShareRocketChatRN.swift | 218 ++++++++ package.json | 1 - yarn.lock | 4 - 57 files changed, 939 insertions(+), 1461 deletions(-) delete mode 100644 android/app/src/main/java/chat/rocket/reactnative/share/ShareActivity.java create mode 100644 android/app/src/main/java/chat/rocket/reactnative/share/ShareActivity.kt delete mode 100644 android/app/src/main/java/chat/rocket/reactnative/share/ShareApplication.java delete mode 100644 app/lib/methods/helpers/askAndroidMediaPermissions.ts delete mode 100644 app/lib/methods/shareExtension.ts delete mode 100644 app/share.tsx create mode 100644 app/stacks/ShareExtensionStack.tsx delete mode 100644 app/views/ShareListView/Header/Header.ios.tsx delete mode 100644 app/views/ShareListView/Header/Header.tsx delete mode 100644 app/views/ShareListView/Header/SearchBox.tsx delete mode 100644 app/views/ShareListView/Header/index.tsx delete mode 100644 app/views/ShareListView/Header/interface.ts delete mode 100644 app/views/ShareView/utils.ts delete mode 100644 app/views/WithoutServersView.tsx delete mode 100644 ios/ShareRocketChatRN/ShareRocketChatRN.m create mode 100644 ios/ShareRocketChatRN/ShareRocketChatRN.swift diff --git a/android/app/src/main/java/chat/rocket/reactnative/share/ShareActivity.java b/android/app/src/main/java/chat/rocket/reactnative/share/ShareActivity.java deleted file mode 100644 index 366efc3b8a..0000000000 --- a/android/app/src/main/java/chat/rocket/reactnative/share/ShareActivity.java +++ /dev/null @@ -1,10 +0,0 @@ -package chat.rocket.reactnative.share; - -import com.facebook.react.ReactActivity; - -public class ShareActivity extends ReactActivity { - @Override - protected String getMainComponentName() { - return "ShareRocketChatRN"; - } -} \ No newline at end of file diff --git a/android/app/src/main/java/chat/rocket/reactnative/share/ShareActivity.kt b/android/app/src/main/java/chat/rocket/reactnative/share/ShareActivity.kt new file mode 100644 index 0000000000..40dc45832c --- /dev/null +++ b/android/app/src/main/java/chat/rocket/reactnative/share/ShareActivity.kt @@ -0,0 +1,137 @@ +package chat.rocket.reactnative.share + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import java.io.File +import java.io.FileOutputStream +import java.util.* + +class ShareActivity : AppCompatActivity() { + + private val appScheme = "rocketchat" + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + handleIntent(intent) + } + + private fun handleIntent(intent: Intent?) { + // Check if the intent contains shared content + if (intent?.action == Intent.ACTION_SEND || intent?.action == Intent.ACTION_SEND_MULTIPLE) { + when { + intent.type?.startsWith("text/") == true -> handleText(intent) + intent.type?.startsWith("image/") == true -> handleMedia(intent, "data") + intent.type?.startsWith("video/") == true -> handleMedia(intent, "data") + intent.type?.startsWith("application/") == true -> handleMedia(intent, "data") + intent.type == "*/*" -> handleMedia(intent, "data") + intent.type == "text/plain" -> handleText(intent) + else -> completeRequest() // No matching type, complete the request + } + } else { + completeRequest() // No relevant intent action, complete the request + } + } + + private fun handleText(intent: Intent) { + // Handle sharing text + val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT) + if (sharedText != null) { + val encoded = Uri.encode(sharedText) + val url = Uri.parse("$appScheme://shareextension?text=$encoded") + openURL(url) + } + completeRequest() + } + + private fun handleMedia(intent: Intent, type: String) { + val mediaUris = StringBuilder() + var valid = true + + val uris = when (intent.action) { + Intent.ACTION_SEND -> listOf(intent.getParcelableExtra(Intent.EXTRA_STREAM) as Uri?) + Intent.ACTION_SEND_MULTIPLE -> intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM) + else -> null + } + + uris?.forEachIndexed { index, uri -> + val mediaUri = uri?.let { handleMediaUri(it, type) } + if (mediaUri != null) { + mediaUris.append(mediaUri) + if (index < uris.size - 1) { + mediaUris.append(",") + } + } else { + valid = false + } + } + + if (valid) { + val encoded = Uri.encode(mediaUris.toString()) + val url = Uri.parse("$appScheme://shareextension?mediaUris=$encoded") + openURL(url) + } + completeRequest() + } + + private fun handleMediaUri(uri: Uri, type: String): String? { + return try { + val inputStream = contentResolver.openInputStream(uri) + val originalFilename = getFileName(uri) + val filename = originalFilename ?: UUID.randomUUID().toString() + getFileExtension(uri, type) + val fileUri = saveDataToCacheDir(inputStream?.readBytes(), filename) + fileUri?.toString() + } catch (e: Exception) { + Log.e("ShareRocketChat", "Failed to process media", e) + null + } + } + + private fun getFileName(uri: Uri): String? { + // Attempt to get the original filename from the Uri + val cursor = contentResolver.query(uri, null, null, null, null) + return cursor?.use { + if (it.moveToFirst()) { + val nameIndex = it.getColumnIndex("_display_name") + if (nameIndex != -1) it.getString(nameIndex) else null + } else null + } + } + + private fun getFileExtension(uri: Uri, type: String): String { + // Determine the file extension based on the mime type, with fallbacks + val mimeType = contentResolver.getType(uri) + return when { + mimeType?.startsWith("image/") == true -> ".jpeg" + mimeType?.startsWith("video/") == true -> ".mp4" + else -> "" // Ignore the file if the type is not recognized + } + } + + private fun saveDataToCacheDir(data: ByteArray?, filename: String): Uri? { + // Save the shared data to the app's cache directory and return the file URI + return try { + val file = File(cacheDir, filename) + FileOutputStream(file).use { it.write(data) } + Uri.fromFile(file) // Return the file URI with file:// scheme + } catch (e: Exception) { + Log.e("ShareRocketChat", "Failed to save data", e) + null + } + } + + private fun openURL(uri: Uri) { + // Open the custom URI in the associated app + val intent = Intent(Intent.ACTION_VIEW, uri) + if (intent.resolveActivity(packageManager) != null) { + startActivity(intent) + } + } + + private fun completeRequest() { + // Finish the share activity + finish() + } +} diff --git a/android/app/src/main/java/chat/rocket/reactnative/share/ShareApplication.java b/android/app/src/main/java/chat/rocket/reactnative/share/ShareApplication.java deleted file mode 100644 index 94cd8b8610..0000000000 --- a/android/app/src/main/java/chat/rocket/reactnative/share/ShareApplication.java +++ /dev/null @@ -1,38 +0,0 @@ -package chat.rocket.reactnative.share; - -import chat.rocket.reactnative.BuildConfig; - -import chat.rocket.SharePackage; - -import android.app.Application; - -import com.facebook.react.shell.MainReactPackage; -import com.facebook.react.ReactNativeHost; -import com.facebook.react.ReactApplication; -import com.facebook.react.ReactPackage; - -import java.util.Arrays; -import java.util.List; - - -public class ShareApplication extends Application implements ReactApplication { - private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { - @Override - public boolean getUseDeveloperSupport() { - return BuildConfig.DEBUG; - } - - @Override - protected List<ReactPackage> getPackages() { - return Arrays.<ReactPackage>asList( - new MainReactPackage(), - new SharePackage() - ); - } - }; - - @Override - public ReactNativeHost getReactNativeHost() { - return mReactNativeHost; - } -} \ No newline at end of file diff --git a/app.json b/app.json index 5b802fd60f..5d8e300850 100644 --- a/app.json +++ b/app.json @@ -1,5 +1,3 @@ { - "name": "RocketChatRN", - "share": "ShareRocketChatRN", - "displayName": "RocketChatRN" + "name": "RocketChatRN" } diff --git a/app/AppContainer.tsx b/app/AppContainer.tsx index a1e99822c8..bfd1a8cc66 100644 --- a/app/AppContainer.tsx +++ b/app/AppContainer.tsx @@ -14,6 +14,7 @@ import SetUsernameView from './views/SetUsernameView'; import OutsideStack from './stacks/OutsideStack'; import InsideStack from './stacks/InsideStack'; import MasterDetailStack from './stacks/MasterDetailStack'; +import ShareExtensionStack from './stacks/ShareExtensionStack'; import { ThemeContext } from './theme'; import { setCurrentScreen } from './lib/methods/helpers/log'; @@ -57,13 +58,18 @@ const App = memo(({ root, isMasterDetail }: { root: string; isMasterDetail: bool Navigation.routeNameRef.current = currentRouteName; }}> <Stack.Navigator screenOptions={{ headerShown: false, animationEnabled: false }}> - {root === RootEnum.ROOT_LOADING ? <Stack.Screen name='AuthLoading' component={AuthLoadingView} /> : null} + {root === RootEnum.ROOT_LOADING || root === RootEnum.ROOT_LOADING_SHARE_EXTENSION ? ( + <Stack.Screen name='AuthLoading' component={AuthLoadingView} /> + ) : null} {root === RootEnum.ROOT_OUTSIDE ? <Stack.Screen name='OutsideStack' component={OutsideStack} /> : null} {root === RootEnum.ROOT_INSIDE && isMasterDetail ? ( <Stack.Screen name='MasterDetailStack' component={MasterDetailStack} /> ) : null} {root === RootEnum.ROOT_INSIDE && !isMasterDetail ? <Stack.Screen name='InsideStack' component={InsideStack} /> : null} {root === RootEnum.ROOT_SET_USERNAME ? <Stack.Screen name='SetUsernameStack' component={SetUsernameStack} /> : null} + {root === RootEnum.ROOT_SHARE_EXTENSION ? ( + <Stack.Screen name='ShareExtensionStack' component={ShareExtensionStack} /> + ) : null} </Stack.Navigator> </NavigationContainer> ); diff --git a/app/actions/actionsTypes.ts b/app/actions/actionsTypes.ts index fe6fb521e8..af1f261b3e 100644 --- a/app/actions/actionsTypes.ts +++ b/app/actions/actionsTypes.ts @@ -10,7 +10,7 @@ function createRequestTypes(base = {}, types = defaultTypes): Record<string, str // Login events export const LOGIN = createRequestTypes('LOGIN', [...defaultTypes, 'SET_SERVICES', 'SET_PREFERENCE', 'SET_LOCAL_AUTHENTICATED']); -export const SHARE = createRequestTypes('SHARE', ['SELECT_SERVER', 'SET_USER', 'SET_SETTINGS', 'SET_SERVER_INFO']); +export const SHARE = createRequestTypes('SHARE', ['SET_PARAMS']); export const USER = createRequestTypes('USER', ['SET', 'CLEAR']); export const ROOMS = createRequestTypes('ROOMS', [ ...defaultTypes, diff --git a/app/actions/share.ts b/app/actions/share.ts index 22e012680c..7350dafcaa 100644 --- a/app/actions/share.ts +++ b/app/actions/share.ts @@ -1,39 +1,17 @@ import { Action } from 'redux'; -import { IShareServer, IShareUser, TShareSettings } from '../reducers/share'; +import { TShareParams } from '../reducers/share'; import { SHARE } from './actionsTypes'; -interface IShareSelectServer extends Action { - server: IShareServer; +interface IShareSetParams extends Action { + params: TShareParams; } -interface IShareSetSettings extends Action { - settings: TShareSettings; -} - -interface IShareSetUser extends Action { - user: IShareUser; -} - -export type TActionsShare = IShareSelectServer & IShareSetSettings & IShareSetUser; - -export function shareSelectServer(server: IShareServer): IShareSelectServer { - return { - type: SHARE.SELECT_SERVER, - server - }; -} - -export function shareSetSettings(settings: TShareSettings): IShareSetSettings { - return { - type: SHARE.SET_SETTINGS, - settings - }; -} +export type TActionsShare = IShareSetParams; -export function shareSetUser(user: IShareUser): IShareSetUser { +export function shareSetParams(params: TShareParams): IShareSetParams { return { - type: SHARE.SET_USER, - user + type: SHARE.SET_PARAMS, + params }; } diff --git a/app/containers/Avatar/AvatarContainer.tsx b/app/containers/Avatar/AvatarContainer.tsx index e8e6a07542..3e1282d429 100644 --- a/app/containers/Avatar/AvatarContainer.tsx +++ b/app/containers/Avatar/AvatarContainer.tsx @@ -21,8 +21,8 @@ const AvatarContainer = ({ isStatic, rid }: IAvatar): React.ReactElement => { - const server = useSelector((state: IApplicationState) => state.share.server.server || state.server.server); - const serverVersion = useSelector((state: IApplicationState) => state.share.server.version || state.server.version); + const server = useSelector((state: IApplicationState) => state.server.server); + const serverVersion = useSelector((state: IApplicationState) => state.server.version); const { id, token, username } = useSelector( (state: IApplicationState) => ({ id: getUserSelector(state).id, @@ -38,11 +38,8 @@ const AvatarContainer = ({ cdnPrefix: state.settings.CDN_PREFIX as string })); const blockUnauthenticatedAccess = useSelector( - (state: IApplicationState) => - (state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess as boolean) ?? - state.settings.Accounts_AvatarBlockUnauthenticatedAccess ?? - true - ); + (state: IApplicationState) => state.settings.Accounts_AvatarBlockUnauthenticatedAccess ?? true + ) as boolean; const { avatarETag } = useAvatarETag({ username, text, type, rid, id }); diff --git a/app/containers/EmojiPicker/CustomEmoji.tsx b/app/containers/EmojiPicker/CustomEmoji.tsx index 02768f29d0..296439cae0 100644 --- a/app/containers/EmojiPicker/CustomEmoji.tsx +++ b/app/containers/EmojiPicker/CustomEmoji.tsx @@ -12,7 +12,7 @@ interface ICustomEmojiProps { const CustomEmoji = React.memo( ({ emoji, style }: ICustomEmojiProps) => { - const baseUrl = useAppSelector(state => state.share.server.server || state.server.server); + const baseUrl = useAppSelector(state => state.server.server); return ( <FastImage style={style} diff --git a/app/containers/ServerItem/index.tsx b/app/containers/ServerItem/index.tsx index 780b5640c7..cd2fc45b45 100644 --- a/app/containers/ServerItem/index.tsx +++ b/app/containers/ServerItem/index.tsx @@ -30,12 +30,11 @@ const ServerItem = React.memo(({ item, onPress, onLongPress, hasCheck }: IServer <Pressable onPress={onPress} onLongPress={() => onLongPress?.()} - testID={`rooms-list-header-server-${item.id}`} + testID={`server-item-${item.id}`} android_ripple={{ color: themes[theme].surfaceNeutral }} style={({ pressed }: { pressed: boolean }) => ({ backgroundColor: isIOS && pressed ? themes[theme].surfaceNeutral : themes[theme].surfaceRoom - })} - > + })}> <View style={styles.serverItemContainer}> {item.iconURL ? ( <FastImage diff --git a/app/definitions/navigationTypes.ts b/app/definitions/navigationTypes.ts index 960b35b5df..cd8e34d123 100644 --- a/app/definitions/navigationTypes.ts +++ b/app/definitions/navigationTypes.ts @@ -29,6 +29,7 @@ export type StackParamList = { InsideStack: NavigatorScreenParams<InsideStackParamList>; MasterDetailStack: NavigatorScreenParams<MasterDetailInsideStackParamList>; SetUsernameStack: NavigatorScreenParams<SetUsernameStackParamList>; + ShareExtensionStack: NavigatorScreenParams<ShareInsideStackParamList>; }; export type ShareInsideStackParamList = { @@ -44,13 +45,3 @@ export type ShareInsideStackParamList = { }; SelectServerView: undefined; }; - -export type ShareOutsideStackParamList = { - WithoutServersView: undefined; -}; - -export type ShareAppStackParamList = { - AuthLoading?: undefined; - OutsideStack?: NavigatorScreenParams<ShareOutsideStackParamList>; - InsideStack?: NavigatorScreenParams<ShareInsideStackParamList>; -}; diff --git a/app/definitions/redux/TRootEnum.ts b/app/definitions/redux/TRootEnum.ts index 8b0faca42e..4c39c38d3e 100644 --- a/app/definitions/redux/TRootEnum.ts +++ b/app/definitions/redux/TRootEnum.ts @@ -2,5 +2,7 @@ export enum RootEnum { ROOT_OUTSIDE = 'outside', ROOT_INSIDE = 'inside', ROOT_LOADING = 'loading', - ROOT_SET_USERNAME = 'setUsername' + ROOT_SET_USERNAME = 'setUsername', + ROOT_SHARE_EXTENSION = 'shareextension', + ROOT_LOADING_SHARE_EXTENSION = 'loadingshareextension' } diff --git a/app/index.tsx b/app/index.tsx index 338fb9c01d..84670f7213 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -4,7 +4,6 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context'; import RNScreens from 'react-native-screens'; import { Provider } from 'react-redux'; -import Clipboard from '@react-native-clipboard/clipboard'; import AppContainer from './AppContainer'; import { appInit, appInitLocalSettings, setMasterDetail as setMasterDetailAction } from './actions/app'; @@ -59,14 +58,23 @@ interface IState { const parseDeepLinking = (url: string) => { if (url) { url = url.replace(/rocketchat:\/\/|https:\/\/go.rocket.chat\//, ''); - const regex = /^(room|auth|invite)\?/; - if (url.match(regex)) { - url = url.replace(regex, '').trim(); - if (url) { - return parseQuery(url); + const regex = /^(room|auth|invite|shareextension)\?/; + const match = url.match(regex); + if (match) { + const matchedPattern = match[1]; + const query = url.replace(regex, '').trim(); + + if (query) { + const parsedQuery = parseQuery(query); + return { + ...parsedQuery, + type: matchedPattern + }; } } } + + // Return null if the URL doesn't match or is not valid return null; }; @@ -131,7 +139,6 @@ export default class Root extends React.Component<{}, IState> { const deepLinking = await Linking.getInitialURL(); const parsedDeepLinkingURL = parseDeepLinking(deepLinking!); if (parsedDeepLinkingURL) { - Clipboard.setString(JSON.stringify(parsedDeepLinkingURL)); store.dispatch(deepLinkingOpen(parsedDeepLinkingURL)); return; } diff --git a/app/lib/database/index.ts b/app/lib/database/index.ts index dee16a5416..aa45bcb209 100644 --- a/app/lib/database/index.ts +++ b/app/lib/database/index.ts @@ -64,7 +64,6 @@ export const getDatabase = (database = ''): Database => { }; interface IDatabases { - shareDB?: TAppDatabase | null; serversDB: TServerDatabase; activeDB?: TAppDatabase; } @@ -82,51 +81,14 @@ class DB { }) as TServerDatabase }; - // Expected at least one database get active(): TAppDatabase { - return this.databases.shareDB || this.databases.activeDB!; - } - - get share() { - return this.databases.shareDB; - } - - set share(db) { - this.databases.shareDB = db; + return this.databases.activeDB!; } get servers() { return this.databases.serversDB; } - setShareDB(database = '') { - const path = database.replace(/(^\w+:|^)\/\//, '').replace(/\//g, '.'); - const dbName = getDatabasePath(path); - - const adapter = new SQLiteAdapter({ - dbName, - schema: appSchema, - migrations, - jsi: true - }); - - this.databases.shareDB = new Database({ - adapter, - modelClasses: [ - Subscription, - Message, - Thread, - ThreadMessage, - Upload, - Permission, - CustomEmoji, - FrequentlyUsedEmoji, - Setting, - User - ] - }) as TAppDatabase; - } - setActiveDB(database: string) { this.databases.activeDB = getDatabase(database) as TAppDatabase; } diff --git a/app/lib/methods/helpers/askAndroidMediaPermissions.ts b/app/lib/methods/helpers/askAndroidMediaPermissions.ts deleted file mode 100644 index 56b3f5f340..0000000000 --- a/app/lib/methods/helpers/askAndroidMediaPermissions.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Permission, PermissionsAndroid, Platform, Rationale } from 'react-native'; - -import i18n from '../../../i18n'; - -// Define a type for the permissions map -type PermissionsMap = { [key: string]: string }; - -/** - * Rationale for requesting read permissions on Android. - */ -const readExternalStorageRationale: Rationale = { - title: i18n.t('Read_External_Permission'), - message: i18n.t('Read_External_Permission_Message'), - buttonPositive: i18n.t('Ok') -}; - -/** - * Checks if all requested permissions are granted. - * - * @param {PermissionsMap} permissionsStatus - The object containing the statuses of the permissions. - * @param {string[]} permissions - The list of permissions to check. - * @return {boolean} Whether all permissions are granted. - */ -const areAllPermissionsGranted = (permissionsStatus: PermissionsMap, permissions: string[]): boolean => - permissions.every(permission => permissionsStatus[permission] === PermissionsAndroid.RESULTS.GRANTED); - -/** - * Requests permission for reading media on Android. - * - * @return {Promise<boolean>} A promise that resolves to a boolean indicating whether the permissions were granted. - */ -export const askAndroidMediaPermissions = async (): Promise<boolean> => { - if (Platform.OS !== 'android') return true; - - // For Android versions that require the new permissions model (API Level >= 33) - if (Platform.constants.Version >= 33) { - const permissions = [ - 'android.permission.READ_MEDIA_IMAGES', - 'android.permission.READ_MEDIA_VIDEO', - 'android.permission.READ_MEDIA_AUDIO' - ]; - - const permissionsStatus = await PermissionsAndroid.requestMultiple(permissions as Permission[]); - return areAllPermissionsGranted(permissionsStatus, permissions); - } - - // For older Android versions - const result = await PermissionsAndroid.request( - PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, - readExternalStorageRationale - ); - - return result === PermissionsAndroid.RESULTS.GRANTED; -}; diff --git a/app/lib/methods/helpers/helpers.ts b/app/lib/methods/helpers/helpers.ts index 46074f1ad2..5e73f4350d 100644 --- a/app/lib/methods/helpers/helpers.ts +++ b/app/lib/methods/helpers/helpers.ts @@ -83,9 +83,8 @@ export function isRead(item) { } export function hasRole(role): boolean { - const shareUser = reduxStore.getState().share.user; const loginUser = reduxStore.getState().login.user; - const userRoles = shareUser?.roles || loginUser?.roles || []; + const userRoles = loginUser?.roles || []; return userRoles.indexOf(role) > -1; } @@ -106,10 +105,8 @@ export async function hasPermission(permissions, rid?: any): Promise<boolean[]> } try { - const shareUser = reduxStore.getState().share.user; const loginUser = reduxStore.getState().login.user; - // get user roles on the server from redux - const userRoles = shareUser?.roles || loginUser?.roles || []; + const userRoles = loginUser?.roles || []; const mergedRoles = [...new Set([...roomRoles, ...userRoles])]; return permissions.map(permission => permission?.some(r => mergedRoles.includes(r) ?? false)); } catch (e) { diff --git a/app/lib/methods/helpers/index.ts b/app/lib/methods/helpers/index.ts index 58dfa6fefe..ebe3110493 100644 --- a/app/lib/methods/helpers/index.ts +++ b/app/lib/methods/helpers/index.ts @@ -15,7 +15,6 @@ export * from './isSsl'; export * from './isValidEmail'; export * from './random'; export * from './image'; -export * from './askAndroidMediaPermissions'; export * from './emitter'; export * from './parseJson'; export * from './fileDownload'; diff --git a/app/lib/methods/helpers/parseQuery.ts b/app/lib/methods/helpers/parseQuery.ts index c6e2bb5fdf..8e3d7abe8c 100644 --- a/app/lib/methods/helpers/parseQuery.ts +++ b/app/lib/methods/helpers/parseQuery.ts @@ -10,6 +10,10 @@ */ export default function (query: string) { + if (query.startsWith('url=')) { + return { text: decodeURIComponent(query.replace('url=', '').trim()) }; + } + return (/^[?#]/.test(query) ? query.slice(1) : query).split('&').reduce((params: { [key: string]: string }, param) => { const [key, value] = param.split('='); params[key] = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : ''; diff --git a/app/lib/methods/shareExtension.ts b/app/lib/methods/shareExtension.ts deleted file mode 100644 index f7ae6c0733..0000000000 --- a/app/lib/methods/shareExtension.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Q } from '@nozbe/watermelondb'; - -import { shareSetSettings, shareSelectServer, shareSetUser } from '../../actions/share'; -import SSLPinning from './helpers/sslPinning'; -import log from './helpers/log'; -import { IShareServer, IShareUser } from '../../reducers/share'; -import UserPreferences from './userPreferences'; -import database from '../database'; -import { encryptionInit } from '../../actions/encryption'; -import { store } from '../store/auxStore'; -import sdk from '../services/sdk'; -import { CERTIFICATE_KEY, TOKEN_KEY } from '../constants'; -import { setCustomEmojis } from './getCustomEmojis'; -import { Services } from '../services'; -import { parseSettings } from './parseSettings'; - -export async function shareExtensionInit(server: string) { - database.setShareDB(server); - - try { - const certificate = UserPreferences.getString(`${CERTIFICATE_KEY}-${server}`); - if (SSLPinning && certificate) { - await SSLPinning.setCertificate(certificate, server); - } - } catch { - // Do nothing - } - - // sdk.current.disconnect(); - sdk.initializeShareExtension(server); - - // set Server - const currentServer: IShareServer = { - server, - version: '' - }; - const serversDB = database.servers; - const serversCollection = serversDB.get('servers'); - try { - const serverRecord = await serversCollection.find(server); - currentServer.version = serverRecord.version; - } catch { - // Record not found - } - store.dispatch(shareSelectServer(currentServer)); - - setCustomEmojis(); - - try { - // set Settings - const settings = ['Accounts_AvatarBlockUnauthenticatedAccess']; - const db = database.active; - const settingsCollection = db.get('settings'); - const settingsRecords = await settingsCollection.query(Q.where('id', Q.oneOf(settings))).fetch(); - const parsed = Object.values(settingsRecords).map(item => ({ - _id: item.id, - valueAsString: item.valueAsString, - valueAsBoolean: item.valueAsBoolean, - valueAsNumber: item.valueAsNumber, - valueAsArray: item.valueAsArray, - _updatedAt: item._updatedAt - })); - store.dispatch(shareSetSettings(parseSettings(parsed))); - - // set User info - const userId = UserPreferences.getString(`${TOKEN_KEY}-${server}`); - const userCollections = serversDB.get('users'); - let user = null; - if (userId) { - const userRecord = await userCollections.find(userId); - user = { - id: userRecord.id, - token: userRecord.token, - username: userRecord.username, - roles: userRecord.roles - }; - } - store.dispatch(shareSetUser(user as IShareUser)); - if (user) { - await Services.login({ resume: user.token }); - } - store.dispatch(encryptionInit()); - } catch (e) { - log(e); - } -} - -export function closeShareExtension() { - sdk.disconnect(); - database.share = null; - - store.dispatch(shareSelectServer({})); - store.dispatch(shareSetUser({})); - store.dispatch(shareSetSettings({})); -} diff --git a/app/lib/methods/useServer.ts b/app/lib/methods/useServer.ts index 60e367a01c..ddda07c9b0 100644 --- a/app/lib/methods/useServer.ts +++ b/app/lib/methods/useServer.ts @@ -6,7 +6,6 @@ import { useAppSelector } from '../hooks'; export default function useServer() { const [server, setServer] = useState<TServerModel | null>(null); - const shareServer = useAppSelector((state: IApplicationState) => state.share.server.server); const appServer = useAppSelector((state: IApplicationState) => state.server.server); useEffect(() => { @@ -15,7 +14,7 @@ export default function useServer() { const serversCollection = serversDB.get('servers'); let serverInfo = null; try { - serverInfo = await serversCollection.find(shareServer || appServer); + serverInfo = await serversCollection.find(appServer); setServer(serverInfo); } catch { setServer(serverInfo); diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 0bfe8d2507..d1572e61fc 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -10,7 +10,6 @@ import { compareServerVersion, random } from '../methods/helpers'; class Sdk { private sdk: typeof Rocketchat; - private shareSdk?: typeof Rocketchat; private code: any; private initializeSdk(server: string): typeof Rocketchat { @@ -25,12 +24,8 @@ class Sdk { return this.sdk; } - initializeShareExtension(server: string) { - this.shareSdk = this.initializeSdk(server); - } - get current() { - return this.shareSdk || this.sdk; + return this.sdk; } /** @@ -38,11 +33,6 @@ class Sdk { * I'm returning "null" because we need to remove both instances of this.sdk here and on rocketchat.js */ disconnect() { - if (this.shareSdk) { - this.shareSdk.disconnect(); - this.shareSdk = null; - return null; - } if (this.sdk) { this.sdk.disconnect(); this.sdk = null; diff --git a/app/reducers/share.test.ts b/app/reducers/share.test.ts index 851e7ad269..22b3a534f1 100644 --- a/app/reducers/share.test.ts +++ b/app/reducers/share.test.ts @@ -1,4 +1,4 @@ -import { shareSelectServer, shareSetSettings, shareSetUser } from '../actions/share'; +import { shareSetParams } from '../actions/share'; import { mockedStore } from './mockedStore'; import { initialState } from './share'; @@ -8,34 +8,22 @@ describe('test share reducer', () => { expect(state).toEqual(initialState); }); - it('should return modified store after shareSelectServer', () => { - const server = { - server: 'https://open.rocket.chat', - version: '4.4.0' + it('should return correctly updated state after calling setParams action', () => { + const params: Record<string, any> = { + mediaUris: 'test' }; - mockedStore.dispatch(shareSelectServer(server)); - const state = mockedStore.getState().share.server; - expect(state).toEqual(server); - }); - - it('should return modified store after shareSetSettings', () => { - const settings = { - Admin: false - }; - mockedStore.dispatch(shareSetSettings(settings)); - const state = mockedStore.getState().share.settings; - expect(state).toEqual(settings); + mockedStore.dispatch(shareSetParams(params)); + const state = mockedStore.getState().share; + expect(state).toEqual({ + ...initialState, + params + }); }); - it('should return modified store after shareSetUser', () => { - const user = { - id: 'dig-joy', - token: 'token', - username: 'rocket.chat', - roles: ['admin'] - }; - mockedStore.dispatch(shareSetUser(user)); - const state = mockedStore.getState().share.user; - expect(state).toEqual(user); + it('should reset params to an empty object', () => { + const params: Record<string, any> = {}; + mockedStore.dispatch(shareSetParams(params)); + const state = mockedStore.getState().share; + expect(state).toEqual(initialState); }); }); diff --git a/app/reducers/share.ts b/app/reducers/share.ts index d62f9c5848..147ef620c5 100644 --- a/app/reducers/share.ts +++ b/app/reducers/share.ts @@ -1,48 +1,22 @@ import { TActionsShare } from '../actions/share'; import { SHARE } from '../actions/actionsTypes'; -export interface IShareServer { - server?: string; - version?: string; -} - -export type TShareSettings = Record<string, string | number | boolean>; - -export interface IShareUser { - id?: string; - token?: string; - username?: string; - roles?: string[]; -} +export type TShareParams = Record<string, any>; export interface IShare { - user: IShareUser; - server: IShareServer; - settings: TShareSettings; + params: TShareParams; } export const initialState: IShare = { - user: {}, - server: {}, - settings: {} + params: {} }; export default function share(state = initialState, action: TActionsShare): IShare { switch (action.type) { - case SHARE.SELECT_SERVER: - return { - ...state, - server: action.server - }; - case SHARE.SET_USER: - return { - ...state, - user: action.user - }; - case SHARE.SET_SETTINGS: + case SHARE.SET_PARAMS: return { ...state, - settings: action.settings + params: action.params }; default: return state; diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index 4e72134011..bf72854640 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -1,5 +1,6 @@ import { all, call, delay, put, select, take, takeLatest } from 'redux-saga/effects'; +import { shareSetParams } from '../actions/share'; import * as types from '../actions/actionsTypes'; import { appInit, appStart } from '../actions/app'; import { inviteLinksRequest, inviteLinksSetToken } from '../actions/inviteLinks'; @@ -17,6 +18,7 @@ import log from '../lib/methods/helpers/log'; import UserPreferences from '../lib/methods/userPreferences'; import { videoConfJoin } from '../lib/methods/videoConf'; import { Services } from '../lib/services'; +import sdk from '../lib/services/sdk'; const roomTypes = { channel: 'c', @@ -84,7 +86,31 @@ const handleOAuth = function* handleOAuth({ params }) { } }; +const handleShareExtension = function* handleOpen({ params }) { + const server = UserPreferences.getString(CURRENT_SERVER); + const user = UserPreferences.getString(`${TOKEN_KEY}-${server}`); + + if (!user) { + yield put(appInit()); + return; + } + + yield put(appStart({ root: RootEnum.ROOT_LOADING_SHARE_EXTENSION })); + yield localAuthenticate(server); + yield put(selectServerRequest(server)); + if (sdk.current?.client?.host !== server) { + yield take(types.LOGIN.SUCCESS); + } + yield put(shareSetParams(params)); + yield put(appStart({ root: RootEnum.ROOT_SHARE_EXTENSION })); +}; + const handleOpen = function* handleOpen({ params }) { + if (params.type === 'shareextension') { + yield handleShareExtension({ params }); + return; + } + const serversDB = database.servers; const serversCollection = serversDB.get('servers'); diff --git a/app/sagas/encryption.js b/app/sagas/encryption.js index 0b8e3e0e7c..a2e9a9aba6 100644 --- a/app/sagas/encryption.js +++ b/app/sagas/encryption.js @@ -11,7 +11,7 @@ import log from '../lib/methods/helpers/log'; import { E2E_BANNER_TYPE, E2E_PRIVATE_KEY, E2E_PUBLIC_KEY, E2E_RANDOM_PASSWORD_KEY } from '../lib/constants'; import { Services } from '../lib/services'; -const getServer = state => state.share.server.server || state.server.server; +const getServer = state => state.server.server; const getE2eEnable = state => state.settings.E2E_Enable; const handleEncryptionInit = function* handleEncryptionInit() { diff --git a/app/sagas/init.js b/app/sagas/init.js index 5de30417d1..37e072fcec 100644 --- a/app/sagas/init.js +++ b/app/sagas/init.js @@ -1,4 +1,4 @@ -import { call, put, takeLatest } from 'redux-saga/effects'; +import { call, put, select, takeLatest } from 'redux-saga/effects'; import RNBootSplash from 'react-native-bootsplash'; import AsyncStorage from '@react-native-async-storage/async-storage'; @@ -23,6 +23,7 @@ export const initLocalSettings = function* initLocalSettings() { const BIOMETRY_MIGRATION_KEY = 'kBiometryMigration'; const restore = function* restore() { + console.log('RESTORE'); try { const server = UserPreferences.getString(CURRENT_SERVER); let userId = UserPreferences.getString(`${TOKEN_KEY}-${server}`); @@ -84,7 +85,11 @@ const restore = function* restore() { }; const start = function* start() { - yield RNBootSplash.hide({ fade: true }); + const currentRoot = yield select(state => state.app.root); + + if (currentRoot !== RootEnum.ROOT_LOADING_SHARE_EXTENSION) { + yield RNBootSplash.hide({ fade: true }); + } }; const root = function* root() { diff --git a/app/sagas/login.js b/app/sagas/login.js index 0d2b26b344..13adecbe35 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -234,7 +234,10 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) { UserPreferences.setString(CURRENT_SERVER, server); yield put(setUser(user)); EventEmitter.emit('connected'); - yield put(appStart({ root: RootEnum.ROOT_INSIDE })); + const currentRoot = yield select(state => state.app.root); + if (currentRoot !== RootEnum.ROOT_SHARE_EXTENSION && currentRoot !== RootEnum.ROOT_LOADING_SHARE_EXTENSION) { + yield put(appStart({ root: RootEnum.ROOT_INSIDE })); + } const inviteLinkToken = yield select(state => state.inviteLinks.token); if (inviteLinkToken) { yield put(inviteLinksRequest(inviteLinkToken)); diff --git a/app/sagas/selectServer.ts b/app/sagas/selectServer.ts index 8433e72a64..883dd8b68e 100644 --- a/app/sagas/selectServer.ts +++ b/app/sagas/selectServer.ts @@ -185,7 +185,10 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch yield put(clearSettings()); yield put(setUser(user)); yield connect({ server, logoutOnError: true }); - yield put(appStart({ root: RootEnum.ROOT_INSIDE })); + const currentRoot = yield* appSelector(state => state.app.root); + if (currentRoot !== RootEnum.ROOT_SHARE_EXTENSION && currentRoot !== RootEnum.ROOT_LOADING_SHARE_EXTENSION) { + yield put(appStart({ root: RootEnum.ROOT_INSIDE })); + } UserPreferences.setString(CURRENT_SERVER, server); // only set server after have a user } else { yield put(clearUser()); diff --git a/app/selectors/login.ts b/app/selectors/login.ts index f8a5198078..1f14068e08 100644 --- a/app/selectors/login.ts +++ b/app/selectors/login.ts @@ -1,5 +1,4 @@ import { createSelector } from 'reselect'; -import isEmpty from 'lodash/isEmpty'; import { IApplicationState, IUser } from '../definitions'; @@ -13,12 +12,7 @@ export interface IServices { wordpress: { clientId: string; serverURL: string }; } -const getUser = (state: IApplicationState): IUser => { - if (!isEmpty(state.share?.user)) { - return state.share.user as IUser; - } - return state.login?.user as IUser; -}; +const getUser = (state: IApplicationState): IUser => state.login?.user as IUser; const getLoginServices = (state: IApplicationState) => (state.login.services as IServices) || {}; const getShowFormLoginSetting = (state: IApplicationState) => (state.settings.Accounts_ShowFormLogin as boolean) || false; const getIframeEnabledSetting = (state: IApplicationState) => (state.settings.Accounts_iframe_enabled as boolean) || false; diff --git a/app/share.tsx b/app/share.tsx deleted file mode 100644 index 0f0f730baa..0000000000 --- a/app/share.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import React, { useContext, useEffect, useLayoutEffect, useState } from 'react'; -import { Dimensions } from 'react-native'; -import { NavigationContainer } from '@react-navigation/native'; -import { createStackNavigator } from '@react-navigation/stack'; -import { Provider } from 'react-redux'; - -import { getTheme, setNativeTheme, initialTheme as initialThemeFunction, unsubscribeTheme } from './lib/methods/helpers/theme'; -import UserPreferences from './lib/methods/userPreferences'; -import Navigation from './lib/navigation/shareNavigation'; -import store from './lib/store'; -import { initStore } from './lib/store/auxStore'; -import { closeShareExtension, shareExtensionInit } from './lib/methods/shareExtension'; -import { defaultHeader, getActiveRouteName, navigationTheme, themedHeader } from './lib/methods/helpers/navigation'; -import { ThemeContext } from './theme'; -import { localAuthenticate } from './lib/methods/helpers/localAuthentication'; -import ScreenLockedView from './views/ScreenLockedView'; -// Outside Stack -import WithoutServersView from './views/WithoutServersView'; -// Inside Stack -import ShareListView from './views/ShareListView'; -import ShareView from './views/ShareView'; -import SelectServerView from './views/SelectServerView'; -import { setCurrentScreen } from './lib/methods/helpers/log'; -import AuthLoadingView from './views/AuthLoadingView'; -import { DimensionsContext } from './dimensions'; -import { ShareInsideStackParamList, ShareOutsideStackParamList, ShareAppStackParamList } from './definitions/navigationTypes'; -import { colors, CURRENT_SERVER } from './lib/constants'; -import Loading from './containers/Loading'; - -initStore(store); - -const Inside = createStackNavigator<ShareInsideStackParamList>(); -const InsideStack = () => { - const { theme } = useContext(ThemeContext); - - const screenOptions = { - ...defaultHeader, - ...themedHeader(theme) - }; - screenOptions.headerStyle = { ...screenOptions.headerStyle, height: 57 }; - - return ( - <Inside.Navigator screenOptions={screenOptions}> - {/* @ts-ignore */} - <Inside.Screen name='ShareListView' component={ShareListView} /> - {/* @ts-ignore */} - <Inside.Screen name='ShareView' component={ShareView} /> - <Inside.Screen name='SelectServerView' component={SelectServerView} /> - </Inside.Navigator> - ); -}; - -const Outside = createStackNavigator<ShareOutsideStackParamList>(); -const OutsideStack = () => { - const { theme } = useContext(ThemeContext); - - return ( - <Outside.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme) }}> - <Outside.Screen name='WithoutServersView' component={WithoutServersView} /> - </Outside.Navigator> - ); -}; - -// App -const Stack = createStackNavigator<ShareAppStackParamList>(); -export const App = ({ root }: { root: string }): React.ReactElement => ( - <Stack.Navigator screenOptions={{ headerShown: false, animationEnabled: false }}> - <> - {!root ? <Stack.Screen name='AuthLoading' component={AuthLoadingView} /> : null} - {root === 'outside' ? <Stack.Screen name='OutsideStack' component={OutsideStack} /> : null} - {root === 'inside' ? <Stack.Screen name='InsideStack' component={InsideStack} /> : null} - </> - </Stack.Navigator> -); - -const { width, height, scale, fontScale } = Dimensions.get('screen'); -const initialTheme = initialThemeFunction(); -const theme = getTheme(initialTheme); - -const Root = (): React.ReactElement => { - const [root, setRoot] = useState(''); - const navTheme = navigationTheme(theme); - - useLayoutEffect(() => { - setNativeTheme(initialTheme); - }, []); - - useEffect(() => { - const authenticateShare = async (currentServer: string) => { - await localAuthenticate(currentServer); - setRoot('inside'); - await shareExtensionInit(currentServer); - }; - - const currentServer = UserPreferences.getString(CURRENT_SERVER); - if (currentServer) { - authenticateShare(currentServer); - } else { - setRoot('outside'); - } - - const state = Navigation.navigationRef.current?.getRootState(); - const currentRouteName = getActiveRouteName(state); - Navigation.routeNameRef.current = currentRouteName; - setCurrentScreen(currentRouteName); - - return () => { - closeShareExtension(); - unsubscribeTheme(); - }; - }, []); - - return ( - <Provider store={store}> - <ThemeContext.Provider value={{ theme, colors: colors[theme] }}> - <DimensionsContext.Provider - value={{ - width, - height, - scale, - fontScale - }}> - <NavigationContainer - theme={navTheme} - ref={Navigation.navigationRef} - onStateChange={state => { - const previousRouteName = Navigation.routeNameRef.current; - const currentRouteName = getActiveRouteName(state); - if (previousRouteName !== currentRouteName) { - setCurrentScreen(currentRouteName); - } - Navigation.routeNameRef.current = currentRouteName; - }}> - <App root={root} /> - <Loading /> - </NavigationContainer> - <ScreenLockedView /> - </DimensionsContext.Provider> - </ThemeContext.Provider> - </Provider> - ); -}; - -export default Root; diff --git a/app/stacks/ShareExtensionStack.tsx b/app/stacks/ShareExtensionStack.tsx new file mode 100644 index 0000000000..07d4aa31c7 --- /dev/null +++ b/app/stacks/ShareExtensionStack.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { createStackNavigator, StackNavigationOptions } from '@react-navigation/stack'; + +import { ThemeContext } from '../theme'; +import { StackAnimation, defaultHeader, themedHeader } from '../lib/methods/helpers/navigation'; +import SelectServerView from '../views/SelectServerView'; +import ShareListView from '../views/ShareListView'; +import ShareView from '../views/ShareView'; + +const ShareExtension = createStackNavigator<any>(); +const ShareExtensionStack = () => { + const { theme } = React.useContext(ThemeContext); + + return ( + <ShareExtension.Navigator + screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation } as StackNavigationOptions}> + {/* @ts-ignore */} + <ShareExtension.Screen name='ShareListView' component={ShareListView} /> + {/* @ts-ignore */} + <ShareExtension.Screen name='ShareView' component={ShareView} /> + <ShareExtension.Screen name='SelectServerView' component={SelectServerView} /> + </ShareExtension.Navigator> + ); +}; + +export default ShareExtensionStack; diff --git a/app/views/SelectServerView.tsx b/app/views/SelectServerView.tsx index ceddb088f8..d3beecf215 100644 --- a/app/views/SelectServerView.tsx +++ b/app/views/SelectServerView.tsx @@ -3,25 +3,27 @@ import { FlatList } from 'react-native'; import { StackNavigationProp } from '@react-navigation/stack'; import { Q } from '@nozbe/watermelondb'; import { useNavigation } from '@react-navigation/native'; +import { useDispatch } from 'react-redux'; import I18n from '../i18n'; import StatusBar from '../containers/StatusBar'; import ServerItem, { ROW_HEIGHT } from '../containers/ServerItem'; -import { shareExtensionInit } from '../lib/methods/shareExtension'; import database from '../lib/database'; import SafeAreaView from '../containers/SafeAreaView'; import * as List from '../containers/List'; import { ShareInsideStackParamList } from '../definitions/navigationTypes'; import { TServerModel } from '../definitions'; import { useAppSelector } from '../lib/hooks'; +import { selectServerRequest } from '../actions/server'; const getItemLayout = (data: any, index: number) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index }); const keyExtractor = (item: TServerModel) => item.id; const SelectServerView = () => { const [servers, setServers] = React.useState<TServerModel[]>([]); + const dispatch = useDispatch(); - const server = useAppSelector(state => state.share.server.server); + const server = useAppSelector(state => state.server.server); const navigation = useNavigation<StackNavigationProp<ShareInsideStackParamList, 'SelectServerView'>>(); useLayoutEffect(() => { @@ -40,15 +42,15 @@ const SelectServerView = () => { init(); }, []); - const select = async (serverSelected: string) => { - navigation.navigate('ShareListView'); + const select = (serverSelected: string) => { if (serverSelected !== server) { - await shareExtensionInit(serverSelected); + dispatch(selectServerRequest(serverSelected)); } + navigation.pop(); }; return ( - <SafeAreaView> + <SafeAreaView testID='select-server-view'> <StatusBar /> <FlatList data={servers} diff --git a/app/views/ShareListView/Header/Header.ios.tsx b/app/views/ShareListView/Header/Header.ios.tsx deleted file mode 100644 index ddd61acd90..0000000000 --- a/app/views/ShareListView/Header/Header.ios.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React, { useState } from 'react'; -import { Keyboard, StyleSheet, View } from 'react-native'; -import ShareExtension from 'rn-extensions-share'; - -import SearchBox from './SearchBox'; -import * as HeaderButton from '../../../containers/HeaderButton'; -import { themes } from '../../../lib/constants'; -import sharedStyles from '../../Styles'; -import { animateNextTransition } from '../../../lib/methods/helpers/layoutAnimation'; -import { IShareListHeaderIos } from './interface'; - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - ...sharedStyles.separatorBottom - } -}); - -const Header = React.memo(({ searching, onChangeSearchText, initSearch, cancelSearch, theme }: IShareListHeaderIos) => { - const [text, setText] = useState(''); - - const onChangeText = (searchText: string) => { - onChangeSearchText(searchText); - setText(searchText); - }; - - const onCancelPress = () => { - Keyboard.dismiss(); - onChangeText(''); - cancelSearch(); - animateNextTransition(); - }; - - const onFocus = () => { - initSearch(); - animateNextTransition(); - }; - - return ( - <View - style={[ - styles.container, - { - borderColor: themes[theme].strokeLight, - backgroundColor: themes[theme].surfaceNeutral - } - ]}> - {!searching ? <HeaderButton.CancelModal onPress={ShareExtension.close} testID='share-extension-close' /> : null} - <SearchBox - value={text} - hasCancel={searching} - onFocus={onFocus} - onCancelPress={onCancelPress} - onChangeText={onChangeText} - testID='rooms-list-view-search' - key='rooms-list-view-search' - /> - </View> - ); -}); - -export default Header; diff --git a/app/views/ShareListView/Header/Header.tsx b/app/views/ShareListView/Header/Header.tsx deleted file mode 100644 index f835aa3e8e..0000000000 --- a/app/views/ShareListView/Header/Header.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import { StyleSheet, Text, View } from 'react-native'; - -import { TextInput } from '../../../containers/TextInput'; -import I18n from '../../../i18n'; -import { themes } from '../../../lib/constants'; -import sharedStyles from '../../Styles'; -import { IShareListHeader } from './interface'; - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center' - }, - search: { - fontSize: 20, - ...sharedStyles.textRegular, - marginHorizontal: 14 - }, - title: { - fontSize: 20, - ...sharedStyles.textBold, - marginHorizontal: 16 - } -}); - -const Header = React.memo(({ searching, onChangeSearchText, theme }: IShareListHeader) => { - const titleColorStyle = { color: themes[theme].fontSecondaryInfo }; - const isLight = theme === 'light'; - if (searching) { - return ( - <View style={styles.container}> - <TextInput - style={[styles.search, isLight && titleColorStyle]} - placeholder={I18n.t('Search')} - onChangeText={onChangeSearchText} - autoFocus - /> - </View> - ); - } - return <Text style={[styles.title, titleColorStyle]}>{I18n.t('Send_to')}</Text>; -}); - -export default Header; diff --git a/app/views/ShareListView/Header/SearchBox.tsx b/app/views/ShareListView/Header/SearchBox.tsx deleted file mode 100644 index 92296fcf15..0000000000 --- a/app/views/ShareListView/Header/SearchBox.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react'; -import { StyleSheet, Text, TextInput as RNTextInput, TextInputProps, View } from 'react-native'; -import Touchable from 'react-native-platform-touchable'; - -import { themes } from '../../../lib/constants'; -import I18n from '../../../i18n'; -import { CustomIcon } from '../../../containers/CustomIcon'; -import { TextInput } from '../../../containers/TextInput'; -import { useTheme } from '../../../theme'; -import { isIOS } from '../../../lib/methods/helpers'; -import sharedStyles from '../../Styles'; - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - alignItems: 'center', - flex: 1 - }, - searchBox: { - alignItems: 'center', - borderRadius: 10, - flexDirection: 'row', - fontSize: 17, - height: 36, - margin: 16, - marginVertical: 10, - paddingHorizontal: 10, - flex: 1 - }, - input: { - flex: 1, - fontSize: 17, - marginLeft: 8, - paddingTop: 0, - paddingBottom: 0, - ...sharedStyles.textRegular - }, - cancel: { - marginRight: 15 - }, - cancelText: { - ...sharedStyles.textRegular, - fontSize: 17 - } -}); - -interface ISearchBox extends TextInputProps { - value?: string; - hasCancel?: boolean; - onCancelPress?: () => void; - inputRef?: React.Ref<RNTextInput>; -} - -const CancelButton = ({ onCancelPress }: { onCancelPress?: () => void }) => { - const { theme } = useTheme(); - return ( - <Touchable onPress={onCancelPress} style={styles.cancel}> - <Text style={[styles.cancelText, { color: themes[theme].fontSecondaryInfo }]}>{I18n.t('Cancel')}</Text> - </Touchable> - ); -}; - -const SearchBox = ({ hasCancel, onCancelPress, inputRef, ...props }: ISearchBox): React.ReactElement => { - const { theme } = useTheme(); - return ( - <View style={[styles.container, { backgroundColor: isIOS ? themes[theme].surfaceNeutral : themes[theme].surfaceLight }]}> - <View style={[styles.searchBox, { backgroundColor: themes[theme].strokeExtraLight }]}> - <CustomIcon name='search' size={14} color={themes[theme].fontSecondaryInfo} /> - <TextInput - ref={inputRef} - autoCapitalize='none' - autoCorrect={false} - blurOnSubmit - clearButtonMode='while-editing' - placeholder={I18n.t('Search')} - returnKeyType='search' - style={styles.input} - underlineColorAndroid='transparent' - {...props} - /> - </View> - {hasCancel && onCancelPress ? <CancelButton onCancelPress={onCancelPress} /> : null} - </View> - ); -}; - -export default SearchBox; diff --git a/app/views/ShareListView/Header/index.tsx b/app/views/ShareListView/Header/index.tsx deleted file mode 100644 index e1feab435b..0000000000 --- a/app/views/ShareListView/Header/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; - -import Header from './Header'; -import { IShareListHeader } from './interface'; - -const ShareListHeader = React.memo(({ searching, initSearch, cancelSearch, onChangeSearchText, theme }: IShareListHeader) => { - const onSearchChangeText = (text: string) => { - onChangeSearchText(text.trim()); - }; - - return ( - <Header - theme={theme} - searching={searching} - initSearch={initSearch} - cancelSearch={cancelSearch} - onChangeSearchText={onSearchChangeText} - /> - ); -}); - -export default ShareListHeader; diff --git a/app/views/ShareListView/Header/interface.ts b/app/views/ShareListView/Header/interface.ts deleted file mode 100644 index 23c1057201..0000000000 --- a/app/views/ShareListView/Header/interface.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { TextInputProps } from 'react-native'; - -import { TSupportedThemes } from '../../../theme'; - -type RequiredOnChangeText = Required<Pick<TextInputProps, 'onChangeText'>>; - -export interface IShareListHeader { - searching: boolean; - onChangeSearchText: RequiredOnChangeText['onChangeText']; - theme: TSupportedThemes; - initSearch?: () => void; - cancelSearch?: () => void; -} - -export type IShareListHeaderIos = Required<IShareListHeader>; diff --git a/app/views/ShareListView/index.tsx b/app/views/ShareListView/index.tsx index 30dc719d63..976548af17 100644 --- a/app/views/ShareListView/index.tsx +++ b/app/views/ShareListView/index.tsx @@ -1,8 +1,7 @@ import React from 'react'; import { Dispatch } from 'redux'; import { StackNavigationProp } from '@react-navigation/stack'; -import { BackHandler, FlatList, Keyboard, ScrollView, Text, View } from 'react-native'; -import ShareExtension from 'rn-extensions-share'; +import { BackHandler, FlatList, Keyboard, Text, View } from 'react-native'; import * as FileSystem from 'expo-file-system'; import { connect } from 'react-redux'; import * as mime from 'react-native-mime-types'; @@ -16,21 +15,18 @@ import ServerItem from '../../containers/ServerItem'; import * as HeaderButton from '../../containers/HeaderButton'; import ActivityIndicator from '../../containers/ActivityIndicator'; import * as List from '../../containers/List'; +import SearchHeader from '../../containers/SearchHeader'; import { themes } from '../../lib/constants'; import { animateNextTransition } from '../../lib/methods/helpers/layoutAnimation'; import { TSupportedThemes, withTheme } from '../../theme'; import SafeAreaView from '../../containers/SafeAreaView'; import { sanitizeLikeString } from '../../lib/database/utils'; import styles from './styles'; -import ShareListHeader from './Header'; -import { IApplicationState, TServerModel, TSubscriptionModel } from '../../definitions'; +import { IApplicationState, RootEnum, TServerModel, TSubscriptionModel } from '../../definitions'; import { ShareInsideStackParamList } from '../../definitions/navigationTypes'; -import { getRoomAvatar, isAndroid, isIOS, askAndroidMediaPermissions } from '../../lib/methods/helpers'; - -interface IDataFromShare { - value: string; - type: string; -} +import { getRoomAvatar, isAndroid, isIOS } from '../../lib/methods/helpers'; +import { shareSetParams } from '../../actions/share'; +import { appStart } from '../../actions/app'; interface IFileToShare { filename: string; @@ -50,7 +46,6 @@ interface IState { text: string; loading: boolean; serverInfo: TServerModel; - needsPermission: boolean; } interface INavigationOption { @@ -59,9 +54,12 @@ interface INavigationOption { interface IShareListViewProps extends INavigationOption { server: string; + connecting: boolean; + isAuthenticated: boolean; token: string; userId: string; theme: TSupportedThemes; + shareExtensionParams: Record<string, any>; dispatch: Dispatch; } @@ -82,10 +80,9 @@ class ShareListView extends React.Component<IShareListViewProps, IState> { chats: [], serversCount: 0, attachments: [], - text: '', + text: props.shareExtensionParams.text, loading: true, - serverInfo: {} as TServerModel, - needsPermission: isAndroid || false + serverInfo: {} as TServerModel }; this.setHeader(); if (isAndroid) { @@ -99,64 +96,72 @@ class ShareListView extends React.Component<IShareListViewProps, IState> { } async componentDidMount() { - try { - const data = (await ShareExtension.data()) as IDataFromShare[]; - if (isAndroid) { - await this.askForPermission(data); + const { shareExtensionParams } = this.props; + const { mediaUris } = shareExtensionParams; + if (mediaUris) { + try { + const info = await Promise.all(mediaUris.split(',').map((uri: string) => FileSystem.getInfoAsync(uri, { size: true }))); + const attachments = info.map(file => { + if (!file.exists) { + return null; + } + + return { + filename: decodeURIComponent(file.uri.substring(file.uri.lastIndexOf('/') + 1)), + description: '', + size: file.size, + mime: mime.lookup(file.uri), + path: file.uri + }; + }) as IFileToShare[]; + this.setState({ + // text, + attachments + }); + } catch { + // Do nothing } - const info = await Promise.all( - data - .filter(item => item.type === 'media') - .map(file => FileSystem.getInfoAsync(this.uriToPath(file.value), { size: true })) - ); - const attachments = info.map(file => { - if (!file.exists) { - return null; - } - - return { - filename: decodeURIComponent(file.uri.substring(file.uri.lastIndexOf('/') + 1)), - description: '', - size: file.size, - mime: mime.lookup(file.uri), - path: file.uri - }; - }) as IFileToShare[]; - const text = data.filter(item => item.type === 'text').reduce((acc, item) => `${item.value}\n${acc}`, ''); - this.setState({ - text, - attachments - }); - } catch { - // Do nothing } this.getSubscriptions(); } - componentDidUpdate(previousProps: IShareListViewProps) { - const { server } = this.props; - if (previousProps.server !== server) { + componentDidUpdate(previousProps: IShareListViewProps, previousState: IState) { + const { searching } = this.state; + const { server, connecting, isAuthenticated } = this.props; + if ( + previousProps.server !== server || + (previousProps.connecting !== connecting && !connecting) || + (previousProps.isAuthenticated !== isAuthenticated && isAuthenticated) + ) { this.getSubscriptions(); } + if (previousProps.connecting !== connecting && connecting) { + this.setState({ chats: [], searchResults: [], searching: false, searchText: '' }); + } + if (previousState.searching !== searching) { + this.setHeader(); + } } shouldComponentUpdate(nextProps: IShareListViewProps, nextState: IState) { - const { searching, needsPermission } = this.state; + const { searching } = this.state; if (nextState.searching !== searching) { return true; } - if (nextState.needsPermission !== needsPermission) { - return true; - } - - const { server, userId } = this.props; + const { server, userId, isAuthenticated, connecting } = this.props; if (server !== nextProps.server) { return true; } if (userId !== nextProps.userId) { return true; } + if (isAuthenticated !== nextProps.isAuthenticated) { + return true; + } + if (connecting !== nextProps.connecting) { + return true; + } const { searchResults } = this.state; if (nextState.searching) { @@ -174,43 +179,41 @@ class ShareListView extends React.Component<IShareListViewProps, IState> { if (this.unsubscribeBlur) { this.unsubscribeBlur(); } + const { dispatch } = this.props; + dispatch(shareSetParams({})); } setHeader = () => { const { searching } = this.state; - const { navigation, theme } = this.props; + const { navigation } = this.props; - if (isIOS) { + if (searching) { navigation.setOptions({ - header: () => ( - <ShareListHeader - searching={searching} - initSearch={this.initSearch} - cancelSearch={this.cancelSearch} - onChangeSearchText={this.search} - theme={theme} - /> - ) + headerTitleAlign: 'left', + headerTitleContainerStyle: { flex: 1, marginHorizontal: 0, marginRight: 15, maxWidth: undefined }, + headerRightContainerStyle: { flexGrow: 0 }, + headerLeft: () => ( + <HeaderButton.Container left> + <HeaderButton.Item iconName='close' onPress={this.cancelSearch} /> + </HeaderButton.Container> + ), + headerTitle: () => <SearchHeader onSearchChangeText={this.search} />, + headerRight: () => null }); return; } navigation.setOptions({ - headerLeft: () => - searching ? ( - <HeaderButton.Container left> - <HeaderButton.Item title='cancel' iconName='close' onPress={this.cancelSearch} /> - </HeaderButton.Container> - ) : ( - <HeaderButton.CancelModal onPress={ShareExtension.close} testID='share-extension-close' /> - ), - headerTitle: () => <ShareListHeader searching={searching} onChangeSearchText={this.search} theme={theme} />, - headerRight: () => - searching ? null : ( - <HeaderButton.Container> - <HeaderButton.Item iconName='search' onPress={this.initSearch} /> - </HeaderButton.Container> - ) + headerTitleAlign: undefined, + headerTitleContainerStyle: undefined, + headerRightContainerStyle: undefined, + headerLeft: () => <HeaderButton.CancelModal onPress={this.closeShareExtension} testID='share-extension-close' />, + headerTitle: I18n.t('Send_to'), + headerRight: () => ( + <HeaderButton.Container> + <HeaderButton.Item iconName='search' onPress={this.initSearch} /> + </HeaderButton.Container> + ) }); }; @@ -265,11 +268,14 @@ class ShareListView extends React.Component<IShareListViewProps, IState> { }; getSubscriptions = async () => { - const { server } = this.props; - const serversDB = database.servers; + const { server, connecting, isAuthenticated } = this.props; + if (connecting || !isAuthenticated) { + return; + } if (server) { const chats = await this.query(); + const serversDB = database.servers; const serversCollection = serversDB.get('servers'); const serversCount = await serversCollection.query(Q.where('rooms_updated_at', Q.notEq(null))).fetchCount(); let serverInfo = {}; @@ -289,19 +295,6 @@ class ShareListView extends React.Component<IShareListViewProps, IState> { } }; - askForPermission = async (data: IDataFromShare[]) => { - const mediaIndex = data.findIndex(item => item.type === 'media'); - if (mediaIndex !== -1) { - const result = await askAndroidMediaPermissions(); - if (!result) { - this.setState({ needsPermission: true }); - return Promise.reject(); - } - } - this.setState({ needsPermission: false }); - return Promise.resolve(); - }; - uriToPath = (uri: string) => decodeURIComponent(isIOS ? uri.replace(/^file:\/\//, '') : uri); getRoomTitle = (item: TSubscriptionModel) => { @@ -349,6 +342,11 @@ class ShareListView extends React.Component<IShareListViewProps, IState> { return false; }; + closeShareExtension = () => { + const { dispatch } = this.props; + dispatch(appStart({ root: RootEnum.ROOT_INSIDE })); + }; + renderSectionHeader = (header: string) => { const { searching } = this.state; const { theme } = this.props; @@ -437,37 +435,19 @@ class ShareListView extends React.Component<IShareListViewProps, IState> { }; render = () => { - const { chats, loading, searchResults, searching, searchText, needsPermission } = this.state; + const { chats, loading, searchResults, searching, searchText } = this.state; const { theme } = this.props; if (loading) { return <ActivityIndicator />; } - if (needsPermission) { - return ( - <SafeAreaView> - <ScrollView - style={{ backgroundColor: themes[theme].surfaceRoom }} - contentContainerStyle={[styles.container, styles.centered, { backgroundColor: themes[theme].surfaceRoom }]}> - <Text style={[styles.permissionTitle, { color: themes[theme].fontTitlesLabels }]}> - {I18n.t('Read_External_Permission')} - </Text> - <Text style={[styles.permissionMessage, { color: themes[theme].fontDefault }]}> - {I18n.t('Read_External_Permission_Message')} - </Text> - </ScrollView> - </SafeAreaView> - ); - } - return ( - <SafeAreaView> + <SafeAreaView testID='share-list-view'> <FlatList data={searching ? searchResults : chats} keyExtractor={keyExtractor} style={[styles.flatlist, { backgroundColor: themes[theme].surfaceHover }]} - contentContainerStyle={{ backgroundColor: themes[theme].surfaceRoom }} renderItem={this.renderItem} getItemLayout={getItemLayout} ItemSeparatorComponent={List.Separator} @@ -482,10 +462,13 @@ class ShareListView extends React.Component<IShareListViewProps, IState> { }; } -const mapStateToProps = ({ share }: IApplicationState) => ({ - userId: share.user && (share.user.id as string), - token: share.user && (share.user.token as string), - server: share.server.server as string +const mapStateToProps = ({ login, server, share }: IApplicationState) => ({ + userId: login?.user?.id as string, + token: login?.user?.token as string, + isAuthenticated: login?.isAuthenticated, + server: server?.server, + connecting: server?.connecting, + shareExtensionParams: share?.params }); export default connect(mapStateToProps)(withTheme(ShareListView)); diff --git a/app/views/ShareView/Preview.tsx b/app/views/ShareView/Preview.tsx index 4440300443..3b7cb6a091 100644 --- a/app/views/ShareView/Preview.tsx +++ b/app/views/ShareView/Preview.tsx @@ -1,17 +1,14 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Video, ResizeMode } from 'expo-av'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { ScrollView, StyleSheet, Text } from 'react-native'; +import { ScrollView, StyleSheet, Text, useWindowDimensions, View } from 'react-native'; import prettyBytes from 'pretty-bytes'; import { useHeaderHeight } from '@react-navigation/elements'; import { CustomIcon, TIconsName } from '../../containers/CustomIcon'; import { ImageViewer, types } from '../../containers/ImageViewer'; -import { useDimensions } from '../../dimensions'; import sharedStyles from '../Styles'; import I18n from '../../i18n'; -import { isAndroid } from '../../lib/methods/helpers'; -import { allowPreview } from './utils'; import { THUMBS_HEIGHT } from './constants'; import { TSupportedThemes } from '../../theme'; import { themes } from '../../lib/constants'; @@ -49,8 +46,7 @@ interface IIconPreview { const IconPreview = React.memo(({ iconName, title, description, theme, width, height, danger }: IIconPreview) => ( <ScrollView style={{ backgroundColor: themes[theme].surfaceNeutral }} - contentContainerStyle={[styles.fileContainer, { width, height }]} - > + contentContainerStyle={[styles.fileContainer, { width, height }]}> <CustomIcon name={iconName} size={56} @@ -70,17 +66,24 @@ interface IPreview { const Preview = React.memo(({ item, theme, isShareExtension, length }: IPreview) => { const type = item?.mime; - const { width, height } = useDimensions(); + const { width, height } = useWindowDimensions(); const insets = useSafeAreaInsets(); const headerHeight = useHeaderHeight(); const thumbsHeight = length > 1 ? THUMBS_HEIGHT : 0; const calculatedHeight = height - insets.top - insets.bottom - MESSAGE_COMPOSER_HEIGHT - thumbsHeight - headerHeight; + const [wrapperDimensions, setWrapperDimensions] = useState<{ width?: number; height?: number }>({}); if (item?.canUpload) { - // Disable video preview on iOS to save memory - if (isAndroid && type?.match(/video/)) { + if (type?.match(/video/)) { return ( - <ScrollView style={{ height: calculatedHeight }}> + <View + style={{ flex: 1 }} + onLayout={ev => { + setWrapperDimensions({ + width: ev.nativeEvent.layout.width, + height: ev.nativeEvent.layout.height + }); + }}> <Video source={{ uri: item.path }} rate={1.0} @@ -88,29 +91,27 @@ const Preview = React.memo(({ item, theme, isShareExtension, length }: IPreview) isMuted={false} resizeMode={ResizeMode.CONTAIN} isLooping={false} - style={{ width, height: calculatedHeight }} + style={{ width: wrapperDimensions?.width, height: wrapperDimensions?.height }} useNativeControls /> - </ScrollView> + </View> ); } // Disallow preview of images too big in order to prevent memory issues on iOS share extension if (type?.match(/image/)) { - if (allowPreview(isShareExtension, item?.size)) { - return ( - <ImageViewer - uri={item.path} - imageComponentType={isShareExtension ? types.REACT_NATIVE_IMAGE : types.FAST_IMAGE} - width={width} - height={calculatedHeight} - /> - ); - } + return ( + <ImageViewer + uri={item.path} + imageComponentType={isShareExtension ? types.REACT_NATIVE_IMAGE : types.FAST_IMAGE} + width={width} + height={calculatedHeight} + /> + ); } return ( <IconPreview - iconName={type?.match(/image/) ? 'image' : 'attach'} + iconName={'attach'} title={item?.filename} description={prettyBytes(item?.size ?? 0)} theme={theme} diff --git a/app/views/ShareView/Thumbs.tsx b/app/views/ShareView/Thumbs.tsx index 320f547b69..6ff524d267 100644 --- a/app/views/ShareView/Thumbs.tsx +++ b/app/views/ShareView/Thumbs.tsx @@ -7,7 +7,6 @@ import { themes } from '../../lib/constants'; import { CustomIcon } from '../../containers/CustomIcon'; import { isIOS } from '../../lib/methods/helpers'; import { THUMBS_HEIGHT } from './constants'; -import { allowPreview } from './utils'; import { TSupportedThemes } from '../../theme'; import { IShareAttachment } from '../../definitions'; @@ -18,11 +17,6 @@ const styles = StyleSheet.create({ height: THUMBS_HEIGHT, paddingHorizontal: 8 }, - videoThumbIcon: { - position: 'absolute', - left: 0, - bottom: 0 - }, dangerIcon: { position: 'absolute', right: 16, @@ -76,40 +70,26 @@ interface IThumbs extends Omit<IThumb, 'item'> { attachments: IShareAttachment[]; } -const ThumbContent = React.memo(({ item, theme, isShareExtension }: IThumbContent) => { +const ThumbContent = React.memo(({ item, theme }: IThumbContent) => { const type = item?.mime; if (type?.match(/image/)) { - // Disallow preview of images too big in order to prevent memory issues on iOS share extension - if (allowPreview(isShareExtension, item?.size)) { - return <Image source={{ uri: item.path }} style={[styles.thumb, { borderColor: themes[theme].strokeLight }]} />; - } - return ( - <View style={[styles.thumb, { borderColor: themes[theme].strokeLight }]}> - <CustomIcon name='image' size={30} color={themes[theme].badgeBackgroundLevel2} /> - </View> - ); + return <Image source={{ uri: item.path }} style={[styles.thumb, { borderColor: themes[theme].strokeLight }]} />; } if (type?.match(/video/)) { - if (isIOS) { - return ( - <View style={[styles.thumb, { borderColor: themes[theme].strokeLight }]}> - <CustomIcon name='camera' size={30} color={themes[theme].badgeBackgroundLevel2} /> - </View> - ); - } - const { uri } = item; return ( - <> - <Image source={{ uri }} style={styles.thumb} /> - <CustomIcon name='camera-filled' size={20} color={themes[theme].fontWhite} style={styles.videoThumbIcon} /> - </> + <View style={[styles.thumb, { borderColor: themes[theme].strokeLight }]}> + <CustomIcon name='camera' size={30} color={themes[theme].badgeBackgroundLevel2} /> + </View> ); } - // Multiple files upload of files different than image/video is not implemented, so there's no thumb - return null; + return ( + <View style={[styles.thumb, { borderColor: themes[theme].strokeLight }]}> + <CustomIcon name='attach' size={30} color={themes[theme].badgeBackgroundLevel2} /> + </View> + ); }); const ThumbButton: typeof React.Component = isIOS ? TouchableOpacity : TouchableNativeFeedback; @@ -123,8 +103,7 @@ const Thumb = ({ item, theme, isShareExtension, onPress, onRemove }: IThumb) => style={[styles.removeButton, { backgroundColor: themes[theme].fontDefault, borderColor: themes[theme].surfaceHover }]} activeOpacity={1} rippleColor={themes[theme].surfaceNeutral} - onPress={() => onRemove(item)} - > + onPress={() => onRemove(item)}> <View style={[styles.removeView, { borderColor: themes[theme].surfaceHover }]}> <CustomIcon name='close' color={themes[theme].surfaceRoom} size={14} /> </View> diff --git a/app/views/ShareView/index.tsx b/app/views/ShareView/index.tsx index 770990b8ef..3d58e816ff 100644 --- a/app/views/ShareView/index.tsx +++ b/app/views/ShareView/index.tsx @@ -1,10 +1,10 @@ import React, { Component } from 'react'; import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack'; import { RouteProp } from '@react-navigation/native'; -import { Text, View } from 'react-native'; +import { Keyboard, Text, View } from 'react-native'; import { connect } from 'react-redux'; -import ShareExtension from 'rn-extensions-share'; import { Q } from '@nozbe/watermelondb'; +import { Dispatch } from 'redux'; import { IMessageComposerRef, MessageComposerContainer } from '../../containers/MessageComposer'; import { InsideStackParamList } from '../../stacks/types'; @@ -17,7 +17,6 @@ import { TSupportedThemes, withTheme } from '../../theme'; import { FormTextInput } from '../../containers/TextInput'; import SafeAreaView from '../../containers/SafeAreaView'; import { getUserSelector } from '../../selectors/login'; -import StatusBar from '../../containers/StatusBar'; import database from '../../lib/database'; import Thumbs from './Thumbs'; import Preview from './Preview'; @@ -28,6 +27,7 @@ import { IServer, IShareAttachment, IUser, + RootEnum, TMessageAction, TSubscriptionModel, TThreadModel @@ -35,6 +35,7 @@ import { import { sendFileMessage, sendMessage } from '../../lib/methods'; import { hasPermission, isAndroid, canUploadFile, isReadOnly, isBlocked } from '../../lib/methods/helpers'; import { RoomContext } from '../RoomView/context'; +import { appStart } from '../../actions/app'; interface IShareViewState { selected: IShareAttachment; @@ -62,6 +63,7 @@ interface IShareViewProps { server: string; FileUpload_MediaTypeWhiteList?: string; FileUpload_MaxFileSize?: number; + dispatch: Dispatch; } class ShareView extends Component<IShareViewProps, IShareViewState> { @@ -236,8 +238,10 @@ class ShareView extends Component<IShareViewProps, IShareViewState> { send = async () => { if (this.state.loading) return; + Keyboard.dismiss(); + const { attachments, room, text, thread, action, selected, selectedMessages } = this.state; - const { navigation, server, user } = this.props; + const { navigation, server, user, dispatch } = this.props; // update state await this.selectFile(selected); @@ -308,7 +312,7 @@ class ShareView extends Component<IShareViewProps, IShareViewState> { // if it's share extension this should close if (this.isShareExtension) { sendLoadingEvent({ visible: false }); - ShareExtension.close(); + dispatch(appStart({ root: RootEnum.ROOT_INSIDE })); } }; @@ -415,7 +419,7 @@ class ShareView extends Component<IShareViewProps, IShareViewState> { const { theme } = this.props; if (readOnly || isBlocked(room)) { return ( - <View style={[styles.container, styles.centered, { backgroundColor: themes[theme].surfaceRoom }]}> + <View style={[styles.container, styles.centered, { backgroundColor: themes[theme].surfaceHover }]}> <Text style={[styles.title, { color: themes[theme].fontTitlesLabels }]}> {isBlocked(room) ? I18n.t('This_room_is_blocked') : I18n.t('This_room_is_read_only')} </Text> @@ -423,8 +427,7 @@ class ShareView extends Component<IShareViewProps, IShareViewState> { ); } return ( - <SafeAreaView style={{ backgroundColor: themes[theme].backdropColor, flex: 1 }}> - <StatusBar barStyle='light-content' backgroundColor={themes[theme].surfaceDark} /> + <SafeAreaView style={{ backgroundColor: themes[theme].surfaceHover, flex: 1 }} testID='share-view'> {this.renderContent()} </SafeAreaView> ); @@ -433,7 +436,7 @@ class ShareView extends Component<IShareViewProps, IShareViewState> { const mapStateToProps = (state: IApplicationState) => ({ user: getUserSelector(state), - server: state.share.server.server || state.server.server, + server: state.server.server, FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList as string, FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize as number }); diff --git a/app/views/ShareView/utils.ts b/app/views/ShareView/utils.ts deleted file mode 100644 index bcc257dee3..0000000000 --- a/app/views/ShareView/utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { isAndroid } from '../../lib/methods/helpers'; - -// Limit preview to 3MB on iOS share extension -export const allowPreview = (isShareExtension: boolean, size: number): boolean => - isAndroid || !isShareExtension || size < 3000000; diff --git a/app/views/WithoutServersView.tsx b/app/views/WithoutServersView.tsx deleted file mode 100644 index 7a4989bef2..0000000000 --- a/app/views/WithoutServersView.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useLayoutEffect } from 'react'; -import { StyleSheet, Text, View } from 'react-native'; -import ShareExtension from 'rn-extensions-share'; -import { useNavigation } from '@react-navigation/native'; - -import * as HeaderButton from '../containers/HeaderButton'; -import I18n from '../i18n'; -import { useTheme } from '../theme'; -import sharedStyles from './Styles'; - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - padding: 15 - }, - title: { - fontSize: 18, - ...sharedStyles.textBold - }, - content: { - fontSize: 14, - ...sharedStyles.textRegular, - ...sharedStyles.textAlignCenter - } -}); - -const WithoutServerView = (): React.ReactElement => { - const navigation = useNavigation(); - const { colors } = useTheme(); - - useLayoutEffect(() => { - navigation.setOptions({ - title: 'Rocket.Chat', - headerLeft: () => <HeaderButton.CancelModal onPress={ShareExtension.close} testID='share-extension-close' /> - }); - }, [navigation]); - - return ( - <View style={[styles.container, { backgroundColor: colors.surfaceRoom }]}> - <Text style={[styles.title, { color: colors.fontTitlesLabels }]}>{I18n.t('Without_Servers')}</Text> - <Text style={[styles.content, { color: colors.fontTitlesLabels }]}> - {I18n.t('You_need_to_access_at_least_one_RocketChat_server_to_share_something')} - </Text> - </View> - ); -}; - -export default WithoutServerView; diff --git a/e2e/helpers/app.ts b/e2e/helpers/app.ts index 6ff9178aea..a7605a0d67 100644 --- a/e2e/helpers/app.ts +++ b/e2e/helpers/app.ts @@ -94,16 +94,20 @@ async function logout() { await expect(element(by.id('new-server-view'))).toBeVisible(); } -async function mockMessage(message: string, isThread = false) { +async function checkMessage(message: string) { const deviceType = device.getPlatform(); const { textMatcher } = platformTypes[deviceType]; - const input = isThread ? 'message-composer-input-thread' : 'message-composer-input'; - await element(by.id(input)).typeText(message); - await element(by.id('message-composer-send')).tap(); await waitFor(element(by[textMatcher](message))) .toExist() .withTimeout(60000); await element(by[textMatcher](message)).atIndex(0).tap(); +} + +async function mockMessage(message: string, isThread = false) { + const input = isThread ? 'message-composer-input-thread' : 'message-composer-input'; + await element(by.id(input)).typeText(message); + await element(by.id('message-composer-send')).tap(); + await checkMessage(message); return message; } @@ -267,6 +271,7 @@ export { navigateToRegister, login, logout, + checkMessage, mockMessage, tapBack, sleep, diff --git a/e2e/tests/assorted/01-e2eencryption.spec.ts b/e2e/tests/assorted/01-e2eencryption.spec.ts index b4177d844b..369a3cea6d 100644 --- a/e2e/tests/assorted/01-e2eencryption.spec.ts +++ b/e2e/tests/assorted/01-e2eencryption.spec.ts @@ -412,7 +412,7 @@ describe('E2E Encryption', () => { await waitFor(element(by.id('rooms-list-header-servers-list'))) .toBeVisible() .withTimeout(5000); - await element(by.id(`rooms-list-header-server-${data.server}`)).tap(); + await element(by.id(`server-item-${data.server}`)).tap(); await waitFor(element(by.id('rooms-list-view'))) .toBeVisible() .withTimeout(10000); diff --git a/e2e/tests/assorted/07-changeserver.spec.ts b/e2e/tests/assorted/07-changeserver.spec.ts index 3fb0d11b64..6371186962 100644 --- a/e2e/tests/assorted/07-changeserver.spec.ts +++ b/e2e/tests/assorted/07-changeserver.spec.ts @@ -64,7 +64,7 @@ describe('Change server', () => { await waitFor(element(by.id('rooms-list-header-servers-list'))) .toBeVisible() .withTimeout(5000); - await element(by.id(`rooms-list-header-server-${data.alternateServer}`)).tap(); + await element(by.id(`server-item-${data.alternateServer}`)).tap(); await waitFor(element(by.id('workspace-view'))) .toBeVisible() .withTimeout(60000); @@ -76,8 +76,11 @@ describe('Change server', () => { // Register new user const randomUser = data.randomUser(); await element(by.id('register-view-name')).replaceText(randomUser.name); + await element(by.id('register-view-name')).tapReturnKey(); await element(by.id('register-view-username')).replaceText(randomUser.username); + await element(by.id('register-view-username')).tapReturnKey(); await element(by.id('register-view-email')).replaceText(randomUser.email); + await element(by.id('register-view-email')).tapReturnKey(); await element(by.id('register-view-password')).replaceText(randomUser.password); await element(by.id('register-view-password')).tapReturnKey(); await expectValidRegisterOrRetry(device.getPlatform()); @@ -98,7 +101,7 @@ describe('Change server', () => { await waitFor(element(by.id('rooms-list-header-servers-list'))) .toBeVisible() .withTimeout(5000); - await element(by.id(`rooms-list-header-server-${data.server}`)).tap(); + await element(by.id(`server-item-${data.server}`)).tap(); await waitFor(element(by.id('rooms-list-view'))) .toBeVisible() .withTimeout(10000); diff --git a/e2e/tests/assorted/10-deleteserver.spec.ts b/e2e/tests/assorted/10-deleteserver.spec.ts index 49f020db37..0b3295219b 100644 --- a/e2e/tests/assorted/10-deleteserver.spec.ts +++ b/e2e/tests/assorted/10-deleteserver.spec.ts @@ -76,13 +76,13 @@ describe('Delete server', () => { await waitFor(element(by.id('rooms-list-header-servers-list'))) .toBeVisible() .withTimeout(5000); - await element(by.id(`rooms-list-header-server-${data.server}`)).longPress(1500); + await element(by.id(`server-item-${data.server}`)).longPress(1500); await element(by[textMatcher]('Delete').and(by.type(alertButtonType))).tap(); await element(by.id('rooms-list-header-servers-list-button')).tap(); await waitFor(element(by.id('rooms-list-header-servers-list'))) .toBeVisible() .withTimeout(5000); - await waitFor(element(by.id(`rooms-list-header-server-${data.server}`))) + await waitFor(element(by.id(`server-item-${data.server}`))) .toBeNotVisible() .withTimeout(10000); }); diff --git a/e2e/tests/assorted/11-deeplinking.spec.ts b/e2e/tests/assorted/11-deeplinking.spec.ts index 5a5c78668b..21b3249d60 100644 --- a/e2e/tests/assorted/11-deeplinking.spec.ts +++ b/e2e/tests/assorted/11-deeplinking.spec.ts @@ -8,7 +8,10 @@ import { navigateToRegister, platformTypes, TTextMatcher, - expectValidRegisterOrRetry + expectValidRegisterOrRetry, + navigateToRoom, + checkMessage, + sleep } from '../../helpers/app'; import { IDeleteCreateUser, @@ -41,6 +44,8 @@ describe('Deep linking', () => { const deleteUsersAfterAll: IDeleteCreateUser[] = []; + const randomUserAlternateServer = data.randomUser(); + beforeAll(async () => { const user = await createRandomUser(); ({ _id: rid, name: room } = await createRandomRoom(user, 'p')); @@ -58,6 +63,25 @@ describe('Deep linking', () => { await deleteCreatedUsers(deleteUsersAfterAll); }); + const authAndNavigate = async () => { + await device.launchApp({ + permissions: { notifications: 'YES' }, + newInstance: true, + url: getDeepLink(DEEPLINK_METHODS.AUTH, data.server, `userId=${userId}${amp}token=${authToken}${amp}path=group/${room}`) + }); + await waitFor(element(by.id(`room-view-title-${room}`))) + .toExist() + .withTimeout(30000); + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + await checkServer(data.server); + await waitFor(element(by.id(`rooms-list-view-item-${room}`))) + .toExist() + .withTimeout(2000); + }; + describe('Authentication', () => { it('should run a deep link to an invalid account and raise error', async () => { await device.launchApp({ @@ -70,25 +94,6 @@ describe('Deep linking', () => { .withTimeout(30000); // TODO: we need to improve this message }); - const authAndNavigate = async () => { - await device.launchApp({ - permissions: { notifications: 'YES' }, - newInstance: true, - url: getDeepLink(DEEPLINK_METHODS.AUTH, data.server, `userId=${userId}${amp}token=${authToken}${amp}path=group/${room}`) - }); - await waitFor(element(by.id(`room-view-title-${room}`))) - .toExist() - .withTimeout(30000); - await tapBack(); - await waitFor(element(by.id('rooms-list-view'))) - .toBeVisible() - .withTimeout(10000); - await checkServer(data.server); - await waitFor(element(by.id(`rooms-list-view-item-${room}`))) - .toExist() - .withTimeout(2000); - }; - it('should authenticate and navigate', async () => { await authAndNavigate(); }); @@ -96,17 +101,16 @@ describe('Deep linking', () => { it('should authenticate while logged in another server', async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToRegister(data.alternateServer); - const randomUser = data.randomUser(); - await element(by.id('register-view-name')).replaceText(randomUser.name); + await element(by.id('register-view-name')).replaceText(randomUserAlternateServer.name); await element(by.id('register-view-name')).tapReturnKey(); - await element(by.id('register-view-username')).replaceText(randomUser.username); + await element(by.id('register-view-username')).replaceText(randomUserAlternateServer.username); await element(by.id('register-view-username')).tapReturnKey(); - await element(by.id('register-view-email')).replaceText(randomUser.email); + await element(by.id('register-view-email')).replaceText(randomUserAlternateServer.email); await element(by.id('register-view-email')).tapReturnKey(); - await element(by.id('register-view-password')).replaceText(randomUser.password); + await element(by.id('register-view-password')).replaceText(randomUserAlternateServer.password); await element(by.id('register-view-password')).tapReturnKey(); await expectValidRegisterOrRetry(device.getPlatform()); - deleteUsersAfterAll.push({ server: data.alternateServer, username: randomUser.username }); + deleteUsersAfterAll.push({ server: data.alternateServer, username: randomUserAlternateServer.username }); await authAndNavigate(); }); @@ -220,7 +224,7 @@ describe('Deep linking', () => { await waitFor(element(by.id('rooms-list-header-servers-list'))) .toBeVisible() .withTimeout(5000); - await element(by.id(`rooms-list-header-server-${data.alternateServer}`)).tap(); + await element(by.id(`server-item-${data.alternateServer}`)).tap(); await checkServer(data.alternateServer); await device.launchApp({ @@ -246,4 +250,87 @@ describe('Deep linking', () => { }); }); }); + + describe('Share extension', () => { + const shareTextMessage = async (message: string) => { + await waitFor(element(by.id(`share-extension-item-${room}`))) + .toBeVisible() + .withTimeout(30000); + await element(by.id(`share-extension-item-${room}`)).tap(); + await waitFor(element(by.id('share-view'))) + .toBeVisible() + .withTimeout(30000); + await waitFor(element(by.text('Send'))) + .toBeVisible() + .withTimeout(30000); + await element(by.text('Send')).tap(); + await navigateToRoom(room); + await checkMessage(message); + }; + + it('should share text', async () => { + const message = random(); + await device.launchApp({ + permissions: { notifications: 'YES' }, + newInstance: true, + url: `rocketchat://shareextension?text=${message}` + }); + await waitFor(element(by.id('share-list-view'))) + .toBeVisible() + .withTimeout(30000); + await shareTextMessage(message); + }); + + it('should change server and share text', async () => { + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + await element(by.id('rooms-list-header-servers-list-button')).tap(); + await waitFor(element(by.id('rooms-list-header-servers-list'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id(`server-item-${data.alternateServer}`)).tap(); + await checkServer(data.alternateServer); + + // share + const message = random(); + await device.launchApp({ + permissions: { notifications: 'YES' }, + newInstance: true, + url: `rocketchat://shareextension?text=${message}` + }); + await waitFor(element(by.id('share-list-view'))) + .toBeVisible() + .withTimeout(30000); + await sleep(300); + await waitFor(element(by.id(`server-item-${data.alternateServer}`))) + .toBeVisible() + .withTimeout(2000); + await element(by.id(`server-item-${data.alternateServer}`)).tap(); + await waitFor(element(by.id('select-server-view'))) + .toBeVisible() + .withTimeout(30000); + await element(by.id(`server-item-${data.server}`)).tap(); + await waitFor(element(by.id('share-list-view'))) + .toBeVisible() + .withTimeout(30000); + await waitFor(element(by.id(`server-item-${data.server}`))) + .toBeVisible() + .withTimeout(2000); + + await shareTextMessage(message); + }); + + it('should open share without being logged in and go to onboarding', async () => { + await device.launchApp({ + permissions: { notifications: 'YES' }, + delete: true, + url: `rocketchat://shareextension?text=whatever` + }); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(30000); + }); + }); }); diff --git a/index.js b/index.js index 74f5332c5c..3007724045 100644 --- a/index.js +++ b/index.js @@ -25,7 +25,6 @@ if (!isFDroidBuild && isAndroid) { } AppRegistry.registerComponent(appName, () => require('./app/index').default); -AppRegistry.registerComponent(shareName, () => require('./app/share').default); // For storybook, comment everything above and uncomment below // import 'react-native-gesture-handler'; diff --git a/ios/Podfile b/ios/Podfile index 9e5b6a17fe..eb8c9b42f5 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -41,7 +41,6 @@ abstract_target 'defaults' do target 'RocketChatRN' # Experimental app target 'Rocket.Chat' # Official app - target 'ShareRocketChatRN' target 'NotificationService' end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 18d448c898..fe95887abb 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1229,8 +1229,6 @@ PODS: - React-perflogger (= 0.73.6) - ReactNativeUiLib (3.0.4): - React - - rn-extensions-share (2.4.1): - - React - RNBootSplash (4.6.0): - React-Core - RNCAsyncStorage (1.22.3): @@ -1396,7 +1394,6 @@ DEPENDENCIES: - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - ReactNativeUiLib (from `../node_modules/react-native-ui-lib`) - - rn-extensions-share (from `../node_modules/rn-extensions-share`) - RNBootSplash (from `../node_modules/react-native-bootsplash`) - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)" @@ -1600,8 +1597,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" ReactNativeUiLib: :path: "../node_modules/react-native-ui-lib" - rn-extensions-share: - :path: "../node_modules/rn-extensions-share" RNBootSplash: :path: "../node_modules/react-native-bootsplash" RNCAsyncStorage: @@ -1748,7 +1743,6 @@ SPEC CHECKSUMS: React-utils: d16c1d2251c088ad817996621947d0ac8167b46c ReactCommon: 2aa35648354bd4c4665b9a5084a7d37097b89c10 ReactNativeUiLib: 33521c0747ea376d292b62b6415e0f1d75bd3c10 - rn-extensions-share: 5fd84a80e6594706f0dfa1884f2d6d591b382cf5 RNBootSplash: 91e0c16bfc96703cb5b0562785b9a8cbfeb298fe RNCAsyncStorage: 10591b9e0a91eaffee14e69b3721009759235125 RNCClipboard: 60fed4b71560d7bfe40e9d35dea9762b024da86d @@ -1780,6 +1774,6 @@ SPEC CHECKSUMS: Yoga: d17d2cc8105eed528474683b42e2ea310e1daf61 ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 -PODFILE CHECKSUM: 4037481efaa1c9c86740a10ac1fa6f1f1845721c +PODFILE CHECKSUM: 23b05e7d7ba785e3452040d94d4b7ab4a920ee60 COCOAPODS: 1.14.3 diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index efe4a8f292..bf31a005a5 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ 0C6E2DE448364EA896869ADF /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B37C79D9BD0742CE936B6982 /* libc++.tbd */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 1C77205B81359264AD42BBF3 /* libPods-defaults-RocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DC1C73C9C5E285F42018A7B /* libPods-defaults-RocketChatRN.a */; }; 1E01C81C2511208400FEF824 /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E01C81B2511208400FEF824 /* URL+Extensions.swift */; }; 1E01C8212511301400FEF824 /* PushResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E01C8202511301400FEF824 /* PushResponse.swift */; }; 1E01C8252511303100FEF824 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E01C8242511303100FEF824 /* Notification.swift */; }; @@ -25,9 +24,7 @@ 1E06561B2B7E91FB0081B01F /* ErrorActionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E06561A2B7E91FB0081B01F /* ErrorActionHandler.swift */; }; 1E06561D2B7E9C1C0081B01F /* MessageActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E06561C2B7E9C1C0081B01F /* MessageActionView.swift */; }; 1E068CFE24FD2DC700A0FFC1 /* AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E068CFD24FD2DC700A0FFC1 /* AppGroup.swift */; }; - 1E068CFF24FD2DC700A0FFC1 /* AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E068CFD24FD2DC700A0FFC1 /* AppGroup.swift */; }; 1E068D0124FD2E0500A0FFC1 /* AppGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E068D0024FD2E0500A0FFC1 /* AppGroup.m */; }; - 1E068D0224FD2E0500A0FFC1 /* AppGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E068D0024FD2E0500A0FFC1 /* AppGroup.m */; }; 1E1C2F80250FCB69005DCE7D /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E1C2F7F250FCB69005DCE7D /* Database.swift */; }; 1E1EA80A2326CD2200E22452 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1EA8092326CD2200E22452 /* AVFoundation.framework */; }; 1E1EA80C2326CD2800E22452 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1EA80B2326CD2800E22452 /* AudioToolbox.framework */; }; @@ -38,7 +35,6 @@ 1E1EA8162326CD4500E22452 /* VideoToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1EA8152326CD4500E22452 /* VideoToolbox.framework */; }; 1E1EA8182326CD4B00E22452 /* libc.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1EA8172326CD4B00E22452 /* libc.tbd */; }; 1E1EA81A2326CD5100E22452 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1EA8192326CD5100E22452 /* libsqlite3.tbd */; }; - 1E25743422CBA2CF005A877F /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */; }; 1E29A2CC2B5857F50093C03C /* RoomListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2CB2B5857F50093C03C /* RoomListView.swift */; }; 1E29A2D02B58582F0093C03C /* RoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2CF2B58582F0093C03C /* RoomView.swift */; }; 1E29A2EF2B585B070093C03C /* RocketChatClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E29A2D22B585B070093C03C /* RocketChatClient.swift */; }; @@ -153,9 +149,7 @@ 1EB8EF722510F1EE00F352B7 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EB8EF712510F1EE00F352B7 /* Storage.swift */; }; 1EC687BA2BA0FF0D00C7BAAD /* MessageInfoMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EC687B82BA0FECC00C7BAAD /* MessageInfoMapper.swift */; }; 1EC687BB2BA0FF0D00C7BAAD /* MessageInfoMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EC687B82BA0FECC00C7BAAD /* MessageInfoMapper.swift */; }; - 1EC6ACB722CB9FC300A41C61 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1EC6ACB522CB9FC300A41C61 /* MainInterface.storyboard */; }; 1EC6ACBB22CB9FC300A41C61 /* ShareRocketChatRN.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 1EC6ACB022CB9FC300A41C61 /* ShareRocketChatRN.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 1EC6ACF622CBA01500A41C61 /* ShareRocketChatRN.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EC6ACF522CBA01500A41C61 /* ShareRocketChatRN.m */; }; 1ED00BB12513E04400A1331F /* ReplyNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED00BB02513E04400A1331F /* ReplyNotification.swift */; }; 1ED033AE2B55B1CC004F4930 /* Default.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033AC2B55B1CC004F4930 /* Default.xcdatamodeld */; }; 1ED033B02B55B25A004F4930 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED033AF2B55B25A004F4930 /* Database.swift */; }; @@ -265,7 +259,6 @@ 1ED1ECE72B8699ED00F6620C /* Rocket.Chat Experimental Watch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 1ED1ECDD2B86997F00F6620C /* Rocket.Chat Experimental Watch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 1ED1ECEA2B869A4A00F6620C /* Official.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A14FCF3257FEB59005BDCD4 /* Official.xcassets */; }; 1ED1ECEC2B869B1300F6620C /* Experimental.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A14FCEC257FEB3A005BDCD4 /* Experimental.xcassets */; }; - 1ED59D4C22CBA77D00C54289 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 1ED59D4B22CBA77D00C54289 /* GoogleService-Info.plist */; }; 1EDB30F22B5B453A00532C7E /* LoggedInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EDB30F12B5B453A00532C7E /* LoggedInView.swift */; }; 1EDFD0FA2B589B8F002FEE5F /* MessagesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EDFD0F92B589B8F002FEE5F /* MessagesLoader.swift */; }; 1EDFD1062B58A66E002FEE5F /* CancelBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EDFD1052B58A66E002FEE5F /* CancelBag.swift */; }; @@ -284,9 +277,10 @@ 1EFEB5982493B6640072EDC0 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFEB5972493B6640072EDC0 /* NotificationService.swift */; }; 1EFEB59C2493B6640072EDC0 /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 1EFEB5952493B6640072EDC0 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 24A2AEF2383D44B586D31C01 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 06BB44DD4855498082A744AD /* libz.tbd */; }; + 39E74E20D9E02DA933B049C1 /* libPods-defaults-NotificationService.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EDAE7DFC486C8E0F5331BC7 /* libPods-defaults-NotificationService.a */; }; 4C4C8603EF082F0A33A95522 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D5C142B655F8EFD006792C /* ExpoModulesProvider.swift */; }; + 624EBD0BDE698092CB04C2FA /* libPods-defaults-RocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F38DDC9B729E9C3C3883E1CF /* libPods-defaults-RocketChatRN.a */; }; 65AD38372BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; - 65AD38382BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; 65AD38392BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; 65AD383A2BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; 65AD383B2BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; @@ -294,11 +288,13 @@ 65B9A71A2AFC24190088956F /* ringtone.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 65B9A7192AFC24190088956F /* ringtone.mp3 */; }; 65B9A71B2AFC24190088956F /* ringtone.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 65B9A7192AFC24190088956F /* ringtone.mp3 */; }; 7A006F14229C83B600803143 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7A006F13229C83B600803143 /* GoogleService-Info.plist */; }; + 7A0129D42C6E8EC800F84A97 /* ShareRocketChatRN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0129D22C6E8B5900F84A97 /* ShareRocketChatRN.swift */; }; + 7A0129D62C6E8F0700F84A97 /* ShareRocketChatRN.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = 1EC6AD6022CBA20C00A41C61 /* ShareRocketChatRN.entitlements */; }; + 7A0129EA2C6E921600F84A97 /* MainInterface.storyboard in Sources */ = {isa = PBXBuildFile; fileRef = 1EC6ACB522CB9FC300A41C61 /* MainInterface.storyboard */; }; 7A0D62D2242AB187006D5C06 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7A0D62D1242AB187006D5C06 /* LaunchScreen.storyboard */; }; 7A14FCED257FEB3A005BDCD4 /* Experimental.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A14FCEC257FEB3A005BDCD4 /* Experimental.xcassets */; }; 7A14FCF4257FEB59005BDCD4 /* Official.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A14FCF3257FEB59005BDCD4 /* Official.xcassets */; }; 7A610CD227ECE38100B8ABDD /* custom.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7A610CD127ECE38100B8ABDD /* custom.ttf */; }; - 7A610CD327ECE38100B8ABDD /* custom.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7A610CD127ECE38100B8ABDD /* custom.ttf */; }; 7A610CD427ECE38100B8ABDD /* custom.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7A610CD127ECE38100B8ABDD /* custom.ttf */; }; 7A610CD527ECE38100B8ABDD /* custom.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7A610CD127ECE38100B8ABDD /* custom.ttf */; }; 7A8B30762BCD9D3F00146A40 /* SSLPinning.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7A8B30752BCD9D3F00146A40 /* SSLPinning.mm */; }; @@ -352,15 +348,11 @@ 7AAB3E4A257E6A6E00707CF6 /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 1EFEB5952493B6640072EDC0 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7ACD4897222860DE00442C55 /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */; }; 7AE10C0628A59530003593CB /* Inter.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7AE10C0528A59530003593CB /* Inter.ttf */; }; - 7AE10C0728A59530003593CB /* Inter.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7AE10C0528A59530003593CB /* Inter.ttf */; }; 7AE10C0828A59530003593CB /* Inter.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7AE10C0528A59530003593CB /* Inter.ttf */; }; 85160EB6C143E0493FE5F014 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194D9A8897F4A486C2C6F89A /* ExpoModulesProvider.swift */; }; - 864F9F60CC1C1301AB392586 /* libPods-defaults-NotificationService.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C3A803D7C0F33FAC94992D06 /* libPods-defaults-NotificationService.a */; }; - 945283F4E4DE01B83BB22420 /* libPods-defaults-Rocket.Chat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AF27585043417AE1F4BFF5F9 /* libPods-defaults-Rocket.Chat.a */; }; BC404914E86821389EEB543D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391C4F7AA7023CD41EEBD106 /* ExpoModulesProvider.swift */; }; - C2DB3AB18736E4829C709762 /* libPods-defaults-ShareRocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B8B55F1CCE1B454F23C46EC4 /* libPods-defaults-ShareRocketChatRN.a */; }; - D94D81FB9E10756FAA03F203 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016747EF3B9FED8DE2C9DA14 /* ExpoModulesProvider.swift */; }; DD2BA30A89E64F189C2C24AC /* libWatermelonDB.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BA7E862283664608B3894E34 /* libWatermelonDB.a */; }; + E02D91DE2C11D3BD9AC34663 /* libPods-defaults-Rocket.Chat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F08B2D270E82A48D344CB0F5 /* libPods-defaults-Rocket.Chat.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -459,14 +451,14 @@ /* Begin PBXFileReference section */ 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = "<group>"; }; - 016747EF3B9FED8DE2C9DA14 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-ShareRocketChatRN/ExpoModulesProvider.swift"; sourceTree = "<group>"; }; 06BB44DD4855498082A744AD /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + 0E80084FB76F2BA00A43C5E0 /* Pods-defaults-RocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.debug.xcconfig"; sourceTree = "<group>"; }; + 0FF30665E726105A87EE37A8 /* Pods-defaults-RocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.release.xcconfig"; sourceTree = "<group>"; }; 13B07F961A680F5B00A75B9A /* Rocket.Chat Experimental.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Rocket.Chat Experimental.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = RocketChatRN/AppDelegate.h; sourceTree = "<group>"; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = RocketChatRN/Images.xcassets; sourceTree = "<group>"; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = RocketChatRN/Info.plist; sourceTree = "<group>"; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = RocketChatRN/main.m; sourceTree = "<group>"; }; - 1610999420E746E4299F1E30 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-ShareRocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN.release.xcconfig"; sourceTree = "<group>"; }; 194D9A8897F4A486C2C6F89A /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-NotificationService/ExpoModulesProvider.swift"; sourceTree = "<group>"; }; 1E01C81B2511208400FEF824 /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = "<group>"; }; 1E01C8202511301400FEF824 /* PushResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushResponse.swift; sourceTree = "<group>"; }; @@ -572,7 +564,6 @@ 1EC6ACB022CB9FC300A41C61 /* ShareRocketChatRN.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareRocketChatRN.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 1EC6ACB622CB9FC300A41C61 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; }; 1EC6ACB822CB9FC300A41C61 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; - 1EC6ACF522CBA01500A41C61 /* ShareRocketChatRN.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShareRocketChatRN.m; sourceTree = "<group>"; }; 1EC6AD6022CBA20C00A41C61 /* ShareRocketChatRN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareRocketChatRN.entitlements; sourceTree = "<group>"; }; 1ED00BB02513E04400A1331F /* ReplyNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyNotification.swift; sourceTree = "<group>"; }; 1ED033AD2B55B1CC004F4930 /* Default.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Default.xcdatamodel; sourceTree = "<group>"; }; @@ -598,7 +589,6 @@ 1ED038C92B50A58400C007D4 /* ServersLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServersLoader.swift; sourceTree = "<group>"; }; 1ED1EC882B867E2400F6620C /* ExtensionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionDelegate.swift; sourceTree = "<group>"; }; 1ED1ECDD2B86997F00F6620C /* Rocket.Chat Experimental Watch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Rocket.Chat Experimental Watch.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 1ED59D4B22CBA77D00C54289 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = SOURCE_ROOT; }; 1EDB30F12B5B453A00532C7E /* LoggedInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedInView.swift; sourceTree = "<group>"; }; 1EDFD0F92B589B8F002FEE5F /* MessagesLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesLoader.swift; sourceTree = "<group>"; }; 1EDFD1052B58A66E002FEE5F /* CancelBag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelBag.swift; sourceTree = "<group>"; }; @@ -613,15 +603,14 @@ 1EFEB5972493B6640072EDC0 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; }; 1EFEB5992493B6640072EDC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 1EFEB5A12493B67D0072EDC0 /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = "<group>"; }; - 2CF5E810829371C4A8189771 /* Pods-defaults-RocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.release.xcconfig"; sourceTree = "<group>"; }; 391C4F7AA7023CD41EEBD106 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-Rocket.Chat/ExpoModulesProvider.swift"; sourceTree = "<group>"; }; + 3A4ED61C8ED0D728A146E398 /* Pods-defaults-Rocket.Chat.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.debug.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.debug.xcconfig"; sourceTree = "<group>"; }; 45D5C142B655F8EFD006792C /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-RocketChatRN/ExpoModulesProvider.swift"; sourceTree = "<group>"; }; - 50B2AC15B2AAC327B74A8BBB /* Pods-defaults-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.debug.xcconfig"; sourceTree = "<group>"; }; - 5DC1C73C9C5E285F42018A7B /* libPods-defaults-RocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-RocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 60B2A6A31FC4588700BD58E5 /* RocketChatRN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = RocketChatRN.entitlements; path = RocketChatRN/RocketChatRN.entitlements; sourceTree = "<group>"; }; 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; }; 65B9A7192AFC24190088956F /* ringtone.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ringtone.mp3; sourceTree = "<group>"; }; 7A006F13229C83B600803143 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; }; + 7A0129D22C6E8B5900F84A97 /* ShareRocketChatRN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareRocketChatRN.swift; sourceTree = "<group>"; }; 7A0D62D1242AB187006D5C06 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; }; 7A14FCEC257FEB3A005BDCD4 /* Experimental.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Experimental.xcassets; sourceTree = "<group>"; }; 7A14FCF3257FEB59005BDCD4 /* Official.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Official.xcassets; sourceTree = "<group>"; }; @@ -633,16 +622,14 @@ 7AAB3E52257E6A6E00707CF6 /* Rocket.Chat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rocket.Chat.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; 7AE10C0528A59530003593CB /* Inter.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Inter.ttf; sourceTree = "<group>"; }; - 9F795E0928F3C7FADAD6BB77 /* Pods-defaults-Rocket.Chat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.release.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.release.xcconfig"; sourceTree = "<group>"; }; - A4CADE732CC8A42A8B30EA03 /* Pods-defaults-RocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.debug.xcconfig"; sourceTree = "<group>"; }; - AF27585043417AE1F4BFF5F9 /* libPods-defaults-Rocket.Chat.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-Rocket.Chat.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 7B2AF8CE5DAA2B9602655ECA /* Pods-defaults-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.debug.xcconfig"; sourceTree = "<group>"; }; + 8F262B0EDDE35FABA5DAC31A /* Pods-defaults-Rocket.Chat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.release.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.release.xcconfig"; sourceTree = "<group>"; }; + 9EDAE7DFC486C8E0F5331BC7 /* libPods-defaults-NotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-NotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; }; B37C79D9BD0742CE936B6982 /* libc++.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; - B8B55F1CCE1B454F23C46EC4 /* libPods-defaults-ShareRocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-ShareRocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - B99AD0CC4154722C23BACE13 /* Pods-defaults-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.release.xcconfig"; sourceTree = "<group>"; }; BA7E862283664608B3894E34 /* libWatermelonDB.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libWatermelonDB.a; sourceTree = "<group>"; }; - BD0E5505861320CE3592D4BF /* Pods-defaults-Rocket.Chat.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.debug.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.debug.xcconfig"; sourceTree = "<group>"; }; - C3A803D7C0F33FAC94992D06 /* libPods-defaults-NotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-NotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - C8FC44D9D09F4A537E3407C2 /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-ShareRocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN.debug.xcconfig"; sourceTree = "<group>"; }; + C1FFA2C4EDACA0718BB71F1C /* Pods-defaults-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.release.xcconfig"; sourceTree = "<group>"; }; + F08B2D270E82A48D344CB0F5 /* libPods-defaults-Rocket.Chat.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-Rocket.Chat.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + F38DDC9B729E9C3C3883E1CF /* libPods-defaults-RocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-RocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -663,16 +650,7 @@ 7ACD4897222860DE00442C55 /* JavaScriptCore.framework in Frameworks */, 24A2AEF2383D44B586D31C01 /* libz.tbd in Frameworks */, DD2BA30A89E64F189C2C24AC /* libWatermelonDB.a in Frameworks */, - 1C77205B81359264AD42BBF3 /* libPods-defaults-RocketChatRN.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 1EC6ACAD22CB9FC300A41C61 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 1E25743422CBA2CF005A877F /* JavaScriptCore.framework in Frameworks */, - C2DB3AB18736E4829C709762 /* libPods-defaults-ShareRocketChatRN.a in Frameworks */, + 624EBD0BDE698092CB04C2FA /* libPods-defaults-RocketChatRN.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -694,7 +672,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 864F9F60CC1C1301AB392586 /* libPods-defaults-NotificationService.a in Frameworks */, + 39E74E20D9E02DA933B049C1 /* libPods-defaults-NotificationService.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -715,7 +693,7 @@ 7AAB3E3D257E6A6E00707CF6 /* JavaScriptCore.framework in Frameworks */, 7AAB3E3E257E6A6E00707CF6 /* libz.tbd in Frameworks */, 7AAB3E3F257E6A6E00707CF6 /* libWatermelonDB.a in Frameworks */, - 945283F4E4DE01B83BB22420 /* libPods-defaults-Rocket.Chat.a in Frameworks */, + E02D91DE2C11D3BD9AC34663 /* libPods-defaults-Rocket.Chat.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -986,11 +964,10 @@ 1EC6ACB122CB9FC300A41C61 /* ShareRocketChatRN */ = { isa = PBXGroup; children = ( - 1ED59D4B22CBA77D00C54289 /* GoogleService-Info.plist */, 1EC6AD6022CBA20C00A41C61 /* ShareRocketChatRN.entitlements */, 1EC6ACB522CB9FC300A41C61 /* MainInterface.storyboard */, 1EC6ACB822CB9FC300A41C61 /* Info.plist */, - 1EC6ACF522CBA01500A41C61 /* ShareRocketChatRN.m */, + 7A0129D22C6E8B5900F84A97 /* ShareRocketChatRN.swift */, ); path = ShareRocketChatRN; sourceTree = "<group>"; @@ -1126,21 +1103,12 @@ path = NotificationService; sourceTree = "<group>"; }; - 4F2FEDCF834DA36ADB5696EB /* ShareRocketChatRN */ = { - isa = PBXGroup; - children = ( - 016747EF3B9FED8DE2C9DA14 /* ExpoModulesProvider.swift */, - ); - name = ShareRocketChatRN; - sourceTree = "<group>"; - }; 7890E71355E6C0A3288089E7 /* ExpoModulesProviders */ = { isa = PBXGroup; children = ( A19B6D9832C0496282053365 /* NotificationService */, 989BF9C58F3C91FDAA6C0452 /* Rocket.Chat */, CADEA0916086EF72D141FF5A /* RocketChatRN */, - 4F2FEDCF834DA36ADB5696EB /* ShareRocketChatRN */, ); name = ExpoModulesProviders; sourceTree = "<group>"; @@ -1148,14 +1116,12 @@ 7AC2B09613AA7C3FEBAC9F57 /* Pods */ = { isa = PBXGroup; children = ( - 50B2AC15B2AAC327B74A8BBB /* Pods-defaults-NotificationService.debug.xcconfig */, - B99AD0CC4154722C23BACE13 /* Pods-defaults-NotificationService.release.xcconfig */, - BD0E5505861320CE3592D4BF /* Pods-defaults-Rocket.Chat.debug.xcconfig */, - 9F795E0928F3C7FADAD6BB77 /* Pods-defaults-Rocket.Chat.release.xcconfig */, - A4CADE732CC8A42A8B30EA03 /* Pods-defaults-RocketChatRN.debug.xcconfig */, - 2CF5E810829371C4A8189771 /* Pods-defaults-RocketChatRN.release.xcconfig */, - C8FC44D9D09F4A537E3407C2 /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */, - 1610999420E746E4299F1E30 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */, + 7B2AF8CE5DAA2B9602655ECA /* Pods-defaults-NotificationService.debug.xcconfig */, + C1FFA2C4EDACA0718BB71F1C /* Pods-defaults-NotificationService.release.xcconfig */, + 3A4ED61C8ED0D728A146E398 /* Pods-defaults-Rocket.Chat.debug.xcconfig */, + 8F262B0EDDE35FABA5DAC31A /* Pods-defaults-Rocket.Chat.release.xcconfig */, + 0E80084FB76F2BA00A43C5E0 /* Pods-defaults-RocketChatRN.debug.xcconfig */, + 0FF30665E726105A87EE37A8 /* Pods-defaults-RocketChatRN.release.xcconfig */, ); path = Pods; sourceTree = "<group>"; @@ -1251,10 +1217,9 @@ 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */, B37C79D9BD0742CE936B6982 /* libc++.tbd */, 06BB44DD4855498082A744AD /* libz.tbd */, - C3A803D7C0F33FAC94992D06 /* libPods-defaults-NotificationService.a */, - AF27585043417AE1F4BFF5F9 /* libPods-defaults-Rocket.Chat.a */, - 5DC1C73C9C5E285F42018A7B /* libPods-defaults-RocketChatRN.a */, - B8B55F1CCE1B454F23C46EC4 /* libPods-defaults-ShareRocketChatRN.a */, + 9EDAE7DFC486C8E0F5331BC7 /* libPods-defaults-NotificationService.a */, + F08B2D270E82A48D344CB0F5 /* libPods-defaults-Rocket.Chat.a */, + F38DDC9B729E9C3C3883E1CF /* libPods-defaults-RocketChatRN.a */, ); name = Frameworks; sourceTree = "<group>"; @@ -1274,7 +1239,7 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "RocketChatRN" */; buildPhases = ( - 3C73B005472EB6FEED6DBBF1 /* [CP] Check Pods Manifest.lock */, + 39683630344A091B5F15ED5E /* [CP] Check Pods Manifest.lock */, 7AA5C63E23E30D110005C4A7 /* Start Packager */, 589729E8381BA997CD19EF19 /* [Expo] Configure project */, 13B07F871A680F5B00A75B9A /* Sources */, @@ -1286,8 +1251,8 @@ 1ED0389C2B507B4F00C007D4 /* Embed Watch Content */, 7AAE9EB32891A0D20024F559 /* Upload source maps to Bugsnag */, 407D3EDE3DABEE15D27BD87D /* ShellScript */, - 581F472335FB8D3145D6165C /* [CP] Embed Pods Frameworks */, - C0D0DC8D75DD64F4BA9F8198 /* [CP] Copy Pods Resources */, + D45499F74A147D677153BC90 /* [CP] Embed Pods Frameworks */, + 9C104B12BEE385F7555E641F /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1305,13 +1270,8 @@ isa = PBXNativeTarget; buildConfigurationList = 1EC6ACF322CB9FC300A41C61 /* Build configuration list for PBXNativeTarget "ShareRocketChatRN" */; buildPhases = ( - A973A5C2F88ACE42FDC5A402 /* [CP] Check Pods Manifest.lock */, - 2C50632AB476A038AFCB1D43 /* [Expo] Configure project */, - 1EC6ACAC22CB9FC300A41C61 /* Sources */, - 1EC6ACAD22CB9FC300A41C61 /* Frameworks */, - 1EC6ACAE22CB9FC300A41C61 /* Resources */, - 1EFE4DC322CBF36300B766B7 /* ShellScript */, - C3DC4216188B500AE69A8DEA /* [CP] Copy Pods Resources */, + A3573D86450E144A0963898E /* Sources */, + 7A0129D52C6E8EFE00F84A97 /* Resources */, ); buildRules = ( ); @@ -1360,12 +1320,12 @@ isa = PBXNativeTarget; buildConfigurationList = 1EFEB5A02493B6640072EDC0 /* Build configuration list for PBXNativeTarget "NotificationService" */; buildPhases = ( - BA8CA2DB368A7DFA6F221A05 /* [CP] Check Pods Manifest.lock */, + 40620FE6C682BABD00C2B4CB /* [CP] Check Pods Manifest.lock */, 86A998705576AFA7CE938617 /* [Expo] Configure project */, 1EFEB5912493B6640072EDC0 /* Sources */, 1EFEB5922493B6640072EDC0 /* Frameworks */, 1EFEB5932493B6640072EDC0 /* Resources */, - 36CB40F595F84A5786EE6F85 /* [CP] Copy Pods Resources */, + 1AC60C112012F2EC4A83B6C6 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1380,7 +1340,7 @@ isa = PBXNativeTarget; buildConfigurationList = 7AAB3E4F257E6A6E00707CF6 /* Build configuration list for PBXNativeTarget "Rocket.Chat" */; buildPhases = ( - FF48CCDFCE7D3F572702FB70 /* [CP] Check Pods Manifest.lock */, + 2A96227A2025150C0D3A4736 /* [CP] Check Pods Manifest.lock */, 7AAB3E13257E6A6E00707CF6 /* Start Packager */, 6723DBD924B66933E14E7EF7 /* [Expo] Configure project */, 7AAB3E14257E6A6E00707CF6 /* Sources */, @@ -1391,8 +1351,8 @@ 7AAB3E4B257E6A6E00707CF6 /* ShellScript */, 1ED1ECE32B8699DD00F6620C /* Embed Watch Content */, 7A10288726B1D15200E47EF8 /* Upload source maps to Bugsnag */, - CBD4174E0909B1B62ED55A6E /* [CP] Embed Pods Frameworks */, - 6DBC49C0F170C83D191D0450 /* [CP] Copy Pods Resources */, + 23F41C80BD65C4C2AC2DD624 /* [CP] Embed Pods Frameworks */, + C75CF748D7A3F20A3D92419B /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1413,7 +1373,7 @@ isa = PBXProject; attributes = { DefaultBuildSystemTypeForWorkspace = Original; - LastSwiftUpdateCheck = 1500; + LastSwiftUpdateCheck = 1530; LastUpgradeCheck = 1130; ORGANIZATIONNAME = Facebook; TargetAttributes = { @@ -1508,18 +1468,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 1EC6ACAE22CB9FC300A41C61 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 7A610CD327ECE38100B8ABDD /* custom.ttf in Resources */, - 1EC6ACB722CB9FC300A41C61 /* MainInterface.storyboard in Resources */, - 1ED59D4C22CBA77D00C54289 /* GoogleService-Info.plist in Resources */, - 65AD38382BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */, - 7AE10C0728A59530003593CB /* Inter.ttf in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 1ED0388C2B507B4B00C007D4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1554,6 +1502,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7A0129D52C6E8EFE00F84A97 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7A0129D62C6E8F0700F84A97 /* ShareRocketChatRN.entitlements in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 7AAB3E41257E6A6E00707CF6 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1586,60 +1542,7 @@ shellPath = /bin/sh; shellScript = ". ~/.nvm/nvm.sh\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\"\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; }; - 1E1EA8082326CCE300E22452 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "echo \"Target architectures: $ARCHS\"\n\nAPP_PATH=\"${TARGET_BUILD_DIR}/${WRAPPER_NAME}\"\n\nfind \"$APP_PATH\" -name '*.framework' -type d | while read -r FRAMEWORK\ndo\nFRAMEWORK_EXECUTABLE_NAME=$(defaults read \"$FRAMEWORK/Info.plist\" CFBundleExecutable)\nFRAMEWORK_EXECUTABLE_PATH=\"$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME\"\necho \"Executable is $FRAMEWORK_EXECUTABLE_PATH\"\necho $(lipo -info \"$FRAMEWORK_EXECUTABLE_PATH\")\n\nFRAMEWORK_TMP_PATH=\"$FRAMEWORK_EXECUTABLE_PATH-tmp\"\n\n# remove simulator's archs if location is not simulator's directory\ncase \"${TARGET_BUILD_DIR}\" in\n*\"iphonesimulator\")\necho \"No need to remove archs\"\n;;\n*)\nif $(lipo \"$FRAMEWORK_EXECUTABLE_PATH\" -verify_arch \"i386\") ; then\nlipo -output \"$FRAMEWORK_TMP_PATH\" -remove \"i386\" \"$FRAMEWORK_EXECUTABLE_PATH\"\necho \"i386 architecture removed\"\nrm \"$FRAMEWORK_EXECUTABLE_PATH\"\nmv \"$FRAMEWORK_TMP_PATH\" \"$FRAMEWORK_EXECUTABLE_PATH\"\nfi\nif $(lipo \"$FRAMEWORK_EXECUTABLE_PATH\" -verify_arch \"x86_64\") ; then\nlipo -output \"$FRAMEWORK_TMP_PATH\" -remove \"x86_64\" \"$FRAMEWORK_EXECUTABLE_PATH\"\necho \"x86_64 architecture removed\"\nrm \"$FRAMEWORK_EXECUTABLE_PATH\"\nmv \"$FRAMEWORK_TMP_PATH\" \"$FRAMEWORK_EXECUTABLE_PATH\"\nfi\n;;\nesac\n\necho \"Completed for executable $FRAMEWORK_EXECUTABLE_PATH\"\necho $\n\ndone\n"; - }; - 1EFE4DC322CBF36300B766B7 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = ". ~/.nvm/nvm.sh\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\"\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; - }; - 2C50632AB476A038AFCB1D43 /* [Expo] Configure project */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "[Expo] Configure project"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-ShareRocketChatRN/expo-configure-project.sh\"\n"; - }; - 36CB40F595F84A5786EE6F85 /* [CP] Copy Pods Resources */ = { + 1AC60C112012F2EC4A83B6C6 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1715,7 +1618,66 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 3C73B005472EB6FEED6DBBF1 /* [CP] Check Pods Manifest.lock */ = { + 1E1EA8082326CCE300E22452 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"Target architectures: $ARCHS\"\n\nAPP_PATH=\"${TARGET_BUILD_DIR}/${WRAPPER_NAME}\"\n\nfind \"$APP_PATH\" -name '*.framework' -type d | while read -r FRAMEWORK\ndo\nFRAMEWORK_EXECUTABLE_NAME=$(defaults read \"$FRAMEWORK/Info.plist\" CFBundleExecutable)\nFRAMEWORK_EXECUTABLE_PATH=\"$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME\"\necho \"Executable is $FRAMEWORK_EXECUTABLE_PATH\"\necho $(lipo -info \"$FRAMEWORK_EXECUTABLE_PATH\")\n\nFRAMEWORK_TMP_PATH=\"$FRAMEWORK_EXECUTABLE_PATH-tmp\"\n\n# remove simulator's archs if location is not simulator's directory\ncase \"${TARGET_BUILD_DIR}\" in\n*\"iphonesimulator\")\necho \"No need to remove archs\"\n;;\n*)\nif $(lipo \"$FRAMEWORK_EXECUTABLE_PATH\" -verify_arch \"i386\") ; then\nlipo -output \"$FRAMEWORK_TMP_PATH\" -remove \"i386\" \"$FRAMEWORK_EXECUTABLE_PATH\"\necho \"i386 architecture removed\"\nrm \"$FRAMEWORK_EXECUTABLE_PATH\"\nmv \"$FRAMEWORK_TMP_PATH\" \"$FRAMEWORK_EXECUTABLE_PATH\"\nfi\nif $(lipo \"$FRAMEWORK_EXECUTABLE_PATH\" -verify_arch \"x86_64\") ; then\nlipo -output \"$FRAMEWORK_TMP_PATH\" -remove \"x86_64\" \"$FRAMEWORK_EXECUTABLE_PATH\"\necho \"x86_64 architecture removed\"\nrm \"$FRAMEWORK_EXECUTABLE_PATH\"\nmv \"$FRAMEWORK_TMP_PATH\" \"$FRAMEWORK_EXECUTABLE_PATH\"\nfi\n;;\nesac\n\necho \"Completed for executable $FRAMEWORK_EXECUTABLE_PATH\"\necho $\n\ndone\n"; + }; + 23F41C80BD65C4C2AC2DD624 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 2A96227A2025150C0D3A4736 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-defaults-Rocket.Chat-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 39683630344A091B5F15ED5E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1737,25 +1699,29 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 407D3EDE3DABEE15D27BD87D /* ShellScript */ = { + 40620FE6C682BABD00C2B4CB /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 581F472335FB8D3145D6165C /* [CP] Embed Pods Frameworks */ = { + 407D3EDE3DABEE15D27BD87D /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1765,10 +1731,8 @@ "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); - name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -1813,82 +1777,6 @@ shellPath = /bin/sh; shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-Rocket.Chat/expo-configure-project.sh\"\n"; }; - 6DBC49C0F170C83D191D0450 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics/FirebaseCrashlytics_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNDeviceInfo/RNDeviceInfoPrivacyInfo.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCrashlytics_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNDeviceInfoPrivacyInfo.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 7A10288726B1D15200E47EF8 /* Upload source maps to Bugsnag */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1897,7 +1785,7 @@ inputFileListPaths = ( ); inputPaths = ( - "$TARGET_BUILD_DIR/$INFOPLIST_PATH", + $TARGET_BUILD_DIR/$INFOPLIST_PATH, ); name = "Upload source maps to Bugsnag"; outputFileListPaths = ( @@ -1983,7 +1871,7 @@ inputFileListPaths = ( ); inputPaths = ( - "$TARGET_BUILD_DIR/$INFOPLIST_PATH", + $TARGET_BUILD_DIR/$INFOPLIST_PATH, ); name = "Upload source maps to Bugsnag"; outputFileListPaths = ( @@ -2013,51 +1901,7 @@ shellPath = /bin/sh; shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-NotificationService/expo-configure-project.sh\"\n"; }; - A973A5C2F88ACE42FDC5A402 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-ShareRocketChatRN-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - BA8CA2DB368A7DFA6F221A05 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - C0D0DC8D75DD64F4BA9F8198 /* [CP] Copy Pods Resources */ = { + 9C104B12BEE385F7555E641F /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -2133,13 +1977,13 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh\"\n"; showEnvVarsInLog = 0; }; - C3DC4216188B500AE69A8DEA /* [CP] Copy Pods Resources */ = { + C75CF748D7A3F20A3D92419B /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", @@ -2206,16 +2050,16 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh\"\n"; showEnvVarsInLog = 0; }; - CBD4174E0909B1B62ED55A6E /* [CP] Embed Pods Frameworks */ = { + D45499F74A147D677153BC90 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh", "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); @@ -2226,29 +2070,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - FF48CCDFCE7D3F572702FB70 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-Rocket.Chat-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -2302,17 +2124,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 1EC6ACAC22CB9FC300A41C61 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 1EC6ACF622CBA01500A41C61 /* ShareRocketChatRN.m in Sources */, - 1E068CFF24FD2DC700A0FFC1 /* AppGroup.swift in Sources */, - 1E068D0224FD2E0500A0FFC1 /* AppGroup.m in Sources */, - D94D81FB9E10756FAA03F203 /* ExpoModulesProvider.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 1ED0388A2B507B4B00C007D4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2579,6 +2390,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A3573D86450E144A0963898E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7A0129EA2C6E921600F84A97 /* MainInterface.storyboard in Sources */, + 7A0129D42C6E8EC800F84A97 /* ShareRocketChatRN.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -2628,7 +2448,7 @@ /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A4CADE732CC8A42A8B30EA03 /* Pods-defaults-RocketChatRN.debug.xcconfig */; + baseConfigurationReference = 0E80084FB76F2BA00A43C5E0 /* Pods-defaults-RocketChatRN.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2689,7 +2509,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 2CF5E810829371C4A8189771 /* Pods-defaults-RocketChatRN.release.xcconfig */; + baseConfigurationReference = 0FF30665E726105A87EE37A8 /* Pods-defaults-RocketChatRN.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2750,7 +2570,6 @@ }; 1EC6ACBC22CB9FC300A41C61 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C8FC44D9D09F4A537E3407C2 /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; APPLICATION_EXTENSION_API_ONLY = YES; @@ -2775,6 +2594,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = S6UPZG7ZR3; ENABLE_BITCODE = NO; ENABLE_TESTABILITY = YES; @@ -2793,7 +2613,7 @@ "$(inherited)", "$(SRCROOT)/../node_modules/rn-extensions-share/ios/**", "$(SRCROOT)/../node_modules/react-native-firebase/ios/RNFirebase/**", - "$PODS_CONFIGURATION_BUILD_DIR/Firebase", + $PODS_CONFIGURATION_BUILD_DIR/Firebase, "$(SRCROOT)/../node_modules/react-native-mmkv-storage/ios/**", ); INFOPLIST_FILE = ShareRocketChatRN/Info.plist; @@ -2826,7 +2646,6 @@ }; 1EC6ACBD22CB9FC300A41C61 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 1610999420E746E4299F1E30 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; APPLICATION_EXTENSION_API_ONLY = YES; @@ -2852,6 +2671,7 @@ CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = S6UPZG7ZR3; ENABLE_BITCODE = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -2869,7 +2689,7 @@ "$(inherited)", "$(SRCROOT)/../node_modules/rn-extensions-share/ios/**", "$(SRCROOT)/../node_modules/react-native-firebase/ios/RNFirebase/**", - "$PODS_CONFIGURATION_BUILD_DIR/Firebase", + $PODS_CONFIGURATION_BUILD_DIR/Firebase, "$(SRCROOT)/../node_modules/react-native-mmkv-storage/ios/**", ); INFOPLIST_FILE = ShareRocketChatRN/Info.plist; @@ -3110,7 +2930,7 @@ }; 1EFEB59D2493B6640072EDC0 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 50B2AC15B2AAC327B74A8BBB /* Pods-defaults-NotificationService.debug.xcconfig */; + baseConfigurationReference = 7B2AF8CE5DAA2B9602655ECA /* Pods-defaults-NotificationService.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; CLANG_ANALYZER_NONNULL = YES; @@ -3152,7 +2972,7 @@ }; 1EFEB59E2493B6640072EDC0 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B99AD0CC4154722C23BACE13 /* Pods-defaults-NotificationService.release.xcconfig */; + baseConfigurationReference = C1FFA2C4EDACA0718BB71F1C /* Pods-defaults-NotificationService.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; CLANG_ANALYZER_NONNULL = YES; @@ -3195,7 +3015,7 @@ }; 7AAB3E50257E6A6E00707CF6 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = BD0E5505861320CE3592D4BF /* Pods-defaults-Rocket.Chat.debug.xcconfig */; + baseConfigurationReference = 3A4ED61C8ED0D728A146E398 /* Pods-defaults-Rocket.Chat.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -3255,7 +3075,7 @@ }; 7AAB3E51257E6A6E00707CF6 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9F795E0928F3C7FADAD6BB77 /* Pods-defaults-Rocket.Chat.release.xcconfig */; + baseConfigurationReference = 8F262B0EDDE35FABA5DAC31A /* Pods-defaults-Rocket.Chat.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -3376,10 +3196,7 @@ ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; @@ -3442,10 +3259,7 @@ MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/ios/ShareRocketChatRN/Base.lproj/MainInterface.storyboard b/ios/ShareRocketChatRN/Base.lproj/MainInterface.storyboard index b822a68217..04f6af80f7 100644 --- a/ios/ShareRocketChatRN/Base.lproj/MainInterface.storyboard +++ b/ios/ShareRocketChatRN/Base.lproj/MainInterface.storyboard @@ -1,28 +1,27 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli"> - <device id="retina6_1" orientation="portrait"> - <adaptation id="fullscreen"/> - </device> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli"> + <device id="retina6_1" orientation="portrait" appearance="light"/> <dependencies> <deployment identifier="iOS"/> - <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <scenes> - <!--Share Rocket.ChatRN--> + <!--Share Rocket ChatRN--> <scene sceneID="ceB-am-kn3"> <objects> - <viewController id="j1y-V4-xli" customClass="ShareRocketChatRN" sceneMemberID="viewController"> + <viewController id="j1y-V4-xli" customClass="ShareRocketChatRN" customModule="ShareRocketChatRN" sceneMemberID="viewController"> <view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP"> <rect key="frame" x="0.0" y="0.0" width="414" height="896"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/> <viewLayoutGuide key="safeArea" id="1Xd-am-t49"/> + <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/> </view> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/> </objects> + <point key="canvasLocation" x="32" y="-23"/> </scene> </scenes> </document> diff --git a/ios/ShareRocketChatRN/ShareRocketChatRN.m b/ios/ShareRocketChatRN/ShareRocketChatRN.m deleted file mode 100644 index f4a53b1b6c..0000000000 --- a/ios/ShareRocketChatRN/ShareRocketChatRN.m +++ /dev/null @@ -1,61 +0,0 @@ -// -// ShareRocketChatRN.m -// ShareRocketChatRN -// -// Created by Djorkaeff Alexandre Vilela Pereira on 16/05/19. -// Copyright © 2019 Facebook. All rights reserved. -// - -#import <Foundation/Foundation.h> -#import "ReactNativeShareExtension.h" -#import <React/RCTBundleURLProvider.h> -#import <React/RCTRootView.h> -#import <React/RCTBridgeDelegate.h> - -#import <MMKV/MMKV.h> -#import <Firebase.h> -#import <Bugsnag/Bugsnag.h> - -@interface ShareRocketChatRN : ReactNativeShareExtension <RCTBridgeDelegate> -@end; - -@implementation ShareRocketChatRN - -RCT_EXPORT_MODULE(); - -- (UIView*) shareView { - NSURL *jsCodeLocation; - - if(![FIRApp defaultApp]){ - [FIRApp configure]; - } - [Bugsnag start]; - - jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; - - RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:nil]; - RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge - moduleName:@"ShareRocketChatRN" - initialProperties:nil]; - rootView.backgroundColor = nil; - - // Uncomment for console output in Xcode console for release mode on device: - // RCTSetLogThreshold(RCTLogLevelInfo - 1); - - // AppGroup MMKV - NSString *groupDir = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]].path; - [MMKV initializeMMKV:nil groupDir:groupDir logLevel:MMKVLogInfo]; - - return rootView; -} - -- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge -{ - #if DEBUG - return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; - #else - return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; - #endif -} - -@end diff --git a/ios/ShareRocketChatRN/ShareRocketChatRN.swift b/ios/ShareRocketChatRN/ShareRocketChatRN.swift new file mode 100644 index 0000000000..e31eeca3b3 --- /dev/null +++ b/ios/ShareRocketChatRN/ShareRocketChatRN.swift @@ -0,0 +1,218 @@ +// +// ShareRocketChatRN.swift +// ShareRocketChatRN +// +// Created by Diego Mello on 8/15/24. +// Copyright © 2024 Facebook. All rights reserved. +// + +import UIKit +import MobileCoreServices + +class ShareRocketChatRN: UIViewController { + let appScheme = "rocketchat" + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem, + let attachments = extensionItem.attachments else { + self.completeRequest() + return + } + + // Handle URL or Text using the first attachment only + if let firstAttachment = attachments.first { + if firstAttachment.hasItemConformingToTypeIdentifier("public.url") { + firstAttachment.loadItem(forTypeIdentifier: "public.url", options: nil) { (data, error) in + if let url = data as? URL { + if url.isFileURL { + // Handle all file URLs + self.handleAllFileURLs(items: attachments) + } else { + // Handle as a web URL + self.handleUrl(item: firstAttachment) + } + } + } + return + } else if firstAttachment.hasItemConformingToTypeIdentifier("public.text") { + self.handleText(item: firstAttachment) + return + } + } + + // Handle Media (Images, Videos) and Data (PDFs, etc.) for all attachments + self.handleMultipleMediaAndData(items: attachments) + } + + private func handleText(item: NSItemProvider) { + item.loadItem(forTypeIdentifier: "public.text", options: nil) { (data, error) in + if let text = data as? String { + if let encoded = text.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed), + let url = URL(string: "\(self.appScheme)://shareextension?text=\(encoded)") { + _ = self.openURL(url) + } + } + self.completeRequest() + } + } + + private func handleUrl(item: NSItemProvider) { + item.loadItem(forTypeIdentifier: "public.url", options: nil) { (data, error) in + if let url = data as? URL { + if let encoded = url.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed), + let finalUrl = URL(string: "\(self.appScheme)://shareextension?url=\(encoded)") { + _ = self.openURL(finalUrl) + } + } + self.completeRequest() + } + } + + private func handleAllFileURLs(items: [NSItemProvider]) { + var fileUris = [String]() + let dispatchGroup = DispatchGroup() + + for item in items { + dispatchGroup.enter() + item.loadItem(forTypeIdentifier: "public.data", options: nil) { (data, error) in + if let fileUrl = data as? URL, fileUrl.isFileURL { + do { + let fileData = try Data(contentsOf: fileUrl) + let originalFilename = fileUrl.lastPathComponent + let savedUrl = self.saveDataToSharedContainer(data: fileData, filename: originalFilename) + if let finalUrl = savedUrl?.absoluteString { + fileUris.append(finalUrl) + } + } catch { + // Handle error + } + } + dispatchGroup.leave() + } + } + + dispatchGroup.notify(queue: .main) { + let combinedFileUris = fileUris.joined(separator: ",") + if let encoded = combinedFileUris.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed), + let url = URL(string: "\(self.appScheme)://shareextension?mediaUris=\(encoded)") { + _ = self.openURL(url) + } + self.completeRequest() + } + } + + + private func handleMultipleMediaAndData(items: [NSItemProvider]) { + var mediaUris = [String]() + let dispatchGroup = DispatchGroup() + + for (_, item) in items.enumerated() { + dispatchGroup.enter() + + if item.hasItemConformingToTypeIdentifier("public.image") { + self.loadAndSaveItem(item: item, type: "public.image", dispatchGroup: dispatchGroup) { mediaUriInfo in + if let mediaUriInfo = mediaUriInfo { + mediaUris.append(mediaUriInfo) + } + } + } else if item.hasItemConformingToTypeIdentifier("public.movie") { + self.loadAndSaveItem(item: item, type: "public.movie", dispatchGroup: dispatchGroup) { mediaUriInfo in + if let mediaUriInfo = mediaUriInfo { + mediaUris.append(mediaUriInfo) + } + } + } else if item.hasItemConformingToTypeIdentifier("public.data") { + self.loadAndSaveItem(item: item, type: "public.data", dispatchGroup: dispatchGroup) { mediaUriInfo in + if let mediaUriInfo = mediaUriInfo { + mediaUris.append(mediaUriInfo) + } + } + } else { + dispatchGroup.leave() + } + } + + dispatchGroup.notify(queue: .main) { + let combinedMediaUris = mediaUris.joined(separator: ",") + if let encoded = combinedMediaUris.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed), + let url = URL(string: "\(self.appScheme)://shareextension?mediaUris=\(encoded)") { + _ = self.openURL(url) + } + self.completeRequest() + } + } + + private func loadAndSaveItem(item: NSItemProvider, type: String, dispatchGroup: DispatchGroup, completion: @escaping (String?) -> Void) { + item.loadItem(forTypeIdentifier: type, options: nil) { (data, error) in + var mediaUriInfo: String? + + if let dataUri = data as? URL { + do { + let data = try Data(contentsOf: dataUri) + let originalFilename = dataUri.lastPathComponent + let savedUrl = self.saveDataToSharedContainer(data: data, filename: originalFilename) + mediaUriInfo = savedUrl?.absoluteString + } catch { + mediaUriInfo = nil + } + } else if let data = data as? Data { + if let fileExtension = self.inferFileExtension(from: item) { + let filename = UUID().uuidString + "." + fileExtension + let savedUrl = self.saveDataToSharedContainer(data: data, filename: filename) + mediaUriInfo = savedUrl?.absoluteString + } + } + + completion(mediaUriInfo) + dispatchGroup.leave() + } + } + + private func saveDataToSharedContainer(data: Data, filename: String) -> URL? { + guard let appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as? String else { + return nil + } + guard let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else { + return nil + } + let fileURL = groupURL.appendingPathComponent(filename) + do { + try data.write(to: fileURL) + return fileURL + } catch { + return nil + } + } + + private func inferFileExtension(from item: NSItemProvider) -> String? { + if item.hasItemConformingToTypeIdentifier(kUTTypeImage as String) { + return "jpeg" + } else if item.hasItemConformingToTypeIdentifier(kUTTypeMovie as String) { + return "mp4" + } else if let typeIdentifier = item.registeredTypeIdentifiers.first as CFString? { + if let utType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, typeIdentifier, nil)?.takeRetainedValue() { + if let preferredExtension = UTTypeCopyPreferredTagWithClass(utType, kUTTagClassFilenameExtension)?.takeRetainedValue() { + return preferredExtension as String + } + } + } + return nil + } + + @objc private func openURL(_ url: URL) -> Bool { + var responder: UIResponder? = self + while responder != nil { + if let application = responder as? UIApplication { + return application.perform(#selector(openURL(_:)), with: url) != nil + } + responder = responder?.next + } + return false + } + + private func completeRequest() { + self.extensionContext?.completeRequest(returningItems: nil) + } +} diff --git a/package.json b/package.json index 6104b0b6ce..d44e3135a2 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,6 @@ "redux-saga": "1.1.3", "remove-markdown": "0.3.0", "reselect": "4.0.0", - "rn-extensions-share": "RocketChat/rn-extensions-share", "rn-root-view": "RocketChat/rn-root-view", "semver": "7.5.2", "transliteration": "2.3.5", diff --git a/yarn.lock b/yarn.lock index 4b5010a1f8..d668f4dbd8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12721,10 +12721,6 @@ rimraf@~2.6.2: dependencies: glob "^7.1.3" -rn-extensions-share@RocketChat/rn-extensions-share: - version "2.4.1" - resolved "https://codeload.github.com/RocketChat/rn-extensions-share/tar.gz/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c" - rn-host-detect@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/rn-host-detect/-/rn-host-detect-1.2.0.tgz#8b0396fc05631ec60c1cb8789e5070cdb04d0da0" From 5cdddc949b7fe703c6da8672fad426eb49abdb77 Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Mon, 16 Sep 2024 14:53:11 -0300 Subject: [PATCH 20/26] fix(iOS): Message details not shown in push notifications in some cases (#5815) --- app/lib/database/index.ts | 2 +- app/lib/{database => methods}/appGroup.ts | 2 + app/lib/methods/logout.ts | 6 +- app/lib/methods/userPreferences.ts | 2 +- app/sagas/login.js | 6 + ios/AppGroup/AppGroup.swift | 7 +- .../NotificationService.swift | 103 ++++++------ ios/Podfile.lock | 20 ++- ios/ReplyNotification.swift | 2 +- ios/RocketChatRN.xcodeproj/project.pbxproj | 148 +++--------------- ios/RocketChatRN/AppDelegate.mm | 2 +- ios/Shared/RocketChat/API/API.swift | 12 +- ios/Shared/RocketChat/API/Request.swift | 4 + ios/Shared/RocketChat/Encryption.swift | 5 +- ios/Shared/RocketChat/MMKV.swift | 2 +- ios/Shared/RocketChat/RocketChat.swift | 15 -- ios/Shared/RocketChat/Storage.swift | 60 +++++-- package.json | 3 +- patches/react-native-mmkv-storage+0.9.1.patch | 12 ++ yarn.lock | 7 +- 20 files changed, 189 insertions(+), 231 deletions(-) rename app/lib/{database => methods}/appGroup.ts (65%) diff --git a/app/lib/database/index.ts b/app/lib/database/index.ts index aa45bcb209..6b503bcc6b 100644 --- a/app/lib/database/index.ts +++ b/app/lib/database/index.ts @@ -2,7 +2,7 @@ import { Database } from '@nozbe/watermelondb'; import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite'; import logger from '@nozbe/watermelondb/utils/common/logger'; -import { appGroupPath } from './appGroup'; +import { appGroupPath } from '../methods/appGroup'; import { isOfficial } from '../constants/environment'; import Subscription from './model/Subscription'; import Room from './model/Room'; diff --git a/app/lib/database/appGroup.ts b/app/lib/methods/appGroup.ts similarity index 65% rename from app/lib/database/appGroup.ts rename to app/lib/methods/appGroup.ts index 641090efa6..0027fc8ee2 100644 --- a/app/lib/database/appGroup.ts +++ b/app/lib/methods/appGroup.ts @@ -3,3 +3,5 @@ import { NativeModules, Platform } from 'react-native'; const { AppGroup } = NativeModules; export const appGroupPath: string = Platform.OS === 'ios' ? AppGroup.path : ''; + +export const appGroupSuiteName: string = Platform.OS === 'ios' ? AppGroup.suiteName : ''; diff --git a/app/lib/methods/logout.ts b/app/lib/methods/logout.ts index 0646c71c54..7bde9e63de 100644 --- a/app/lib/methods/logout.ts +++ b/app/lib/methods/logout.ts @@ -1,6 +1,7 @@ import * as FileSystem from 'expo-file-system'; import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk'; import Model from '@nozbe/watermelondb/Model'; +import * as Keychain from 'react-native-keychain'; import { getDeviceToken } from '../notifications'; import { extractHostname, isSsl } from './helpers'; @@ -15,7 +16,7 @@ import { Services } from '../services'; import { roomsSubscription } from './subscriptions/rooms'; import { _activeUsersSubTimeout } from '.'; -function removeServerKeys({ server, userId }: { server: string; userId?: string | null }) { +async function removeServerKeys({ server, userId }: { server: string; userId?: string | null }) { UserPreferences.removeItem(`${TOKEN_KEY}-${server}`); if (userId) { UserPreferences.removeItem(`${TOKEN_KEY}-${userId}`); @@ -24,6 +25,7 @@ function removeServerKeys({ server, userId }: { server: string; userId?: string UserPreferences.removeItem(`${server}-${E2E_PUBLIC_KEY}`); UserPreferences.removeItem(`${server}-${E2E_PRIVATE_KEY}`); UserPreferences.removeItem(`${server}-${E2E_RANDOM_PASSWORD_KEY}`); + await Keychain.resetInternetCredentials(server); } async function removeSharedCredentials({ server }: { server: string }) { @@ -56,7 +58,7 @@ export async function removeServerData({ server }: { server: string }): Promise< await serversDB.write(() => serversDB.batch(...batch)); await removeSharedCredentials({ server }); - removeServerKeys({ server, userId }); + await removeServerKeys({ server, userId }); } catch (e) { log(e); } diff --git a/app/lib/methods/userPreferences.ts b/app/lib/methods/userPreferences.ts index f61944bd7c..ec2904409f 100644 --- a/app/lib/methods/userPreferences.ts +++ b/app/lib/methods/userPreferences.ts @@ -3,7 +3,7 @@ import { create, MMKVLoader, MMKVInstance, ProcessingModes, IOSAccessibleStates const MMKV = new MMKVLoader() // MODES.MULTI_PROCESS = ACCESSIBLE BY APP GROUP (iOS) .setProcessingMode(ProcessingModes.MULTI_PROCESS) - .setAccessibleIOS(IOSAccessibleStates.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY) + .setAccessibleIOS(IOSAccessibleStates.AFTER_FIRST_UNLOCK) .withEncryption() .initialize(); diff --git a/app/sagas/login.js b/app/sagas/login.js index 13adecbe35..fb2fcabc69 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -2,6 +2,7 @@ import React from 'react'; import { call, cancel, delay, fork, put, race, select, take, takeLatest } from 'redux-saga/effects'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { Q } from '@nozbe/watermelondb'; +import * as Keychain from 'react-native-keychain'; import moment from 'moment'; import * as types from '../actions/actionsTypes'; @@ -41,6 +42,7 @@ import { import { Services } from '../lib/services'; import { setUsersRoles } from '../actions/usersRoles'; import { getServerById } from '../lib/database/services/Server'; +import { appGroupSuiteName } from '../lib/methods/appGroup'; import appNavigation from '../lib/navigation/appNavigation'; import { showActionSheetRef } from '../containers/ActionSheet'; import { SupportedVersionsWarning } from '../containers/SupportedVersions'; @@ -232,6 +234,10 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) { UserPreferences.setString(`${TOKEN_KEY}-${server}`, user.id); UserPreferences.setString(`${TOKEN_KEY}-${user.id}`, user.token); UserPreferences.setString(CURRENT_SERVER, server); + yield Keychain.setInternetCredentials(server, user.id, user.token, { + accessGroup: appGroupSuiteName, + securityLevel: Keychain.SECURITY_LEVEL.SECURE_SOFTWARE + }); yield put(setUser(user)); EventEmitter.emit('connected'); const currentRoot = yield select(state => state.app.root); diff --git a/ios/AppGroup/AppGroup.swift b/ios/AppGroup/AppGroup.swift index 278e9be57d..f9f2c8efb0 100644 --- a/ios/AppGroup/AppGroup.swift +++ b/ios/AppGroup/AppGroup.swift @@ -15,12 +15,15 @@ class AppGroup: NSObject { func constantsToExport() -> [AnyHashable : Any]! { // Get App Group directory var path = "" - if let suiteName = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as? String { + var suiteName = "" + + if let suite = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as? String { + suiteName = suite if let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: suiteName) { path = directory.path } } - return ["path": "\(path)/"] + return ["path": "\(path)/", "suiteName": suiteName] } } diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift index d2d31f7200..6ca52c5c4e 100644 --- a/ios/NotificationService/NotificationService.swift +++ b/ios/NotificationService/NotificationService.swift @@ -1,55 +1,66 @@ import UserNotifications class NotificationService: UNNotificationServiceExtension { - - var contentHandler: ((UNNotificationContent) -> Void)? - var bestAttemptContent: UNMutableNotificationContent? - var rocketchat: RocketChat? - - override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { - self.contentHandler = contentHandler - bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) - if let bestAttemptContent = bestAttemptContent { - let ejson = (bestAttemptContent.userInfo["ejson"] as? String ?? "").data(using: .utf8)! - guard let data = try? (JSONDecoder().decode(Payload.self, from: ejson)) else { - return - } - - rocketchat = RocketChat.instanceForServer(server: data.host.removeTrailingSlash()) - - // If the notification has the content on the payload, show it - if data.notificationType != .messageIdOnly { - self.processPayload(payload: data) - return - } - - // Request the content from server - rocketchat?.getPushWithId(data.messageId) { notification in - if let notification = notification { - self.bestAttemptContent?.title = notification.title - self.bestAttemptContent?.body = notification.text - self.processPayload(payload: notification.payload) - } - } - } - } - - func processPayload(payload: Payload) { - // If is a encrypted message - if payload.messageType == .e2e { - if let message = payload.msg, let rid = payload.rid { - if let decryptedMessage = rocketchat?.decryptMessage(rid: rid, message: message) { - bestAttemptContent?.body = decryptedMessage - if let roomType = payload.type, roomType == .group, let sender = payload.senderName { - bestAttemptContent?.body = "\(sender): \(decryptedMessage)" - } + var contentHandler: ((UNNotificationContent) -> Void)? + var bestAttemptContent: UNMutableNotificationContent? + var rocketchat: RocketChat? + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + self.contentHandler = contentHandler + bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + + if let bestAttemptContent = bestAttemptContent { + let ejson = (bestAttemptContent.userInfo["ejson"] as? String ?? "").data(using: .utf8)! + guard let data = try? (JSONDecoder().decode(Payload.self, from: ejson)) else { + return + } + + rocketchat = RocketChat(server: data.host.removeTrailingSlash()) + + // If the notification has the content on the payload, show it + if data.notificationType != .messageIdOnly { + self.processPayload(payload: data) + return + } + + // Merge missing content notifications + UNUserNotificationCenter.current().getDeliveredNotifications { deliveredNotifications in + let identifiersToRemove = deliveredNotifications.filter { + $0.request.content.body == "You have a new message" + }.map { $0.request.identifier } + + if identifiersToRemove.count > 0 { + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: identifiersToRemove) + } + + // Request the content from server + self.rocketchat?.getPushWithId(data.messageId) { notification in + if let notification = notification { + self.bestAttemptContent?.title = notification.title + self.bestAttemptContent?.body = notification.text + self.processPayload(payload: notification.payload) + } + } + } } - } } - if let bestAttemptContent = bestAttemptContent { - contentHandler?(bestAttemptContent) + func processPayload(payload: Payload) { + // If is a encrypted message + if payload.messageType == .e2e { + if let message = payload.msg, let rid = payload.rid { + if let decryptedMessage = rocketchat?.decryptMessage(rid: rid, message: message) { + bestAttemptContent?.body = decryptedMessage + if let roomType = payload.type, roomType == .group, let sender = payload.senderName { + bestAttemptContent?.body = "\(sender): \(decryptedMessage)" + } + } + } + } + + if let bestAttemptContent = bestAttemptContent { + contentHandler?(bestAttemptContent) + } } - } } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index fe95887abb..3b71517052 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -149,9 +149,9 @@ PODS: - libwebp/sharpyuv (1.3.2) - libwebp/webp (1.3.2): - libwebp/sharpyuv - - MMKV (1.2.13): - - MMKVCore (~> 1.2.13) - - MMKVCore (1.2.16) + - MMKV (1.3.9): + - MMKVCore (~> 1.3.9) + - MMKVCore (1.3.9) - nanopb (2.30910.0): - nanopb/decode (= 2.30910.0) - nanopb/encode (= 2.30910.0) @@ -1041,7 +1041,7 @@ PODS: - react-native-cookies (6.2.1): - React-Core - react-native-mmkv-storage (0.9.1): - - MMKV (= 1.2.13) + - MMKV (>= 1.3.3) - React-Core - react-native-netinfo (11.3.1): - React-Core @@ -1276,6 +1276,8 @@ PODS: - React-Core - React-RCTImage - TOCropViewController + - RNKeychain (8.2.0): + - React-Core - RNLocalize (2.1.1): - React-Core - RNNotifee (7.8.2): @@ -1409,6 +1411,7 @@ DEPENDENCIES: - RNFileViewer (from `../node_modules/react-native-file-viewer`) - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`) + - RNKeychain (from `../node_modules/react-native-keychain`) - RNLocalize (from `../node_modules/react-native-localize`) - "RNNotifee (from `../node_modules/@notifee/react-native`)" - RNReanimated (from `../node_modules/react-native-reanimated`) @@ -1627,6 +1630,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-gesture-handler" RNImageCropPicker: :path: "../node_modules/react-native-image-crop-picker" + RNKeychain: + :path: "../node_modules/react-native-keychain" RNLocalize: :path: "../node_modules/react-native-localize" RNNotifee: @@ -1684,8 +1689,8 @@ SPEC CHECKSUMS: hermes-engine: 9cecf9953a681df7556b8cc9c74905de8f3293c0 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 - MMKV: aac95d817a100479445633f2b3ed8961b4ac5043 - MMKVCore: 9cfef4c48c6c46f66226fc2e4634d78490206a48 + MMKV: 817ba1eea17421547e01e087285606eb270a8dcb + MMKVCore: af055b00e27d88cd92fad301c5fecd1ff9b26dd9 nanopb: 438bc412db1928dac798aa6fd75726007be04262 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 @@ -1714,7 +1719,7 @@ SPEC CHECKSUMS: react-native-background-timer: 17ea5e06803401a379ebf1f20505b793ac44d0fe react-native-cameraroll: 4593ffad9e619781aed27dda64739387d55ed402 react-native-cookies: f54fcded06bb0cda05c11d86788020b43528a26c - react-native-mmkv-storage: cfb6854594cfdc5f7383a9e464bb025417d1721c + react-native-mmkv-storage: 7953fb2d2128987153afabd0deb3fadbba009395 react-native-netinfo: bdb108d340cdb41875c9ced535977cac6d2ff321 react-native-notifications: 4601a5a8db4ced6ae7cfc43b44d35fe437ac50c4 react-native-restart: 733a51ad137f15b0f8dc34c4082e55af7da00979 @@ -1758,6 +1763,7 @@ SPEC CHECKSUMS: RNFileViewer: 83cc066ad795b1f986791d03b56fe0ee14b6a69f RNGestureHandler: bc2cdb2dc42facdf34992ae364b8a728e19a3686 RNImageCropPicker: 97289cd94fb01ab79db4e5c92938be4d0d63415d + RNKeychain: bfe3d12bf4620fe488771c414530bf16e88f3678 RNLocalize: 82a569022724d35461e2dc5b5d015a13c3ca995b RNNotifee: 8e2d3df3f0e9ce8f5d1fe4c967431138190b6175 RNReanimated: 8a4d86eb951a4a99d8e86266dc71d7735c0c30a9 diff --git a/ios/ReplyNotification.swift b/ios/ReplyNotification.swift index 5d08406327..b21b219ee9 100644 --- a/ios/ReplyNotification.swift +++ b/ios/ReplyNotification.swift @@ -32,7 +32,7 @@ class ReplyNotification: RNNotificationEventHandler { if let data = (notification["ejson"] as? String)?.data(using: .utf8) { if let payload = try? JSONDecoder().decode(Payload.self, from: data), let rid = payload.rid { if let msg = (response as? UNTextInputNotificationResponse)?.userText { - let rocketchat = RocketChat.instanceForServer(server: payload.host.removeTrailingSlash()) + let rocketchat = RocketChat(server: payload.host.removeTrailingSlash()) let backgroundTask = UIApplication.shared.beginBackgroundTask(expirationHandler: nil) rocketchat.sendMessage(rid: rid, message: msg, threadIdentifier: payload.tmid) { response in guard let response = response, response.success else { diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index bf31a005a5..c281dc2b6b 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 0134A33CEB44035971D5A18E /* libPods-defaults-RocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 48C83B3466485303788E4521 /* libPods-defaults-RocketChatRN.a */; }; + 04587880D3FA9BCC2E9C7490 /* libPods-defaults-ShareRocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AAE735590E45F323097F18D1 /* libPods-defaults-ShareRocketChatRN.a */; }; 0C6E2DE448364EA896869ADF /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B37C79D9BD0742CE936B6982 /* libc++.tbd */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; @@ -287,6 +289,7 @@ 65AD383C2BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; 65B9A71A2AFC24190088956F /* ringtone.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 65B9A7192AFC24190088956F /* ringtone.mp3 */; }; 65B9A71B2AFC24190088956F /* ringtone.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 65B9A7192AFC24190088956F /* ringtone.mp3 */; }; + 6D168AA9955DC38D82D3C051 /* libPods-defaults-Rocket.Chat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A349601A16A2DEC0EEDCB1E8 /* libPods-defaults-Rocket.Chat.a */; }; 7A006F14229C83B600803143 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7A006F13229C83B600803143 /* GoogleService-Info.plist */; }; 7A0129D42C6E8EC800F84A97 /* ShareRocketChatRN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0129D22C6E8B5900F84A97 /* ShareRocketChatRN.swift */; }; 7A0129D62C6E8F0700F84A97 /* ShareRocketChatRN.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = 1EC6AD6022CBA20C00A41C61 /* ShareRocketChatRN.entitlements */; }; @@ -609,6 +612,8 @@ 60B2A6A31FC4588700BD58E5 /* RocketChatRN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = RocketChatRN.entitlements; path = RocketChatRN/RocketChatRN.entitlements; sourceTree = "<group>"; }; 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; }; 65B9A7192AFC24190088956F /* ringtone.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ringtone.mp3; sourceTree = "<group>"; }; + 70993AF097DB20918D5ABBDC /* Pods-defaults-Rocket.Chat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.release.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.release.xcconfig"; sourceTree = "<group>"; }; + 7702E49AD9EFBD11DE2AE855 /* libPods-defaults-NotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-NotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 7A006F13229C83B600803143 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; }; 7A0129D22C6E8B5900F84A97 /* ShareRocketChatRN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareRocketChatRN.swift; sourceTree = "<group>"; }; 7A0D62D1242AB187006D5C06 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; }; @@ -1548,74 +1553,18 @@ files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics/FirebaseCrashlytics_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNDeviceInfo/RNDeviceInfoPrivacyInfo.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", + "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); - name = "[CP] Copy Pods Resources"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCrashlytics_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNDeviceInfoPrivacyInfo.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 1E1EA8082326CCE300E22452 /* ShellScript */ = { @@ -1692,7 +1641,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-RocketChatRN-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-defaults-Rocket.Chat-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -1903,79 +1852,22 @@ }; 9C104B12BEE385F7555E641F /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics/FirebaseCrashlytics_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNDeviceInfo/RNDeviceInfoPrivacyInfo.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf", - "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", ); - name = "[CP] Copy Pods Resources"; + name = "[Expo] Configure project"; + outputFileListPaths = ( + ); outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCrashlytics_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNDeviceInfoPrivacyInfo.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-NotificationService/expo-configure-project.sh\"\n"; }; C75CF748D7A3F20A3D92419B /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; diff --git a/ios/RocketChatRN/AppDelegate.mm b/ios/RocketChatRN/AppDelegate.mm index 07e098ec9b..82c40ace8b 100644 --- a/ios/RocketChatRN/AppDelegate.mm +++ b/ios/RocketChatRN/AppDelegate.mm @@ -18,7 +18,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( // AppGroup MMKV NSString *groupDir = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]].path; - [MMKV initializeMMKV:nil groupDir:groupDir logLevel:MMKVLogInfo]; + [MMKV initializeMMKV:nil groupDir:groupDir logLevel:MMKVLogDebug]; [RNNotifications startMonitorNotifications]; [ReplyNotification configure]; diff --git a/ios/Shared/RocketChat/API/API.swift b/ios/Shared/RocketChat/API/API.swift index 68b2a7c0e6..1268761cdd 100644 --- a/ios/Shared/RocketChat/API/API.swift +++ b/ios/Shared/RocketChat/API/API.swift @@ -34,19 +34,21 @@ final class API { final let credentials: Credentials? final let decoder = JSONDecoder() - static var instances: [Server: API] = [:] - convenience init?(server: Server) { guard let server = URL(string: server.removeTrailingSlash()) else { return nil } + + guard let credentials = Storage().getCredentials(server: server.absoluteString) else { + return nil + } - self.init(server: server) + self.init(server: server, credentials: credentials) } - init(server: URL) { + init(server: URL, credentials: Credentials) { self.server = server - self.credentials = Storage.shared.getCredentials(server: server.absoluteString) + self.credentials = credentials } func fetch<T: Request>(request: T, retry: Retry? = nil, completion: @escaping((APIResponse<T.ResponseType>) -> Void)) { diff --git a/ios/Shared/RocketChat/API/Request.swift b/ios/Shared/RocketChat/API/Request.swift index 016e631eba..a1f6a728c5 100644 --- a/ios/Shared/RocketChat/API/Request.swift +++ b/ios/Shared/RocketChat/API/Request.swift @@ -58,9 +58,13 @@ extension Request { if let userId = api.credentials?.userId { request.addValue(userId, forHTTPHeaderField: "x-user-id") + } else { + return nil } if let userToken = api.credentials?.userToken { request.addValue(userToken, forHTTPHeaderField: "x-auth-token") + } else { + return nil } return request diff --git a/ios/Shared/RocketChat/Encryption.swift b/ios/Shared/RocketChat/Encryption.swift index e8a7956d4f..97b1f28baf 100644 --- a/ios/Shared/RocketChat/Encryption.swift +++ b/ios/Shared/RocketChat/Encryption.swift @@ -37,8 +37,9 @@ final class Encryption { private final let encoder = JSONEncoder() init(server: String, rid: String) { - self.privateKey = Storage.shared.getPrivateKey(server: server) - self.credentials = Storage.shared.getCredentials(server: server) + let storage = Storage() + self.privateKey = storage.getPrivateKey(server: server) + self.credentials = storage.getCredentials(server: server) self.server = server self.rid = rid diff --git a/ios/Shared/RocketChat/MMKV.swift b/ios/Shared/RocketChat/MMKV.swift index 69a1eb669d..1359270592 100644 --- a/ios/Shared/RocketChat/MMKV.swift +++ b/ios/Shared/RocketChat/MMKV.swift @@ -5,7 +5,7 @@ extension MMKV { let password = SecureStorage().getSecureKey("com.MMKV.default".toHex()) let groupDir = FileManager.default.groupDir() - MMKV.initialize(rootDir: nil, groupDir: groupDir, logLevel: MMKVLogLevel.none) + MMKV.initialize(rootDir: nil, groupDir: groupDir, logLevel: MMKVLogLevel.info) guard let mmkv = MMKV(mmapID: "default", cryptKey: password?.data(using: .utf8), mode: MMKVMode.multiProcess) else { fatalError("Could not initialize MMKV instance.") diff --git a/ios/Shared/RocketChat/RocketChat.swift b/ios/Shared/RocketChat/RocketChat.swift index 294bfebce5..8a15ed65a5 100644 --- a/ios/Shared/RocketChat/RocketChat.swift +++ b/ios/Shared/RocketChat/RocketChat.swift @@ -15,10 +15,7 @@ final class RocketChat { let server: Server let api: API? - static var instances: [Server: RocketChat] = [:] var encryptionInstances: [RoomId: Encryption] = [:] - - static private var queue = DispatchQueue(label: "chat.rocket.instanceQueue") private var encryptionQueue = DispatchQueue(label: "chat.rocket.encryptionQueue") init(server: Server) { @@ -26,18 +23,6 @@ final class RocketChat { self.api = API(server: server) } - static func instanceForServer(server: Server) -> RocketChat { - queue.sync { - if let rocketchat = instances[server] { - return rocketchat - } - - let rocketchat = RocketChat(server: server) - instances[server] = rocketchat - return rocketchat - } - } - func getPushWithId(_ msgId: String, completion: @escaping((Notification?) -> Void)) { api?.fetch(request: PushRequest(msgId: msgId), retry: Retry(retries: 4)) { response in switch response { diff --git a/ios/Shared/RocketChat/Storage.swift b/ios/Shared/RocketChat/Storage.swift index 019c564193..6793ab5c9d 100644 --- a/ios/Shared/RocketChat/Storage.swift +++ b/ios/Shared/RocketChat/Storage.swift @@ -1,24 +1,50 @@ import Foundation +import Security struct Credentials { - let userId: String - let userToken: String + let userId: String + let userToken: String } final class Storage { - static let shared = Storage() - - private let mmkv = MMKV.build() - - func getCredentials(server: String) -> Credentials? { - guard let userId = mmkv.userId(for: server), let userToken = mmkv.userToken(for: userId) else { - return nil - } - - return .init(userId: userId, userToken: userToken) - } - - func getPrivateKey(server: String) -> String? { - mmkv.privateKey(for: server) - } + private let mmkv = MMKV.build() + + private var appGroupIdentifier: String? { + return Bundle.main.object(forInfoDictionaryKey: "AppGroup") as? String + } + + func getCredentials(server: String) -> Credentials? { + guard let appGroup = appGroupIdentifier else { + return nil + } + + let query: [String: Any] = [ + kSecClass as String: kSecClassInternetPassword, + kSecAttrServer as String: server, + kSecAttrAccessGroup as String: appGroup, + kSecMatchLimit as String: kSecMatchLimitOne, + kSecReturnAttributes as String: true, + kSecReturnData as String: true + ] + + var item: CFTypeRef? + let status = SecItemCopyMatching(query as CFDictionary, &item) + + guard status == errSecSuccess else { + return nil + } + + guard let existingItem = item as? [String: Any], + let account = existingItem[kSecAttrAccount as String] as? String, + let passwordData = existingItem[kSecValueData as String] as? Data, + let password = String(data: passwordData, encoding: .utf8) else { + return nil + } + + return .init(userId: account, userToken: password) + } + + func getPrivateKey(server: String) -> String? { + mmkv.privateKey(for: server) + } } diff --git a/package.json b/package.json index d44e3135a2..b01a323d8c 100644 --- a/package.json +++ b/package.json @@ -100,11 +100,12 @@ "react-native-image-crop-picker": "RocketChat/react-native-image-crop-picker#54e1e1c7e7d4b731c74ce9dd1bf9eb5148065092", "react-native-image-progress": "1.1.1", "react-native-katex": "0.5.1", + "react-native-keychain": "^8.2.0", "react-native-linear-gradient": "2.6.2", "react-native-localize": "2.1.1", "react-native-math-view": "3.9.5", "react-native-mime-types": "2.3.0", - "react-native-mmkv-storage": "^0.9.1", + "react-native-mmkv-storage": "0.9.1", "react-native-modal": "13.0.1", "react-native-navigation-bar-color": "2.0.1", "react-native-notifications": "5.1.0", diff --git a/patches/react-native-mmkv-storage+0.9.1.patch b/patches/react-native-mmkv-storage+0.9.1.patch index 4bc20f740a..b0029db5d6 100644 --- a/patches/react-native-mmkv-storage+0.9.1.patch +++ b/patches/react-native-mmkv-storage+0.9.1.patch @@ -83,3 +83,15 @@ index dbea26b..2483375 100644 if(serviceName == nil){ serviceName = [[NSBundle mainBundle] bundleIdentifier]; } +diff --git a/node_modules/react-native-mmkv-storage/react-native-mmkv-storage.podspec b/node_modules/react-native-mmkv-storage/react-native-mmkv-storage.podspec +index dadb094..97a4754 100644 +--- a/node_modules/react-native-mmkv-storage/react-native-mmkv-storage.podspec ++++ b/node_modules/react-native-mmkv-storage/react-native-mmkv-storage.podspec +@@ -14,5 +14,5 @@ Pod::Spec.new do |s| + s.source_files = "ios/**/*.{h,m,mm}" + s.requires_arc = true + s.dependency 'React-Core' +- s.dependency 'MMKV', '1.2.13' ++ s.dependency "MMKV", ">= 1.3.3" + end +\ No newline at end of file diff --git a/yarn.lock b/yarn.lock index d668f4dbd8..e6bcf75602 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12039,6 +12039,11 @@ react-native-katex@0.5.1: dependencies: react-native-webview "^11.18.2" +react-native-keychain@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/react-native-keychain/-/react-native-keychain-8.2.0.tgz#aea82df37aacbb04f8b567a8e0e6d7292025610a" + integrity sha512-SkRtd9McIl1Ss2XSWNLorG+KMEbgeVqX+gV+t3u1EAAqT8q2/OpRmRbxpneT2vnb/dMhiU7g6K/pf3nxLUXRvA== + react-native-linear-gradient@2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/react-native-linear-gradient/-/react-native-linear-gradient-2.6.2.tgz#56598a76832724b2afa7889747635b5c80948f38" @@ -12066,7 +12071,7 @@ react-native-mime-types@2.3.0: dependencies: mime-db "~1.37.0" -react-native-mmkv-storage@^0.9.1: +react-native-mmkv-storage@0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/react-native-mmkv-storage/-/react-native-mmkv-storage-0.9.1.tgz#0db7e8c1726713dce68704bb8795dc64096c8cbb" integrity sha512-FzSx4PKxK2ocT/OuKGlaVziWZyQYHYLUx9595i1oXY263C5mG19PN5RiBgEGL2S5lK4VGUCzO85GAcsrNPtpOg== From 40e18a4af77faa95274d38c22995e67820634912 Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Mon, 16 Sep 2024 16:20:23 -0300 Subject: [PATCH 21/26] fix: last message preview flickering (#5854) --- .../message/hooks/useMediaAutoDownload.tsx | 5 ++-- app/containers/message/index.tsx | 7 ++++-- app/lib/methods/handleMediaDownload.ts | 2 +- app/lib/methods/updateMessages.ts | 6 +++++ app/views/RoomView/LoadMore/index.tsx | 23 +++++++++++------- app/views/RoomView/index.tsx | 24 +++++++++++-------- 6 files changed, 43 insertions(+), 24 deletions(-) diff --git a/app/containers/message/hooks/useMediaAutoDownload.tsx b/app/containers/message/hooks/useMediaAutoDownload.tsx index 2d1aa5e25c..e7d7c60609 100644 --- a/app/containers/message/hooks/useMediaAutoDownload.tsx +++ b/app/containers/message/hooks/useMediaAutoDownload.tsx @@ -72,7 +72,7 @@ export const useMediaAutoDownload = ({ } return () => { - emitter.off(`downloadMedia${id}`, downloadMediaListener); + emitter.off(`downloadMedia${url}`, downloadMediaListener); }; }, []); @@ -81,7 +81,8 @@ export const useMediaAutoDownload = ({ }, []); const resumeDownload = () => { - emitter.on(`downloadMedia${id}`, downloadMediaListener); + setStatus('loading'); + emitter.on(`downloadMedia${url}`, downloadMediaListener); }; const tryAutoDownload = async () => { diff --git a/app/containers/message/index.tsx b/app/containers/message/index.tsx index ce706a9bdc..37122689e2 100644 --- a/app/containers/message/index.tsx +++ b/app/containers/message/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { ReactElement } from 'react'; import { Keyboard, ViewStyle } from 'react-native'; import Message from './Message'; @@ -60,6 +60,7 @@ interface IMessageContainerProps { closeEmojiAndAction?: (action?: Function, params?: any) => void; isBeingEdited?: boolean; isPreview?: boolean; + separator?: ReactElement | null; } interface IMessageContainerState { @@ -360,7 +361,8 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC jumpToMessage, highlighted, isBeingEdited, - isPreview + isPreview, + separator } = this.props; const { id, @@ -432,6 +434,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC translateLanguage: canTranslateMessage ? autoTranslateLanguage : undefined, isEncrypted: this.isEncrypted }}> + {separator || null} {/* @ts-ignore*/} <Message id={id} diff --git a/app/lib/methods/handleMediaDownload.ts b/app/lib/methods/handleMediaDownload.ts index 367abf9832..9178b194e6 100644 --- a/app/lib/methods/handleMediaDownload.ts +++ b/app/lib/methods/handleMediaDownload.ts @@ -284,7 +284,7 @@ export function downloadMediaFile({ await persistMessage(messageId, result.uri, !!encryption); - emitter.emit(`downloadMedia${messageId}`, result.uri); + emitter.emit(`downloadMedia${downloadUrl}`, result.uri); return resolve(result.uri); } catch (e) { console.error(e); diff --git a/app/lib/methods/updateMessages.ts b/app/lib/methods/updateMessages.ts index 08df5577bb..3fcce5e2e4 100644 --- a/app/lib/methods/updateMessages.ts +++ b/app/lib/methods/updateMessages.ts @@ -136,6 +136,7 @@ export default async function updateMessages({ try { return message.prepareUpdate( protectedFunction((m: TMessageModel) => { + const { attachments } = m; if (newMessage && !newMessage?.blocks) { newMessage.blocks = null; } @@ -143,6 +144,11 @@ export default async function updateMessages({ newMessage.md = undefined; } Object.assign(m, newMessage); + + // If image_url didn't change, keep the same attachments, trying to stick to already downloaded media inside att.title_link (starting with file://) + if (attachments?.[0]?.image_url === newMessage?.attachments?.[0]?.image_url) { + m.attachments = attachments; + } }) ); } catch { diff --git a/app/views/RoomView/LoadMore/index.tsx b/app/views/RoomView/LoadMore/index.tsx index 99d8297eb1..e1c049c2be 100644 --- a/app/views/RoomView/LoadMore/index.tsx +++ b/app/views/RoomView/LoadMore/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { ReactElement, useEffect } from 'react'; import { ActivityIndicator, StyleSheet, Text } from 'react-native'; import { useDispatch } from 'react-redux'; @@ -29,13 +29,15 @@ const LoadMore = React.memo( t, loaderId, type, - runOnRender + runOnRender, + separator }: { rid: string; t: RoomType; loaderId: string; type: MessageType; runOnRender: boolean; + separator?: ReactElement | null; }): React.ReactElement => { const { colors } = useTheme(); const dispatch = useDispatch(); @@ -58,13 +60,16 @@ const LoadMore = React.memo( } return ( - <Touch onPress={handleLoad} style={styles.button} enabled={!loading}> - {loading ? ( - <ActivityIndicator color={colors.fontSecondaryInfo} /> - ) : ( - <Text style={[styles.text, { color: colors.fontTitlesLabels }]}>{I18n.t(text)}</Text> - )} - </Touch> + <> + {separator || null} + <Touch onPress={handleLoad} style={styles.button} enabled={!loading}> + {loading ? ( + <ActivityIndicator color={colors.fontSecondaryInfo} /> + ) : ( + <Text style={[styles.text, { color: colors.fontTitlesLabels }]}>{I18n.t(text)}</Text> + )} + </Touch> + </> ); } ); diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx index 87f7b854e9..9a54a7b046 100644 --- a/app/views/RoomView/index.tsx +++ b/app/views/RoomView/index.tsx @@ -1297,6 +1297,9 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> { dateSeparator = item.ts; } } + + const separator = showUnreadSeparator || dateSeparator ? <Separator ts={dateSeparator} unread={showUnreadSeparator} /> : null; + let content = null; if (item.t && MESSAGE_TYPE_ANY_LOAD.includes(item.t as MessageTypeLoad)) { const runOnRender = () => { @@ -1306,7 +1309,16 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> { } return false; }; - content = <LoadMore rid={room.rid} t={room.t as RoomType} loaderId={item.id} type={item.t} runOnRender={runOnRender()} />; + content = ( + <LoadMore + rid={room.rid} + t={room.t as RoomType} + loaderId={item.id} + type={item.t} + runOnRender={runOnRender()} + separator={separator} + /> + ); } else { if (inAppFeedback?.[item.id]) { this.hapticFeedback(item.id); @@ -1353,19 +1365,11 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> { theme={theme} closeEmojiAndAction={this.handleCloseEmoji} isBeingEdited={isBeingEdited} + separator={separator} /> ); } - if (showUnreadSeparator || dateSeparator) { - return ( - <> - <Separator ts={dateSeparator} unread={showUnreadSeparator} /> - {content} - </> - ); - } - return content; }; From 5225e5eddbe0f09603d735315cbf5fea90b5a1c4 Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Tue, 17 Sep 2024 10:38:03 -0300 Subject: [PATCH 22/26] chore: Remove codecov (#5870) --- .circleci/config.yml | 7 ---- package.json | 1 - yarn.lock | 80 ++------------------------------------------ 3 files changed, 3 insertions(+), 85 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8dd656f556..8f2db51e3f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -383,8 +383,6 @@ jobs: docker: - image: cimg/node:lts resource_class: large - environment: - CODECOV_TOKEN: caa771ab-3d45-4756-8e2a-e1f25996fef6 steps: - checkout @@ -403,11 +401,6 @@ jobs: command: | yarn test --runInBand - - run: - name: Codecov - command: | - yarn codecov - - save_cache: *save-npm-cache-linux # Android builds diff --git a/package.json b/package.json index b01a323d8c..b83b975b19 100644 --- a/package.json +++ b/package.json @@ -194,7 +194,6 @@ "babel-jest": "^29.7.0", "babel-loader": "^9.1.3", "babel-plugin-transform-remove-console": "^6.9.4", - "codecov": "3.8.3", "detox": "^20.20.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", diff --git a/yarn.lock b/yarn.lock index e6bcf75602..37046652e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4149,11 +4149,6 @@ pretty-format "^29.7.0" redent "^3.0.0" -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== - "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" @@ -5097,11 +5092,6 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -argv@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab" - integrity sha512-dEamhpPEwRUBpLNHeuCm/v+g0anFByHahxodVO/BbAarHVBBg2MccCwf9K+o1Pof+2btdnkJelYVUWjW/VrATw== - aria-query@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" @@ -6059,17 +6049,6 @@ co@^4.6.0: resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== -codecov@3.8.3: - version "3.8.3" - resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.8.3.tgz#9c3e364b8a700c597346ae98418d09880a3fdbe7" - integrity sha512-Y8Hw+V3HgR7V71xWH2vQ9lyS358CbGCldWlJFR0JirqoGtOoas3R3/OclRTvgUYFK29mmJICDPauVKmpqbwhOA== - dependencies: - argv "0.0.2" - ignore-walk "3.0.4" - js-yaml "3.14.1" - teeny-request "7.1.1" - urlgrey "1.0.0" - collect-v8-coverage@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" @@ -7816,13 +7795,6 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fast-url-parser@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" - integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ== - dependencies: - punycode "^1.3.2" - fast-xml-parser@^4.0.12, fast-xml-parser@^4.2.4: version "4.4.1" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f" @@ -8606,15 +8578,6 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" -http-proxy-agent@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - http-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" @@ -8624,7 +8587,7 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: +https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== @@ -8661,13 +8624,6 @@ ieee754@^1.1.13: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore-walk@3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" - integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ== - dependencies: - minimatch "^3.0.4" - ignore@^5.0.5, ignore@^5.2.4: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" @@ -9747,7 +9703,7 @@ js-sha256@0.9.0, js-sha256@^0.9.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@3.14.1, js-yaml@^3.13.1: +js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -11764,7 +11720,7 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@^1.3.2, punycode@^1.4.1: +punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== @@ -13301,13 +13257,6 @@ stream-chain@^2.2.5: resolved "https://registry.yarnpkg.com/stream-chain/-/stream-chain-2.2.5.tgz#b30967e8f14ee033c5b9a19bbe8a2cba90ba0d09" integrity sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA== -stream-events@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5" - integrity sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg== - dependencies: - stubs "^3.0.0" - stream-json@^1.7.4, stream-json@^1.7.5: version "1.8.0" resolved "https://registry.yarnpkg.com/stream-json/-/stream-json-1.8.0.tgz#53f486b2e3b4496c506131f8d7260ba42def151c" @@ -13555,11 +13504,6 @@ structured-headers@^0.4.1: resolved "https://registry.yarnpkg.com/structured-headers/-/structured-headers-0.4.1.tgz#77abd9410622c6926261c09b9d16cf10592694d1" integrity sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg== -stubs@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b" - integrity sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw== - sucrase@3.34.0: version "3.34.0" resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.34.0.tgz#1e0e2d8fcf07f8b9c3569067d92fbd8690fb576f" @@ -13708,17 +13652,6 @@ tar@^6.0.2, tar@^6.0.5: mkdirp "^1.0.3" yallist "^4.0.0" -teeny-request@7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-7.1.1.tgz#2b0d156f4a8ad81de44303302ba8d7f1f05e20e6" - integrity sha512-iwY6rkW5DDGq8hE2YgNQlKbptYpY5Nn2xecjQiNjOXWbKzPGUfmeUBCSQbbr306d7Z7U2N0TPl+/SwYRfua1Dg== - dependencies: - http-proxy-agent "^4.0.0" - https-proxy-agent "^5.0.0" - node-fetch "^2.6.1" - stream-events "^1.0.5" - uuid "^8.0.0" - telejson@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/telejson/-/telejson-7.2.0.tgz#3994f6c9a8f8d7f2dba9be2c7c5bbb447e876f32" @@ -14363,13 +14296,6 @@ url@^0.11.0: punycode "^1.4.1" qs "^6.11.2" -urlgrey@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-1.0.0.tgz#72d2f904482d0b602e3c7fa599343d699bbe1017" - integrity sha512-hJfIzMPJmI9IlLkby8QrsCykQ+SXDeO2W5Q9QTW3QpqZVTx4a/K7p8/5q+/isD8vsbVaFgql/gvAoQCRQ2Cb5w== - dependencies: - fast-url-parser "^1.1.3" - use-debounce@9.0.4: version "9.0.4" resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-9.0.4.tgz#51d25d856fbdfeb537553972ce3943b897f1ac85" From f77e1760ad990d38c1df2a7fa05e5a65761ed7e1 Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Tue, 24 Sep 2024 10:00:16 -0300 Subject: [PATCH 23/26] feat: E2EE messages mentions (#5744) --- app/lib/constants/defaultSettings.ts | 6 ++++++ app/lib/encryption/room.ts | 2 ++ app/lib/encryption/utils.ts | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/app/lib/constants/defaultSettings.ts b/app/lib/constants/defaultSettings.ts index ab1b4ea150..290d447ff7 100644 --- a/app/lib/constants/defaultSettings.ts +++ b/app/lib/constants/defaultSettings.ts @@ -258,5 +258,11 @@ export const defaultSettings = { Accounts_ConfirmPasswordPlaceholder: { type: 'valueAsString' }, + E2E_Enabled_Mentions: { + type: 'valueAsBoolean' + }, + UTF8_User_Names_Validation: { + type: 'valueAsString' + }, ...deprecatedSettings } as const; diff --git a/app/lib/encryption/room.ts b/app/lib/encryption/room.ts index 10cd50fda0..247d6f9f87 100644 --- a/app/lib/encryption/room.ts +++ b/app/lib/encryption/room.ts @@ -16,6 +16,7 @@ import { bufferToB64, bufferToB64URI, bufferToUtf8, + getE2EEMentions, encryptAESCTR, exportAESCTR, generateAESCTRKey, @@ -245,6 +246,7 @@ export default class EncryptionRoom { t: E2E_MESSAGE_TYPE, e2e: E2E_STATUS.PENDING, msg, + e2eMentions: getE2EEMentions(message.msg), content: { algorithm: 'rc.v1.aes-sha2' as const, ciphertext: await this.encryptText( diff --git a/app/lib/encryption/utils.ts b/app/lib/encryption/utils.ts index a79a55a6cd..71787799f1 100644 --- a/app/lib/encryption/utils.ts +++ b/app/lib/encryption/utils.ts @@ -59,6 +59,24 @@ export const toString = (thing: string | ByteBuffer | Buffer | ArrayBuffer | Uin // @ts-ignore return new ByteBuffer.wrap(thing).toString('binary'); }; + +// https://github.com/RocketChat/Rocket.Chat/blob/b94db45cab297a3bcbafca4d135d83c898222380/apps/meteor/app/mentions/lib/MentionsParser.ts#L50 +const userMentionRegex = (pattern: string) => new RegExp(`(^|\\s|>)@(${pattern}(@(${pattern}))?(:([0-9a-zA-Z-_.]+))?)`, 'gm'); +const channelMentionRegex = (pattern: string) => new RegExp(`(^|\\s|>)#(${pattern}(@(${pattern}))?)`, 'gm'); + +export const getE2EEMentions = (message?: string) => { + const e2eEnabledMentions = store.getState().settings.E2E_Enabled_Mentions; + if (!e2eEnabledMentions || !message) { + return undefined; + } + const utf8UserNamesValidation = store.getState().settings.UTF8_User_Names_Validation as string; + + return { + e2eUserMentions: (message.match(userMentionRegex(utf8UserNamesValidation)) || []).map(match => match.trim()), + e2eChannelMentions: (message.match(channelMentionRegex(utf8UserNamesValidation)) || []).map(match => match.trim()) + }; +}; + export const randomPassword = async (): Promise<string> => { const random = await Promise.all(Array.from({ length: 4 }, () => SimpleCrypto.utils.getRandomValues(3))); return `${random[0]}-${random[1]}-${random[2]}-${random[3]}`; From e1f58bc79b1e9a40028569d21c80c5ae6e49cf8c Mon Sep 17 00:00:00 2001 From: Yash Rajpal <58601732+yash-rajpal@users.noreply.github.com> Date: Tue, 24 Sep 2024 22:06:35 +0530 Subject: [PATCH 24/26] feat(a11y): Color and contrast improvements (#5869) --- app/containers/Button/Button.stories.tsx | 2 +- app/containers/Button/Button.test.tsx | 4 ++-- app/containers/Button/index.tsx | 2 +- app/containers/CallHeader.tsx | 2 +- app/containers/Check.tsx | 2 +- app/containers/TwoFactor/index.tsx | 13 +++---------- app/containers/markdown/Hashtag.tsx | 4 ++-- app/lib/constants/colors.ts | 4 ++-- .../hooks/useVideoConf/StartACallActionSheet.tsx | 8 +++----- app/lib/methods/helpers/navigation/index.ts | 6 ++++-- app/views/RoomsListView/ServersList.tsx | 4 ++-- 11 files changed, 22 insertions(+), 29 deletions(-) diff --git a/app/containers/Button/Button.stories.tsx b/app/containers/Button/Button.stories.tsx index d58fcdbe14..2ecf791037 100644 --- a/app/containers/Button/Button.stories.tsx +++ b/app/containers/Button/Button.stories.tsx @@ -4,7 +4,7 @@ import Button from '.'; const buttonProps = { title: 'Press me!', - type: 'primary', + type: 'primary' as const, onPress: () => {}, testID: 'testButton' }; diff --git a/app/containers/Button/Button.test.tsx b/app/containers/Button/Button.test.tsx index 6d89faa452..9fd76940fa 100644 --- a/app/containers/Button/Button.test.tsx +++ b/app/containers/Button/Button.test.tsx @@ -8,7 +8,7 @@ const onPressMock = jest.fn(); const testProps = { title: 'Press me!', - type: 'primary', + type: 'primary' as const, onPress: onPressMock, testID: 'testButton', initialText: 'Initial text', @@ -19,7 +19,7 @@ const TestButton = ({ loading = false, disabled = false }) => ( <View> <Button title={testProps.title} - type={testProps.title} + type={testProps.type} onPress={testProps.onPress} testID={testProps.testID} accessibilityLabel={testProps.title} diff --git a/app/containers/Button/index.tsx b/app/containers/Button/index.tsx index 1632cded4a..1e3c56e04f 100644 --- a/app/containers/Button/index.tsx +++ b/app/containers/Button/index.tsx @@ -9,7 +9,7 @@ import ActivityIndicator from '../ActivityIndicator'; interface IButtonProps extends PlatformTouchableProps { title: string; onPress: () => void; - type?: string; // primary | secondary + type?: 'primary' | 'secondary'; backgroundColor?: string; loading?: boolean; color?: string; diff --git a/app/containers/CallHeader.tsx b/app/containers/CallHeader.tsx index a0ca72520f..10fde5cd90 100644 --- a/app/containers/CallHeader.tsx +++ b/app/containers/CallHeader.tsx @@ -33,7 +33,7 @@ export const CallHeader = ({ mic, cam, setCam, setMic, title, avatar, uid, name, if (enabled) return { button: colors.buttonBackgroundSecondaryDisabled, icon: colors.strokeExtraDark }; return { button: 'transparent', icon: colors.strokeLight }; } - if (enabled) return { button: colors.strokeHighlight, icon: colors.surfaceLight }; + if (enabled) return { button: colors.buttonBackgroundPrimaryDefault, icon: colors.surfaceLight }; return { button: 'transparent', icon: colors.strokeExtraDark }; }; diff --git a/app/containers/Check.tsx b/app/containers/Check.tsx index d97b71f9ee..5f47f52fb1 100644 --- a/app/containers/Check.tsx +++ b/app/containers/Check.tsx @@ -15,7 +15,7 @@ const styles = StyleSheet.create({ const Check = React.memo(() => { const { theme } = useTheme(); - return <CustomIcon style={styles.icon} color={themes[theme].badgeBackgroundLevel2} size={22} name='check' />; + return <CustomIcon style={styles.icon} color={themes[theme].fontInfo} size={22} name='check' />; }); export default Check; diff --git a/app/containers/TwoFactor/index.tsx b/app/containers/TwoFactor/index.tsx index b184227533..376f1fc98a 100644 --- a/app/containers/TwoFactor/index.tsx +++ b/app/containers/TwoFactor/index.tsx @@ -110,9 +110,8 @@ const TwoFactor = React.memo(({ isMasterDetail }: { isMasterDetail: boolean }) = style={[ styles.content, isMasterDetail && [sharedStyles.modalFormSheet, styles.tablet], - { backgroundColor: themes[theme].surfaceHover } - ]} - > + { backgroundColor: themes[theme].surfaceTint } + ]}> <Text style={[styles.title, { color }]}>{I18n.t(method?.title || 'Two_Factor_Authentication')}</Text> {method?.text ? <Text style={[styles.subtitle, { color }]}>{I18n.t(method.text)}</Text> : null} <FormTextInput @@ -133,13 +132,7 @@ const TwoFactor = React.memo(({ isMasterDetail }: { isMasterDetail: boolean }) = </Text> ) : null} <View style={styles.buttonContainer}> - <Button - title={I18n.t('Cancel')} - type='secondary' - backgroundColor={themes[theme].surfaceTint} - style={styles.button} - onPress={onCancel} - /> + <Button title={I18n.t('Cancel')} type='secondary' style={styles.button} onPress={onCancel} /> <Button title={I18n.t('Verify')} type='primary' style={styles.button} onPress={onSubmit} testID='two-factor-send' /> </View> </View> diff --git a/app/containers/markdown/Hashtag.tsx b/app/containers/markdown/Hashtag.tsx index 2433ec897e..79b9912433 100644 --- a/app/containers/markdown/Hashtag.tsx +++ b/app/containers/markdown/Hashtag.tsx @@ -54,7 +54,7 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IH style={[ styles.mention, { - color: themes[theme].badgeBackgroundLevel1 + color: themes[theme].fontInfo }, ...style ]} @@ -63,7 +63,7 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IH </Text> ); } - return <Text style={[styles.text, { color: themes[theme].fontDefault }, ...style]}>{`#${hashtag}`}</Text>; + return <Text style={[styles.text, { color: themes[theme].fontInfo }, ...style]}>{`#${hashtag}`}</Text>; }); export default Hashtag; diff --git a/app/lib/constants/colors.ts b/app/lib/constants/colors.ts index 52a10b2551..9e7753c20a 100644 --- a/app/lib/constants/colors.ts +++ b/app/lib/constants/colors.ts @@ -23,7 +23,7 @@ const light = { strokeError: '#EC0D2A', fontWhite: '#FFFFFF', - fontDisabled: '#CBCED1', + fontDisabled: '#FFFFFF', fontAnnotation: '#9EA2A8', fontHint: '#6C727A', fontSecondaryInfo: '#6C727A', @@ -152,7 +152,7 @@ const dark = { userPresenceOffline: '#6C727A', userPresenceDisabled: '#955828', - buttonBackgroundPrimaryDefault: '#3976D1', + buttonBackgroundPrimaryDefault: '#095AD2', buttonBackgroundPrimaryPress: '#245399', buttonBackgroundPrimaryDisabled: '#1D3963', diff --git a/app/lib/hooks/useVideoConf/StartACallActionSheet.tsx b/app/lib/hooks/useVideoConf/StartACallActionSheet.tsx index e2445d1c8e..c5a4601caf 100644 --- a/app/lib/hooks/useVideoConf/StartACallActionSheet.tsx +++ b/app/lib/hooks/useVideoConf/StartACallActionSheet.tsx @@ -47,8 +47,7 @@ export default function StartACallActionSheet({ return ( <View style={[style.actionSheetContainer, { paddingBottom: bottom }]} - onLayout={e => setContainerWidth(e.nativeEvent.layout.width / 2)} - > + onLayout={e => setContainerWidth(e.nativeEvent.layout.width / 2)}> {calling && roomType === SubscriptionType.DIRECT ? <Ringer ringer={ERingerSounds.DIALTONE} /> : null} <CallHeader title={calling && user.direct ? i18n.t('Calling') : i18n.t('Start_a_call')} @@ -65,8 +64,7 @@ export default function StartACallActionSheet({ style={[ style.actionSheetPhotoContainer, { backgroundColor: cam ? undefined : colors.surfaceNeutral, width: containerWidth } - ]} - > + ]}> {cam ? ( <Camera style={[style.cameraContainer, { width: containerWidth }]} type={CameraType.front} /> ) : ( @@ -75,7 +73,7 @@ export default function StartACallActionSheet({ </View> <Button backgroundColor={calling ? colors.buttonBackgroundPrimaryDisabled : colors.buttonBackgroundPrimaryDefault} - color={calling ? colors.strokeDark : colors.surfaceLight} + color={calling ? colors.strokeDark : colors.fontWhite} onPress={() => { if (calling) { dispatch(cancelCall({})); diff --git a/app/lib/methods/helpers/navigation/index.ts b/app/lib/methods/helpers/navigation/index.ts index e20101c47f..a7a8d43617 100644 --- a/app/lib/methods/helpers/navigation/index.ts +++ b/app/lib/methods/helpers/navigation/index.ts @@ -1,5 +1,6 @@ import { StyleSheet } from 'react-native'; import { DarkTheme, DefaultTheme } from '@react-navigation/native'; +import { StackNavigationOptions } from '@react-navigation/stack'; import { themes } from '../../../constants'; import { TSupportedThemes } from '../../../../theme'; @@ -7,11 +8,12 @@ import sharedStyles from '../../../../views/Styles'; export * from './animations'; -export const defaultHeader = { +export const defaultHeader: StackNavigationOptions = { headerBackTitleVisible: false, headerBackTestID: 'header-back', cardOverlayEnabled: true, - cardStyle: { backgroundColor: 'transparent' } + cardStyle: { backgroundColor: 'transparent' }, + headerTitleAlign: 'left' }; export const cardStyle = { diff --git a/app/views/RoomsListView/ServersList.tsx b/app/views/RoomsListView/ServersList.tsx index b98eb41a78..452c2664ed 100644 --- a/app/views/RoomsListView/ServersList.tsx +++ b/app/views/RoomsListView/ServersList.tsx @@ -136,7 +136,7 @@ const ServersList = () => { <View style={[styles.serversListContainerHeader, styles.serverHeader, { borderColor: colors.strokeLight }]}> <Text style={[styles.serverHeaderText, { color: colors.fontSecondaryInfo }]}>{I18n.t('Server')}</Text> <TouchableOpacity onPress={addServer} testID='rooms-list-header-server-add'> - <Text style={[styles.serverHeaderAdd, { color: colors.badgeBackgroundLevel2 }]}>{I18n.t('Add_Server')}</Text> + <Text style={[styles.serverHeaderAdd, { color: colors.fontInfo }]}>{I18n.t('Add_Server')}</Text> </TouchableOpacity> </View> <FlatList @@ -156,7 +156,7 @@ const ServersList = () => { style={styles.buttonCreateWorkspace} color={colors.badgeBackgroundLevel2} backgroundColor={colors.surfaceRoom} - styleText={[styles.serverHeaderAdd, { textAlign: 'center' }]} + styleText={[styles.serverHeaderAdd, { textAlign: 'center', color: colors.fontInfo }]} /> </View> ); From 19f0288411a696cbc5e67792d8480058058e0249 Mon Sep 17 00:00:00 2001 From: Yash Rajpal <58601732+yash-rajpal@users.noreply.github.com> Date: Tue, 24 Sep 2024 22:53:57 +0530 Subject: [PATCH 25/26] feat: Help page (#5855) Co-authored-by: Diego Mello <diegolmello@gmail.com> --- app/containers/List/ListIcon.tsx | 2 +- app/containers/NewWindowIcon.tsx | 10 +++ app/i18n/locales/ar.json | 5 ++ app/i18n/locales/bn-IN.json | 5 ++ app/i18n/locales/cs.json | 5 ++ app/i18n/locales/de.json | 5 ++ app/i18n/locales/en.json | 5 ++ app/i18n/locales/es.json | 5 ++ app/i18n/locales/fi.json | 5 ++ app/i18n/locales/fr.json | 5 ++ app/i18n/locales/hi-IN.json | 5 ++ app/i18n/locales/hu.json | 5 ++ app/i18n/locales/it.json | 5 ++ app/i18n/locales/ja.json | 5 ++ app/i18n/locales/nl.json | 5 ++ app/i18n/locales/pt-BR.json | 5 ++ app/i18n/locales/pt-PT.json | 5 ++ app/i18n/locales/ru.json | 5 ++ app/i18n/locales/sl-SI.json | 5 ++ app/i18n/locales/sv.json | 5 ++ app/i18n/locales/ta-IN.json | 5 ++ app/i18n/locales/te-IN.json | 5 ++ app/i18n/locales/tr.json | 5 ++ app/i18n/locales/zh-CN.json | 5 ++ app/i18n/locales/zh-TW.json | 5 ++ app/stacks/InsideStack.tsx | 2 + app/stacks/types.ts | 1 + app/views/GetHelpView.tsx | 61 +++++++++++++++++++ .../components/NotificationDelay.tsx | 3 +- app/views/SettingsView/index.tsx | 15 ++++- e2e/tests/assorted/04-setting.spec.ts | 13 +++- 31 files changed, 216 insertions(+), 6 deletions(-) create mode 100644 app/containers/NewWindowIcon.tsx create mode 100644 app/views/GetHelpView.tsx diff --git a/app/containers/List/ListIcon.tsx b/app/containers/List/ListIcon.tsx index df22f38719..2e074421ca 100644 --- a/app/containers/List/ListIcon.tsx +++ b/app/containers/List/ListIcon.tsx @@ -5,7 +5,7 @@ import { CustomIcon, TIconsName } from '../CustomIcon'; import { ICON_SIZE } from './constants'; import { useTheme } from '../../theme'; -interface IListIcon { +export interface IListIcon { name: TIconsName; color?: string; style?: StyleProp<ViewStyle>; diff --git a/app/containers/NewWindowIcon.tsx b/app/containers/NewWindowIcon.tsx new file mode 100644 index 0000000000..da984412c9 --- /dev/null +++ b/app/containers/NewWindowIcon.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { I18nManager } from 'react-native'; + +import ListIcon, { IListIcon } from './List/ListIcon'; + +const NewWindowIcon = (props: Omit<IListIcon, 'name'>) => ( + <ListIcon name='new-window' style={I18nManager.isRTL ? { transform: [{ rotateY: '180deg' }] } : null} {...props} /> +); + +export default NewWindowIcon; diff --git a/app/i18n/locales/ar.json b/app/i18n/locales/ar.json index c3bac972bf..69252aec1b 100644 --- a/app/i18n/locales/ar.json +++ b/app/i18n/locales/ar.json @@ -1,6 +1,7 @@ { "1_person_reacted": "تفاعل شخص واحد", "A_meaningful_name_for_the_discussion_room": "اسم معبر لغرفة النقاش", + "Accessibility_statement": "بيان الوصول", "Actions": "الإجراءات", "Activity": "النشاط", "Add_Server": "إضافة خادم", @@ -163,6 +164,9 @@ "Forward_to_user": "إعادة توجيه لمستخدم", "Full_table": "انقر لرؤية الجدول كاملاً", "Generate_New_Link": "إنشاء رابط جديد", + "Get_help": "احصل على المساعدة", + "Glossary_of_simplified_terms": "مسرد المصطلحات المبسطة", + "Help": "مساعدة", "Hide_room": "إخفاء", "Hide_System_Messages": "إخفاء رسائل النظام", "Hide_type_messages": "إخفاء رسائل \"{{type}}\"", @@ -352,6 +356,7 @@ "Review_app_unable_store": "لم يتمكن من فتح {{store}}", "Review_app_yes": "أكيد!", "Review_this_app": "تقييم هذا التطبيق", + "Rocket_Chat_Documentation": "توثيق Rocket.Chat", "Roles": "أدوار", "Room_Info": "معلومات الغرفة", "Room_Info_Edit": "تعديل معلومات الغرفة", diff --git a/app/i18n/locales/bn-IN.json b/app/i18n/locales/bn-IN.json index 06719eb495..bd3fda9693 100644 --- a/app/i18n/locales/bn-IN.json +++ b/app/i18n/locales/bn-IN.json @@ -6,6 +6,7 @@ "A_new_owner_will_be_assigned_automatically_to__count__room": "{{count}} রুমে নতুন মালিক স্বয়ংক্রিয়ভাবে নির্ধারণ করা হবে।", "A_new_owner_will_be_assigned_automatically_to__count__rooms": "{{count}} রুমে নতুন মালিক স্বয়ংক্রিয়ভাবে নির্ধারণ করা হবে।", "accept": "গ্রহণ করুন", + "Accessibility_statement": "অ্যাক্সেসিবিলিটি বিবৃতি", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "ব্যবহারকারীদের 'সেইমসঙ্গে চ্যানেলে প্রেরণ করুন' আচরণ নির্ধারণ করার অনুমতি দিন", "Actions": "ক্রিয়াবলী", "Activity": "ক্রিয়া", @@ -275,9 +276,12 @@ "Forward_to_user": "ব্যবহারকারীকে ফরোয়ার্ড", "Full_table": "পুরো টেবিল দেখতে ক্লিক করুন", "Generate_New_Link": "নতুন লিঙ্ক তৈরি করুন", + "Get_help": "সাহায্য পান", "Get_link": "লিঙ্ক পান", + "Glossary_of_simplified_terms": "সরলীকৃত শর্তগুলির গ্লোসারি", "Group_by": "দ্বারা গ্রুপ করুন", "Has_left_the_team": "দল ছেড়ে দিয়েছে", + "Help": "সাহায্য", "Hide_System_Messages": "সিস্টেম বার্তা লুকান", "Hide_type_messages": "\"{{type}}\" বার্তা লুকান", "How_It_Works": "এটি কীভাবে কাজ করে", @@ -542,6 +546,7 @@ "Review_app_unable_store": "{{store}} খোলতে অক্ষম", "Review_app_yes": "হ্যাঁ!", "Review_this_app": "এই অ্যাপটি পর্যালোচনা করুন", + "Rocket_Chat_Documentation": "Rocket.Chat ডকুমেন্টেশন", "Roles": "ভূমিকা", "room_allowed_reactions": "রিয়েকশন অনুমতি দেওয়া হয়েছে", "room_archived": "আর্কাইভ করা রুম", diff --git a/app/i18n/locales/cs.json b/app/i18n/locales/cs.json index a5334a0ec1..a561550396 100644 --- a/app/i18n/locales/cs.json +++ b/app/i18n/locales/cs.json @@ -6,6 +6,7 @@ "A_new_owner_will_be_assigned_automatically_to__count__room": "K {{count}} místnosti bude automaticky přiřazen nový vlastník.", "A_new_owner_will_be_assigned_automatically_to__count__rooms": "K {{count}} místnostem bude automaticky přiřazen nový vlastník.", "accept": "Akceptovat", + "Accessibility_statement": "Prohlášení o přístupnosti", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Povolit uživatelům vybrat chování 'Také odeslat do kanálu'", "Actions": "Akce", "Activity": "Aktivita", @@ -294,10 +295,13 @@ "Forward_to_user": "Přeposlat uživateli", "Full_table": "Kliknutím zobrazíte celou tabulku", "Generate_New_Link": "Vygenerovat nový odkaz", + "Get_help": "Získat pomoc", "Get_link": "Získat odkaz", + "Glossary_of_simplified_terms": "Slovník zjednodušených termínů", "Go_to_your_device_settings_and_allow_microphone": "Přejděte do nastavení zařízení a povolte přístup k mikrofonu pro Rocket.Chat", "Group_by": "Skupina vytvořená", "Has_left_the_team": "opustil tým", + "Help": "Pomoc", "Hide_room": "Skrýt", "Hide_System_Messages": "Skrýt systémové zprávy", "Hide_type_messages": "Skrýt \"{{type}}\" zprávy", @@ -592,6 +596,7 @@ "Review_app_unable_store": "Nelze otevřít {{store}}", "Review_app_yes": "Tak určitě!", "Review_this_app": "Zkontrolovat tuto aplikaci", + "Rocket_Chat_Documentation": "Dokumentace Rocket.Chat", "Roles": "Role", "room_allowed_reactions": "povolené reakce", "room_archived": "archivovaná místnost", diff --git a/app/i18n/locales/de.json b/app/i18n/locales/de.json index bf58637a52..dc565d4a07 100644 --- a/app/i18n/locales/de.json +++ b/app/i18n/locales/de.json @@ -6,6 +6,7 @@ "A_new_owner_will_be_assigned_automatically_to__count__room": "Ein neuer Besitzer wird automatisch zu {{count}} Raum zugeordnet.", "A_new_owner_will_be_assigned_automatically_to__count__rooms": "Ein neuer Besitzer wird automatisch zu {{count}} Räumen zugeordnet.", "accept": "Annehmen", + "Accessibility_statement": "Barrierefreiheits-Erklärung", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Nutzern erlauben das Verhalten für \"auch an den Kanal senden\" zu bestimmen.", "Actions": "Aktionen", "Activity": "Aktivität", @@ -268,9 +269,12 @@ "Forward_to_user": "Weiterleiten an Benutzer", "Full_table": "Klicken, um die ganze Tabelle anzuzeigen", "Generate_New_Link": "Neuen Link erstellen", + "Get_help": "Hilfe holen", "Get_link": "Link erhalten", + "Glossary_of_simplified_terms": "Glossar vereinfachter Begriffe", "Group_by": "Gruppieren nach", "Has_left_the_team": "Hat das Team verlassen", + "Help": "Hilfe", "Hide_room": "Raum verstecken", "Hide_System_Messages": "Systemnachrichten ausblenden", "Hide_type_messages": "\"{{type}}\"-Nachrichten ausblenden", @@ -530,6 +534,7 @@ "Review_app_unable_store": "Kann {{store}} nicht öffnen", "Review_app_yes": "Sicher!", "Review_this_app": "App bewerten", + "Rocket_Chat_Documentation": "Rocket.Chat Dokumentation", "Roles": "Rollen", "room_allowed_reactions": "hat Reaktionen erlaubt", "room_archived": "hat den Raum archiviert", diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index ab49887112..95a36428d9 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -6,6 +6,7 @@ "A_new_owner_will_be_assigned_automatically_to__count__room": "A new owner will be assigned automatically to {{count}} room.", "A_new_owner_will_be_assigned_automatically_to__count__rooms": "A new owner will be assigned automatically to {{count}} rooms.", "accept": "Accept", + "Accessibility_statement": "Accessibility statement", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Allow users to select the 'Also send to channel' behavior", "Actions": "Actions", "Activity": "Activity", @@ -310,10 +311,13 @@ "Forward_to_user": "Forward to user", "Full_table": "Click to see full table", "Generate_New_Link": "Generate new link", + "Get_help": "Get help", "Get_link": "Get link", + "Glossary_of_simplified_terms": "Glossary of simplified terms", "Go_to_your_device_settings_and_allow_microphone": "Go to your device settings and allow microphone access for Rocket.Chat", "Group_by": "Group by", "Has_left_the_team": "has left the team", + "Help": "Help", "Hide_room": "Hide", "Hide_System_Messages": "Hide system messages", "Hide_type_messages": "Hide \"{{type}}\" messages", @@ -611,6 +615,7 @@ "Review_app_unable_store": "Unable to open {{store}}", "Review_app_yes": "Sure!", "Review_this_app": "Review this app", + "Rocket_Chat_Documentation": "Rocket.Chat documentation", "Roles": "Roles", "room_allowed_reactions": "allowed reactions", "room_archived": "archived room", diff --git a/app/i18n/locales/es.json b/app/i18n/locales/es.json index 6273ba700c..e8f766c798 100644 --- a/app/i18n/locales/es.json +++ b/app/i18n/locales/es.json @@ -1,5 +1,6 @@ { "1_person_reacted": "1 persona reaccionó", + "Accessibility_statement": "Declaración de accesibilidad", "Actions": "Acciones", "Activity": "Actividad", "Add_Server": "Añadir servidor", @@ -108,7 +109,10 @@ "Forgot_password": "¿Ha olvidado su contraseña?", "Forgot_password_If_this_email_is_registered": "Si este email está registrado, te enviaremos las instrucciones para resetear tu contraseña. Si no recibes un email en breve, vuelve aquí e inténtalo de nuevo.", "Full_table": "Click para ver la tabla completa", + "Get_help": "Obtener ayuda", "Get_link": "Obtener enlace", + "Glossary_of_simplified_terms": "Glosario de términos simplificados", + "Help": "Ayuda", "Hide_room": "Ocultar", "In_App_And_Desktop": "En la aplicación y en el escritorio", "In_App_and_Desktop_Alert_info": "Muestra un banner en la parte superior de la pantalla cuando la aplicación esté abierta y muestra una notificación en el escritorio", @@ -203,6 +207,7 @@ "RESET": "RESET", "Reset_password": "Resetear contraseña", "resetting_password": "reseteando contraseña", + "Rocket_Chat_Documentation": "Documentación de Rocket.Chat", "Roles": "Roles", "Room_Info": "Información de la sala", "Room_Info_Edit": "Editar información de la sala", diff --git a/app/i18n/locales/fi.json b/app/i18n/locales/fi.json index 97f5504a4c..04210d90cf 100644 --- a/app/i18n/locales/fi.json +++ b/app/i18n/locales/fi.json @@ -5,6 +5,7 @@ "A_meaningful_name_for_the_discussion_room": "Mielekäs nimi keskusteluhuoneelle", "A_new_owner_will_be_assigned_automatically_to__count__room": "Uusi omistaja liitetään automaattisesti {{count}} huoneeseen.", "A_new_owner_will_be_assigned_automatically_to__count__rooms": "Uusi omistaja liitetään automaattisesti {{count}} huoneeseen.", + "Accessibility_statement": "Saavutettavuusilmoitus", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Salli käyttäjien valita Lähetä myös kanavalle -toiminta", "Actions": "Toimet", "Activity": "Toiminta", @@ -253,9 +254,12 @@ "Forward_to_user": "Välitä käyttäjälle", "Full_table": "Näytä koko taulukko napsauttamalla", "Generate_New_Link": "Luo uusi linkki", + "Get_help": "Hae apua", "Get_link": "Hanki linkki", + "Glossary_of_simplified_terms": "Yksinkertaistetut termit -sanasto", "Group_by": "Ryhmittelyperuste", "Has_left_the_team": "on poistunut tiimistä", + "Help": "Apua", "Hide_room": "Piilota", "Hide_System_Messages": "Piilota järjestelmäilmoitukset", "Hide_type_messages": "Piilota \"{{type}}\"-viestit", @@ -505,6 +509,7 @@ "Review_app_unable_store": "Ei voida avata: {{store}}", "Review_app_yes": "Selvä!", "Review_this_app": "Arvioi tämä sovellus", + "Rocket_Chat_Documentation": "Rocket.Chat dokumentaatio", "Roles": "Roolit", "room_allowed_reactions": "salli reaktiot", "room_archived": "arkistoi huoneen", diff --git a/app/i18n/locales/fr.json b/app/i18n/locales/fr.json index 0458384c79..69c14e3545 100644 --- a/app/i18n/locales/fr.json +++ b/app/i18n/locales/fr.json @@ -1,6 +1,7 @@ { "1_person_reacted": "1 personne a réagi", "A_meaningful_name_for_the_discussion_room": "Un nom significatif pour le salon de discussion", + "Accessibility_statement": "Déclaration d'accessibilité", "Actions": "Actions", "Activity": "Activité", "Add_Channel_to_Team": "Ajouter un canal à l'équipe", @@ -217,9 +218,12 @@ "Forward_to_user": "Transmettre à l'utilisateur", "Full_table": "Cliquez pour voir le tableau complet", "Generate_New_Link": "Générer un nouveau lien", + "Get_help": "Obtenez de l'aide", "Get_link": "Obtenir le lien", + "Glossary_of_simplified_terms": "Glossaire des termes simplifiés", "Group_by": "Grouper par", "Has_left_the_team": "a quitté l'équipe", + "Help": "Aide", "Hide_room": "Masquer", "Hide_System_Messages": "Masquer les messages système", "Hide_type_messages": "Masquer les messages \"{{type}}\"", @@ -458,6 +462,7 @@ "Review_app_unable_store": "Impossible d'ouvrir {{store}}", "Review_app_yes": "Bien sûr !", "Review_this_app": "Donnez votre avis sur cette application", + "Rocket_Chat_Documentation": "Documentation Rocket.Chat", "Roles": "Rôles", "Room_Info": "Info sur le salon", "Room_Info_Edit": "Modifier les informations du salon", diff --git a/app/i18n/locales/hi-IN.json b/app/i18n/locales/hi-IN.json index a3b9d1b525..99ecdb2469 100644 --- a/app/i18n/locales/hi-IN.json +++ b/app/i18n/locales/hi-IN.json @@ -6,6 +6,7 @@ "A_new_owner_will_be_assigned_automatically_to__count__room": "{{count}} कमरे को स्वच्छता से नया मालिक सौंपा जाएगा।", "A_new_owner_will_be_assigned_automatically_to__count__rooms": "{{count}} कमरों को स्वच्छता से नया मालिक सौंपा जाएगा।", "accept": "स्वीकार करें", + "Accessibility_statement": "पहुंच उपलब्धता विवरण", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "उपयोगकर्ताओं को 'चैनल में भी भेजें' व्यवहार का चयन करने की अनुमति दें", "Actions": "क्रियाएँ", "Activity": "गतिविधि", @@ -275,9 +276,12 @@ "Forward_to_user": "उपयोगकर्ता को आगे बढ़ाएं", "Full_table": "पूर्ण तालिका देखने के लिए क्लिक करें", "Generate_New_Link": "नई लिंक बनाएं", + "Get_help": "सहायता प्राप्त करें", "Get_link": "लिंक प्राप्त करें", + "Glossary_of_simplified_terms": "सरल शब्दावली", "Group_by": "इसके अनुसार समूहीकृत करें", "Has_left_the_team": "टीम छोड़ गया है", + "Help": "मदद", "Hide_System_Messages": "सिस्टम संदेश छुपाएं", "Hide_type_messages": "\"{{type}}\" संदेश छुपाएं", "How_It_Works": "यह कैसे काम करता है", @@ -542,6 +546,7 @@ "Review_app_unable_store": "{{store}} नहीं खोल सका जा रहा है", "Review_app_yes": "बिल्कुल!", "Review_this_app": "इस ऐप की समीक्षा करें", + "Rocket_Chat_Documentation": "Rocket.Chat दस्तावेज़ीकरण", "Roles": "भूमिकाएँ", "room_allowed_reactions": "प्रतिक्रियाएँ अनुमत हैं", "room_archived": "संग्रहित कमरा", diff --git a/app/i18n/locales/hu.json b/app/i18n/locales/hu.json index 1964200302..ff0c549126 100644 --- a/app/i18n/locales/hu.json +++ b/app/i18n/locales/hu.json @@ -6,6 +6,7 @@ "A_new_owner_will_be_assigned_automatically_to__count__room": "Az új tulajdonos automatikus hozzárendelésre kerül {{count}} szobához.", "A_new_owner_will_be_assigned_automatically_to__count__rooms": "Az új tulajdonos automatikus hozzárendelésre kerül {{count}} szobához.", "accept": "Elfogadom", + "Accessibility_statement": "Hozzáférhetőségi nyilatkozat", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Engedélyezze a felhasználóknak a \"Küldés a csatornára is” viselkedés kiválasztását", "Actions": "Tevékenységek", "Activity": "Aktivitás", @@ -275,9 +276,12 @@ "Forward_to_user": "Továbbítás a felhasználónak", "Full_table": "Kattintson a teljes táblázat megtekintéséhez", "Generate_New_Link": "Új hivatkozás létrehozása", + "Get_help": "Kérjen segítséget", "Get_link": "Hivatkozás lekérése", + "Glossary_of_simplified_terms": "Egyszerűsített kifejezések szótára", "Group_by": "Csoportosítás", "Has_left_the_team": "elhagyta a csapatot", + "Help": "Segítség", "Hide_System_Messages": "Rendszerüzenetek elrejtése", "Hide_type_messages": "\"{{type}}\" üzenetek elrejtése", "How_It_Works": "Hogyan működik", @@ -543,6 +547,7 @@ "Review_app_unable_store": "Nem lehet megnyitni {{store}}", "Review_app_yes": "Persze!", "Review_this_app": "App értékelése", + "Rocket_Chat_Documentation": "Rocket.Chat dokumentáció", "Roles": "Szerepek", "room_allowed_reactions": "reagálások engedélyezve", "room_archived": "archivált szoba", diff --git a/app/i18n/locales/it.json b/app/i18n/locales/it.json index d894666f8f..5b6f54fd8c 100644 --- a/app/i18n/locales/it.json +++ b/app/i18n/locales/it.json @@ -5,6 +5,7 @@ "A_meaningful_name_for_the_discussion_room": "Un nome significativo per il canale di discussione", "A_new_owner_will_be_assigned_automatically_to__count__room": "Un nuovo proprietario verrà assegnato automaticamente alla stanza {{count}}.", "A_new_owner_will_be_assigned_automatically_to__count__rooms": "Un nuovo proprietario verrà assegnato automaticamente a {{count}} stanze.", + "Accessibility_statement": "Dichiarazione di accessibilità", "Actions": "Azioni", "Activity": "Attività", "Add_Server": "Aggiungi server", @@ -190,8 +191,11 @@ "Forward_to_user": "Inoltra ad udente", "Full_table": "Clicca per la tabella completa", "Generate_New_Link": "Genera nuovo link", + "Get_help": "Ottieni aiuto", "Get_link": "Ottieni link", + "Glossary_of_simplified_terms": "Glossario dei termini semplificati", "Has_left_the_team": "Ha lasciato il team", + "Help": "Aiuto", "Hide_room": "Nascondi il canale", "Hide_System_Messages": "Nascondi messaggi di sistema", "Hide_type_messages": "Nascondi messaggi di \"{{type}}\"", @@ -388,6 +392,7 @@ "Review_app_unable_store": "Impossibile aprire {{store}}", "Review_app_yes": "Certo!", "Review_this_app": "Recensisci questa app", + "Rocket_Chat_Documentation": "Documentazione Rocket.Chat", "Roles": "Ruoli", "Room_Info": "Informazioni stanza", "Room_Info_Edit": "Modifica informazioni stanza", diff --git a/app/i18n/locales/ja.json b/app/i18n/locales/ja.json index 4641bf86aa..c510d13697 100644 --- a/app/i18n/locales/ja.json +++ b/app/i18n/locales/ja.json @@ -1,6 +1,7 @@ { "1_person_reacted": "1人がリアクション", "A_meaningful_name_for_the_discussion_room": "ディスカッションルームわかりやすい名前", + "Accessibility_statement": "アクセシビリティ声明", "Actions": "アクション", "Activity": "アクティビティ順", "Add_Server": "サーバーを追加", @@ -163,7 +164,10 @@ "Forward_to_user": "ユーザーに転送する", "Full_table": "クリックしてテーブル全体を見る", "Generate_New_Link": "新しいリンクを生成", + "Get_help": "ヘルプを得る", + "Glossary_of_simplified_terms": "簡易用語集", "Has_left_the_team": "チームを退出しました", + "Help": "助け", "Hide_room": "ルームを非表示", "Hide_System_Messages": "システムメッセージを非表示にする", "Hide_type_messages": "\"{{type}}\"メッセージを非表示にする", @@ -301,6 +305,7 @@ "Review_app_unable_store": "{{store}}を開けません。", "Review_app_yes": "はい!", "Review_this_app": "アプリをレビューする", + "Rocket_Chat_Documentation": "Rocket.Chat ドキュメント", "Roles": "ロール", "Room_Info": "ルーム情報", "Room_Info_Edit": "ルーム情報を編集", diff --git a/app/i18n/locales/nl.json b/app/i18n/locales/nl.json index 8a003fd2b7..b44e2b70a5 100644 --- a/app/i18n/locales/nl.json +++ b/app/i18n/locales/nl.json @@ -1,6 +1,7 @@ { "1_person_reacted": "1 persoon heeft gereageerd", "A_meaningful_name_for_the_discussion_room": "Een betekenisvolle naam voor de discussieruimte", + "Accessibility_statement": "Toegankelijkheidsverklaring", "Actions": "Acties", "Activity": "Activiteit", "Add_Channel_to_Team": "Kanaal toevoegen aan team", @@ -217,9 +218,12 @@ "Forward_to_user": "Doorsturen naar gebruiker", "Full_table": "Klik om de volledige tabel te zien", "Generate_New_Link": "Nieuwe link genereren", + "Get_help": "Hulp krijgen", "Get_link": "Link krijgen", + "Glossary_of_simplified_terms": "Glossarium van vereenvoudigde termen", "Group_by": "Groeperen op", "Has_left_the_team": "heeft het team verlaten", + "Help": "Hulp", "Hide_room": "Kamer verbergen", "Hide_System_Messages": "Verberg systeemberichten", "Hide_type_messages": "Verberg \"{{type}}\" berichten", @@ -458,6 +462,7 @@ "Review_app_unable_store": "Kan {{store}} niet openen", "Review_app_yes": "Zeker!", "Review_this_app": "Beoordeel deze app", + "Rocket_Chat_Documentation": "Rocket.Chat documentatie", "Roles": "Rollen", "Room_Info": "Kamer info", "Room_Info_Edit": "Kamer info bewerken", diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json index 54fb56cf32..da40be94f7 100644 --- a/app/i18n/locales/pt-BR.json +++ b/app/i18n/locales/pt-BR.json @@ -6,6 +6,7 @@ "A_new_owner_will_be_assigned_automatically_to__count__room": "Um novo proprietário será atribuído automaticamente a {{count}} sala.", "A_new_owner_will_be_assigned_automatically_to__count__rooms": "Um novo proprietário será atribuído automaticamente a {{count}} salas.", "accept": "Aceitar", + "Accessibility_statement": "Declaração de acessibilidade", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Permitir que os usuários selecionem o comportamento Também enviar para o canal", "Actions": "Ações", "Activity": "Atividade", @@ -305,10 +306,13 @@ "Forward_to_user": "Encaminhar para usuário", "Full_table": "Clique para ver a tabela completa", "Generate_New_Link": "Gerar novo convite", + "Get_help": "Obter ajuda", "Get_link": "Obter link", + "Glossary_of_simplified_terms": "Glossário de termos simplificados", "Go_to_your_device_settings_and_allow_microphone": "Vá para as configurações do seu dispositivo e permita o acesso ao microfone pelo aplicativo Rocket.Chat", "Group_by": "Agrupar por", "Has_left_the_team": "saiu do time", + "Help": "Ajuda", "Hide_room": "Ocultar", "Hide_System_Messages": "Esconder mensagens do sistema", "Hide_type_messages": "Esconder mensagens de \"{{type}}\"", @@ -599,6 +603,7 @@ "Review_app_unable_store": "Não foi possível abrir {{store}}", "Review_app_yes": "Claro!", "Review_this_app": "Avaliar esse app", + "Rocket_Chat_Documentation": "Documentação do Rocket.Chat", "Roles": "Papéis", "room_allowed_reactions": "adicionou permissão de reagir", "room_archived": "arquivou a sala", diff --git a/app/i18n/locales/pt-PT.json b/app/i18n/locales/pt-PT.json index f789e1f6ab..4c61335d44 100644 --- a/app/i18n/locales/pt-PT.json +++ b/app/i18n/locales/pt-PT.json @@ -1,6 +1,7 @@ { "1_person_reacted": "1 pessoa reagiu", "A_meaningful_name_for_the_discussion_room": "Um nome significativo para a sala de discussão", + "Accessibility_statement": "Declaração de acessibilidade", "Actions": "Acções", "Activity": "Actividade", "Add_Server": "Adicionar Servidor", @@ -158,7 +159,10 @@ "Forward_to_user": "Reencaminhar para o utilizador", "Full_table": "Clique para ver a tabela completa", "Generate_New_Link": "Gerar Novo Link", + "Get_help": "Obter ajuda", "Get_link": "Obter Ligação", + "Glossary_of_simplified_terms": "Glossário de termos simplificados", + "Help": "Ajuda", "Hide_room": "Esconder sala", "Hide_System_Messages": "Esconder mensagens do sistema", "Hide_type_messages": "Esconder mensagens \"{{type}}\"", @@ -305,6 +309,7 @@ "RESET": "REPOR", "Reset_password": "Repor palavra-passe", "resetting_password": "a repor palavra-passe", + "Rocket_Chat_Documentation": "Documentação do Rocket.Chat", "Roles": "Funções", "Room_Info": "Informação da Sala", "Room_Info_Edit": "Editar Informação da Sala", diff --git a/app/i18n/locales/ru.json b/app/i18n/locales/ru.json index 96a69220b3..94f2015dcb 100644 --- a/app/i18n/locales/ru.json +++ b/app/i18n/locales/ru.json @@ -5,6 +5,7 @@ "A_meaningful_name_for_the_discussion_room": "Осмысленное имя для обсуждения", "A_new_owner_will_be_assigned_automatically_to__count__room": "Новый владелец будет автоматически назначен на {{count}} чатов.", "A_new_owner_will_be_assigned_automatically_to__count__rooms": "Новый владелец будет автоматически назначен на {{count}} чатов.", + "Accessibility_statement": "Заявление о доступности", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Разрешить пользователям выбирать поведение \"Также отправить в чат\"", "Actions": "Действия", "Activity": "Активность", @@ -243,9 +244,12 @@ "Forward_to_user": "Перенаправить пользователю", "Full_table": "Нажмите, чтобы увидеть полную таблицу", "Generate_New_Link": "Сгенерировать Новую Ссылку", + "Get_help": "Получить помощь", "Get_link": "Получить ссылку", + "Glossary_of_simplified_terms": "Глоссарий упрощенных терминов", "Group_by": "Группировка", "Has_left_the_team": "покинул Команду", + "Help": "Помощь", "Hide_room": "Скрыть комнату", "Hide_System_Messages": "Скрыть Системные Сообщения", "Hide_type_messages": "Скрыть \"{{type}}\" сообщения", @@ -492,6 +496,7 @@ "Review_app_unable_store": "Невозможно открыть {{store}}", "Review_app_yes": "Конечно!", "Review_this_app": "Оценить это приложение", + "Rocket_Chat_Documentation": "Документация Rocket.Chat", "Roles": "Роли", "Room_Info": "Информация о канале", "Room_Info_Edit": "Изменить информацию о чате", diff --git a/app/i18n/locales/sl-SI.json b/app/i18n/locales/sl-SI.json index 9d3fed05d7..94b6959bb4 100644 --- a/app/i18n/locales/sl-SI.json +++ b/app/i18n/locales/sl-SI.json @@ -5,6 +5,7 @@ "A_meaningful_name_for_the_discussion_room": "Pomenljivo ime za pogovorno sobo", "A_new_owner_will_be_assigned_automatically_to__count__room": "Novi lastnik bo samodejno dodeljen {{count}} sobi.", "A_new_owner_will_be_assigned_automatically_to__count__rooms": "Novi lastnik bo samodejno dodeljen {{count}} sobam.", + "Accessibility_statement": "Izjava o dostopnosti", "Actions": "Dejanja", "Activity": "Aktivnost", "Add_Channel_to_Team": "Dodajte kanal v ekipo", @@ -228,8 +229,11 @@ "Forward_to_user": "Posreduj uporabniku", "Full_table": "Kliknite za ogled celotne tabele", "Generate_New_Link": "Ustvari novo povezavo", + "Get_help": "Pridobite pomoč", + "Glossary_of_simplified_terms": "Glosarij poenostavljenih izrazov", "Group_by": "Skupina po", "Has_left_the_team": "je zapustil ekipo", + "Help": "Pomoč", "Hide_room": "Skrij sobo", "Hide_System_Messages": "Skrij sistemska sporočila", "Hide_type_messages": "Skrij sporočila \"{{type}}\"", @@ -471,6 +475,7 @@ "Review_app_unable_store": "Ne morem odpreti {{store}}", "Review_app_yes": "Zagotovo!", "Review_this_app": "Preglejte to aplikacijo", + "Rocket_Chat_Documentation": "Dokumentacija Rocket.Chat", "Roles": "Vloge", "Room_Info": "Informacije o sobi", "Room_Info_Edit": "Uredi informacije o sobi", diff --git a/app/i18n/locales/sv.json b/app/i18n/locales/sv.json index 376b7e61c0..38acee28f1 100644 --- a/app/i18n/locales/sv.json +++ b/app/i18n/locales/sv.json @@ -5,6 +5,7 @@ "A_meaningful_name_for_the_discussion_room": "Ett beskrivande namn på diskussionsrummet", "A_new_owner_will_be_assigned_automatically_to__count__room": "En ny ägare utses automatiskt för {{count}} rum.", "A_new_owner_will_be_assigned_automatically_to__count__rooms": "En ny ägare utses automatiskt för {{count}} rum.", + "Accessibility_statement": "Tillgänglighetsredogörelse", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Tillåt användare att välja alternativet Skicka även till kanal", "Actions": "Åtgärder", "Activity": "Aktivitet", @@ -252,9 +253,12 @@ "Forward_to_user": "Vidarebefordra till användare", "Full_table": "Klicka för att visa hela tabellen", "Generate_New_Link": "Generera ny länk", + "Get_help": "Få hjälp", "Get_link": "Hämta länk", + "Glossary_of_simplified_terms": "Ordlista över förenklade termer", "Group_by": "Gruppera per", "Has_left_the_team": "har lämnat kanalen", + "Help": "Hjälp", "Hide_room": "Dölj rum", "Hide_System_Messages": "Dölj systemmeddelanden", "Hide_type_messages": "Dölj meddelanden av typen {{type}}", @@ -504,6 +508,7 @@ "Review_app_unable_store": "Det går inte att öppna {{store}}", "Review_app_yes": "Visst!", "Review_this_app": "Betygsätt appen", + "Rocket_Chat_Documentation": "Rocket.Chat dokumentation", "Roles": "Roller", "room_allowed_reactions": "tillåtna reaktioner", "room_archived": "arkiverade rum", diff --git a/app/i18n/locales/ta-IN.json b/app/i18n/locales/ta-IN.json index 8923e20e4a..9c115a4790 100644 --- a/app/i18n/locales/ta-IN.json +++ b/app/i18n/locales/ta-IN.json @@ -6,6 +6,7 @@ "A_new_owner_will_be_assigned_automatically_to__count__room": "{{count}} அறைக்கு புதிய உரிமையாளர் தானாகவே முடிந்துவிடப்படும்.", "A_new_owner_will_be_assigned_automatically_to__count__rooms": "{{count}} அறைகளுக்கு புதிய உரிமையாளர் தானாகவே முடிந்துவிடப்படும்.", "accept": "ஏற்றுக்கொள்", + "Accessibility_statement": "அணுகல் கூற்று", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "பயனர்களுக்கு 'சேனலுக்கும் அனுப்புக' செய்தி நடவடிக்கையை தேர்ந்தெடுத்துக் கொள்ள அனுமதிக்கு", "Actions": "செயல்கள்", "Activity": "செயல்திருத்தம்", @@ -275,9 +276,12 @@ "Forward_to_user": "பயனருக்கு முன்னேற்று", "Full_table": "முழு அட்டவணையைக் காண கிளிக் செய்க", "Generate_New_Link": "புதிய இணைப்பை உருவாக்கு", + "Get_help": "உதவி பெறுங்கள்", "Get_link": "இணைப்பைப் பெறுக", + "Glossary_of_simplified_terms": "எளிதாக்கப்பட்ட சொற்கள் குறியீடு", "Group_by": "குழுப்படுத்து", "Has_left_the_team": "குழுவை விட்டுவிட்டார்", + "Help": "உதவி", "Hide_System_Messages": "அமைப்பு செய்திகளை மறைக்க", "Hide_type_messages": "\"{{type}}\" செய்திகளை மறைக்க", "How_It_Works": "இது எப்படி வேலை செய்கின்றது", @@ -542,6 +546,7 @@ "Review_app_unable_store": "{{store}} கொள்ளையைத் திறக்க முடியவில்லை", "Review_app_yes": "உண்டு!", "Review_this_app": "இந்த ஆப்பை பரிசோதிக்க", + "Rocket_Chat_Documentation": "Rocket.Chat ஆவணக்காப்பு", "Roles": "பங்குகள்", "room_allowed_reactions": "பிரதிகிதங்களை அனுமதிக்கப்பட்டுள்ளது", "room_archived": "கோப்பு சேகரிக்கப்பட்ட அறை", diff --git a/app/i18n/locales/te-IN.json b/app/i18n/locales/te-IN.json index 1af32b3aa1..853d2d21e7 100644 --- a/app/i18n/locales/te-IN.json +++ b/app/i18n/locales/te-IN.json @@ -6,6 +6,7 @@ "A_new_owner_will_be_assigned_automatically_to__count__room": "కొత్త యజమాని స్వయంగా నియమించబడుతుంది {{count}} అంగడాయినందు.", "A_new_owner_will_be_assigned_automatically_to__count__rooms": "కొత్త యజమాని స్వయంగా నియమించబడుతుంది {{count}} అంగడులకు.", "accept": "అంగీకరించు", + "Accessibility_statement": "పేరుబట్టి వివరణ", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "వాడాకు 'కూడా చానల్‌కు పంపండి' పనికి అనుమతిస్తుంది", "Actions": "చర్యలు", "Activity": "చట్టం", @@ -275,9 +276,12 @@ "Forward_to_user": "వాడికి అగ్రగామించండి", "Full_table": "పూర్తి పటం చూడండి కొరకు క్లిక్ చేయండి", "Generate_New_Link": "కొత్త లింక్‌ను రూపొందించండి", + "Get_help": "సహాయం పొందండి", "Get_link": "లింక్ పొందండి", + "Glossary_of_simplified_terms": "సరళీకృత పదాల పదకోశం", "Group_by": "గ్రూప్ చేయండి", "Has_left_the_team": "టీమ్ ను వదిలిపోయారు", + "Help": "సహాయం", "Hide_System_Messages": "వ్యవస్థ సందేశాలను దాచండి", "Hide_type_messages": "{{type}} సందేశాలను దాచండి", "How_It_Works": "ఇది ఎలా పని చేస్తుంది", @@ -542,6 +546,7 @@ "Review_app_unable_store": "{{store}} తెరవలేకపోయింది", "Review_app_yes": "అవును!", "Review_this_app": "ఈ యాప్‌ను సమీక్షించండి", + "Rocket_Chat_Documentation": "Rocket.Chat డాక్యుమెంటేషన్", "Roles": "పాత్రలు", "room_allowed_reactions": "గదిలో ప్రతిక్రియలను అనుమతించారు", "room_archived": "అభిలోఖిత కొరకు ఉన్నది", diff --git a/app/i18n/locales/tr.json b/app/i18n/locales/tr.json index e28f4727b4..e7c446590a 100644 --- a/app/i18n/locales/tr.json +++ b/app/i18n/locales/tr.json @@ -1,6 +1,7 @@ { "1_person_reacted": "1 kişi tepki verdi.", "A_meaningful_name_for_the_discussion_room": "Tartışma odası için anlamlı bir isim", + "Accessibility_statement": "Erişilebilirlik beyanı", "Actions": "İşlemler", "Activity": "Etkinlik", "Add_Server": "Sunucu ekle", @@ -177,7 +178,10 @@ "Forward_to_user": "Kullanıcıya İlet", "Full_table": "Tam tabloyu görmek için tıklayın", "Generate_New_Link": "Yeni Bağlantı Oluştur", + "Get_help": "Yardım alın", "Get_link": "Bağlantıyı Al", + "Glossary_of_simplified_terms": "Basitleştirilmiş terimler sözlüğü", + "Help": "Yardım", "Hide_room": "Gizle", "Hide_System_Messages": "Sistem İletilerını Gizle", "Hide_type_messages": "\"{{type}}\" iletilerini gizle", @@ -371,6 +375,7 @@ "Review_app_unable_store": "{{store}} açılamıyor!", "Review_app_yes": "Elbette!", "Review_this_app": "Bu uygulamayı değerlendirin", + "Rocket_Chat_Documentation": "Rocket.Chat dokümantasyonu", "Roles": "Roller", "Room_Info": "Oda Bilgisi", "Room_Info_Edit": "Oda Bilgilerini Düzenle", diff --git a/app/i18n/locales/zh-CN.json b/app/i18n/locales/zh-CN.json index 8f434a0cfd..b79a5d164c 100644 --- a/app/i18n/locales/zh-CN.json +++ b/app/i18n/locales/zh-CN.json @@ -1,6 +1,7 @@ { "1_person_reacted": "1 人回复了", "A_meaningful_name_for_the_discussion_room": "取一个有意义的讨论区的名称", + "Accessibility_statement": "无障碍声明", "Actions": "操作", "Activity": "按活动时间排列", "Add_Server": "創建服务器", @@ -172,6 +173,9 @@ "Forward_to_user": "转发给用戶", "Full_table": "点击以查看完整表格", "Generate_New_Link": "产生新的链接", + "Get_help": "获取帮助", + "Glossary_of_simplified_terms": "简化术语词汇表", + "Help": "帮助", "Hide_System_Messages": "隐藏系统信息", "Hide_type_messages": "隐藏 \"{{type}}\" 信息", "How_It_Works": "运作方式", @@ -351,6 +355,7 @@ "Review_app_unable_store": "无法开启 {{store}}", "Review_app_yes": "没问题", "Review_this_app": "评分此 App", + "Rocket_Chat_Documentation": "Rocket.Chat 文档", "Roles": "角色", "Room_Info": "聊天室信息", "Room_Info_Edit": "聊天室信息编辑", diff --git a/app/i18n/locales/zh-TW.json b/app/i18n/locales/zh-TW.json index f9a9114e10..821d1c669d 100644 --- a/app/i18n/locales/zh-TW.json +++ b/app/i18n/locales/zh-TW.json @@ -1,6 +1,7 @@ { "1_person_reacted": "1 人回覆了", "A_meaningful_name_for_the_discussion_room": "取一個有意義的討論區名稱", + "Accessibility_statement": "无障碍声明", "Actions": "操作", "Activity": "以活動時間排序", "Add_Server": "新增伺服器", @@ -180,7 +181,10 @@ "Forward_to_user": "轉發給使用者", "Full_table": "點擊以查看完整表格", "Generate_New_Link": "產生新的連結", + "Get_help": "獲取幫助", "Get_link": "取得連結", + "Glossary_of_simplified_terms": "簡化術語詞彙表", + "Help": "幫助", "Hide_room": "隱藏", "Hide_System_Messages": "隱藏系統訊息", "Hide_type_messages": "隱藏 '{{type}}' 訊息", @@ -369,6 +373,7 @@ "Review_app_unable_store": "無法開啟 {{store}}", "Review_app_yes": "沒問題", "Review_this_app": "評分此 App", + "Rocket_Chat_Documentation": "Rocket.Chat 文件", "Roles": "角色", "Room_Info": "聊天室資訊", "Room_Info_Edit": "修改聊天室資訊", diff --git a/app/stacks/InsideStack.tsx b/app/stacks/InsideStack.tsx index b74fcdbbd2..751188099c 100644 --- a/app/stacks/InsideStack.tsx +++ b/app/stacks/InsideStack.tsx @@ -42,6 +42,7 @@ import DisplayPrefsView from '../views/DisplayPrefsView'; // Settings Stack import SettingsView from '../views/SettingsView'; import SecurityPrivacyView from '../views/SecurityPrivacyView'; +import GetHelpView from '../views/GetHelpView'; import PushTroubleshootView from '../views/PushTroubleshootView'; import E2EEncryptionSecurityView from '../views/E2EEncryptionSecurityView'; import LanguageView from '../views/LanguageView'; @@ -185,6 +186,7 @@ const SettingsStackNavigator = () => { <SettingsStack.Screen name='ThemeView' component={ThemeView} /> <SettingsStack.Screen name='DefaultBrowserView' component={DefaultBrowserView} /> <SettingsStack.Screen name='MediaAutoDownloadView' component={MediaAutoDownloadView} /> + <SettingsStack.Screen name='GetHelpView' component={GetHelpView} /> <SettingsStack.Screen name='ScreenLockConfigView' // @ts-ignore diff --git a/app/stacks/types.ts b/app/stacks/types.ts index da4af05999..27fbdba913 100644 --- a/app/stacks/types.ts +++ b/app/stacks/types.ts @@ -211,6 +211,7 @@ export type SettingsStackParamList = { DisplayPrefsView: undefined; MediaAutoDownloadView: undefined; PushTroubleshootView: undefined; + GetHelpView: undefined; }; export type AdminPanelStackParamList = { diff --git a/app/views/GetHelpView.tsx b/app/views/GetHelpView.tsx new file mode 100644 index 0000000000..82fa1eb2c7 --- /dev/null +++ b/app/views/GetHelpView.tsx @@ -0,0 +1,61 @@ +import { useLayoutEffect } from 'react'; +import { useNavigation } from '@react-navigation/native'; +import { StackNavigationProp } from '@react-navigation/stack'; + +import SafeAreaView from '../containers/SafeAreaView'; +import * as List from '../containers/List'; +import StatusBar from '../containers/StatusBar'; +import NewWindowIcon from '../containers/NewWindowIcon'; +import { SettingsStackParamList } from '../stacks/types'; +import i18n from '../i18n'; +import openLink from '../lib/methods/helpers/openLink'; +import { useTheme } from '../theme'; + +const DOCS_LINK = 'https://docs.rocket.chat/'; +const ACCESSIBILITY_LINK = 'https://go.rocket.chat/i/accessibility'; +const GLOSSARY_LINK = 'https://go.rocket.chat/i/glossary'; + +const GetHelpView = () => { + const navigation = useNavigation<StackNavigationProp<SettingsStackParamList, 'GetHelpView'>>(); + const { theme } = useTheme(); + + useLayoutEffect(() => { + navigation.setOptions({ + title: i18n.t('Help') + }); + }, [navigation]); + + return ( + <SafeAreaView> + <StatusBar /> + <List.Container> + <List.Section> + <List.Separator /> + <List.Item + title='Rocket_Chat_Documentation' + right={() => <NewWindowIcon />} + onPress={() => openLink(DOCS_LINK, theme)} + testID='settings-view-get-help-documentation' + /> + <List.Separator /> + <List.Item + title='Accessibility_statement' + right={() => <NewWindowIcon />} + onPress={() => openLink(ACCESSIBILITY_LINK, theme)} + testID='settings-view-get-help-accessibility-statement' + /> + <List.Separator /> + <List.Item + title='Glossary_of_simplified_terms' + right={() => <NewWindowIcon />} + onPress={() => openLink(GLOSSARY_LINK, theme)} + testID='settings-view-get-help-glossary' + /> + <List.Separator /> + </List.Section> + </List.Container> + </SafeAreaView> + ); +}; + +export default GetHelpView; diff --git a/app/views/PushTroubleshootView/components/NotificationDelay.tsx b/app/views/PushTroubleshootView/components/NotificationDelay.tsx index 5b32a412db..9b1994de7d 100644 --- a/app/views/PushTroubleshootView/components/NotificationDelay.tsx +++ b/app/views/PushTroubleshootView/components/NotificationDelay.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Linking } from 'react-native'; +import NewWindowIcon from '../../../containers/NewWindowIcon'; import * as List from '../../../containers/List'; import { useTheme } from '../../../theme'; @@ -15,7 +16,7 @@ export default function NotificationDelay(): React.ReactElement { <List.Item title='Documentation' onPress={openNotificationDocumentation} - right={() => <List.Icon size={32} name='new-window' color={colors.fontAnnotation} />} + right={() => <NewWindowIcon size={32} color={colors.fontAnnotation} />} testID='push-troubleshoot-view-notification-delay' /> <List.Separator /> diff --git a/app/views/SettingsView/index.tsx b/app/views/SettingsView/index.tsx index c8dc406e3e..5c7a15783f 100644 --- a/app/views/SettingsView/index.tsx +++ b/app/views/SettingsView/index.tsx @@ -11,6 +11,7 @@ import { appStart } from '../../actions/app'; import { logout } from '../../actions/login'; import { selectServerRequest } from '../../actions/server'; import * as HeaderButton from '../../containers/HeaderButton'; +import NewWindowIcon from '../../containers/NewWindowIcon'; import * as List from '../../containers/List'; import SafeAreaView from '../../containers/SafeAreaView'; import StatusBar from '../../containers/StatusBar'; @@ -238,9 +239,17 @@ const SettingsView = (): React.ReactElement => { testID='settings-view-security-privacy' left={() => <List.Icon name='locker' />} /> + <List.Separator /> </List.Section> <List.Section> + <List.Item + title='Get_help' + left={() => <List.Icon name='support' />} + showActionIndicator + onPress={() => navigateToScreen('GetHelpView')} + testID='settings-view-get-help' + /> <List.Separator /> <List.Item title='Share_this_app' @@ -255,7 +264,7 @@ const SettingsView = (): React.ReactElement => { onPress={sendEmail} testID='settings-view-contact' left={() => <List.Icon name='mail' />} - right={() => <List.Icon name='new-window' />} + right={() => <NewWindowIcon />} /> <List.Separator /> {!isFDroidBuild ? ( @@ -265,7 +274,7 @@ const SettingsView = (): React.ReactElement => { onPress={onReviewPress} testID='settings-view-review-app' left={() => <List.Icon name='star' />} - right={() => <List.Icon name='new-window' />} + right={() => <NewWindowIcon />} /> </> ) : null} @@ -275,7 +284,7 @@ const SettingsView = (): React.ReactElement => { onPress={onPressLicense} testID='settings-view-license' left={() => <List.Icon name='file-document' />} - right={() => <List.Icon name='new-window' />} + right={() => <NewWindowIcon />} /> <List.Separator /> <List.Item diff --git a/e2e/tests/assorted/04-setting.spec.ts b/e2e/tests/assorted/04-setting.spec.ts index e866ad9c75..dc178fdfe2 100644 --- a/e2e/tests/assorted/04-setting.spec.ts +++ b/e2e/tests/assorted/04-setting.spec.ts @@ -1,6 +1,6 @@ import { device, waitFor, element, by, expect } from 'detox'; -import { navigateToLogin, login, platformTypes, TTextMatcher } from '../../helpers/app'; +import { navigateToLogin, login, platformTypes, TTextMatcher, tapBack } from '../../helpers/app'; import { createRandomRoom, createRandomUser, ITestUser } from '../../helpers/data_setup'; describe('Settings screen', () => { @@ -76,6 +76,17 @@ describe('Settings screen', () => { it('should have server version', async () => { await expect(element(by.id('settings-view-server-version'))).toExist(); }); + + it('should have get help', async () => { + await expect(element(by.id('settings-view-get-help'))).toExist(); + await element(by.id('settings-view-get-help')).tap(); + await waitFor(element(by.id('settings-view-get-help-documentation'))) + .toBeVisible() + .withTimeout(2000); + await expect(element(by.id('settings-view-get-help-accessibility-statement'))).toExist(); + await expect(element(by.id('settings-view-get-help-glossary'))).toExist(); + await tapBack(); + }); }); describe('Usage', () => { From bc35b092a43b7c1ab5caa47d763af128de038568 Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Tue, 24 Sep 2024 17:14:04 -0300 Subject: [PATCH 26/26] fix: Image viewer loosing quality (#5878) --- app/containers/ImageViewer/ImageComponent.ts | 24 -------------------- app/containers/ImageViewer/ImageViewer.tsx | 9 +++----- app/containers/ImageViewer/index.ts | 2 -- app/containers/ImageViewer/types.ts | 4 ---- app/views/ShareView/Preview.tsx | 16 ++++--------- app/views/ShareView/index.tsx | 1 - 6 files changed, 7 insertions(+), 49 deletions(-) delete mode 100644 app/containers/ImageViewer/ImageComponent.ts delete mode 100644 app/containers/ImageViewer/types.ts diff --git a/app/containers/ImageViewer/ImageComponent.ts b/app/containers/ImageViewer/ImageComponent.ts deleted file mode 100644 index ad307ee440..0000000000 --- a/app/containers/ImageViewer/ImageComponent.ts +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { Image } from 'react-native'; -import { FastImageProps } from 'react-native-fast-image'; - -import { types } from './types'; -import { LOCAL_DOCUMENT_DIRECTORY } from '../../lib/methods/handleMediaDownload'; - -export function ImageComponent({ - type, - uri -}: { - type?: string; - uri: string; -}): React.ComponentType<Partial<Image> | FastImageProps> { - let Component; - if (type === types.REACT_NATIVE_IMAGE || (LOCAL_DOCUMENT_DIRECTORY && uri.startsWith(LOCAL_DOCUMENT_DIRECTORY))) { - const { Image } = require('react-native'); - Component = Image; - } else { - const FastImage = require('react-native-fast-image'); - Component = FastImage; - } - return Component; -} diff --git a/app/containers/ImageViewer/ImageViewer.tsx b/app/containers/ImageViewer/ImageViewer.tsx index c47ca1296c..09fb450418 100644 --- a/app/containers/ImageViewer/ImageViewer.tsx +++ b/app/containers/ImageViewer/ImageViewer.tsx @@ -2,9 +2,9 @@ import React, { useState } from 'react'; import { LayoutChangeEvent, StyleSheet, StyleProp, ViewStyle, ImageStyle, View } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import Animated, { withTiming, useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated'; +import FastImage from 'react-native-fast-image'; import { useTheme } from '../../theme'; -import { ImageComponent } from './ImageComponent'; interface ImageViewerProps { style?: StyleProp<ImageStyle>; @@ -12,7 +12,6 @@ interface ImageViewerProps { imageContainerStyle?: StyleProp<ViewStyle>; uri: string; - imageComponentType?: string; width: number; height: number; onLoadEnd?: () => void; @@ -27,7 +26,7 @@ const styles = StyleSheet.create({ } }); -export const ImageViewer = ({ uri = '', imageComponentType, width, height, ...props }: ImageViewerProps): React.ReactElement => { +export const ImageViewer = ({ uri = '', width, height, ...props }: ImageViewerProps): React.ReactElement => { const [centerX, setCenterX] = useState(0); const [centerY, setCenterY] = useState(0); @@ -109,15 +108,13 @@ export const ImageViewer = ({ uri = '', imageComponentType, width, height, ...pr const gesture = Gesture.Simultaneous(pinchGesture, panGesture, doubleTapGesture); - const Component = ImageComponent({ type: imageComponentType, uri }); - const { colors } = useTheme(); return ( <View style={[styles.flex, { width, height, backgroundColor: colors.surfaceNeutral }]}> <GestureDetector gesture={gesture}> <Animated.View onLayout={onLayout} style={[styles.flex, style]}> - <Component + <FastImage // @ts-ignore style={styles.image} resizeMode='contain' diff --git a/app/containers/ImageViewer/index.ts b/app/containers/ImageViewer/index.ts index bf629ae84b..69943f2ff5 100644 --- a/app/containers/ImageViewer/index.ts +++ b/app/containers/ImageViewer/index.ts @@ -1,3 +1 @@ export * from './ImageViewer'; -export * from './types'; -export * from './ImageComponent'; diff --git a/app/containers/ImageViewer/types.ts b/app/containers/ImageViewer/types.ts deleted file mode 100644 index 56c4a7d27d..0000000000 --- a/app/containers/ImageViewer/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const types = { - FAST_IMAGE: 'FAST_IMAGE', - REACT_NATIVE_IMAGE: 'REACT_NATIVE' -}; diff --git a/app/views/ShareView/Preview.tsx b/app/views/ShareView/Preview.tsx index 3b7cb6a091..16a0f25264 100644 --- a/app/views/ShareView/Preview.tsx +++ b/app/views/ShareView/Preview.tsx @@ -6,7 +6,7 @@ import prettyBytes from 'pretty-bytes'; import { useHeaderHeight } from '@react-navigation/elements'; import { CustomIcon, TIconsName } from '../../containers/CustomIcon'; -import { ImageViewer, types } from '../../containers/ImageViewer'; +import { ImageViewer } from '../../containers/ImageViewer'; import sharedStyles from '../Styles'; import I18n from '../../i18n'; import { THUMBS_HEIGHT } from './constants'; @@ -60,11 +60,10 @@ const IconPreview = React.memo(({ iconName, title, description, theme, width, he interface IPreview { item: IShareAttachment; theme: TSupportedThemes; - isShareExtension: boolean; length: number; } -const Preview = React.memo(({ item, theme, isShareExtension, length }: IPreview) => { +const Preview = React.memo(({ item, theme, length }: IPreview) => { const type = item?.mime; const { width, height } = useWindowDimensions(); const insets = useSafeAreaInsets(); @@ -98,17 +97,10 @@ const Preview = React.memo(({ item, theme, isShareExtension, length }: IPreview) ); } - // Disallow preview of images too big in order to prevent memory issues on iOS share extension if (type?.match(/image/)) { - return ( - <ImageViewer - uri={item.path} - imageComponentType={isShareExtension ? types.REACT_NATIVE_IMAGE : types.FAST_IMAGE} - width={width} - height={calculatedHeight} - /> - ); + return <ImageViewer uri={item.path} width={width} height={calculatedHeight} />; } + return ( <IconPreview iconName={'attach'} diff --git a/app/views/ShareView/index.tsx b/app/views/ShareView/index.tsx index 3d58e816ff..50e12ddd7f 100644 --- a/app/views/ShareView/index.tsx +++ b/app/views/ShareView/index.tsx @@ -382,7 +382,6 @@ class ShareView extends Component<IShareViewProps, IShareViewState> { item={selected} length={attachments.length} theme={theme} - isShareExtension={this.isShareExtension} /> <MessageComposerContainer ref={this.messageComposerRef}> <Thumbs