From d7f7b1da9f7e973792f916442033a90f5b84bbb6 Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Fri, 29 Jul 2022 01:18:42 -0300 Subject: [PATCH 01/10] [FIX] iFrame login takes user to blank screen (#4389) --- app/sagas/selectServer.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index 9e65fa7bf..cc01354c3 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -1,4 +1,4 @@ -import { put, takeLatest } from 'redux-saga/effects'; +import { put, takeLatest, select } from 'redux-saga/effects'; import { Alert } from 'react-native'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { Q } from '@nozbe/watermelondb'; @@ -163,7 +163,8 @@ const handleServerRequest = function* handleServerRequest({ server, username, fr yield getLoginSettings({ server }); Navigation.navigate('WorkspaceView'); - if (fromServerHistory) { + const Accounts_iframe_enabled = yield select(state => state.settings.Accounts_iframe_enabled); + if (fromServerHistory && !Accounts_iframe_enabled) { Navigation.navigate('LoginView', { username }); } From 0e378c67b86fa8c693989dadc3e43f2f8a20bdac Mon Sep 17 00:00:00 2001 From: Gleidson Daniel Silva Date: Fri, 29 Jul 2022 14:10:31 -0300 Subject: [PATCH 02/10] Chore: Try Github Actions (#4376) --- .github/workflow-android-experimental.yml | 76 +++++++++++++++++++++++ .github/workflow-ios-experimental.yml | 71 +++++++++++++++++++++ .github/workflow-test-and-lint.yml | 26 ++++++++ 3 files changed, 173 insertions(+) create mode 100644 .github/workflow-android-experimental.yml create mode 100644 .github/workflow-ios-experimental.yml create mode 100644 .github/workflow-test-and-lint.yml diff --git a/.github/workflow-android-experimental.yml b/.github/workflow-android-experimental.yml new file mode 100644 index 000000000..4aaca6e6e --- /dev/null +++ b/.github/workflow-android-experimental.yml @@ -0,0 +1,76 @@ +name: Build Android + +on: + pull_request: + types: ["labeled"] + +jobs: + build-android: + if: contains(github.event.pull_request.labels.*.name, 'android-build') + + name: Build Android + runs-on: ubuntu-latest + environment: develop + defaults: + run: + working-directory: android + + env: + JAVA_OPTS: "-Xms512m -Xmx2g" + GRADLE_OPTS: '-Xmx3g -Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-Xmx2g -XX:+HeapDumpOnOutOfMemoryError"' + TERM: dumb + + steps: + - name: Check out Git repository + uses: actions/checkout@v3 + + - name: Decode Keystore + run: echo ${{ secrets.KEYSTORE_BASE_64 }} | base64 -d > ./app/key.keystore + + - uses: c-hive/gha-yarn-cache@v2 + + - name: Install dependencies + run: yarn install --prefer-offline + + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + distribution: "zulu" + java-version: 11.0.14 + cache: "gradle" + + - name: Make Gradlew Executable + run: chmod +x ./gradlew + + - name: Set gradle.properties + run: | + echo -e "" > ./gradle.properties + echo -e android.useAndroidX=true >> ./gradle.properties + echo -e android.enableJetifier=true >> ./gradle.properties + echo -e FLIPPER_VERSION=0.51.0 >> ./gradle.properties + echo -e VERSIONCODE=${{ github.run_number }} >> ./gradle.properties + echo -e APPLICATION_ID=chat.rocket.reactnative >> ./gradle.properties + echo -e BugsnagAPIKey=${{ secrets.BUGSNAG_KEY }} >> ./gradle.properties + echo -e KEYSTORE=${{ secrets.KEYSTORE }} >> ./gradle.properties + echo -e KEYSTORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }} >> ./gradle.properties + echo -e KEY_ALIAS=${{ secrets.KEY_ALIAS }} >> ./gradle.properties + echo -e KEY_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }} >> ./gradle.properties + + - name: Generate App APK + run: | + ./gradlew bundleExperimentalPlayRelease --no-daemon + + - name: Upload sourcemaps to Bugsnag + run: | + npx bugsnag-source-maps upload-react-native \ + --api-key=${{ secrets.BUGSNAG_KEY }} \ + --app-version-code=${{ github.event.number }} \ + --platform android \ + --source-map=app/build/generated/sourcemaps/react/experimentalPlay/release/app.bundle.map \ + --bundle app/build/generated/assets/react/experimentalPlay/release/app.bundle + + - name: Store Artifacts + uses: actions/upload-artifact@v3 + with: + name: app.bundle-${{ github.event.number }} + path: app/build/generated/assets/react/experimentalPlay/release/app.bundle diff --git a/.github/workflow-ios-experimental.yml b/.github/workflow-ios-experimental.yml new file mode 100644 index 000000000..8cf8c4a71 --- /dev/null +++ b/.github/workflow-ios-experimental.yml @@ -0,0 +1,71 @@ +name: Build iOS + +on: + pull_request: + types: ['labeled'] + +jobs: + build-android: + if: contains(github.event.pull_request.labels.*.name, 'ios-build') + + name: Build iOS + runs-on: macos-latest + environment: develop + defaults: + run: + working-directory: ios + + steps: + - name: Check out Git repository + uses: actions/checkout@v3 + + - uses: c-hive/gha-yarn-cache@v2 + + - name: Install dependencies + run: yarn install --prefer-offline + + - name: Setup Ruby (bundle) + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.6 + bundler-cache: true + - run: bundle install + + - name: Restore Pods cache + uses: actions/cache@v2 + with: + path: | + ios/Pods + ~/Library/Caches/CocoaPods + ~/.cocoapods + key: ${{ runner.os }}-pods-${{ hashFiles('ios/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + + - name: Install Pods + run: pod install --repo-update && cd .. + + - name: Set version + run: agvtool new-version -all ${{ github.run_number }} + + - name: Set Keys + run: | + /usr/libexec/PlistBuddy -c "Set :bugsnag:apiKey ${{ secrets.BUGSNAG_KEY }}" ./RocketChatRN/Info.plist + /usr/libexec/PlistBuddy -c "Set :bugsnag:apiKey ${{ secrets.BUGSNAG_KEY }}" ./ShareRocketChatRN/Info.plist + /usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./RocketChatRN/Info.plist + /usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./ShareRocketChatRN/Info.plist + /usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./NotificationService/Info.plist + + - name: Decode P8 + run: echo ${{ secrets.APP_STORE_CONNECT_API_BASE64 }} | base64 --decode > ./fastlane/app_store_connect_api_key.p8 + + - name: Run fastlane + run: fastlane ios build_experimental + + - name: Store Artifacts + uses: actions/upload-artifact@v3 + with: + name: Rocket.Chat.${{ github.event.number }}.ipa + path: | + Rocket.Chat.ipa + Rocket.Chat.app.dSYM.zip diff --git a/.github/workflow-test-and-lint.yml b/.github/workflow-test-and-lint.yml new file mode 100644 index 000000000..294f67f77 --- /dev/null +++ b/.github/workflow-test-and-lint.yml @@ -0,0 +1,26 @@ +name: Test and Lint + +on: [push] + +jobs: + test-and-lint: + name: Test and lint + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v2 + + - uses: c-hive/gha-yarn-cache@v2 + + - name: Install dependencies + run: yarn install --prefer-offline + + - name: Lint + run: yarn lint + + - name: Test + run: yarn test -w 8 + + - name: Codecov + run: yarn codecov From 5875454a70186c200834e38c95d80afd9ed6d587 Mon Sep 17 00:00:00 2001 From: Gleidson Daniel Silva Date: Fri, 29 Jul 2022 14:18:11 -0300 Subject: [PATCH 03/10] [FIX] Github Actions folder (#4400) --- .github/{ => workflows}/workflow-android-experimental.yml | 0 .github/{ => workflows}/workflow-ios-experimental.yml | 0 .github/{ => workflows}/workflow-test-and-lint.yml | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename .github/{ => workflows}/workflow-android-experimental.yml (100%) rename .github/{ => workflows}/workflow-ios-experimental.yml (100%) rename .github/{ => workflows}/workflow-test-and-lint.yml (100%) diff --git a/.github/workflow-android-experimental.yml b/.github/workflows/workflow-android-experimental.yml similarity index 100% rename from .github/workflow-android-experimental.yml rename to .github/workflows/workflow-android-experimental.yml diff --git a/.github/workflow-ios-experimental.yml b/.github/workflows/workflow-ios-experimental.yml similarity index 100% rename from .github/workflow-ios-experimental.yml rename to .github/workflows/workflow-ios-experimental.yml diff --git a/.github/workflow-test-and-lint.yml b/.github/workflows/workflow-test-and-lint.yml similarity index 100% rename from .github/workflow-test-and-lint.yml rename to .github/workflows/workflow-test-and-lint.yml From 41dbbf4d4b88b6152595bcbd91847142ca4cb785 Mon Sep 17 00:00:00 2001 From: Gleidson Daniel Silva Date: Mon, 1 Aug 2022 13:35:05 -0300 Subject: [PATCH 04/10] Chore: Migrate NotificationPreferencesView to hooks (#4327) * add none option * create showErrorAlertWithEMessage function * migrate NotificationPreferencesView to hooks and improves overall * change icon to right * fix navigation options * remove none * fix types * remove memo * cleaning * switching to destructuring * add observe for hideUnreadStatus * fix desktop options * remove unused options Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> --- app/containers/ActionSheet/Item.tsx | 9 +- app/containers/ActionSheet/Provider.tsx | 2 +- app/containers/ActionSheet/styles.ts | 1 - app/definitions/IRoom.ts | 1 + app/lib/methods/helpers/info.ts | 8 + app/stacks/InsideStack.tsx | 6 +- app/stacks/MasterDetailStack/index.tsx | 6 +- .../NotificationPreferencesView/index.tsx | 436 ++++++++---------- .../NotificationPreferencesView/options.ts | 53 +-- 9 files changed, 208 insertions(+), 314 deletions(-) diff --git a/app/containers/ActionSheet/Item.tsx b/app/containers/ActionSheet/Item.tsx index 2b0b50080..7b48dcefd 100644 --- a/app/containers/ActionSheet/Item.tsx +++ b/app/containers/ActionSheet/Item.tsx @@ -26,11 +26,16 @@ export const Item = React.memo(({ item, hide }: IActionSheetItem) => { style={[styles.item, { backgroundColor: themes[theme].focusedBackground }]} theme={theme} testID={item.testID}> - + {item.icon ? ( + + ) : null} + style={[ + styles.title, + { color: item.danger ? themes[theme].dangerColor : themes[theme].bodyText, marginLeft: item.icon ? 16 : 0 } + ]}> {item.title} diff --git a/app/containers/ActionSheet/Provider.tsx b/app/containers/ActionSheet/Provider.tsx index 945bb9aff..b7caf6416 100644 --- a/app/containers/ActionSheet/Provider.tsx +++ b/app/containers/ActionSheet/Provider.tsx @@ -6,7 +6,7 @@ import ActionSheet from './ActionSheet'; export type TActionSheetOptionsItem = { title: string; - icon: TIconsName; + icon?: TIconsName; danger?: boolean; testID?: string; onPress: () => void; diff --git a/app/containers/ActionSheet/styles.ts b/app/containers/ActionSheet/styles.ts index 3ffb90240..4c099c71b 100644 --- a/app/containers/ActionSheet/styles.ts +++ b/app/containers/ActionSheet/styles.ts @@ -27,7 +27,6 @@ export default StyleSheet.create({ }, title: { fontSize: 16, - marginLeft: 16, ...sharedStyles.textRegular }, handle: { diff --git a/app/definitions/IRoom.ts b/app/definitions/IRoom.ts index 1444d4fef..d49eccf67 100644 --- a/app/definitions/IRoom.ts +++ b/app/definitions/IRoom.ts @@ -237,6 +237,7 @@ export interface IRoomNotifications { desktopNotifications?: TNotifications; mobilePushNotifications?: TNotifications; emailNotifications?: TNotifications; + hideMentionStatus?: boolean; } export type TRoomNotificationsModel = IRoomNotifications & Model; diff --git a/app/lib/methods/helpers/info.ts b/app/lib/methods/helpers/info.ts index 4971f743e..2fc903d04 100644 --- a/app/lib/methods/helpers/info.ts +++ b/app/lib/methods/helpers/info.ts @@ -5,6 +5,14 @@ import I18n from '../../../i18n'; export const showErrorAlert = (message: string, title?: string, onPress = () => {}): void => Alert.alert(title || '', message, [{ text: 'OK', onPress }], { cancelable: true }); +export const showErrorAlertWithEMessage = (e: any): void => { + const messageError = + e.data && e.data.error.includes('[error-too-many-requests]') + ? I18n.t('error-too-many-requests', { seconds: e.data.error.replace(/\D/g, '') }) + : e.data.errorType; + showErrorAlert(messageError); +}; + interface IShowConfirmationAlert { title?: string; message: string; diff --git a/app/stacks/InsideStack.tsx b/app/stacks/InsideStack.tsx index ea17fee27..10889aac8 100644 --- a/app/stacks/InsideStack.tsx +++ b/app/stacks/InsideStack.tsx @@ -111,11 +111,7 @@ const ChatsStackNavigator = () => { - + diff --git a/app/stacks/MasterDetailStack/index.tsx b/app/stacks/MasterDetailStack/index.tsx index bf9a1425e..890e52c5a 100644 --- a/app/stacks/MasterDetailStack/index.tsx +++ b/app/stacks/MasterDetailStack/index.tsx @@ -151,11 +151,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => { options={props => DirectoryView.navigationOptions!({ ...props, isMasterDetail: true })} /> - + diff --git a/app/views/NotificationPreferencesView/index.tsx b/app/views/NotificationPreferencesView/index.tsx index aa2931f0f..7029e5a7d 100644 --- a/app/views/NotificationPreferencesView/index.tsx +++ b/app/views/NotificationPreferencesView/index.tsx @@ -1,282 +1,222 @@ -import React from 'react'; -import { StyleSheet, Switch, Text } from 'react-native'; -import { RouteProp } from '@react-navigation/core'; -import { StackNavigationProp } from '@react-navigation/stack'; -import { Observable, Subscription } from 'rxjs'; -import { connect } from 'react-redux'; +import { RouteProp, useNavigation, useRoute } from '@react-navigation/core'; +import React, { useEffect, useState } from 'react'; +import { Switch, Text } from 'react-native'; -import database from '../../lib/database'; -import { SWITCH_TRACK_COLOR, themes } from '../../lib/constants'; -import StatusBar from '../../containers/StatusBar'; +import { TActionSheetOptionsItem, useActionSheet } from '../../containers/ActionSheet'; +import { CustomIcon } from '../../containers/CustomIcon'; import * as List from '../../containers/List'; -import I18n from '../../i18n'; -import { TSupportedThemes, withTheme } from '../../theme'; -import protectedFunction from '../../lib/methods/helpers/protectedFunction'; import SafeAreaView from '../../containers/SafeAreaView'; -import log, { events, logEvent } from '../../lib/methods/helpers/log'; -import sharedStyles from '../Styles'; -import { IOptionsField, OPTIONS } from './options'; -import { ChatsStackParamList } from '../../stacks/types'; -import { IApplicationState, IRoomNotifications, TRoomNotificationsModel } from '../../definitions'; -import { Services } from '../../lib/services'; +import StatusBar from '../../containers/StatusBar'; +import { IRoomNotifications, TRoomNotificationsModel } from '../../definitions'; +import I18n from '../../i18n'; +import { SWITCH_TRACK_COLOR } from '../../lib/constants'; +import { useAppSelector } from '../../lib/hooks'; +import { showErrorAlertWithEMessage } from '../../lib/methods/helpers'; import { compareServerVersion } from '../../lib/methods/helpers/compareServerVersion'; +import log, { events, logEvent } from '../../lib/methods/helpers/log'; +import { Services } from '../../lib/services'; +import { ChatsStackParamList } from '../../stacks/types'; +import { useTheme } from '../../theme'; +import sharedStyles from '../Styles'; +import { OPTIONS } from './options'; -const styles = StyleSheet.create({ - pickerText: { - ...sharedStyles.textRegular, - fontSize: 16 - } -}); +type TOptions = keyof typeof OPTIONS; +type TRoomNotifications = keyof IRoomNotifications; +type TUnionOptionsRoomNotifications = TOptions | TRoomNotifications; -interface INotificationPreferencesViewProps { - navigation: StackNavigationProp; - route: RouteProp; - theme: TSupportedThemes; - serverVersion: string | null; -} - -interface INotificationPreferencesViewState { +interface IBaseParams { + preference: TUnionOptionsRoomNotifications; room: TRoomNotificationsModel; + onChangeValue: (pref: TUnionOptionsRoomNotifications, param: { [key: string]: string }, onError: () => void) => void; } -class NotificationPreferencesView extends React.Component { - static navigationOptions = () => ({ - title: I18n.t('Notification_Preferences') - }); +const RenderListPicker = ({ + preference, + room, + title, + testID, + onChangeValue +}: { + title: string; + testID: string; +} & IBaseParams) => { + const { showActionSheet, hideActionSheet } = useActionSheet(); + const { colors } = useTheme(); - private mounted: boolean; - private rid: string; - private roomObservable?: Observable; - private subscription?: Subscription; + const pref = room[preference] + ? OPTIONS[preference as TOptions].find(option => option.value === room[preference]) + : OPTIONS[preference as TOptions][0]; - constructor(props: INotificationPreferencesViewProps) { - super(props); - this.mounted = false; - this.rid = props.route.params?.rid ?? ''; - const room = props.route.params?.room; - this.state = { - room: room || {} - }; - if (room && room.observe) { - this.roomObservable = room.observe(); - this.subscription = this.roomObservable.subscribe(changes => { - if (this.mounted) { - this.setState({ room: changes }); - } else { - // @ts-ignore - this.state.room = changes; - } - }); - } - } + const [option, setOption] = useState(pref); - componentDidMount() { - this.mounted = true; - } + const options: TActionSheetOptionsItem[] = OPTIONS[preference as TOptions].map(i => ({ + title: I18n.t(i.label, { defaultValue: i.label, second: i.second }), + onPress: () => { + hideActionSheet(); + onChangeValue(preference, { [preference]: i.value.toString() }, () => setOption(option)); + setOption(i); + }, + right: option?.value === i.value ? () => : undefined + })); - componentWillUnmount() { - if (this.subscription && this.subscription.unsubscribe) { - this.subscription.unsubscribe(); - } - } + return ( + showActionSheet({ options })} + right={() => ( + + {option?.label ? I18n.t(option?.label, { defaultValue: option?.label, second: option?.second }) : option?.label} + + )} + /> + ); +}; - saveNotificationSettings = async (key: string, value: string | boolean, params: IRoomNotifications) => { - // @ts-ignore - logEvent(events[`NP_${key.toUpperCase()}`]); - const { room } = this.state; - const db = database.active; +const RenderSwitch = ({ preference, room, onChangeValue }: IBaseParams) => { + const [switchValue, setSwitchValue] = useState(!room[preference]); + return ( + { + onChangeValue(preference, { [preference]: switchValue ? '1' : '0' }, () => setSwitchValue(switchValue)); + setSwitchValue(value); + }} + /> + ); +}; +const NotificationPreferencesView = (): React.ReactElement => { + const route = useRoute>(); + const { rid, room } = route.params; + const navigation = useNavigation(); + const serverVersion = useAppSelector(state => state.server.version); + const [hideUnreadStatus, setHideUnreadStatus] = useState(room.hideUnreadStatus); + + useEffect(() => { + navigation.setOptions({ + title: I18n.t('Notification_Preferences') + }); + }, []); + + useEffect(() => { + const observe = room.observe(); + observe.subscribe(data => { + setHideUnreadStatus(data.hideUnreadStatus); + }); + }, []); + + const saveNotificationSettings = async (key: TUnionOptionsRoomNotifications, params: IRoomNotifications, onError: Function) => { try { - await db.write(async () => { - await room.update( - protectedFunction((r: IRoomNotifications) => { - r[key] = value; - }) - ); - }); - - try { - const result = await Services.saveNotificationSettings(this.rid, params); - if (result.success) { - return; - } - } catch { - // do nothing - } - - await db.write(async () => { - await room.update( - protectedFunction((r: IRoomNotifications) => { - r[key] = room[key]; - }) - ); - }); + // @ts-ignore + logEvent(events[`NP_${key.toUpperCase()}`]); + await Services.saveNotificationSettings(rid, params); } catch (e) { // @ts-ignore logEvent(events[`NP_${key.toUpperCase()}_F`]); log(e); + onError(); + showErrorAlertWithEMessage(e); } }; - onValueChangeSwitch = (key: string, value: string | boolean) => - this.saveNotificationSettings(key, value, { [key]: value ? '1' : '0' }); + return ( + + + + + + } + /> + + + - onValueChangePicker = (key: string, value: string) => this.saveNotificationSettings(key, value, { [key]: value.toString() }); + + + } + /> + + + - pickerSelection = (title: string, key: string) => { - const { room } = this.state; - const { navigation } = this.props; - navigation.navigate('PickerView', { - title, - data: OPTIONS[key], - value: room[key], - onChangeValue: (value: string) => this.onValueChangePicker(key, value) - }); - }; + + + } + /> + + + - renderPickerOption = (key: string) => { - const { room } = this.state; - const { theme } = this.props; - const text = room[key] ? OPTIONS[key].find(option => option.value === room[key]) : (OPTIONS[key][0] as IOptionsField); - return ( - - {text?.label ? I18n.t(text?.label, { defaultValue: text?.label, second: text?.second }) : text?.label} - - ); - }; - - renderSwitch = (key: string) => { - const { room } = this.state; - return ( - this.onValueChangeSwitch(key, !value)} - /> - ); - }; - - render() { - const { serverVersion } = this.props; - const { room } = this.state; - return ( - - - + {hideUnreadStatus && compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.8.0') ? ( this.renderSwitch('disableNotifications')} + title='Show_badge_for_mentions' + testID='notification-preference-view-badge-for-mentions' + right={() => } /> - + + ) : null} - - - this.renderSwitch('muteGroupMentions')} - /> - - - + + + + + + + + + + + + + + + + + + + + + ); +}; - - - this.renderSwitch('hideUnreadStatus')} - /> - - - - - {room.hideUnreadStatus && compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.8.0') ? ( - - - this.renderSwitch('hideMentionStatus')} - /> - - - - ) : null} - - - - this.pickerSelection(title, 'desktopNotifications')} - right={() => this.renderPickerOption('desktopNotifications')} - /> - - - - - - - this.pickerSelection(title, 'mobilePushNotifications')} - right={() => this.renderPickerOption('mobilePushNotifications')} - /> - - - - - - - this.pickerSelection(title, 'audioNotifications')} - right={() => this.renderPickerOption('audioNotifications')} - /> - - this.pickerSelection(title, 'audioNotificationValue')} - right={() => this.renderPickerOption('audioNotificationValue')} - /> - - this.pickerSelection(title, 'desktopNotificationDuration')} - right={() => this.renderPickerOption('desktopNotificationDuration')} - /> - - - - - - this.pickerSelection(title, 'emailNotifications')} - right={() => this.renderPickerOption('emailNotifications')} - /> - - - - - ); - } -} - -const mapStateToProps = (state: IApplicationState) => ({ - serverVersion: state.server.version -}); - -export default connect(mapStateToProps)(withTheme(NotificationPreferencesView)); +export default NotificationPreferencesView; diff --git a/app/views/NotificationPreferencesView/options.ts b/app/views/NotificationPreferencesView/options.ts index a2b3251c6..ee98aebbb 100644 --- a/app/views/NotificationPreferencesView/options.ts +++ b/app/views/NotificationPreferencesView/options.ts @@ -4,11 +4,9 @@ export interface IOptionsField { second?: number; } export interface INotificationOptions { - [desktopNotifications: string]: IOptionsField[]; - audioNotifications: IOptionsField[]; + desktopNotifications: IOptionsField[]; mobilePushNotifications: IOptionsField[]; emailNotifications: IOptionsField[]; - desktopNotificationDuration: IOptionsField[]; audioNotificationValue: IOptionsField[]; } @@ -31,24 +29,6 @@ export const OPTIONS: INotificationOptions = { value: 'nothing' } ], - audioNotifications: [ - { - label: 'Default', - value: 'default' - }, - { - label: 'All_Messages', - value: 'all' - }, - { - label: 'Mentions', - value: 'mentions' - }, - { - label: 'Nothing', - value: 'nothing' - } - ], mobilePushNotifications: [ { label: 'Default', @@ -85,37 +65,6 @@ export const OPTIONS: INotificationOptions = { value: 'nothing' } ], - desktopNotificationDuration: [ - { - label: 'Default', - value: 0 - }, - { - label: 'Seconds', - second: 1, - value: 1 - }, - { - label: 'Seconds', - second: 2, - value: 2 - }, - { - label: 'Seconds', - second: 3, - value: 3 - }, - { - label: 'Seconds', - second: 4, - value: 4 - }, - { - label: 'Seconds', - second: 5, - value: 5 - } - ], audioNotificationValue: [ { label: 'None', From 2c32be6e519ca6f04fe68816b7bc9b4455afa75c Mon Sep 17 00:00:00 2001 From: Gleidson Daniel Silva Date: Mon, 1 Aug 2022 13:42:10 -0300 Subject: [PATCH 05/10] [FIX] Fix display order render on RoomListView (#4392) --- app/views/RoomsListView/index.tsx | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/app/views/RoomsListView/index.tsx b/app/views/RoomsListView/index.tsx index 90f22b749..84062f715 100644 --- a/app/views/RoomsListView/index.tsx +++ b/app/views/RoomsListView/index.tsx @@ -530,21 +530,6 @@ class RoomsListView extends React.Component ({ rid: item.rid, alert: item.alert })); - } else { - /** - * Otherwise, we trigger re-render only when chats order changes - * RoomItem handles its own re-render - */ - chatsUpdate = data.map(item => item.rid); - } - const isOmnichannelAgent = user?.roles?.includes('livechat-agent'); if (isOmnichannelAgent) { const omnichannel = chats.filter(s => filterIsOmnichannel(s)); @@ -586,6 +571,8 @@ class RoomsListView extends React.Component item.rid); + this.internalSetState({ chats: tempChats, chatsUpdate, From a4f171a12d0c5a1830ad71551a584df7028db10b Mon Sep 17 00:00:00 2001 From: Danish Ahmed Mirza <77742477+try-catch-stack@users.noreply.github.com> Date: Wed, 3 Aug 2022 22:02:16 +0530 Subject: [PATCH 06/10] [NEW] Redesign reactions list (#4346) --- app/containers/ActionSheet/ActionSheet.tsx | 2 + app/containers/ActionSheet/Provider.tsx | 1 + app/containers/ReactionsList.tsx | 143 ++++++++++++++++++ app/containers/ReactionsModal.tsx | 166 --------------------- app/definitions/index.ts | 1 + app/views/RoomView/index.tsx | 38 ++--- 6 files changed, 167 insertions(+), 184 deletions(-) create mode 100644 app/containers/ReactionsList.tsx delete mode 100644 app/containers/ReactionsModal.tsx diff --git a/app/containers/ActionSheet/ActionSheet.tsx b/app/containers/ActionSheet/ActionSheet.tsx index fb00d5267..e114b8e1c 100644 --- a/app/containers/ActionSheet/ActionSheet.tsx +++ b/app/containers/ActionSheet/ActionSheet.tsx @@ -140,6 +140,8 @@ const ActionSheet = React.memo( style={{ ...styles.container, ...bottomSheet }} backgroundStyle={{ backgroundColor: colors.focusedBackground }} onChange={index => index === -1 && onClose()} + // We need this to allow horizontal swipe gestures inside bottom sheet like in reaction picker + enableContentPanningGesture={data?.enableContentPanningGesture ?? true} {...androidTablet}> diff --git a/app/containers/ActionSheet/Provider.tsx b/app/containers/ActionSheet/Provider.tsx index b7caf6416..4b21726bc 100644 --- a/app/containers/ActionSheet/Provider.tsx +++ b/app/containers/ActionSheet/Provider.tsx @@ -22,6 +22,7 @@ export type TActionSheetOptions = { children?: React.ReactElement | null; snaps?: (string | number)[]; onClose?: () => void; + enableContentPanningGesture?: boolean; }; export interface IActionSheetProvider { showActionSheet: (item: TActionSheetOptions) => void; diff --git a/app/containers/ReactionsList.tsx b/app/containers/ReactionsList.tsx new file mode 100644 index 000000000..10a1b3c80 --- /dev/null +++ b/app/containers/ReactionsList.tsx @@ -0,0 +1,143 @@ +import React from 'react'; +import { StyleSheet, Text, Pressable, View, ScrollView } from 'react-native'; +import ScrollableTabView from 'react-native-scrollable-tab-view'; +import { FlatList } from 'react-native-gesture-handler'; + +import Emoji from './message/Emoji'; +import { useTheme } from '../theme'; +import { TGetCustomEmoji } from '../definitions/IEmoji'; +import { IReaction } from '../definitions'; +import Avatar from './Avatar'; +import sharedStyles from '../views/Styles'; + +const MIN_TAB_WIDTH = 70; + +const styles = StyleSheet.create({ + reactionsListContainer: { height: '100%', width: '100%' }, + tabBarItem: { + paddingHorizontal: 10, + paddingBottom: 10, + justifyContent: 'center', + alignItems: 'center', + flexDirection: 'row' + }, + reactionCount: { marginLeft: 5 }, + emojiName: { margin: 10 }, + userItemContainer: { marginHorizontal: 10, marginVertical: 5, flexDirection: 'row' }, + usernameContainer: { marginHorizontal: 10, justifyContent: 'center' }, + usernameText: { fontSize: 17, ...sharedStyles.textMedium }, + standardEmojiStyle: { fontSize: 20, color: '#fff' }, + customEmojiStyle: { width: 25, height: 25 } +}); + +interface IReactionsListBase { + baseUrl: string; + getCustomEmoji: TGetCustomEmoji; +} + +interface IReactionsListProps extends IReactionsListBase { + reactions?: IReaction[]; + width: number; +} + +interface ITabBarItem extends IReactionsListBase { + tab: IReaction; + index: number; + goToPage?: (index: number) => void; +} +interface IReactionsTabBar extends IReactionsListBase { + activeTab?: number; + tabs?: IReaction[]; + goToPage?: (index: number) => void; + width: number; +} + +const TabBarItem = ({ tab, index, goToPage, baseUrl, getCustomEmoji }: ITabBarItem) => { + const { colors } = useTheme(); + return ( + { + goToPage?.(index); + }} + style={({ pressed }: { pressed: boolean }) => ({ + opacity: pressed ? 0.7 : 1 + })}> + + + {tab.usernames.length} + + + ); +}; + +const ReactionsTabBar = ({ tabs, activeTab, goToPage, baseUrl, getCustomEmoji, width }: IReactionsTabBar) => { + const tabWidth = tabs && Math.max(width / tabs.length, MIN_TAB_WIDTH); + const { colors } = useTheme(); + return ( + + + {tabs?.map((tab, index) => { + const isActiveTab = activeTab === index; + return ( + + + + ); + })} + + + ); +}; + +const UsersList = ({ tabLabel }: { tabLabel: IReaction }) => { + const { colors } = useTheme(); + const { emoji, usernames } = tabLabel; + return ( + ( + + {emoji} + + )} + renderItem={({ item }) => ( + + + + {item} + + + )} + keyExtractor={item => item} + /> + ); +}; + +const ReactionsList = ({ reactions, baseUrl, getCustomEmoji, width }: IReactionsListProps): React.ReactElement => { + // sorting reactions in descending order on the basic of number of users reacted + const sortedReactions = reactions?.sort((reaction1, reaction2) => reaction2.usernames.length - reaction1.usernames.length); + + return ( + + }> + {sortedReactions?.map(reaction => ( + + ))} + + + ); +}; + +export default ReactionsList; diff --git a/app/containers/ReactionsModal.tsx b/app/containers/ReactionsModal.tsx deleted file mode 100644 index b74707f04..000000000 --- a/app/containers/ReactionsModal.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import React from 'react'; -import { FlatList, StyleSheet, Text, View } from 'react-native'; -import Modal from 'react-native-modal'; -import Touchable from 'react-native-platform-touchable'; - -import Emoji from './message/Emoji'; -import I18n from '../i18n'; -import { CustomIcon } from './CustomIcon'; -import sharedStyles from '../views/Styles'; -import { themes } from '../lib/constants'; -import { TSupportedThemes, useTheme, withTheme } from '../theme'; -import { TGetCustomEmoji } from '../definitions/IEmoji'; -import { TMessageModel, ILoggedUser } from '../definitions'; -import SafeAreaView from './SafeAreaView'; - -const styles = StyleSheet.create({ - safeArea: { - backgroundColor: 'transparent' - }, - titleContainer: { - alignItems: 'center', - paddingVertical: 10 - }, - title: { - fontSize: 16, - ...sharedStyles.textSemibold, - ...sharedStyles.textAlignCenter - }, - reactCount: { - fontSize: 13, - ...sharedStyles.textRegular - }, - peopleReacted: { - fontSize: 14, - ...sharedStyles.textMedium - }, - peopleItemContainer: { - flex: 1, - flexDirection: 'column', - justifyContent: 'center' - }, - emojiContainer: { - width: 50, - height: 50, - alignItems: 'center', - justifyContent: 'center' - }, - itemContainer: { - height: 50, - flexDirection: 'row' - }, - listContainer: { - flex: 1 - }, - closeButton: { - position: 'absolute', - left: 0, - top: 10 - } -}); -const standardEmojiStyle = { fontSize: 20 }; -const customEmojiStyle = { width: 20, height: 20 }; - -interface ISharedFields { - user?: Pick; - baseUrl: string; - getCustomEmoji: TGetCustomEmoji; -} - -interface IItem extends ISharedFields { - item: { - usernames: string[]; - emoji: string; - }; -} - -interface IModalContent extends ISharedFields { - message?: TMessageModel; - onClose: () => void; - theme: TSupportedThemes; -} - -interface IReactionsModal extends ISharedFields { - message?: TMessageModel; - isVisible: boolean; - onClose(): void; -} - -const Item = React.memo(({ item, user, baseUrl, getCustomEmoji }: IItem) => { - const { theme } = useTheme(); - const count = item.usernames.length; - let usernames = item.usernames - .slice(0, 3) - .map((username: string) => (username === user?.username ? I18n.t('you') : username)) - .join(', '); - if (count > 3) { - usernames = `${usernames} ${I18n.t('and_more')} ${count - 3}`; - } else { - usernames = usernames.replace(/,(?=[^,]*$)/, ` ${I18n.t('and')}`); - } - return ( - - - - - - - {count === 1 ? I18n.t('1_person_reacted') : I18n.t('N_people_reacted', { n: count })} - - {usernames} - - - ); -}); - -const ModalContent = React.memo(({ message, onClose, ...props }: IModalContent) => { - if (message && message.reactions) { - return ( - - - - - {I18n.t('Reactions')} - - - } - keyExtractor={item => item.emoji} - /> - - ); - } - return null; -}); - -const ReactionsModal = React.memo( - ({ isVisible, onClose, ...props }: IReactionsModal) => { - const { theme } = useTheme(); - return ( - - - - ); - }, - (prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible -); - -ReactionsModal.displayName = 'ReactionsModal'; -ModalContent.displayName = 'ReactionsModalContent'; -Item.displayName = 'ReactionsModalItem'; - -export default withTheme(ReactionsModal); diff --git a/app/definitions/index.ts b/app/definitions/index.ts index 4f7d7c3d3..cbe244639 100644 --- a/app/definitions/index.ts +++ b/app/definitions/index.ts @@ -28,6 +28,7 @@ export * from './ICredentials'; export * from './ISearch'; export * from './TUserStatus'; export * from './IProfile'; +export * from './IReaction'; export interface IBaseScreen, S extends string> { navigation: StackNavigationProp; diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx index 0c79ddd41..f12177925 100644 --- a/app/views/RoomView/index.tsx +++ b/app/views/RoomView/index.tsx @@ -3,7 +3,6 @@ import { InteractionManager, Text, View } from 'react-native'; import { connect } from 'react-redux'; import parse from 'url-parse'; import moment from 'moment'; -import * as Haptics from 'expo-haptics'; import { Q } from '@nozbe/watermelondb'; import { dequal } from 'dequal'; import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context'; @@ -22,7 +21,7 @@ import EventEmitter from '../../lib/methods/helpers/events'; import I18n from '../../i18n'; import RoomHeader from '../../containers/RoomHeader'; import StatusBar from '../../containers/StatusBar'; -import ReactionsModal from '../../containers/ReactionsModal'; +import ReactionsList from '../../containers/ReactionsList'; import { LISTENER } from '../../containers/Toast'; import { getBadgeColor, isBlocked, makeThreadName } from '../../lib/methods/helpers/room'; import { isReadOnly } from '../../lib/methods/helpers/isReadOnly'; @@ -100,6 +99,7 @@ import { hasPermission } from '../../lib/methods/helpers'; import { Services } from '../../lib/services'; +import { withActionSheet, TActionSheetOptions } from '../../containers/ActionSheet'; type TStateAttrsUpdate = keyof IRoomViewState; @@ -170,6 +170,7 @@ interface IRoomViewProps extends IBaseScreen { transferLivechatGuestPermission?: string[]; // TODO: Check if its the correct type viewCannedResponsesPermission?: string[]; // TODO: Check if its the correct type livechatAllowManualOnHold?: boolean; + showActionSheet: (options: TActionSheetOptions) => void; } interface IRoomViewState { @@ -853,12 +854,21 @@ class RoomView extends React.Component { }; onReactionLongPress = (message: TAnyMessageModel) => { - this.setState({ selectedMessage: message, reactionsModalVisible: true }); - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - }; - - onCloseReactionsModal = () => { - this.setState({ selectedMessage: undefined, reactionsModalVisible: false }); + this.setState({ selectedMessage: message }); + const { showActionSheet, baseUrl, width } = this.props; + const { selectedMessage } = this.state; + showActionSheet({ + children: ( + + ), + snaps: ['50%'], + enableContentPanningGesture: false + }); }; onEncryptedPress = () => { @@ -1469,7 +1479,7 @@ class RoomView extends React.Component { render() { console.count(`${this.constructor.name}.render calls`); - const { room, reactionsModalVisible, selectedMessage, loading, reacting, showingBlockingLoader } = this.state; + const { room, selectedMessage, loading, reacting, showingBlockingLoader } = this.state; const { user, baseUrl, theme, navigation, Hide_System_Messages, width, height, serverVersion } = this.props; const { rid, t } = room; let sysMes; @@ -1512,14 +1522,6 @@ class RoomView extends React.Component { theme={theme} /> - @@ -1545,4 +1547,4 @@ const mapStateToProps = (state: IApplicationState) => ({ livechatAllowManualOnHold: state.settings.Livechat_allow_manual_on_hold as boolean }); -export default connect(mapStateToProps)(withDimensions(withTheme(withSafeAreaInsets(RoomView)))); +export default connect(mapStateToProps)(withDimensions(withTheme(withSafeAreaInsets(withActionSheet(RoomView))))); From e38aedcbff3db3649b71684286f2e78e0be67b15 Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Mon, 8 Aug 2022 15:38:01 -0300 Subject: [PATCH 07/10] [IMPROVE] Add emoji picker to iOS (#4366) * [IMPROVE] Add emoji picker to iOS * clean left and right buttons * fix the redux in emojipicker * fix behavior when emoji keyboard is openning * added isIOS * fix show reactions when emoji is open * minor tweak * add provider * fix baseurl * minor tweak * create closeEmojiAndAction and added to record * fix actionsheet for omnichannel * fix action sheet * fix close emoji when navigate to other screen * added iactionsheetprovider to roomview * clean variables * fix theme * close the emojikeyboard when click on message * apoint package.json to new pr * fix branch * fix package.json --- app/containers/EmojiPicker/EmojiCategory.tsx | 1 + app/containers/EmojiPicker/index.tsx | 7 +- app/containers/MessageBox/EmojiKeyboard.tsx | 37 +++---- app/containers/MessageBox/LeftButtons.ios.tsx | 24 ----- ...eftButtons.android.tsx => LeftButtons.tsx} | 0 app/containers/MessageBox/RecordAudio.tsx | 4 + .../MessageBox/RightButtons.ios.tsx | 17 ---- ...htButtons.android.tsx => RightButtons.tsx} | 3 +- app/containers/MessageBox/constants.ts | 2 + app/containers/MessageBox/index.tsx | 40 ++++---- app/containers/message/index.tsx | 11 ++- app/views/RoomView/ReactionPicker.tsx | 6 +- app/views/RoomView/RightButtons.tsx | 98 ++++++++++--------- app/views/RoomView/index.tsx | 20 ++-- package.json | 2 +- yarn.lock | 6 +- 16 files changed, 129 insertions(+), 149 deletions(-) delete mode 100644 app/containers/MessageBox/LeftButtons.ios.tsx rename app/containers/MessageBox/{LeftButtons.android.tsx => LeftButtons.tsx} (100%) delete mode 100644 app/containers/MessageBox/RightButtons.ios.tsx rename app/containers/MessageBox/{RightButtons.android.tsx => RightButtons.tsx} (83%) diff --git a/app/containers/EmojiPicker/EmojiCategory.tsx b/app/containers/EmojiPicker/EmojiCategory.tsx index a7fb07b11..a1cf1f981 100644 --- a/app/containers/EmojiPicker/EmojiCategory.tsx +++ b/app/containers/EmojiPicker/EmojiCategory.tsx @@ -65,6 +65,7 @@ class EmojiCategory extends React.Component { initialNumToRender={45} removeClippedSubviews {...scrollPersistTaps} + keyboardDismissMode={'none'} /> ); } diff --git a/app/containers/EmojiPicker/index.tsx b/app/containers/EmojiPicker/index.tsx index 4bfa17b6a..558a23bb4 100644 --- a/app/containers/EmojiPicker/index.tsx +++ b/app/containers/EmojiPicker/index.tsx @@ -17,7 +17,7 @@ import protectedFunction from '../../lib/methods/helpers/protectedFunction'; import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode'; import log from '../../lib/methods/helpers/log'; import { themes } from '../../lib/constants'; -import { TSupportedThemes, withTheme } from '../../theme'; +import { TSupportedThemes } from '../../theme'; import { IEmoji, TGetCustomEmoji, IApplicationState, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definitions'; interface IEmojiPickerProps { @@ -198,7 +198,8 @@ class EmojiPicker extends Component { } const mapStateToProps = (state: IApplicationState) => ({ - customEmojis: state.customEmojis + customEmojis: state.customEmojis, + baseUrl: state.share.server.server || state.server.server }); -export default connect(mapStateToProps)(withTheme(EmojiPicker)); +export default connect(mapStateToProps)(EmojiPicker); diff --git a/app/containers/MessageBox/EmojiKeyboard.tsx b/app/containers/MessageBox/EmojiKeyboard.tsx index 172b7f4b8..114733eeb 100644 --- a/app/containers/MessageBox/EmojiKeyboard.tsx +++ b/app/containers/MessageBox/EmojiKeyboard.tsx @@ -1,39 +1,28 @@ import React from 'react'; import { View } from 'react-native'; import { KeyboardRegistry } from 'react-native-ui-lib/keyboard'; +import { Provider } from 'react-redux'; -import { store } from '../../lib/store/auxStore'; +import store from '../../lib/store'; import EmojiPicker from '../EmojiPicker'; import styles from './styles'; import { themes } from '../../lib/constants'; -import { TSupportedThemes, withTheme } from '../../theme'; +import { TSupportedThemes } from '../../theme'; -interface IMessageBoxEmojiKeyboard { - theme: TSupportedThemes; -} - -export default class EmojiKeyboard extends React.PureComponent { - private readonly baseUrl: string; - - constructor(props: IMessageBoxEmojiKeyboard) { - super(props); - const state = store.getState(); - this.baseUrl = state.share.server.server || state.server.server; - } - - onEmojiSelected = (emoji: string) => { +const EmojiKeyboard = ({ theme }: { theme: TSupportedThemes }) => { + const onEmojiSelected = (emoji: string) => { KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji }); }; - render() { - const { theme } = this.props; - return ( + return ( + - + - ); - } -} -KeyboardRegistry.registerKeyboard('EmojiKeyboard', () => withTheme(EmojiKeyboard)); + + ); +}; + +KeyboardRegistry.registerKeyboard('EmojiKeyboard', () => EmojiKeyboard); diff --git a/app/containers/MessageBox/LeftButtons.ios.tsx b/app/containers/MessageBox/LeftButtons.ios.tsx deleted file mode 100644 index 94d253705..000000000 --- a/app/containers/MessageBox/LeftButtons.ios.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { View } from 'react-native'; - -import { ActionsButton, CancelEditingButton } from './buttons'; -import styles from './styles'; - -interface IMessageBoxLeftButtons { - showMessageBoxActions(): void; - editing: boolean; - editCancel(): void; - isActionsEnabled: boolean; -} - -const LeftButtons = React.memo(({ showMessageBoxActions, editing, editCancel, isActionsEnabled }: IMessageBoxLeftButtons) => { - if (editing) { - return ; - } - if (isActionsEnabled) { - return ; - } - return ; -}); - -export default LeftButtons; diff --git a/app/containers/MessageBox/LeftButtons.android.tsx b/app/containers/MessageBox/LeftButtons.tsx similarity index 100% rename from app/containers/MessageBox/LeftButtons.android.tsx rename to app/containers/MessageBox/LeftButtons.tsx diff --git a/app/containers/MessageBox/RecordAudio.tsx b/app/containers/MessageBox/RecordAudio.tsx index 4b550b62b..1dd5e99ec 100644 --- a/app/containers/MessageBox/RecordAudio.tsx +++ b/app/containers/MessageBox/RecordAudio.tsx @@ -17,6 +17,7 @@ interface IMessageBoxRecordAudioProps { permissionToUpload: boolean; recordingCallback: Function; onFinish: Function; + onStart: Function; } const RECORDING_EXTENSION = '.m4a'; @@ -116,6 +117,9 @@ export default class RecordAudio extends React.PureComponent { + const { onStart } = this.props; + onStart(); + logEvent(events.ROOM_AUDIO_RECORD); if (!this.isRecorderBusy) { this.isRecorderBusy = true; diff --git a/app/containers/MessageBox/RightButtons.ios.tsx b/app/containers/MessageBox/RightButtons.ios.tsx deleted file mode 100644 index f2cf2bca5..000000000 --- a/app/containers/MessageBox/RightButtons.ios.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; - -import { SendButton } from './buttons'; - -interface IMessageBoxRightButtons { - showSend: boolean; - submit(): void; -} - -const RightButtons = ({ showSend, submit }: IMessageBoxRightButtons) => { - if (showSend) { - return ; - } - return null; -}; - -export default RightButtons; diff --git a/app/containers/MessageBox/RightButtons.android.tsx b/app/containers/MessageBox/RightButtons.tsx similarity index 83% rename from app/containers/MessageBox/RightButtons.android.tsx rename to app/containers/MessageBox/RightButtons.tsx index 4af8d5027..d19109e61 100644 --- a/app/containers/MessageBox/RightButtons.android.tsx +++ b/app/containers/MessageBox/RightButtons.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { View } from 'react-native'; +import { isIOS } from '../../lib/methods/helpers'; import { ActionsButton, SendButton } from './buttons'; import styles from './styles'; @@ -18,7 +19,7 @@ const RightButtons = React.memo(({ showSend, submit, showMessageBoxActions, isAc if (isActionsEnabled) { return ; } - return ; + return !isIOS ? : null; }); export default RightButtons; diff --git a/app/containers/MessageBox/constants.ts b/app/containers/MessageBox/constants.ts index dabaee497..c966c60ce 100644 --- a/app/containers/MessageBox/constants.ts +++ b/app/containers/MessageBox/constants.ts @@ -4,3 +4,5 @@ export const MENTIONS_TRACKING_TYPE_COMMANDS = '/'; export const MENTIONS_TRACKING_TYPE_ROOMS = '#'; export const MENTIONS_TRACKING_TYPE_CANNED = '!'; export const MENTIONS_COUNT_TO_DISPLAY = 4; + +export const TIMEOUT_CLOSE_EMOJI = 300; diff --git a/app/containers/MessageBox/index.tsx b/app/containers/MessageBox/index.tsx index 107edd64d..e42c30905 100644 --- a/app/containers/MessageBox/index.tsx +++ b/app/containers/MessageBox/index.tsx @@ -19,11 +19,7 @@ import RecordAudio from './RecordAudio'; import I18n from '../../i18n'; import ReplyPreview from './ReplyPreview'; import { themes } from '../../lib/constants'; -// @ts-ignore -// eslint-disable-next-line import/extensions,import/no-unresolved import LeftButtons from './LeftButtons'; -// @ts-ignore -// eslint-disable-next-line import/extensions,import/no-unresolved import RightButtons from './RightButtons'; import { canUploadFile } from '../../lib/methods/helpers/media'; import EventEmiter from '../../lib/methods/helpers/events'; @@ -37,12 +33,13 @@ import { MENTIONS_TRACKING_TYPE_COMMANDS, MENTIONS_TRACKING_TYPE_EMOJIS, MENTIONS_TRACKING_TYPE_ROOMS, - MENTIONS_TRACKING_TYPE_USERS + MENTIONS_TRACKING_TYPE_USERS, + TIMEOUT_CLOSE_EMOJI } from './constants'; import CommandsPreview from './CommandsPreview'; import { getUserSelector } from '../../selectors/login'; import Navigation from '../../lib/navigation/appNavigation'; -import { withActionSheet } from '../ActionSheet'; +import { TActionSheetOptionsItem, withActionSheet } from '../ActionSheet'; import { sanitizeLikeString } from '../../lib/database/utils'; import { CustomIcon } from '../CustomIcon'; import { forceJpgExtension } from './forceJpgExtension'; @@ -58,14 +55,12 @@ import { } from '../../definitions'; import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types'; import { getPermalinkMessage, search, sendFileMessage } from '../../lib/methods'; -import { hasPermission, debounce, isAndroid, isTablet } from '../../lib/methods/helpers'; +import { hasPermission, debounce, isAndroid, isIOS, isTablet } from '../../lib/methods/helpers'; import { Services } from '../../lib/services'; import { TSupportedThemes } from '../../theme'; import { ChatsStackParamList } from '../../stacks/types'; -if (isAndroid) { - require('./EmojiKeyboard'); -} +require('./EmojiKeyboard'); const imagePickerConfig = { cropping: true, @@ -270,6 +265,7 @@ class MessageBox extends Component { }, 500); }); this.unsubscribeBlur = navigation.addListener('blur', () => { + this.closeEmoji(); this.component?.blur(); }); } @@ -330,6 +326,9 @@ class MessageBox extends Component { if (nextProps.theme !== theme) { return true; } + if (nextState.showEmojiKeyboard !== showEmojiKeyboard) { + return true; + } if (!isFocused()) { return false; } @@ -342,9 +341,6 @@ class MessageBox extends Component { if (nextProps.editing !== editing) { return true; } - if (nextState.showEmojiKeyboard !== showEmojiKeyboard) { - return true; - } if (nextState.trackingType !== trackingType) { return true; } @@ -809,7 +805,7 @@ class MessageBox extends Component { const { permissionToUpload } = this.state; const { showActionSheet, goToCannedResponses } = this.props; - const options = []; + const options: TActionSheetOptionsItem[] = []; if (goToCannedResponses) { options.push({ title: I18n.t('Canned_Responses'), @@ -847,7 +843,8 @@ class MessageBox extends Component { icon: 'discussions', onPress: this.createDiscussion }); - showActionSheet({ options }); + + this.closeEmojiAndAction(showActionSheet, { options }); }; editCancel = () => { @@ -883,6 +880,13 @@ class MessageBox extends Component { this.setState({ showEmojiKeyboard: false }); }; + closeEmojiAndAction = (action?: Function, params?: any) => { + const { showEmojiKeyboard } = this.state; + + this.closeEmoji(); + setTimeout(() => action && action(params), showEmojiKeyboard && isIOS ? TIMEOUT_CLOSE_EMOJI : null); + }; + submit = async () => { const { tshow } = this.state; const { onSubmit, rid: roomId, tmid, showSend, sharing } = this.props; @@ -1089,6 +1093,7 @@ class MessageBox extends Component { recordingCallback={this.recordingCallback} onFinish={this.finishAudioMessage} permissionToUpload={permissionToUpload} + onStart={this.closeEmoji} /> ); @@ -1112,14 +1117,11 @@ class MessageBox extends Component { const textInputAndButtons = !recording ? ( <> (this.component = component)} @@ -1138,7 +1140,6 @@ class MessageBox extends Component { {...isAndroidTablet} /> { renderContent={this.renderContent} kbInputRef={this.component} kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null} + kbInitialProps={{ theme }} onKeyboardResigned={this.onKeyboardResigned} onItemSelected={this.onEmojiSelected} trackInteractive diff --git a/app/containers/message/index.tsx b/app/containers/message/index.tsx index 7af7f0899..e123bb34a 100644 --- a/app/containers/message/index.tsx +++ b/app/containers/message/index.tsx @@ -58,6 +58,7 @@ interface IMessageContainerProps { jumpToMessage?: (link: string) => void; onPress?: () => void; theme: TSupportedThemes; + closeEmojiAndAction?: (action?: Function, params?: any) => void; } interface IMessageContainerState { @@ -114,6 +115,14 @@ class MessageContainer extends React.Component { + const { closeEmojiAndAction } = this.props; + + if (closeEmojiAndAction) { + closeEmojiAndAction(this.onPress); + } + }; + onPress = debounce( () => { const { onPress } = this.props; @@ -373,7 +382,7 @@ class MessageContainer extends React.Component { }; render() { - const { width, height, show, baseUrl, reactionClose, isMasterDetail, theme } = this.props; + const { width, height, show, reactionClose, isMasterDetail, theme } = this.props; let widthStyle = width - margin; let heightStyle = Math.min(width, height) - margin * 2; @@ -70,7 +69,7 @@ class ReactionPicker extends React.Component { } ]} testID='reaction-picker'> - + ) : null; @@ -78,7 +77,6 @@ class ReactionPicker extends React.Component { } const mapStateToProps = (state: IApplicationState) => ({ - baseUrl: state.server.server, isMasterDetail: state.app.isMasterDetail }); diff --git a/app/views/RoomView/RightButtons.tsx b/app/views/RoomView/RightButtons.tsx index 6aaabdcc8..308169094 100644 --- a/app/views/RoomView/RightButtons.tsx +++ b/app/views/RoomView/RightButtons.tsx @@ -13,7 +13,7 @@ import { events, logEvent } from '../../lib/methods/helpers/log'; import { isTeamRoom } from '../../lib/methods/helpers/room'; import { IApplicationState, SubscriptionType, TMessageModel, TSubscriptionModel } from '../../definitions'; import { ChatsStackParamList } from '../../stacks/types'; -import { IActionSheetProvider, TActionSheetOptionsItem, withActionSheet } from '../../containers/ActionSheet'; +import { TActionSheetOptionsItem } from '../../containers/ActionSheet'; import i18n from '../../i18n'; import { showConfirmationAlert, showErrorAlert } from '../../lib/methods/helpers'; import { onHoldLivechat, returnLivechat } from '../../lib/services/restApi'; @@ -21,10 +21,10 @@ import { closeLivechat as closeLivechatService } from '../../lib/methods/helpers import { Services } from '../../lib/services'; import { ILivechatDepartment } from '../../definitions/ILivechatDepartment'; -interface IRightButtonsProps extends IActionSheetProvider { +interface IRightButtonsProps { userId?: string; threadsEnabled: boolean; - rid: string; + rid?: string; t: string; tmid?: string; teamId?: string; @@ -34,7 +34,6 @@ interface IRightButtonsProps extends IActionSheetProvider { status?: string; dispatch: Dispatch; encrypted?: boolean; - transferLivechatGuestPermission: boolean; navigation: StackNavigationProp; omnichannelPermissions: { canForwardGuest: boolean; @@ -42,6 +41,7 @@ interface IRightButtonsProps extends IActionSheetProvider { canPlaceLivechatOnHold: boolean; }; livechatRequestComment: boolean; + showActionSheet: Function; departmentId?: string; } @@ -187,34 +187,38 @@ class RightButtonsContainer extends Component { const { rid } = this.props; - showConfirmationAlert({ - message: i18n.t('Would_you_like_to_return_the_inquiry'), - confirmationText: i18n.t('Yes'), - onPress: async () => { - try { - await returnLivechat(rid); - } catch (e: any) { - showErrorAlert(e.reason, i18n.t('Oops')); + if (rid) { + showConfirmationAlert({ + message: i18n.t('Would_you_like_to_return_the_inquiry'), + confirmationText: i18n.t('Yes'), + onPress: async () => { + try { + await returnLivechat(rid); + } catch (e: any) { + showErrorAlert(e.reason, i18n.t('Oops')); + } } - } - }); + }); + } }; placeOnHoldLivechat = () => { const { navigation, rid } = this.props; - showConfirmationAlert({ - title: i18n.t('Are_you_sure_question_mark'), - message: i18n.t('Would_like_to_place_on_hold'), - confirmationText: i18n.t('Yes'), - onPress: async () => { - try { - await onHoldLivechat(rid); - navigation.navigate('RoomsListView'); - } catch (e: any) { - showErrorAlert(e.data?.error, i18n.t('Oops')); + if (rid) { + showConfirmationAlert({ + title: i18n.t('Are_you_sure_question_mark'), + message: i18n.t('Would_like_to_place_on_hold'), + confirmationText: i18n.t('Yes'), + onPress: async () => { + try { + await onHoldLivechat(rid); + navigation.navigate('RoomsListView'); + } catch (e: any) { + showErrorAlert(e.data?.error, i18n.t('Oops')); + } } - } - }); + }); + } }; closeLivechat = async () => { @@ -234,18 +238,20 @@ class RightButtonsContainer extends Component { - if (isMasterDetail) { - navigation.navigate('ModalStackNavigator', { - screen: 'ForwardLivechatView', - params: { rid } - }); - } else { - navigation.navigate('ForwardLivechatView', { rid }); + if (rid) { + if (isMasterDetail) { + navigation.navigate('ModalStackNavigator', { + screen: 'ForwardLivechatView', + params: { rid } + }); + } else { + navigation.navigate('ForwardLivechatView', { rid }); + } } } }); @@ -379,4 +387,4 @@ const mapStateToProps = (state: IApplicationState) => ({ livechatRequestComment: state.settings.Livechat_request_comment_when_closing_conversation as boolean }); -export default connect(mapStateToProps)(withActionSheet(RightButtonsContainer)); +export default connect(mapStateToProps)(RightButtonsContainer); diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx index f12177925..1803974bb 100644 --- a/app/views/RoomView/index.tsx +++ b/app/views/RoomView/index.tsx @@ -99,7 +99,7 @@ import { hasPermission } from '../../lib/methods/helpers'; import { Services } from '../../lib/services'; -import { withActionSheet, TActionSheetOptions } from '../../containers/ActionSheet'; +import { withActionSheet, IActionSheetProvider } from '../../containers/ActionSheet'; type TStateAttrsUpdate = keyof IRoomViewState; @@ -150,7 +150,7 @@ const roomAttrsUpdate = [ 't' ] as TRoomUpdate[]; -interface IRoomViewProps extends IBaseScreen { +interface IRoomViewProps extends IActionSheetProvider, IBaseScreen { user: Pick; appState: string; useRealName?: boolean; @@ -170,7 +170,6 @@ interface IRoomViewProps extends IBaseScreen { transferLivechatGuestPermission?: string[]; // TODO: Check if its the correct type viewCannedResponsesPermission?: string[]; // TODO: Check if its the correct type livechatAllowManualOnHold?: boolean; - showActionSheet: (options: TActionSheetOptions) => void; } interface IRoomViewState { @@ -185,7 +184,7 @@ interface IRoomViewState { fname?: string; prid?: string; joinCodeRequired?: boolean; - status?: boolean; + status?: string; lastMessage?: ILastMessage; sysMes?: boolean; onHold?: boolean; @@ -635,6 +634,7 @@ class RoomView extends React.Component { encrypted={encrypted} navigation={navigation} toggleFollowThread={this.toggleFollowThread} + showActionSheet={this.showActionSheet} departmentId={departmentId} /> ) @@ -785,7 +785,12 @@ class RoomView extends React.Component { }; errorActionsShow = (message: TAnyMessageModel) => { - this.messageErrorActions?.showMessageErrorActions(message); + this.messagebox?.current?.closeEmojiAndAction(this.messageErrorActions?.showMessageErrorActions, message); + }; + + showActionSheet = (options: any) => { + const { showActionSheet } = this.props; + this.messagebox?.current?.closeEmojiAndAction(showActionSheet, options); }; onEditInit = (message: TAnyMessageModel) => { @@ -834,7 +839,7 @@ class RoomView extends React.Component { }; onMessageLongPress = (message: TAnyMessageModel) => { - this.messageActions?.showMessageActions(message); + this.messagebox?.current?.closeEmojiAndAction(this.messageActions?.showMessageActions, message); }; showAttachment = (attachment: IAttachment) => { @@ -857,7 +862,7 @@ class RoomView extends React.Component { this.setState({ selectedMessage: message }); const { showActionSheet, baseUrl, width } = this.props; const { selectedMessage } = this.state; - showActionSheet({ + this.messagebox?.current?.closeEmojiAndAction(showActionSheet, { children: ( { jumpToMessage={this.jumpToMessageByUrl} highlighted={highlightedMessage === item.id} theme={theme} + closeEmojiAndAction={this.messagebox?.current?.closeEmojiAndAction} /> ); } diff --git a/package.json b/package.json index c3a015060..e5e246116 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "react-native-simple-crypto": "RocketChat/react-native-simple-crypto#0.5.0", "react-native-slowlog": "^1.0.2", "react-native-svg": "^12.3.0", - "react-native-ui-lib": "RocketChat/react-native-ui-lib#minor-improvements", + "react-native-ui-lib": "RocketChat/react-native-ui-lib", "react-native-unimodules": "^0.14.8", "react-native-vector-icons": "9.1.0", "react-native-webview": "10.3.2", diff --git a/yarn.lock b/yarn.lock index cb3170660..36a9eeaed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15491,9 +15491,9 @@ react-native-text-size@4.0.0-rc.1: resolved "https://registry.yarnpkg.com/react-native-text-size/-/react-native-text-size-4.0.0-rc.1.tgz#1e048d345dd6a5a8e1269e0585c1a5948c478da5" integrity sha512-CysqjU2jK6Yc+a+kEI222pUyTY2ywcU2HqbFqf1KHymW6OPTdvBBHqbEJKL0QiLhQaFYDbqicM+h990s9TP00g== -react-native-ui-lib@RocketChat/react-native-ui-lib#minor-improvements: - version "4.2.1" - resolved "https://codeload.github.com/RocketChat/react-native-ui-lib/tar.gz/a80f38aaa947849736ce8643253991cdcb639414" +react-native-ui-lib@RocketChat/react-native-ui-lib: + version "4.2.0" + resolved "https://codeload.github.com/RocketChat/react-native-ui-lib/tar.gz/d20c1bcd09b694fc5133fc2232fd510f5f4ba581" dependencies: babel-plugin-transform-inline-environment-variables "^0.0.2" color "^3.1.0" From 2b08b683d705b869131c1cb60b10707502a9794c Mon Sep 17 00:00:00 2001 From: Gleidson Daniel Silva Date: Mon, 8 Aug 2022 18:02:08 -0300 Subject: [PATCH 08/10] Chore: Upgrade React Native to 0.68.2 (#4316) Co-authored-by: Diego Mello --- .bundle/config | 2 + .circleci/config.yml | 12 +- .gitignore | 1 + .prettierrc.js | 4 +- .ruby-version | 1 + Gemfile | 4 + README.md | 2 +- __mocks__/expo-av.js | 14 - __mocks__/expo-keep-awake.js | 4 - __mocks__/expo-web-browser.js | 3 - __mocks__/react-native-mmkv-storage.js | 17 + __mocks__/rn-user-defaults.js | 4 - android/app/build.gradle | 124 +- android/app/src/debug/AndroidManifest.xml | 2 +- .../reactnative/ReactNativeFlipper.java | 3 +- android/app/src/main/AndroidManifest.xml | 8 +- .../chat/rocket/reactnative/MainActivity.java | 47 +- .../rocket/reactnative/MainApplication.java | 36 +- .../generated/BasePackageList.java | 21 - .../MainApplicationReactNativeHost.java | 116 + .../components/MainComponentsRegistry.java | 36 + ...ApplicationTurboModuleManagerDelegate.java | 48 + android/app/src/main/jni/Android.mk | 49 + .../jni/MainApplicationModuleProvider.cpp | 24 + .../main/jni/MainApplicationModuleProvider.h | 16 + ...nApplicationTurboModuleManagerDelegate.cpp | 45 + ...ainApplicationTurboModuleManagerDelegate.h | 38 + .../src/main/jni/MainComponentsRegistry.cpp | 61 + .../app/src/main/jni/MainComponentsRegistry.h | 32 + android/app/src/main/jni/OnLoad.cpp | 11 + .../res/drawable/rn_edit_text_material.xml | 29 + android/app/src/main/res/values/styles.xml | 2 + android/app/src/play/AndroidManifest.xml | 4 +- android/build.gradle | 56 +- android/gradle.properties | 22 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/gradlew | 269 +- android/settings.gradle | 12 +- app/AppContainer.tsx | 3 +- .../ActionSheet/BottomSheetContent.tsx | 3 +- app/containers/ActionSheet/Item.tsx | 6 +- .../__snapshots__/index.stories.storyshot | 6 +- .../__snapshots__/Button.stories.storyshot | 10 +- app/containers/Button/index.tsx | 3 +- app/containers/EmojiPicker/EmojiCategory.tsx | 3 +- app/containers/EmojiPicker/TabBar.tsx | 3 +- app/containers/EmojiPicker/index.tsx | 3 +- app/containers/FormContainer.tsx | 6 +- app/containers/ImageViewer/ImageViewer.tsx | 8 +- .../InAppNotification/NotifierComponent.tsx | 6 +- app/containers/KeyboardView.tsx | 3 +- app/containers/List/ListContainer.tsx | 3 +- app/containers/List/ListItem.tsx | 6 +- .../LoginServices/ButtonService.tsx | 3 +- .../LoginServices.stories.storyshot | 2 +- app/containers/MessageActions/Header.tsx | 6 +- .../MessageBox/CommandsPreview/Item.tsx | 6 +- app/containers/MessageBox/EmojiKeyboard.tsx | 3 +- .../MessageBox/Mentions/FixedMentionItem.tsx | 3 +- .../MessageBox/Mentions/MentionItem.tsx | 3 +- app/containers/MessageBox/RecordAudio.tsx | 9 +- .../MessageBox/buttons/BaseButton.tsx | 3 +- app/containers/MessageBox/index.tsx | 11 +- app/containers/Passcode/Base/Button.tsx | 3 +- app/containers/RoomHeader/RoomHeader.tsx | 3 +- app/containers/RoomItem/Actions.tsx | 9 +- app/containers/RoomItem/RoomItem.tsx | 6 +- app/containers/RoomItem/Title.tsx | 3 +- app/containers/RoomItem/Touchable.tsx | 3 +- app/containers/RoomItem/UpdatedAt.tsx | 3 +- app/containers/RoomItem/Wrapper.tsx | 6 +- app/containers/SafeAreaView.tsx | 3 +- .../__snapshots__/SearchBox.stories.storyshot | 2 +- app/containers/ServerItem/index.tsx | 3 +- .../__snapshots__/TextInput.stories.storyshot | 2 +- app/containers/TwoFactor/index.tsx | 3 +- app/containers/UIKit/DatePicker.tsx | 3 +- app/containers/UIKit/MultiSelect/Chips.tsx | 3 +- app/containers/UIKit/MultiSelect/Input.tsx | 3 +- app/containers/UIKit/Overflow.tsx | 9 +- app/containers/UnreadBadge/index.tsx | 3 +- app/containers/UserItem.tsx | 3 +- app/containers/markdown/AtMention.tsx | 3 +- app/containers/markdown/Hashtag.tsx | 3 +- app/containers/markdown/Preview.tsx | 3 +- app/containers/markdown/Table.tsx | 3 +- app/containers/markdown/index.tsx | 6 +- app/containers/markdown/new/Code.tsx | 3 +- app/containers/markdown/new/InlineCode.tsx | 3 +- app/containers/markdown/new/index.tsx | 3 +- app/containers/message/Audio.tsx | 12 +- app/containers/message/Broadcast.tsx | 3 +- app/containers/message/CallButton.tsx | 3 +- .../CollapsibleQuote.stories.js | 3 +- .../CollapsibleQuote.test.tsx | 3 +- .../CollapsibleQuote.stories.storyshot | 2 +- .../Components/CollapsibleQuote/index.tsx | 3 +- app/containers/message/Discussion.tsx | 3 +- app/containers/message/Image.tsx | 3 +- app/containers/message/Message.tsx | 3 +- app/containers/message/Reactions.tsx | 6 +- app/containers/message/Reply.tsx | 6 +- app/containers/message/Urls.tsx | 3 +- app/containers/message/User.tsx | 3 +- app/containers/message/Video.tsx | 3 +- app/containers/message/index.tsx | 3 +- app/index.tsx | 64 +- app/lib/methods/helpers/log/index.ts | 2 +- app/lib/methods/helpers/theme.ts | 4 +- app/lib/methods/helpers/touch.tsx | 3 +- app/lib/methods/userPreferences.ts | 16 +- app/share.tsx | 59 +- app/stacks/InsideStack.tsx | 30 +- .../MasterDetailStack/ModalContainer.tsx | 3 +- app/stacks/MasterDetailStack/index.tsx | 12 +- app/stacks/OutsideStack.tsx | 3 +- app/views/AttachmentView.tsx | 4 +- .../Dropdown/index.tsx | 3 +- .../CannedResponseItem.stories.storyshot | 2 +- app/views/CreateChannelView.tsx | 3 +- app/views/CreateDiscussionView/index.tsx | 3 +- app/views/DirectoryView/Options.tsx | 9 +- app/views/DirectoryView/index.tsx | 3 +- app/views/DiscussionsView/Item.tsx | 3 +- .../__snapshots__/Item.stories.storyshot | 4 +- app/views/E2EEnterYourPasswordView.tsx | 10 +- app/views/E2ESaveYourPasswordView.tsx | 3 +- app/views/InviteUsersView/index.tsx | 3 +- app/views/LivechatEditView.tsx | 3 +- app/views/LoginView.tsx | 7 +- app/views/ModalBlockView.tsx | 3 +- app/views/NewMessageView.tsx | 3 +- app/views/NewServerView/ServerInput/index.tsx | 3 +- app/views/NewServerView/index.tsx | 21 +- app/views/ProfileView/index.tsx | 9 +- app/views/ReadReceiptView/index.tsx | 6 +- app/views/RegisterView.tsx | 19 +- app/views/RoomActionsView/index.tsx | 3 +- app/views/RoomInfoEditView/index.tsx | 33 +- app/views/RoomInfoView/index.tsx | 3 +- app/views/RoomView/Banner.tsx | 6 +- app/views/RoomView/JoinCode.tsx | 3 +- app/views/RoomView/List/List.tsx | 7 +- app/views/RoomView/List/index.tsx | 8 +- .../__snapshots__/LoadMore.stories.storyshot | 8 +- app/views/RoomView/ReactionPicker.tsx | 6 +- app/views/RoomView/UploadProgress.tsx | 6 +- app/views/RoomView/index.tsx | 12 +- app/views/RoomsListView/Header/Header.tsx | 3 +- app/views/RoomsListView/ServerDropdown.tsx | 3 +- app/views/ScreenLockedView.tsx | 3 +- app/views/ShareListView/Header/Header.ios.tsx | 3 +- app/views/ShareListView/Header/SearchBox.tsx | 3 +- app/views/ShareListView/index.tsx | 3 +- app/views/ShareView/Preview.tsx | 7 +- app/views/ShareView/Thumbs.tsx | 3 +- app/views/ShareView/index.tsx | 3 +- app/views/SidebarView/SidebarItem.tsx | 3 +- app/views/SidebarView/index.tsx | 6 +- .../ThreadMessagesView/Dropdown/index.tsx | 3 +- app/views/ThreadMessagesView/Item.tsx | 3 +- .../__snapshots__/Item.stories.storyshot | 6 +- e2e/data.js | 2 +- e2e/data/data.cloud.js | 4 +- e2e/data/data.docker.js | 2 +- e2e/tests/assorted/02-broadcast.spec.js | 3 + e2e/tests/assorted/07-changeserver.spec.js | 5 +- e2e/tests/assorted/13-display-pref.spec.js | 4 +- e2e/tests/onboarding/04-createuser.spec.js | 4 +- e2e/tests/room/04-discussion.spec.js | 3 + e2e/tests/room/06-createdmgroup.spec.js | 3 + ios/Podfile | 38 +- ios/Podfile.lock | 1066 ++-- ios/RocketChatRN.xcodeproj/project.pbxproj | 491 +- .../xcschemes/RocketChatRN.xcscheme | 2 +- ios/RocketChatRN/AppDelegate.h | 6 +- ios/RocketChatRN/AppDelegate.m | 125 - ios/RocketChatRN/AppDelegate.mm | 142 + ios/ShareRocketChatRN/ShareRocketChatRN.m | 23 +- jest.setup.js | 12 - metro.config.js | 3 +- package.json | 86 +- ...po-av+9.2.3.patch => expo-av+11.2.3.patch} | 2 +- .../react-native-mmkv-storage+0.6.12.patch | 44 - patches/react-native-mmkv-storage+0.7.6.patch | 114 + patches/react-native-reanimated+2.2.2.patch | 3385 ------------ storybook/stories/Message.js | 3 +- storybook/stories/ServerItem.js | 3 +- storybook/stories/UiKitMessage.js | 3 +- storybook/stories/UiKitModal.js | 3 +- storybook/stories/UnreadBadge.js | 3 +- .../stories/__snapshots__/Avatar.storyshot | 2 +- .../stories/__snapshots__/List.storyshot | 2 +- .../stories/__snapshots__/Markdown.storyshot | 24 +- .../stories/__snapshots__/Message.storyshot | 80 +- .../__snapshots__/NewMarkdown.storyshot | 22 +- storybook/stories/index.js | 2 +- yarn.lock | 4597 +++++++++++------ 198 files changed, 5610 insertions(+), 6634 deletions(-) create mode 100644 .bundle/config create mode 100644 .ruby-version create mode 100644 Gemfile delete mode 100644 __mocks__/expo-av.js delete mode 100644 __mocks__/expo-keep-awake.js delete mode 100644 __mocks__/expo-web-browser.js create mode 100644 __mocks__/react-native-mmkv-storage.js delete mode 100644 __mocks__/rn-user-defaults.js delete mode 100644 android/app/src/main/java/chat/rocket/reactnative/generated/BasePackageList.java create mode 100644 android/app/src/main/java/chat/rocket/reactnative/newarchitecture/MainApplicationReactNativeHost.java create mode 100644 android/app/src/main/java/chat/rocket/reactnative/newarchitecture/components/MainComponentsRegistry.java create mode 100644 android/app/src/main/java/chat/rocket/reactnative/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java create mode 100644 android/app/src/main/jni/Android.mk create mode 100644 android/app/src/main/jni/MainApplicationModuleProvider.cpp create mode 100644 android/app/src/main/jni/MainApplicationModuleProvider.h create mode 100644 android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp create mode 100644 android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h create mode 100644 android/app/src/main/jni/MainComponentsRegistry.cpp create mode 100644 android/app/src/main/jni/MainComponentsRegistry.h create mode 100644 android/app/src/main/jni/OnLoad.cpp create mode 100644 android/app/src/main/res/drawable/rn_edit_text_material.xml delete mode 100644 ios/RocketChatRN/AppDelegate.m create mode 100644 ios/RocketChatRN/AppDelegate.mm rename patches/{expo-av+9.2.3.patch => expo-av+11.2.3.patch} (98%) delete mode 100644 patches/react-native-mmkv-storage+0.6.12.patch create mode 100644 patches/react-native-mmkv-storage+0.7.6.patch delete mode 100644 patches/react-native-reanimated+2.2.2.patch diff --git a/.bundle/config b/.bundle/config new file mode 100644 index 000000000..d137d242e --- /dev/null +++ b/.bundle/config @@ -0,0 +1,2 @@ +BUNDLE_PATH: "vendor/bundle" +BUNDLE_FORCE_RUBY_PLATFORM: 1 \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml index 1be1e3871..0de081c36 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -80,18 +80,15 @@ commands: steps: - restore_cache: name: Restore pods - # TODO: CircleCI isn't caching Podfile.lock correctly, because checksum changes after install - # key: pods-v1-{{ checksum "ios/Podfile.lock" }} - key: pods-v1-{{ checksum "ios/Podfile" }} + key: pods-v1-{{ checksum "ios/Podfile.lock" }} - run: name: Install pods libs command: | - bundle exec pod install + bundle exec pod install --deployment working_directory: ios - save_cache: name: Save pods specs and pods cache - # key: pods-v1-{{ checksum "ios/Podfile.lock" }} - key: pods-v1-{{ checksum "ios/Podfile" }} + key: pods-v1-{{ checksum "ios/Podfile.lock" }} paths: - ~/.pods - ios/Pods @@ -114,7 +111,8 @@ commands: # echo -e "android.enableAapt2=false" >> ./gradle.properties echo -e "android.useAndroidX=true" >> ./gradle.properties echo -e "android.enableJetifier=true" >> ./gradle.properties - echo -e "FLIPPER_VERSION=0.51.0" >> ./gradle.properties + echo -e "newArchEnabled=false" >> ./gradle.properties + echo -e "FLIPPER_VERSION=0.125.0" >> ./gradle.properties echo -e "VERSIONCODE=$CIRCLE_BUILD_NUM" >> ./gradle.properties if [[ $CIRCLE_JOB == "android-build-official" ]]; then diff --git a/.gitignore b/.gitignore index efb752943..19f2bcab6 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ DerivedData project.xcworkspace *.mobileprovision ios/Pods/ +/vendor/bundle/ # Android/IntelliJ # diff --git a/.prettierrc.js b/.prettierrc.js index 7321592a1..04f6b3a09 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,10 +1,10 @@ module.exports = { bracketSpacing: true, - jsxBracketSameLine: true, singleQuote: true, jsxSingleQuote: true, trailingComma: 'none', printWidth: 130, useTabs: true, - arrowParens: 'avoid' + arrowParens: 'avoid', + bracketSameLine: true }; diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 000000000..a4dd9dba4 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.7.4 diff --git a/Gemfile b/Gemfile new file mode 100644 index 000000000..bbba14aff --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' +# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version +ruby '2.7.4' +gem 'cocoapods', '~> 1.11', '>= 1.11.2' \ No newline at end of file diff --git a/README.md b/README.md index 50e97ea19..0a5f6d766 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![CodeFactor](https://www.codefactor.io/repository/github/rocketchat/rocket.chat.reactnative/badge)](https://www.codefactor.io/repository/github/rocketchat/rocket.chat.reactnative) - **Supported server versions:** 0.70.0+ -- **Supported iOS versions**: 11+ +- **Supported iOS versions**: 12+ - **Supported Android versions**: 6.0+ ## Download diff --git a/__mocks__/expo-av.js b/__mocks__/expo-av.js deleted file mode 100644 index e20feac67..000000000 --- a/__mocks__/expo-av.js +++ /dev/null @@ -1,14 +0,0 @@ -export class Sound { - loadAsync = () => {}; - - playAsync = () => {}; - - pauseAsync = () => {}; - - stopAsync = () => {}; - - setOnPlaybackStatusUpdate = () => {}; - - setPositionAsync = () => {}; -} -export const Audio = { Sound }; diff --git a/__mocks__/expo-keep-awake.js b/__mocks__/expo-keep-awake.js deleted file mode 100644 index 4df99a606..000000000 --- a/__mocks__/expo-keep-awake.js +++ /dev/null @@ -1,4 +0,0 @@ -const activateKeepAwake = () => ''; -const deactivateKeepAwake = () => ''; - -export { activateKeepAwake, deactivateKeepAwake }; diff --git a/__mocks__/expo-web-browser.js b/__mocks__/expo-web-browser.js deleted file mode 100644 index 3e5611b84..000000000 --- a/__mocks__/expo-web-browser.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - openBrowserAsync: () => '' -}; diff --git a/__mocks__/react-native-mmkv-storage.js b/__mocks__/react-native-mmkv-storage.js new file mode 100644 index 000000000..e34fed189 --- /dev/null +++ b/__mocks__/react-native-mmkv-storage.js @@ -0,0 +1,17 @@ +export class MMKVLoader { + constructor() { + console.log('MMKVLoader constructor mock'); + } + + setProcessingMode = jest.fn().mockImplementation(() => ({ + withEncryption: jest.fn().mockImplementation(() => ({ + initialize: jest.fn().mockImplementation(() => {}) + })) + })); +} + +export const ProcessingModes = { + MULTI_PROCESS: '' +}; + +export const create = jest.fn(); diff --git a/__mocks__/rn-user-defaults.js b/__mocks__/rn-user-defaults.js deleted file mode 100644 index 71f26ca30..000000000 --- a/__mocks__/rn-user-defaults.js +++ /dev/null @@ -1,4 +0,0 @@ -export default { - set: () => '', - get: () => '' -}; diff --git a/android/app/build.gradle b/android/app/build.gradle index caff3ff8b..68e818f42 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -93,7 +93,6 @@ project.ext.react = [ ] apply from: "../../node_modules/react-native/react.gradle" -apply from: '../../node_modules/react-native-unimodules/gradle.groovy' /** * Set this to true to create two separate APKs instead of one: @@ -131,13 +130,17 @@ def jscFlavor = 'org.webkit:android-jsc:+' */ def enableHermes = project.ext.react.get("enableHermes", false); -android { - compileSdkVersion rootProject.ext.compileSdkVersion +/** + * Architectures to build native code for. + */ +def reactNativeArchitectures() { + def value = project.getProperties().get("reactNativeArchitectures") + return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] +} - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } +android { + ndkVersion rootProject.ext.ndkVersion + compileSdkVersion rootProject.ext.compileSdkVersion defaultConfig { applicationId APPLICATION_ID @@ -153,6 +156,76 @@ android { resValue "string", "rn_config_reader_custom_package", "chat.rocket.reactnative" testBuildType System.getProperty('testBuildType', 'debug') testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + + buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() + if (isNewArchitectureEnabled()) { + // We configure the NDK build only if you decide to opt-in for the New Architecture. + externalNativeBuild { + ndkBuild { + arguments "APP_PLATFORM=android-21", + "APP_STL=c++_shared", + "NDK_TOOLCHAIN_VERSION=clang", + "GENERATED_SRC_DIR=$buildDir/generated/source", + "PROJECT_BUILD_DIR=$buildDir", + "REACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid", + "REACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build" + cFlags "-Wall", "-Werror", "-fexceptions", "-frtti", "-DWITH_INSPECTOR=1" + cppFlags "-std=c++17" + // Make sure this target name is the same you specify inside the + // src/main/jni/Android.mk file for the `LOCAL_MODULE` variable. + targets "rndiffapp_appmodules" + // Fix for windows limit on number of character in file paths and in command lines + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + arguments "NDK_APP_SHORT_COMMANDS=true" + } + } + } + if (!enableSeparateBuildPerCPUArchitecture) { + ndk { + abiFilters (*reactNativeArchitectures()) + } + } + } + } + + if (isNewArchitectureEnabled()) { + // We configure the NDK build only if you decide to opt-in for the New Architecture. + externalNativeBuild { + ndkBuild { + path "$projectDir/src/main/jni/Android.mk" + } + } + def reactAndroidProjectDir = project(':ReactAndroid').projectDir + def packageReactNdkDebugLibs = tasks.register("packageReactNdkDebugLibs", Copy) { + dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck") + from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") + into("$buildDir/react-ndk/exported") + } + def packageReactNdkReleaseLibs = tasks.register("packageReactNdkReleaseLibs", Copy) { + dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck") + from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") + into("$buildDir/react-ndk/exported") + } + afterEvaluate { + // If you wish to add a custom TurboModule or component locally, + // you should uncomment this line. + // preBuild.dependsOn("generateCodegenArtifactsFromSchema") + preDebugBuild.dependsOn(packageReactNdkDebugLibs) + preReleaseBuild.dependsOn(packageReactNdkReleaseLibs) + // Due to a bug inside AGP, we have to explicitly set a dependency + // between configureNdkBuild* tasks and the preBuild tasks. + // This can be removed once this is solved: https://issuetracker.google.com/issues/207403732 + configureNdkBuildRelease.dependsOn(preReleaseBuild) + configureNdkBuildDebug.dependsOn(preDebugBuild) + reactNativeArchitectures().each { architecture -> + tasks.findByName("configureNdkBuildDebug[${architecture}]")?.configure { + dependsOn("preDebugBuild") + } + tasks.findByName("configureNdkBuildRelease[${architecture}]")?.configure { + dependsOn("preReleaseBuild") + } + } + } } signingConfigs { @@ -170,7 +243,7 @@ android { reset() enable enableSeparateBuildPerCPUArchitecture universalApk false // If true, also generate a universal APK - include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" + include (*reactNativeArchitectures()) } } buildTypes { @@ -181,6 +254,8 @@ android { if (!isFoss) { firebaseCrashlytics { nativeSymbolUploadEnabled true + strippedNativeLibsDir 'build/intermediates/stripped_native_libs/release/out/lib' + unstrippedNativeLibsDir 'build/intermediates/merged_native_libs/release/out/lib' } } } @@ -275,7 +350,6 @@ android { } dependencies { - addUnimodulesDependencies() implementation project(':@react-native-community_viewpager') playImplementation project(':react-native-notifications') playImplementation 'com.google.firebase:firebase-core:16.0.0' @@ -286,14 +360,16 @@ dependencies { exclude group: 'com.facebook.react',module:'react-native-svg' } implementation fileTree(dir: "libs", include: ["*.jar"]) + //noinspection GradleDynamicVersion implementation "com.facebook.react:react-native:+" // From node_modules implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { - exclude group:'com.facebook.fbjni' + exclude group:'com.facebook.fbjni' } debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { exclude group:'com.facebook.flipper' + exclude group:'com.squareup.okhttp3', module:'okhttp' } debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { exclude group:'com.facebook.flipper' @@ -307,20 +383,30 @@ dependencies { implementation jscFlavor } - implementation "com.google.code.gson:gson:2.8.5" + implementation "com.google.code.gson:gson:2.8.9" implementation "com.github.bumptech.glide:glide:4.9.0" annotationProcessor "com.github.bumptech.glide:compiler:4.9.0" - implementation "com.tencent:mmkv-static:1.2.1" - implementation 'com.squareup.okhttp3:okhttp:4.9.0' - implementation "com.squareup.okhttp3:okhttp-urlconnection:4.9.0" + implementation "com.tencent:mmkv-static:1.2.10" androidTestImplementation('com.wix:detox:+') { transitive = true } androidTestImplementation 'junit:junit:4.12' } +if (isNewArchitectureEnabled()) { + // If new architecture is enabled, we let you build RN from source + // Otherwise we fallback to a prebuilt .aar bundled in the NPM package. + // This will be applied to all the imported transtitive dependency. + configurations.all { + resolutionStrategy.dependencySubstitution { + substitute(module("com.facebook.react:react-native")) + .using(project(":ReactAndroid")).because("On New Architecture we're building React Native from source") + } + } +} + // Run this once to be able to run the application with BUCK // puts all compile dependencies into folder libs for BUCK to use task copyDownloadableDepsToLibs(type: Copy) { - from configurations.compile + from configurations.implementation into 'libs' } @@ -328,3 +414,11 @@ apply from: file("../../node_modules/@react-native-community/cli-platform-androi if (!isFoss) { apply plugin: 'com.google.gms.google-services' } + +def isNewArchitectureEnabled() { + // To opt-in for the New Architecture, you can either: + // - Set `newArchEnabled` to true inside the `gradle.properties` file + // - Invoke gradle with `-newArchEnabled=true` + // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" +} \ No newline at end of file diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index efbd3e20b..febfaf65f 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -8,7 +8,7 @@ tools:replace="android:name" tools:targetApi="28" android:networkSecurityConfig="@xml/network_security_config"> - + diff --git a/android/app/src/debug/java/chat/rocket/reactnative/ReactNativeFlipper.java b/android/app/src/debug/java/chat/rocket/reactnative/ReactNativeFlipper.java index 6ed1a4798..6d30e2d25 100644 --- a/android/app/src/debug/java/chat/rocket/reactnative/ReactNativeFlipper.java +++ b/android/app/src/debug/java/chat/rocket/reactnative/ReactNativeFlipper.java @@ -18,6 +18,7 @@ import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; import com.facebook.flipper.plugins.react.ReactFlipperPlugin; import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; +import com.facebook.react.ReactInstanceEventListener; import com.facebook.react.ReactInstanceManager; import com.facebook.react.bridge.ReactContext; import com.facebook.react.modules.network.NetworkingModule; @@ -47,7 +48,7 @@ public class ReactNativeFlipper { ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); if (reactContext == null) { reactInstanceManager.addReactInstanceEventListener( - new ReactInstanceManager.ReactInstanceEventListener() { + new ReactInstanceEventListener() { @Override public void onReactContextInitialized(ReactContext reactContext) { reactInstanceManager.removeReactInstanceEventListener(this); diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b5b6ec2c1..3ced55f71 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -17,7 +17,8 @@ tools:replace="android:allowBackup"> + android:theme="@style/BootTheme" + android:exported="true"> @@ -25,7 +26,7 @@ + android:theme="@style/AppTheme" + android:exported="true"> diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java index e67d24c91..fa292ee99 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java +++ b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java @@ -1,33 +1,18 @@ package chat.rocket.reactnative; import android.os.Bundle; -import android.content.Context; import android.content.Intent; import android.content.res.Configuration; -import android.content.SharedPreferences; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableMap; import com.facebook.react.ReactRootView; import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.ReactFragmentActivity; +import com.facebook.react.ReactActivity; + +import expo.modules.ReactActivityDelegateWrapper; import com.zoontek.rnbootsplash.RNBootSplash; -import com.google.gson.Gson; -class ThemePreferences { - String currentTheme; - String darkLevel; -} - -class SortPreferences { - String sortBy; - Boolean groupByType; - Boolean showFavorites; - Boolean showUnread; -} - -public class MainActivity extends ReactFragmentActivity { +public class MainActivity extends ReactActivity { @Override protected void onCreate(Bundle savedInstanceState) { @@ -58,5 +43,27 @@ public class MainActivity extends ReactFragmentActivity { intent.putExtra("newConfig", newConfig); this.sendBroadcast(intent); } -} + /** + * Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and + * you can specify the rendered you wish to use (Fabric or the older renderer). + */ + @Override + protected ReactActivityDelegate createReactActivityDelegate() { + return new ReactActivityDelegateWrapper(this, new MainActivityDelegate(this, getMainComponentName())); + } + + public static class MainActivityDelegate extends ReactActivityDelegate { + public MainActivityDelegate(ReactActivity activity, String mainComponentName) { + super(activity, mainComponentName); + } + + @Override + protected ReactRootView createRootView() { + ReactRootView reactRootView = new ReactRootView(getContext()); + // If you opted-in for the New Architecture, we enable the Fabric Renderer. + reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED); + return reactRootView; + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java index c0361e78d..871e6e86d 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java +++ b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java @@ -8,24 +8,24 @@ import com.facebook.react.PackageList; import com.facebook.react.ReactApplication; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; +import com.facebook.react.config.ReactFeatureFlags; import com.facebook.soloader.SoLoader; import com.reactnativecommunity.viewpager.RNCViewPagerPackage; import com.facebook.react.bridge.JSIModulePackage; import com.swmansion.reanimated.ReanimatedJSIModulePackage; -import org.unimodules.adapters.react.ModuleRegistryAdapter; -import org.unimodules.adapters.react.ReactModuleRegistryProvider; +import android.content.res.Configuration; +import expo.modules.ApplicationLifecycleDispatcher; +import expo.modules.ReactNativeHostWrapper; import java.util.Arrays; import java.util.List; -import chat.rocket.reactnative.generated.BasePackageList; +import chat.rocket.reactnative.newarchitecture.MainApplicationReactNativeHost; import chat.rocket.reactnative.networking.SSLPinningPackage; public class MainApplication extends Application implements ReactApplication { - private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(new BasePackageList().getPackageList(), null); - - private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { + private final ReactNativeHost mReactNativeHost = new ReactNativeHostWrapper(this, new ReactNativeHost(this) { @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; @@ -37,10 +37,6 @@ public class MainApplication extends Application implements ReactApplication { List packages = new PackageList(this).getPackages(); packages.add(new RNCViewPagerPackage()); packages.add(new SSLPinningPackage()); - List unimodules = Arrays.asList( - new ModuleRegistryAdapter(mModuleRegistryProvider) - ); - packages.addAll(unimodules); List additionalModules = new AdditionalModules().getAdditionalModules(MainApplication.this); packages.addAll(additionalModules); return packages; @@ -60,16 +56,32 @@ public class MainApplication extends Application implements ReactApplication { protected @Nullable String getBundleAssetName() { return "app.bundle"; } - }; + }); + + private final ReactNativeHost mNewArchitectureNativeHost = + new ReactNativeHostWrapper(this, new MainApplicationReactNativeHost(this)); @Override public ReactNativeHost getReactNativeHost() { - return mReactNativeHost; + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + return mNewArchitectureNativeHost; + } else { + return mReactNativeHost; + } } @Override public void onCreate() { super.onCreate(); + // If you opted-in for the New Architecture, we enable the TurboModule system + ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; SoLoader.init(this, /* native exopackage */ false); + ApplicationLifecycleDispatcher.onApplicationCreate(this); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig); } } diff --git a/android/app/src/main/java/chat/rocket/reactnative/generated/BasePackageList.java b/android/app/src/main/java/chat/rocket/reactnative/generated/BasePackageList.java deleted file mode 100644 index 8143f5969..000000000 --- a/android/app/src/main/java/chat/rocket/reactnative/generated/BasePackageList.java +++ /dev/null @@ -1,21 +0,0 @@ -package chat.rocket.reactnative.generated; - -import java.util.Arrays; -import java.util.List; -import org.unimodules.core.interfaces.Package; - -public class BasePackageList { - public List getPackageList() { - return Arrays.asList( - new expo.modules.av.AVPackage(), - new expo.modules.constants.ConstantsPackage(), - new expo.modules.filesystem.FileSystemPackage(), - new expo.modules.haptics.HapticsPackage(), - new expo.modules.imageloader.ImageLoaderPackage(), - new expo.modules.keepawake.KeepAwakePackage(), - new expo.modules.localauthentication.LocalAuthenticationPackage(), - new expo.modules.videothumbnails.VideoThumbnailsPackage(), - new expo.modules.webbrowser.WebBrowserPackage() - ); - } -} diff --git a/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/MainApplicationReactNativeHost.java b/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/MainApplicationReactNativeHost.java new file mode 100644 index 000000000..cf5e9c1ad --- /dev/null +++ b/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/MainApplicationReactNativeHost.java @@ -0,0 +1,116 @@ +package chat.rocket.reactnative.newarchitecture; + +import android.app.Application; +import androidx.annotation.NonNull; +import com.facebook.react.PackageList; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactPackage; +import com.facebook.react.ReactPackageTurboModuleManagerDelegate; +import com.facebook.react.bridge.JSIModulePackage; +import com.facebook.react.bridge.JSIModuleProvider; +import com.facebook.react.bridge.JSIModuleSpec; +import com.facebook.react.bridge.JSIModuleType; +import com.facebook.react.bridge.JavaScriptContextHolder; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.UIManager; +import com.facebook.react.fabric.ComponentFactory; +import com.facebook.react.fabric.CoreComponentsRegistry; +import com.facebook.react.fabric.EmptyReactNativeConfig; +import com.facebook.react.fabric.FabricJSIModuleProvider; +import com.facebook.react.uimanager.ViewManagerRegistry; +import chat.rocket.reactnative.BuildConfig; +import chat.rocket.reactnative.newarchitecture.components.MainComponentsRegistry; +import chat.rocket.reactnative.newarchitecture.modules.MainApplicationTurboModuleManagerDelegate; +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link ReactNativeHost} that helps you load everything needed for the New Architecture, both + * TurboModule delegates and the Fabric Renderer. + * + *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the + * `newArchEnabled` property). Is ignored otherwise. + */ +public class MainApplicationReactNativeHost extends ReactNativeHost { + public MainApplicationReactNativeHost(Application application) { + super(application); + } + + @Override + public boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + List packages = new PackageList(this).getPackages(); + // Packages that cannot be autolinked yet can be added manually here, for example: + // packages.add(new MyReactNativePackage()); + // TurboModules must also be loaded here providing a valid TurboReactPackage implementation: + // packages.add(new TurboReactPackage() { ... }); + // If you have custom Fabric Components, their ViewManagers should also be loaded here + // inside a ReactPackage. + return packages; + } + + @Override + protected String getJSMainModuleName() { + return "index"; + } + + @NonNull + @Override + protected ReactPackageTurboModuleManagerDelegate.Builder + getReactPackageTurboModuleManagerDelegateBuilder() { + // Here we provide the ReactPackageTurboModuleManagerDelegate Builder. This is necessary + // for the new architecture and to use TurboModules correctly. + return new MainApplicationTurboModuleManagerDelegate.Builder(); + } + + @Override + protected JSIModulePackage getJSIModulePackage() { + return new JSIModulePackage() { + @Override + public List getJSIModules( + final ReactApplicationContext reactApplicationContext, + final JavaScriptContextHolder jsContext) { + final List specs = new ArrayList<>(); + + // Here we provide a new JSIModuleSpec that will be responsible of providing the + // custom Fabric Components. + specs.add( + new JSIModuleSpec() { + @Override + public JSIModuleType getJSIModuleType() { + return JSIModuleType.UIManager; + } + + @Override + public JSIModuleProvider getJSIModuleProvider() { + final ComponentFactory componentFactory = new ComponentFactory(); + CoreComponentsRegistry.register(componentFactory); + + // Here we register a Components Registry. + // The one that is generated with the template contains no components + // and just provides you the one from React Native core. + MainComponentsRegistry.register(componentFactory); + + final ReactInstanceManager reactInstanceManager = getReactInstanceManager(); + + ViewManagerRegistry viewManagerRegistry = + new ViewManagerRegistry( + reactInstanceManager.getOrCreateViewManagers(reactApplicationContext)); + + return new FabricJSIModuleProvider( + reactApplicationContext, + componentFactory, + new EmptyReactNativeConfig(), + viewManagerRegistry); + } + }); + return specs; + } + }; + } +} \ No newline at end of file diff --git a/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/components/MainComponentsRegistry.java b/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/components/MainComponentsRegistry.java new file mode 100644 index 000000000..6519aa669 --- /dev/null +++ b/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/components/MainComponentsRegistry.java @@ -0,0 +1,36 @@ +package chat.rocket.reactnative.newarchitecture.components; + +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.fabric.ComponentFactory; +import com.facebook.soloader.SoLoader; + +/** + * Class responsible to load the custom Fabric Components. This class has native methods and needs a + * corresponding C++ implementation/header file to work correctly (already placed inside the jni/ + * folder for you). + * + *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the + * `newArchEnabled` property). Is ignored otherwise. + */ +@DoNotStrip +public class MainComponentsRegistry { + static { + SoLoader.loadLibrary("fabricjni"); + } + + @DoNotStrip private final HybridData mHybridData; + + @DoNotStrip + private native HybridData initHybrid(ComponentFactory componentFactory); + + @DoNotStrip + private MainComponentsRegistry(ComponentFactory componentFactory) { + mHybridData = initHybrid(componentFactory); + } + + @DoNotStrip + public static MainComponentsRegistry register(ComponentFactory componentFactory) { + return new MainComponentsRegistry(componentFactory); + } +} \ No newline at end of file diff --git a/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java b/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java new file mode 100644 index 000000000..74a5728f2 --- /dev/null +++ b/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java @@ -0,0 +1,48 @@ +package chat.rocket.reactnative.newarchitecture.modules; + +import com.facebook.jni.HybridData; +import com.facebook.react.ReactPackage; +import com.facebook.react.ReactPackageTurboModuleManagerDelegate; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.soloader.SoLoader; +import java.util.List; + +/** + * Class responsible to load the TurboModules. This class has native methods and needs a + * corresponding C++ implementation/header file to work correctly (already placed inside the jni/ + * folder for you). + * + *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the + * `newArchEnabled` property). Is ignored otherwise. + */ +public class MainApplicationTurboModuleManagerDelegate + extends ReactPackageTurboModuleManagerDelegate { + + private static volatile boolean sIsSoLibraryLoaded; + + protected MainApplicationTurboModuleManagerDelegate( + ReactApplicationContext reactApplicationContext, List packages) { + super(reactApplicationContext, packages); + } + + protected native HybridData initHybrid(); + + native boolean canCreateTurboModule(String moduleName); + + public static class Builder extends ReactPackageTurboModuleManagerDelegate.Builder { + protected MainApplicationTurboModuleManagerDelegate build( + ReactApplicationContext context, List packages) { + return new MainApplicationTurboModuleManagerDelegate(context, packages); + } + } + + @Override + protected synchronized void maybeLoadOtherSoLibraries() { + if (!sIsSoLibraryLoaded) { + // If you change the name of your application .so file in the Android.mk file, + // make sure you update the name here as well. + SoLoader.loadLibrary("rocketchat_appmodules"); + sIsSoLibraryLoaded = true; + } + } +} \ No newline at end of file diff --git a/android/app/src/main/jni/Android.mk b/android/app/src/main/jni/Android.mk new file mode 100644 index 000000000..db632d8b3 --- /dev/null +++ b/android/app/src/main/jni/Android.mk @@ -0,0 +1,49 @@ +THIS_DIR := $(call my-dir) + +include $(REACT_ANDROID_DIR)/Android-prebuilt.mk + +# If you wish to add a custom TurboModule or Fabric component in your app you +# will have to include the following autogenerated makefile. +# include $(GENERATED_SRC_DIR)/codegen/jni/Android.mk +include $(CLEAR_VARS) + +LOCAL_PATH := $(THIS_DIR) + +# You can customize the name of your application .so file here. +LOCAL_MODULE := rocketchat_appmodules + +LOCAL_C_INCLUDES := $(LOCAL_PATH) +LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp) +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) + +# If you wish to add a custom TurboModule or Fabric component in your app you +# will have to uncomment those lines to include the generated source +# files from the codegen (placed in $(GENERATED_SRC_DIR)/codegen/jni) +# +# LOCAL_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni +# LOCAL_SRC_FILES += $(wildcard $(GENERATED_SRC_DIR)/codegen/jni/*.cpp) +# LOCAL_EXPORT_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni + +# Here you should add any native library you wish to depend on. +LOCAL_SHARED_LIBRARIES := \ + libfabricjni \ + libfbjni \ + libfolly_futures \ + libfolly_json \ + libglog \ + libjsi \ + libreact_codegen_rncore \ + libreact_debug \ + libreact_nativemodule_core \ + libreact_render_componentregistry \ + libreact_render_core \ + libreact_render_debug \ + libreact_render_graphics \ + librrc_view \ + libruntimeexecutor \ + libturbomodulejsijni \ + libyoga + +LOCAL_CFLAGS := -DLOG_TAG=\"ReactNative\" -fexceptions -frtti -std=c++17 -Wall + +include $(BUILD_SHARED_LIBRARY) \ No newline at end of file diff --git a/android/app/src/main/jni/MainApplicationModuleProvider.cpp b/android/app/src/main/jni/MainApplicationModuleProvider.cpp new file mode 100644 index 000000000..39de093d1 --- /dev/null +++ b/android/app/src/main/jni/MainApplicationModuleProvider.cpp @@ -0,0 +1,24 @@ +#include "MainApplicationModuleProvider.h" + +#include + +namespace facebook { +namespace react { + +std::shared_ptr MainApplicationModuleProvider( + const std::string moduleName, + const JavaTurboModule::InitParams ¶ms) { + // Here you can provide your own module provider for TurboModules coming from + // either your application or from external libraries. The approach to follow + // is similar to the following (for a library called `samplelibrary`: + // + // auto module = samplelibrary_ModuleProvider(moduleName, params); + // if (module != nullptr) { + // return module; + // } + // return rncore_ModuleProvider(moduleName, params); + return rncore_ModuleProvider(moduleName, params); +} + +} // namespace react +} // namespace facebook \ No newline at end of file diff --git a/android/app/src/main/jni/MainApplicationModuleProvider.h b/android/app/src/main/jni/MainApplicationModuleProvider.h new file mode 100644 index 000000000..2f2fb24a3 --- /dev/null +++ b/android/app/src/main/jni/MainApplicationModuleProvider.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#include + +namespace facebook { +namespace react { + +std::shared_ptr MainApplicationModuleProvider( + const std::string moduleName, + const JavaTurboModule::InitParams ¶ms); + +} // namespace react +} // namespace facebook \ No newline at end of file diff --git a/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp b/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp new file mode 100644 index 000000000..f2e4714dc --- /dev/null +++ b/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp @@ -0,0 +1,45 @@ +#include "MainApplicationTurboModuleManagerDelegate.h" +#include "MainApplicationModuleProvider.h" + +namespace facebook { +namespace react { + +jni::local_ref +MainApplicationTurboModuleManagerDelegate::initHybrid( + jni::alias_ref) { + return makeCxxInstance(); +} + +void MainApplicationTurboModuleManagerDelegate::registerNatives() { + registerHybrid({ + makeNativeMethod( + "initHybrid", MainApplicationTurboModuleManagerDelegate::initHybrid), + makeNativeMethod( + "canCreateTurboModule", + MainApplicationTurboModuleManagerDelegate::canCreateTurboModule), + }); +} + +std::shared_ptr +MainApplicationTurboModuleManagerDelegate::getTurboModule( + const std::string name, + const std::shared_ptr jsInvoker) { + // Not implemented yet: provide pure-C++ NativeModules here. + return nullptr; +} + +std::shared_ptr +MainApplicationTurboModuleManagerDelegate::getTurboModule( + const std::string name, + const JavaTurboModule::InitParams ¶ms) { + return MainApplicationModuleProvider(name, params); +} + +bool MainApplicationTurboModuleManagerDelegate::canCreateTurboModule( + std::string name) { + return getTurboModule(name, nullptr) != nullptr || + getTurboModule(name, {.moduleName = name}) != nullptr; +} + +} // namespace react +} // namespace facebook \ No newline at end of file diff --git a/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h b/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h new file mode 100644 index 000000000..f2401d08f --- /dev/null +++ b/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h @@ -0,0 +1,38 @@ +#include +#include + +#include +#include + +namespace facebook { +namespace react { + +class MainApplicationTurboModuleManagerDelegate + : public jni::HybridClass< + MainApplicationTurboModuleManagerDelegate, + TurboModuleManagerDelegate> { + public: + // Adapt it to the package you used for your Java class. + static constexpr auto kJavaDescriptor = + "Lchat/rocket/reactnative/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate;"; + + static jni::local_ref initHybrid(jni::alias_ref); + + static void registerNatives(); + + std::shared_ptr getTurboModule( + const std::string name, + const std::shared_ptr jsInvoker) override; + std::shared_ptr getTurboModule( + const std::string name, + const JavaTurboModule::InitParams ¶ms) override; + + /** + * Test-only method. Allows user to verify whether a TurboModule can be + * created by instances of this class. + */ + bool canCreateTurboModule(std::string name); +}; + +} // namespace react +} // namespace facebook \ No newline at end of file diff --git a/android/app/src/main/jni/MainComponentsRegistry.cpp b/android/app/src/main/jni/MainComponentsRegistry.cpp new file mode 100644 index 000000000..c5188f4dc --- /dev/null +++ b/android/app/src/main/jni/MainComponentsRegistry.cpp @@ -0,0 +1,61 @@ +#include "MainComponentsRegistry.h" + +#include +#include +#include +#include + +namespace facebook { +namespace react { + +MainComponentsRegistry::MainComponentsRegistry(ComponentFactory *delegate) {} + +std::shared_ptr +MainComponentsRegistry::sharedProviderRegistry() { + auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry(); + + // Custom Fabric Components go here. You can register custom + // components coming from your App or from 3rd party libraries here. + // + // providerRegistry->add(concreteComponentDescriptorProvider< + // AocViewerComponentDescriptor>()); + return providerRegistry; +} + +jni::local_ref +MainComponentsRegistry::initHybrid( + jni::alias_ref, + ComponentFactory *delegate) { + auto instance = makeCxxInstance(delegate); + + auto buildRegistryFunction = + [](EventDispatcher::Weak const &eventDispatcher, + ContextContainer::Shared const &contextContainer) + -> ComponentDescriptorRegistry::Shared { + auto registry = MainComponentsRegistry::sharedProviderRegistry() + ->createComponentDescriptorRegistry( + {eventDispatcher, contextContainer}); + + auto mutableRegistry = + std::const_pointer_cast(registry); + + mutableRegistry->setFallbackComponentDescriptor( + std::make_shared( + ComponentDescriptorParameters{ + eventDispatcher, contextContainer, nullptr})); + + return registry; + }; + + delegate->buildRegistryFunction = buildRegistryFunction; + return instance; +} + +void MainComponentsRegistry::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", MainComponentsRegistry::initHybrid), + }); +} + +} // namespace react +} // namespace facebook \ No newline at end of file diff --git a/android/app/src/main/jni/MainComponentsRegistry.h b/android/app/src/main/jni/MainComponentsRegistry.h new file mode 100644 index 000000000..8e5cd1f1f --- /dev/null +++ b/android/app/src/main/jni/MainComponentsRegistry.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +namespace facebook { +namespace react { + +class MainComponentsRegistry + : public facebook::jni::HybridClass { + public: + // Adapt it to the package you used for your Java class. + constexpr static auto kJavaDescriptor = + "Lchat/rocket/reactnative/newarchitecture/components/MainComponentsRegistry;"; + + static void registerNatives(); + + MainComponentsRegistry(ComponentFactory *delegate); + + private: + static std::shared_ptr + sharedProviderRegistry(); + + static jni::local_ref initHybrid( + jni::alias_ref, + ComponentFactory *delegate); +}; + +} // namespace react +} // namespace facebook \ No newline at end of file diff --git a/android/app/src/main/jni/OnLoad.cpp b/android/app/src/main/jni/OnLoad.cpp new file mode 100644 index 000000000..ae1ef007d --- /dev/null +++ b/android/app/src/main/jni/OnLoad.cpp @@ -0,0 +1,11 @@ +#include +#include "MainApplicationTurboModuleManagerDelegate.h" +#include "MainComponentsRegistry.h" + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { + return facebook::jni::initialize(vm, [] { + facebook::react::MainApplicationTurboModuleManagerDelegate:: + registerNatives(); + facebook::react::MainComponentsRegistry::registerNatives(); + }); +} \ No newline at end of file diff --git a/android/app/src/main/res/drawable/rn_edit_text_material.xml b/android/app/src/main/res/drawable/rn_edit_text_material.xml new file mode 100644 index 000000000..bb6f578c3 --- /dev/null +++ b/android/app/src/main/res/drawable/rn_edit_text_material.xml @@ -0,0 +1,29 @@ + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 22d14143e..1fd46175e 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -4,6 +4,8 @@ @color/splashBackground @color/splashBackground false + + @drawable/rn_edit_text_material