diff --git a/native/chat/chat-thread-list-item.react.js b/native/chat/chat-thread-list-item.react.js index bc5fdf8af..56bdcf786 100644 --- a/native/chat/chat-thread-list-item.react.js +++ b/native/chat/chat-thread-list-item.react.js @@ -1,214 +1,218 @@ // @flow import * as React from 'react'; import { Text, View } from 'react-native'; import type { ChatThreadItem } from 'lib/selectors/chat-selectors'; import type { ThreadInfo } from 'lib/types/thread-types'; import type { UserInfo } from 'lib/types/user-types'; import { shortAbsoluteDate } from 'lib/utils/date-utils'; import Button from '../components/button.react'; import ColorSplotch from '../components/color-splotch.react'; import { SingleLine } from '../components/single-line.react'; import ThreadAncestorsLabel from '../components/thread-ancestors-label.react'; import UnreadDot from '../components/unread-dot.react'; import { useColors, useStyles } from '../themes/colors'; import ChatThreadListSeeMoreSidebars from './chat-thread-list-see-more-sidebars.react'; import ChatThreadListSidebar from './chat-thread-list-sidebar.react'; import MessagePreview from './message-preview.react'; import SwipeableThread from './swipeable-thread.react'; type Props = { +data: ChatThreadItem, +onPressItem: ( threadInfo: ThreadInfo, pendingPersonalThreadUserInfo?: UserInfo, ) => void, +onPressSeeMoreSidebars: (threadInfo: ThreadInfo) => void, +onSwipeableWillOpen: (threadInfo: ThreadInfo) => void, +currentlyOpenedSwipeableId: string, }; function ChatThreadListItem({ data, onPressItem, onPressSeeMoreSidebars, onSwipeableWillOpen, currentlyOpenedSwipeableId, }: Props): React.Node { const styles = useStyles(unboundStyles); const colors = useColors(); const lastMessage = React.useMemo(() => { const mostRecentMessageInfo = data.mostRecentMessageInfo; if (!mostRecentMessageInfo) { return ( No messages ); } return ( ); }, [data.mostRecentMessageInfo, data.threadInfo, styles]); + const numOfSidebarsWithExtendedArrow = + data.sidebars.filter(sidebarItem => sidebarItem.type === 'sidebar').length - + 1; + const sidebars = data.sidebars.map((sidebarItem, index) => { if (sidebarItem.type === 'sidebar') { const { type, ...sidebarInfo } = sidebarItem; return ( 0} + extendArrow={index < numOfSidebarsWithExtendedArrow} /> ); } else if (sidebarItem.type === 'seeMore') { return ( ); } else { return ; } }); const onPress = React.useCallback(() => { onPressItem(data.threadInfo, data.pendingPersonalThreadUserInfo); }, [onPressItem, data.threadInfo, data.pendingPersonalThreadUserInfo]); const threadNameStyle = React.useMemo(() => { if (!data.threadInfo.currentUser.unread) { return styles.threadName; } return [styles.threadName, styles.unreadThreadName]; }, [ data.threadInfo.currentUser.unread, styles.threadName, styles.unreadThreadName, ]); const lastActivity = shortAbsoluteDate(data.lastUpdatedTime); const lastActivityStyle = React.useMemo(() => { if (!data.threadInfo.currentUser.unread) { return styles.lastActivity; } return [styles.lastActivity, styles.unreadLastActivity]; }, [ data.threadInfo.currentUser.unread, styles.lastActivity, styles.unreadLastActivity, ]); return ( <> {sidebars} ); } const chatThreadListItemHeight = 70; const spacerHeight = 6; const unboundStyles = { container: { height: chatThreadListItemHeight, justifyContent: 'center', backgroundColor: 'listBackground', }, content: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, colorSplotch: { marginLeft: 6, marginBottom: 12, }, threadDetails: { paddingLeft: 12, paddingRight: 18, justifyContent: 'center', flex: 1, marginTop: 5, }, lastActivity: { color: 'listForegroundTertiaryLabel', fontSize: 14, marginLeft: 10, }, unreadLastActivity: { color: 'listForegroundLabel', fontWeight: 'bold', }, noMessages: { color: 'listForegroundTertiaryLabel', flex: 1, fontSize: 14, fontStyle: 'italic', }, row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, threadName: { color: 'listForegroundSecondaryLabel', flex: 1, fontSize: 21, }, unreadThreadName: { color: 'listForegroundLabel', fontWeight: '500', }, spacer: { height: spacerHeight, }, }; export { ChatThreadListItem, chatThreadListItemHeight, spacerHeight }; diff --git a/native/chat/chat-thread-list-sidebar.react.js b/native/chat/chat-thread-list-sidebar.react.js index c8b9ff510..fee4f1fb2 100644 --- a/native/chat/chat-thread-list-sidebar.react.js +++ b/native/chat/chat-thread-list-sidebar.react.js @@ -1,92 +1,117 @@ // @flow import * as React from 'react'; -import { View, StyleSheet } from 'react-native'; +import { View } from 'react-native'; import type { ThreadInfo, SidebarInfo } from 'lib/types/thread-types'; -import ArrowLong from '../components/arrow-long.react'; +import ExtendedArrow from '../components/arrow-extended.react'; import Arrow from '../components/arrow.react'; +import Button from '../components/button.react'; import UnreadDot from '../components/unread-dot.react'; -import { SidebarItem } from './sidebar-item.react'; +import { useColors, useStyles } from '../themes/colors'; +import { SidebarItem, sidebarHeight } from './sidebar-item.react'; import SwipeableThread from './swipeable-thread.react'; type Props = { +sidebarInfo: SidebarInfo, +onPressItem: (threadInfo: ThreadInfo) => void, +onSwipeableWillOpen: (threadInfo: ThreadInfo) => void, +currentlyOpenedSwipeableId: string, +extendArrow: boolean, }; function ChatThreadListSidebar(props: Props): React.Node { + const colors = useColors(); + const styles = useStyles(unboundStyles); + const { sidebarInfo, onSwipeableWillOpen, currentlyOpenedSwipeableId, onPressItem, extendArrow = false, } = props; let arrow; if (extendArrow) { arrow = ( - - + + ); } else { arrow = ( ); } + const { threadInfo } = sidebarInfo; + + const onPress = React.useCallback(() => onPressItem(threadInfo), [ + threadInfo, + onPressItem, + ]); + return ( - + ); } -const styles = StyleSheet.create({ +const unboundStyles = { arrow: { - left: 27.5, + left: 28, position: 'absolute', top: -12, }, - chatThreadListContainer: { - display: 'flex', - flexDirection: 'row', - }, - longArrow: { + extendedArrow: { left: 28, position: 'absolute', - top: -19, + top: -6, + }, + sidebar: { + alignItems: 'center', + flexDirection: 'row', + width: '100%', + height: sidebarHeight, + paddingLeft: 6, + paddingRight: 18, + backgroundColor: 'listBackground', }, swipeableThreadContainer: { flex: 1, + height: '100%', }, unreadIndicatorContainer: { - display: 'flex', - justifyContent: 'center', + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'flex-start', paddingLeft: 6, width: 56, }, -}); +}; export default ChatThreadListSidebar; diff --git a/native/chat/sidebar-item.react.js b/native/chat/sidebar-item.react.js index ca799b88e..a7d113f0d 100644 --- a/native/chat/sidebar-item.react.js +++ b/native/chat/sidebar-item.react.js @@ -1,84 +1,58 @@ // @flow import * as React from 'react'; -import { Text } from 'react-native'; +import { Text, View } from 'react-native'; -import type { ThreadInfo, SidebarInfo } from 'lib/types/thread-types'; +import type { SidebarInfo } from 'lib/types/thread-types'; import { shortAbsoluteDate } from 'lib/utils/date-utils'; -import Button from '../components/button.react'; import { SingleLine } from '../components/single-line.react'; -import { useColors, useStyles } from '../themes/colors'; -import type { ViewStyle } from '../types/styles'; +import { useStyles } from '../themes/colors'; type Props = { +sidebarInfo: SidebarInfo, - +onPressItem: (threadInfo: ThreadInfo) => void, - +style?: ?ViewStyle, }; function SidebarItem(props: Props): React.Node { const { lastUpdatedTime } = props.sidebarInfo; const lastActivity = shortAbsoluteDate(lastUpdatedTime); const { threadInfo } = props.sidebarInfo; const styles = useStyles(unboundStyles); const unreadStyle = threadInfo.currentUser.unread ? styles.unread : null; - const { onPressItem } = props; - const onPress = React.useCallback(() => onPressItem(threadInfo), [ - threadInfo, - onPressItem, - ]); - - const colors = useColors(); - - const sidebarStyle = React.useMemo(() => { - return [styles.sidebar, props.style]; - }, [props.style, styles.sidebar]); - return ( - + ); } const sidebarHeight = 30; const unboundStyles = { + itemContainer: { + flexDirection: 'row', + height: sidebarHeight, + alignItems: 'center', + }, unread: { color: 'listForegroundLabel', fontWeight: 'bold', }, - sidebar: { - height: sidebarHeight, - flexDirection: 'row', - display: 'flex', - paddingLeft: 6, - paddingRight: 18, - alignItems: 'center', - backgroundColor: 'listBackground', - }, name: { color: 'listForegroundSecondaryLabel', flex: 1, fontSize: 16, paddingLeft: 3, paddingBottom: 2, }, lastActivity: { color: 'listForegroundTertiaryLabel', fontSize: 14, marginLeft: 10, }, }; export { SidebarItem, sidebarHeight }; diff --git a/native/chat/sidebar-list-modal.react.js b/native/chat/sidebar-list-modal.react.js index f86a54f60..b32c8589c 100644 --- a/native/chat/sidebar-list-modal.react.js +++ b/native/chat/sidebar-list-modal.react.js @@ -1,141 +1,200 @@ // @flow import * as React from 'react'; -import { TextInput, FlatList, StyleSheet, View } from 'react-native'; +import { TextInput, FlatList, View } from 'react-native'; import { useSearchSidebars } from 'lib/hooks/search-sidebars'; import type { ThreadInfo, SidebarInfo } from 'lib/types/thread-types'; -import ArrowLong from '../components/arrow-long.react'; +import ExtendedArrow from '../components/arrow-extended.react'; +import Arrow from '../components/arrow.react'; +import Button from '../components/button.react'; import Modal from '../components/modal.react'; import Search from '../components/search.react'; import type { RootNavigationProp } from '../navigation/root-navigator.react'; import type { NavigationRoute } from '../navigation/route-names'; -import { useIndicatorStyle } from '../themes/colors'; +import { useColors, useIndicatorStyle, useStyles } from '../themes/colors'; import { waitForModalInputFocus } from '../utils/timers'; import { useNavigateToThread } from './message-list-types'; import { SidebarItem } from './sidebar-item.react'; export type SidebarListModalParams = { +threadInfo: ThreadInfo, }; function keyExtractor(sidebarInfo: SidebarInfo) { return sidebarInfo.threadInfo.id; } function getItemLayout(data: ?$ReadOnlyArray, index: number) { return { length: 24, offset: 24 * index, index }; } type Props = { +navigation: RootNavigationProp<'SidebarListModal'>, +route: NavigationRoute<'SidebarListModal'>, }; function SidebarListModal(props: Props): React.Node { const { listData, searchState, setSearchState, onChangeSearchInputText, } = useSearchSidebars(props.route.params.threadInfo); const searchTextInputRef = React.useRef(); const setSearchTextInputRef = React.useCallback( async (textInput: ?React.ElementRef) => { searchTextInputRef.current = textInput; if (!textInput) { return; } await waitForModalInputFocus(); if (searchTextInputRef.current) { searchTextInputRef.current.focus(); } }, [], ); const navigateToThread = useNavigateToThread(); const onPressItem = React.useCallback( (threadInfo: ThreadInfo) => { setSearchState({ text: '', results: new Set(), }); if (searchTextInputRef.current) { searchTextInputRef.current.blur(); } navigateToThread({ threadInfo }); }, [navigateToThread, setSearchState], ); + const styles = useStyles(unboundStyles); + + const numOfSidebarsWithExtendedArrow = React.useMemo( + () => listData.length - 1, + [listData], + ); + const renderItem = React.useCallback( - (row: { item: SidebarInfo, ... }) => { + (row: { item: SidebarInfo, index: number, ... }) => { + let extendArrow: boolean = false; + if (row.index < numOfSidebarsWithExtendedArrow) { + extendArrow = true; + } return ( - - - - - - - - - + ); }, - [onPressItem], + [onPressItem, numOfSidebarsWithExtendedArrow], ); const indicatorStyle = useIndicatorStyle(); return ( ); } -const styles = StyleSheet.create({ +function Item(props: { + item: SidebarInfo, + onPressItem: (threadInfo: ThreadInfo) => void, + extendArrow: boolean, +}): React.Node { + const { item, onPressItem, extendArrow } = props; + const { threadInfo } = item; + + const onPressButton = React.useCallback(() => onPressItem(threadInfo), [ + onPressItem, + threadInfo, + ]); + + const colors = useColors(); + const styles = useStyles(unboundStyles); + + let arrow; + if (extendArrow) { + arrow = ( + + + + ); + } else { + arrow = ( + + + + ); + } + + return ( + + ); +} + +const unboundStyles = { arrow: { position: 'absolute', - top: -19, + top: -12, + }, + extendedArrow: { + position: 'absolute', + top: -6, }, search: { marginBottom: 8, }, sidebar: { - backgroundColor: 'transparent', + backgroundColor: 'listBackground', paddingLeft: 0, paddingRight: 5, }, sidebarItemContainer: { flex: 1, }, sidebarRowContainer: { flex: 1, flexDirection: 'row', }, spacer: { width: 30, }, -}); +}; export default SidebarListModal; diff --git a/native/components/arrow-extended.react.js b/native/components/arrow-extended.react.js new file mode 100644 index 000000000..836aefad9 --- /dev/null +++ b/native/components/arrow-extended.react.js @@ -0,0 +1,23 @@ +// @flow + +import * as React from 'react'; +import Svg, { Path } from 'react-native-svg'; + +function ExtendedArrow(): React.Node { + return ( + + + + ); +} + +export default ExtendedArrow; diff --git a/native/components/arrow-long.react.js b/native/components/arrow-long.react.js deleted file mode 100644 index fe5457ca2..000000000 --- a/native/components/arrow-long.react.js +++ /dev/null @@ -1,23 +0,0 @@ -// @flow - -import * as React from 'react'; -import Svg, { Path } from 'react-native-svg'; - -function ArrowLong(): React.Node { - return ( - - - - ); -} - -export default ArrowLong;