forked from Ivasoft/mattermost-mobile
Moves collapse animation to FlatList, updates timings (#6220)
* Moves collapse animation to FlatList, updates timings * dev review * filters unreads from other categories & removes duplicate Co-authored-by: Elias Nahum <nahumhbl@gmail.com> Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
@@ -2,129 +2,109 @@
|
||||
|
||||
exports[`components/channel_list/categories/body/channel_item should match snapshot 1`] = `
|
||||
<View
|
||||
animatedStyle={
|
||||
Object {
|
||||
"value": Object {
|
||||
"height": 40,
|
||||
"marginVertical": 2,
|
||||
"opacity": 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
accessible={true}
|
||||
collapsable={false}
|
||||
focusable={true}
|
||||
onClick={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"height": 40,
|
||||
"marginVertical": 2,
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
accessible={true}
|
||||
collapsable={false}
|
||||
focusable={true}
|
||||
onClick={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"opacity": 1,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 40,
|
||||
"paddingHorizontal": 20,
|
||||
},
|
||||
false,
|
||||
undefined,
|
||||
Object {
|
||||
"minHeight": 40,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="channel_list_item.hello.collapsed.true"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 40,
|
||||
"paddingHorizontal": 20,
|
||||
},
|
||||
false,
|
||||
undefined,
|
||||
Object {
|
||||
"minHeight": 40,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
testID="channel_list_item.hello.collapsed.false"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
},
|
||||
Object {
|
||||
"height": 24,
|
||||
"width": 24,
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
<Icon
|
||||
name="globe"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
Object {
|
||||
"height": 24,
|
||||
"width": 24,
|
||||
"fontSize": 24,
|
||||
"left": 1,
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
testID="undefined.public"
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
},
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
]
|
||||
}
|
||||
testID="channel_list_item.hello.display_name"
|
||||
>
|
||||
<Icon
|
||||
name="globe"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
Object {
|
||||
"fontSize": 24,
|
||||
"left": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="undefined.public"
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
},
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
]
|
||||
}
|
||||
testID="channel_list_item.hello.display_name"
|
||||
>
|
||||
Hello!
|
||||
</Text>
|
||||
</View>
|
||||
Hello!
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -133,129 +113,109 @@ exports[`components/channel_list/categories/body/channel_item should match snaps
|
||||
|
||||
exports[`components/channel_list/categories/body/channel_item should match snapshot when it has a draft 1`] = `
|
||||
<View
|
||||
animatedStyle={
|
||||
Object {
|
||||
"value": Object {
|
||||
"height": 40,
|
||||
"marginVertical": 2,
|
||||
"opacity": 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
accessible={true}
|
||||
collapsable={false}
|
||||
focusable={true}
|
||||
onClick={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"height": 40,
|
||||
"marginVertical": 2,
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
accessible={true}
|
||||
collapsable={false}
|
||||
focusable={true}
|
||||
onClick={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"opacity": 1,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 40,
|
||||
"paddingHorizontal": 20,
|
||||
},
|
||||
false,
|
||||
undefined,
|
||||
Object {
|
||||
"minHeight": 40,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="channel_list_item.hello.collapsed.true"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 40,
|
||||
"paddingHorizontal": 20,
|
||||
},
|
||||
false,
|
||||
undefined,
|
||||
Object {
|
||||
"minHeight": 40,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
testID="channel_list_item.hello.collapsed.false"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
},
|
||||
Object {
|
||||
"height": 24,
|
||||
"width": 24,
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
<Icon
|
||||
name="pencil-outline"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
Object {
|
||||
"height": 24,
|
||||
"width": 24,
|
||||
"fontSize": 24,
|
||||
"left": 2,
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
testID="undefined.draft"
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
},
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
]
|
||||
}
|
||||
testID="channel_list_item.hello.display_name"
|
||||
>
|
||||
<Icon
|
||||
name="pencil-outline"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
Object {
|
||||
"fontSize": 24,
|
||||
"left": 2,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="undefined.draft"
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
},
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
]
|
||||
}
|
||||
testID="channel_list_item.hello.display_name"
|
||||
>
|
||||
Hello!
|
||||
</Text>
|
||||
</View>
|
||||
Hello!
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -37,10 +37,8 @@ describe('components/channel_list/categories/body/channel_item', () => {
|
||||
membersCount={0}
|
||||
myChannel={myChannel}
|
||||
isMuted={false}
|
||||
collapsed={false}
|
||||
currentUserId={'id'}
|
||||
testID='channel_list_item'
|
||||
isVisible={true}
|
||||
onPress={() => undefined}
|
||||
/>,
|
||||
);
|
||||
@@ -57,10 +55,8 @@ describe('components/channel_list/categories/body/channel_item', () => {
|
||||
membersCount={3}
|
||||
myChannel={myChannel}
|
||||
isMuted={false}
|
||||
collapsed={false}
|
||||
currentUserId={'id'}
|
||||
testID='channel_list_item'
|
||||
isVisible={true}
|
||||
onPress={() => undefined}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useMemo} from 'react';
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleSheet, Text, TouchableOpacity, View} from 'react-native';
|
||||
import Animated, {Easing, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
|
||||
|
||||
import Badge from '@components/badge';
|
||||
import ChannelIcon from '@components/channel_icon';
|
||||
@@ -22,13 +21,11 @@ import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
|
||||
type Props = {
|
||||
channel: ChannelModel;
|
||||
collapsed: boolean;
|
||||
currentUserId: string;
|
||||
hasDraft: boolean;
|
||||
isActive: boolean;
|
||||
isInfo?: boolean;
|
||||
isMuted: boolean;
|
||||
isVisible: boolean;
|
||||
membersCount: number;
|
||||
myChannel?: MyChannelModel;
|
||||
onPress: (channelId: string) => void;
|
||||
@@ -115,8 +112,8 @@ export const textStyle = StyleSheet.create({
|
||||
});
|
||||
|
||||
const ChannelListItem = ({
|
||||
channel, collapsed, currentUserId, hasDraft,
|
||||
isActive, isInfo, isMuted, isVisible, membersCount,
|
||||
channel, currentUserId, hasDraft,
|
||||
isActive, isInfo, isMuted, membersCount,
|
||||
myChannel, onPress, teamDisplayName, testID}: Props) => {
|
||||
const {formatMessage} = useIntl();
|
||||
const theme = useTheme();
|
||||
@@ -126,8 +123,6 @@ const ChannelListItem = ({
|
||||
// Make it brighter if it's not muted, and highlighted or has unreads
|
||||
const isBright = !isMuted && (myChannel && (myChannel.isUnread || myChannel.mentionsCount > 0));
|
||||
|
||||
const shouldCollapse = (collapsed && !isBright) && !isActive;
|
||||
const sharedValue = useSharedValue(shouldCollapse);
|
||||
const height = useMemo(() => {
|
||||
let h = 40;
|
||||
if (isInfo) {
|
||||
@@ -136,18 +131,6 @@ const ChannelListItem = ({
|
||||
return h;
|
||||
}, [teamDisplayName, isInfo, isTablet]);
|
||||
|
||||
useEffect(() => {
|
||||
sharedValue.value = shouldCollapse;
|
||||
}, [shouldCollapse]);
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
marginVertical: withTiming(sharedValue.value ? 0 : 2, {duration: 500}),
|
||||
height: withTiming(sharedValue.value ? 0 : height, {duration: 500}),
|
||||
opacity: withTiming(sharedValue.value ? 0 : 1, {duration: 500, easing: Easing.inOut(Easing.exp)}),
|
||||
};
|
||||
}, [height]);
|
||||
|
||||
const handleOnPress = useCallback(() => {
|
||||
onPress(myChannel?.id || channel.id);
|
||||
}, [channel.id, myChannel?.id]);
|
||||
@@ -169,7 +152,7 @@ const ChannelListItem = ({
|
||||
],
|
||||
[height, isActive, isInfo, styles]);
|
||||
|
||||
if ((!isInfo && (channel.deleteAt > 0 && !isActive)) || !myChannel || !isVisible) {
|
||||
if (!myChannel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -182,37 +165,36 @@ const ChannelListItem = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Animated.View style={animatedStyle}>
|
||||
<TouchableOpacity onPress={handleOnPress}>
|
||||
<>
|
||||
<View
|
||||
style={containerStyle}
|
||||
testID={`${testID}.${channel.name}.collapsed.${collapsed && !isActive}`}
|
||||
>
|
||||
<View style={styles.wrapper}>
|
||||
<ChannelIcon
|
||||
hasDraft={hasDraft}
|
||||
isActive={isInfo ? false : isActive}
|
||||
isInfo={isInfo}
|
||||
isUnread={isBright}
|
||||
isArchived={channel.deleteAt > 0}
|
||||
membersCount={membersCount}
|
||||
name={channel.name}
|
||||
shared={channel.shared}
|
||||
size={24}
|
||||
type={channel.type}
|
||||
isMuted={isMuted}
|
||||
/>
|
||||
<View>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={textStyles}
|
||||
testID={`${testID}.${channel.name}.display_name`}
|
||||
>
|
||||
{displayName}
|
||||
</Text>
|
||||
{isInfo && Boolean(teamDisplayName) && !isTablet &&
|
||||
<TouchableOpacity onPress={handleOnPress}>
|
||||
<>
|
||||
<View
|
||||
style={containerStyle}
|
||||
testID={`${testID}.${channel.name}.collapsed.${!isActive}`}
|
||||
>
|
||||
<View style={styles.wrapper}>
|
||||
<ChannelIcon
|
||||
hasDraft={hasDraft}
|
||||
isActive={isInfo ? false : isActive}
|
||||
isInfo={isInfo}
|
||||
isUnread={isBright}
|
||||
isArchived={channel.deleteAt > 0}
|
||||
membersCount={membersCount}
|
||||
name={channel.name}
|
||||
shared={channel.shared}
|
||||
size={24}
|
||||
type={channel.type}
|
||||
isMuted={isMuted}
|
||||
/>
|
||||
<View>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={textStyles}
|
||||
testID={`${testID}.${channel.name}.display_name`}
|
||||
>
|
||||
{displayName}
|
||||
</Text>
|
||||
{isInfo && Boolean(teamDisplayName) && !isTablet &&
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
@@ -221,34 +203,33 @@ const ChannelListItem = ({
|
||||
>
|
||||
{teamDisplayName}
|
||||
</Text>
|
||||
}
|
||||
</View>
|
||||
{Boolean(teammateId) &&
|
||||
<CustomStatus
|
||||
isInfo={isInfo}
|
||||
userId={teammateId!}
|
||||
/>
|
||||
}
|
||||
{isInfo && Boolean(teamDisplayName) && isTablet &&
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
testID={`${testID}.${teamDisplayName}.display_name`}
|
||||
style={[styles.teamName, styles.teamNameTablet]}
|
||||
>
|
||||
{teamDisplayName}
|
||||
</Text>
|
||||
}
|
||||
</View>
|
||||
<Badge
|
||||
visible={myChannel.mentionsCount > 0}
|
||||
value={myChannel.mentionsCount}
|
||||
style={[styles.badge, isMuted && styles.mutedBadge, isInfo && styles.infoBadge]}
|
||||
{Boolean(teammateId) &&
|
||||
<CustomStatus
|
||||
isInfo={isInfo}
|
||||
userId={teammateId!}
|
||||
/>
|
||||
}
|
||||
{isInfo && Boolean(teamDisplayName) && isTablet &&
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
testID={`${testID}.${teamDisplayName}.display_name`}
|
||||
style={[styles.teamName, styles.teamNameTablet]}
|
||||
>
|
||||
{teamDisplayName}
|
||||
</Text>
|
||||
}
|
||||
</View>
|
||||
</>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
<Badge
|
||||
visible={myChannel.mentionsCount > 0}
|
||||
value={myChannel.mentionsCount}
|
||||
style={[styles.badge, isMuted && styles.mutedBadge, isInfo && styles.infoBadge]}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,14 +4,12 @@
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import React from 'react';
|
||||
import {of as of$, combineLatest} from 'rxjs';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap, distinctUntilChanged} from 'rxjs/operators';
|
||||
|
||||
import {General, Preferences} from '@constants';
|
||||
import {getPreferenceAsBool} from '@helpers/api/preference';
|
||||
import {General} from '@constants';
|
||||
import {observeMyChannel} from '@queries/servers/channel';
|
||||
import {queryDraft} from '@queries/servers/drafts';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {observeCurrentChannelId, observeCurrentUserId} from '@queries/servers/system';
|
||||
import ChannelModel from '@typings/database/models/servers/channel';
|
||||
import MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
@@ -19,18 +17,15 @@ import MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
import ChannelItem from './channel_item';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type PreferenceModel from '@typings/database/models/servers/preference';
|
||||
|
||||
type EnhanceProps = WithDatabaseArgs & {
|
||||
channel: ChannelModel;
|
||||
isInfo?: boolean;
|
||||
isUnreads?: boolean;
|
||||
showTeamName?: boolean;
|
||||
}
|
||||
|
||||
const observeIsMutedSetting = (mc: MyChannelModel) => mc.settings.observe().pipe(switchMap((s) => of$(s?.notifyProps?.mark_unread === 'mention')));
|
||||
|
||||
const enhance = withObservables(['channel', 'isUnreads', 'showTeamName'], ({channel, database, isInfo, isUnreads, showTeamName}: EnhanceProps) => {
|
||||
const enhance = withObservables(['channel', 'showTeamName'], ({channel, database, showTeamName}: EnhanceProps) => {
|
||||
const currentUserId = observeCurrentUserId(database);
|
||||
const myChannel = observeMyChannel(database, channel.id);
|
||||
|
||||
@@ -39,29 +34,6 @@ const enhance = withObservables(['channel', 'isUnreads', 'showTeamName'], ({chan
|
||||
);
|
||||
|
||||
const isActive = observeCurrentChannelId(database).pipe(switchMap((id) => of$(id ? id === channel.id : false)), distinctUntilChanged());
|
||||
const unreadsOnTop = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS).
|
||||
observeWithColumns(['value']).
|
||||
pipe(
|
||||
switchMap((prefs: PreferenceModel[]) => of$(getPreferenceAsBool(prefs, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS, false))),
|
||||
);
|
||||
|
||||
const isVisible = combineLatest([myChannel, unreadsOnTop]).pipe(
|
||||
switchMap(([mc, u]) => {
|
||||
if (!mc) {
|
||||
return of$(false);
|
||||
}
|
||||
|
||||
if (isInfo) {
|
||||
return of$(true);
|
||||
}
|
||||
|
||||
if (isUnreads) {
|
||||
return of$(u);
|
||||
}
|
||||
|
||||
return u ? of$(!mc.isUnread || !mc.mentionsCount) : of$(true);
|
||||
}),
|
||||
);
|
||||
|
||||
const isMuted = myChannel.pipe(
|
||||
switchMap((mc) => {
|
||||
@@ -90,7 +62,6 @@ const enhance = withObservables(['channel', 'isUnreads', 'showTeamName'], ({chan
|
||||
hasDraft,
|
||||
isActive,
|
||||
isMuted,
|
||||
isVisible,
|
||||
membersCount,
|
||||
myChannel,
|
||||
teamDisplayName,
|
||||
|
||||
@@ -170,7 +170,6 @@ const FilteredList = ({
|
||||
}, {displayName}),
|
||||
);
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
await close();
|
||||
@@ -209,7 +208,6 @@ const FilteredList = ({
|
||||
return (
|
||||
<ChannelItem
|
||||
channel={item}
|
||||
collapsed={false}
|
||||
isInfo={true}
|
||||
onPress={onSwitchToChannel}
|
||||
showTeamName={showTeamName}
|
||||
|
||||
@@ -76,7 +76,6 @@ const UnfilteredList = ({close, keyboardHeight, recentChannels, showTeamName, un
|
||||
return (
|
||||
<ChannelItem
|
||||
channel={item}
|
||||
collapsed={false}
|
||||
isInfo={true}
|
||||
onPress={onPress}
|
||||
showTeamName={showTeamName}
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/channel_list/categories/body should match snapshot 1`] = `
|
||||
Object {
|
||||
"children": Array [
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<View
|
||||
animatedStyle={
|
||||
Object {
|
||||
"value": Object {
|
||||
"height": 40,
|
||||
"marginVertical": 2,
|
||||
"opacity": 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
"height": 40,
|
||||
"marginVertical": 2,
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
accessible={true}
|
||||
collapsable={false}
|
||||
focusable={true}
|
||||
onClick={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 40,
|
||||
"paddingHorizontal": 20,
|
||||
},
|
||||
false,
|
||||
undefined,
|
||||
Object {
|
||||
"minHeight": 40,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="category.test_category.channel_list_item.channel.collapsed.false"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
},
|
||||
Object {
|
||||
"height": 24,
|
||||
"width": 24,
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
name="globe"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
Object {
|
||||
"fontSize": 24,
|
||||
"left": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="undefined.public"
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"lineHeight": 24,
|
||||
},
|
||||
Object {
|
||||
"color": "rgba(255,255,255,0.72)",
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
},
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
]
|
||||
}
|
||||
testID="category.test_category.channel_list_item.channel.display_name"
|
||||
>
|
||||
Channel
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>,
|
||||
],
|
||||
"props": Object {
|
||||
"data": Anything,
|
||||
"getItem": [Function],
|
||||
"getItemCount": [Function],
|
||||
"initialNumToRender": 20,
|
||||
"invertStickyHeaders": undefined,
|
||||
"keyExtractor": [Function],
|
||||
"onContentSizeChange": [Function],
|
||||
"onLayout": [Function],
|
||||
"onMomentumScrollBegin": [Function],
|
||||
"onMomentumScrollEnd": [Function],
|
||||
"onScroll": [Function],
|
||||
"onScrollBeginDrag": [Function],
|
||||
"onScrollEndDrag": [Function],
|
||||
"removeClippedSubviews": true,
|
||||
"renderItem": [Function],
|
||||
"scrollEventThrottle": 50,
|
||||
"stickyHeaderIndices": Array [],
|
||||
"style": undefined,
|
||||
"updateCellsBatchingPeriod": 10,
|
||||
"viewabilityConfigCallbackPairs": Array [],
|
||||
"windowSize": 15,
|
||||
},
|
||||
"type": "RCTScrollView",
|
||||
}
|
||||
`;
|
||||
@@ -35,15 +35,14 @@ describe('components/channel_list/categories/body', () => {
|
||||
<CategoryBody
|
||||
category={category}
|
||||
locale={DEFAULT_LOCALE}
|
||||
isTablet={false}
|
||||
onChannelSwitch={() => undefined}
|
||||
/>,
|
||||
{database},
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
expect(wrapper.toJSON()).toMatchSnapshot({
|
||||
props: {data: expect.anything()},
|
||||
});
|
||||
expect(wrapper.toJSON()).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import React, {useCallback, useEffect, useMemo} from 'react';
|
||||
import {FlatList} from 'react-native';
|
||||
import Animated, {Easing, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
|
||||
|
||||
import ChannelItem from '@components/channel_item';
|
||||
import {DMS_CATEGORY} from '@constants/categories';
|
||||
@@ -39,26 +40,40 @@ const CategoryBody = ({sortedChannels, category, hiddenChannelIds, limit, onChan
|
||||
return (
|
||||
<ChannelItem
|
||||
channel={item}
|
||||
collapsed={category.collapsed}
|
||||
testID={`category.${category.displayName.replace(/ /g, '_').toLocaleLowerCase()}.channel_list_item`}
|
||||
onPress={onChannelSwitch}
|
||||
/>
|
||||
);
|
||||
}, [category.collapsed, onChannelSwitch]);
|
||||
}, [onChannelSwitch]);
|
||||
|
||||
const sharedValue = useSharedValue(category.collapsed);
|
||||
|
||||
useEffect(() => {
|
||||
sharedValue.value = category.collapsed;
|
||||
}, [category.collapsed]);
|
||||
|
||||
const height = ids.length ? ids.length * 40 : 0;
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
height: withTiming(sharedValue.value ? 1 : height, {duration: 300}),
|
||||
opacity: withTiming(sharedValue.value ? 0 : 1, {duration: sharedValue.value ? 200 : 300, easing: Easing.inOut(Easing.exp)}),
|
||||
};
|
||||
}, [height]);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={ids}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={extractKey}
|
||||
removeClippedSubviews={true}
|
||||
initialNumToRender={20}
|
||||
windowSize={15}
|
||||
updateCellsBatchingPeriod={10}
|
||||
<Animated.View
|
||||
style={animatedStyle}
|
||||
>
|
||||
<FlatList
|
||||
data={ids}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={extractKey}
|
||||
|
||||
// @ts-expect-error strictMode not exposed on the types
|
||||
strictMode={true}
|
||||
/>
|
||||
// @ts-expect-error strictMode not exposed on the types
|
||||
strictMode={true}
|
||||
/>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,13 +5,14 @@ import {Database} from '@nozbe/watermelondb';
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {combineLatest, of as of$} from 'rxjs';
|
||||
import {map, switchMap, concatAll} from 'rxjs/operators';
|
||||
import {map, switchMap, concatAll, combineLatestWith} from 'rxjs/operators';
|
||||
|
||||
import {General, Preferences} from '@constants';
|
||||
import {DMS_CATEGORY} from '@constants/categories';
|
||||
import {queryChannelsByNames, queryMyChannelSettingsByIds} from '@queries/servers/channel';
|
||||
import {getPreferenceAsBool} from '@helpers/api/preference';
|
||||
import {observeAllMyChannelNotifyProps, queryChannelsByNames, queryMyChannelSettingsByIds} from '@queries/servers/channel';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {observeCurrentUserId} from '@queries/servers/system';
|
||||
import {observeCurrentChannelId, observeCurrentUserId, observeLastUnreadChannelId} from '@queries/servers/system';
|
||||
import {WithDatabaseArgs} from '@typings/database/database';
|
||||
import {getDirectChannelName} from '@utils/channel';
|
||||
|
||||
@@ -104,11 +105,12 @@ type EnhanceProps = {
|
||||
category: CategoryModel;
|
||||
locale: string;
|
||||
currentUserId: string;
|
||||
isTablet: boolean;
|
||||
} & WithDatabaseArgs
|
||||
|
||||
const withUserId = withObservables([], ({database}: WithDatabaseArgs) => ({currentUserId: observeCurrentUserId(database)}));
|
||||
|
||||
const enhance = withObservables(['category', 'locale'], ({category, locale, database, currentUserId}: EnhanceProps) => {
|
||||
const enhance = withObservables(['category', 'isTablet', 'locale'], ({category, locale, isTablet, database, currentUserId}: EnhanceProps) => {
|
||||
const observedCategory = category.observe();
|
||||
const sortedChannels = observedCategory.pipe(
|
||||
switchMap((c) => getSortedChannels(database, c, locale)),
|
||||
@@ -145,10 +147,42 @@ const enhance = withObservables(['category', 'locale'], ({category, locale, data
|
||||
([a, b]) => of$(new Set(a.concat(b))),
|
||||
));
|
||||
|
||||
const unreadsOnTop = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS).
|
||||
observeWithColumns(['value']).
|
||||
pipe(
|
||||
switchMap((prefs: PreferenceModel[]) => of$(getPreferenceAsBool(prefs, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS, false))),
|
||||
);
|
||||
|
||||
const notifyProps = observeAllMyChannelNotifyProps(database);
|
||||
const lastUnreadId = isTablet ? observeLastUnreadChannelId(database) : of$(undefined);
|
||||
const unreadChannelIds = category.myChannels.observeWithColumns(['mentions_count', 'is_unread']).pipe(
|
||||
combineLatestWith(unreadsOnTop, notifyProps, lastUnreadId),
|
||||
map(([my, unreadTop, settings, lastUnread]) => {
|
||||
if (!unreadTop) {
|
||||
return new Set();
|
||||
}
|
||||
return my.reduce<Set<string>>((set, m) => {
|
||||
const isMuted = settings[m.id]?.mark_unread === 'mention';
|
||||
if ((isMuted && m.mentionsCount) || (!isMuted && m.isUnread) || m.id === lastUnread) {
|
||||
set.add(m.id);
|
||||
}
|
||||
return set;
|
||||
}, new Set());
|
||||
}),
|
||||
);
|
||||
|
||||
const currentChannelId = observeCurrentChannelId(database);
|
||||
const filtered = sortedChannels.pipe(
|
||||
combineLatestWith(currentChannelId, unreadChannelIds),
|
||||
map(([channels, ccId, unreadIds]) => {
|
||||
return channels.filter((c) => c && ((c.deleteAt > 0 && c.id === ccId) || !c.deleteAt) && !unreadIds.has(c.id));
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
limit,
|
||||
hiddenChannelIds,
|
||||
sortedChannels,
|
||||
sortedChannels: filtered,
|
||||
category: observedCategory,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import {FlatList, StyleSheet} from 'react-native';
|
||||
|
||||
import {switchToChannelById} from '@actions/remote/channel';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
|
||||
import CategoryBody from './body';
|
||||
import LoadCategoriesError from './error';
|
||||
@@ -35,6 +36,7 @@ const Categories = ({categories, currentTeamId, unreadsOnTop}: Props) => {
|
||||
const intl = useIntl();
|
||||
const listRef = useRef<FlatList>(null);
|
||||
const serverUrl = useServerUrl();
|
||||
const isTablet = useIsTablet();
|
||||
|
||||
const onChannelSwitch = useCallback(async (channelId: string) => {
|
||||
switchToChannelById(serverUrl, channelId);
|
||||
@@ -45,6 +47,7 @@ const Categories = ({categories, currentTeamId, unreadsOnTop}: Props) => {
|
||||
return (
|
||||
<UnreadCategories
|
||||
currentTeamId={currentTeamId}
|
||||
isTablet={isTablet}
|
||||
onChannelSwitch={onChannelSwitch}
|
||||
/>
|
||||
);
|
||||
@@ -54,12 +57,13 @@ const Categories = ({categories, currentTeamId, unreadsOnTop}: Props) => {
|
||||
<CategoryHeader category={data.item}/>
|
||||
<CategoryBody
|
||||
category={data.item}
|
||||
isTablet={isTablet}
|
||||
locale={intl.locale}
|
||||
onChannelSwitch={onChannelSwitch}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}, [currentTeamId, intl.locale, onChannelSwitch]);
|
||||
}, [currentTeamId, intl.locale, isTablet, onChannelSwitch]);
|
||||
|
||||
useEffect(() => {
|
||||
listRef.current?.scrollToOffset({animated: false, offset: 0});
|
||||
@@ -87,11 +91,7 @@ const Categories = ({categories, currentTeamId, unreadsOnTop}: Props) => {
|
||||
showsHorizontalScrollIndicator={false}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyExtractor={extractKey}
|
||||
removeClippedSubviews={true}
|
||||
initialNumToRender={5}
|
||||
windowSize={15}
|
||||
updateCellsBatchingPeriod={10}
|
||||
maxToRenderPerBatch={5}
|
||||
initialNumToRender={categoriesToShow.length}
|
||||
|
||||
// @ts-expect-error strictMode not included in the types
|
||||
strictMode={true}
|
||||
|
||||
@@ -21,7 +21,10 @@ import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type PreferenceModel from '@typings/database/models/servers/preference';
|
||||
|
||||
type WithDatabaseProps = { currentTeamId: string } & WithDatabaseArgs
|
||||
type WithDatabaseProps = WithDatabaseArgs & {
|
||||
currentTeamId: string;
|
||||
isTablet: boolean;
|
||||
}
|
||||
|
||||
type CA = [
|
||||
a: Array<ChannelModel | null>,
|
||||
@@ -42,7 +45,7 @@ const filterMutedFromMyChannels = ([myChannels, notifyProps]: [MyChannelModel[],
|
||||
);
|
||||
};
|
||||
|
||||
const enhanced = withObservables(['currentTeamId'], ({currentTeamId, database}: WithDatabaseProps) => {
|
||||
const enhanced = withObservables(['currentTeamId', 'isTablet'], ({currentTeamId, isTablet, database}: WithDatabaseProps) => {
|
||||
const unreadsOnTop = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS).
|
||||
observeWithColumns(['value']).
|
||||
pipe(
|
||||
@@ -53,9 +56,9 @@ const enhanced = withObservables(['currentTeamId'], ({currentTeamId, database}:
|
||||
|
||||
const unreadChannels = unreadsOnTop.pipe(switchMap((gU) => {
|
||||
if (gU) {
|
||||
const lastUnread = observeLastUnreadChannelId(database).pipe(
|
||||
const lastUnread = isTablet ? observeLastUnreadChannelId(database).pipe(
|
||||
switchMap(getC),
|
||||
);
|
||||
) : of$('');
|
||||
const notifyProps = observeAllMyChannelNotifyProps(database);
|
||||
|
||||
const unreads = queryMyChannelUnreads(database, currentTeamId).observe().pipe(
|
||||
|
||||
@@ -36,8 +36,6 @@ const UnreadCategories = ({onChannelSwitch, unreadChannels}: UnreadCategoriesPro
|
||||
return (
|
||||
<ChannelItem
|
||||
channel={item}
|
||||
collapsed={false}
|
||||
isUnreads={true}
|
||||
onPress={onChannelSwitch}
|
||||
/>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user