From 3fe24fac7b5632204feb1810cf99f62b0ad45d38 Mon Sep 17 00:00:00 2001 From: Gleidson Daniel Silva Date: Tue, 19 Mar 2024 16:40:48 -0300 Subject: [PATCH 01/10] regression: use correct parameters on upload image on profile view (#5620) --- app/views/ChangeAvatarView/index.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/views/ChangeAvatarView/index.tsx b/app/views/ChangeAvatarView/index.tsx index 1b52c843b..494c8ff21 100644 --- a/app/views/ChangeAvatarView/index.tsx +++ b/app/views/ChangeAvatarView/index.tsx @@ -138,11 +138,13 @@ const ChangeAvatarView = () => { cropperAvoidEmptySpaceAroundImage: false, cropperChooseText: I18n.t('Choose'), cropperCancelText: I18n.t('Cancel'), - includeBase64: true, - useFrontCamera: isCam + includeBase64: true }; try { - const response: Image = isCam === true ? await ImagePicker.openCamera(options) : await ImagePicker.openPicker(options); + const response: Image = + isCam === true + ? await ImagePicker.openCamera({ ...options, useFrontCamera: true }) + : await ImagePicker.openPicker(options); dispatchAvatar({ type: AvatarStateActions.CHANGE_AVATAR, payload: { url: response.path, data: `data:image/jpeg;base64,${response.data}`, service: 'upload' } From 4ca641228c4e4fda6c3115ec45bf8ae62054b0ff Mon Sep 17 00:00:00 2001 From: Gleidson Daniel Silva Date: Tue, 19 Mar 2024 16:46:30 -0300 Subject: [PATCH 02/10] regression: disable notifiee handler on ios devices (#5619) * chore: test ios push * revert notifications * fix: disable notifiee handler on ios devices * chore: fix notifee version * chore: update notifee lib --- ios/Podfile.lock | 8 +++---- package.json | 2 +- ...atch => @notifee+react-native+7.8.2.patch} | 21 +++++++++++++++++++ yarn.lock | 8 +++---- 4 files changed, 30 insertions(+), 9 deletions(-) rename patches/{@notifee+react-native+7.8.0.patch => @notifee+react-native+7.8.2.patch} (50%) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 1ba319c09..d67fe6338 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -534,10 +534,10 @@ PODS: - React-Core - RNMathView (1.0.0): - iosMath - - RNNotifee (7.8.0): + - RNNotifee (7.8.2): - React-Core - - RNNotifee/NotifeeCore (= 7.8.0) - - RNNotifee/NotifeeCore (7.8.0): + - RNNotifee/NotifeeCore (= 7.8.2) + - RNNotifee/NotifeeCore (7.8.2): - React-Core - RNReanimated (2.8.0): - DoubleConversion @@ -980,7 +980,7 @@ SPEC CHECKSUMS: RNImageCropPicker: 97289cd94fb01ab79db4e5c92938be4d0d63415d RNLocalize: 82a569022724d35461e2dc5b5d015a13c3ca995b RNMathView: 4c8a3c081fa671ab3136c51fa0bdca7ffb708bd5 - RNNotifee: f3c01b391dd8e98e67f539f9a35a9cbcd3bae744 + RNNotifee: 8e2d3df3f0e9ce8f5d1fe4c967431138190b6175 RNReanimated: 64573e25e078ae6bec03b891586d50b9ec284393 RNRootView: 895a4813dedeaca82db2fa868ca1c333d790e494 RNScreens: fa9b582d85ae5d62c91c66003b5278458fed7aaa diff --git a/package.json b/package.json index 3efb1d30c..5ce432444 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "@codler/react-native-keyboard-aware-scroll-view": "^2.0.1", "@gorhom/bottom-sheet": "^4.3.1", "@hookform/resolvers": "^2.9.10", - "@notifee/react-native": "^7.8.0", + "@notifee/react-native": "7.8.2", "@nozbe/watermelondb": "0.23.0", "@react-native-async-storage/async-storage": "^1.17.11", "@react-native-camera-roll/camera-roll": "5.6.1", diff --git a/patches/@notifee+react-native+7.8.0.patch b/patches/@notifee+react-native+7.8.2.patch similarity index 50% rename from patches/@notifee+react-native+7.8.0.patch rename to patches/@notifee+react-native+7.8.2.patch index 04bfbe16c..cdb17b785 100644 --- a/patches/@notifee+react-native+7.8.0.patch +++ b/patches/@notifee+react-native+7.8.2.patch @@ -18,3 +18,24 @@ Notifee.REQUEST_CODE_NOTIFICATION_PERMISSION, this); } +diff --git a/node_modules/@notifee/react-native/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m b/node_modules/@notifee/react-native/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m +index cf8020d..3a1e080 100644 +--- a/node_modules/@notifee/react-native/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m ++++ b/node_modules/@notifee/react-native/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m +@@ -179,11 +179,11 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center + + _notificationOpenedAppID = notifeeNotification[@"id"]; + +- // handle notification outside of notifee +- if (notifeeNotification == nil) { +- notifeeNotification = +- [NotifeeCoreUtil parseUNNotificationRequest:response.notification.request]; +- } ++ // disable notifee handler on ios devices ++ // if (notifeeNotification == nil) { ++ // notifeeNotification = ++ // [NotifeeCoreUtil parseUNNotificationRequest:response.notification.request]; ++ // } + + if (notifeeNotification != nil) { + if ([response.actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier]) { diff --git a/yarn.lock b/yarn.lock index 3abd0c30d..4f74d81f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5158,10 +5158,10 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@notifee/react-native@^7.8.0": - version "7.8.0" - resolved "https://registry.yarnpkg.com/@notifee/react-native/-/react-native-7.8.0.tgz#2990883753990f3585aa0cb5becc5cbdbcd87a43" - integrity sha512-sx8h62U4FrR4pqlbN1rkgPsdamDt9Tad0zgfO6VtP6rNJq/78k8nxUnh0xIX3WPDcCV8KAzdYCE7+UNvhF1CpQ== +"@notifee/react-native@7.8.2": + version "7.8.2" + resolved "https://registry.yarnpkg.com/@notifee/react-native/-/react-native-7.8.2.tgz#72d3199ae830b4128ddaef3c1c2f11604759c9c4" + integrity sha512-VG4IkWJIlOKqXwa3aExC3WFCVCGCC9BA55Ivg0SMRfEs+ruvYy/zlLANcrVGiPtgkUEryXDhA8SXx9+JcO8oLA== "@nozbe/simdjson@0.9.6-fix2": version "0.9.6-fix2" From a2975f7c13cecfbc00c276556fecd051d08809f4 Mon Sep 17 00:00:00 2001 From: Sathurshan Date: Wed, 20 Mar 2024 19:27:44 +0530 Subject: [PATCH 03/10] chore: migrate several class-based components to functional components (#5584) --- .../Dropdown/index.tsx | 122 ++++--- app/views/DirectoryView/Options.tsx | 151 +++++---- app/views/DirectoryView/index.tsx | 1 - app/views/RoomsListView/ServerDropdown.tsx | 298 ++++++++---------- app/views/RoomsListView/index.tsx | 6 +- .../ThreadMessagesView/Dropdown/index.tsx | 114 ++++--- app/views/ThreadMessagesView/index.tsx | 8 +- 7 files changed, 320 insertions(+), 380 deletions(-) diff --git a/app/views/CannedResponsesListView/Dropdown/index.tsx b/app/views/CannedResponsesListView/Dropdown/index.tsx index a2f9156d7..9da55f407 100644 --- a/app/views/CannedResponsesListView/Dropdown/index.tsx +++ b/app/views/CannedResponsesListView/Dropdown/index.tsx @@ -1,10 +1,8 @@ -import React from 'react'; +import React, { useEffect, useRef } from 'react'; import { Animated, Easing, FlatList, TouchableWithoutFeedback } from 'react-native'; -import { withSafeAreaInsets } from 'react-native-safe-area-context'; import styles from '../styles'; -import { themes } from '../../../lib/constants'; -import { TSupportedThemes, withTheme } from '../../../theme'; +import { useTheme } from '../../../theme'; import * as List from '../../../containers/List'; import DropdownItemFilter from './DropdownItemFilter'; import DropdownItemHeader from './DropdownItemHeader'; @@ -12,35 +10,31 @@ import { ROW_HEIGHT } from './DropdownItem'; import { ILivechatDepartment } from '../../../definitions/ILivechatDepartment'; const ANIMATION_DURATION = 200; +const HEIGHT_DESTINATION = 0; +const MAX_ROWS = 5; interface IDropdownProps { - theme?: TSupportedThemes; currentDepartment: ILivechatDepartment; onClose: () => void; onDepartmentSelected: (value: ILivechatDepartment) => void; departments: ILivechatDepartment[]; } -class Dropdown extends React.Component { - private animatedValue: Animated.Value; +const Dropdown = ({ currentDepartment, onClose, onDepartmentSelected, departments }: IDropdownProps) => { + const animatedValue = useRef(new Animated.Value(0)).current; + const { colors } = useTheme(); - constructor(props: IDropdownProps) { - super(props); - this.animatedValue = new Animated.Value(0); - } - - componentDidMount() { - Animated.timing(this.animatedValue, { + useEffect(() => { + Animated.timing(animatedValue, { toValue: 1, duration: ANIMATION_DURATION, easing: Easing.inOut(Easing.quad), useNativeDriver: true }).start(); - } + }, [animatedValue]); - close = () => { - const { onClose } = this.props; - Animated.timing(this.animatedValue, { + const close = () => { + Animated.timing(animatedValue, { toValue: 0, duration: ANIMATION_DURATION, easing: Easing.inOut(Easing.quad), @@ -48,58 +42,54 @@ class Dropdown extends React.Component { }).start(() => onClose()); }; - render() { - const { theme, currentDepartment, onDepartmentSelected, departments } = this.props; - const heightDestination = 0; - const translateY = this.animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [-300, heightDestination] // approximated height of the component when closed/open - }); - const backdropOpacity = this.animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [0, themes[theme!].backdropOpacity] - }); + const translateY = animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [-300, HEIGHT_DESTINATION] // approximated height of the component when closed/open + }); - const maxRows = 5; - return ( - <> - - - + const backdropOpacity = animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [0, colors.backdropOpacity] + }); + + return ( + <> + - - - item._id} - renderItem={({ item }) => ( - - )} - keyboardShouldPersistTaps='always' - /> - - - ); - } -} + /> + + + + + item._id} + renderItem={({ item }) => ( + + )} + keyboardShouldPersistTaps='always' + /> + + + ); +}; -export default withTheme(withSafeAreaInsets(Dropdown)); +export default Dropdown; diff --git a/app/views/DirectoryView/Options.tsx b/app/views/DirectoryView/Options.tsx index 43c84fc09..4003dc13f 100644 --- a/app/views/DirectoryView/Options.tsx +++ b/app/views/DirectoryView/Options.tsx @@ -1,13 +1,13 @@ -import React, { PureComponent } from 'react'; +import React, { useEffect, useRef } from 'react'; import { Animated, Easing, Switch, Text, TouchableWithoutFeedback, View } from 'react-native'; import Touch from '../../containers/Touch'; import { CustomIcon, TIconsName } from '../../containers/CustomIcon'; import Check from '../../containers/Check'; import I18n from '../../i18n'; -import { SWITCH_TRACK_COLOR, themes } from '../../lib/constants'; +import { SWITCH_TRACK_COLOR } from '../../lib/constants'; import styles from './styles'; -import { TSupportedThemes } from '../../theme'; +import { useTheme } from '../../theme'; const ANIMATION_DURATION = 200; const ANIMATION_PROPS = { @@ -23,34 +23,34 @@ interface IDirectoryOptionsProps { close: Function; changeType: Function; toggleWorkspace(): void; - theme: TSupportedThemes; } -export default class DirectoryOptions extends PureComponent { - private animatedValue: Animated.Value; +const DirectoryOptions = ({ + type: propType, + globalUsers, + isFederationEnabled, + close: onClose, + changeType, + toggleWorkspace +}: IDirectoryOptionsProps) => { + const animatedValue = useRef(new Animated.Value(0)).current; + const { colors } = useTheme(); - constructor(props: IDirectoryOptionsProps) { - super(props); - this.animatedValue = new Animated.Value(0); - } - - componentDidMount() { - Animated.timing(this.animatedValue, { + useEffect(() => { + Animated.timing(animatedValue, { toValue: 1, ...ANIMATION_PROPS }).start(); - } + }, [animatedValue]); - close = () => { - const { close } = this.props; - Animated.timing(this.animatedValue, { + const close = () => { + Animated.timing(animatedValue, { toValue: 0, ...ANIMATION_PROPS - }).start(() => close()); + }).start(() => onClose()); }; - renderItem = (itemType: string) => { - const { changeType, type: propType, theme } = this.props; + const renderItem = (itemType: string) => { let text = 'Users'; let icon: TIconsName = 'user'; if (itemType === 'channels') { @@ -66,70 +66,61 @@ export default class DirectoryOptions extends PureComponent changeType(itemType)} style={styles.dropdownItemButton} accessibilityLabel={I18n.t(text)}> - - {I18n.t(text)} + + {I18n.t(text)} {propType === itemType ? : null} ); }; - render() { - const translateY = this.animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [-326, 0] - }); - const { globalUsers, toggleWorkspace, isFederationEnabled, theme } = this.props; - const backdropOpacity = this.animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [0, themes[theme].backdropOpacity] - }); - return ( - <> - - - - - - - {I18n.t('Search_by')} - - - - {this.renderItem('channels')} - {this.renderItem('users')} - {this.renderItem('teams')} - {isFederationEnabled ? ( - <> - - - - - {I18n.t('Search_global_users')} - - - {I18n.t('Search_global_users_description')} - - - + const translateY = animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [-326, 0] + }); + + const backdropOpacity = animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [0, colors.backdropOpacity] + }); + + return ( + <> + + + + + + + {I18n.t('Search_by')} + + + + {renderItem('channels')} + {renderItem('users')} + {renderItem('teams')} + {isFederationEnabled ? ( + <> + + + + {I18n.t('Search_global_users')} + + {I18n.t('Search_global_users_description')} + - - ) : null} - - - ); - } -} + + + + ) : null} + + + ); +}; + +export default DirectoryOptions; diff --git a/app/views/DirectoryView/index.tsx b/app/views/DirectoryView/index.tsx index ee5f9a357..131ef2cb4 100644 --- a/app/views/DirectoryView/index.tsx +++ b/app/views/DirectoryView/index.tsx @@ -315,7 +315,6 @@ class DirectoryView extends React.Component {showOptionsDropdown ? ( { - insets?: { - top: number; - }; - closeServerDropdown: boolean; - server: string; - isMasterDetail: boolean; -} +const ServerDropdown = () => { + const animatedValue = useRef(new Animated.Value(0)).current; + const subscription = useRef(); + const newServerTimeout = useRef | false>(); + const isMounted = useRef(false); + const [servers, setServers] = useState([]); + const dispatch = useDispatch(); + const closeServerDropdown = useAppSelector(state => state.rooms.closeServerDropdown); + const server = useAppSelector(state => state.server.server); + const isMasterDetail = useAppSelector(state => state.app.isMasterDetail); + const { colors } = useTheme(); + const insets = useSafeAreaInsets(); -interface IServerDropdownState { - servers: TServerModel[]; -} + useEffect(() => { + if (isMounted.current) close(); + }, [closeServerDropdown]); -class ServerDropdown extends Component { - private animatedValue: Animated.Value; - private subscription?: Subscription; - private newServerTimeout?: ReturnType | false; + useEffect(() => { + isMounted.current = true; - constructor(props: IServerDropdownProps) { - super(props); - this.state = { servers: [] }; - this.animatedValue = new Animated.Value(0); - } + const init = async () => { + const serversDB = database.servers; + const observable = await serversDB.get('servers').query().observeWithColumns(['name']); - async componentDidMount() { - const serversDB = database.servers; - const observable = await serversDB.get('servers').query().observeWithColumns(['name']); + subscription.current = observable.subscribe(data => { + setServers(data); + }); - this.subscription = observable.subscribe(data => { - this.setState({ servers: data }); - }); + Animated.timing(animatedValue, { + toValue: 1, + duration: ANIMATION_DURATION, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true + }).start(); + }; + init(); - Animated.timing(this.animatedValue, { - toValue: 1, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - }).start(); - } + return () => { + if (newServerTimeout.current) { + clearTimeout(newServerTimeout.current); + newServerTimeout.current = false; + } + if (subscription.current && subscription.current.unsubscribe) { + subscription.current.unsubscribe(); + } + }; + }, []); - componentDidUpdate(prevProps: IServerDropdownProps) { - const { closeServerDropdown } = this.props; - if (prevProps.closeServerDropdown !== closeServerDropdown) { - this.close(); - } - } - - componentWillUnmount() { - if (this.newServerTimeout) { - clearTimeout(this.newServerTimeout); - this.newServerTimeout = false; - } - if (this.subscription && this.subscription.unsubscribe) { - this.subscription.unsubscribe(); - } - } - - close = () => { - const { dispatch } = this.props; - Animated.timing(this.animatedValue, { + const close = () => { + Animated.timing(animatedValue, { toValue: 0, duration: ANIMATION_DURATION, easing: Easing.inOut(Easing.quad), @@ -96,7 +87,7 @@ class ServerDropdown extends Component dispatch(toggleServerDropdown())); }; - createWorkspace = async () => { + const createWorkspace = async () => { logEvent(events.RL_CREATE_NEW_WORKSPACE); try { await Linking.openURL('https://cloud.rocket.chat/trial'); @@ -105,52 +96,49 @@ class ServerDropdown extends Component { - const { dispatch } = this.props; + const navToNewServer = (previousServer: string) => { batch(() => { dispatch(appStart({ root: RootEnum.ROOT_OUTSIDE })); dispatch(serverInitAdd(previousServer)); }); }; - addServer = () => { + const addServer = () => { logEvent(events.RL_ADD_SERVER); - const { server } = this.props; - this.close(); + close(); setTimeout(() => { - this.navToNewServer(server); + navToNewServer(server); }, ANIMATION_DURATION); }; - select = async (server: string, version?: string) => { - const { server: currentServer, dispatch, isMasterDetail } = this.props; - this.close(); - if (currentServer !== server) { + const select = async (serverParam: string, version?: string) => { + close(); + if (server !== serverParam) { logEvent(events.RL_CHANGE_SERVER); - const userId = UserPreferences.getString(`${TOKEN_KEY}-${server}`); + const userId = UserPreferences.getString(`${TOKEN_KEY}-${serverParam}`); if (isMasterDetail) { goRoom({ item: {}, isMasterDetail }); } if (!userId) { setTimeout(() => { - this.navToNewServer(currentServer); - this.newServerTimeout = setTimeout(() => { - EventEmitter.emit('NewServer', { server }); + navToNewServer(server); + newServerTimeout.current = setTimeout(() => { + EventEmitter.emit('NewServer', { server: serverParam }); }, ANIMATION_DURATION); }, ANIMATION_DURATION); } else { - await localAuthenticate(server); - dispatch(selectServerRequest(server, version, true, true)); + await localAuthenticate(serverParam); + dispatch(selectServerRequest(serverParam, version, true, true)); } } }; - remove = (server: string) => + const remove = (server: string) => showConfirmationAlert({ message: I18n.t('This_will_remove_all_data_from_this_server'), confirmationText: I18n.t('Delete'), onPress: async () => { - this.close(); + close(); try { await removeServer({ server }); } catch { @@ -159,93 +147,81 @@ class ServerDropdown extends Component { - const { server } = this.props; + const renderItem = ({ item }: { item: { id: string; iconURL: string; name: string; version: string } }) => ( + select(item.id, item.version)} + onLongPress={() => item.id === server || remove(item.id)} + hasCheck={item.id === server} + /> + ); - return ( - this.select(item.id, item.version)} - onLongPress={() => item.id === server || this.remove(item.id)} - hasCheck={item.id === server} - /> - ); - }; + const initialTop = 87 + Math.min(servers.length, MAX_ROWS) * ROW_HEIGHT; + const statusBarHeight = insets?.top ?? 0; + const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0; - render() { - const { servers } = this.state; - const { theme, isMasterDetail, insets } = this.props; - const maxRows = 4; - const initialTop = 87 + Math.min(servers.length, maxRows) * ROW_HEIGHT; - const statusBarHeight = insets?.top ?? 0; - const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0; - const translateY = this.animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [-initialTop, heightDestination] - }); - const backdropOpacity = this.animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [0, themes[theme].backdropOpacity] - }); - return ( - <> - - - + const translateY = animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [-initialTop, heightDestination] + }); + + const backdropOpacity = animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [0, colors.backdropOpacity] + }); + + return ( + <> + - - {I18n.t('Server')} - - {I18n.t('Add_Server')} - - - item.id} - renderItem={this.renderServer} - ItemSeparatorComponent={List.Separator} - keyboardShouldPersistTaps='always' - /> - -