diff --git a/app/actions/remote/channel.ts b/app/actions/remote/channel.ts
index 290001f17..accc15c8e 100644
--- a/app/actions/remote/channel.ts
+++ b/app/actions/remote/channel.ts
@@ -14,6 +14,7 @@ import NetworkManager from '@init/network_manager';
import {prepareMyChannelsForTeam, queryChannelById, queryChannelByName, queryMyChannel} from '@queries/servers/channel';
import {queryCommonSystemValues, queryCurrentTeamId, queryCurrentUserId} from '@queries/servers/system';
import {prepareMyTeams, queryNthLastChannelFromTeam, queryMyTeamById, queryTeamById, queryTeamByName} from '@queries/servers/team';
+import {queryCurrentUser} from '@queries/servers/user';
import {getDirectChannelName} from '@utils/channel';
import {PERMALINK_GENERIC_TEAM_NAME_REDIRECT} from '@utils/url';
import {displayGroupMessageName, displayUsername} from '@utils/user';
@@ -267,9 +268,9 @@ export const fetchMyChannel = async (serverUrl: string, teamId: string, channelI
}
};
-export const fetchMissingSidebarInfo = async (serverUrl: string, directChannels: Channel[], locale?: string, teammateDisplayNameSetting?: string, exludeUserId?: string, fetchOnly = false) => {
+export const fetchMissingSidebarInfo = async (serverUrl: string, directChannels: Channel[], locale?: string, teammateDisplayNameSetting?: string, currentUserId?: string, fetchOnly = false) => {
const channelIds = directChannels.sort((a, b) => b.last_post_at - a.last_post_at).map((dc) => dc.id);
- const result = await fetchProfilesPerChannels(serverUrl, channelIds, exludeUserId, false);
+ const result = await fetchProfilesPerChannels(serverUrl, channelIds, currentUserId, false);
if (result.error) {
return {error: result.error};
}
@@ -280,7 +281,7 @@ export const fetchMissingSidebarInfo = async (serverUrl: string, directChannels:
result.data.forEach((data) => {
if (data.users) {
if (data.users.length > 1) {
- displayNameByChannel[data.channelId] = displayGroupMessageName(data.users, locale, teammateDisplayNameSetting, exludeUserId);
+ displayNameByChannel[data.channelId] = displayGroupMessageName(data.users, locale, teammateDisplayNameSetting, currentUserId);
} else {
displayNameByChannel[data.channelId] = displayUsername(data.users[0], locale, teammateDisplayNameSetting);
}
@@ -295,6 +296,15 @@ export const fetchMissingSidebarInfo = async (serverUrl: string, directChannels:
}
});
+ if (currentUserId) {
+ const ownDirectChannel = directChannels.find((dm) => dm.name === getDirectChannelName(currentUserId, currentUserId));
+ const database = DatabaseManager.serverDatabases[serverUrl]?.database;
+ if (ownDirectChannel && database) {
+ const currentUser = await queryCurrentUser(database);
+ ownDirectChannel.display_name = displayUsername(currentUser, locale, teammateDisplayNameSetting);
+ }
+ }
+
if (!fetchOnly) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (operator) {
diff --git a/app/components/channel_icon/dm_avatar/dm_avatar.tsx b/app/components/channel_icon/dm_avatar/dm_avatar.tsx
new file mode 100644
index 000000000..290962d25
--- /dev/null
+++ b/app/components/channel_icon/dm_avatar/dm_avatar.tsx
@@ -0,0 +1,37 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+
+import ProfilePicture from '@components/profile_picture';
+import {useTheme} from '@context/theme';
+import {makeStyleSheetFromTheme} from '@utils/theme';
+
+import type UserModel from '@typings/database/models/servers/user';
+
+type Props = {
+ author?: UserModel;
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
+ status: {
+ backgroundColor: theme.sidebarBg,
+ borderWidth: 0,
+ },
+}));
+
+const DmAvatar = ({author}: Props) => {
+ const theme = useTheme();
+ const style = getStyleSheet(theme);
+ return (
+
+ );
+};
+
+export default DmAvatar;
diff --git a/app/components/channel_icon/dm_avatar/index.ts b/app/components/channel_icon/dm_avatar/index.ts
new file mode 100644
index 000000000..0f2359f44
--- /dev/null
+++ b/app/components/channel_icon/dm_avatar/index.ts
@@ -0,0 +1,45 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {Q} 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 {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
+import {getUserIdFromChannelName} from '@utils/user';
+
+import DmAvatar from './dm_avatar';
+
+import type {WithDatabaseArgs} from '@typings/database/database';
+import type SystemModel from '@typings/database/models/servers/system';
+import type UserModel from '@typings/database/models/servers/user';
+
+const {SERVER: {USER, SYSTEM}} = MM_TABLES;
+const {CURRENT_USER_ID} = SYSTEM_IDENTIFIERS;
+
+const enhance = withObservables(['channelName'], ({channelName, database}: {channelName: string} & WithDatabaseArgs) => {
+ const currentUserId = database.get(SYSTEM).findAndObserve(CURRENT_USER_ID).pipe(
+ switchMap(({value}) => of$(value)),
+ );
+
+ const authorId = currentUserId.pipe(
+ switchMap((userId) => of$(getUserIdFromChannelName(userId, channelName))),
+ );
+
+ const author = authorId.pipe(
+ switchMap((id) => {
+ return database.get(USER).query(Q.where('id', id)).observe().pipe(
+ // eslint-disable-next-line max-nested-callbacks
+ switchMap((users) => (users[0] ? of$(users[0]) : of$(undefined))),
+ );
+ }),
+ );
+
+ return {
+ author,
+ };
+});
+
+export default withDatabase(enhance(DmAvatar));
diff --git a/app/components/channel_icon/index.tsx b/app/components/channel_icon/index.tsx
index 6477e3624..fcd535333 100644
--- a/app/components/channel_icon/index.tsx
+++ b/app/components/channel_icon/index.tsx
@@ -8,6 +8,9 @@ import CompassIcon from '@components/compass_icon';
import General from '@constants/general';
import {useTheme} from '@context/theme';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
+import {typography} from '@utils/typography';
+
+import DmAvatar from './dm_avatar';
type ChannelIconProps = {
hasDraft?: boolean;
@@ -16,6 +19,7 @@ type ChannelIconProps = {
isInfo?: boolean;
isUnread?: boolean;
membersCount?: number;
+ name: string;
shared: boolean;
size?: number;
style?: StyleProp;
@@ -58,8 +62,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
},
group: {
color: theme.sidebarText,
- fontSize: 10,
- fontFamily: 'OpenSans-SemiBold',
+ ...typography('Body', 75, 'SemiBold'),
},
groupActive: {
color: theme.sidebarTextActiveColor,
@@ -80,6 +83,7 @@ const ChannelIcon = ({
isInfo = false,
isUnread = false,
membersCount = 0,
+ name,
shared,
size = 12,
style,
@@ -159,10 +163,9 @@ const ChannelIcon = ({
);
} else if (type === General.GM_CHANNEL) {
const fontSize = size - 12;
- const boxSize = size - 4;
icon = (
);
} else if (type === General.DM_CHANNEL) {
- //todo: Implement ProfilePicture component
+ icon = ();
}
return (
diff --git a/app/components/channel_list/__snapshots__/index.test.tsx.snap b/app/components/channel_list/__snapshots__/index.test.tsx.snap
index d0c7d8347..49acd1e40 100644
--- a/app/components/channel_list/__snapshots__/index.test.tsx.snap
+++ b/app/components/channel_list/__snapshots__/index.test.tsx.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`components/channel_list should match snapshot 1`] = `
+exports[`components/channel_list should render channels error 1`] = `
- Couldn't load
+ Couldn't load Test Team!
+
+
+ There was a problem loading content for this team.
+
+
+
+
+ Retry
+
+
+
+
+
+`;
+
+exports[`components/channel_list should render team error 1`] = `
+
+
+
+
+
+
+
+
+
+
+ Threads
+
+
+
+
+
+
+
+
+
+ Couldn't load categories for
-
-
-
-
-
-
-
- Threads
-
-
-
-
-
-
-
-
- MY FIRST CATEGORY
-
-
-
-
-
-
-
- Just a channel
-
-
-
-
-
-
-
- Highlighted!!!
-
-
-
-
-
-
-
-
- ANOTHER CAT
-
-
-
-
-
-
-
- Just a channel
-
-
-
-
-
-
-
- Highlighted!!!
-
-
-
-
-
-
-
-`;
diff --git a/app/components/channel_list/categories/body/__snapshots__/category_body.test.tsx.snap b/app/components/channel_list/categories/body/__snapshots__/category_body.test.tsx.snap
new file mode 100644
index 000000000..7221c7a67
--- /dev/null
+++ b/app/components/channel_list/categories/body/__snapshots__/category_body.test.tsx.snap
@@ -0,0 +1,123 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`components/channel_list/categories/body should match snapshot 1`] = `
+Object {
+ "children": Array [
+
+
+
+
+
+
+
+
+
+ Channel
+
+
+
+
+
+ ,
+ ],
+ "props": Object {
+ "data": Anything,
+ "getItem": [Function],
+ "getItemCount": [Function],
+ "invertStickyHeaders": undefined,
+ "keyExtractor": [Function],
+ "onContentSizeChange": [Function],
+ "onLayout": [Function],
+ "onMomentumScrollBegin": [Function],
+ "onMomentumScrollEnd": [Function],
+ "onScroll": [Function],
+ "onScrollBeginDrag": [Function],
+ "onScrollEndDrag": [Function],
+ "removeClippedSubviews": false,
+ "renderItem": [Function],
+ "scrollEventThrottle": 50,
+ "stickyHeaderIndices": Array [],
+ "style": undefined,
+ "viewabilityConfigCallbackPairs": Array [],
+ },
+ "type": "RCTScrollView",
+}
+`;
diff --git a/app/components/channel_list/categories/body/__snapshots__/index.test.tsx.snap b/app/components/channel_list/categories/body/__snapshots__/index.test.tsx.snap
deleted file mode 100644
index 2014360a3..000000000
--- a/app/components/channel_list/categories/body/__snapshots__/index.test.tsx.snap
+++ /dev/null
@@ -1,127 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Category Body Component should match snapshot 1`] = `
-
-
-
-
-
-
- Just a channel
-
-
-
-
-
-
-
- Highlighted!!!
-
-
-
-
-
-`;
diff --git a/app/components/channel_list/categories/body/category_body.test.tsx b/app/components/channel_list/categories/body/category_body.test.tsx
new file mode 100644
index 000000000..7a0027400
--- /dev/null
+++ b/app/components/channel_list/categories/body/category_body.test.tsx
@@ -0,0 +1,64 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {Database, Q} from '@nozbe/watermelondb';
+import React from 'react';
+
+import {MM_TABLES} from '@constants/database';
+import {renderWithEverything} from '@test/intl-test-helper';
+import TestHelper from '@test/test_helper';
+
+import CategoryBody from './category_body';
+
+import type CategoryModel from '@typings/database/models/servers/category';
+import type CategoryChannelModel from '@typings/database/models/servers/category_channel';
+import type ChannelModel from '@typings/database/models/servers/channel';
+import type MyChannelModel from '@typings/database/models/servers/my_channel';
+
+const {SERVER: {CATEGORY, CATEGORY_CHANNEL, CHANNEL, MY_CHANNEL}} = MM_TABLES;
+
+describe('components/channel_list/categories/body', () => {
+ let database: Database;
+ let category: CategoryModel;
+ let categoryChannels: CategoryChannelModel[];
+ let myChannels: MyChannelModel[];
+ let channels: ChannelModel[];
+
+ beforeAll(async () => {
+ const server = await TestHelper.setupServerDatabase();
+ database = server.database;
+
+ const categories = await database.get(CATEGORY).query(
+ Q.take(1),
+ ).fetch();
+
+ category = categories[0];
+
+ categoryChannels = await database.get(CATEGORY_CHANNEL).query(
+ Q.where('category_id', category.id),
+ ).fetch();
+
+ const channelIds = await database.get(CHANNEL).query(
+ Q.on(CATEGORY_CHANNEL, 'category_id', category.id),
+ ).fetchIds();
+
+ myChannels = await database.get(MY_CHANNEL).query(
+ Q.where('id', Q.oneOf(channelIds)),
+ ).fetch();
+ });
+
+ it('should match snapshot', () => {
+ const wrapper = renderWithEverything(
+ ,
+ {database},
+ );
+ expect(wrapper.toJSON()).toMatchSnapshot({
+ props: {data: expect.anything()},
+ });
+ });
+});
diff --git a/app/components/channel_list/categories/body/category_body.tsx b/app/components/channel_list/categories/body/category_body.tsx
new file mode 100644
index 000000000..806324c4c
--- /dev/null
+++ b/app/components/channel_list/categories/body/category_body.tsx
@@ -0,0 +1,47 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React, {useMemo} from 'react';
+import {FlatList} from 'react-native';
+
+import ChannelListItem from './channel';
+
+import type CategoryModel from '@typings/database/models/servers/category';
+import type CategoryChannelModel from '@typings/database/models/servers/category_channel';
+import type ChannelModel from '@typings/database/models/servers/channel';
+import type MyChannelModel from '@typings/database/models/servers/my_channel';
+
+type Props = {
+ category: CategoryModel;
+ channels: ChannelModel[];
+ myChannels: MyChannelModel[];
+ categoryChannels: CategoryChannelModel[];
+};
+
+const ChannelItem = ({item}: {item: string}) => {
+ return (
+
+ );
+};
+
+const CategoryBody = ({category, categoryChannels, channels, myChannels}: Props) => {
+ const data: string[] = useMemo(() => {
+ switch (category.sorting) {
+ case 'alpha':
+ return channels.map((c) => c.id);
+ case 'manual':
+ return categoryChannels.map((cc) => cc.channelId);
+ default:
+ return myChannels.map((m) => m.id);
+ }
+ }, [category.sorting, categoryChannels, channels, myChannels]);
+
+ return (
+
+ );
+};
+
+export default CategoryBody;
diff --git a/app/components/channel_list/categories/body/channel/__snapshots__/channel_list_item.test.tsx.snap b/app/components/channel_list/categories/body/channel/__snapshots__/channel_list_item.test.tsx.snap
new file mode 100644
index 000000000..4cda3d4c9
--- /dev/null
+++ b/app/components/channel_list/categories/body/channel/__snapshots__/channel_list_item.test.tsx.snap
@@ -0,0 +1,114 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`components/channel_list/categories/body/channel/item should match snapshot 1`] = `
+
+
+
+
+
+
+ 1
+
+
+
+
+ Hello!
+
+
+
+
+`;
diff --git a/app/components/channel_list/categories/body/channel/__snapshots__/index.test.tsx.snap b/app/components/channel_list/categories/body/channel/__snapshots__/index.test.tsx.snap
deleted file mode 100644
index 55ea35f10..000000000
--- a/app/components/channel_list/categories/body/channel/__snapshots__/index.test.tsx.snap
+++ /dev/null
@@ -1,43 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Category Channel List Item should match snapshot 1`] = `
-
-
-
- Channel Name
-
-
-`;
diff --git a/app/components/channel_list/categories/body/channel/channel_list_item.test.tsx b/app/components/channel_list/categories/body/channel/channel_list_item.test.tsx
new file mode 100644
index 000000000..1eeca24aa
--- /dev/null
+++ b/app/components/channel_list/categories/body/channel/channel_list_item.test.tsx
@@ -0,0 +1,41 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {Database, Q} from '@nozbe/watermelondb';
+import React from 'react';
+
+import {MM_TABLES} from '@constants/database';
+import {renderWithIntlAndTheme} from '@test/intl-test-helper';
+import TestHelper from '@test/test_helper';
+
+import ChannelListItem from './channel_list_item';
+
+import type MyChannelModel from '@typings/database/models/servers/my_channel';
+
+describe('components/channel_list/categories/body/channel/item', () => {
+ let database: Database;
+ let myChannel: MyChannelModel;
+
+ beforeAll(async () => {
+ const server = await TestHelper.setupServerDatabase();
+ database = server.database;
+
+ const myChannels = await database.get(MM_TABLES.SERVER.MY_CHANNEL).query(
+ Q.take(1),
+ ).fetch();
+
+ myChannel = myChannels[0];
+ });
+
+ it('should match snapshot', () => {
+ const wrapper = renderWithIntlAndTheme(
+ ,
+ );
+
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/channel_list/categories/body/channel/channel_list_item.tsx b/app/components/channel_list/categories/body/channel/channel_list_item.tsx
new file mode 100644
index 000000000..e8af22910
--- /dev/null
+++ b/app/components/channel_list/categories/body/channel/channel_list_item.tsx
@@ -0,0 +1,106 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React, {useMemo} from 'react';
+import {useIntl} from 'react-intl';
+import {StyleSheet, Text, View} from 'react-native';
+import {TouchableOpacity} from 'react-native-gesture-handler';
+
+import {switchToChannelById} from '@actions/remote/channel';
+import ChannelIcon from '@components/channel_icon';
+import {General} from '@constants';
+import {useServerUrl} from '@context/server';
+import {useTheme} from '@context/theme';
+import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
+import {typography} from '@utils/typography';
+
+import type ChannelModel from '@typings/database/models/servers/channel';
+import type MyChannelModel from '@typings/database/models/servers/my_channel';
+
+const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
+ container: {
+ flexDirection: 'row',
+ marginBottom: 8,
+ paddingLeft: 2,
+ paddingVertical: 4,
+ },
+ icon: {
+ fontSize: 24,
+ lineHeight: 28,
+ color: changeOpacity(theme.sidebarText, 0.72),
+ },
+ text: {
+ marginTop: -1,
+ color: changeOpacity(theme.sidebarText, 0.72),
+ paddingLeft: 12,
+ flex: 1,
+ },
+ highlight: {
+ color: theme.sidebarText,
+ },
+}));
+
+const textStyle = StyleSheet.create({
+ bright: typography('Body', 200, 'SemiBold'),
+ regular: typography('Body', 200, 'Regular'),
+});
+
+type Props = {
+ channel: Pick;
+ isOwnDirectMessage: boolean;
+ myChannel: MyChannelModel;
+}
+
+const ChannelListItem = ({channel, isOwnDirectMessage, myChannel}: Props) => {
+ const {formatMessage} = useIntl();
+ const theme = useTheme();
+ const styles = getStyleSheet(theme);
+ const serverUrl = useServerUrl();
+
+ // Make it brighter if it's highlighted, or has unreads
+ const bright = myChannel.isUnread || myChannel.mentionsCount > 0;
+
+ const switchChannels = () => switchToChannelById(serverUrl, myChannel.id);
+ const membersCount = useMemo(() => {
+ if (channel.type === General.GM_CHANNEL) {
+ return channel.displayName?.split(',').length;
+ }
+
+ return 0;
+ }, [channel.type, channel.displayName]);
+
+ const textStyles = useMemo(() => [
+ bright ? textStyle.bright : textStyle.regular,
+ styles.text,
+ bright && styles.highlight,
+ ], [bright, styles]);
+
+ let displayName = channel.displayName;
+ if (isOwnDirectMessage) {
+ displayName = formatMessage({id: 'channel_header.directchannel.you', defaultMessage: '{displayName} (you)'}, {displayName});
+ }
+
+ return (
+
+
+
+
+ {displayName}
+
+
+
+
+ );
+};
+
+export default ChannelListItem;
diff --git a/app/components/channel_list/categories/body/channel/index.test.tsx b/app/components/channel_list/categories/body/channel/index.test.tsx
deleted file mode 100644
index ccb818945..000000000
--- a/app/components/channel_list/categories/body/channel/index.test.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-import React from 'react';
-
-import {renderWithIntlAndTheme} from '@test/intl-test-helper';
-
-import ChannelItem from './index';
-
-test('Category Channel List Item should match snapshot', () => {
- const {toJSON} = renderWithIntlAndTheme(
- ,
- );
-
- expect(toJSON()).toMatchSnapshot();
-});
diff --git a/app/components/channel_list/categories/body/channel/index.ts b/app/components/channel_list/categories/body/channel/index.ts
new file mode 100644
index 000000000..d0cdfc494
--- /dev/null
+++ b/app/components/channel_list/categories/body/channel/index.ts
@@ -0,0 +1,54 @@
+// 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 {combineLatest, of as of$} from 'rxjs';
+import {switchMap} from 'rxjs/operators';
+
+import {General} from '@constants';
+import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
+import {getUserIdFromChannelName} from '@utils/user';
+
+import ChannelListItem from './channel_list_item';
+
+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 SystemModel from '@typings/database/models/servers/system';
+
+const {SERVER: {MY_CHANNEL, SYSTEM}} = MM_TABLES;
+const {CURRENT_USER_ID} = SYSTEM_IDENTIFIERS;
+
+const enhance = withObservables(['channelId'], ({channelId, database}: {channelId: string} & WithDatabaseArgs) => {
+ const myChannel = database.get(MY_CHANNEL).findAndObserve(channelId);
+ const currentUserId = database.get(SYSTEM).findAndObserve(CURRENT_USER_ID).pipe(
+ switchMap(({value}) => of$(value)),
+ );
+
+ const channel = myChannel.pipe(switchMap((my) => my.channel.observe()));
+ const isOwnDirectMessage = combineLatest([currentUserId, channel]).pipe(
+ switchMap(([userId, ch]) => {
+ if (ch?.type === General.DM_CHANNEL) {
+ const teammateId = getUserIdFromChannelName(userId, ch.name);
+ return of$(userId === teammateId);
+ }
+
+ return of$(false);
+ }),
+ );
+ return {
+ isOwnDirectMessage,
+ myChannel,
+ channel: channel.pipe(
+ switchMap((c: ChannelModel) => of$({
+ displayName: c.displayName,
+ name: c.name,
+ shared: c.shared,
+ type: c.type,
+ })),
+ ),
+ };
+});
+
+export default withDatabase(enhance(ChannelListItem));
diff --git a/app/components/channel_list/categories/body/channel/index.tsx b/app/components/channel_list/categories/body/channel/index.tsx
deleted file mode 100644
index ae3b09e8d..000000000
--- a/app/components/channel_list/categories/body/channel/index.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-import React from 'react';
-import {StyleSheet, Text, View} from 'react-native';
-
-import CompassIcon from '@components/compass_icon';
-import {useTheme} from '@context/theme';
-import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
-import {typography} from '@utils/typography';
-
-const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
- container: {
- paddingVertical: 4,
- flexDirection: 'row',
- },
- icon: {
- fontSize: 24,
- lineHeight: 28,
- color: changeOpacity(theme.sidebarText, 0.72),
- },
- text: {
- marginTop: 1,
- color: changeOpacity(theme.sidebarText, 0.72),
- paddingLeft: 12,
- },
- highlight: {
- color: theme.sidebarText,
- },
-}));
-
-const textStyle = StyleSheet.create({
- bright: typography('Body', 200, 'SemiBold'),
- regular: typography('Body', 200, 'Regular'),
-});
-
-type Props = {
- unreadCount?: number;
- highlight?: boolean;
- name: string;
- icon: string;
-}
-
-const ChannelListItem = (props: Props) => {
- const theme = useTheme();
- const styles = getStyleSheet(theme);
-
- const {unreadCount, highlight, name, icon} = props;
-
- // Make it brighter if it's highlighted, or has unreads
- const bright = highlight || (unreadCount && unreadCount > 0);
-
- return (
-
- {icon && (
-
- )}
-
- {name}
-
-
-
- );
-};
-
-export default ChannelListItem;
diff --git a/app/components/channel_list/categories/body/index.test.tsx b/app/components/channel_list/categories/body/index.test.tsx
deleted file mode 100644
index 612428cc1..000000000
--- a/app/components/channel_list/categories/body/index.test.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-import React from 'react';
-
-import {renderWithIntlAndTheme} from '@test/intl-test-helper';
-
-import CategoryBody from './index';
-
-const channels: TempoChannel[] = [
- {id: '1', name: 'Just a channel'},
- {id: '2', name: 'Highlighted!!!', highlight: true},
-];
-
-test('Category Body Component should match snapshot', () => {
- const {toJSON} = renderWithIntlAndTheme(
- ,
- );
-
- expect(toJSON()).toMatchSnapshot();
-});
diff --git a/app/components/channel_list/categories/body/index.ts b/app/components/channel_list/categories/body/index.ts
new file mode 100644
index 000000000..241cb4a35
--- /dev/null
+++ b/app/components/channel_list/categories/body/index.ts
@@ -0,0 +1,17 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import withObservables from '@nozbe/with-observables';
+
+import CategoryBody from './category_body';
+
+import type CategoryModel from '@typings/database/models/servers/category';
+
+const withCategory = withObservables(['category'], ({category}: {category: CategoryModel}) => ({
+ category,
+ categoryChannels: category.categoryChannelsBySortOrder.observeWithColumns(['sort_order']),
+ myChannels: category.myChannels.observeWithColumns(['last_post_at']),
+ channels: category.channels.observeWithColumns(['display_name']),
+}));
+
+export default withCategory(CategoryBody);
diff --git a/app/components/channel_list/categories/body/index.tsx b/app/components/channel_list/categories/body/index.tsx
deleted file mode 100644
index 3ac9d8b50..000000000
--- a/app/components/channel_list/categories/body/index.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-import React from 'react';
-import {FlatList} from 'react-native';
-
-import ChannelListItem from './channel';
-
-const renderChannelItem = (data: { item: TempoChannel }) => {
- return (
-
- );
-};
-
-type Props = {
- channels: TempoChannel[];
-};
-
-const CategoryBody = (props: Props) => {
- return (
-
- );
-};
-
-export default CategoryBody;
diff --git a/app/components/channel_list/categories/categories.test.tsx b/app/components/channel_list/categories/categories.test.tsx
new file mode 100644
index 000000000..a9efa8699
--- /dev/null
+++ b/app/components/channel_list/categories/categories.test.tsx
@@ -0,0 +1,27 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import Database from '@nozbe/watermelondb/Database';
+import React from 'react';
+
+import {renderWithEverything} from '@test/intl-test-helper';
+import TestHelper from '@test/test_helper';
+
+import Categories from './';
+
+describe('components/channel_list/categories', () => {
+ let database: Database;
+ beforeAll(async () => {
+ const server = await TestHelper.setupServerDatabase();
+ database = server.database;
+ });
+
+ it('render without error', () => {
+ const wrapper = renderWithEverything(
+ ,
+ {database},
+ );
+
+ expect(wrapper.toJSON()).toBeTruthy();
+ });
+});
diff --git a/app/components/channel_list/categories/categories.tsx b/app/components/channel_list/categories/categories.tsx
new file mode 100644
index 000000000..8ab4e14e7
--- /dev/null
+++ b/app/components/channel_list/categories/categories.tsx
@@ -0,0 +1,51 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+import {FlatList, StyleSheet} from 'react-native';
+
+import CategoryBody from './body';
+import LoadCategoriesError from './error';
+import CategoryHeader from './header';
+
+import type {CategoryModel} from '@database/models/server';
+
+type Props = {
+ categories: CategoryModel[];
+}
+
+const styles = StyleSheet.create({
+ flex: {
+ flex: 1,
+ },
+});
+
+const renderCategory = (data: {item: CategoryModel}) => {
+ return (
+ <>
+
+
+ >
+ );
+};
+
+const Categories = (props: Props) => {
+ if (!props.categories.length) {
+ return ;
+ }
+
+ // Sort Categories
+ props.categories.sort((a, b) => a.sortOrder - b.sortOrder);
+
+ return (
+
+ );
+};
+
+export default Categories;
diff --git a/app/components/channel_list/categories/error.tsx b/app/components/channel_list/categories/error.tsx
new file mode 100644
index 000000000..1866d27c8
--- /dev/null
+++ b/app/components/channel_list/categories/error.tsx
@@ -0,0 +1,36 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React, {useCallback, useState} from 'react';
+import {useIntl} from 'react-intl';
+
+import {retryInitialTeamAndChannel} from '@actions/remote/retry';
+import LoadingError from '@components/channel_list/loading_error';
+import {useServerDisplayName, useServerUrl} from '@context/server';
+
+const LoadCategoriesError = () => {
+ const {formatMessage} = useIntl();
+ const serverUrl = useServerUrl();
+ const serverName = useServerDisplayName();
+ const [loading, setLoading] = useState(false);
+
+ const onRetryTeams = useCallback(async () => {
+ setLoading(true);
+ const {error} = await retryInitialTeamAndChannel(serverUrl);
+
+ if (error) {
+ setLoading(false);
+ }
+ }, []);
+
+ return (
+
+ );
+};
+
+export default LoadCategoriesError;
diff --git a/app/components/channel_list/categories/header/__snapshots__/index.test.tsx.snap b/app/components/channel_list/categories/header/__snapshots__/header.test.tsx.snap
similarity index 75%
rename from app/components/channel_list/categories/header/__snapshots__/index.test.tsx.snap
rename to app/components/channel_list/categories/header/__snapshots__/header.test.tsx.snap
index 2baacfda8..12a68fea7 100644
--- a/app/components/channel_list/categories/header/__snapshots__/index.test.tsx.snap
+++ b/app/components/channel_list/categories/header/__snapshots__/header.test.tsx.snap
@@ -1,10 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Category Header Component should match snapshot 1`] = `
+exports[`components/channel_list/categories/header should match snapshot 1`] = `
- CAT HEADING
+ TEST CATEGORY
`;
diff --git a/app/components/channel_list/categories/header/header.test.tsx b/app/components/channel_list/categories/header/header.test.tsx
new file mode 100644
index 000000000..bb0dc5e89
--- /dev/null
+++ b/app/components/channel_list/categories/header/header.test.tsx
@@ -0,0 +1,38 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {Database, Q} from '@nozbe/watermelondb';
+import React from 'react';
+
+import {MM_TABLES} from '@constants/database';
+import {renderWithIntlAndTheme} from '@test/intl-test-helper';
+import TestHelper from '@test/test_helper';
+
+import CategoryHeader from './header';
+
+import type CategoryModel from '@typings/database/models/servers/category';
+
+describe('components/channel_list/categories/header', () => {
+ let database: Database;
+ let category: CategoryModel;
+ beforeAll(async () => {
+ const server = await TestHelper.setupServerDatabase();
+
+ database = server.database;
+ const categories = await database.get(MM_TABLES.SERVER.CATEGORY).query(
+ Q.take(1),
+ ).fetch();
+
+ category = categories[0];
+ });
+
+ it('should match snapshot', () => {
+ const wrapper = renderWithIntlAndTheme(
+ ,
+ );
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/channel_list/categories/header/index.tsx b/app/components/channel_list/categories/header/header.tsx
similarity index 69%
rename from app/components/channel_list/categories/header/index.tsx
rename to app/components/channel_list/categories/header/header.tsx
index c2741dafa..89adf8ffe 100644
--- a/app/components/channel_list/categories/header/index.tsx
+++ b/app/components/channel_list/categories/header/header.tsx
@@ -8,10 +8,13 @@ import {useTheme} from '@context/theme';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
+import type CategoryModel from '@typings/database/models/servers/category';
+
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
container: {
paddingVertical: 8,
marginTop: 12,
+ paddingLeft: 2,
},
heading: {
color: changeOpacity(theme.sidebarText, 0.64),
@@ -20,17 +23,23 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
}));
type Props = {
- heading: string;
+ category: CategoryModel;
+ hasChannels: boolean;
}
-const CategoryHeader = (props: Props) => {
+const CategoryHeader = ({category, hasChannels}: Props) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
+ // Hide favs if empty
+ if (!hasChannels && category.type === 'favorites') {
+ return (null);
+ }
+
return (
- {props.heading.toUpperCase()}
+ {category.displayName.toUpperCase()}
);
diff --git a/app/components/channel_list/categories/header/index.test.tsx b/app/components/channel_list/categories/header/index.test.tsx
deleted file mode 100644
index 231e446f9..000000000
--- a/app/components/channel_list/categories/header/index.test.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-import React from 'react';
-
-import {renderWithIntlAndTheme} from '@test/intl-test-helper';
-
-import Header from './index';
-
-test('Category Header Component should match snapshot', () => {
- const {toJSON} = renderWithIntlAndTheme(
- ,
- );
-
- expect(toJSON()).toMatchSnapshot();
-});
diff --git a/app/components/channel_list/categories/header/index.ts b/app/components/channel_list/categories/header/index.ts
new file mode 100644
index 000000000..c543400d9
--- /dev/null
+++ b/app/components/channel_list/categories/header/index.ts
@@ -0,0 +1,15 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import withObservables from '@nozbe/with-observables';
+
+import CategoryHeader from './header';
+
+import type CategoryModel from '@typings/database/models/servers/category';
+
+const enhanced = withObservables(['category'], ({category}: {category: CategoryModel}) => ({
+ category,
+ hasChannels: category.hasChannels,
+}));
+
+export default enhanced(CategoryHeader);
diff --git a/app/components/channel_list/categories/index.test.tsx b/app/components/channel_list/categories/index.test.tsx
deleted file mode 100644
index b5804400c..000000000
--- a/app/components/channel_list/categories/index.test.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-import React from 'react';
-
-import {renderWithEverything} from '@test/intl-test-helper';
-import TestHelper from '@test/test_helper';
-
-import Category from './index';
-
-import type Database from '@nozbe/watermelondb/Database';
-
-const channels: TempoChannel[] = [
- {id: '1', name: 'Just a channel'},
- {id: '2', name: 'Highlighted!!!', highlight: true},
-];
-
-const categories: TempoCategory[] = [
- {id: '1', title: 'My first Category', channels},
- {id: '2', title: 'Another cat', channels},
-];
-
-describe('Category List Component ', () => {
- let database: Database | undefined;
- beforeAll(async () => {
- const server = await TestHelper.setupServerDatabase();
- database = server.database;
- });
-
- test('should match snapshot', () => {
- const {toJSON} = renderWithEverything(
- ,
- {database},
- );
-
- expect(toJSON()).toMatchSnapshot();
- });
-});
diff --git a/app/components/channel_list/categories/index.ts b/app/components/channel_list/categories/index.ts
new file mode 100644
index 000000000..d169db261
--- /dev/null
+++ b/app/components/channel_list/categories/index.ts
@@ -0,0 +1,31 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {Q} from '@nozbe/watermelondb';
+import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
+import withObservables from '@nozbe/with-observables';
+
+import {MM_TABLES} from '@constants/database';
+
+import Categories from './categories';
+
+import type {WithDatabaseArgs} from '@typings/database/database';
+import type CategoryModel from '@typings/database/models/servers/category';
+
+const {SERVER: {CATEGORY}} = MM_TABLES;
+
+type WithDatabaseProps = {currentTeamId: string } & WithDatabaseArgs
+
+const enhanced = withObservables(
+ ['currentTeamId'],
+ ({currentTeamId, database}: WithDatabaseProps) => {
+ const categories = database.get(CATEGORY).query(
+ Q.where('team_id', currentTeamId),
+ ).observeWithColumns(['sort_order']);
+
+ return {
+ categories,
+ };
+ });
+
+export default withDatabase(enhanced(Categories));
diff --git a/app/components/channel_list/categories/index.tsx b/app/components/channel_list/categories/index.tsx
deleted file mode 100644
index 82927dd21..000000000
--- a/app/components/channel_list/categories/index.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-import React from 'react';
-import {FlatList, StyleSheet} from 'react-native';
-
-import ThreadsButton from '../threads';
-
-import CategoryBody from './body';
-import CategoryHeader from './header';
-
-type Props = {
- categories: TempoCategory[];
-}
-
-const styles = StyleSheet.create({
- flex: {
- flex: 1,
- },
-});
-
-const renderCategory = (data: {item: TempoCategory}) => (
- <>
-
-
- >
-);
-
-const Categories = (props: Props) => {
- return (
-
- );
-};
-
-export default Categories;
diff --git a/app/components/channel_list/index.test.tsx b/app/components/channel_list/index.test.tsx
index fdccc6eb7..6533469c6 100644
--- a/app/components/channel_list/index.test.tsx
+++ b/app/components/channel_list/index.test.tsx
@@ -10,7 +10,7 @@ import {TeamModel} from '@database/models/server';
import {renderWithEverything} from '@test/intl-test-helper';
import TestHelper from '@test/test_helper';
-import ChannelsList from './index';
+import ChannelsList from './';
describe('components/channel_list', () => {
let database: Database;
@@ -26,12 +26,44 @@ describe('components/channel_list', () => {
});
});
- it('should match snapshot', () => {
+ it('should render', () => {
const wrapper = renderWithEverything(
+ ,
+ {database},
+ );
+ expect(wrapper.toJSON()).toBeTruthy();
+ });
+
+ it('should render team error', () => {
+ const wrapper = renderWithEverything(
+
+
+ ,
+ {database},
+ );
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('should render channels error', () => {
+ const wrapper = renderWithEverything(
+
+
,
{database},
diff --git a/app/components/channel_list/index.tsx b/app/components/channel_list/index.tsx
index 0dc649589..61013b9bc 100644
--- a/app/components/channel_list/index.tsx
+++ b/app/components/channel_list/index.tsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import React, {useEffect, useState} from 'react';
+import React, {useEffect} from 'react';
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import {TABLET_SIDEBAR_WIDTH, TEAM_SIDEBAR_WIDTH} from '@constants/view';
@@ -13,38 +13,28 @@ import ChannelListHeader from './header';
import LoadChannelsError from './load_channels_error';
import LoadTeamsError from './load_teams_error';
import SearchField from './search';
-
-// import Loading from '@components/loading';
-
-const channels: TempoChannel[] = [
- {id: '1', name: 'Just a channel'},
- {id: '2', name: 'A Highlighted Channel!!!', highlight: true},
- {id: '3', name: 'And a longer channel name.'},
-];
-
-const categories: TempoCategory[] = [
- {id: '1', title: 'My first Category', channels},
- {id: '2', title: 'Another Cat', channels},
-];
+import Threads from './threads';
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
container: {
flex: 1,
backgroundColor: theme.sidebarBg,
- paddingHorizontal: 20,
+ paddingLeft: 18,
+ paddingRight: 20,
paddingVertical: 10,
},
}));
type ChannelListProps = {
+ channelsCount: number;
currentTeamId?: string;
iconPad?: boolean;
isTablet: boolean;
teamsCount: number;
}
-const ChannelList = ({currentTeamId, iconPad, isTablet, teamsCount}: ChannelListProps) => {
+const ChannelList = ({channelsCount, currentTeamId, iconPad, isTablet, teamsCount}: ChannelListProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
const tabletWidth = useSharedValue(TABLET_SIDEBAR_WIDTH);
@@ -64,26 +54,28 @@ const ChannelList = ({currentTeamId, iconPad, isTablet, teamsCount}: ChannelList
}
}, [isTablet, teamsCount]);
- const [showCats, setShowCats] = useState(true);
let content;
+
if (!currentTeamId) {
content = ();
- } else if (showCats) {
+ } else if (channelsCount < 1) {
+ content = ();
+ } else {
content = (
<>
-
+
+
>
);
- } else {
- content = ();
}
return (
setShowCats(!showCats)}
/>
{content}
diff --git a/app/components/channel_list/search/index.tsx b/app/components/channel_list/search/index.tsx
index 718d81117..b7e8a2f1a 100644
--- a/app/components/channel_list/search/index.tsx
+++ b/app/components/channel_list/search/index.tsx
@@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import React from 'react';
-import {StyleSheet, TextInput, View} from 'react-native';
+import {TextInput, StyleSheet, View} from 'react-native';
import CompassIcon from '@components/compass_icon';
import {useTheme} from '@context/theme';
diff --git a/app/components/channel_list/tempo-types.d.ts b/app/components/channel_list/tempo-types.d.ts
deleted file mode 100644
index e0993d6c0..000000000
--- a/app/components/channel_list/tempo-types.d.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-// THIS IS TEMPORARY POC/MVP - WILL BE REMOVEd
-// @to-do - Wire up actual models
-type TempoChannel = {
- id: string;
- name: string;
- highlight?: boolean;
-};
-
-type TempoCategory = {
- id: string;
- title: string;
- channels: TempoChannel[];
-}
diff --git a/app/components/channel_list/threads/index.test.tsx b/app/components/channel_list/threads/index.test.tsx
index 0a3d4b581..daa5cf9fb 100644
--- a/app/components/channel_list/threads/index.test.tsx
+++ b/app/components/channel_list/threads/index.test.tsx
@@ -3,26 +3,14 @@
import React from 'react';
-import {renderWithEverything} from '@test/intl-test-helper';
-import TestHelper from '@test/test_helper';
+import {renderWithIntlAndTheme} from '@test/intl-test-helper';
import Threads from './index';
-import type Database from '@nozbe/watermelondb/Database';
+test('Threads Component should match snapshot', () => {
+ const {toJSON} = renderWithIntlAndTheme(
+ ,
+ );
-describe('Threads Component', () => {
- let database: Database | undefined;
- beforeAll(async () => {
- const server = await TestHelper.setupServerDatabase();
- database = server.database;
- });
-
- test('should match snapshot', () => {
- const {toJSON} = renderWithEverything(
- ,
- {database},
- );
-
- expect(toJSON()).toMatchSnapshot();
- });
+ expect(toJSON()).toMatchSnapshot();
});
diff --git a/app/components/channel_list/threads/index.tsx b/app/components/channel_list/threads/index.tsx
index d723eb6c2..17fca262e 100644
--- a/app/components/channel_list/threads/index.tsx
+++ b/app/components/channel_list/threads/index.tsx
@@ -1,28 +1,17 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import {Q} from '@nozbe/watermelondb';
-import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
-import withObservables from '@nozbe/with-observables';
import React from 'react';
import {StyleSheet, Text, View} from 'react-native';
-import {of as of$} from 'rxjs';
-import {switchMap} from 'rxjs/operators';
-import {switchToChannelById} from '@actions/remote/channel';
import CompassIcon from '@components/compass_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
-import {General} from '@constants';
-import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
-import {useServerUrl} from '@context/server';
+import {Screens} from '@constants';
import {useTheme} from '@context/theme';
+import {goToScreen} from '@screens/navigation';
import {makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
-import type {WithDatabaseArgs} from '@typings/database/database';
-import type ChannelModel from '@typings/database/models/servers/channel';
-import type SystemModel from '@typings/database/models/servers/system';
-
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
container: {
display: 'flex',
@@ -41,19 +30,18 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
const textStyle = StyleSheet.create([typography('Body', 200, 'SemiBold')]);
-const ThreadsButton = ({channelId}: {channelId?: string}) => {
+const ThreadsButton = () => {
const theme = useTheme();
- const serverUrl = useServerUrl();
const styles = getStyleSheet(theme);
/*
* @to-do:
- * - Check if there are threads, else return null (think of doing this before mounting this component)
- * - Change to button, navigate to threads view instead of the current team Town Square
+ * - Check if there are threads, else return null
+ * - Change to button, navigate to threads view
* - Add right-side number badge
*/
return (
- (channelId ? switchToChannelById(serverUrl, channelId) : true)} >
+ goToScreen(Screens.CHANNEL, 'Channel', {}, {topBar: {visible: false}})} >
{
);
};
-const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
- const currentTeamId = database.get(MM_TABLES.SERVER.SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID);
- const channelId = currentTeamId.pipe(
- switchMap((model) => database.get(MM_TABLES.SERVER.CHANNEL).query(
- Q.where('team_id', model.value),
- Q.where('name', General.DEFAULT_CHANNEL),
- ).observe().pipe(
- // eslint-disable-next-line max-nested-callbacks
- switchMap((channels) => (channels.length ? of$(channels[0].id) : of$(undefined))),
- )),
- );
-
- return {channelId};
-});
-
-export default withDatabase(enhanced(ThreadsButton));
+export default ThreadsButton;
diff --git a/app/components/profile_picture/status.tsx b/app/components/profile_picture/status.tsx
index 6be404b7a..c416c972b 100644
--- a/app/components/profile_picture/status.tsx
+++ b/app/components/profile_picture/status.tsx
@@ -38,7 +38,8 @@ const Status = ({author, statusSize, statusStyle, theme}: Props) => {
styles.statusWrapper,
statusStyle,
{borderRadius: statusSize / 2},
- ]), []);
+ ]), [statusStyle]);
+
if (author?.status && !author.isBot) {
return (
{
teamsCount={props.teamsCount}
/>
{isTablet && Boolean(props.currentTeamId) &&
diff --git a/app/screens/home/channel_list/index.ts b/app/screens/home/channel_list/index.ts
index ee1890448..d2a42724a 100644
--- a/app/screens/home/channel_list/index.ts
+++ b/app/screens/home/channel_list/index.ts
@@ -7,14 +7,15 @@ import {catchError, of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
-import MyTeamModel from '@typings/database/models/servers/my_team';
import ChannelsList from './channel_list';
import type {WithDatabaseArgs} from '@typings/database/database';
+import type MyChannelModel from '@typings/database/models/servers/my_channel';
+import type MyTeamModel from '@typings/database/models/servers/my_team';
import type SystemModel from '@typings/database/models/servers/system';
-const {SERVER: {MY_TEAM, SYSTEM}} = MM_TABLES;
+const {SERVER: {MY_CHANNEL, MY_TEAM, SYSTEM}} = MM_TABLES;
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
currentTeamId: database.get(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID).pipe(
@@ -22,6 +23,7 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
catchError(() => of$(undefined)),
),
teamsCount: database.get(MY_TEAM).query().observeCount(),
+ channelsCount: database.get(MY_CHANNEL).query().observeCount(),
}));
export default withDatabase(enhanced(ChannelsList));
diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json
index 546d693b9..dc4950d33 100644
--- a/assets/base/i18n/en.json
+++ b/assets/base/i18n/en.json
@@ -39,7 +39,7 @@
"camera_type.video.option": "Record Video",
"center_panel.archived.closeChannel": "Close Channel",
"channel": "{count, plural, one {# member} other {# members}}",
- "channel_header.directchannel.you": "{displayname} (you)",
+ "channel_header.directchannel.you": "{displayName} (you)",
"channel_loader.someone": "Someone",
"channel_modal.optional": "(optional)",
"combined_system_message.added_to_channel.many_expanded": "{users} and {lastUser} were **added to the channel** by {actor}.",
@@ -150,6 +150,8 @@
"last_users_message.others": "{numOthers} others ",
"last_users_message.removed_from_channel.type": "were **removed from the channel**.",
"last_users_message.removed_from_team.type": "were **removed from the team**.",
+ "load_categories_error.message": "There was a problem loading content for this server.",
+ "load_categories_error.title": "Couldn't load categories for {serverName}",
"load_channels_error.message": "There was a problem loading content for this team.",
"load_channels_error.title": "Couldn't load {teamDisplayName}",
"load_teams_error.message": "There was a problem loading content for this server.",
diff --git a/package.json b/package.json
index 17d472911..0a7f8df47 100644
--- a/package.json
+++ b/package.json
@@ -162,6 +162,7 @@
"build:ios-sim": "./scripts/build.sh ipa simulator",
"build:ios-unsigned": "./scripts/build.sh ipa unsigned",
"check": "npm run lint && npm run tsc",
+ "check-test": "npm run lint && npm run tsc && npm run test",
"clean": "./scripts/clean.sh",
"e2e:android": "cd detox && npm run e2e:android-build && npm run e2e:android-test && cd ..",
"e2e:ios": "cd detox && npm run e2e:ios-test && cd ..",
diff --git a/test/test_helper.js b/test/test_helper.js
index 67082bda1..36d8d5fa2 100644
--- a/test/test_helper.js
+++ b/test/test_helper.js
@@ -3,6 +3,7 @@
import assert from 'assert';
+import {random} from 'lodash';
import nock from 'nock';
import Config from '@assets/config.json';
@@ -22,8 +23,11 @@ class TestHelper {
this.basicUser = null;
this.basicTeam = null;
this.basicTeamMember = null;
+ this.basicCategory = null;
+ this.basicCategoryChannel = null;
this.basicChannel = null;
this.basicChannelMember = null;
+ this.basicMyChannel = null;
this.basicPost = null;
this.basicRoles = null;
this.basicScheme = null;
@@ -52,6 +56,25 @@ class TestHelper {
prepareRecordsOnly: false,
});
+ // Add a category and associated channel entities
+ await operator.handleCategories({
+ categories: [this.basicCategory],
+ prepareRecordsOnly: false,
+ });
+ await operator.handleCategoryChannels({
+ categoryChannels: [this.basicCategoryChannel],
+ prepareRecordsOnly: false,
+ });
+ await operator.handleChannel({
+ channels: [this.basicChannel],
+ prepareRecordsOnly: false,
+ });
+ await operator.handleMyChannel({
+ prepareRecordsOnly: false,
+ channels: [this.basicChannel],
+ myChannels: [this.basicMyChannel],
+ });
+
const systems = await prepareCommonSystemValues(operator, {
config: {},
license: {},
@@ -93,13 +116,53 @@ class TestHelper {
return new Client(mockApiClient, mockApiClient.baseUrl);
};
+ fakeCategory = (teamId) => {
+ return {
+ display_name: 'Test Category',
+ type: 'custom',
+ sort_order: 0,
+ sorting: 'manual',
+ muted: false,
+ collapsed: false,
+ team_id: teamId,
+ };
+ };
+
+ fakeCategoryWithId = (teamId) => {
+ return {
+ ...this.fakeCategory(teamId),
+ id: this.generateId(),
+ };
+ };
+
+ fakeCategoryChannel = (categoryId, channelId) => {
+ return {
+ category_id: categoryId,
+ channel_id: channelId,
+ sort_order: random(0, 10, false),
+ };
+ };
+
+ fakeCategoryChannelWithId = (teamId, categoryId, channelId) => {
+ return {
+ id: teamId + channelId,
+ category_id: categoryId,
+ channel_id: channelId,
+ sort_order: random(0, 10, false),
+ };
+ };
+
fakeChannel = (teamId) => {
const name = this.generateId();
return {
name,
team_id: teamId,
- display_name: `Unit Test ${name}`,
+
+ // @to-do: Make tests more detriministic;
+ // https://jestjs.io/docs/snapshot-testing#2-tests-should-be-deterministic
+ // display_name: `Unit Test ${name}`,
+ display_name: 'Channel',
type: 'O',
delete_at: 0,
total_msg_count: 0,
@@ -144,6 +207,21 @@ class TestHelper {
};
};
+ fakeMyChannel = (channelId) => {
+ return {
+ id: channelId,
+ channel_id: channelId,
+ last_post_at: 0,
+ last_viewed_at: 0,
+ manually_unread: false,
+ mentions_count: 0,
+ message_count: 0,
+ is_unread: false,
+ roles: '',
+ viewed_at: 0,
+ };
+ };
+
fakeEmail = () => {
return 'success' + this.generateId() + '@simulator.amazonses.com';
};
@@ -318,8 +396,11 @@ class TestHelper {
this.basicUser.roles = 'system_user system_admin';
this.basicTeam = this.fakeTeamWithId();
this.basicTeamMember = this.fakeTeamMember(this.basicUser.id, this.basicTeam.id);
+ this.basicCategory = this.fakeCategoryWithId(this.basicTeam.id);
this.basicChannel = this.fakeChannelWithId(this.basicTeam.id);
+ this.basicCategoryChannel = this.fakeCategoryChannelWithId(this.basicTeam.id, this.basicCategory.id, this.basicChannel.id);
this.basicChannelMember = this.fakeChannelMember(this.basicUser.id, this.basicChannel.id);
+ this.basicMyChannel = this.fakeMyChannel(this.basicChannel.id);
this.basicPost = {...this.fakePostWithId(this.basicChannel.id), create_at: 1507841118796};
this.basicRoles = {
system_admin: {