Generic badge & fixes to home tab & team sidebar items (#5794)
This commit is contained in:
parent
458a006cde
commit
c01bcb7559
6 changed files with 115 additions and 98 deletions
|
|
@ -2,37 +2,30 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import * as React from 'react';
|
||||
import {Animated, StyleProp, StyleSheet, TextStyle} from 'react-native';
|
||||
import {Animated, Platform, StyleProp, StyleSheet, TextStyle} from 'react-native';
|
||||
|
||||
import {useTheme} from '@context/theme';
|
||||
|
||||
type Props = {
|
||||
backgroundColor?: string;
|
||||
borderColor: string;
|
||||
color?: string;
|
||||
style?: Animated.WithAnimatedValue<StyleProp<TextStyle>>;
|
||||
type?: 'Normal' | 'Small';
|
||||
|
||||
/**
|
||||
* Whether the badge is visible
|
||||
*/
|
||||
visible: boolean;
|
||||
|
||||
/**
|
||||
* Content of the `Badge`.
|
||||
*/
|
||||
children?: string | number;
|
||||
|
||||
/**
|
||||
* Size of the `Badge`.
|
||||
*/
|
||||
size?: number;
|
||||
|
||||
/**
|
||||
* Style object for the tab bar container.
|
||||
*/
|
||||
style?: Animated.WithAnimatedValue<StyleProp<TextStyle>>;
|
||||
/**
|
||||
* Value of the `Badge` for unread dot use a negative value.
|
||||
*/
|
||||
value: number;
|
||||
visible: boolean;
|
||||
};
|
||||
|
||||
export default function Badge({
|
||||
borderColor,
|
||||
color,
|
||||
visible = true,
|
||||
size = 18,
|
||||
children,
|
||||
type = 'Normal',
|
||||
value,
|
||||
style,
|
||||
...rest
|
||||
}: Props) {
|
||||
|
|
@ -66,11 +59,33 @@ export default function Badge({
|
|||
}
|
||||
|
||||
// @ts-expect-error: backgroundColor definitely exists
|
||||
const {backgroundColor = theme.buttonBg, ...restStyle} =
|
||||
const {backgroundColor = rest.backgroundColor || theme.mentionBg, ...restStyle} =
|
||||
StyleSheet.flatten(style) || {};
|
||||
const textColor = theme.buttonColor;
|
||||
const textColor = color || theme.mentionColor;
|
||||
let lineHeight = Platform.select({android: 21, ios: 16.5});
|
||||
let fontSize = 12;
|
||||
let size = value < 0 ? 12 : 22;
|
||||
let minWidth = value < 0 ? size : 26;
|
||||
let additionalStyle;
|
||||
if (type === 'Small') {
|
||||
size = value < 0 ? 12 : 20;
|
||||
lineHeight = Platform.select({android: 19, ios: 15});
|
||||
fontSize = 11;
|
||||
minWidth = value < 0 ? size : 24;
|
||||
}
|
||||
const borderRadius = size / 2;
|
||||
const fontSize = Math.floor((size * 3) / 4);
|
||||
|
||||
let badge: string = value?.toString();
|
||||
if (value < 0) {
|
||||
badge = '';
|
||||
additionalStyle = {paddingHorizontal: 0};
|
||||
} else if (value < 99) {
|
||||
badge = value.toString();
|
||||
additionalStyle = {paddingHorizontal: 5};
|
||||
} else {
|
||||
badge = '99+';
|
||||
additionalStyle = {paddingLeft: 4, paddingRight: 3};
|
||||
}
|
||||
|
||||
return (
|
||||
<Animated.Text
|
||||
|
|
@ -86,20 +101,22 @@ export default function Badge({
|
|||
}),
|
||||
},
|
||||
],
|
||||
borderColor,
|
||||
backgroundColor,
|
||||
color: textColor,
|
||||
fontSize,
|
||||
lineHeight: size - 1,
|
||||
lineHeight,
|
||||
minWidth,
|
||||
height: size,
|
||||
minWidth: size,
|
||||
borderRadius,
|
||||
},
|
||||
styles.container,
|
||||
additionalStyle,
|
||||
restStyle,
|
||||
]}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
{badge}
|
||||
</Animated.Text>
|
||||
);
|
||||
}
|
||||
|
|
@ -113,5 +130,7 @@ const styles = StyleSheet.create({
|
|||
textAlign: 'center',
|
||||
paddingHorizontal: 4,
|
||||
overflow: 'hidden',
|
||||
borderWidth: 2,
|
||||
fontFamily: 'OpenSans-Bold',
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -29,21 +29,27 @@ export default function TeamItem({team, hasUnreads, mentionCount, currentTeamId}
|
|||
const selected = team.id === currentTeamId;
|
||||
|
||||
const hasBadge = Boolean(mentionCount || hasUnreads);
|
||||
let mentionText = mentionCount ? mentionCount.toString() : '';
|
||||
let left = 35;
|
||||
let badgeStyle = styles.unread;
|
||||
let value = mentionCount;
|
||||
if (!mentionCount && hasUnreads) {
|
||||
value = -1;
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case mentionCount > 99:
|
||||
mentionText = '99+';
|
||||
left = 20;
|
||||
case value > 99:
|
||||
badgeStyle = styles.mentionsThreeDigits;
|
||||
break;
|
||||
case mentionCount > 9:
|
||||
left = 26;
|
||||
case value > 9:
|
||||
badgeStyle = styles.mentionsTwoDigits;
|
||||
break;
|
||||
case value > 0:
|
||||
badgeStyle = styles.mentionsOneDigit;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={selected ? styles.containerSelected : styles.container}>
|
||||
<View style={[styles.container, selected ? styles.containerSelected : undefined]}>
|
||||
<TouchableWithFeedback
|
||||
onPress={() => handleTeamChange(serverUrl, team.id)}
|
||||
type='opacity'
|
||||
|
|
@ -57,12 +63,11 @@ export default function TeamItem({team, hasUnreads, mentionCount, currentTeamId}
|
|||
</TouchableWithFeedback>
|
||||
</View>
|
||||
<Badge
|
||||
visible={hasBadge && !selected}
|
||||
style={mentionCount > 0 ? [styles.mentions, {left}] : styles.unread}
|
||||
size={mentionCount > 0 ? 16 : 12}
|
||||
>
|
||||
{mentionText}
|
||||
</Badge>
|
||||
borderColor={theme.sidebarTeamBarBg}
|
||||
visible={hasBadge}
|
||||
style={badgeStyle}
|
||||
value={value}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -70,44 +75,33 @@ export default function TeamItem({team, hasUnreads, mentionCount, currentTeamId}
|
|||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
height: 48,
|
||||
width: 48,
|
||||
height: 54,
|
||||
width: 54,
|
||||
flex: 0,
|
||||
padding: 3,
|
||||
borderRadius: 10,
|
||||
marginVertical: 6,
|
||||
marginVertical: 3,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
containerSelected: {
|
||||
height: 48,
|
||||
width: 48,
|
||||
padding: 3,
|
||||
borderWidth: 3,
|
||||
borderColor: theme.sidebarTextActiveBorder,
|
||||
borderRadius: 10,
|
||||
marginVertical: 6,
|
||||
},
|
||||
unread: {
|
||||
left: 40,
|
||||
top: 3,
|
||||
borderColor: theme.sidebarTeamBarBg,
|
||||
borderWidth: 2,
|
||||
backgroundColor: theme.mentionBg,
|
||||
width: 12,
|
||||
},
|
||||
mentions: {
|
||||
mentionsOneDigit: {
|
||||
top: 1,
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold',
|
||||
fontFamily: 'OpenSans',
|
||||
lineHeight: 15,
|
||||
borderColor: theme.sidebarTeamBarBg,
|
||||
alignItems: 'center',
|
||||
borderWidth: 2,
|
||||
minWidth: 22,
|
||||
height: 18,
|
||||
borderRadius: 9,
|
||||
backgroundColor: theme.mentionBg,
|
||||
color: theme.mentionColor,
|
||||
left: 28,
|
||||
},
|
||||
mentionsTwoDigits: {
|
||||
top: 1,
|
||||
left: 26,
|
||||
},
|
||||
mentionsThreeDigits: {
|
||||
top: 1,
|
||||
left: 23,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ export const MAX_BADGE_RIGHT_POSITION = -13;
|
|||
export const LARGE_BADGE_RIGHT_POSITION = -11;
|
||||
export const SMALL_BADGE_RIGHT_POSITION = -9;
|
||||
export const TEAM_SIDEBAR_WIDTH = 72;
|
||||
export const BOTTOM_TAB_ICON_SIZE = 31.2;
|
||||
|
||||
export const TABLET = {
|
||||
SIDEBAR_WIDTH: 320,
|
||||
|
|
@ -118,6 +119,7 @@ const RequiredServer = {
|
|||
export default {
|
||||
...ViewTypes,
|
||||
RequiredServer,
|
||||
BOTTOM_TAB_ICON_SIZE,
|
||||
FEATURE_TOGGLE_PREFIX: 'feature_enabled_',
|
||||
EMBED_PREVIEW: 'embed_preview',
|
||||
LINK_PREVIEW_DISPLAY: 'link_previews',
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@
|
|||
|
||||
import {Q} from '@nozbe/watermelondb';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Platform, View} from 'react-native';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
|
||||
import Badge from '@components/badge';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {BOTTOM_TAB_ICON_SIZE} from '@constants/view';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
|
||||
import type ServersModel from '@typings/database/models/app/servers';
|
||||
import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
|
|
@ -33,29 +34,25 @@ const {SERVERS} = MM_TABLES.APP;
|
|||
const {CHANNEL, MY_CHANNEL} = MM_TABLES.SERVER;
|
||||
const subscriptions: Map<string, UnreadSubscription> = new Map();
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
const style = StyleSheet.create({
|
||||
unread: {
|
||||
left: 16,
|
||||
top: 1,
|
||||
borderColor: theme.centerChannelBg,
|
||||
borderWidth: 2,
|
||||
paddingHorizontal: 0,
|
||||
left: 19,
|
||||
top: 4,
|
||||
},
|
||||
mentions: {
|
||||
fontSize: 10,
|
||||
fontFamily: 'OpenSans-Semibold',
|
||||
lineHeight: Platform.select({android: 15, ios: 12.6}),
|
||||
borderColor: theme.centerChannelBg,
|
||||
borderWidth: 2,
|
||||
minWidth: 18,
|
||||
height: 16,
|
||||
mentionsOneDigit: {
|
||||
left: 12,
|
||||
},
|
||||
}));
|
||||
mentionsTwoDigits: {
|
||||
left: 13,
|
||||
},
|
||||
mentionsThreeDigits: {
|
||||
left: 10,
|
||||
},
|
||||
});
|
||||
|
||||
const Home = ({isFocused, theme}: Props) => {
|
||||
const db = DatabaseManager.appDatabase?.database;
|
||||
const [total, setTotal] = useState<UnreadMessages>({mentions: 0, messages: 0});
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const updateTotal = () => {
|
||||
let messages = 0;
|
||||
|
|
@ -128,30 +125,33 @@ const Home = ({isFocused, theme}: Props) => {
|
|||
}, []);
|
||||
|
||||
let unreadStyle;
|
||||
let text: string | number = '';
|
||||
let size = 16;
|
||||
if (total.mentions) {
|
||||
text = total.mentions > 99 ? '99+' : total.mentions;
|
||||
unreadStyle = style.mentions;
|
||||
unreadStyle = style.mentionsOneDigit;
|
||||
if (total.mentions > 9) {
|
||||
unreadStyle = style.mentionsTwoDigits;
|
||||
} else if (total.mentions > 99) {
|
||||
unreadStyle = style.mentionsThreeDigits;
|
||||
}
|
||||
} else if (total.messages) {
|
||||
unreadStyle = style.unread;
|
||||
size = 12;
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<CompassIcon
|
||||
size={28}
|
||||
size={BOTTOM_TAB_ICON_SIZE}
|
||||
name='home-variant-outline'
|
||||
color={isFocused ? theme.buttonBg : changeOpacity(theme.centerChannelColor, 0.48)}
|
||||
/>
|
||||
<Badge
|
||||
style={[unreadStyle]}
|
||||
backgroundColor={theme.buttonBg}
|
||||
borderColor={theme.centerChannelBg}
|
||||
color={theme.buttonColor}
|
||||
style={unreadStyle}
|
||||
visible={!isFocused && Boolean(unreadStyle)}
|
||||
size={size}
|
||||
>
|
||||
{text}
|
||||
</Badge>
|
||||
type='Small'
|
||||
value={total.mentions || (total.messages * -1)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import React from 'react';
|
|||
import {View} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {BOTTOM_TAB_ICON_SIZE} from '@constants/view';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
|
||||
type Props = {
|
||||
|
|
@ -16,7 +17,7 @@ const Mentions = ({isFocused, theme}: Props) => {
|
|||
return (
|
||||
<View>
|
||||
<CompassIcon
|
||||
size={28}
|
||||
size={BOTTOM_TAB_ICON_SIZE}
|
||||
name='at'
|
||||
color={isFocused ? theme.buttonBg : changeOpacity(theme.centerChannelColor, 0.48)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import React from 'react';
|
|||
import {View} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {BOTTOM_TAB_ICON_SIZE} from '@constants/view';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
|
||||
type Props = {
|
||||
|
|
@ -16,7 +17,7 @@ const Search = ({isFocused, theme}: Props) => {
|
|||
return (
|
||||
<View>
|
||||
<CompassIcon
|
||||
size={28}
|
||||
size={BOTTOM_TAB_ICON_SIZE}
|
||||
name='magnify'
|
||||
color={isFocused ? theme.buttonBg : changeOpacity(theme.centerChannelColor, 0.48)}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Reference in a new issue