forked from Ivasoft/mattermost-mobile
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:
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -19,6 +19,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
paddingVertical: 8,
|
||||
marginTop: 12,
|
||||
paddingLeft: 2,
|
||||
marginLeft: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
|
||||
@@ -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$([]);
|
||||
}));
|
||||
|
||||
@@ -19,7 +19,10 @@ describe('components/channel_list/categories/body', () => {
|
||||
|
||||
it('render without error', () => {
|
||||
const wrapper = renderWithEverything(
|
||||
<UnreadsCategory unreadChannels={[]}/>,
|
||||
<UnreadsCategory
|
||||
currentChannelId={''}
|
||||
unreadChannels={[]}
|
||||
/>,
|
||||
{database},
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,7 +23,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
paddingRight: 20,
|
||||
paddingTop: 10,
|
||||
},
|
||||
|
||||
}));
|
||||
|
||||
type ChannelListProps = {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 '';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user