diff --git a/app/actions/websocket/index.ts b/app/actions/websocket/index.ts
index a3405adb3..f27770baf 100644
--- a/app/actions/websocket/index.ts
+++ b/app/actions/websocket/index.ts
@@ -10,9 +10,9 @@ import {fetchRoles} from '@actions/remote/role';
import {fetchConfigAndLicense} from '@actions/remote/systems';
import {fetchAllTeams, fetchMyTeams} from '@actions/remote/team';
import {fetchMe, updateAllUsersSinceLastDisconnect} from '@actions/remote/user';
-import Events from '@app/constants/events';
import {General, WebsocketEvents} from '@constants';
import {SYSTEM_IDENTIFIERS} from '@constants/database';
+import Events from '@constants/events';
import DatabaseManager from '@database/manager';
import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
import {prepareMyChannelsForTeam} from '@queries/servers/channel';
diff --git a/app/components/channel_list/__snapshots__/index.test.tsx.snap b/app/components/channel_list/__snapshots__/index.test.tsx.snap
new file mode 100644
index 000000000..eaeb5b73d
--- /dev/null
+++ b/app/components/channel_list/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,762 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Channels List should match snapshot 1`] = `
+
+
+
+
+
+
+
+ Contributors
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Community TEST
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Threads
+
+
+
+
+
+
+
+
+ MY FIRST CATEGORY
+
+
+
+
+
+
+
+ Just a channel
+
+
+
+
+
+
+
+ A Highlighted Channel!!!
+
+
+
+
+
+
+
+ And a longer channel name.
+
+
+
+
+
+
+
+
+ ANOTHER CAT
+
+
+
+
+
+
+
+ Just a channel
+
+
+
+
+
+
+
+ A Highlighted Channel!!!
+
+
+
+
+
+
+
+ And a longer channel name.
+
+
+
+
+
+
+
+
+`;
diff --git a/app/components/channel_list/categories/__snapshots__/index.test.tsx.snap b/app/components/channel_list/categories/__snapshots__/index.test.tsx.snap
new file mode 100644
index 000000000..2d0e3b41a
--- /dev/null
+++ b/app/components/channel_list/categories/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,430 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Category List Component should match snapshot 1`] = `
+
+
+
+
+
+
+
+
+ Threads
+
+
+
+
+
+
+
+
+ MY FIRST CATEGORY
+
+
+
+
+
+
+
+ Just a channel
+
+
+
+
+
+
+
+ Highlighted!!!
+
+
+
+
+
+
+
+
+ ANOTHER CAT
+
+
+
+
+
+
+
+ Just a channel
+
+
+
+
+
+
+
+ Highlighted!!!
+
+
+
+
+
+
+
+`;
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
new file mode 100644
index 000000000..2014360a3
--- /dev/null
+++ b/app/components/channel_list/categories/body/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,127 @@
+// 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/channel/__snapshots__/index.test.tsx.snap b/app/components/channel_list/categories/body/channel/__snapshots__/index.test.tsx.snap
new file mode 100644
index 000000000..55ea35f10
--- /dev/null
+++ b/app/components/channel_list/categories/body/channel/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,43 @@
+// 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/index.test.tsx b/app/components/channel_list/categories/body/channel/index.test.tsx
new file mode 100644
index 000000000..ccb818945
--- /dev/null
+++ b/app/components/channel_list/categories/body/channel/index.test.tsx
@@ -0,0 +1,19 @@
+// 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.tsx b/app/components/channel_list/categories/body/channel/index.tsx
new file mode 100644
index 000000000..ae3b09e8d
--- /dev/null
+++ b/app/components/channel_list/categories/body/channel/index.tsx
@@ -0,0 +1,75 @@
+// 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
new file mode 100644
index 000000000..612428cc1
--- /dev/null
+++ b/app/components/channel_list/categories/body/index.test.tsx
@@ -0,0 +1,21 @@
+// 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.tsx b/app/components/channel_list/categories/body/index.tsx
new file mode 100644
index 000000000..3ac9d8b50
--- /dev/null
+++ b/app/components/channel_list/categories/body/index.tsx
@@ -0,0 +1,32 @@
+// 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/header/__snapshots__/index.test.tsx.snap b/app/components/channel_list/categories/header/__snapshots__/index.test.tsx.snap
new file mode 100644
index 000000000..2baacfda8
--- /dev/null
+++ b/app/components/channel_list/categories/header/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,26 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Category Header Component should match snapshot 1`] = `
+
+
+ CAT HEADING
+
+
+`;
diff --git a/app/components/channel_list/categories/header/index.test.tsx b/app/components/channel_list/categories/header/index.test.tsx
new file mode 100644
index 000000000..231e446f9
--- /dev/null
+++ b/app/components/channel_list/categories/header/index.test.tsx
@@ -0,0 +1,16 @@
+// 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.tsx b/app/components/channel_list/categories/header/index.tsx
new file mode 100644
index 000000000..c2741dafa
--- /dev/null
+++ b/app/components/channel_list/categories/header/index.tsx
@@ -0,0 +1,39 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+import {Text, View} from 'react-native';
+
+import {useTheme} from '@context/theme';
+import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
+import {typography} from '@utils/typography';
+
+const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
+ container: {
+ paddingVertical: 8,
+ marginTop: 12,
+ },
+ heading: {
+ color: changeOpacity(theme.sidebarText, 0.64),
+ ...typography('Heading', 75),
+ },
+}));
+
+type Props = {
+ heading: string;
+}
+
+const CategoryHeader = (props: Props) => {
+ const theme = useTheme();
+ const styles = getStyleSheet(theme);
+
+ return (
+
+
+ {props.heading.toUpperCase()}
+
+
+ );
+};
+
+export default CategoryHeader;
diff --git a/app/components/channel_list/categories/index.test.tsx b/app/components/channel_list/categories/index.test.tsx
new file mode 100644
index 000000000..848404b8f
--- /dev/null
+++ b/app/components/channel_list/categories/index.test.tsx
@@ -0,0 +1,26 @@
+// 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 Category from './index';
+
+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},
+];
+
+test('Category List Component should match snapshot', () => {
+ const {toJSON} = renderWithIntlAndTheme(
+ ,
+ );
+
+ expect(toJSON()).toMatchSnapshot();
+});
diff --git a/app/components/channel_list/categories/index.tsx b/app/components/channel_list/categories/index.tsx
new file mode 100644
index 000000000..82927dd21
--- /dev/null
+++ b/app/components/channel_list/categories/index.tsx
@@ -0,0 +1,40 @@
+// 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/header/__snapshots__/index.test.tsx.snap b/app/components/channel_list/header/__snapshots__/index.test.tsx.snap
new file mode 100644
index 000000000..f3f8451e0
--- /dev/null
+++ b/app/components/channel_list/header/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,137 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Channel List Header Component should match snapshot 1`] = `
+
+
+
+
+ Hello World!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/app/components/channel_list/header/index.test.tsx b/app/components/channel_list/header/index.test.tsx
new file mode 100644
index 000000000..35247b2cb
--- /dev/null
+++ b/app/components/channel_list/header/index.test.tsx
@@ -0,0 +1,16 @@
+// 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('Channel List Header Component should match snapshot', () => {
+ const {toJSON} = renderWithIntlAndTheme(
+ ,
+ );
+
+ expect(toJSON()).toMatchSnapshot();
+});
diff --git a/app/components/channel_list/header/index.tsx b/app/components/channel_list/header/index.tsx
new file mode 100644
index 000000000..f263bfae1
--- /dev/null
+++ b/app/components/channel_list/header/index.tsx
@@ -0,0 +1,89 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+import {Text, View} from 'react-native';
+
+import CompassIcon from '@components/compass_icon';
+import TouchableWithFeedback from '@components/touchable_with_feedback';
+import {useTheme} from '@context/theme';
+import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
+import {typography} from '@utils/typography';
+
+type Props = {
+ heading: string;
+ subheading?: string;
+ iconPad?: boolean;
+}
+
+const getStyles = makeStyleSheetFromTheme((theme: Theme) => ({
+ headingStyles: {
+ color: theme.sidebarText,
+ ...typography('Heading', 700),
+ },
+ subHeadingStyles: {
+ color: changeOpacity(theme.sidebarText, 0.64),
+ ...typography('Heading', 50),
+ },
+ iconPad: {
+ marginLeft: 44,
+ },
+ headerRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ },
+ chevronButton: {
+ marginLeft: 4,
+ },
+ chevronIcon: {
+ color: changeOpacity(theme.sidebarText, 0.8),
+ fontSize: 24,
+ },
+ plusButton: {
+ backgroundColor: changeOpacity(theme.sidebarText, 0.08),
+ height: 28,
+ width: 28,
+ borderRadius: 14,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ plusIcon: {
+ color: changeOpacity(theme.sidebarText, 0.8),
+ fontSize: 18,
+ },
+}));
+
+const ChannelListHeader = (props: Props) => {
+ const theme = useTheme();
+ const styles = getStyles(theme);
+
+ return (
+
+
+
+
+ {props.heading}
+
+
+
+
+
+
+
+
+
+
+ {props.subheading}
+
+
+ );
+};
+
+export default ChannelListHeader;
diff --git a/app/components/channel_list/index.test.tsx b/app/components/channel_list/index.test.tsx
new file mode 100644
index 000000000..fa96d5b9a
--- /dev/null
+++ b/app/components/channel_list/index.test.tsx
@@ -0,0 +1,16 @@
+// 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 ChannelsList from './index';
+
+test('Channels List should match snapshot', () => {
+ const {toJSON} = renderWithIntlAndTheme(
+ ,
+ );
+
+ expect(toJSON()).toMatchSnapshot();
+});
diff --git a/app/components/channel_list/index.tsx b/app/components/channel_list/index.tsx
new file mode 100644
index 000000000..77fe21c15
--- /dev/null
+++ b/app/components/channel_list/index.tsx
@@ -0,0 +1,79 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React, {useState} from 'react';
+import {View} from 'react-native';
+import {TouchableOpacity} from 'react-native-gesture-handler';
+
+import TABLET from '@constants/view';
+import {useTheme} from '@context/theme';
+import {useIsTablet} from '@hooks/device';
+import {makeStyleSheetFromTheme} from '@utils/theme';
+
+import Categories from './categories';
+import ChannelListHeader from './header';
+import LoadingError from './loading_error';
+import SearchField from './search';
+
+// import Loading from './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},
+];
+
+const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
+ container: {
+ flex: 1,
+ backgroundColor: theme.sidebarBg,
+ paddingHorizontal: 20,
+ paddingVertical: 10,
+ },
+ maxW: {
+ maxWidth: TABLET.TABLET_SIDEBAR_WIDTH,
+ },
+
+}));
+
+type ChannelListProps = {
+ iconPad?: boolean;
+}
+
+const ChannelList = ({iconPad}: ChannelListProps) => {
+ const theme = useTheme();
+ const styles = getStyleSheet(theme);
+
+ // @to-do; remove after testing
+ const [showCats, setShowCats] = useState(true);
+
+ const isTablet = useIsTablet();
+
+ return (
+
+ setShowCats(!showCats)}>
+
+
+
+ {showCats && (
+ <>
+
+
+ >
+ )}
+ {/* */}
+ {!showCats && ()}
+
+ );
+};
+
+export default ChannelList;
diff --git a/app/components/channel_list/loading/__snapshots__/index.test.tsx.snap b/app/components/channel_list/loading/__snapshots__/index.test.tsx.snap
new file mode 100644
index 000000000..f36ef5870
--- /dev/null
+++ b/app/components/channel_list/loading/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,58 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Loading should match snapshot 1`] = `
+
+
+
+
+
+`;
diff --git a/app/components/channel_list/loading/index.test.tsx b/app/components/channel_list/loading/index.test.tsx
new file mode 100644
index 000000000..b783cde84
--- /dev/null
+++ b/app/components/channel_list/loading/index.test.tsx
@@ -0,0 +1,16 @@
+// 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 Loading from './index';
+
+test('Loading should match snapshot', () => {
+ const {toJSON} = renderWithIntlAndTheme(
+ ,
+ );
+
+ expect(toJSON()).toMatchSnapshot();
+});
diff --git a/app/components/channel_list/loading/index.tsx b/app/components/channel_list/loading/index.tsx
new file mode 100644
index 000000000..67f7a68ab
--- /dev/null
+++ b/app/components/channel_list/loading/index.tsx
@@ -0,0 +1,31 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import LottieView from 'lottie-react-native';
+import React from 'react';
+import {StyleSheet, View} from 'react-native';
+
+const Loading = () => (
+
+
+
+);
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ justifyContent: 'center',
+ padding: 20,
+ maxHeight: 40,
+ },
+ lottie: {
+ height: 32,
+ width: 32,
+ },
+});
+
+export default Loading;
diff --git a/app/components/channel_list/loading/spinner.json b/app/components/channel_list/loading/spinner.json
new file mode 100644
index 000000000..8fa3774f2
--- /dev/null
+++ b/app/components/channel_list/loading/spinner.json
@@ -0,0 +1 @@
+{"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":40,"w":32,"h":32,"nm":"Spinner - white","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":40,"s":[360]}],"ix":10},"p":{"a":0,"k":[16,16,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[27,27],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"gs","o":{"a":0,"k":100,"ix":9},"w":{"a":0,"k":3,"ix":10},"g":{"p":3,"k":{"a":0,"k":[0.001,1,1,1,0.5,1,1,1,1,1,1,1,0,1,0.5,0.5,1,0],"ix":8}},"s":{"a":0,"k":[2.423,11.896],"ix":4},"e":{"a":0,"k":[1.431,-12.387],"ix":5},"t":1,"lc":1,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":13},"bm":0,"nm":"Gradient Stroke 1","mn":"ADBE Vector Graphic - G-Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.05,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-15.95,-15.938],[-15.963,16],[-0.025,16],[-0.013,-15.938]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[27,27],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.05,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/app/components/channel_list/loading_error/__snapshots__/index.test.tsx.snap b/app/components/channel_list/loading_error/__snapshots__/index.test.tsx.snap
new file mode 100644
index 000000000..a4e54968e
--- /dev/null
+++ b/app/components/channel_list/loading_error/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,151 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Loading Error should match snapshot 1`] = `
+
+
+
+
+
+ Couldn’t load Staff
+
+
+ There was a problem loading the content for this team.
+
+
+
+
+ Retry
+
+
+
+
+`;
diff --git a/app/components/channel_list/loading_error/index.test.tsx b/app/components/channel_list/loading_error/index.test.tsx
new file mode 100644
index 000000000..90dd1723f
--- /dev/null
+++ b/app/components/channel_list/loading_error/index.test.tsx
@@ -0,0 +1,16 @@
+// 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 ErrorComponent from './index';
+
+test('Loading Error should match snapshot', () => {
+ const {toJSON} = renderWithIntlAndTheme(
+ ,
+ );
+
+ expect(toJSON()).toMatchSnapshot();
+});
diff --git a/app/components/channel_list/loading_error/index.tsx b/app/components/channel_list/loading_error/index.tsx
new file mode 100644
index 000000000..d928d4b8f
--- /dev/null
+++ b/app/components/channel_list/loading_error/index.tsx
@@ -0,0 +1,70 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+import React from 'react';
+import {Text, View} from 'react-native';
+
+import CompassIcon from '@components/compass_icon';
+import TouchableWithFeedback from '@components/touchable_with_feedback';
+import {useTheme} from '@context/theme';
+import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
+import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
+import {typography} from '@utils/typography';
+
+const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 20,
+ },
+ iconWrapper: {
+ height: 120,
+ width: 120,
+ backgroundColor: changeOpacity(theme.sidebarText, 0.08),
+ borderRadius: 60,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ icon: {
+ fontSize: 72,
+ lineHeight: 72,
+ color: changeOpacity(theme.sidebarText, 0.48),
+ },
+ header: {
+ color: theme.sidebarHeaderTextColor,
+ marginTop: 20,
+ textAlign: 'center',
+ },
+ body: {
+ color: theme.sidebarText,
+ textAlign: 'center',
+ marginTop: 4,
+ },
+}));
+
+const LoadingError = () => {
+ const theme = useTheme();
+ const styles = getStyleSheet(theme);
+
+ return (
+
+
+
+
+
+ {'Couldn’t load Staff'}
+
+
+ {'There was a problem loading the content for this team.'}
+
+
+ {'Retry'}
+
+
+ );
+};
+
+export default LoadingError;
diff --git a/app/components/channel_list/search/__snapshots__/index.test.tsx.snap b/app/components/channel_list/search/__snapshots__/index.test.tsx.snap
new file mode 100644
index 000000000..640e69691
--- /dev/null
+++ b/app/components/channel_list/search/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,63 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Search Field should match snapshot 1`] = `
+
+
+
+
+`;
diff --git a/app/components/channel_list/search/index.test.tsx b/app/components/channel_list/search/index.test.tsx
new file mode 100644
index 000000000..04c35e38e
--- /dev/null
+++ b/app/components/channel_list/search/index.test.tsx
@@ -0,0 +1,16 @@
+// 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 Search from './index';
+
+test('Search Field should match snapshot', () => {
+ const {toJSON} = renderWithIntlAndTheme(
+ ,
+ );
+
+ expect(toJSON()).toMatchSnapshot();
+});
diff --git a/app/components/channel_list/search/index.tsx b/app/components/channel_list/search/index.tsx
new file mode 100644
index 000000000..698ed8907
--- /dev/null
+++ b/app/components/channel_list/search/index.tsx
@@ -0,0 +1,69 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+import {StyleSheet, View} from 'react-native';
+import {TextInput} from 'react-native-gesture-handler';
+
+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: {
+ flex: 1,
+ flexDirection: 'row',
+ justifyContent: 'flex-start',
+ alignItems: 'center',
+ width: '100%',
+ backgroundColor: changeOpacity(theme.sidebarText, 0.12),
+ borderRadius: 8,
+ padding: 8,
+ marginVertical: 20,
+ height: 40,
+ maxHeight: 40,
+ },
+ icon: {
+ width: 24,
+ fontSize: 24,
+ color: changeOpacity(theme.sidebarText, 0.72),
+ },
+ input: {
+ flex: 1,
+ height: 40,
+ color: theme.sidebarText,
+ alignItems: 'center',
+ alignContent: 'center',
+ marginLeft: 5,
+ marginTop: -2,
+ },
+}));
+
+const textStyles = StyleSheet.create([
+ typography('Body', 200),
+ {textAlignVertical: 'center'},
+]);
+
+const SearchField = () => {
+ const theme = useTheme();
+ const styles = getStyleSheet(theme);
+
+ return (
+
+
+
+
+ );
+};
+
+export default SearchField;
diff --git a/app/components/channel_list/tempo-types.d.ts b/app/components/channel_list/tempo-types.d.ts
new file mode 100644
index 000000000..e0993d6c0
--- /dev/null
+++ b/app/components/channel_list/tempo-types.d.ts
@@ -0,0 +1,16 @@
+// 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/__snapshots__/index.test.tsx.snap b/app/components/channel_list/threads/__snapshots__/index.test.tsx.snap
new file mode 100644
index 000000000..cd2f561d2
--- /dev/null
+++ b/app/components/channel_list/threads/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,70 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Threads Component should match snapshot 1`] = `
+
+
+
+
+
+ Threads
+
+
+
+
+`;
diff --git a/app/components/channel_list/threads/index.test.tsx b/app/components/channel_list/threads/index.test.tsx
new file mode 100644
index 000000000..daa5cf9fb
--- /dev/null
+++ b/app/components/channel_list/threads/index.test.tsx
@@ -0,0 +1,16 @@
+// 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 Threads from './index';
+
+test('Threads Component should match snapshot', () => {
+ const {toJSON} = renderWithIntlAndTheme(
+ ,
+ );
+
+ expect(toJSON()).toMatchSnapshot();
+});
diff --git a/app/components/channel_list/threads/index.tsx b/app/components/channel_list/threads/index.tsx
new file mode 100644
index 000000000..17fca262e
--- /dev/null
+++ b/app/components/channel_list/threads/index.tsx
@@ -0,0 +1,56 @@
+// 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 TouchableWithFeedback from '@components/touchable_with_feedback';
+import {Screens} from '@constants';
+import {useTheme} from '@context/theme';
+import {goToScreen} from '@screens/navigation';
+import {makeStyleSheetFromTheme} from '@utils/theme';
+import {typography} from '@utils/typography';
+
+const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
+ container: {
+ display: 'flex',
+ flexDirection: 'row',
+ },
+ icon: {
+ fontSize: 24,
+ lineHeight: 28,
+ color: theme.sidebarText,
+ },
+ text: {
+ color: theme.sidebarText,
+ paddingLeft: 12,
+ },
+}));
+
+const textStyle = StyleSheet.create([typography('Body', 200, 'SemiBold')]);
+
+const ThreadsButton = () => {
+ const theme = useTheme();
+ const styles = getStyleSheet(theme);
+
+ /*
+ * @to-do:
+ * - Check if there are threads, else return null
+ * - Change to button, navigate to threads view
+ * - Add right-side number badge
+ */
+ return (
+ goToScreen(Screens.CHANNEL, 'Channel', {}, {topBar: {visible: false}})} >
+
+
+ {'Threads'}
+
+
+ );
+};
+
+export default ThreadsButton;
diff --git a/app/components/compass_icon/index.tsx b/app/components/compass_icon/index.tsx
index fe63c6a66..22139a372 100644
--- a/app/components/compass_icon/index.tsx
+++ b/app/components/compass_icon/index.tsx
@@ -5,4 +5,7 @@ import {createIconSetFromFontello} from 'react-native-vector-icons';
import fontelloConfig from '@assets/compass-icons.json';
-export default createIconSetFromFontello(fontelloConfig, 'compass-icons', 'compass-icons.ttf');
+const CompassIcon = createIconSetFromFontello(fontelloConfig, 'compass-icons',
+ 'compass-icons.ttf');
+
+export default CompassIcon;
diff --git a/app/components/team_sidebar/add_team/team_list.tsx b/app/components/team_sidebar/add_team/team_list.tsx
index 583338c24..2c337e46b 100644
--- a/app/components/team_sidebar/add_team/team_list.tsx
+++ b/app/components/team_sidebar/add_team/team_list.tsx
@@ -5,9 +5,9 @@ import React from 'react';
import {ListRenderItemInfo, View} from 'react-native';
import {FlatList} from 'react-native-gesture-handler';
-import {makeStyleSheetFromTheme} from '@app/utils/theme';
import FormattedText from '@components/formatted_text';
import {useTheme} from '@context/theme';
+import {makeStyleSheetFromTheme} from '@utils/theme';
import TeamListItem from './team_list_item';
diff --git a/app/components/team_sidebar/index.ts b/app/components/team_sidebar/index.ts
index d29c2ec79..023487ba2 100644
--- a/app/components/team_sidebar/index.ts
+++ b/app/components/team_sidebar/index.ts
@@ -7,9 +7,9 @@ import withObservables from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
-import {Permissions} from '@app/constants';
-import {hasPermission} from '@app/utils/role';
+import {Permissions} from '@constants';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
+import {hasPermission} from '@utils/role';
import TeamSidebar from './team_sidebar';
diff --git a/app/components/team_sidebar/server_icon/server_icon.tsx b/app/components/team_sidebar/server_icon/server_icon.tsx
deleted file mode 100644
index 013dd7c8e..000000000
--- a/app/components/team_sidebar/server_icon/server_icon.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 {View} from 'react-native';
-
-import CompassIcon from '@components/compass_icon';
-import {useTheme} from '@context/theme';
-import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
-
-// TODO PLACEHOLDER
-export default function ServerIcon() {
- const theme = useTheme();
- const styles = getStyleSheet(theme);
- return (
-
-
-
- );
-}
-
-const getStyleSheet = makeStyleSheetFromTheme(() => {
- return {
- container: {
- flex: 0,
- borderRadius: 10,
- height: 60,
- width: '100%',
- overflow: 'hidden',
- alignItems: 'center',
- justifyContent: 'center',
- },
- };
-});
diff --git a/app/components/team_sidebar/team_sidebar.tsx b/app/components/team_sidebar/team_sidebar.tsx
index 8d801dbf4..440925acd 100644
--- a/app/components/team_sidebar/team_sidebar.tsx
+++ b/app/components/team_sidebar/team_sidebar.tsx
@@ -11,7 +11,6 @@ import {useTheme} from '@context/theme';
import {makeStyleSheetFromTheme} from '@utils/theme';
import AddTeam from './add_team/add_team';
-import ServerIcon from './server_icon/server_icon';
import TeamList from './team_list';
import type TeamModel from '@typings/database/models/servers/team';
@@ -19,9 +18,10 @@ import type TeamModel from '@typings/database/models/servers/team';
type Props = {
canCreateTeams: boolean;
otherTeams: TeamModel[];
+ iconPad?: boolean;
}
-export default function TeamSidebar({canCreateTeams, otherTeams}: Props) {
+export default function TeamSidebar({canCreateTeams, otherTeams, iconPad}: Props) {
const theme = useTheme();
const styles = getStyleSheet(theme);
const serverUrl = useServerUrl();
@@ -34,8 +34,7 @@ export default function TeamSidebar({canCreateTeams, otherTeams}: Props) {
return (
-
-
+
{showAddTeam && (
{
height: '100%',
backgroundColor: theme.sidebarBg,
display: 'flex',
+ paddingTop: 10,
},
listContainer: {
backgroundColor: theme.sidebarTeamBarBg,
borderTopRightRadius: 12,
flex: 1,
},
+ iconMargin: {
+ marginTop: 44,
+ paddingTop: 0,
+ },
};
});
diff --git a/app/init/push_notifications.ts b/app/init/push_notifications.ts
index 7fd796c7b..ab48e7413 100644
--- a/app/init/push_notifications.ts
+++ b/app/init/push_notifications.ts
@@ -16,7 +16,6 @@ import {
import {markChannelAsViewed} from '@actions/local/channel';
import {backgroundNotification, openNotification} from '@actions/remote/notifications';
-import EphemeralStore from '@app/store/ephemeral_store';
import {Device, Events, Navigation, Screens} from '@constants';
import {GLOBAL_IDENTIFIERS} from '@constants/database';
import DatabaseManager from '@database/manager';
@@ -25,6 +24,7 @@ import NativeNotifications from '@notifications';
import {queryServerName} from '@queries/app/servers';
import {queryCurrentChannelId} from '@queries/servers/system';
import {showOverlay} from '@screens/navigation';
+import EphemeralStore from '@store/ephemeral_store';
import {isTablet} from '@utils/helpers';
import {convertToNotificationData} from '@utils/notification';
diff --git a/app/init/websocket_manager.ts b/app/init/websocket_manager.ts
index 5e5f59240..ffdc01a45 100644
--- a/app/init/websocket_manager.ts
+++ b/app/init/websocket_manager.ts
@@ -7,11 +7,11 @@ import {AppState, AppStateStatus} from 'react-native';
import {setCurrentUserStatusOffline} from '@actions/local/user';
import {fetchStatusByIds} from '@actions/remote/user';
import {handleClose, handleEvent, handleFirstConnect, handleReconnect} from '@actions/websocket';
-import WebSocketClient from '@app/client/websocket';
-import {General} from '@app/constants';
-import {queryCurrentUserId, resetWebSocketLastDisconnected} from '@app/queries/servers/system';
-import {queryAllUsers} from '@app/queries/servers/user';
+import WebSocketClient from '@client/websocket';
+import {General} from '@constants';
import DatabaseManager from '@database/manager';
+import {queryCurrentUserId, resetWebSocketLastDisconnected} from '@queries/servers/system';
+import {queryAllUsers} from '@queries/servers/user';
import type {ServerCredential} from '@typings/credentials';
diff --git a/app/screens/bottom_sheet/index.tsx b/app/screens/bottom_sheet/index.tsx
index d55e84a1a..7af97bcc0 100644
--- a/app/screens/bottom_sheet/index.tsx
+++ b/app/screens/bottom_sheet/index.tsx
@@ -8,12 +8,12 @@ import {Navigation as RNN} from 'react-native-navigation';
import Animated from 'react-native-reanimated';
import RNBottomSheet from 'reanimated-bottom-sheet';
-import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme';
import {Navigation} from '@constants';
import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device';
import {dismissModal} from '@screens/navigation';
import {hapticFeedback} from '@utils/general';
+import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import Indicator from './indicator';
diff --git a/app/screens/channel_list/index.tsx b/app/screens/channel_list/index.tsx
deleted file mode 100644
index c58b73168..000000000
--- a/app/screens/channel_list/index.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-import React from 'react';
-import {Text, View} from 'react-native';
-
-import {makeStyleSheetFromTheme} from '@utils/theme';
-
-type ChannelProps = {
- theme: Theme;
-}
-
-const ChannelList = ({theme}: ChannelProps) => {
- const styles = getStyleSheet(theme);
-
- return (
-
- {' Channel Screen '}
-
- );
-};
-
-const getStyleSheet = makeStyleSheetFromTheme(() => ({
- container: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- backgroundColor: '#ffffff',
- },
- tabContainer: {
- position: 'absolute',
- bottom: 0,
- backgroundColor: '#ffffff',
- },
- textContainer: {
- marginTop: 30,
- alignItems: 'center',
- },
- screenTitle: {
- fontSize: 30,
- alignSelf: 'center',
- },
-}));
-
-export default ChannelList;
diff --git a/app/screens/emoji_picker/index.tsx b/app/screens/emoji_picker/index.tsx
index 046ee1feb..64fb90f10 100644
--- a/app/screens/emoji_picker/index.tsx
+++ b/app/screens/emoji_picker/index.tsx
@@ -5,7 +5,7 @@ import React, {useCallback, useEffect} from 'react';
import {Keyboard} from 'react-native';
import {Navigation} from 'react-native-navigation';
-import EmojiPicker from '@app/components/emoji_picker';
+import EmojiPicker from '@components/emoji_picker';
import {dismissModal, setButtons} from '@screens/navigation';
type Props = {
diff --git a/app/screens/home/account/components/options/user_presence/index.tsx b/app/screens/home/account/components/options/user_presence/index.tsx
index 591405a26..619a11e20 100644
--- a/app/screens/home/account/components/options/user_presence/index.tsx
+++ b/app/screens/home/account/components/options/user_presence/index.tsx
@@ -6,13 +6,13 @@ import {useIntl} from 'react-intl';
import {DeviceEventEmitter, TextStyle} from 'react-native';
import {setStatus} from '@actions/remote/user';
-import {useServerUrl} from '@app/context/server_url';
import DrawerItem from '@components/drawer_item';
import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item';
import StatusLabel from '@components/status_label';
import UserStatusIndicator from '@components/user_status';
import {Navigation} from '@constants';
import General from '@constants/general';
+import {useServerUrl} from '@context/server_url';
import {bottomSheet, dismissModal} from '@screens/navigation';
import {preventDoubleTap} from '@utils/tap';
import {changeOpacity} from '@utils/theme';
diff --git a/app/screens/home/channel_list/index.tsx b/app/screens/home/channel_list/index.tsx
index e6fc38528..4346879de 100644
--- a/app/screens/home/channel_list/index.tsx
+++ b/app/screens/home/channel_list/index.tsx
@@ -3,16 +3,16 @@
import {useIsFocused, useRoute} from '@react-navigation/native';
import React from 'react';
-import {Text, View} from 'react-native';
+import {View} from 'react-native';
import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated';
import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
+import ChannelList from '@components/channel_list';
import TeamSidebar from '@components/team_sidebar';
-import {View as ViewConstants} from '@constants';
import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device';
import Channel from '@screens/channel';
-import {goToScreen} from '@screens/navigation';
+import ServerIcon from '@screens/home/channel_list/server_icon/server_icon';
import {makeStyleSheetFromTheme} from '@utils/theme';
import type {LaunchProps} from '@typings/launch';
@@ -43,18 +43,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
const ChannelListScreen = (props: ChannelProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
+
const isTablet = useIsTablet();
const route = useRoute();
const isFocused = useIsFocused();
const insets = useSafeAreaInsets();
const params = route.params as {direction: string};
- let tabletSidebarStyle;
- if (isTablet) {
- const {TABLET_SIDEBAR_WIDTH, TEAM_SIDEBAR_WIDTH} = ViewConstants;
- tabletSidebarStyle = {maxWidth: (TABLET_SIDEBAR_WIDTH - TEAM_SIDEBAR_WIDTH)};
- }
-
const animated = useAnimatedStyle(() => {
if (!isFocused) {
let initial = 0;
@@ -79,18 +74,15 @@ const ChannelListScreen = (props: ChannelProps) => {
style={styles.content}
edges={['bottom', 'left', 'right']}
>
+
-
-
- goToScreen('Channel', '', undefined, {topBar: {visible: false}})}
- style={{fontSize: 20, color: theme.centerChannelColor}}
- >
- {'Channel List'}
-
-
+ {/* @to-do: Server Icon requires padding in the team and channel components:
+ * https://mattermost.atlassian.net/browse/MM-39702
+ */}
+
+
{isTablet &&
}
diff --git a/app/screens/home/channel_list/server_icon/__snapshots__/server_icon.test.tsx.snap b/app/screens/home/channel_list/server_icon/__snapshots__/server_icon.test.tsx.snap
new file mode 100644
index 000000000..c3ca88edb
--- /dev/null
+++ b/app/screens/home/channel_list/server_icon/__snapshots__/server_icon.test.tsx.snap
@@ -0,0 +1,26 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Server Icon Component should match snapshot 1`] = `
+
+
+
+`;
diff --git a/app/screens/home/channel_list/server_icon/server_icon.test.tsx b/app/screens/home/channel_list/server_icon/server_icon.test.tsx
new file mode 100644
index 000000000..20ee4f5f5
--- /dev/null
+++ b/app/screens/home/channel_list/server_icon/server_icon.test.tsx
@@ -0,0 +1,16 @@
+// 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 Icon from './server_icon';
+
+test('Server Icon Component should match snapshot', () => {
+ const {toJSON} = renderWithIntlAndTheme(
+ ,
+ );
+
+ expect(toJSON()).toMatchSnapshot();
+});
diff --git a/app/screens/home/channel_list/server_icon/server_icon.tsx b/app/screens/home/channel_list/server_icon/server_icon.tsx
new file mode 100644
index 000000000..ef65f12c7
--- /dev/null
+++ b/app/screens/home/channel_list/server_icon/server_icon.tsx
@@ -0,0 +1,39 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+import {StyleSheet, View} from 'react-native';
+
+import CompassIcon from '@components/compass_icon';
+import {useTheme} from '@context/theme';
+import {changeOpacity} from '@utils/theme';
+
+const styles = StyleSheet.create({
+ icon: {
+ flex: 0,
+ overflow: 'hidden',
+ alignItems: 'center',
+ justifyContent: 'center',
+ position: 'absolute',
+ zIndex: 10,
+ top: 10,
+ left: 16,
+ width: 40,
+ height: 40,
+ },
+});
+
+export default function ServerIcon() {
+ const theme = useTheme();
+
+ return (
+
+
+
+ );
+}
+
diff --git a/app/screens/server/form.tsx b/app/screens/server/form.tsx
index 4b34fd119..bbb1aa267 100644
--- a/app/screens/server/form.tsx
+++ b/app/screens/server/form.tsx
@@ -7,8 +7,8 @@ import {ActivityIndicator, Platform, useWindowDimensions, View} from 'react-nati
import Button from 'react-native-button';
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
-import FormattedText from '@app/components/formatted_text';
import FloatingTextInput, {FloatingTextInputRef} from '@components/floating_text_input_label';
+import FormattedText from '@components/formatted_text';
import {useIsTablet} from '@hooks/device';
import {t} from '@i18n';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
diff --git a/app/screens/server/header.tsx b/app/screens/server/header.tsx
index f27a44b13..7add7fee0 100644
--- a/app/screens/server/header.tsx
+++ b/app/screens/server/header.tsx
@@ -4,7 +4,7 @@
import React from 'react';
import {View} from 'react-native';
-import FormattedText from '@app/components/formatted_text';
+import FormattedText from '@components/formatted_text';
import {useIsTablet} from '@hooks/device';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
diff --git a/app/utils/buttonStyles.ts b/app/utils/buttonStyles.ts
index 21987691b..dd760a683 100644
--- a/app/utils/buttonStyles.ts
+++ b/app/utils/buttonStyles.ts
@@ -44,6 +44,7 @@ export const buttonBackgroundStyle = (
flex: 0,
alignItems: 'center',
justifyContent: 'center',
+ textAlignVertical: 'center',
borderRadius: 4,
},
fullWidth: {
@@ -106,13 +107,13 @@ export const buttonBackgroundStyle = (
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
},
hover: {
- backgroundColor: changeOpacity(theme.centerChannelColor, 0),
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
},
active: {
- backgroundColor: changeOpacity(theme.centerChannelColor, 0),
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
},
focus: {
- backgroundColor: changeOpacity(theme.centerChannelColor, 0),
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
},
},
},
@@ -392,6 +393,10 @@ export const buttonTextStyle = (
main: {
fontFamily: 'OpenSans-SemiBold',
fontWeight: '600',
+ textAlignVertical: 'center',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 1,
},
underline: {
textDecorationLine: 'underline',
@@ -403,18 +408,22 @@ export const buttonTextStyle = (
fontSize: 11,
lineHeight: 10,
letterSpacing: 0.02,
+ marginTop: 2,
},
s: {
fontSize: 12,
- lineHeight: 11,
+ lineHeight: 12,
+ marginTop: 1,
},
m: {
fontSize: 14,
lineHeight: 14,
+ marginTop: 3,
},
lg: {
fontSize: 16,
lineHeight: 18,
+ marginTop: 2,
},
});
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index c488bc66a..724673b56 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -40,9 +40,13 @@ PODS:
- libwebp/webp (= 1.2.0)
- libwebp/demux (1.2.0):
- libwebp/webp
- - libwebp/mux (1.2.0):
+ - libwebp/mux (1.2.1):
- libwebp/demux
- - libwebp/webp (1.2.0)
+ - libwebp/webp (1.2.1)
+ - lottie-ios (3.2.3)
+ - lottie-react-native (4.1.3):
+ - lottie-ios (~> 3.2.3)
+ - React-Core
- Permission-Camera (3.1.0):
- RNPermissions
- Permission-PhotoLibrary (3.1.0):
@@ -479,7 +483,7 @@ PODS:
- React-jsi
- XCDYouTubeKit (2.8.2)
- Yoga (1.14.0)
- - YoutubePlayer-in-WKWebView (0.3.5)
+ - YoutubePlayer-in-WKWebView (0.3.8)
DEPENDENCIES:
- boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
@@ -494,6 +498,8 @@ DEPENDENCIES:
- hermes-engine (~> 0.9.0)
- jail-monkey (from `../node_modules/jail-monkey`)
- libevent (~> 2.1.12)
+ - lottie-ios (from `../node_modules/lottie-ios`)
+ - lottie-react-native (from `../node_modules/lottie-react-native`)
- Permission-Camera (from `../node_modules/react-native-permissions/ios/Camera`)
- Permission-PhotoLibrary (from `../node_modules/react-native-permissions/ios/PhotoLibrary`)
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
@@ -609,6 +615,10 @@ EXTERNAL SOURCES:
:podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
jail-monkey:
:path: "../node_modules/jail-monkey"
+ lottie-ios:
+ :path: "../node_modules/lottie-ios"
+ lottie-react-native:
+ :path: "../node_modules/lottie-react-native"
Permission-Camera:
:path: "../node_modules/react-native-permissions/ios/Camera"
Permission-PhotoLibrary:
@@ -777,6 +787,8 @@ SPEC CHECKSUMS:
jail-monkey: 07b83767601a373db876e939b8dbf3f5eb15f073
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
libwebp: e90b9c01d99205d03b6bb8f2c8c415e5a4ef66f0
+ lottie-ios: c058aeafa76daa4cf64d773554bccc8385d0150e
+ lottie-react-native: b561920bba7ec727ec94a473b683cffba00faece
Permission-Camera: 0db4fd6e1c556c1cf47f38b989a8084cea3ec3dd
Permission-PhotoLibrary: 9dcf80d1353d81b9f1e210c34291591236aaf2b6
RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9
@@ -856,7 +868,7 @@ SPEC CHECKSUMS:
WatermelonDB: e043b1a32ddc63864eb539b562e86ef80f8224cd
XCDYouTubeKit: 79baadb0560673a67c771eba45f83e353fd12c1f
Yoga: 2b4a01651f42a32f82e6cef3830a3ba48088237f
- YoutubePlayer-in-WKWebView: cfbf46da51d7370662a695a8f351e5fa1d3e1008
+ YoutubePlayer-in-WKWebView: 4fca3b4f6f09940077bfbae7bddb771f2b43aacd
PODFILE CHECKSUM: 7d223db07581157903686d5b376c1b697a056354
diff --git a/package-lock.json b/package-lock.json
index 68c5cae36..97850b717 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,6 +5,7 @@
"requires": true,
"packages": {
"": {
+ "name": "mattermost-mobile",
"version": "2.0.0",
"hasInstallScript": true,
"license": "Apache 2.0",
@@ -41,6 +42,8 @@
"emoji-regex": "10.0.0",
"fuse.js": "6.4.6",
"jail-monkey": "2.6.0",
+ "lottie-ios": "3.2.3",
+ "lottie-react-native": "4.1.3",
"mime-db": "1.50.0",
"moment-timezone": "0.5.33",
"prop-types": "15.7.2",
@@ -15875,6 +15878,42 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/lottie-ios": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/lottie-ios/-/lottie-ios-3.2.3.tgz",
+ "integrity": "sha512-mubYMN6+1HXa8z3EJKBvNBkl4UoVM4McjESeB2PgvRMSngmJtC5yUMRdhbbrIAn5Liu3hFGao/14s5hQIgtkRQ=="
+ },
+ "node_modules/lottie-react-native": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/lottie-react-native/-/lottie-react-native-4.1.3.tgz",
+ "integrity": "sha512-RgQrn1VYRXMV3YTZE9DgZy/UqNsMmZvzXBU4eEUWDOTY9cemOoWmCg2BHrL7nNtDJqtsu1Mi/6e8hp0yN2mcBA==",
+ "dependencies": {
+ "invariant": "^2.2.2",
+ "prop-types": "^15.5.10",
+ "react-native-safe-modules": "^1.0.3"
+ },
+ "peerDependencies": {
+ "lottie-ios": "^3.2.3",
+ "react": "*",
+ "react-native": ">=0.46"
+ }
+ },
+ "node_modules/lottie-react-native/node_modules/dedent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.6.0.tgz",
+ "integrity": "sha1-Dm2o8M5Sg471zsXI+TlrDBtko8s="
+ },
+ "node_modules/lottie-react-native/node_modules/react-native-safe-modules": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/react-native-safe-modules/-/react-native-safe-modules-1.0.3.tgz",
+ "integrity": "sha512-DUxti4Z+AgJ/ZsO5U7p3uSCUBko8JT8GvFlCeOXk9bMd+4qjpoDvMYpfbixXKgL88M+HwmU/KI1YFN6gsQZyBA==",
+ "dependencies": {
+ "dedent": "^0.6.0"
+ },
+ "peerDependencies": {
+ "react-native": "*"
+ }
+ },
"node_modules/lru-cache": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
@@ -37342,6 +37381,36 @@
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
+ "lottie-ios": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/lottie-ios/-/lottie-ios-3.2.3.tgz",
+ "integrity": "sha512-mubYMN6+1HXa8z3EJKBvNBkl4UoVM4McjESeB2PgvRMSngmJtC5yUMRdhbbrIAn5Liu3hFGao/14s5hQIgtkRQ=="
+ },
+ "lottie-react-native": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/lottie-react-native/-/lottie-react-native-4.1.3.tgz",
+ "integrity": "sha512-RgQrn1VYRXMV3YTZE9DgZy/UqNsMmZvzXBU4eEUWDOTY9cemOoWmCg2BHrL7nNtDJqtsu1Mi/6e8hp0yN2mcBA==",
+ "requires": {
+ "invariant": "^2.2.2",
+ "prop-types": "^15.5.10",
+ "react-native-safe-modules": "^1.0.3"
+ },
+ "dependencies": {
+ "dedent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.6.0.tgz",
+ "integrity": "sha1-Dm2o8M5Sg471zsXI+TlrDBtko8s="
+ },
+ "react-native-safe-modules": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/react-native-safe-modules/-/react-native-safe-modules-1.0.3.tgz",
+ "integrity": "sha512-DUxti4Z+AgJ/ZsO5U7p3uSCUBko8JT8GvFlCeOXk9bMd+4qjpoDvMYpfbixXKgL88M+HwmU/KI1YFN6gsQZyBA==",
+ "requires": {
+ "dedent": "^0.6.0"
+ }
+ }
+ }
+ },
"lru-cache": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
diff --git a/package.json b/package.json
index b184d49ae..0991ae262 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,8 @@
"emoji-regex": "10.0.0",
"fuse.js": "6.4.6",
"jail-monkey": "2.6.0",
+ "lottie-ios": "3.2.3",
+ "lottie-react-native": "4.1.3",
"mime-db": "1.50.0",
"moment-timezone": "0.5.33",
"prop-types": "15.7.2",