forked from Ivasoft/mattermost-mobile
[Gekidou] Add empty unreads placeholder (#6350)
* Add empty unreads placeholder * ux feedback * Use SidebarText witn 0.12 opacity as the button bg color * Set tertiary button helper to use theme.sidebarText color
This commit is contained in:
@@ -102,7 +102,17 @@ const Categories = ({categories, onlyUnreads, unreadsOnTop}: Props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{!switchingTeam && !initiaLoad && (
|
||||
{!switchingTeam && !initiaLoad && onlyUnreads &&
|
||||
<View style={styles.mainList}>
|
||||
<UnreadCategories
|
||||
currentTeamId={teamId}
|
||||
isTablet={isTablet}
|
||||
onChannelSwitch={onChannelSwitch}
|
||||
onlyUnreads={onlyUnreads}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
{!switchingTeam && !initiaLoad && !onlyUnreads && (
|
||||
<FlatList
|
||||
data={categoriesToShow}
|
||||
ref={listRef}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import * as React from 'react';
|
||||
import Svg, {Ellipse, Path} from 'react-native-svg';
|
||||
|
||||
import {useTheme} from '@context/theme';
|
||||
|
||||
function SvgComponent() {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Svg
|
||||
width={140}
|
||||
height={140}
|
||||
viewBox='0 0 140 140'
|
||||
fill='none'
|
||||
>
|
||||
<Ellipse
|
||||
cx={70}
|
||||
cy={114}
|
||||
rx={45}
|
||||
ry={3}
|
||||
fill='#000'
|
||||
fillOpacity={0.08}
|
||||
/>
|
||||
<Path
|
||||
d='M107.838 26.436h-75.65a10.577 10.577 0 00-7.475 3.06 10.533 10.533 0 00-3.117 7.44v47.977a10.516 10.516 0 003.117 7.44 10.56 10.56 0 007.475 3.06h11.165v17.959l16.746-17.96h47.712a10.576 10.576 0 007.476-3.06 10.52 10.52 0 003.117-7.439V36.937a10.521 10.521 0 00-3.108-7.43 10.567 10.567 0 00-7.458-3.07z'
|
||||
fill='#FFBC1F'
|
||||
/>
|
||||
<Path
|
||||
d='M60.1 95.413h47.711a10.576 10.576 0 007.476-3.06 10.52 10.52 0 003.117-7.439V55.785s-3.331 26.935-3.93 29.306c-.598 2.37-1.786 5.918-7.413 6.506-5.627.588-46.962 3.815-46.962 3.815z'
|
||||
fill='#CC8F00'
|
||||
/>
|
||||
<Path
|
||||
d='M29.447 46.526a21.375 21.375 0 013.823-7.464 21.426 21.426 0 016.403-5.424.74.74 0 00-.303-1.4c-4.77-.285-14.398.731-11.38 14.217a.749.749 0 001.171.469.748.748 0 00.286-.398z'
|
||||
fill='#FFD470'
|
||||
/>
|
||||
<Path
|
||||
d='M86.565 44.167L66.313 66.44l-5.878-4.455h-3.268l9.146 14.848 23.52-32.664h-3.268z'
|
||||
fill={theme.sidebarBg}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default SvgComponent;
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React, {useMemo} from 'react';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import {showUnreadChannelsOnly} from '@actions/local/channel';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import EmptyIllustration from './empty_unreads';
|
||||
|
||||
type Props = {
|
||||
onlyUnreads: boolean;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
button: {
|
||||
marginTop: 24,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 40,
|
||||
maxWidth: 480,
|
||||
top: -20,
|
||||
},
|
||||
title: {
|
||||
color: theme.sidebarText,
|
||||
textAlign: 'center',
|
||||
...typography('Heading', 400, 'SemiBold'),
|
||||
},
|
||||
paragraph: {
|
||||
marginTop: 8,
|
||||
textAlign: 'center',
|
||||
color: changeOpacity(theme.sidebarText, 0.72),
|
||||
...typography('Body', 200),
|
||||
},
|
||||
}));
|
||||
|
||||
function EmptyUnreads({onlyUnreads}: Props) {
|
||||
const theme = useTheme();
|
||||
const serverUrl = useServerUrl();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const buttonStyle = useMemo(() => [buttonBackgroundStyle(theme, 'lg', 'tertiary', 'inverted'), styles.button],
|
||||
[theme]);
|
||||
|
||||
const onPress = () => {
|
||||
showUnreadChannelsOnly(serverUrl, !onlyUnreads);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<EmptyIllustration/>
|
||||
<FormattedText
|
||||
defaultMessage='No more unreads'
|
||||
id='unreads.empty.title'
|
||||
style={styles.title}
|
||||
testID='unreads.empty.title'
|
||||
/>
|
||||
<FormattedText
|
||||
defaultMessage={'Turn off the unread filter to show all your channels.'}
|
||||
id='unreads.empty.paragraph'
|
||||
style={styles.paragraph}
|
||||
testID='unreads.empty.paragraph'
|
||||
/>
|
||||
<TouchableWithFeedback
|
||||
style={buttonStyle}
|
||||
onPress={onPress}
|
||||
type={'opacity'}
|
||||
>
|
||||
<FormattedText
|
||||
id='unreads.empty.show_all'
|
||||
defaultMessage='Show all'
|
||||
style={buttonTextStyle(theme, 'lg', 'tertiary', 'inverted')}
|
||||
/>
|
||||
</TouchableWithFeedback>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default EmptyUnreads;
|
||||
@@ -9,7 +9,7 @@ import {combineLatestWith, map, switchMap} from 'rxjs/operators';
|
||||
import {Preferences} from '@constants';
|
||||
import {getPreferenceAsBool} from '@helpers/api/preference';
|
||||
import {filterAndSortMyChannels, makeChannelsMap} from '@helpers/database';
|
||||
import {getChannelById, observeChannelsByLastPostAt, observeNotifyPropsByChannels, queryMyChannelUnreads} from '@queries/servers/channel';
|
||||
import {getChannelById, observeChannelsByLastPostAt, observeCurrentChannel, observeNotifyPropsByChannels, queryMyChannelUnreads} from '@queries/servers/channel';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {observeLastUnreadChannelId} from '@queries/servers/system';
|
||||
|
||||
@@ -45,7 +45,10 @@ const enhanced = withObservables(['currentTeamId', 'isTablet', 'onlyUnreads'], (
|
||||
|
||||
const unreadChannels = unreadsOnTop.pipe(switchMap((gU) => {
|
||||
if (gU || onlyUnreads) {
|
||||
const lastUnread = isTablet ? observeLastUnreadChannelId(database).pipe(switchMap(getC)) : of$(undefined);
|
||||
const lastUnread = isTablet ? observeLastUnreadChannelId(database).pipe(
|
||||
switchMap(getC),
|
||||
switchMap((ch) => (ch ? of$(ch) : observeCurrentChannel(database))),
|
||||
) : of$(undefined);
|
||||
const myUnreadChannels = queryMyChannelUnreads(database, currentTeamId).observeWithColumns(['last_post_at']);
|
||||
const notifyProps = myUnreadChannels.pipe(switchMap((cs) => observeNotifyPropsByChannels(database, cs)));
|
||||
const channels = myUnreadChannels.pipe(switchMap((myChannels) => observeChannelsByLastPostAt(database, myChannels)));
|
||||
|
||||
@@ -22,6 +22,7 @@ describe('components/channel_list/categories/body', () => {
|
||||
<UnreadsCategory
|
||||
unreadChannels={[]}
|
||||
onChannelSwitch={() => undefined}
|
||||
onlyUnreads={false}
|
||||
/>,
|
||||
{database},
|
||||
);
|
||||
|
||||
@@ -7,12 +7,20 @@ import {FlatList, Text} from 'react-native';
|
||||
|
||||
import ChannelItem from '@components/channel_item';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import Empty from './empty_state';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
empty: {
|
||||
alignItems: 'center',
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
heading: {
|
||||
color: changeOpacity(theme.sidebarText, 0.64),
|
||||
...typography('Heading', 75),
|
||||
@@ -24,16 +32,18 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
}));
|
||||
|
||||
type UnreadCategoriesProps = {
|
||||
unreadChannels: ChannelModel[];
|
||||
onChannelSwitch: (channelId: string) => void;
|
||||
onlyUnreads: boolean;
|
||||
unreadChannels: ChannelModel[];
|
||||
}
|
||||
|
||||
const extractKey = (item: ChannelModel) => item.id;
|
||||
|
||||
const UnreadCategories = ({onChannelSwitch, unreadChannels}: UnreadCategoriesProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const UnreadCategories = ({onChannelSwitch, onlyUnreads, unreadChannels}: UnreadCategoriesProps) => {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const isTablet = useIsTablet();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const renderItem = useCallback(({item}: {item: ChannelModel}) => {
|
||||
return (
|
||||
@@ -44,20 +54,31 @@ const UnreadCategories = ({onChannelSwitch, unreadChannels}: UnreadCategoriesPro
|
||||
);
|
||||
}, [onChannelSwitch]);
|
||||
|
||||
if (!unreadChannels.length) {
|
||||
const showEmptyState = onlyUnreads && !unreadChannels.length;
|
||||
const showTitle = !onlyUnreads || (onlyUnreads && !showEmptyState);
|
||||
const EmptyState = showEmptyState && !isTablet ? (
|
||||
<Empty onlyUnreads={onlyUnreads}/>
|
||||
) : undefined;
|
||||
|
||||
if (!unreadChannels.length && !onlyUnreads) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{showTitle &&
|
||||
<Text
|
||||
style={styles.heading}
|
||||
>
|
||||
{intl.formatMessage({id: 'mobile.channel_list.unreads', defaultMessage: 'UNREADS'})}
|
||||
</Text>
|
||||
}
|
||||
<FlatList
|
||||
contentContainerStyle={showEmptyState && !isTablet && styles.empty}
|
||||
data={unreadChannels}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={extractKey}
|
||||
ListEmptyComponent={EmptyState}
|
||||
removeClippedSubviews={true}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -81,7 +81,7 @@ const ThreadsButton = ({currentChannelId, onlyUnreads, unreadsAndMentions}: Prop
|
||||
return [container, icon, text];
|
||||
}, [customStyles, isActive, styles, unreads]);
|
||||
|
||||
if (onlyUnreads && !unreads && !mentions) {
|
||||
if (onlyUnreads && !isActive && !unreads && !mentions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -241,16 +241,16 @@ export const buttonBackgroundStyle = (
|
||||
},
|
||||
inverted: {
|
||||
default: {
|
||||
backgroundColor: changeOpacity('#FFFFFF', 0.12),
|
||||
backgroundColor: changeOpacity(theme.sidebarText, 0.12),
|
||||
},
|
||||
hover: {
|
||||
backgroundColor: changeOpacity('#FFFFFF', 0.16),
|
||||
backgroundColor: changeOpacity(theme.sidebarText, 0.16),
|
||||
},
|
||||
active: {
|
||||
backgroundColor: changeOpacity('#FFFFFF', 0.24),
|
||||
backgroundColor: changeOpacity(theme.sidebarText, 0.24),
|
||||
},
|
||||
focus: {
|
||||
backgroundColor: changeOpacity('#FFFFFF', 0.08),
|
||||
backgroundColor: changeOpacity(theme.sidebarText, 0.08),
|
||||
borderColor: theme.sidebarTextActiveBorder, // @to-do; needs 32% white?
|
||||
borderWidth: 2,
|
||||
},
|
||||
@@ -388,6 +388,10 @@ export const buttonTextStyle = (
|
||||
color = theme.buttonBg;
|
||||
}
|
||||
|
||||
if (type === 'inverted' && emphasis === 'tertiary') {
|
||||
color = theme.sidebarText;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
main: {
|
||||
fontFamily: 'OpenSans-SemiBold',
|
||||
|
||||
@@ -555,7 +555,6 @@
|
||||
"notification_settings.auto_responder.default_message": "Hello, I am out of office and unable to respond to messages.",
|
||||
"notification_settings.auto_responder.enabled": "Enabled",
|
||||
"notification_settings.auto_responder.footer_message": "Set a custom message that will be automatically sent in response to Direct Messages. Mentions in Public and Private Channels will not trigger the automated reply. Enabling Automatic Replies sets your status to Out of Office and disables email and push notifications.",
|
||||
"notification_settings.auto_responder.message_placeholder": "Message",
|
||||
"notification_settings.mention.reply": "Send reply notifications for",
|
||||
"notification_settings.mentions": "Mentions",
|
||||
"notification_settings.mentions_replies": "Mentions and Replies",
|
||||
@@ -701,6 +700,9 @@
|
||||
"threads.replies": "{count} {count, plural, one {reply} other {replies}}",
|
||||
"threads.unfollowMessage": "Unfollow Message",
|
||||
"threads.unfollowThread": "Unfollow Thread",
|
||||
"unreads.empty.paragraph": "Turn off the unread filter to show all your channels.",
|
||||
"unreads.empty.show_all": "Show all",
|
||||
"unreads.empty.title": "No more unreads",
|
||||
"user.edit_profile.email.auth_service": "Login occurs through {service}. Email cannot be updated. Email address used for notifications is {email}.",
|
||||
"user.edit_profile.email.web_client": "Email must be updated using a web client or desktop application.",
|
||||
"user.settings.general.email": "Email",
|
||||
|
||||
Reference in New Issue
Block a user