Active State for selected Channel (#6163)

* Unreads on top

* Feedback addressed

* WIP: Using a lastUnreadChannelId to maintain an Active State on selected channel

* Pair programming session

* Pair programming #2

* Pair programming #3

* Test fix
This commit is contained in:
Shaz MJ
2022-04-13 23:36:47 +10:00
committed by GitHub
parent f09ab7c90a
commit 10ea42c812
20 changed files with 195 additions and 83 deletions

View File

@@ -121,7 +121,7 @@ describe('switchToChannel', () => {
const {systemValues, teamHistory, channelHistory, member} = await queryDatabaseValues(operator.database, teamId, channelId);
expect(error).toBeUndefined();
expect(models?.length).toBe(1); // Viewed at
expect(models?.length).toBe(2); // Viewed at, lastUnread
expect(systemValues.currentTeamId).toBe(teamId);
expect(systemValues.currentChannelId).toBe(channelId);
expect(teamHistory.length).toBe(0);
@@ -146,7 +146,7 @@ describe('switchToChannel', () => {
const {systemValues, teamHistory, channelHistory, member} = await queryDatabaseValues(operator.database, teamId, channelId);
expect(error).toBeUndefined();
expect(models?.length).toBe(3); // Viewed at, channel history and currentChannelId
expect(models?.length).toBe(4); // Viewed at, channel history and currentChannelId
expect(systemValues.currentTeamId).toBe(teamId);
expect(systemValues.currentChannelId).toBe(channelId);
expect(teamHistory.length).toBe(0);
@@ -172,7 +172,7 @@ describe('switchToChannel', () => {
const {systemValues, teamHistory, channelHistory, member} = await queryDatabaseValues(operator.database, teamId, channelId);
expect(error).toBeUndefined();
expect(models?.length).toBe(5); // Viewed at, channel history, team history, currentTeamId and currentChannelId
expect(models?.length).toBe(6); // Viewed at, channel history, team history, currentTeamId and currentChannelId
expect(systemValues.currentTeamId).toBe(teamId);
expect(systemValues.currentChannelId).toBe(channelId);
expect(teamHistory.length).toBe(1);
@@ -200,7 +200,7 @@ describe('switchToChannel', () => {
const {systemValues, teamHistory, channelHistory, member} = await queryDatabaseValues(operator.database, teamId, channelId);
expect(error).toBeUndefined();
expect(models?.length).toBe(4); // Viewed at, channel history, team history and currentTeamId
expect(models?.length).toBe(5); // Viewed at, channel history, team history and currentTeamId, lastUnread
expect(systemValues.currentTeamId).toBe(teamId);
expect(systemValues.currentChannelId).toBe(channelId);
expect(teamHistory.length).toBe(1);
@@ -228,7 +228,7 @@ describe('switchToChannel', () => {
const {systemValues, teamHistory, channelHistory, member} = await queryDatabaseValues(operator.database, teamId, channelId);
expect(error).toBeUndefined();
expect(models?.length).toBe(5); // Viewed at, channel history, team history, currentTeamId and currentChannelId
expect(models?.length).toBe(6); // Viewed at, channel history, team history, currentTeamId and currentChannelId, lastUnread
expect(systemValues.currentTeamId).toBe(teamId);
expect(systemValues.currentChannelId).toBe(channelId);
expect(teamHistory.length).toBe(1);
@@ -255,7 +255,7 @@ describe('switchToChannel', () => {
const {systemValues, teamHistory, channelHistory, member} = await queryDatabaseValues(operator.database, teamId, channelId);
expect(error).toBeUndefined();
expect(models?.length).toBe(5); // Viewed at, channel history, team history, currentTeamId and currentChannelId
expect(models?.length).toBe(6); // Viewed at, channel history, team history, currentTeamId and currentChannelId, lastUnread
expect(systemValues.currentTeamId).toBe(teamId);
expect(systemValues.currentChannelId).toBe(channelId);
expect(teamHistory.length).toBe(1);
@@ -282,7 +282,7 @@ describe('switchToChannel', () => {
const {systemValues, teamHistory, channelHistory, member} = await queryDatabaseValues(operator.database, teamId, channelId);
expect(error).toBeUndefined();
expect(models?.length).toBe(3); // Viewed at, channel history and currentChannelId
expect(models?.length).toBe(4); // Viewed at, channel history and currentChannelId, lastUnread
expect(systemValues.currentTeamId).toBe(teamId);
expect(systemValues.currentChannelId).toBe(channelId);
expect(teamHistory.length).toBe(0);
@@ -309,7 +309,7 @@ describe('switchToChannel', () => {
const {systemValues, teamHistory, channelHistory, member} = await queryDatabaseValues(operator.database, teamId, channelId);
expect(error).toBeUndefined();
expect(models?.length).toBe(3); // Viewed at, channel history and currentChannelId
expect(models?.length).toBe(4); // Viewed at, channel history and currentChannelId, lastUnread
expect(systemValues.currentTeamId).toBe(teamId);
expect(systemValues.currentChannelId).toBe(channelId);
expect(teamHistory.length).toBe(0);
@@ -335,7 +335,7 @@ describe('switchToChannel', () => {
const {systemValues, teamHistory, channelHistory, member} = await queryDatabaseValues(operator.database, teamId, channelId);
expect(error).toBeUndefined();
expect(models?.length).toBe(5); // Viewed at, channel history, team history, currentTeamId and currentChannelId
expect(models?.length).toBe(6); // Viewed at, channel history, team history, currentTeamId and currentChannelId, lastUnread
expect(systemValues.currentTeamId).toBe(teamId);
expect(systemValues.currentChannelId).toBe(channelId);
expect(teamHistory.length).toBe(1);
@@ -395,7 +395,7 @@ describe('switchToChannel', () => {
const {systemValues, teamHistory, channelHistory, member} = await queryDatabaseValues(operator.database, teamId, channelId);
expect(error).toBeUndefined();
expect(models?.length).toBe(5); // Viewed at, channel history, team history, currentTeamId and currentChannelId
expect(models?.length).toBe(6); // Viewed at, channel history, team history, currentTeamId and currentChannelId, lastunread
expect(systemValues.currentTeamId).toBe(currentTeamId);
expect(systemValues.currentChannelId).toBe(currentChannelId);
expect(teamHistory.length).toBe(0);
@@ -422,7 +422,7 @@ describe('switchToChannel', () => {
const {systemValues, teamHistory, channelHistory, member} = await queryDatabaseValues(operator.database, teamId, channelId);
expect(error).toBeUndefined();
expect(models?.length).toBe(5); // Viewed at, channel history, team history, currentTeamId and currentChannelId
expect(models?.length).toBe(6); // Viewed at, channel history, team history, currentTeamId and currentChannelId, lastUnread
expect(systemValues.currentTeamId).toBe(teamId);
expect(systemValues.currentChannelId).toBe(channelId);
expect(teamHistory.length).toBe(1);

View File

@@ -61,15 +61,18 @@ export async function switchToChannel(serverUrl: string, channelId: string, team
models.push(...history);
}
const commonValues: PrepareCommonSystemValuesArgs = {
lastUnreadChannelId: member.isUnread ? channelId : '',
};
if ((system.currentTeamId !== toTeamId) || (system.currentChannelId !== channelId)) {
const commonValues: PrepareCommonSystemValuesArgs = {
currentChannelId: system.currentChannelId === channelId ? undefined : channelId,
currentTeamId: system.currentTeamId === toTeamId ? undefined : toTeamId,
};
const common = await prepareCommonSystemValues(operator, commonValues);
if (common) {
models.push(...common);
}
commonValues.currentChannelId = system.currentChannelId === channelId ? undefined : channelId;
commonValues.currentTeamId = system.currentTeamId === toTeamId ? undefined : toTeamId;
}
const common = await prepareCommonSystemValues(operator, commonValues);
if (common) {
models.push(...common);
}
if (system.currentChannelId !== channelId || system.currentTeamId !== toTeamId) {

View File

@@ -21,6 +21,12 @@ export async function appEntry(serverUrl: string, since = 0) {
}
const {database} = operator;
// clear lastUnreadChannelId
const removeLastUnreadChannelId = await prepareCommonSystemValues(operator, {lastUnreadChannelId: ''});
if (removeLastUnreadChannelId) {
operator.batchRecords(removeLastUnreadChannelId);
}
const tabletDevice = await isTablet();
const currentTeamId = await getCurrentTeamId(database);
const lastDisconnectedAt = (await getWebSocketLastDisconnected(database)) || since;
@@ -72,6 +78,7 @@ export async function appEntry(serverUrl: string, since = 0) {
if (rolesData.roles?.length) {
modelPromises.push(operator.handleRole({roles: rolesData.roles, prepareRecordsOnly: true}));
}
const models = await Promise.all(modelPromises);
await operator.batchRecords(models.flat());

View File

@@ -274,7 +274,7 @@ export async function handleTeamChange(serverUrl: string, teamId: string) {
}
const models = [];
const system = await prepareCommonSystemValues(operator, {currentChannelId: channelId, currentTeamId: teamId});
const system = await prepareCommonSystemValues(operator, {currentChannelId: channelId, currentTeamId: teamId, lastUnreadChannelId: ''});
if (system?.length) {
models.push(...system);
}

View File

@@ -1,7 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {fetchMissingSidebarInfo, switchToChannelById} from '@actions/remote/channel';
import {markChannelAsViewed} from '@actions/local/channel';
import {fetchMissingSidebarInfo, markChannelAsRead, switchToChannelById} from '@actions/remote/channel';
import {AppEntryData, AppEntryError, fetchAppEntryData, teamsToRemove} from '@actions/remote/entry/common';
import {fetchPostsForUnreadChannels, fetchPostsSince} from '@actions/remote/post';
import {fetchRoles} from '@actions/remote/role';
@@ -9,13 +10,14 @@ import {fetchConfigAndLicense} from '@actions/remote/systems';
import {fetchAllTeams, fetchTeamsChannelsAndUnreadPosts} from '@actions/remote/team';
import {fetchNewThreads} from '@actions/remote/thread';
import {fetchStatusByIds, updateAllUsersSince} from '@actions/remote/user';
import {WebsocketEvents} from '@constants';
import {Screens, WebsocketEvents} from '@constants';
import {SYSTEM_IDENTIFIERS} from '@constants/database';
import DatabaseManager from '@database/manager';
import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
import {queryChannelsById, getDefaultChannelForTeam} from '@queries/servers/channel';
import {prepareModels} from '@queries/servers/entry';
import {getCommonSystemValues, getConfig, getCurrentChannelId, getWebSocketLastDisconnected, resetWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system';
import EphemeralStore from '@store/ephemeral_store';
import {isDMorGM} from '@utils/channel';
import {isTablet} from '@utils/helpers';
import {isCRTEnabled} from '@utils/thread';
@@ -188,6 +190,13 @@ async function doReconnect(serverUrl: string) {
// https://mattermost.atlassian.net/browse/MM-40098
fetchPostsSince(serverUrl, currentChannelId, lastDisconnectedAt);
const isChannelScreenMounted = EphemeralStore.getNavigationComponents().includes(Screens.CHANNEL);
if (isChannelScreenMounted || tabletDevice) {
markChannelAsRead(serverUrl, currentChannelId);
markChannelAsViewed(serverUrl, currentChannelId);
}
// defer fetching posts for unread channels on initial team
if (chData?.channels && chData.memberships) {
fetchPostsForUnreadChannels(serverUrl, chData.channels, chData.memberships, currentChannelId);

View File

@@ -10,12 +10,14 @@ import {createThreadFromNewPost, updateThread} from '@actions/local/thread';
import {fetchMyChannel, markChannelAsRead} from '@actions/remote/channel';
import {fetchPostAuthors, fetchPostById} from '@actions/remote/post';
import {fetchThread} from '@actions/remote/thread';
import {ActionType, Events} from '@constants';
import {ActionType, Events, Screens} from '@constants';
import DatabaseManager from '@database/manager';
import {getChannelById, getMyChannel} from '@queries/servers/channel';
import {getPostById} from '@queries/servers/post';
import {getCurrentChannelId, getCurrentUserId} from '@queries/servers/system';
import {getIsCRTEnabled} from '@queries/servers/thread';
import EphemeralStore from '@store/ephemeral_store';
import {isTablet} from '@utils/helpers';
import {isFromWebhook, isSystemMessage, shouldIgnorePost} from '@utils/post';
import type MyChannelModel from '@typings/database/models/servers/my_channel';
@@ -136,8 +138,14 @@ export async function handleNewPostEvent(serverUrl: string, msg: WebSocketMessag
} else if ((post.channel_id === currentChannelId)) { // TODO: THREADS && !viewingGlobalThreads) {
// Don't mark as read if we're in global threads screen
// the currentChannelId still refers to previously viewed channel
markAsViewed = false;
markAsRead = true;
const isChannelScreenMounted = EphemeralStore.getNavigationComponents().includes(Screens.CHANNEL);
const isTabletDevice = await isTablet();
if (isChannelScreenMounted || isTabletDevice) {
markAsViewed = false;
markAsRead = true;
}
}
}

View File

@@ -38,7 +38,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
color: changeOpacity(theme.sidebarText, 0.4),
},
iconActive: {
color: theme.sidebarTextActiveColor,
color: theme.sidebarText,
},
iconUnread: {
color: theme.sidebarUnreadText,
@@ -53,7 +53,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
justifyContent: 'center',
},
groupBoxActive: {
backgroundColor: changeOpacity(theme.sidebarTextActiveColor, 0.3),
backgroundColor: changeOpacity(theme.sidebarText, 0.3),
},
groupBoxUnread: {
backgroundColor: changeOpacity(theme.sidebarUnreadText, 0.3),
@@ -66,7 +66,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
...typography('Body', 75, 'SemiBold'),
},
groupActive: {
color: theme.sidebarTextActiveColor,
color: theme.sidebarText,
},
groupUnread: {
color: theme.sidebarUnreadText,

View File

@@ -46,12 +46,15 @@ Object {
>
<View
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"height": 40,
"paddingLeft": 2,
}
Array [
Object {
"alignItems": "center",
"flexDirection": "row",
"height": 40,
"paddingHorizontal": 20,
},
false,
]
}
testID="category.test_category.channel_list_item.channel.collapsed.false"
>
@@ -108,6 +111,7 @@ Object {
},
false,
false,
null,
]
}
testID="category.test_category.channel_list_item.channel.display_name"

View File

@@ -39,12 +39,15 @@ exports[`components/channel_list/categories/body/channel/item should match snaps
>
<View
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"height": 40,
"paddingLeft": 2,
}
Array [
Object {
"alignItems": "center",
"flexDirection": "row",
"height": 40,
"paddingHorizontal": 20,
},
false,
]
}
testID="channel_list_item.hello.collapsed.false"
>
@@ -124,6 +127,7 @@ exports[`components/channel_list/categories/body/channel/item should match snaps
},
false,
false,
null,
]
}
testID="channel_list_item.hello.display_name"

View File

@@ -22,7 +22,7 @@ import type MyChannelModel from '@typings/database/models/servers/my_channel';
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
container: {
flexDirection: 'row',
paddingLeft: 2,
paddingHorizontal: 20,
height: 40,
alignItems: 'center',
},
@@ -52,6 +52,17 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
mutedBadge: {
opacity: 0.4,
},
activeItem: {
backgroundColor: changeOpacity(theme.sidebarTextActiveColor, 0.1),
borderLeftColor: theme.sidebarTextActiveBorder,
borderLeftWidth: 5,
marginLeft: 0,
paddingLeft: 14,
},
textActive: {
color: theme.sidebarText,
},
}));
const textStyle = StyleSheet.create({
@@ -78,13 +89,14 @@ const ChannelListItem = ({channel, isActive, currentUserId, isMuted, myChannel,
const isOwnDirectMessage = (channel.type === General.DM_CHANNEL) && currentUserId === getUserIdFromChannelName(currentUserId, channel.name);
// Make it brighter if it's not muted, and highlighted or has unreads
const bright = !isMuted && (isActive || (myChannel && (myChannel.isUnread || myChannel.mentionsCount > 0)));
const isUnread = !isMuted && (myChannel && (myChannel.isUnread || myChannel.mentionsCount > 0));
const sharedValue = useSharedValue(collapsed && !bright);
const shouldCollapse = (collapsed && !isUnread) && !isActive;
const sharedValue = useSharedValue(shouldCollapse);
useEffect(() => {
sharedValue.value = collapsed && !bright;
}, [collapsed && !bright]);
sharedValue.value = shouldCollapse;
}, [shouldCollapse]);
const animatedStyle = useAnimatedStyle(() => {
return {
@@ -99,6 +111,7 @@ const ChannelListItem = ({channel, isActive, currentUserId, isMuted, myChannel,
switchToChannelById(serverUrl, myChannel.id);
}
}, [myChannel?.id, serverUrl]);
const membersCount = useMemo(() => {
if (channel.type === General.GM_CHANNEL) {
return channel.displayName.split(',').length;
@@ -107,11 +120,12 @@ const ChannelListItem = ({channel, isActive, currentUserId, isMuted, myChannel,
}, [channel.type, channel.displayName]);
const textStyles = useMemo(() => [
bright ? textStyle.bright : textStyle.regular,
isUnread ? textStyle.bright : textStyle.regular,
styles.text,
bright && styles.highlight,
isUnread && styles.highlight,
isMuted && styles.muted,
], [bright, styles, isMuted]);
isActive ? styles.textActive : null,
], [isUnread, styles, isMuted]);
let displayName = channel.displayName;
if (isOwnDirectMessage) {
@@ -126,7 +140,7 @@ const ChannelListItem = ({channel, isActive, currentUserId, isMuted, myChannel,
<Animated.View style={animatedStyle}>
<TouchableOpacity onPress={switchChannels}>
<View
style={styles.container}
style={[styles.container, isActive && styles.activeItem]}
testID={`${testID}.${channel.name}.collapsed.${collapsed && !isActive}`}
>
<ChannelIcon

View File

@@ -24,6 +24,8 @@ type Props = {
const styles = StyleSheet.create({
mainList: {
flex: 1,
marginLeft: -18,
marginRight: -20,
},
});
@@ -44,7 +46,12 @@ const Categories = ({categories, currentChannelId, currentUserId, currentTeamId,
const renderCategory = useCallback((data: {item: CategoryModel | string[]}) => {
if (Array.isArray(data.item)) {
return <UnreadCategories unreadChannels={unreadChannels}/>;
return (
<UnreadCategories
currentChannelId={currentChannelId}
unreadChannels={unreadChannels}
/>
);
}
return (

View File

@@ -24,6 +24,7 @@ exports[`components/channel_list/categories/header should match snapshot 1`] = `
Object {
"alignItems": "flex-start",
"flexDirection": "row",
"marginLeft": 16,
"marginTop": 12,
"paddingLeft": 2,
"paddingVertical": 8,

View File

@@ -19,6 +19,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
paddingVertical: 8,
marginTop: 12,
paddingLeft: 2,
marginLeft: 16,
flexDirection: 'row',
alignItems: 'flex-start',
},

View File

@@ -3,22 +3,31 @@
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {combineLatest, of as of$} from 'rxjs';
import {concatAll, map, switchMap} from 'rxjs/operators';
import {Preferences} from '@app/constants';
import {getPreferenceAsBool} from '@app/helpers/api/preference';
import {queryMyChannelUnreads} from '@app/queries/servers/channel';
import {getChannelById, queryMyChannelUnreads} from '@app/queries/servers/channel';
import {queryPreferencesByCategoryAndName} from '@app/queries/servers/preference';
import {queryCategoriesByTeamIds} from '@queries/servers/categories';
import {observeCurrentChannelId, observeCurrentUserId} from '@queries/servers/system';
import {observeCurrentChannelId, observeCurrentUserId, observeLastUnreadChannelId} from '@queries/servers/system';
import {getChannelsFromRelation} from './body';
import Categories from './categories';
import type {WithDatabaseArgs} from '@typings/database/database';
import type ChannelModel from '@typings/database/models/servers/channel';
import type PreferenceModel from '@typings/database/models/servers/preference';
type CA = [
a: Array<ChannelModel | null>,
b: ChannelModel | undefined,
]
const concatenateChannelsArray = ([a, b]: CA) => {
return of$(b ? a.filter((c) => c && c.id !== b.id).concat(b) : a);
};
type WithDatabaseProps = { currentTeamId: string } & WithDatabaseArgs
const enhanced = withObservables(
@@ -34,12 +43,24 @@ const enhanced = withObservables(
switchMap((prefs: PreferenceModel[]) => of$(getPreferenceAsBool(prefs, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS, false))),
);
const getC = (lastUnreadChannelId: string) => getChannelById(database, lastUnreadChannelId);
const unreadChannels = unreadsOnTop.pipe(switchMap((gU) => {
if (gU) {
return queryMyChannelUnreads(database, currentTeamId).observe().pipe(
const lastUnread = observeLastUnreadChannelId(database).pipe(
switchMap(getC),
);
const unreads = queryMyChannelUnreads(database, currentTeamId).observe().pipe(
map(getChannelsFromRelation),
concatAll(),
);
const combined = combineLatest([unreads, lastUnread]).pipe(
switchMap(concatenateChannelsArray),
);
return combined;
}
return of$([]);
}));

View File

@@ -19,7 +19,10 @@ describe('components/channel_list/categories/body', () => {
it('render without error', () => {
const wrapper = renderWithEverything(
<UnreadsCategory unreadChannels={[]}/>,
<UnreadsCategory
currentChannelId={''}
unreadChannels={[]}
/>,
{database},
);

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import React, {useCallback} from 'react';
import {useIntl} from 'react-intl';
import {FlatList, Text} from 'react-native';
@@ -17,26 +17,32 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
heading: {
color: changeOpacity(theme.sidebarText, 0.64),
...typography('Heading', 75),
paddingLeft: 5,
paddingTop: 10,
paddingLeft: 18,
paddingVertical: 8,
marginTop: 12,
},
}));
const renderItem = ({item}: {item: ChannelModel}) => {
return (
<ChannelListItem
channel={item}
isActive={true}
collapsed={false}
/>
);
};
type UnreadCategoriesProps = {
unreadChannels: ChannelModel[];
currentChannelId: string;
}
const UnreadCategories = ({unreadChannels}: {unreadChannels: ChannelModel[]}) => {
const UnreadCategories = ({unreadChannels, currentChannelId}: UnreadCategoriesProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
const intl = useIntl();
const renderItem = useCallback(({item}: {item: ChannelModel}) => {
return (
<ChannelListItem
channel={item}
isActive={item.id === currentChannelId}
collapsed={false}
/>
);
}, [currentChannelId]);
return (
<>
<Text

View File

@@ -23,7 +23,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
paddingRight: 20,
paddingTop: 10,
},
}));
type ChannelListProps = {

View File

@@ -48,6 +48,7 @@ export const MIGRATION_EVENTS = keyMirror({
export const SYSTEM_IDENTIFIERS = {
CONFIG: 'config',
CURRENT_CHANNEL_ID: 'currentChannelId',
LAST_UNREAD_CHANNEL_ID: 'lastUnreadChannelId',
CURRENT_TEAM_ID: 'currentTeamId',
CURRENT_USER_ID: 'currentUserId',
DATA_RETENTION_POLICIES: 'dataRetentionPolicies',

View File

@@ -12,6 +12,7 @@ import type SystemModel from '@typings/database/models/servers/system';
export type PrepareCommonSystemValuesArgs = {
config?: ClientConfig;
lastUnreadChannelId?: string;
currentChannelId?: string;
currentTeamId?: string;
currentUserId?: string;
@@ -83,6 +84,7 @@ export const getCommonSystemValues = async (serverDatabase: Database) => {
let currentChannelId = '';
let currentTeamId = '';
let currentUserId = '';
let lastUnreadChannelId = '';
systemRecords.forEach((systemRecord) => {
switch (systemRecord.id) {
case SYSTEM_IDENTIFIERS.CONFIG:
@@ -100,6 +102,9 @@ export const getCommonSystemValues = async (serverDatabase: Database) => {
case SYSTEM_IDENTIFIERS.LICENSE:
license = systemRecord.value as ClientLicense;
break;
case SYSTEM_IDENTIFIERS.LAST_UNREAD_CHANNEL_ID:
lastUnreadChannelId = systemRecord.value;
break;
}
});
@@ -107,6 +112,7 @@ export const getCommonSystemValues = async (serverDatabase: Database) => {
currentChannelId,
currentTeamId,
currentUserId,
lastUnreadChannelId,
config: (config as ClientConfig),
license: (license as ClientLicense),
};
@@ -274,7 +280,7 @@ export const patchTeamHistory = (operator: ServerDataOperator, value: string[],
export async function prepareCommonSystemValues(
operator: ServerDataOperator, values: PrepareCommonSystemValuesArgs): Promise<SystemModel[]> {
try {
const {config, currentChannelId, currentTeamId, currentUserId, license} = values;
const {config, lastUnreadChannelId, currentChannelId, currentTeamId, currentUserId, license} = values;
const systems: IdValue[] = [];
if (config !== undefined) {
systems.push({
@@ -290,6 +296,13 @@ export async function prepareCommonSystemValues(
});
}
if (lastUnreadChannelId !== undefined) {
systems.push({
id: SYSTEM_IDENTIFIERS.LAST_UNREAD_CHANNEL_ID,
value: lastUnreadChannelId,
});
}
if (currentUserId !== undefined) {
systems.push({
id: SYSTEM_IDENTIFIERS.CURRENT_USER_ID,
@@ -349,3 +362,23 @@ export async function setCurrentTeamAndChannelId(operator: ServerDataOperator, t
}
}
export const observeLastUnreadChannelId = (database: Database) => {
return querySystemValue(database, SYSTEM_IDENTIFIERS.LAST_UNREAD_CHANNEL_ID).observe().pipe(
switchMap((result) => (result.length ? result[0].observe() : of$({value: ''}))),
).pipe(
switchMap((model) => of$(model.value as string)),
);
};
export const queryLastUnreadChannelId = (database: Database) => {
return querySystemValue(database, SYSTEM_IDENTIFIERS.LAST_UNREAD_CHANNEL_ID);
};
export const getLastUnreadChannelId = async (serverDatabase: Database): Promise<string> => {
try {
const lastUnreadChannelId = (await queryLastUnreadChannelId(serverDatabase).fetch())[0];
return lastUnreadChannelId?.value || '';
} catch {
return '';
}
};

View File

@@ -4,6 +4,7 @@
import {useManagedConfig} from '@mattermost/react-native-emm';
import {useIsFocused, useRoute} from '@react-navigation/native';
import React from 'react';
import {StyleSheet} from 'react-native';
import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated';
import {Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
@@ -13,7 +14,6 @@ import TeamSidebar from '@components/team_sidebar';
import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device';
import Channel from '@screens/channel';
import {makeStyleSheetFromTheme} from '@utils/theme';
import Servers from './servers';
@@ -25,7 +25,8 @@ type ChannelProps = {
};
const edges: Edge[] = ['bottom', 'left', 'right'];
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
const styles = StyleSheet.create({
flex: {
flex: 1,
},
@@ -33,20 +34,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
flex: 1,
flexDirection: 'row',
},
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontFamily: 'OpenSans-SemiBold',
color: theme.centerChannelColor,
},
}));
});
const ChannelListScreen = (props: ChannelProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
const managedConfig = useManagedConfig<ManagedConfig>();
const isTablet = useIsTablet();