[Gekidou] Update DM and thread title/subtitle as well as navbar rounded corners (#6247)
* Update DM and thread title/subtitle as well as navbar rounded corners * Fix rounded corners * ux review
This commit is contained in:
parent
f54f549cbd
commit
591cfa1ae4
13 changed files with 332 additions and 175 deletions
|
|
@ -97,13 +97,12 @@ export const switchToThread = async (serverUrl: string, rootId: string) => {
|
|||
const translations = getTranslations(user.locale);
|
||||
|
||||
// Get title translation or default title message
|
||||
let title = translations[t('thread.header.thread')] || 'Thread';
|
||||
if (channel.type === General.DM_CHANNEL) {
|
||||
title = translations[t('thread.header.thread_dm')] || 'Direct Message Thread';
|
||||
}
|
||||
const title = translations[t('thread.header.thread')] || 'Thread';
|
||||
|
||||
let subtitle = '';
|
||||
if (channel?.type !== General.DM_CHANNEL) {
|
||||
if (channel?.type === General.DM_CHANNEL) {
|
||||
subtitle = channel.displayName;
|
||||
} else {
|
||||
// Get translation or default message
|
||||
subtitle = translations[t('thread.header.thread_in')] || 'in {channelName}';
|
||||
subtitle = subtitle.replace('{channelName}', channel.displayName);
|
||||
|
|
@ -118,6 +117,10 @@ export const switchToThread = async (serverUrl: string, rootId: string) => {
|
|||
color: changeOpacity(theme.sidebarHeaderTextColor, 0.72),
|
||||
text: subtitle,
|
||||
},
|
||||
noBorder: true,
|
||||
scrollEdgeAppearance: {
|
||||
noBorder: true,
|
||||
},
|
||||
rightButtons,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import React from 'react';
|
||||
import Animated, {useAnimatedStyle} from 'react-native-reanimated';
|
||||
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import RoundedHeaderContext from '@components/rounded_header_context';
|
||||
|
||||
type Props = {
|
||||
defaultHeight: number;
|
||||
|
|
@ -12,50 +12,39 @@ type Props = {
|
|||
isLargeTitle: boolean;
|
||||
largeHeight: number;
|
||||
scrollValue?: Animated.SharedValue<number>;
|
||||
theme: Theme;
|
||||
top: number;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
container: {
|
||||
backgroundColor: theme.sidebarBg,
|
||||
height: 16,
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
},
|
||||
content: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderTopLeftRadius: 12,
|
||||
borderTopRightRadius: 12,
|
||||
flex: 1,
|
||||
},
|
||||
}));
|
||||
|
||||
const NavigationHeaderContext = ({
|
||||
defaultHeight,
|
||||
hasSearch,
|
||||
isLargeTitle,
|
||||
largeHeight,
|
||||
scrollValue,
|
||||
theme,
|
||||
top,
|
||||
}: Props) => {
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const marginTop = useAnimatedStyle(() => {
|
||||
const normal = defaultHeight + top;
|
||||
const calculated = -(top + (scrollValue?.value || 0));
|
||||
const searchHeight = hasSearch ? defaultHeight + 9 : 0;
|
||||
if (!isLargeTitle) {
|
||||
return {marginTop: Math.max((normal + calculated), normal)};
|
||||
let margin: number;
|
||||
if (isLargeTitle) {
|
||||
margin = Math.max((-(scrollValue?.value || 0) + largeHeight + searchHeight), normal);
|
||||
} else {
|
||||
margin = Math.max((normal + calculated), normal);
|
||||
}
|
||||
|
||||
return {marginTop: Math.max((-(scrollValue?.value || 0) + largeHeight + searchHeight), normal)};
|
||||
return {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
marginTop: margin,
|
||||
};
|
||||
}, [defaultHeight, largeHeight, isLargeTitle, hasSearch]);
|
||||
|
||||
return (
|
||||
<Animated.View style={[styles.container, marginTop]}>
|
||||
<Animated.View style={styles.content}/>
|
||||
<Animated.View style={marginTop}>
|
||||
<RoundedHeaderContext/>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -55,11 +55,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
|||
subtitleContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: Platform.select({android: 'flex-start', ios: 'center'}),
|
||||
left: Platform.select({ios: undefined, default: 3}),
|
||||
},
|
||||
subtitle: {
|
||||
color: changeOpacity(theme.sidebarHeaderTextColor, 0.72),
|
||||
fontFamily: 'OpenSans',
|
||||
fontSize: 12,
|
||||
...typography('Body', 75),
|
||||
lineHeight: 12,
|
||||
marginBottom: 8,
|
||||
marginTop: 2,
|
||||
|
|
|
|||
|
|
@ -129,7 +129,6 @@ const NavigationHeader = ({
|
|||
isLargeTitle={isLargeTitle}
|
||||
largeHeight={largeHeight}
|
||||
scrollValue={scrollValue}
|
||||
theme={theme}
|
||||
top={insets.top}
|
||||
/>
|
||||
}
|
||||
|
|
|
|||
36
app/components/rounded_header_context/index.tsx
Normal file
36
app/components/rounded_header_context/index.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// 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 {useTheme} from '@context/theme';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
container: {
|
||||
backgroundColor: theme.sidebarBg,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
},
|
||||
content: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderTopLeftRadius: 12,
|
||||
borderTopRightRadius: 12,
|
||||
flex: 1,
|
||||
},
|
||||
}));
|
||||
|
||||
const RoundedHeaderContext = () => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.content}/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default RoundedHeaderContext;
|
||||
|
|
@ -1,37 +1,24 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {DeviceEventEmitter, Keyboard, Platform, StyleSheet, View} from 'react-native';
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {DeviceEventEmitter, StyleSheet, View} from 'react-native';
|
||||
import {KeyboardTrackingViewRef} from 'react-native-keyboard-tracking-view';
|
||||
import {Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FreezeScreen from '@components/freeze_screen';
|
||||
import NavigationHeader from '@components/navigation_header';
|
||||
import PostDraft from '@components/post_draft';
|
||||
import {Events, Navigation} from '@constants';
|
||||
import {Events} from '@constants';
|
||||
import {ACCESSORIES_CONTAINER_NATIVE_ID} from '@constants/post_draft';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useAppState, useIsTablet} from '@hooks/device';
|
||||
import {useDefaultHeaderHeight} from '@hooks/header';
|
||||
import {popTopScreen} from '@screens/navigation';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
|
||||
import ChannelPostList from './channel_post_list';
|
||||
import OtherMentionsBadge from './other_mentions_badge';
|
||||
|
||||
import type {HeaderRightButton} from '@components/navigation_header/header';
|
||||
import ChannelHeader from './header';
|
||||
|
||||
type ChannelProps = {
|
||||
channelId: string;
|
||||
componentId?: string;
|
||||
displayName: string;
|
||||
isOwnDirectMessage: boolean;
|
||||
memberCount: number;
|
||||
name: string;
|
||||
teamId: string;
|
||||
};
|
||||
|
||||
const edges: Edge[] = ['left', 'right'];
|
||||
|
|
@ -42,54 +29,13 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
const Channel = ({channelId, componentId, displayName, isOwnDirectMessage, memberCount, name, teamId}: ChannelProps) => {
|
||||
const {formatMessage} = useIntl();
|
||||
const Channel = ({channelId, componentId}: ChannelProps) => {
|
||||
const appState = useAppState();
|
||||
const isTablet = useIsTablet();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [shouldRenderPosts, setShouldRenderPosts] = useState(false);
|
||||
const theme = useTheme();
|
||||
const defaultHeight = useDefaultHeaderHeight();
|
||||
const postDraftRef = useRef<KeyboardTrackingViewRef>(null);
|
||||
const rightButtons: HeaderRightButton[] = useMemo(() => ([{
|
||||
iconName: 'magnify',
|
||||
onPress: () => {
|
||||
DeviceEventEmitter.emit(Navigation.NAVIGATE_TO_TAB, {screen: 'Search', params: {searchTerm: `in: ${name}`}});
|
||||
if (!isTablet) {
|
||||
popTopScreen(componentId);
|
||||
}
|
||||
},
|
||||
}, {
|
||||
iconName: Platform.select({android: 'dots-vertical', default: 'dots-horizontal'}),
|
||||
onPress: () => true,
|
||||
buttonType: 'opacity',
|
||||
}]), [channelId, isTablet, name]);
|
||||
|
||||
const leftComponent = useMemo(() => {
|
||||
if (isTablet || !channelId || !teamId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (<OtherMentionsBadge channelId={channelId}/>);
|
||||
}, [isTablet, channelId, teamId]);
|
||||
|
||||
const subtitleCompanion = useMemo(() => (
|
||||
<CompassIcon
|
||||
color={changeOpacity(theme.sidebarHeaderTextColor, 0.72)}
|
||||
name='chevron-right'
|
||||
size={14}
|
||||
/>
|
||||
), []);
|
||||
|
||||
const onBackPress = useCallback(() => {
|
||||
Keyboard.dismiss();
|
||||
popTopScreen(componentId);
|
||||
}, []);
|
||||
|
||||
const onTitlePress = useCallback(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Title Press go to Channel Info', displayName);
|
||||
}, [channelId]);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = DeviceEventEmitter.addListener(Events.PAUSE_KEYBOARD_TRACKING_VIEW, (pause: boolean) => {
|
||||
|
|
@ -104,11 +50,6 @@ const Channel = ({channelId, componentId, displayName, isOwnDirectMessage, membe
|
|||
return () => listener.remove();
|
||||
}, []);
|
||||
|
||||
let title = displayName;
|
||||
if (isOwnDirectMessage) {
|
||||
title = formatMessage({id: 'channel_header.directchannel.you', defaultMessage: '{displayName} (you)'}, {displayName});
|
||||
}
|
||||
|
||||
const marginTop = defaultHeight + (isTablet ? insets.top : 0);
|
||||
useEffect(() => {
|
||||
// This is done so that the header renders
|
||||
|
|
@ -128,17 +69,7 @@ const Channel = ({channelId, componentId, displayName, isOwnDirectMessage, membe
|
|||
edges={edges}
|
||||
testID='channel.screen'
|
||||
>
|
||||
<NavigationHeader
|
||||
isLargeTitle={false}
|
||||
leftComponent={leftComponent}
|
||||
onBackPress={onBackPress}
|
||||
onTitlePress={onTitlePress}
|
||||
rightButtons={rightButtons}
|
||||
showBackButton={!isTablet}
|
||||
subtitle={formatMessage({id: 'channel', defaultMessage: '{count, plural, one {# member} other {# members}}'}, {count: memberCount})}
|
||||
subtitleCompanion={subtitleCompanion}
|
||||
title={title}
|
||||
/>
|
||||
<ChannelHeader componentId={componentId}/>
|
||||
{shouldRenderPosts && Boolean(channelId) &&
|
||||
<>
|
||||
<View style={[styles.flex, {marginTop}]}>
|
||||
|
|
|
|||
161
app/screens/channel/header/header.tsx
Normal file
161
app/screens/channel/header/header.tsx
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {DeviceEventEmitter, Keyboard, Platform, Text, View} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import CustomStatusEmoji from '@components/custom_status/custom_status_emoji';
|
||||
import NavigationHeader from '@components/navigation_header';
|
||||
import {Navigation} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {popTopScreen, showModal} from '@screens/navigation';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import OtherMentionsBadge from './other_mentions_badge';
|
||||
|
||||
import type {HeaderRightButton} from '@components/navigation_header/header';
|
||||
|
||||
type ChannelProps = {
|
||||
channelId: string;
|
||||
customStatus?: UserCustomStatus;
|
||||
isCustomStatusExpired: boolean;
|
||||
componentId?: string;
|
||||
displayName: string;
|
||||
isOwnDirectMessage: boolean;
|
||||
memberCount?: number;
|
||||
searchTerm: string;
|
||||
teamId: string;
|
||||
};
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
customStatusContainer: {
|
||||
flexDirection: 'row',
|
||||
height: 13,
|
||||
left: Platform.select({ios: undefined, default: -2}),
|
||||
marginTop: Platform.select({ios: undefined, default: 1}),
|
||||
},
|
||||
customStatusEmoji: {marginRight: 5},
|
||||
customStatusText: {
|
||||
alignItems: 'center',
|
||||
height: 13,
|
||||
},
|
||||
subtitle: {
|
||||
color: changeOpacity(theme.sidebarHeaderTextColor, 0.72),
|
||||
...typography('Body', 75),
|
||||
lineHeight: 12,
|
||||
marginBottom: 8,
|
||||
marginTop: 2,
|
||||
height: 13,
|
||||
},
|
||||
}));
|
||||
|
||||
const ChannelHeader = ({
|
||||
channelId, componentId, customStatus, displayName,
|
||||
isCustomStatusExpired, isOwnDirectMessage, memberCount,
|
||||
searchTerm, teamId,
|
||||
}: ChannelProps) => {
|
||||
const {formatMessage} = useIntl();
|
||||
const isTablet = useIsTablet();
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const leftComponent = useMemo(() => {
|
||||
if (isTablet || !channelId || !teamId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (<OtherMentionsBadge channelId={channelId}/>);
|
||||
}, [isTablet, channelId, teamId]);
|
||||
|
||||
const rightButtons: HeaderRightButton[] = useMemo(() => ([{
|
||||
iconName: 'magnify',
|
||||
onPress: () => {
|
||||
DeviceEventEmitter.emit(Navigation.NAVIGATE_TO_TAB, {screen: 'Search', params: {searchTerm: `in: ${searchTerm}`}});
|
||||
if (!isTablet) {
|
||||
popTopScreen(componentId);
|
||||
}
|
||||
},
|
||||
}, {
|
||||
iconName: Platform.select({android: 'dots-vertical', default: 'dots-horizontal'}),
|
||||
onPress: () => true,
|
||||
buttonType: 'opacity',
|
||||
}]), [isTablet, searchTerm]);
|
||||
|
||||
const onBackPress = useCallback(() => {
|
||||
Keyboard.dismiss();
|
||||
popTopScreen(componentId);
|
||||
}, []);
|
||||
|
||||
const onTitlePress = useCallback(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Title Press go to Channel Info');
|
||||
showModal('ChannelInfo', '', {channelId});
|
||||
}, [channelId]);
|
||||
|
||||
let title = displayName;
|
||||
if (isOwnDirectMessage) {
|
||||
title = formatMessage({id: 'channel_header.directchannel.you', defaultMessage: '{displayName} (you)'}, {displayName});
|
||||
}
|
||||
|
||||
let subtitle;
|
||||
if (memberCount) {
|
||||
subtitle = formatMessage({id: 'channel', defaultMessage: '{count, plural, one {# member} other {# members}}'}, {count: memberCount});
|
||||
} else if (!customStatus || !customStatus.text || isCustomStatusExpired) {
|
||||
subtitle = formatMessage({id: 'channel.details', defaultMessage: 'View details'});
|
||||
}
|
||||
|
||||
const subtitleCompanion = useMemo(() => {
|
||||
if (memberCount || !customStatus || !customStatus.text || isCustomStatusExpired) {
|
||||
return (
|
||||
<CompassIcon
|
||||
color={changeOpacity(theme.sidebarHeaderTextColor, 0.72)}
|
||||
name='chevron-right'
|
||||
size={14}
|
||||
/>
|
||||
);
|
||||
} else if (customStatus && customStatus.text) {
|
||||
return (
|
||||
<View style={styles.customStatusContainer}>
|
||||
{Boolean(customStatus.emoji) &&
|
||||
<CustomStatusEmoji
|
||||
customStatus={customStatus}
|
||||
emojiSize={13}
|
||||
style={styles.customStatusEmoji}
|
||||
/>
|
||||
}
|
||||
<View style={styles.customStatusText}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
ellipsizeMode='tail'
|
||||
style={styles.subtitle}
|
||||
>
|
||||
{customStatus.text}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [memberCount, customStatus, isCustomStatusExpired]);
|
||||
|
||||
return (
|
||||
<NavigationHeader
|
||||
isLargeTitle={false}
|
||||
leftComponent={leftComponent}
|
||||
onBackPress={onBackPress}
|
||||
onTitlePress={onTitlePress}
|
||||
rightButtons={rightButtons}
|
||||
showBackButton={!isTablet}
|
||||
subtitle={subtitle}
|
||||
subtitleCompanion={subtitleCompanion}
|
||||
title={title}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelHeader;
|
||||
87
app/screens/channel/header/index.ts
Normal file
87
app/screens/channel/header/index.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {combineLatestWith, switchMap} from 'rxjs/operators';
|
||||
|
||||
import {General} from '@constants';
|
||||
import {observeChannel, observeChannelInfo} from '@queries/servers/channel';
|
||||
import {observeCurrentChannelId, observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system';
|
||||
import {observeUser} from '@queries/servers/user';
|
||||
import {getUserCustomStatus, getUserIdFromChannelName, isCustomStatusExpired as checkCustomStatusIsExpired} from '@utils/user';
|
||||
|
||||
import ChannelHeader from './header';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
const currentUserId = observeCurrentUserId(database);
|
||||
const channelId = observeCurrentChannelId(database);
|
||||
const teamId = observeCurrentTeamId(database);
|
||||
|
||||
const channel = channelId.pipe(
|
||||
switchMap((id) => observeChannel(database, id)),
|
||||
);
|
||||
|
||||
const channelInfo = channelId.pipe(
|
||||
switchMap((id) => observeChannelInfo(database, id)),
|
||||
);
|
||||
|
||||
const dmUser = currentUserId.pipe(
|
||||
combineLatestWith(channel),
|
||||
switchMap(([userId, c]) => {
|
||||
if (c?.type === General.DM_CHANNEL) {
|
||||
const teammateId = getUserIdFromChannelName(userId, c.name);
|
||||
return observeUser(database, teammateId);
|
||||
}
|
||||
|
||||
return of$(undefined);
|
||||
}),
|
||||
);
|
||||
|
||||
const isOwnDirectMessage = currentUserId.pipe(
|
||||
combineLatestWith(dmUser),
|
||||
switchMap(([userId, dm]) => of$(userId === dm?.id)),
|
||||
);
|
||||
|
||||
const customStatus = dmUser.pipe(
|
||||
switchMap((dm) => of$(getUserCustomStatus(dm))),
|
||||
);
|
||||
|
||||
const isCustomStatusExpired = dmUser.pipe(
|
||||
switchMap((dm) => of$(checkCustomStatusIsExpired(dm))),
|
||||
);
|
||||
|
||||
const searchTerm = channel.pipe(
|
||||
combineLatestWith(dmUser),
|
||||
switchMap(([c, dm]) => {
|
||||
if (c?.type === General.DM_CHANNEL) {
|
||||
return of$(dm ? `@${dm.username}` : '');
|
||||
} else if (c?.type === General.GM_CHANNEL) {
|
||||
return of$(`@${c.name}`);
|
||||
}
|
||||
|
||||
return of$(c?.name);
|
||||
}),
|
||||
);
|
||||
|
||||
const displayName = channel.pipe(switchMap((c) => of$(c?.displayName)));
|
||||
const memberCount = channelInfo.pipe(
|
||||
combineLatestWith(dmUser),
|
||||
switchMap(([ci, dm]) => of$(dm ? undefined : ci?.memberCount)));
|
||||
|
||||
return {
|
||||
channelId,
|
||||
customStatus,
|
||||
displayName,
|
||||
isCustomStatusExpired,
|
||||
isOwnDirectMessage,
|
||||
memberCount,
|
||||
searchTerm,
|
||||
teamId,
|
||||
};
|
||||
});
|
||||
|
||||
export default withDatabase(enhanced(ChannelHeader));
|
||||
|
|
@ -17,6 +17,9 @@ type Props = {
|
|||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
minWidth: 24,
|
||||
},
|
||||
badge: {
|
||||
left: 2,
|
||||
position: 'relative',
|
||||
|
|
@ -91,7 +94,7 @@ const OtherMentionsBadge = ({channelId}: Props) => {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.container}>
|
||||
<Badge
|
||||
type='Small'
|
||||
visible={count > 0}
|
||||
|
|
@ -3,67 +3,15 @@
|
|||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {combineLatest, of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {General} from '@constants';
|
||||
import {observeChannel, observeChannelInfo} from '@queries/servers/channel';
|
||||
import {observeCurrentChannelId, observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system';
|
||||
import {observeUser} from '@queries/servers/user';
|
||||
import {getUserIdFromChannelName} from '@utils/user';
|
||||
import {observeCurrentChannelId} from '@queries/servers/system';
|
||||
|
||||
import Channel from './channel';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
const currentUserId = observeCurrentUserId(database);
|
||||
const channelId = observeCurrentChannelId(database);
|
||||
const teamId = observeCurrentTeamId(database);
|
||||
|
||||
const channel = channelId.pipe(
|
||||
switchMap((id) => observeChannel(database, id)),
|
||||
);
|
||||
|
||||
const channelInfo = channelId.pipe(
|
||||
switchMap((id) => observeChannelInfo(database, id)),
|
||||
);
|
||||
|
||||
const isOwnDirectMessage = combineLatest([currentUserId, channel]).pipe(
|
||||
switchMap(([userId, ch]) => {
|
||||
if (ch?.type === General.DM_CHANNEL) {
|
||||
const teammateId = getUserIdFromChannelName(userId, ch.name);
|
||||
return of$(userId === teammateId);
|
||||
}
|
||||
|
||||
return of$(false);
|
||||
}),
|
||||
);
|
||||
|
||||
const displayName = channel.pipe(switchMap((c) => of$(c?.displayName)));
|
||||
const name = combineLatest([currentUserId, channel]).pipe(switchMap(([userId, c]) => {
|
||||
if (c?.type === General.DM_CHANNEL) {
|
||||
const teammateId = getUserIdFromChannelName(userId, c.name);
|
||||
return observeUser(database, teammateId).pipe(
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
switchMap((u) => (u ? of$(`@${u.username}`) : of$('Someone'))),
|
||||
);
|
||||
} else if (c?.type === General.GM_CHANNEL) {
|
||||
return of$(`@${c.name}`);
|
||||
}
|
||||
|
||||
return of$(c?.name);
|
||||
}));
|
||||
const memberCount = channelInfo.pipe(switchMap((ci) => of$(ci?.memberCount || 0)));
|
||||
|
||||
return {
|
||||
channelId,
|
||||
displayName,
|
||||
isOwnDirectMessage,
|
||||
memberCount,
|
||||
name,
|
||||
teamId,
|
||||
};
|
||||
});
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
|
||||
channelId: observeCurrentChannelId(database),
|
||||
}));
|
||||
|
||||
export default withDatabase(enhanced(Channel));
|
||||
|
|
|
|||
|
|
@ -131,10 +131,13 @@ Navigation.setDefaultOptions({
|
|||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
},
|
||||
backButton: {
|
||||
enableMenu: false,
|
||||
},
|
||||
subtitle: {
|
||||
fontFamily: 'OpenSans',
|
||||
fontSize: 12,
|
||||
fontWeight: '500',
|
||||
fontWeight: '400',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
|||
|
||||
import FreezeScreen from '@components/freeze_screen';
|
||||
import PostDraft from '@components/post_draft';
|
||||
import RoundedHeaderContext from '@components/rounded_header_context';
|
||||
import {THREAD_ACCESSORIES_CONTAINER_NATIVE_ID} from '@constants/post_draft';
|
||||
import {useAppState} from '@hooks/device';
|
||||
import useDidUpdate from '@hooks/did_update';
|
||||
|
|
@ -24,15 +25,12 @@ type ThreadProps = {
|
|||
|
||||
const edges: Edge[] = ['left', 'right'];
|
||||
|
||||
const getStyleSheet = StyleSheet.create(() => ({
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
}));
|
||||
const styles = StyleSheet.create({
|
||||
flex: {flex: 1},
|
||||
});
|
||||
|
||||
const Thread = ({componentId, rootPost}: ThreadProps) => {
|
||||
const appState = useAppState();
|
||||
const styles = getStyleSheet();
|
||||
const postDraftRef = useRef<KeyboardTrackingViewRef>(null);
|
||||
|
||||
useDidUpdate(() => {
|
||||
|
|
@ -49,6 +47,7 @@ const Thread = ({componentId, rootPost}: ThreadProps) => {
|
|||
edges={edges}
|
||||
testID='thread.screen'
|
||||
>
|
||||
<RoundedHeaderContext/>
|
||||
{Boolean(rootPost?.id) &&
|
||||
<>
|
||||
<View style={styles.flex}>
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@
|
|||
"channel_modal.optional": "(optional)",
|
||||
"channel_modal.purpose": "Purpose",
|
||||
"channel_modal.purposeEx": "A channel to file bugs and improvements",
|
||||
"channel.details": "View details",
|
||||
"combined_system_message.added_to_channel.many_expanded": "{users} and {lastUser} were **added to the channel** by {actor}.",
|
||||
"combined_system_message.added_to_channel.one": "{firstUser} **added to the channel** by {actor}.",
|
||||
"combined_system_message.added_to_channel.one_you": "You were **added to the channel** by {actor}.",
|
||||
|
|
@ -477,8 +478,6 @@
|
|||
"mobile.unsupported_server.title": "Unsupported server version",
|
||||
"mobile.user_list.deactivated": "Deactivated",
|
||||
"mobile.write_storage_permission_denied_description": "Save files to your device. Open Settings to grant {applicationName} write access to files on this device.",
|
||||
"mobile.youtube_playback_error.description": "An error occurred while trying to play the YouTube video.\nDetails: {details}",
|
||||
"mobile.youtube_playback_error.title": "YouTube playback error",
|
||||
"modal.manual_status.auto_responder.message_": "Would you like to switch your status to \"{status}\" and disable Automatic Replies?",
|
||||
"modal.manual_status.auto_responder.message_away": "Would you like to switch your status to \"Away\" and disable Automatic Replies?",
|
||||
"modal.manual_status.auto_responder.message_dnd": "Would you like to switch your status to \"Do Not Disturb\" and disable Automatic Replies?",
|
||||
|
|
@ -576,7 +575,6 @@
|
|||
"team_list.no_other_teams.description": "To join another team, ask a Team Admin for an invitation, or create your own team.",
|
||||
"team_list.no_other_teams.title": "No additional teams to join",
|
||||
"thread.header.thread": "Thread",
|
||||
"thread.header.thread_dm": "Direct Message Thread",
|
||||
"thread.header.thread_in": "in {channelName}",
|
||||
"thread.noReplies": "No replies yet",
|
||||
"thread.options.title": "THREAD ACTIONS",
|
||||
|
|
|
|||
Loading…
Reference in a new issue