diff options
Diffstat (limited to 'src/view/com')
-rw-r--r-- | src/view/com/discover/SuggestedFollows.tsx | 16 | ||||
-rw-r--r-- | src/view/com/discover/WhoToFollow.tsx | 27 | ||||
-rw-r--r-- | src/view/com/post/Post.tsx | 5 | ||||
-rw-r--r-- | src/view/com/profile/ProfileCard.tsx | 4 | ||||
-rw-r--r-- | src/view/com/search/HeaderWithInput.tsx | 146 | ||||
-rw-r--r-- | src/view/com/search/SearchResults.tsx | 110 | ||||
-rw-r--r-- | src/view/com/search/Suggestions.tsx | 50 | ||||
-rw-r--r-- | src/view/com/util/TabBar.tsx | 1 | ||||
-rw-r--r-- | src/view/com/util/UserBanner.tsx | 1 |
9 files changed, 319 insertions, 41 deletions
diff --git a/src/view/com/discover/SuggestedFollows.tsx b/src/view/com/discover/SuggestedFollows.tsx index 7a64a15f6..bce224231 100644 --- a/src/view/com/discover/SuggestedFollows.tsx +++ b/src/view/com/discover/SuggestedFollows.tsx @@ -15,7 +15,7 @@ export const SuggestedFollows = ({ }) => { const pal = usePalette('default') return ( - <View style={[styles.container, pal.view]}> + <View style={[styles.container, pal.view, pal.border]}> <Text type="title" style={[styles.heading, pal.text]}> {title} </Text> @@ -45,24 +45,16 @@ export const SuggestedFollows = ({ const styles = StyleSheet.create({ container: { - paddingVertical: 10, - paddingHorizontal: 4, + borderBottomWidth: 1, }, heading: { fontWeight: 'bold', - paddingHorizontal: 4, + paddingHorizontal: 12, paddingBottom: 8, }, card: { - borderRadius: 12, - marginBottom: 2, - borderWidth: 1, - }, - - loadMore: { - paddingLeft: 16, - paddingVertical: 12, + borderTopWidth: 1, }, }) diff --git a/src/view/com/discover/WhoToFollow.tsx b/src/view/com/discover/WhoToFollow.tsx index 17c10ca7e..715fadae2 100644 --- a/src/view/com/discover/WhoToFollow.tsx +++ b/src/view/com/discover/WhoToFollow.tsx @@ -1,10 +1,5 @@ import React from 'react' -import { - ActivityIndicator, - StyleSheet, - TouchableOpacity, - View, -} from 'react-native' +import {ActivityIndicator, StyleSheet, View} from 'react-native' import {observer} from 'mobx-react-lite' import {useStores} from 'state/index' import {SuggestedActorsViewModel} from 'state/models/suggested-actors-view' @@ -17,7 +12,7 @@ export const WhoToFollow = observer(() => { const pal = usePalette('default') const store = useStores() const suggestedActorsView = React.useMemo<SuggestedActorsViewModel>( - () => new SuggestedActorsViewModel(store, {pageSize: 5}), + () => new SuggestedActorsViewModel(store, {pageSize: 15}), [store], ) @@ -25,9 +20,6 @@ export const WhoToFollow = observer(() => { suggestedActorsView.loadMore(true) }, [store, suggestedActorsView]) - const onPressLoadMoreSuggestedActors = () => { - suggestedActorsView.loadMore() - } return ( <> {(suggestedActorsView.hasContent || suggestedActorsView.isLoading) && ( @@ -50,15 +42,6 @@ export const WhoToFollow = observer(() => { /> ))} </View> - {!suggestedActorsView.isLoading && suggestedActorsView.hasMore && ( - <TouchableOpacity - onPress={onPressLoadMoreSuggestedActors} - style={styles.loadMore}> - <Text type="lg" style={pal.link}> - Show more - </Text> - </TouchableOpacity> - )} </> )} {suggestedActorsView.isLoading && ( @@ -74,16 +57,10 @@ const styles = StyleSheet.create({ heading: { fontWeight: 'bold', paddingHorizontal: 12, - paddingTop: 16, paddingBottom: 8, }, bottomBorder: { borderBottomWidth: 1, }, - - loadMore: { - paddingLeft: 16, - paddingVertical: 12, - }, }) diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index ac7d1cc55..a6c66d143 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -30,11 +30,13 @@ export const Post = observer(function Post({ uri, initView, showReplyLine, + hideError, style, }: { uri: string initView?: PostThreadViewModel showReplyLine?: boolean + hideError?: boolean style?: StyleProp<ViewStyle> }) { const pal = usePalette('default') @@ -70,6 +72,9 @@ export const Post = observer(function Post({ // error // = if (view.hasError || !view.thread || !view.thread?.postRecord) { + if (hideError) { + return <View /> + } return ( <View style={pal.view}> <Text>{view.error || 'Thread not found'}</Text> diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index 7b454cc8b..748648742 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -184,7 +184,7 @@ const styles = StyleSheet.create({ paddingRight: 10, }, details: { - paddingLeft: 60, + paddingLeft: 54, paddingRight: 10, paddingBottom: 10, }, @@ -202,7 +202,7 @@ const styles = StyleSheet.create({ followedBy: { flexDirection: 'row', - alignItems: 'flex-start', + alignItems: 'center', paddingLeft: 54, paddingRight: 20, marginBottom: 10, diff --git a/src/view/com/search/HeaderWithInput.tsx b/src/view/com/search/HeaderWithInput.tsx new file mode 100644 index 000000000..cc0b90af7 --- /dev/null +++ b/src/view/com/search/HeaderWithInput.tsx @@ -0,0 +1,146 @@ +import React from 'react' +import {StyleSheet, TextInput, TouchableOpacity, View} from 'react-native' +import { + FontAwesomeIcon, + FontAwesomeIconStyle, +} from '@fortawesome/react-native-fontawesome' +import {UserAvatar} from 'view/com/util/UserAvatar' +import {Text} from 'view/com/util/text/Text' +import {MagnifyingGlassIcon} from 'lib/icons' +import {useTheme} from 'lib/ThemeContext' +import {usePalette} from 'lib/hooks/usePalette' +import {useStores} from 'state/index' +import {useAnalytics} from 'lib/analytics' + +const MENU_HITSLOP = {left: 10, top: 10, right: 30, bottom: 10} + +interface Props { + isInputFocused: boolean + query: string + setIsInputFocused: (v: boolean) => void + onChangeQuery: (v: string) => void + onPressClearQuery: () => void + onPressCancelSearch: () => void + onSubmitQuery: () => void +} +export function HeaderWithInput({ + isInputFocused, + query, + setIsInputFocused, + onChangeQuery, + onPressClearQuery, + onPressCancelSearch, + onSubmitQuery, +}: Props) { + const store = useStores() + const theme = useTheme() + const pal = usePalette('default') + const {track} = useAnalytics() + const textInput = React.useRef<TextInput>(null) + + const onPressMenu = React.useCallback(() => { + track('ViewHeader:MenuButtonClicked') + store.shell.openDrawer() + }, [track, store]) + + const onPressCancelSearchInner = React.useCallback(() => { + onPressCancelSearch() + textInput.current?.blur() + }, [onPressCancelSearch, textInput]) + + return ( + <View style={[pal.view, pal.border, styles.header]}> + <TouchableOpacity + testID="viewHeaderBackOrMenuBtn" + onPress={onPressMenu} + hitSlop={MENU_HITSLOP} + style={styles.headerMenuBtn}> + <UserAvatar size={30} avatar={store.me.avatar} /> + </TouchableOpacity> + <View + style={[ + {backgroundColor: pal.colors.backgroundLight}, + styles.headerSearchContainer, + ]}> + <MagnifyingGlassIcon + style={[pal.icon, styles.headerSearchIcon]} + size={21} + /> + <TextInput + testID="searchTextInput" + ref={textInput} + placeholder="Search" + placeholderTextColor={pal.colors.textLight} + selectTextOnFocus + returnKeyType="search" + value={query} + style={[pal.text, styles.headerSearchInput]} + keyboardAppearance={theme.colorScheme} + onFocus={() => setIsInputFocused(true)} + onBlur={() => setIsInputFocused(false)} + onChangeText={onChangeQuery} + onSubmitEditing={onSubmitQuery} + /> + {query ? ( + <TouchableOpacity onPress={onPressClearQuery}> + <FontAwesomeIcon + icon="xmark" + size={16} + style={pal.textLight as FontAwesomeIconStyle} + /> + </TouchableOpacity> + ) : undefined} + </View> + {query || isInputFocused ? ( + <View style={styles.headerCancelBtn}> + <TouchableOpacity onPress={onPressCancelSearchInner}> + <Text style={pal.text}>Cancel</Text> + </TouchableOpacity> + </View> + ) : undefined} + </View> + ) +} + +const styles = StyleSheet.create({ + header: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 12, + paddingVertical: 4, + }, + headerMenuBtn: { + width: 40, + height: 30, + marginLeft: 6, + }, + headerSearchContainer: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + borderRadius: 30, + paddingHorizontal: 12, + paddingVertical: 8, + }, + headerSearchIcon: { + marginRight: 6, + alignSelf: 'center', + }, + headerSearchInput: { + flex: 1, + fontSize: 17, + }, + headerCancelBtn: { + width: 60, + paddingLeft: 10, + }, + + searchPrompt: { + textAlign: 'center', + paddingTop: 10, + }, + + suggestions: { + marginBottom: 8, + }, +}) diff --git a/src/view/com/search/SearchResults.tsx b/src/view/com/search/SearchResults.tsx new file mode 100644 index 000000000..062b703ee --- /dev/null +++ b/src/view/com/search/SearchResults.tsx @@ -0,0 +1,110 @@ +import React from 'react' +import {StyleSheet, View} from 'react-native' +import {observer} from 'mobx-react-lite' +import {SearchUIModel} from 'state/models/ui/search' +import {CenteredView, ScrollView} from '../util/Views' +import {Pager, RenderTabBarFnProps} from 'view/com/util/pager/Pager' +import {TabBar} from 'view/com/util/TabBar' +import {Post} from 'view/com/post/Post' +import {ProfileCardWithFollowBtn} from 'view/com/profile/ProfileCard' +import { + PostFeedLoadingPlaceholder, + ProfileCardFeedLoadingPlaceholder, +} from 'view/com/util/LoadingPlaceholder' +import {Text} from 'view/com/util/text/Text' +import {usePalette} from 'lib/hooks/usePalette' +import {s} from 'lib/styles' + +const SECTIONS = ['Posts', 'Users'] + +export const SearchResults = observer(({model}: {model: SearchUIModel}) => { + const pal = usePalette('default') + + const renderTabBar = React.useCallback( + (props: RenderTabBarFnProps) => { + return ( + <CenteredView style={[pal.border, styles.tabBar]}> + <TabBar {...props} items={SECTIONS} /> + </CenteredView> + ) + }, + [pal], + ) + + return ( + <Pager renderTabBar={renderTabBar} tabBarPosition="top" initialPage={0}> + <PostResults key="0" model={model} /> + <Profiles key="1" model={model} /> + </Pager> + ) +}) + +const PostResults = observer(({model}: {model: SearchUIModel}) => { + const pal = usePalette('default') + if (model.isPostsLoading) { + return <PostFeedLoadingPlaceholder /> + } + + if (model.postUris.length === 0) { + return ( + <Text type="xl" style={[styles.empty, pal.text]}> + No posts found for "{model.query}" + </Text> + ) + } + + return ( + <ScrollView style={pal.view}> + {model.postUris.map(uri => ( + <Post key={uri} uri={uri} hideError /> + ))} + <View style={s.footerSpacer} /> + <View style={s.footerSpacer} /> + <View style={s.footerSpacer} /> + </ScrollView> + ) +}) + +const Profiles = observer(({model}: {model: SearchUIModel}) => { + const pal = usePalette('default') + if (model.isProfilesLoading) { + return <ProfileCardFeedLoadingPlaceholder /> + } + + if (model.profiles.length === 0) { + return ( + <Text type="xl" style={[styles.empty, pal.text]}> + No users found for "{model.query}" + </Text> + ) + } + + return ( + <ScrollView style={pal.view}> + {model.profiles.map(item => ( + <ProfileCardWithFollowBtn + key={item.did} + did={item.did} + declarationCid={item.declaration.cid} + handle={item.handle} + displayName={item.displayName} + avatar={item.avatar} + description={item.description} + /> + ))} + <View style={s.footerSpacer} /> + <View style={s.footerSpacer} /> + <View style={s.footerSpacer} /> + </ScrollView> + ) +}) + +const styles = StyleSheet.create({ + tabBar: { + borderBottomWidth: 1, + }, + empty: { + paddingHorizontal: 14, + paddingVertical: 16, + }, +}) diff --git a/src/view/com/search/Suggestions.tsx b/src/view/com/search/Suggestions.tsx new file mode 100644 index 000000000..1747036ba --- /dev/null +++ b/src/view/com/search/Suggestions.tsx @@ -0,0 +1,50 @@ +import React from 'react' +import {StyleSheet, View} from 'react-native' +import {observer} from 'mobx-react-lite' +import {FoafsModel} from 'state/models/discovery/foafs' +import {WhoToFollow} from 'view/com/discover/WhoToFollow' +import {SuggestedFollows} from 'view/com/discover/SuggestedFollows' +import {ProfileCardFeedLoadingPlaceholder} from 'view/com/util/LoadingPlaceholder' + +export const Suggestions = observer(({foafs}: {foafs: FoafsModel}) => { + if (foafs.isLoading) { + return <ProfileCardFeedLoadingPlaceholder /> + } + if (foafs.hasContent) { + return ( + <> + {foafs.popular.length > 0 && ( + <View style={styles.suggestions}> + <SuggestedFollows + title="In your network" + suggestions={foafs.popular} + /> + </View> + )} + <WhoToFollow /> + {foafs.sources.map((source, i) => { + const item = foafs.foafs.get(source) + if (!item || item.follows.length === 0) { + return <View key={`sf-${item?.did || i}`} /> + } + return ( + <View key={`sf-${item.did}`} style={styles.suggestions}> + <SuggestedFollows + title={`Followed by ${item.displayName || item.handle}`} + suggestions={item.follows.slice(0, 10)} + /> + </View> + ) + })} + </> + ) + } + return <WhoToFollow /> +}) + +const styles = StyleSheet.create({ + suggestions: { + marginTop: 10, + marginBottom: 20, + }, +}) diff --git a/src/view/com/util/TabBar.tsx b/src/view/com/util/TabBar.tsx index 4b67b8a80..545a6b742 100644 --- a/src/view/com/util/TabBar.tsx +++ b/src/view/com/util/TabBar.tsx @@ -157,6 +157,5 @@ const styles = isDesktopWeb left: 0, width: 1, height: 3, - borderRadius: 4, }, }) diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx index 847ef6dba..1752c260e 100644 --- a/src/view/com/util/UserBanner.tsx +++ b/src/view/com/util/UserBanner.tsx @@ -1,6 +1,5 @@ import React from 'react' import {StyleSheet, View} from 'react-native' -import Svg, {Rect} from 'react-native-svg' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {IconProp} from '@fortawesome/fontawesome-svg-core' import Image from 'view/com/util/images/Image' |