From ec7066ad06038026ed5bc7d77825dedd220ca83f Mon Sep 17 00:00:00 2001 From: Saturnino Abril Date: Tue, 13 Dec 2022 05:58:11 +0800 Subject: [PATCH 1/9] Combine fastlane for iOS unsigned and simulator and enable build of iOS simulator both in release and PR (#6854) * combine fastlane for ios unsigned and simulator and enable build of ios simulator both in release and pr * revert changes to circleci --- detox/.detoxrc.json | 2 +- detox/package.json | 2 +- fastlane/Fastfile | 54 +++++++++++++++++++++++++-------------------- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/detox/.detoxrc.json b/detox/.detoxrc.json index 1987dd715..2f006892d 100644 --- a/detox/.detoxrc.json +++ b/detox/.detoxrc.json @@ -13,7 +13,7 @@ "ios.release": { "type": "ios.app", "binaryPath": "../ios/Build/Products/Release-iphonesimulator/Mattermost.app", - "build": "cd ../fastlane && NODE_ENV=production bundle exec fastlane ios simulator && cd ../detox" + "build": "cd .. && npm run build:ios-sim && cd detox" }, "android.debug": { "type": "android.apk", diff --git a/detox/package.json b/detox/package.json index d16f9b24c..1e81067ea 100644 --- a/detox/package.json +++ b/detox/package.json @@ -52,7 +52,7 @@ "e2e:android-test-release": "detox test -c android.emu.release --record-logs failing --take-screenshots failing", "e2e:ios-test": "IOS=true detox test -c ios.sim.debug", "e2e:ios-build-release": "detox build -c ios.sim.release", - "e2e:ios-test-release": "IOS=true detox test -c ios.sim.release --headless --record-logs failing --take-screenshots failing", + "e2e:ios-test-release": "IOS=true detox test -c ios.sim.release --record-logs failing --take-screenshots failing", "check": "npm run lint && npm run tsc", "lint": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet .", "start:webhook": "node webhook_server.js", diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 4b479955c..3fad4a6b8 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -355,39 +355,27 @@ platform :ios do update_identifiers replace_assets - sh 'rm -rf ../build-ios/' - sh 'cd ../ios/ && xcodebuild -workspace Mattermost.xcworkspace/ -scheme Mattermost -sdk iphoneos -configuration Release -parallelizeTargets -resultBundlePath ../build-ios/result -derivedDataPath ../build-ios/ CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO' - sh 'cd ../build-ios/ && mkdir -p Payload && cp -R ../build-ios/Build/Products/Release-iphoneos/Mattermost.app Payload/ && zip -r Mattermost-unsigned.ipa Payload/' - sh 'mv ../build-ios/Mattermost-unsigned.ipa ../' - sh 'rm -rf ../build-ios/' + build_ios_unsigned_or_simulator( + more_xc_args: "-sdk iphoneos CODE_SIGNING_ALLOWED=NO", + rel_build_dir: "Release-iphoneos", + output_file: "Mattermost-unsigned.ipa", + ) end lane :simulator do UI.success('Building iOS app for simulator') - ENV['APP_NAME'] = 'Mattermost' - ENV['REPLACE_ASSETS'] = 'true' - ENV['BUILD_FOR_RELEASE'] = 'true' - ENV['APP_SCHEME'] = 'mattermost' + output_file = "Mattermost-simulator-x86_64.app.zip" - update_identifiers - replace_assets - - data_path = 'ios' - if ENV['CIRCLECI'] == 'true' - data_path = 'build-ios' - end - - sh 'rm -rf ../ios/build/' - sh 'rm -rf ../build-ios/' - sh "cd ../ios && xcodebuild -workspace Mattermost.xcworkspace -scheme Mattermost -arch x86_64 -sdk iphonesimulator -configuration Release -parallelizeTargets -derivedDataPath ../#{data_path} ENABLE_BITCODE=NO CODE_SIGN_IDENTITY='' CODE_SIGNING_REQUIRED=NO ENABLE_BITCODE=NO" - sh "cd ../#{data_path}/Build/Products/Release-iphonesimulator && zip -r Mattermost-simulator-x86_64.app.zip Mattermost.app" - sh "mv ../#{data_path}/Build/Products/Release-iphonesimulator/Mattermost-simulator-x86_64.app.zip ../" - sh 'rm -rf ../build-ios/' + build_ios_unsigned_or_simulator( + more_xc_args: "-sdk iphonesimulator -arch x86_64", + rel_build_dir: "Release-iphonesimulator", + output_file: output_file, + ) upload_file_to_s3({ :os_type => "ios", - :file => "Mattermost-simulator-x86_64.app.zip" + :file => output_file }) end @@ -599,6 +587,24 @@ platform :ios do ) end + def build_ios_unsigned_or_simulator(more_xc_args:, rel_build_dir:, output_file:) + root_dir = Dir[File.expand_path('..')].first + ios_build_dir = "#{root_dir}/ios/Build/Products" + + ENV['APP_NAME'] = 'Mattermost' + ENV['REPLACE_ASSETS'] = 'true' + ENV['BUILD_FOR_RELEASE'] = 'true' + ENV['APP_SCHEME'] = 'mattermost' + + update_identifiers + replace_assets + + sh "rm -rf #{ios_build_dir}/" + sh "cd #{root_dir}/ios && xcodebuild -workspace Mattermost.xcworkspace -scheme Mattermost -configuration Release -parallelizeTargets CODE_SIGN_IDENTITY='' CODE_SIGNING_REQUIRED=NO SYMROOT='#{ios_build_dir}' #{more_xc_args} " + sh "cd #{ios_build_dir}/#{rel_build_dir} && zip -r #{output_file} Mattermost.app" + sh "mv #{ios_build_dir}/#{rel_build_dir}/#{output_file} #{root_dir}" + end + end platform :android do From 917ea371a0868eaa4d4a1a1d110ec5150725978c Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 13 Dec 2022 09:14:39 +0200 Subject: [PATCH 2/9] Fix ios keyboard tracking and profile status theme (#6859) * add listener when screen is popped * Fix profile status theme --- app/components/profile_picture/status.tsx | 2 +- app/hooks/keyboard_tracking.ts | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/components/profile_picture/status.tsx b/app/components/profile_picture/status.tsx index ef82320b6..52546266d 100644 --- a/app/components/profile_picture/status.tsx +++ b/app/components/profile_picture/status.tsx @@ -38,7 +38,7 @@ const Status = ({author, statusSize, statusStyle, theme}: Props) => { styles.statusWrapper, statusStyle, {borderRadius: statusSize / 2}, - ]), [statusStyle]); + ]), [statusStyle, styles]); const isBot = author && (('isBot' in author) ? author.isBot : author.is_bot); if (author?.status && !isBot) { return ( diff --git a/app/hooks/keyboard_tracking.ts b/app/hooks/keyboard_tracking.ts index 806b21ff0..0a9a31ce2 100644 --- a/app/hooks/keyboard_tracking.ts +++ b/app/hooks/keyboard_tracking.ts @@ -11,6 +11,14 @@ export const useKeyboardTrackingPaused = (keyboardTrackingRef: RefObject { + const onCommandComplete = () => { + const id = NavigationStore.getVisibleScreen(); + if (screens.includes(id) && isPostDraftPaused.current) { + isPostDraftPaused.current = false; + keyboardTrackingRef.current?.resumeTracking(trackerId); + } + }; + const commandListener = Navigation.events().registerCommandListener(() => { if (!isPostDraftPaused.current) { isPostDraftPaused.current = true; @@ -19,16 +27,17 @@ export const useKeyboardTrackingPaused = (keyboardTrackingRef: RefObject { - const id = NavigationStore.getVisibleScreen(); - if (screens.includes(id) && isPostDraftPaused.current) { - isPostDraftPaused.current = false; - keyboardTrackingRef.current?.resumeTracking(trackerId); - } + onCommandComplete(); + }); + + const popListener = Navigation.events().registerScreenPoppedListener(() => { + onCommandComplete(); }); return () => { commandListener.remove(); commandCompletedListener.remove(); + popListener.remove(); }; }, [trackerId]); }; From 25ee7db6efae8b96d357babbf93c1bb8ad371fe6 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 13 Dec 2022 12:15:19 +0200 Subject: [PATCH 3/9] Fix caret position on search phrase (#6861) --- app/screens/home/search/initial/modifiers/modifier.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/screens/home/search/initial/modifiers/modifier.tsx b/app/screens/home/search/initial/modifiers/modifier.tsx index e2f303bcf..b58df28bd 100644 --- a/app/screens/home/search/initial/modifiers/modifier.tsx +++ b/app/screens/home/search/initial/modifiers/modifier.tsx @@ -46,9 +46,9 @@ const Modifier = ({item, searchRef, searchValue, setSearchValue}: Props) => { setSearchValue(newValue); if (item.cursorPosition) { const position = newValue.length + item.cursorPosition; - requestAnimationFrame(() => { + setTimeout(() => { searchRef.current?.setNativeProps({selection: {start: position, end: position}}); - }); + }, 50); } }); From 26d32285e5c7f11250f838f74667ae81e1393b2f Mon Sep 17 00:00:00 2001 From: Avinash Lingaloo Date: Tue, 13 Dec 2022 14:36:11 +0400 Subject: [PATCH 4/9] Bump app build number to 443 (#6862) --- android/app/build.gradle | 2 +- ios/Mattermost.xcodeproj/project.pbxproj | 8 ++++---- ios/Mattermost/Info.plist | 2 +- ios/MattermostShare/Info.plist | 2 +- ios/NotificationService/Info.plist | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index ced9265ed..9a8df50de 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -145,7 +145,7 @@ android { applicationId "com.mattermost.rnbeta" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 442 + versionCode 443 versionName "2.0.0" testBuildType System.getProperty('testBuildType', 'debug') testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index 7d1df6be7..7dc713ae8 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -1095,7 +1095,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 442; + CURRENT_PROJECT_VERSION = 443; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; HEADER_SEARCH_PATHS = ( @@ -1139,7 +1139,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 442; + CURRENT_PROJECT_VERSION = 443; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; HEADER_SEARCH_PATHS = ( @@ -1282,7 +1282,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 442; + CURRENT_PROJECT_VERSION = 443; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = UQ8HT4Q2XM; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1333,7 +1333,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 442; + CURRENT_PROJECT_VERSION = 443; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = UQ8HT4Q2XM; GCC_C_LANGUAGE_STANDARD = gnu11; diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index 5393a3c26..5a74b2bbc 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -37,7 +37,7 @@ CFBundleVersion - 442 + 443 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index 904466732..145d5228a 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -21,7 +21,7 @@ CFBundleShortVersionString 2.0.0 CFBundleVersion - 442 + 443 UIAppFonts OpenSans-Bold.ttf diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist index b312569d9..e5f3b06f3 100644 --- a/ios/NotificationService/Info.plist +++ b/ios/NotificationService/Info.plist @@ -21,7 +21,7 @@ CFBundleShortVersionString 2.0.0 CFBundleVersion - 442 + 443 NSExtension NSExtensionPointIdentifier From f120282465d60feb7cfad45d3a32d1472639261f Mon Sep 17 00:00:00 2001 From: Joseph Baylon Date: Wed, 14 Dec 2022 12:54:49 -0800 Subject: [PATCH 5/9] Detox/E2E: Fix android detox build and upgrade simulator to iPhone 14 (#6864) * Detox/E2E: Fix android detox build * Upgrade to sim iPhone 14 --- .../java/com/mattermost/rnbeta/DetoxTest.java | 10 ++++++---- detox/.detoxrc.json | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/android/app/src/androidTest/java/com/mattermost/rnbeta/DetoxTest.java b/android/app/src/androidTest/java/com/mattermost/rnbeta/DetoxTest.java index 220be5e4a..dfc645da8 100644 --- a/android/app/src/androidTest/java/com/mattermost/rnbeta/DetoxTest.java +++ b/android/app/src/androidTest/java/com/mattermost/rnbeta/DetoxTest.java @@ -1,6 +1,7 @@ package com.mattermost.rnbeta; import com.wix.detox.Detox; +import com.wix.detox.config.DetoxConfig; import org.junit.Rule; import org.junit.Test; @@ -19,10 +20,11 @@ public class DetoxTest { @Test public void runDetoxTests() { - Detox.DetoxIdlePolicyConfig idlePolicyConfig = new Detox.DetoxIdlePolicyConfig(); - idlePolicyConfig.masterTimeoutSec = 60; - idlePolicyConfig.idleResourceTimeoutSec = 30; + DetoxConfig detoxConfig = new DetoxConfig(); + detoxConfig.idlePolicyConfig.masterTimeoutSec = 90; + detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 60; + detoxConfig.rnContextLoadTimeoutSec = (BuildConfig.DEBUG ? 180 : 60); - Detox.runTests(mActivityRule, idlePolicyConfig); + Detox.runTests(mActivityRule, detoxConfig); } } diff --git a/detox/.detoxrc.json b/detox/.detoxrc.json index 2f006892d..fb7bc86c1 100644 --- a/detox/.detoxrc.json +++ b/detox/.detoxrc.json @@ -30,7 +30,7 @@ "ios.simulator": { "type": "ios.simulator", "device": { - "type": "iPhone 13" + "type": "iPhone 14" } }, "android.emulator": { From f51557bcd1c405be7f503c520e885c781fdecf65 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Thu, 15 Dec 2022 13:56:46 +0200 Subject: [PATCH 6/9] Improve cold start (#6868) --- app/components/emoji/emoji.tsx | 152 ++++++++++++++++ app/components/emoji/index.tsx | 166 ++---------------- app/components/markdown/markdown.tsx | 2 +- .../markdown/markdown_code_block/index.tsx | 14 +- app/components/syntax_highlight/index.tsx | 11 +- types/components/emoji.ts | 19 ++ types/components/syntax_highlight.ts | 11 ++ 7 files changed, 209 insertions(+), 166 deletions(-) create mode 100644 app/components/emoji/emoji.tsx create mode 100644 types/components/emoji.ts create mode 100644 types/components/syntax_highlight.ts diff --git a/app/components/emoji/emoji.tsx b/app/components/emoji/emoji.tsx new file mode 100644 index 000000000..8334aea4d --- /dev/null +++ b/app/components/emoji/emoji.tsx @@ -0,0 +1,152 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; +import withObservables from '@nozbe/with-observables'; +import React from 'react'; +import { + Platform, + StyleSheet, + Text, +} from 'react-native'; +import FastImage from 'react-native-fast-image'; +import {of as of$} from 'rxjs'; +import {switchMap} from 'rxjs/operators'; + +import {fetchCustomEmojiInBatch} from '@actions/remote/custom_emoji'; +import {useServerUrl} from '@context/server'; +import NetworkManager from '@managers/network_manager'; +import {queryCustomEmojisByName} from '@queries/servers/custom_emoji'; +import {observeConfigBooleanValue} from '@queries/servers/system'; +import {EmojiIndicesByAlias, Emojis} from '@utils/emoji'; +import {isUnicodeEmoji} from '@utils/emoji/helpers'; + +import type {EmojiProps} from '@typings/components/emoji'; +import type {WithDatabaseArgs} from '@typings/database/database'; + +const assetImages = new Map([['mattermost.png', require('@assets/images/emojis/mattermost.png')]]); + +const Emoji = (props: EmojiProps) => { + const { + customEmojis, + customEmojiStyle, + displayTextOnly, + emojiName, + literal = '', + testID, + textStyle, + } = props; + const serverUrl = useServerUrl(); + let assetImage = ''; + let unicode; + let imageUrl = ''; + const name = emojiName.trim(); + if (EmojiIndicesByAlias.has(name)) { + const emoji = Emojis[EmojiIndicesByAlias.get(name)!]; + if (emoji.category === 'custom') { + assetImage = emoji.fileName; + } else { + unicode = emoji.image; + } + } else { + const custom = customEmojis.find((ce) => ce.name === name); + if (custom) { + try { + const client = NetworkManager.getClient(serverUrl); + imageUrl = client.getCustomEmojiImageUrl(custom.id); + } catch { + // do nothing + } + } else if (name && !isUnicodeEmoji(name)) { + fetchCustomEmojiInBatch(serverUrl, name); + } + } + + let size = props.size; + let fontSize = size; + if (!size && textStyle) { + const flatten = StyleSheet.flatten(textStyle); + fontSize = flatten.fontSize; + size = fontSize; + } + + if (displayTextOnly || (!imageUrl && !assetImage && !unicode)) { + return ( + + {literal} + ); + } + + const width = size; + const height = size; + + if (unicode && !imageUrl) { + const codeArray = unicode.split('-'); + const code = codeArray.reduce((acc: string, c: string) => { + return acc + String.fromCodePoint(parseInt(c, 16)); + }, ''); + + return ( + + {code} + + ); + } + + if (assetImage) { + const key = Platform.OS === 'android' ? (`${assetImage}-${height}-${width}`) : null; + + const image = assetImages.get(assetImage); + if (!image) { + return null; + } + return ( + + ); + } + + if (!imageUrl) { + return null; + } + + // Android can't change the size of an image after its first render, so + // force a new image to be rendered when the size changes + const key = Platform.OS === 'android' ? (`${imageUrl}-${height}-${width}`) : null; + + return ( + + ); +}; + +const withCustomEmojis = withObservables(['emojiName'], ({database, emojiName}: WithDatabaseArgs & {emojiName: string}) => { + const hasEmojiBuiltIn = EmojiIndicesByAlias.has(emojiName); + + const displayTextOnly = hasEmojiBuiltIn ? of$(false) : observeConfigBooleanValue(database, 'EnableCustomEmoji').pipe( + switchMap((value) => of$(!value)), + ); + + return { + displayTextOnly, + customEmojis: hasEmojiBuiltIn ? of$([]) : queryCustomEmojisByName(database, [emojiName]).observe(), + }; +}); + +export default withDatabase(withCustomEmojis(Emoji)); diff --git a/app/components/emoji/index.tsx b/app/components/emoji/index.tsx index 090fe3ad3..918f4bd23 100644 --- a/app/components/emoji/index.tsx +++ b/app/components/emoji/index.tsx @@ -1,165 +1,21 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; -import withObservables from '@nozbe/with-observables'; -import React from 'react'; -import { - Platform, - StyleProp, - StyleSheet, - Text, - TextStyle, -} from 'react-native'; -import FastImage, {ImageStyle} from 'react-native-fast-image'; -import {of as of$} from 'rxjs'; -import {switchMap} from 'rxjs/operators'; +import React, {useMemo} from 'react'; -import {fetchCustomEmojiInBatch} from '@actions/remote/custom_emoji'; -import {useServerUrl} from '@context/server'; -import NetworkManager from '@managers/network_manager'; -import {queryCustomEmojisByName} from '@queries/servers/custom_emoji'; -import {observeConfigBooleanValue} from '@queries/servers/system'; -import {EmojiIndicesByAlias, Emojis} from '@utils/emoji'; -import {isUnicodeEmoji} from '@utils/emoji/helpers'; +import {EmojiComponent, EmojiProps} from '@typings/components/emoji'; -import type {WithDatabaseArgs} from '@typings/database/database'; -import type CustomEmojiModel from '@typings/database/models/servers/custom_emoji'; +let emojiComponent: EmojiComponent; -const assetImages = new Map([['mattermost.png', require('@assets/images/emojis/mattermost.png')]]); - -type Props = { - emojiName: string; - displayTextOnly?: boolean; - literal?: string; - size?: number; - textStyle?: StyleProp; - customEmojiStyle?: StyleProp; - customEmojis: CustomEmojiModel[]; - testID?: string; -} - -const Emoji = (props: Props) => { - const { - customEmojis, - customEmojiStyle, - displayTextOnly, - emojiName, - literal = '', - testID, - textStyle, - } = props; - const serverUrl = useServerUrl(); - let assetImage = ''; - let unicode; - let imageUrl = ''; - const name = emojiName.trim(); - if (EmojiIndicesByAlias.has(name)) { - const emoji = Emojis[EmojiIndicesByAlias.get(name)!]; - if (emoji.category === 'custom') { - assetImage = emoji.fileName; - } else { - unicode = emoji.image; +const EmojiWrapper = (props: Omit) => { + const Emoji = useMemo(() => { + if (!emojiComponent) { + emojiComponent = require('./emoji').default; } - } else { - const custom = customEmojis.find((ce) => ce.name === name); - if (custom) { - try { - const client = NetworkManager.getClient(serverUrl); - imageUrl = client.getCustomEmojiImageUrl(custom.id); - } catch { - // do nothing - } - } else if (name && !isUnicodeEmoji(name)) { - fetchCustomEmojiInBatch(serverUrl, name); - } - } + return emojiComponent; + }, []); - let size = props.size; - let fontSize = size; - if (!size && textStyle) { - const flatten = StyleSheet.flatten(textStyle); - fontSize = flatten.fontSize; - size = fontSize; - } - - if (displayTextOnly || (!imageUrl && !assetImage && !unicode)) { - return ( - - {literal} - ); - } - - const width = size; - const height = size; - - if (unicode && !imageUrl) { - const codeArray = unicode.split('-'); - const code = codeArray.reduce((acc: string, c: string) => { - return acc + String.fromCodePoint(parseInt(c, 16)); - }, ''); - - return ( - - {code} - - ); - } - - if (assetImage) { - const key = Platform.OS === 'android' ? (`${assetImage}-${height}-${width}`) : null; - - const image = assetImages.get(assetImage); - if (!image) { - return null; - } - return ( - - ); - } - - if (!imageUrl) { - return null; - } - - // Android can't change the size of an image after its first render, so - // force a new image to be rendered when the size changes - const key = Platform.OS === 'android' ? (`${imageUrl}-${height}-${width}`) : null; - - return ( - - ); + return (); }; -const withCustomEmojis = withObservables(['emojiName'], ({database, emojiName}: WithDatabaseArgs & {emojiName: string}) => { - const hasEmojiBuiltIn = EmojiIndicesByAlias.has(emojiName); - - const displayTextOnly = hasEmojiBuiltIn ? of$(false) : observeConfigBooleanValue(database, 'EnableCustomEmoji').pipe( - switchMap((value) => of$(!value)), - ); - - return { - displayTextOnly, - customEmojis: hasEmojiBuiltIn ? of$([]) : queryCustomEmojisByName(database, [emojiName]).observe(), - }; -}); - -export default withDatabase(withCustomEmojis(Emoji)); +export default EmojiWrapper; diff --git a/app/components/markdown/markdown.tsx b/app/components/markdown/markdown.tsx index 4bdf7a6d5..a7efb3a11 100644 --- a/app/components/markdown/markdown.tsx +++ b/app/components/markdown/markdown.tsx @@ -9,13 +9,13 @@ import {Dimensions, GestureResponderEvent, Platform, StyleProp, Text, TextStyle, import CompassIcon from '@components/compass_icon'; import Emoji from '@components/emoji'; import FormattedText from '@components/formatted_text'; -import Hashtag from '@components/markdown/hashtag'; import {computeTextStyle} from '@utils/markdown'; import {blendColors, changeOpacity, concatStyles, makeStyleSheetFromTheme} from '@utils/theme'; import {getScheme} from '@utils/url'; import AtMention from './at_mention'; import ChannelMention, {ChannelMentions} from './channel_mention'; +import Hashtag from './hashtag'; import MarkdownBlockQuote from './markdown_block_quote'; import MarkdownCodeBlock from './markdown_code_block'; import MarkdownImage from './markdown_image'; diff --git a/app/components/markdown/markdown_code_block/index.tsx b/app/components/markdown/markdown_code_block/index.tsx index 8e0dad2ed..20549d427 100644 --- a/app/components/markdown/markdown_code_block/index.tsx +++ b/app/components/markdown/markdown_code_block/index.tsx @@ -3,14 +3,13 @@ import {useManagedConfig} from '@mattermost/react-native-emm'; import Clipboard from '@react-native-clipboard/clipboard'; -import React, {useCallback} from 'react'; +import React, {useCallback, useMemo} from 'react'; import {useIntl} from 'react-intl'; import {Keyboard, StyleSheet, Text, TextStyle, TouchableOpacity, View} from 'react-native'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import FormattedText from '@components/formatted_text'; import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item'; -import SyntaxHighlighter from '@components/syntax_highlight'; import {Screens} from '@constants'; import {useTheme} from '@context/theme'; import {bottomSheet, dismissBottomSheet, goToScreen} from '@screens/navigation'; @@ -19,6 +18,8 @@ import {getHighlightLanguageFromNameOrAlias, getHighlightLanguageName} from '@ut import {preventDoubleTap} from '@utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import type {SyntaxHiglightProps} from '@typings/components/syntax_highlight'; + type MarkdownCodeBlockProps = { language: string; content: string; @@ -27,6 +28,8 @@ type MarkdownCodeBlockProps = { const MAX_LINES = 4; +let syntaxHighlighter: (props: SyntaxHiglightProps) => JSX.Element; + const getStyleSheet = makeStyleSheetFromTheme((theme) => { return { bottomSheet: { @@ -70,6 +73,13 @@ const MarkdownCodeBlock = ({language = '', content, textStyle}: MarkdownCodeBloc const theme = useTheme(); const insets = useSafeAreaInsets(); const style = getStyleSheet(theme); + const SyntaxHighlighter = useMemo(() => { + if (!syntaxHighlighter) { + syntaxHighlighter = require('@components/syntax_highlight').default; + } + + return syntaxHighlighter; + }, []); const handlePress = useCallback(preventDoubleTap(() => { const screen = Screens.CODE; diff --git a/app/components/syntax_highlight/index.tsx b/app/components/syntax_highlight/index.tsx index 963be4759..a83b2e3e0 100644 --- a/app/components/syntax_highlight/index.tsx +++ b/app/components/syntax_highlight/index.tsx @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import React, {useCallback, useMemo} from 'react'; -import {StyleSheet, TextStyle, View} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import SyntaxHighlighter from 'react-syntax-highlighter'; import {github, monokai, solarizedDark, solarizedLight} from 'react-syntax-highlighter/dist/cjs/styles/hljs'; @@ -10,12 +10,7 @@ import {useTheme} from '@context/theme'; import CodeHighlightRenderer from './renderer'; -type Props = { - code: string; - language: string; - textStyle: TextStyle; - selectable?: boolean; -} +import type {SyntaxHiglightProps} from '@typings/components/syntax_highlight'; const codeTheme: Record = { github, @@ -34,7 +29,7 @@ const styles = StyleSheet.create({ }, }); -const Highlighter = ({code, language, textStyle, selectable = false}: Props) => { +const Highlighter = ({code, language, textStyle, selectable = false}: SyntaxHiglightProps) => { const theme = useTheme(); const style = codeTheme[theme.codeTheme] || github; const preTagStyle = useMemo(() => [ diff --git a/types/components/emoji.ts b/types/components/emoji.ts new file mode 100644 index 000000000..8daf4074f --- /dev/null +++ b/types/components/emoji.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import type CustomEmojiModel from '@typings/database/models/servers/custom_emoji'; +import type {StyleProp, TextStyle} from 'react-native'; +import type {ImageStyle} from 'react-native-fast-image'; + +export type EmojiProps = { + emojiName: string; + displayTextOnly?: boolean; + literal?: string; + size?: number; + textStyle?: StyleProp; + customEmojiStyle?: StyleProp; + customEmojis: CustomEmojiModel[]; + testID?: string; +} + +export type EmojiComponent = (props: Omit) => JSX.Element; diff --git a/types/components/syntax_highlight.ts b/types/components/syntax_highlight.ts new file mode 100644 index 000000000..d8f959e5f --- /dev/null +++ b/types/components/syntax_highlight.ts @@ -0,0 +1,11 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {TextStyle} from 'react-native'; + +export type SyntaxHiglightProps = { + code: string; + language: string; + textStyle: TextStyle; + selectable?: boolean; +}; From b28e21a27273b89b8d5528febacb480c5be3c34f Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Thu, 15 Dec 2022 14:15:16 +0200 Subject: [PATCH 7/9] standardize user-agent (#6866) --- NOTICE.txt | 36 --------------------------------- app/managers/network_manager.ts | 6 +++--- ios/Podfile.lock | 6 ------ package-lock.json | 15 -------------- package.json | 1 - test/setup.ts | 6 ------ 6 files changed, 3 insertions(+), 67 deletions(-) diff --git a/NOTICE.txt b/NOTICE.txt index 645a46a76..336ae5e2a 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -2636,42 +2636,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- - -## react-native-user-agent - -This product contains 'react-native-user-agent' by Bebnev Anton. - -Library that helps you to get mobile application user agent and web view user agent strings. - -* HOMEPAGE: - * https://github.com/bebnev/react-native-user-agent - -* LICENSE: MIT - -MIT License - -Copyright (c) 2018 Anton Bebnev - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - --- ## react-native-vector-icons diff --git a/app/managers/network_manager.ts b/app/managers/network_manager.ts index 7bd5cd04f..d94501eb9 100644 --- a/app/managers/network_manager.ts +++ b/app/managers/network_manager.ts @@ -9,7 +9,7 @@ import { RetryTypes, } from '@mattermost/react-native-network-client'; import {DeviceEventEmitter} from 'react-native'; -import UserAgent from 'react-native-user-agent'; +import DeviceInfo from 'react-native-device-info'; import LocalConfig from '@assets/config.json'; import {Client} from '@client/rest'; @@ -95,11 +95,11 @@ class NetworkManager { }; private buildConfig = async () => { - const userAgent = UserAgent.getUserAgent(); + const userAgent = `Mattermost Mobile/${DeviceInfo.getVersion()}+${DeviceInfo.getBuildNumber()} (${DeviceInfo.getSystemName()}; ${DeviceInfo.getSystemVersion()}; ${DeviceInfo.getModel()})`; const managedConfig = ManagedApp.enabled ? Emm.getManagedConfig() : undefined; const headers: Record = { - ...this.DEFAULT_CONFIG.headers, [ClientConstants.HEADER_USER_AGENT]: userAgent, + ...this.DEFAULT_CONFIG.headers, }; const config = { diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5aec0ee03..3a085da90 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -369,8 +369,6 @@ PODS: - ReactCommon/turbomodule/core - react-native-turbo-mailer (0.2.0): - React-Core - - react-native-user-agent (2.3.1): - - React - react-native-video (5.2.1): - React-Core - react-native-video/Video (= 5.2.1) @@ -618,7 +616,6 @@ DEPENDENCIES: - "react-native-paste-input (from `../node_modules/@mattermost/react-native-paste-input`)" - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - "react-native-turbo-mailer (from `../node_modules/@mattermost/react-native-turbo-mailer`)" - - react-native-user-agent (from `../node_modules/react-native-user-agent`) - react-native-video (from `../node_modules/react-native-video`) - react-native-webrtc (from `../node_modules/react-native-webrtc`) - react-native-webview (from `../node_modules/react-native-webview`) @@ -776,8 +773,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-safe-area-context" react-native-turbo-mailer: :path: "../node_modules/@mattermost/react-native-turbo-mailer" - react-native-user-agent: - :path: "../node_modules/react-native-user-agent" react-native-video: :path: "../node_modules/react-native-video" react-native-webrtc: @@ -928,7 +923,6 @@ SPEC CHECKSUMS: react-native-paste-input: 88709b4fd586ea8cc56ba5e2fc4cdfe90597730c react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a react-native-turbo-mailer: 226fc3533d16500fb4ad08cf8ab2cfc7bb1ef593 - react-native-user-agent: a90a1e839b99801baad67a73dd6f361a52aa3cf1 react-native-video: c26780b224543c62d5e1b2a7244a5cd1b50e8253 react-native-webrtc: 86d841823e66d68cc1f86712db1c2956056bf0c2 react-native-webview: 91a6bd643c6c298f6dfac309231c15d60b8e9505 diff --git a/package-lock.json b/package-lock.json index 32a707182..30cd1b8b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -88,7 +88,6 @@ "react-native-shadow-2": "7.0.6", "react-native-share": "8.0.0", "react-native-svg": "13.6.0", - "react-native-user-agent": "2.3.1", "react-native-vector-icons": "9.2.0", "react-native-video": "5.2.1", "react-native-webrtc": "github:mattermost/react-native-webrtc", @@ -18733,14 +18732,6 @@ "resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz", "integrity": "sha512-Ns7Bn9H/Tyw278+5SQx9oAblDZ7JixyzeOczcBK8dipQk2pD7Djkcfnf1nB/8RErAmMLL9iXgW0QHqiII8AhKw==" }, - "node_modules/react-native-user-agent": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/react-native-user-agent/-/react-native-user-agent-2.3.1.tgz", - "integrity": "sha512-AIFr1VgJHwgWmMwCOmIGxuBeAaADlouXKc10UyR4fzWneUbt5uIJIoRu2oExlfCtiT8IyCp106khDD5vx7RUjw==", - "peerDependencies": { - "react-native": "*" - } - }, "node_modules/react-native-vector-icons": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-9.2.0.tgz", @@ -36079,12 +36070,6 @@ "resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz", "integrity": "sha512-Ns7Bn9H/Tyw278+5SQx9oAblDZ7JixyzeOczcBK8dipQk2pD7Djkcfnf1nB/8RErAmMLL9iXgW0QHqiII8AhKw==" }, - "react-native-user-agent": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/react-native-user-agent/-/react-native-user-agent-2.3.1.tgz", - "integrity": "sha512-AIFr1VgJHwgWmMwCOmIGxuBeAaADlouXKc10UyR4fzWneUbt5uIJIoRu2oExlfCtiT8IyCp106khDD5vx7RUjw==", - "requires": {} - }, "react-native-vector-icons": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-9.2.0.tgz", diff --git a/package.json b/package.json index 215b6d158..59dc29df2 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,6 @@ "react-native-shadow-2": "7.0.6", "react-native-share": "8.0.0", "react-native-svg": "13.6.0", - "react-native-user-agent": "2.3.1", "react-native-vector-icons": "9.2.0", "react-native-video": "5.2.1", "react-native-webrtc": "github:mattermost/react-native-webrtc", diff --git a/test/setup.ts b/test/setup.ts index 0ee8bf871..eea80218b 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -236,12 +236,6 @@ jest.mock('react-native-device-info', () => { }; }); -jest.mock('react-native-user-agent', () => { - return { - getUserAgent: () => 'user-agent', - }; -}); - jest.mock('react-native-localize', () => ({ getTimeZone: () => 'World/Somewhere', getLocales: () => ([ From 3e48e472821c2f25918cf38043be15561d9faf90 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Thu, 15 Dec 2022 14:18:12 +0200 Subject: [PATCH 8/9] remove unused scripts from package.json (#6870) --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index 59dc29df2..01bee6303 100644 --- a/package.json +++ b/package.json @@ -174,7 +174,6 @@ }, "scripts": { "android": "react-native run-android", - "build-storybook": "build-storybook", "build:android": "./scripts/build.sh apk", "build:android-unsigned": "./scripts/build.sh apk unsigned", "build:ios": "./scripts/build.sh ipa", @@ -195,9 +194,7 @@ "pod-install": "cd ios && bundle exec pod install", "postinstall": "patch-package && ./scripts/postinstall.sh", "preinstall": "npx solidarity", - "prestorybook": "rnstl", "start": "react-native start", - "storybook": "start-storybook -p 7007", "test": "jest --forceExit --runInBand", "test:coverage": "jest --coverage", "test:watch": "npm test -- --watch", From aff0de5a13ef55f907b8f247e20166d5dfa3fa14 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Fri, 16 Dec 2022 12:19:58 +0200 Subject: [PATCH 9/9] Resume keyboard tracking on mount (#6874) --- app/hooks/keyboard_tracking.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/hooks/keyboard_tracking.ts b/app/hooks/keyboard_tracking.ts index 0a9a31ce2..ad1b8670c 100644 --- a/app/hooks/keyboard_tracking.ts +++ b/app/hooks/keyboard_tracking.ts @@ -10,6 +10,10 @@ import NavigationStore from '@store/navigation_store'; export const useKeyboardTrackingPaused = (keyboardTrackingRef: RefObject, trackerId: string, screens: string[]) => { const isPostDraftPaused = useRef(false); + useEffect(() => { + keyboardTrackingRef.current?.resumeTracking(trackerId); + }, []); + useEffect(() => { const onCommandComplete = () => { const id = NavigationStore.getVisibleScreen();