From 597b5b03f133096a53663c2d57137ce667a522f0 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Mon, 9 May 2022 11:29:27 -0400 Subject: [PATCH] Filter to show only unread channels (#6248) --- app/actions/local/channel.ts | 16 +++ app/constants/database.ts | 1 + app/queries/servers/system.ts | 7 + .../__snapshots__/index.test.tsx.snap | 125 +++++++++++++----- .../categories_list/categories/categories.tsx | 11 +- .../categories_list/categories/index.ts | 2 + .../categories/unreads/index.ts | 5 +- .../channel_list/categories_list/index.tsx | 4 +- .../categories_list/subheader/index.ts | 30 +++++ .../__snapshots__/index.test.tsx.snap | 2 +- .../search_field}/index.test.tsx | 0 .../search_field}/index.tsx | 2 +- .../categories_list/subheader/subheader.tsx | 44 ++++++ .../subheader/unread_filter/index.ts | 17 +++ .../subheader/unread_filter/unread_filter.tsx | 54 ++++++++ 15 files changed, 278 insertions(+), 42 deletions(-) create mode 100644 app/screens/home/channel_list/categories_list/subheader/index.ts rename app/screens/home/channel_list/categories_list/{search => subheader/search_field}/__snapshots__/index.test.tsx.snap (97%) rename app/screens/home/channel_list/categories_list/{search => subheader/search_field}/index.test.tsx (100%) rename app/screens/home/channel_list/categories_list/{search => subheader/search_field}/index.tsx (99%) create mode 100644 app/screens/home/channel_list/categories_list/subheader/subheader.tsx create mode 100644 app/screens/home/channel_list/categories_list/subheader/unread_filter/index.ts create mode 100644 app/screens/home/channel_list/categories_list/subheader/unread_filter/unread_filter.tsx diff --git a/app/actions/local/channel.ts b/app/actions/local/channel.ts index 1539070fd..f17731175 100644 --- a/app/actions/local/channel.ts +++ b/app/actions/local/channel.ts @@ -6,6 +6,7 @@ import {DeviceEventEmitter} from 'react-native'; import {General, Navigation as NavigationConstants, Preferences, Screens} from '@constants'; import {CHANNELS_CATEGORY, DMS_CATEGORY} from '@constants/categories'; +import {SYSTEM_IDENTIFIERS} from '@constants/database'; import DatabaseManager from '@database/manager'; import {getTeammateNameDisplaySetting} from '@helpers/api/preference'; import {extractChannelDisplayName} from '@helpers/database'; @@ -467,3 +468,18 @@ export async function addChannelToDefaultCategory(serverUrl: string, channel: Ch return {models}; } + +export async function showUnreadChannelsOnly(serverUrl: string, onlyUnreads: boolean) { + const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; + if (!operator) { + return {error: `${serverUrl} database not found`}; + } + + return operator.handleSystem({ + systems: [{ + id: SYSTEM_IDENTIFIERS.ONLY_UNREADS, + value: JSON.stringify(onlyUnreads), + }], + prepareRecordsOnly: false, + }); +} diff --git a/app/constants/database.ts b/app/constants/database.ts index 0fdca2fd4..040a483af 100644 --- a/app/constants/database.ts +++ b/app/constants/database.ts @@ -54,6 +54,7 @@ export const SYSTEM_IDENTIFIERS = { DATA_RETENTION_POLICIES: 'dataRetentionPolicies', EXPANDED_LINKS: 'expandedLinks', LICENSE: 'license', + ONLY_UNREADS: 'onlyUnreads', PUSH_VERIFICATION_STATUS: 'pushVerificationStatus', RECENT_CUSTOM_STATUS: 'recentCustomStatus', RECENT_MENTIONS: 'recentMentions', diff --git a/app/queries/servers/system.ts b/app/queries/servers/system.ts index a934b0770..e8b2ecc5f 100644 --- a/app/queries/servers/system.ts +++ b/app/queries/servers/system.ts @@ -398,3 +398,10 @@ export const getLastUnreadChannelId = async (serverDatabase: Database): Promise< return ''; } }; + +export const observeOnlyUnreads = (database: Database) => { + return querySystemValue(database, SYSTEM_IDENTIFIERS.ONLY_UNREADS).observeWithColumns(['value']).pipe( + switchMap((result) => (result.length ? result[0].observe() : of$({value: false}))), + switchMap((model) => of$(model.value as boolean)), + ); +}; diff --git a/app/screens/home/channel_list/categories_list/__snapshots__/index.test.tsx.snap b/app/screens/home/channel_list/categories_list/__snapshots__/index.test.tsx.snap index 5c8055160..a74370738 100644 --- a/app/screens/home/channel_list/categories_list/__snapshots__/index.test.tsx.snap +++ b/app/screens/home/channel_list/categories_list/__snapshots__/index.test.tsx.snap @@ -415,53 +415,112 @@ exports[`components/categories_list should render team error 1`] = ` - - - Find channels... - + + + + + + + + Find channels... + + (item === 'UNREADS' ? 'UNREADS' : item.id); -const Categories = ({categories, currentTeamId, unreadsOnTop}: Props) => { +const Categories = ({categories, currentTeamId, onlyUnreads, unreadsOnTop}: Props) => { const intl = useIntl(); const listRef = useRef(null); const serverUrl = useServerUrl(); @@ -49,6 +50,7 @@ const Categories = ({categories, currentTeamId, unreadsOnTop}: Props) => { currentTeamId={currentTeamId} isTablet={isTablet} onChannelSwitch={onChannelSwitch} + onlyUnreads={onlyUnreads} /> ); } @@ -63,20 +65,23 @@ const Categories = ({categories, currentTeamId, unreadsOnTop}: Props) => { /> ); - }, [currentTeamId, intl.locale, isTablet, onChannelSwitch]); + }, [currentTeamId, intl.locale, isTablet, onChannelSwitch, onlyUnreads]); useEffect(() => { listRef.current?.scrollToOffset({animated: false, offset: 0}); }, [currentTeamId]); const categoriesToShow = useMemo(() => { + if (onlyUnreads && !unreadsOnTop) { + return ['UNREADS' as const]; + } const orderedCategories = [...categories]; orderedCategories.sort((a, b) => a.sortOrder - b.sortOrder); if (unreadsOnTop) { return ['UNREADS' as const, ...orderedCategories]; } return orderedCategories; - }, [categories, unreadsOnTop]); + }, [categories, onlyUnreads, unreadsOnTop]); if (!categories.length) { return ; diff --git a/app/screens/home/channel_list/categories_list/categories/index.ts b/app/screens/home/channel_list/categories_list/categories/index.ts index 11cadbb7b..0161f676c 100644 --- a/app/screens/home/channel_list/categories_list/categories/index.ts +++ b/app/screens/home/channel_list/categories_list/categories/index.ts @@ -10,6 +10,7 @@ import {Preferences} from '@constants'; import {getPreferenceAsBool} from '@helpers/api/preference'; import {queryCategoriesByTeamIds} from '@queries/servers/categories'; import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; +import {observeOnlyUnreads} from '@queries/servers/system'; import Categories from './categories'; @@ -31,6 +32,7 @@ const enhanced = withObservables( return { categories, + onlyUnreads: observeOnlyUnreads(database), unreadsOnTop, }; }); diff --git a/app/screens/home/channel_list/categories_list/categories/unreads/index.ts b/app/screens/home/channel_list/categories_list/categories/unreads/index.ts index 4e1c5336e..efe1f65b5 100644 --- a/app/screens/home/channel_list/categories_list/categories/unreads/index.ts +++ b/app/screens/home/channel_list/categories_list/categories/unreads/index.ts @@ -24,6 +24,7 @@ import type PreferenceModel from '@typings/database/models/servers/preference'; type WithDatabaseProps = WithDatabaseArgs & { currentTeamId: string; isTablet: boolean; + onlyUnreads: boolean; } type CA = [ @@ -89,7 +90,7 @@ const filterAndSortMyChannels = ([myChannels, notifyProps]: [MyChannelModel[], N return [...mentions, ...unreads, ...mutedMentions]; }; -const enhanced = withObservables(['currentTeamId', 'isTablet'], ({currentTeamId, isTablet, database}: WithDatabaseProps) => { +const enhanced = withObservables(['currentTeamId', 'isTablet', 'onlyUnreads'], ({currentTeamId, isTablet, database, onlyUnreads}: WithDatabaseProps) => { const unreadsOnTop = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS). observeWithColumns(['value']). pipe( @@ -99,7 +100,7 @@ const enhanced = withObservables(['currentTeamId', 'isTablet'], ({currentTeamId, const getC = (lastUnreadChannelId: string) => getChannelById(database, lastUnreadChannelId); const unreadChannels = unreadsOnTop.pipe(switchMap((gU) => { - if (gU) { + if (gU || onlyUnreads) { const lastUnread = isTablet ? observeLastUnreadChannelId(database).pipe( switchMap(getC), ) : of$(''); diff --git a/app/screens/home/channel_list/categories_list/index.tsx b/app/screens/home/channel_list/categories_list/index.tsx index f0159f90e..6bfe9904d 100644 --- a/app/screens/home/channel_list/categories_list/index.tsx +++ b/app/screens/home/channel_list/categories_list/index.tsx @@ -12,7 +12,7 @@ import Categories from './categories'; import ChannelListHeader from './header'; import LoadChannelsError from './load_channels_error'; import LoadTeamsError from './load_teams_error'; -import SearchField from './search'; +import SubHeader from './subheader'; import ThreadsButton from './threads_button'; const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ @@ -68,7 +68,7 @@ const CategoriesList = ({channelsCount, currentTeamId, iconPad, isCRTEnabled, is } else { content = ( <> - + {isCRTEnabled && } { + const unreadsOnTop = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS). + observeWithColumns(['value']). + pipe( + switchMap((prefs: PreferenceModel[]) => of$(getPreferenceAsBool(prefs, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS, false))), + ); + + return { + unreadsOnTop, + }; +}); + +export default withDatabase(enhanced(SubHeader)); diff --git a/app/screens/home/channel_list/categories_list/search/__snapshots__/index.test.tsx.snap b/app/screens/home/channel_list/categories_list/subheader/search_field/__snapshots__/index.test.tsx.snap similarity index 97% rename from app/screens/home/channel_list/categories_list/search/__snapshots__/index.test.tsx.snap rename to app/screens/home/channel_list/categories_list/subheader/search_field/__snapshots__/index.test.tsx.snap index d26b4a3b3..8b7d36ac3 100644 --- a/app/screens/home/channel_list/categories_list/search/__snapshots__/index.test.tsx.snap +++ b/app/screens/home/channel_list/categories_list/subheader/search_field/__snapshots__/index.test.tsx.snap @@ -15,12 +15,12 @@ exports[`Search Field should match snapshot 1`] = ` Object { "backgroundColor": "rgba(255,255,255,0.12)", "borderRadius": 8, + "flex": 1, "flexDirection": "row", "height": 40, "justifyContent": "flex-start", "marginVertical": 20, "padding": 8, - "width": "100%", } } > diff --git a/app/screens/home/channel_list/categories_list/search/index.test.tsx b/app/screens/home/channel_list/categories_list/subheader/search_field/index.test.tsx similarity index 100% rename from app/screens/home/channel_list/categories_list/search/index.test.tsx rename to app/screens/home/channel_list/categories_list/subheader/search_field/index.test.tsx diff --git a/app/screens/home/channel_list/categories_list/search/index.tsx b/app/screens/home/channel_list/categories_list/subheader/search_field/index.tsx similarity index 99% rename from app/screens/home/channel_list/categories_list/search/index.tsx rename to app/screens/home/channel_list/categories_list/subheader/search_field/index.tsx index ae21ec9be..4e86bf841 100644 --- a/app/screens/home/channel_list/categories_list/search/index.tsx +++ b/app/screens/home/channel_list/categories_list/subheader/search_field/index.tsx @@ -18,7 +18,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ container: { flexDirection: 'row', justifyContent: 'flex-start', - width: '100%', + flex: 1, backgroundColor: changeOpacity(theme.sidebarText, 0.12), borderRadius: 8, padding: 8, diff --git a/app/screens/home/channel_list/categories_list/subheader/subheader.tsx b/app/screens/home/channel_list/categories_list/subheader/subheader.tsx new file mode 100644 index 000000000..46a6d9ffc --- /dev/null +++ b/app/screens/home/channel_list/categories_list/subheader/subheader.tsx @@ -0,0 +1,44 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useEffect} from 'react'; +import {StyleSheet, View} from 'react-native'; +import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; + +import SearchField from './search_field'; +import UnreadFilter from './unread_filter'; + +type Props = { + unreadsOnTop: boolean; +} + +const style = StyleSheet.create({ + container: { + flexDirection: 'row', + }, +}); + +const SubHeader = ({unreadsOnTop}: Props) => { + const showFilter = useSharedValue(!unreadsOnTop); + + const animatedStyle = useAnimatedStyle(() => ({ + marginRight: withTiming(showFilter.value ? 8 : 0, {duration: 300}), + width: withTiming(showFilter.value ? 40 : 0, {duration: 300}), + opacity: withTiming(showFilter.value ? 1 : 0, {duration: 300}), + })); + + useEffect(() => { + showFilter.value = !unreadsOnTop; + }, [unreadsOnTop]); + + return ( + + + + + + + ); +}; + +export default SubHeader; diff --git a/app/screens/home/channel_list/categories_list/subheader/unread_filter/index.ts b/app/screens/home/channel_list/categories_list/subheader/unread_filter/index.ts new file mode 100644 index 000000000..c69c62453 --- /dev/null +++ b/app/screens/home/channel_list/categories_list/subheader/unread_filter/index.ts @@ -0,0 +1,17 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; +import withObservables from '@nozbe/with-observables'; + +import {observeOnlyUnreads} from '@queries/servers/system'; + +import UnreadFilter from './unread_filter'; + +import type {WithDatabaseArgs} from '@typings/database/database'; + +const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({ + onlyUnreads: observeOnlyUnreads(database), +})); + +export default withDatabase(enhanced(UnreadFilter)); diff --git a/app/screens/home/channel_list/categories_list/subheader/unread_filter/unread_filter.tsx b/app/screens/home/channel_list/categories_list/subheader/unread_filter/unread_filter.tsx new file mode 100644 index 000000000..c7ccd5512 --- /dev/null +++ b/app/screens/home/channel_list/categories_list/subheader/unread_filter/unread_filter.tsx @@ -0,0 +1,54 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {TouchableWithoutFeedback, View} from 'react-native'; + +import {showUnreadChannelsOnly} from '@actions/local/channel'; +import CompassIcon from '@components/compass_icon'; +import {useServerUrl} from '@context/server'; +import {useTheme} from '@context/theme'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; + +type Props = { + onlyUnreads: boolean; +} + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ + container: { + alignItems: 'center', + backgroundColor: changeOpacity(theme.sidebarText, 0.12), + borderRadius: 8, + height: 40, + justifyContent: 'center', + marginVertical: 20, + width: 40, + }, + filtered: { + backgroundColor: theme.sidebarText, + }, +})); + +const UnreadFilter = ({onlyUnreads}: Props) => { + const theme = useTheme(); + const serverUrl = useServerUrl(); + const styles = getStyleSheet(theme); + + const onPress = () => { + showUnreadChannelsOnly(serverUrl, !onlyUnreads); + }; + + return ( + + + + + + ); +}; + +export default UnreadFilter;