From c9b6ee6c53d25a423365df4622dc80cc5b08a1d3 Mon Sep 17 00:00:00 2001 From: noumantahir Date: Tue, 19 Dec 2023 17:08:55 +0500 Subject: [PATCH 1/5] refactored notification view to functional component --- .../notification/view/notificationView.tsx | 399 +++++++++--------- 1 file changed, 190 insertions(+), 209 deletions(-) diff --git a/src/components/notification/view/notificationView.tsx b/src/components/notification/view/notificationView.tsx index d9ab24a2e4..ae6df550ea 100644 --- a/src/components/notification/view/notificationView.tsx +++ b/src/components/notification/view/notificationView.tsx @@ -1,8 +1,8 @@ /* eslint-disable react/jsx-wrap-multilines */ -import React, { PureComponent } from 'react'; +import React, { PureComponent, useMemo, useRef, useState } from 'react'; import { connect } from 'react-redux'; import { View, ActivityIndicator, Text } from 'react-native'; -import { injectIntl } from 'react-intl'; +import { injectIntl, useIntl } from 'react-intl'; // Constants // Components @@ -19,66 +19,61 @@ import { isToday, isYesterday, isThisWeek, isLastWeek, isThisMonth } from '../.. // Styles import styles from './notificationStyles'; import globalStyles from '../../../globalStyles'; +import { useAppSelector } from '../../../hooks'; + + +const FILTERS = [ + { key: 'activities', value: 'ALL' }, + { key: 'replies', value: 'REPLIES' }, + { key: 'mentions', value: 'MENTIONS' }, +] + + +interface Props { + notifications: any[]; + isLoading: boolean; + isNotificationRefreshing: boolean; + globalProps: any; + handleOnUserPress: () => void; + readAllNotification: () => void; + getActivities: () => void; + changeSelectedFilter: () => void; + navigateToNotificationRoute: () => void; +} -class NotificationView extends PureComponent { - /* Props - * ------------------------------------------------ - * @prop { type } name - Description.... - */ - listRef = null; - - constructor(props) { - super(props); - this.state = { - // TODO: Remove filters from local state. - filters: [ - { key: 'activities', value: 'ALL' }, - { key: 'replies', value: 'REPLIES' }, - { key: 'mentions', value: 'MENTIONS' }, - // { key: 'reblogs', value: 'REBLOGS' }, - ], - selectedIndex: 0, - }; - this.listRef = React.createRef(); - } +const NotificationView = ({ + notifications, + isLoading, + isNotificationRefreshing, + globalProps, + handleOnUserPress, + readAllNotification, + getActivities, + changeSelectedFilter, + navigateToNotificationRoute - // Component Life Cycles +}: Props) => { + const intl = useIntl(); - // Component Functions + const listRef = useRef(null); - _handleOnDropdownSelect = async (index) => { - const { changeSelectedFilter } = this.props; - const { filters, contentOffset } = this.state; + const isDarkTheme = useAppSelector(state => state.application.isDarkTheme); + const [selectedIndex, setSelectedIndex] = useState(0); - const _selectedFilter = filters[index].key; - this.setState({ selectedIndex: index, contentOffset }); - await changeSelectedFilter(_selectedFilter, index); - this.listRef.current?.scrollToOffset({ x: 0, y: 0, animated: false }); - }; + const _notifications = useMemo(() => _getSectionedNotifications(notifications, intl), [notifications]) - _renderList = (data) => { - const { navigateToNotificationRoute, globalProps } = this.props; - return ( - item.id} - renderItem={({ item }) => ( - - )} - /> - ); + const _handleOnDropdownSelect = async (index) => { + + const _selectedFilter = FILTERS[index].key; + + setSelectedIndex(index); + await changeSelectedFilter(_selectedFilter, index); + listRef.current?.scrollToOffset({ x: 0, y: 0, animated: false }); }; - _renderFooterLoading = () => { - const { isLoading } = this.props; + const _renderFooterLoading = () => { if (isLoading) { return ( @@ -89,177 +84,163 @@ class NotificationView extends PureComponent { return null; }; - _getNotificationsArrays = () => { - const { notifications, intl } = this.props; - - if (!notifications && notifications.length < 1) { - return null; - } - - const notificationArray = [ - { - title: intl.formatMessage({ - id: 'notification.recent', - }), - data: [], - }, - { - title: intl.formatMessage({ - id: 'notification.yesterday', - }), - data: [], - }, - { - title: intl.formatMessage({ - id: 'notification.this_week', - }), - data: [], - }, - { - title: intl.formatMessage({ - id: 'notification.last_week', - }), - data: [], - }, - { - title: intl.formatMessage({ - id: 'notification.this_month', - }), - data: [], - }, - { - title: intl.formatMessage({ - id: 'notification.older_then', - }), - data: [], - }, - ]; - - let sectionIndex = -1; - return notifications.map((item) => { - const timeIndex = this._getTimeListIndex(item.timestamp); - if (timeIndex !== sectionIndex && timeIndex > sectionIndex) { - if (sectionIndex === -1) { - item.firstSection = true; - } - item.sectionTitle = notificationArray[timeIndex].title; - sectionIndex = timeIndex; - } - return item; - }); - - // return notificationArray.filter((item) => item.data.length > 0).map((item, index)=>{item.index = index; return item}); - }; - - _getTimeListIndex = (timestamp) => { - if (isToday(timestamp)) { - return 0; - } - - if (isYesterday(timestamp)) { - return 1; - } - - if (isThisWeek(timestamp)) { - return 2; - } - - if (isLastWeek(timestamp)) { - return 3; - } - - if (isThisMonth(timestamp)) { - return 4; - } - - return 5; - }; - - _getActivityIndicator = () => ( - - - - ); - - _renderSectionHeader = ({ section: { title, index } }) => ( + const _renderSectionHeader = ({ section: { title, index } }) => ( ); - _renderItem = ({ item }) => ( + const _renderItem = ({ item }) => ( <> {item.sectionTitle && ( )} { - this.props.handleOnUserPress(item.source); + handleOnUserPress(item.source); }} - globalProps={this.props.globalProps} + globalProps={globalProps} /> ); - render() { - const { isDarkTheme } = this.props; - - const { readAllNotification, getActivities, isNotificationRefreshing, intl } = this.props; - const { filters, selectedIndex } = this.state; - const _notifications = this._getNotificationsArrays(); - - return ( - - - intl.formatMessage({ id: `notification.${item.key}` }).toUpperCase(), - )} - defaultText="ALL" - onDropdownSelect={this._handleOnDropdownSelect} - rightIconName="playlist-add-check" - rightIconType="MaterialIcons" - selectedOptionIndex={selectedIndex} - onRightIconPress={readAllNotification} - /> - - `${item.id}-${index}`} - onEndReached={() => getActivities(true)} - onEndReachedThreshold={0.3} - ListFooterComponent={this._renderFooterLoading} - ListEmptyComponent={ - isNotificationRefreshing ? ( - - ) : ( - - {intl.formatMessage({ id: 'notification.noactivity' })} - - ) - } - contentContainerStyle={styles.listContentContainer} - refreshControl={ - getActivities()} - progressBackgroundColor="#357CE6" - tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'} - titleColor="#fff" - colors={['#fff']} - /> - } - renderItem={this._renderItem} - /> - - ); - } + + return ( + + + intl.formatMessage({ id: `notification.${item.key}` }).toUpperCase(), + )} + defaultText="ALL" + onDropdownSelect={_handleOnDropdownSelect} + rightIconName="playlist-add-check" + rightIconType="MaterialIcons" + selectedOptionIndex={selectedIndex} + onRightIconPress={readAllNotification} + /> + + `${item.id}-${index}`} + onEndReached={() => getActivities(true)} + onEndReachedThreshold={0.3} + ListFooterComponent={_renderFooterLoading} + ListEmptyComponent={ + isNotificationRefreshing ? ( + + ) : ( + + {intl.formatMessage({ id: 'notification.noactivity' })} + + ) + } + contentContainerStyle={styles.listContentContainer} + refreshControl={ + getActivities()} + progressBackgroundColor="#357CE6" + tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'} + titleColor="#fff" + colors={['#fff']} + /> + } + renderItem={_renderItem} + /> + + ); + } -const mapStateToProps = (state: { application: { isDarkTheme: any } }) => ({ - isDarkTheme: state.application.isDarkTheme, -}); -export default connect(mapStateToProps)(injectIntl(NotificationView)); -// export default injectIntl(NotificationView); -/* eslint-enable */ + +export default NotificationView; + + +const _getSectionedNotifications = (notifications: any[], intl: any) => { + + if (!notifications && notifications.length < 1) { + return null; + } + + const notificationArray = [ + { + title: intl.formatMessage({ + id: 'notification.recent', + }), + data: [], + }, + { + title: intl.formatMessage({ + id: 'notification.yesterday', + }), + data: [], + }, + { + title: intl.formatMessage({ + id: 'notification.this_week', + }), + data: [], + }, + { + title: intl.formatMessage({ + id: 'notification.last_week', + }), + data: [], + }, + { + title: intl.formatMessage({ + id: 'notification.this_month', + }), + data: [], + }, + { + title: intl.formatMessage({ + id: 'notification.older_then', + }), + data: [], + }, + ]; + + let sectionIndex = -1; + return notifications.map((item) => { + + const timeIndex = _getTimeListIndex(item.gk); + if (timeIndex !== sectionIndex && timeIndex > sectionIndex) { + if (sectionIndex === -1) { + item.firstSection = true; + } + item.sectionTitle = notificationArray[timeIndex].title; + sectionIndex = timeIndex; + } + return item; + }); + + // return notificationArray.filter((item) => item.data.length > 0).map((item, index)=>{item.index = index; return item}); +}; + + +const _getTimeListIndex = (gk: string) => { + if (gk === "Recent" || gk.match(/\d+\s*hours?/)) { + return 0; + } + + if (gk === "Yesterday") { + return 1; + } + + if (isThisWeek(gk)) { + return 2; + } + + if (isLastWeek(gk)) { + return 3; + } + + if (isThisMonth(gk)) { + return 4; + } + + return 5; +}; \ No newline at end of file From c1b3b104f79379ce9ad42cb06ed683d21d784810 Mon Sep 17 00:00:00 2001 From: noumantahir Date: Tue, 19 Dec 2023 17:21:58 +0500 Subject: [PATCH 2/5] using sectioned list for better performance --- .../notification/view/notificationView.tsx | 89 +++++++++---------- 1 file changed, 43 insertions(+), 46 deletions(-) diff --git a/src/components/notification/view/notificationView.tsx b/src/components/notification/view/notificationView.tsx index ae6df550ea..1b1cea98db 100644 --- a/src/components/notification/view/notificationView.tsx +++ b/src/components/notification/view/notificationView.tsx @@ -1,33 +1,28 @@ -/* eslint-disable react/jsx-wrap-multilines */ -import React, { PureComponent, useMemo, useRef, useState } from 'react'; -import { connect } from 'react-redux'; -import { View, ActivityIndicator, Text } from 'react-native'; -import { injectIntl, useIntl } from 'react-intl'; +import React, { useMemo, useRef, useState } from 'react'; +import { useIntl } from 'react-intl'; +import { ActivityIndicator, SectionList, Text, View, RefreshControl } from 'react-native'; // Constants // Components -import { RefreshControl, FlatList } from 'react-native-gesture-handler'; import EStyleSheet from 'react-native-extended-stylesheet'; -import { ContainerHeader } from '../../containerHeader'; -import { FilterBar } from '../../filterBar'; import { NotificationLine } from '../..'; import { ListPlaceHolder } from '../../basicUIElements'; +import { ContainerHeader } from '../../containerHeader'; +import { FilterBar } from '../../filterBar'; // Utils -import { isToday, isYesterday, isThisWeek, isLastWeek, isThisMonth } from '../../../utils/time'; +import { isLastWeek, isThisMonth, isThisWeek } from '../../../utils/time'; // Styles -import styles from './notificationStyles'; import globalStyles from '../../../globalStyles'; import { useAppSelector } from '../../../hooks'; - +import styles from './notificationStyles'; const FILTERS = [ { key: 'activities', value: 'ALL' }, { key: 'replies', value: 'REPLIES' }, { key: 'mentions', value: 'MENTIONS' }, -] - +]; interface Props { notifications: any[]; @@ -50,22 +45,21 @@ const NotificationView = ({ readAllNotification, getActivities, changeSelectedFilter, - navigateToNotificationRoute - + navigateToNotificationRoute, }: Props) => { const intl = useIntl(); const listRef = useRef(null); - const isDarkTheme = useAppSelector(state => state.application.isDarkTheme); + const isDarkTheme = useAppSelector((state) => state.application.isDarkTheme); const [selectedIndex, setSelectedIndex] = useState(0); - - const _notifications = useMemo(() => _getSectionedNotifications(notifications, intl), [notifications]) - + const _notifications = useMemo( + () => _getSectionedNotifications(notifications, intl), + [notifications], + ); const _handleOnDropdownSelect = async (index) => { - const _selectedFilter = FILTERS[index].key; setSelectedIndex(index); @@ -90,9 +84,9 @@ const NotificationView = ({ const _renderItem = ({ item }) => ( <> - {item.sectionTitle && ( + {/* {item.sectionTitle && ( - )} + )} */} ); - return ( - `${item.id}-${index}`} onEndReached={() => getActivities(true)} onEndReachedThreshold={0.3} @@ -148,18 +142,15 @@ const NotificationView = ({ /> } renderItem={_renderItem} + renderSectionHeader={_renderSectionHeader} /> ); - -} - +}; export default NotificationView; - const _getSectionedNotifications = (notifications: any[], intl: any) => { - if (!notifications && notifications.length < 1) { return null; } @@ -203,30 +194,36 @@ const _getSectionedNotifications = (notifications: any[], intl: any) => { }, ]; - let sectionIndex = -1; - return notifications.map((item) => { - + // let sectionIndex = -1; + notifications.forEach((item) => { const timeIndex = _getTimeListIndex(item.gk); - if (timeIndex !== sectionIndex && timeIndex > sectionIndex) { - if (sectionIndex === -1) { - item.firstSection = true; - } - item.sectionTitle = notificationArray[timeIndex].title; - sectionIndex = timeIndex; - } - return item; + notificationArray[timeIndex].data.push(item); + + // if (timeIndex !== sectionIndex && timeIndex > sectionIndex) { + // if (sectionIndex === -1) { + // item.firstSection = true; + // } + // // item.sectionTitle = notificationArray[timeIndex].title; + // notificationArray[timeIndex].data.push(item); + // sectionIndex = timeIndex; + // } + // return item; }); - // return notificationArray.filter((item) => item.data.length > 0).map((item, index)=>{item.index = index; return item}); + return notificationArray + .filter((item) => item.data.length > 0) + .map((item, index) => { + item.index = index; + return item; + }); }; - const _getTimeListIndex = (gk: string) => { - if (gk === "Recent" || gk.match(/\d+\s*hours?/)) { + if (gk === 'Recent' || gk.match(/\d+\s*hours?/)) { return 0; } - if (gk === "Yesterday") { + if (gk === 'Yesterday') { return 1; } @@ -243,4 +240,4 @@ const _getTimeListIndex = (gk: string) => { } return 5; -}; \ No newline at end of file +}; From a4969825391a0c903cc247bfbf6b66c9aa21c1db Mon Sep 17 00:00:00 2001 From: noumantahir Date: Tue, 19 Dec 2023 17:22:39 +0500 Subject: [PATCH 3/5] "last month & older" label update --- src/config/locales/en-US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/locales/en-US.json b/src/config/locales/en-US.json index 157fd62959..b6d338f362 100644 --- a/src/config/locales/en-US.json +++ b/src/config/locales/en-US.json @@ -252,7 +252,7 @@ "this_week": "This Week", "last_week": "Last Week", "this_month": "This Month", - "older_then": "Older Than A Month", + "older_then": "Last Month & Older", "activities": "All", "replies": "Replies", "mentions": "Mentions", From 97e83ae9f611953d7b125d373c3c63b2945ccfc2 Mon Sep 17 00:00:00 2001 From: noumantahir Date: Tue, 19 Dec 2023 17:37:52 +0500 Subject: [PATCH 4/5] fix Last Week calculation issue --- src/utils/time.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/utils/time.js b/src/utils/time.js index 876ce9dba9..5ca350b475 100644 --- a/src/utils/time.js +++ b/src/utils/time.js @@ -3,6 +3,7 @@ import moment from 'moment'; const TODAY = new Date(); const ONE_DAY = new Date(TODAY.getTime() - 24 * 60 * 60 * 1000); const SEVEN_DAY = new Date(TODAY.getTime() - 7 * 24 * 60 * 60 * 1000); +const FOURTEEN_DAY = new Date(TODAY.getTime() - 14 * 24 * 60 * 60 * 1000); const MINUTE = 60; const HOUR = 60 * 60; @@ -112,17 +113,17 @@ export const isYesterday = (value) => { export const isThisWeek = (value) => { const day = new Date(value).getTime(); - return day < TODAY.getTime() && day > SEVEN_DAY.getTime(); + return day < TODAY.getTime() && day >= SEVEN_DAY.getTime(); }; export const isLastWeek = (value) => { const day = new Date(value).getTime(); - return day < SEVEN_DAY.getTime() && day > 2 * SEVEN_DAY.getTime(); + return day < SEVEN_DAY.getTime() && day >= FOURTEEN_DAY.getTime(); }; export const isThisMonth = (value) => { const day = new Date(value); - return TODAY.getMonth() === day.getMonth() && TODAY.getFullYear() === day.getFullYear() ? 1 : 0; + return TODAY.getMonth() === day.getMonth() && TODAY.getFullYear() === day.getFullYear(); }; export const isEmptyContentDate = (value) => { From 76cd09f080f47cc796119969df31aeb5a3aa3430 Mon Sep 17 00:00:00 2001 From: noumantahir Date: Tue, 19 Dec 2023 17:43:11 +0500 Subject: [PATCH 5/5] fix scrolling on filter change --- .../notification/view/notificationView.tsx | 38 ++++++------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/src/components/notification/view/notificationView.tsx b/src/components/notification/view/notificationView.tsx index 1b1cea98db..bd4ad4633d 100644 --- a/src/components/notification/view/notificationView.tsx +++ b/src/components/notification/view/notificationView.tsx @@ -49,7 +49,7 @@ const NotificationView = ({ }: Props) => { const intl = useIntl(); - const listRef = useRef(null); + const listRef = useRef(null); const isDarkTheme = useAppSelector((state) => state.application.isDarkTheme); const [selectedIndex, setSelectedIndex] = useState(0); @@ -61,10 +61,9 @@ const NotificationView = ({ const _handleOnDropdownSelect = async (index) => { const _selectedFilter = FILTERS[index].key; - setSelectedIndex(index); - await changeSelectedFilter(_selectedFilter, index); - listRef.current?.scrollToOffset({ x: 0, y: 0, animated: false }); + changeSelectedFilter(_selectedFilter, index); + listRef.current?.scrollToLocation({ itemIndex: 0, sectionIndex: 0, animated: false }); }; const _renderFooterLoading = () => { @@ -83,19 +82,14 @@ const NotificationView = ({ ); const _renderItem = ({ item }) => ( - <> - {/* {item.sectionTitle && ( - - )} */} - { - handleOnUserPress(item.source); - }} - globalProps={globalProps} - /> - + { + handleOnUserPress(item.source); + }} + globalProps={globalProps} + /> ); return ( @@ -198,16 +192,6 @@ const _getSectionedNotifications = (notifications: any[], intl: any) => { notifications.forEach((item) => { const timeIndex = _getTimeListIndex(item.gk); notificationArray[timeIndex].data.push(item); - - // if (timeIndex !== sectionIndex && timeIndex > sectionIndex) { - // if (sectionIndex === -1) { - // item.firstSection = true; - // } - // // item.sectionTitle = notificationArray[timeIndex].title; - // notificationArray[timeIndex].data.push(item); - // sectionIndex = timeIndex; - // } - // return item; }); return notificationArray