forked from Ivasoft/mattermost-mobile
[Gekidou] Sort unfiltered find channels by mention / unread (#6340)
* Sort unfiltered find channels by mention / unread * Prevent Double tap to access find channels
This commit is contained in:
@@ -5,6 +5,24 @@ import {General} from '@constants';
|
||||
|
||||
import type Model from '@nozbe/watermelondb/Model';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
|
||||
type NotifyProps = {
|
||||
[key: string]: Partial<ChannelNotifyProps>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtering / Sorting:
|
||||
*
|
||||
* Unreads, Mentions, and Muted Mentions Only
|
||||
* Mentions on top, then unreads, then muted channels with mentions.
|
||||
*/
|
||||
|
||||
type FilterAndSortMyChannelsArgs = [
|
||||
MyChannelModel[],
|
||||
Record<string, ChannelModel>,
|
||||
NotifyProps,
|
||||
]
|
||||
|
||||
export const extractRecordsForTable = <T>(records: Model[], tableName: string): T[] => {
|
||||
// @ts-expect-error constructor.table not exposed in type definition
|
||||
@@ -32,3 +50,44 @@ export function extractChannelDisplayName(raw: Pick<Channel, 'type' | 'display_n
|
||||
|
||||
return displayName;
|
||||
}
|
||||
|
||||
export const makeChannelsMap = (channels: ChannelModel[]) => {
|
||||
return channels.reduce<Record<string, ChannelModel>>((result, c) => {
|
||||
result[c.id] = c;
|
||||
return result;
|
||||
}, {});
|
||||
};
|
||||
|
||||
export const filterAndSortMyChannels = ([myChannels, channels, notifyProps]: FilterAndSortMyChannelsArgs): ChannelModel[] => {
|
||||
const mentions: ChannelModel[] = [];
|
||||
const unreads: ChannelModel[] = [];
|
||||
const mutedMentions: ChannelModel[] = [];
|
||||
|
||||
const isMuted = (id: string) => {
|
||||
return notifyProps[id]?.mark_unread === 'mention';
|
||||
};
|
||||
|
||||
for (const myChannel of myChannels) {
|
||||
const id = myChannel.id;
|
||||
|
||||
// is it a mention?
|
||||
if (!isMuted(id) && myChannel.mentionsCount > 0 && channels[id]) {
|
||||
mentions.push(channels[id]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// is it unread?
|
||||
if (!isMuted(myChannel.id) && myChannel.isUnread && channels[id]) {
|
||||
unreads.push(channels[id]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// is it a muted mention?
|
||||
if (isMuted(myChannel.id) && myChannel.mentionsCount > 0 && channels[id]) {
|
||||
mutedMentions.push(channels[id]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return [...mentions, ...unreads, ...mutedMentions];
|
||||
};
|
||||
|
||||
@@ -466,16 +466,21 @@ export function observeMyChannelMentionCount(database: Database, teamId?: string
|
||||
|
||||
export function queryMyChannelsByUnread(database: Database, isUnread: boolean, sortBy: 'last_viewed_at' | 'last_post_at', take: number, excludeIds?: string[]) {
|
||||
const clause: Q.Clause[] = [Q.where('is_unread', Q.eq(isUnread))];
|
||||
const count: Q.Clause[] = [];
|
||||
|
||||
if (excludeIds?.length) {
|
||||
clause.push(Q.where('id', Q.notIn(excludeIds)));
|
||||
}
|
||||
|
||||
if (take > 0) {
|
||||
count.push(Q.take(take));
|
||||
}
|
||||
|
||||
return queryAllMyChannel(database).extend(
|
||||
Q.on(CHANNEL, Q.where('delete_at', Q.eq(0))),
|
||||
...clause,
|
||||
Q.sortBy(sortBy, Q.desc),
|
||||
Q.take(take),
|
||||
...count,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@ import {Database} from '@nozbe/watermelondb';
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
import {combineLatestWith, map, switchMap} from 'rxjs/operators';
|
||||
|
||||
import {queryMyChannelsByUnread} from '@queries/servers/channel';
|
||||
import {filterAndSortMyChannels, makeChannelsMap} from '@helpers/database';
|
||||
import {observeNotifyPropsByChannels, queryMyChannelsByUnread} from '@queries/servers/channel';
|
||||
import {queryJoinedTeams} from '@queries/servers/team';
|
||||
import {retrieveChannels} from '@screens/find_channels/utils';
|
||||
|
||||
@@ -30,10 +31,18 @@ const observeRecentChannels = (database: Database, unreads: ChannelModel[]) => {
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
const teamsCount = queryJoinedTeams(database).observeCount();
|
||||
|
||||
const unreadChannels = queryMyChannelsByUnread(database, true, 'last_post_at', MAX_UNREAD_CHANNELS).
|
||||
observeWithColumns(['last_post_at']).pipe(
|
||||
switchMap((myChannels) => retrieveChannels(database, myChannels)),
|
||||
);
|
||||
const myUnreadChannels = queryMyChannelsByUnread(database, true, 'last_post_at', 0).
|
||||
observeWithColumns(['last_post_at']);
|
||||
const notifyProps = myUnreadChannels.pipe(switchMap((cs) => observeNotifyPropsByChannels(database, cs)));
|
||||
const channels = myUnreadChannels.pipe(
|
||||
switchMap((myChannels) => retrieveChannels(database, myChannels)),
|
||||
);
|
||||
const channelsMap = channels.pipe(switchMap((cs) => of$(makeChannelsMap(cs))));
|
||||
const unreadChannels = myUnreadChannels.pipe(
|
||||
combineLatestWith(channelsMap, notifyProps),
|
||||
map(filterAndSortMyChannels),
|
||||
switchMap((cs) => of$(cs.slice(0, MAX_UNREAD_CHANNELS))),
|
||||
);
|
||||
|
||||
const recentChannels = unreadChannels.pipe(
|
||||
switchMap((unreads) => observeRecentChannels(database, unreads)),
|
||||
|
||||
@@ -8,6 +8,7 @@ import {combineLatestWith, map, switchMap} from 'rxjs/operators';
|
||||
|
||||
import {Preferences} from '@constants';
|
||||
import {getPreferenceAsBool} from '@helpers/api/preference';
|
||||
import {filterAndSortMyChannels, makeChannelsMap} from '@helpers/database';
|
||||
import {getChannelById, observeChannelsByLastPostAt, observeNotifyPropsByChannels, queryMyChannelUnreads} from '@queries/servers/channel';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {observeLastUnreadChannelId} from '@queries/servers/system';
|
||||
@@ -16,7 +17,6 @@ import UnreadCategories from './unreads';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
import type PreferenceModel from '@typings/database/models/servers/preference';
|
||||
|
||||
type WithDatabaseProps = WithDatabaseArgs & {
|
||||
@@ -30,68 +30,10 @@ type CA = [
|
||||
b: ChannelModel | undefined,
|
||||
]
|
||||
|
||||
type NotifyProps = {
|
||||
[key: string]: Partial<ChannelNotifyProps>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtering / Sorting:
|
||||
*
|
||||
* Unreads, Mentions, and Muted Mentions Only
|
||||
* Mentions on top, then unreads, then muted channels with mentions.
|
||||
*/
|
||||
|
||||
type FilterAndSortMyChannelsArgs = [
|
||||
MyChannelModel[],
|
||||
Record<string, ChannelModel>,
|
||||
NotifyProps,
|
||||
]
|
||||
|
||||
const concatenateChannelsArray = ([a, b]: CA) => {
|
||||
return of$(b ? a.filter((c) => c && c.id !== b.id).concat(b) : a);
|
||||
};
|
||||
|
||||
const filterAndSortMyChannels = ([myChannels, channels, notifyProps]: FilterAndSortMyChannelsArgs): ChannelModel[] => {
|
||||
const mentions: ChannelModel[] = [];
|
||||
const unreads: ChannelModel[] = [];
|
||||
const mutedMentions: ChannelModel[] = [];
|
||||
|
||||
const isMuted = (id: string) => {
|
||||
return notifyProps[id]?.mark_unread === 'mention';
|
||||
};
|
||||
|
||||
for (const myChannel of myChannels) {
|
||||
const id = myChannel.id;
|
||||
|
||||
// is it a mention?
|
||||
if (!isMuted(id) && myChannel.mentionsCount > 0 && channels[id]) {
|
||||
mentions.push(channels[id]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// is it unread?
|
||||
if (!isMuted(myChannel.id) && myChannel.isUnread && channels[id]) {
|
||||
unreads.push(channels[id]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// is it a muted mention?
|
||||
if (isMuted(myChannel.id) && myChannel.mentionsCount > 0 && channels[id]) {
|
||||
mutedMentions.push(channels[id]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return [...mentions, ...unreads, ...mutedMentions];
|
||||
};
|
||||
|
||||
const makeChannelsMap = (channels: ChannelModel[]) => {
|
||||
return channels.reduce<Record<string, ChannelModel>>((result, c) => {
|
||||
result[c.id] = c;
|
||||
return result;
|
||||
}, {});
|
||||
};
|
||||
|
||||
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']).
|
||||
|
||||
@@ -9,6 +9,7 @@ import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {findChannels} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
@@ -41,12 +42,12 @@ const SearchField = () => {
|
||||
const intl = useIntl();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
const onPress = useCallback(preventDoubleTap(() => {
|
||||
findChannels(
|
||||
intl.formatMessage({id: 'find_channels.title', defaultMessage: 'Find Channels'}),
|
||||
theme,
|
||||
);
|
||||
}, [intl.locale, theme]);
|
||||
}), [intl.locale, theme]);
|
||||
|
||||
return (
|
||||
<TouchableHighlight
|
||||
|
||||
Reference in New Issue
Block a user