forked from Ivasoft/mattermost-mobile
Gekidou category collapse animated (#6049)
* Adds chevron animation for collapsing categories * Adds category body channel item collapsing * Updates category body channel list tests and timing
This commit is contained in:
@@ -84,3 +84,28 @@ export const storeCategories = async (serverUrl: string, categories: CategoryWit
|
||||
|
||||
return {models: flattenedModels};
|
||||
};
|
||||
|
||||
export const toggleCollapseCategory = async (serverUrl: string, categoryId: string) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl].database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const category = await queryCategoryById(database, categoryId);
|
||||
|
||||
if (category) {
|
||||
await database.write(async () => {
|
||||
await category.update(() => {
|
||||
category.collapsed = !category.collapsed;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {category};
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('FAILED TO COLLAPSE CATEGORY', categoryId, error);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,95 +5,114 @@ Object {
|
||||
"children": Array [
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<RNGestureHandlerButton
|
||||
collapsable={false}
|
||||
exclusive={true}
|
||||
onGestureEvent={[Function]}
|
||||
onGestureHandlerEvent={[Function]}
|
||||
onGestureHandlerStateChange={[Function]}
|
||||
onHandlerStateChange={[Function]}
|
||||
rippleColor={0}
|
||||
>
|
||||
<View
|
||||
accessible={true}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
<View
|
||||
animatedStyle={
|
||||
Object {
|
||||
"value": Object {
|
||||
"height": 40,
|
||||
"opacity": 1,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
"height": 40,
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<RNGestureHandlerButton
|
||||
collapsable={false}
|
||||
exclusive={true}
|
||||
onGestureEvent={[Function]}
|
||||
onGestureHandlerEvent={[Function]}
|
||||
onGestureHandlerStateChange={[Function]}
|
||||
onHandlerStateChange={[Function]}
|
||||
rippleColor={0}
|
||||
>
|
||||
<View
|
||||
accessible={true}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"marginBottom": 8,
|
||||
"paddingLeft": 2,
|
||||
"paddingVertical": 4,
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
},
|
||||
Object {
|
||||
"height": 24,
|
||||
"width": 24,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"marginBottom": 8,
|
||||
"paddingLeft": 2,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
name="globe"
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
Object {
|
||||
"fontSize": 24,
|
||||
"left": 1,
|
||||
"height": 24,
|
||||
"width": 24,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
testID="undefined.public"
|
||||
/>
|
||||
>
|
||||
<Icon
|
||||
name="globe"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "rgba(255,255,255,0.4)",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
Object {
|
||||
"fontSize": 24,
|
||||
"left": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="undefined.public"
|
||||
/>
|
||||
</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)",
|
||||
"flex": 1,
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
},
|
||||
false,
|
||||
]
|
||||
}
|
||||
>
|
||||
Channel
|
||||
</Text>
|
||||
</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)",
|
||||
"flex": 1,
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
},
|
||||
false,
|
||||
]
|
||||
}
|
||||
>
|
||||
Channel
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</RNGestureHandlerButton>
|
||||
</RNGestureHandlerButton>
|
||||
</View>
|
||||
</View>
|
||||
</View>,
|
||||
],
|
||||
@@ -101,7 +120,6 @@ Object {
|
||||
"data": Anything,
|
||||
"getItem": [Function],
|
||||
"getItemCount": [Function],
|
||||
"getItemLayout": [Function],
|
||||
"initialNumToRender": 20,
|
||||
"invertStickyHeaders": undefined,
|
||||
"keyExtractor": [Function],
|
||||
|
||||
@@ -6,22 +6,23 @@ import {FlatList} from 'react-native';
|
||||
|
||||
import ChannelListItem from './channel';
|
||||
|
||||
import type CategoryModel from '@typings/database/models/servers/category';
|
||||
|
||||
type Props = {
|
||||
currentChannelId: string;
|
||||
sortedIds: string[];
|
||||
category: CategoryModel;
|
||||
};
|
||||
|
||||
const extractKey = (item: any) => item;
|
||||
const itemLayout = (d: any, index: number) => (
|
||||
{length: 40, offset: 40 * index, index}
|
||||
);
|
||||
|
||||
const CategoryBody = ({currentChannelId, sortedIds}: Props) => {
|
||||
const CategoryBody = ({currentChannelId, sortedIds, category}: Props) => {
|
||||
const ChannelItem = useCallback(({item}: {item: string}) => {
|
||||
return (
|
||||
<ChannelListItem
|
||||
channelId={item}
|
||||
isActive={item === currentChannelId}
|
||||
collapsed={category.collapsed}
|
||||
/>
|
||||
);
|
||||
}, [currentChannelId]);
|
||||
@@ -35,7 +36,6 @@ const CategoryBody = ({currentChannelId, sortedIds}: Props) => {
|
||||
initialNumToRender={20}
|
||||
windowSize={15}
|
||||
updateCellsBatchingPeriod={10}
|
||||
getItemLayout={itemLayout}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,47 +1,49 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/channel_list/categories/body/channel/item should match snapshot 1`] = `
|
||||
<RNGestureHandlerButton
|
||||
collapsable={false}
|
||||
exclusive={true}
|
||||
onGestureEvent={[Function]}
|
||||
onGestureHandlerEvent={[Function]}
|
||||
onGestureHandlerStateChange={[Function]}
|
||||
onHandlerStateChange={[Function]}
|
||||
rippleColor={0}
|
||||
>
|
||||
<View
|
||||
accessible={true}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
<View
|
||||
animatedStyle={
|
||||
Object {
|
||||
"value": Object {
|
||||
"height": 40,
|
||||
"opacity": 1,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
"height": 40,
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<RNGestureHandlerButton
|
||||
collapsable={false}
|
||||
exclusive={true}
|
||||
onGestureEvent={[Function]}
|
||||
onGestureHandlerEvent={[Function]}
|
||||
onGestureHandlerStateChange={[Function]}
|
||||
onHandlerStateChange={[Function]}
|
||||
rippleColor={0}
|
||||
>
|
||||
<View
|
||||
accessible={true}
|
||||
collapsable={false}
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"marginBottom": 8,
|
||||
"paddingLeft": 2,
|
||||
"paddingVertical": 4,
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
},
|
||||
Object {
|
||||
"height": 24,
|
||||
"width": 24,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"marginBottom": 8,
|
||||
"paddingLeft": 2,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
@@ -49,66 +51,82 @@ exports[`components/channel_list/categories/body/channel/item should match snaps
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "rgba(255,255,255,0.16)",
|
||||
"borderRadius": 4,
|
||||
"justifyContent": "center",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
Object {
|
||||
"height": 24,
|
||||
"width": 24,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontFamily": "OpenSans-SemiBold",
|
||||
"fontSize": 12,
|
||||
"fontWeight": "600",
|
||||
"lineHeight": 16,
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "rgba(255,255,255,0.16)",
|
||||
"borderRadius": 4,
|
||||
"justifyContent": "center",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
Object {
|
||||
"fontSize": 12,
|
||||
"height": 24,
|
||||
"width": 24,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="undefined.gm_member_count"
|
||||
>
|
||||
1
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontFamily": "OpenSans-SemiBold",
|
||||
"fontSize": 12,
|
||||
"fontWeight": "600",
|
||||
"lineHeight": 16,
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
Object {
|
||||
"fontSize": 12,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="undefined.gm_member_count"
|
||||
>
|
||||
1
|
||||
</Text>
|
||||
</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)",
|
||||
"flex": 1,
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
},
|
||||
false,
|
||||
]
|
||||
}
|
||||
>
|
||||
Hello!
|
||||
</Text>
|
||||
</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)",
|
||||
"flex": 1,
|
||||
"marginTop": -1,
|
||||
"paddingLeft": 12,
|
||||
},
|
||||
false,
|
||||
]
|
||||
}
|
||||
>
|
||||
Hello!
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</RNGestureHandlerButton>
|
||||
</RNGestureHandlerButton>
|
||||
</View>
|
||||
`;
|
||||
|
||||
@@ -34,6 +34,7 @@ describe('components/channel_list/categories/body/channel/item', () => {
|
||||
isActive={false}
|
||||
isOwnDirectMessage={false}
|
||||
myChannel={myChannel}
|
||||
collapsed={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useMemo} from 'react';
|
||||
import React, {useEffect, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
import {TouchableOpacity} from 'react-native-gesture-handler';
|
||||
import Animated, {Easing, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
|
||||
|
||||
import {switchToChannelById} from '@actions/remote/channel';
|
||||
import ChannelIcon from '@components/channel_icon';
|
||||
@@ -50,9 +51,10 @@ type Props = {
|
||||
isActive: boolean;
|
||||
isOwnDirectMessage: boolean;
|
||||
myChannel: MyChannelModel;
|
||||
collapsed: boolean;
|
||||
}
|
||||
|
||||
const ChannelListItem = ({channel, isActive, isOwnDirectMessage, myChannel}: Props) => {
|
||||
const ChannelListItem = ({channel, isActive, isOwnDirectMessage, myChannel, collapsed}: Props) => {
|
||||
const {formatMessage} = useIntl();
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
@@ -61,6 +63,19 @@ const ChannelListItem = ({channel, isActive, isOwnDirectMessage, myChannel}: Pro
|
||||
// Make it brighter if it's highlighted, or has unreads
|
||||
const bright = myChannel.isUnread || myChannel.mentionsCount > 0;
|
||||
|
||||
const sharedValue = useSharedValue(collapsed && !bright);
|
||||
|
||||
useEffect(() => {
|
||||
sharedValue.value = collapsed && !bright;
|
||||
}, [collapsed, bright]);
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
height: withTiming(sharedValue.value ? 0 : 40, {duration: 500}),
|
||||
opacity: withTiming(sharedValue.value ? 0 : 1, {duration: 500, easing: Easing.inOut(Easing.exp)}),
|
||||
};
|
||||
});
|
||||
|
||||
const switchChannels = () => switchToChannelById(serverUrl, myChannel.id);
|
||||
const membersCount = useMemo(() => {
|
||||
if (channel.type === General.GM_CHANNEL) {
|
||||
@@ -86,27 +101,29 @@ const ChannelListItem = ({channel, isActive, isOwnDirectMessage, myChannel}: Pro
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={switchChannels}>
|
||||
<View style={styles.container}>
|
||||
<ChannelIcon
|
||||
isActive={isActive}
|
||||
isArchived={channel.deleteAt > 0}
|
||||
membersCount={membersCount}
|
||||
name={channel.name}
|
||||
shared={channel.shared}
|
||||
size={24}
|
||||
type={channel.type}
|
||||
/>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={textStyles}
|
||||
>
|
||||
{displayName}
|
||||
</Text>
|
||||
<Animated.View style={animatedStyle}>
|
||||
<TouchableOpacity onPress={switchChannels}>
|
||||
<View style={styles.container}>
|
||||
<ChannelIcon
|
||||
isActive={isActive}
|
||||
isArchived={channel.deleteAt > 0}
|
||||
membersCount={membersCount}
|
||||
name={channel.name}
|
||||
shared={channel.shared}
|
||||
size={24}
|
||||
type={channel.type}
|
||||
/>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={textStyles}
|
||||
>
|
||||
{displayName}
|
||||
</Text>
|
||||
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -82,12 +82,14 @@ const getSortedIds = (database: Database, category: CategoryModel, locale: strin
|
||||
};
|
||||
|
||||
const enhance = withObservables(['category'], ({category, locale, database}: {category: CategoryModel; locale: string} & WithDatabaseArgs) => {
|
||||
const sortedIds = category.observe().pipe(
|
||||
const observedCategory = category.observe();
|
||||
const sortedIds = observedCategory.pipe(
|
||||
switchMap((c) => getSortedIds(database, c, locale)),
|
||||
);
|
||||
|
||||
return {
|
||||
sortedIds,
|
||||
category: observedCategory,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -2,26 +2,76 @@
|
||||
|
||||
exports[`components/channel_list/categories/header should match snapshot 1`] = `
|
||||
<View
|
||||
accessible={true}
|
||||
collapsable={false}
|
||||
focusable={true}
|
||||
onClick={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"marginTop": 12,
|
||||
"paddingLeft": 2,
|
||||
"paddingVertical": 8,
|
||||
"opacity": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(255,255,255,0.64)",
|
||||
"fontFamily": "OpenSans-SemiBold",
|
||||
"fontSize": 12,
|
||||
"fontWeight": "600",
|
||||
"lineHeight": 16,
|
||||
"alignItems": "flex-start",
|
||||
"flexDirection": "row",
|
||||
"marginTop": 12,
|
||||
"paddingLeft": 2,
|
||||
"paddingVertical": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
TEST CATEGORY
|
||||
</Text>
|
||||
<Icon
|
||||
animatedStyle={
|
||||
Object {
|
||||
"value": Object {
|
||||
"transform": Array [
|
||||
Object {
|
||||
"rotate": "0deg",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
collapsable={false}
|
||||
name="chevron-down"
|
||||
size={20}
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(255,255,255,0.64)",
|
||||
"height": 20,
|
||||
"marginRight": 2,
|
||||
"marginTop": -2,
|
||||
"transform": Array [
|
||||
Object {
|
||||
"rotate": "0deg",
|
||||
},
|
||||
],
|
||||
"width": 20,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(255,255,255,0.64)",
|
||||
"fontFamily": "OpenSans-SemiBold",
|
||||
"fontSize": 12,
|
||||
"fontWeight": "600",
|
||||
"lineHeight": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
TEST CATEGORY
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
// 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 React, {useCallback, useEffect} from 'react';
|
||||
import {Text, TouchableOpacity, View} from 'react-native';
|
||||
import Animated, {Easing, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated';
|
||||
|
||||
import {toggleCollapseCategory} from '@actions/local/category';
|
||||
import CompassIcon from '@app/components/compass_icon';
|
||||
import {useServerUrl} from '@app/context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
@@ -15,11 +19,20 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
paddingVertical: 8,
|
||||
marginTop: 12,
|
||||
paddingLeft: 2,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
heading: {
|
||||
color: changeOpacity(theme.sidebarText, 0.64),
|
||||
...typography('Heading', 75),
|
||||
},
|
||||
chevron: {
|
||||
marginTop: -2,
|
||||
marginRight: 2,
|
||||
color: changeOpacity(theme.sidebarText, 0.64),
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
}));
|
||||
|
||||
type Props = {
|
||||
@@ -27,9 +40,33 @@ type Props = {
|
||||
hasChannels: boolean;
|
||||
}
|
||||
|
||||
const AnimatedCompassIcon = Animated.createAnimatedComponent(CompassIcon);
|
||||
|
||||
const CategoryHeader = ({category, hasChannels}: Props) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const serverUrl = useServerUrl();
|
||||
const collapsed = useSharedValue(category.collapsed);
|
||||
|
||||
// Action
|
||||
const toggleCollapse = useCallback(() => toggleCollapseCategory(serverUrl, category.id), [category.id, serverUrl]);
|
||||
|
||||
const rotate = useDerivedValue(() => {
|
||||
return withTiming(collapsed.value ? -90 : 0, {
|
||||
duration: 100,
|
||||
easing: Easing.linear,
|
||||
});
|
||||
});
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
transform: [{rotate: `${rotate.value}deg`}],
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
collapsed.value = category.collapsed;
|
||||
}, [category.collapsed]);
|
||||
|
||||
// Hide favs if empty
|
||||
if (!hasChannels && category.type === 'favorites') {
|
||||
@@ -37,11 +74,18 @@ const CategoryHeader = ({category, hasChannels}: Props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.heading}>
|
||||
{category.displayName.toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity onPress={toggleCollapse}>
|
||||
<View style={styles.container}>
|
||||
<AnimatedCompassIcon
|
||||
name={'chevron-down'}
|
||||
style={[styles.chevron, animatedStyle]}
|
||||
size={20}
|
||||
/>
|
||||
<Text style={styles.heading}>
|
||||
{category.displayName.toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user