From 3ad0f3cc4adddba253ad44e108f818410e29af43 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Thu, 9 Feb 2023 16:45:37 -0600 Subject: [PATCH] Bundle of UI modifications (#175) * Adjust visual balance of SuggestedPosts and WhoToFollow * Fix bug in the discovery load trigger * Adjust search header aesthetic and have it scroll away * More visual balance tweaks on the search page * Even more visual balance tweaks on the search page * Hide the footer on scroll in search * Ditch the composer prompt buttons in the home feed * Center the view header title * Hide header on scroll on the home feed --- src/view/com/discover/SuggestedPosts.tsx | 3 +- src/view/com/discover/WhoToFollow.tsx | 41 +++-------- src/view/com/posts/Feed.tsx | 25 ++++--- src/view/com/posts/PromptButtons.tsx | 59 ---------------- src/view/com/util/ViewHeader.tsx | 64 +++++++++++++++-- src/view/screens/Home.tsx | 4 +- src/view/screens/ProfileFollows.tsx | 2 +- src/view/screens/Search.tsx | 89 ++++++++++++------------ 8 files changed, 134 insertions(+), 153 deletions(-) delete mode 100644 src/view/com/posts/PromptButtons.tsx diff --git a/src/view/com/discover/SuggestedPosts.tsx b/src/view/com/discover/SuggestedPosts.tsx index d9be964be8..1b40971ebb 100644 --- a/src/view/com/discover/SuggestedPosts.tsx +++ b/src/view/com/discover/SuggestedPosts.tsx @@ -25,7 +25,7 @@ export const SuggestedPosts = observer(() => { return ( <> {(suggestedPostsView.hasContent || suggestedPostsView.isLoading) && ( - + Recently, on Bluesky... )} @@ -49,6 +49,7 @@ export const SuggestedPosts = observer(() => { const styles = StyleSheet.create({ heading: { + fontWeight: 'bold', paddingHorizontal: 12, paddingTop: 16, paddingBottom: 8, diff --git a/src/view/com/discover/WhoToFollow.tsx b/src/view/com/discover/WhoToFollow.tsx index 1554a4a0f9..880ab6ea3d 100644 --- a/src/view/com/discover/WhoToFollow.tsx +++ b/src/view/com/discover/WhoToFollow.tsx @@ -6,8 +6,6 @@ import { View, } from 'react-native' import {observer} from 'mobx-react-lite' -import LinearGradient from 'react-native-linear-gradient' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import _omit from 'lodash.omit' import {useStores} from '../../../state' import { @@ -15,7 +13,7 @@ import { SuggestedActor, } from '../../../state/models/suggested-actors-view' import * as apilib from '../../../state/lib/api' -import {s, gradients} from '../../lib/styles' +import {s} from '../../lib/styles' import {ProfileCard} from '../profile/ProfileCard' import * as Toast from '../util/Toast' import {Text} from '../util/text/Text' @@ -59,7 +57,7 @@ export const WhoToFollow = observer(() => { return ( <> {(suggestedActorsView.hasContent || suggestedActorsView.isLoading) && ( - + Who to follow )} @@ -86,7 +84,7 @@ export const WhoToFollow = observer(() => { - + Show more @@ -110,33 +108,20 @@ function FollowBtn({ onPress: () => void }) { const pal = usePalette('default') - if (isFollowing) { - return ( - - - - Unfollow - - - - ) - } return ( - - - Follow - + + + {isFollowing ? 'Unfollow' : 'Follow'} + + ) } const styles = StyleSheet.create({ heading: { + fontWeight: 'bold', paddingHorizontal: 12, paddingTop: 16, paddingBottom: 8, @@ -147,8 +132,8 @@ const styles = StyleSheet.create({ }, loadMore: { - paddingLeft: 12, - paddingVertical: 10, + paddingLeft: 16, + paddingVertical: 12, }, btn: { @@ -160,8 +145,4 @@ const styles = StyleSheet.create({ marginLeft: 6, paddingHorizontal: 14, }, - gradientBtn: { - paddingHorizontal: 24, - paddingVertical: 6, - }, }) diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 611a539020..b33ef2cd53 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -13,30 +13,29 @@ import {EmptyState} from '../util/EmptyState' import {ErrorMessage} from '../util/error/ErrorMessage' import {FeedModel} from '../../../state/models/feed-view' import {FeedItem} from './FeedItem' -import {PromptButtons} from './PromptButtons' import {OnScrollCb} from '../../lib/hooks/useOnMainScroll' import {s} from '../../lib/styles' import {useAnalytics} from '@segment/analytics-react-native' -const COMPOSE_PROMPT_ITEM = {_reactKey: '__prompt__'} +const HEADER_SPACER_ITEM = {_reactKey: '__spacer__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} export const Feed = observer(function Feed({ feed, style, scrollElRef, - onPressCompose, onPressTryAgain, onScroll, testID, + headerSpacer, }: { feed: FeedModel style?: StyleProp scrollElRef?: MutableRefObject | null> - onPressCompose: (imagesOpen?: boolean) => void onPressTryAgain?: () => void onScroll?: OnScrollCb testID?: string + headerSpacer?: boolean }) { const {screen, track} = useAnalytics() @@ -49,9 +48,7 @@ export const Feed = observer(function Feed({ // renderItem function renders components that follow React performance best practices // like PureComponent, shouldComponentUpdate, etc const renderItem = ({item}: {item: any}) => { - if (item === COMPOSE_PROMPT_ITEM) { - return - } else if (item === EMPTY_FEED_ITEM) { + if (item === EMPTY_FEED_ITEM) { return ( ) + } + if (item === HEADER_SPACER_ITEM) { + return } else { return } @@ -77,12 +77,15 @@ export const Feed = observer(function Feed({ .loadMore() .catch(err => feed.rootStore.log.error('Failed to load more posts', err)) } - let data + let data = [] + if (headerSpacer) { + data.push(HEADER_SPACER_ITEM) + } if (feed.hasLoaded) { if (feed.isEmpty) { - data = [COMPOSE_PROMPT_ITEM, EMPTY_FEED_ITEM] + data.push(EMPTY_FEED_ITEM) } else { - data = [COMPOSE_PROMPT_ITEM].concat(feed.feed) + data = data.concat(feed.feed) } } const FeedFooter = () => @@ -95,7 +98,6 @@ export const Feed = observer(function Feed({ ) return ( - {!data && } {feed.isLoading && !data && } {feed.hasError && ( @@ -120,6 +122,7 @@ export const Feed = observer(function Feed({ }) const styles = StyleSheet.create({ + headerSpacer: {height: 42}, feedFooter: {paddingTop: 20}, emptyState: {paddingVertical: 40}, }) diff --git a/src/view/com/posts/PromptButtons.tsx b/src/view/com/posts/PromptButtons.tsx deleted file mode 100644 index b2bd1980b9..0000000000 --- a/src/view/com/posts/PromptButtons.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react' -import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {Text} from '../util/text/Text' -import {usePalette} from '../../lib/hooks/usePalette' -import {useAnalytics} from '@segment/analytics-react-native' - -export function PromptButtons({ - onPressCompose, -}: { - onPressCompose: (imagesOpen?: boolean) => void -}) { - const pal = usePalette('default') - const {track} = useAnalytics() - - const onPressNewPost = () => { - track('PromptButtons:NewPost') - onPressCompose(false) - } - - const onPressSharePhoto = () => { - track('PromptButtons:SharePhoto') - onPressCompose(true) - } - return ( - - - - New post - - - - - Share photo - - - - ) -} - -const styles = StyleSheet.create({ - container: { - paddingVertical: 12, - paddingHorizontal: 16, - flexDirection: 'row', - alignItems: 'center', - borderTopWidth: 1, - }, - btn: { - paddingVertical: 6, - paddingHorizontal: 14, - borderRadius: 30, - marginRight: 10, - }, -}) diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx index 9c460f1e75..3689113a87 100644 --- a/src/view/com/util/ViewHeader.tsx +++ b/src/view/com/util/ViewHeader.tsx @@ -1,11 +1,12 @@ import React from 'react' import {observer} from 'mobx-react-lite' -import {StyleSheet, TouchableOpacity, View} from 'react-native' +import {Animated, StyleSheet, TouchableOpacity, View} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {UserAvatar} from './UserAvatar' import {Text} from './text/Text' import {useStores} from '../../../state' import {usePalette} from '../../lib/hooks/usePalette' +import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue' import {useAnalytics} from '@segment/analytics-react-native' const BACK_HITSLOP = {left: 10, top: 10, right: 30, bottom: 10} @@ -13,9 +14,11 @@ const BACK_HITSLOP = {left: 10, top: 10, right: 30, bottom: 10} export const ViewHeader = observer(function ViewHeader({ title, canGoBack, + hideOnScroll, }: { title: string canGoBack?: boolean + hideOnScroll?: boolean }) { const pal = usePalette('default') const store = useStores() @@ -31,7 +34,7 @@ export const ViewHeader = observer(function ViewHeader({ canGoBack = store.nav.tab.canGoBack } return ( - + - + + ) }) +const Container = observer( + ({ + children, + hideOnScroll, + }: { + children: React.ReactNode + hideOnScroll: boolean + }) => { + const store = useStores() + const pal = usePalette('default') + const interp = useAnimatedValue(0) + + React.useEffect(() => { + if (store.shell.minimalShellMode) { + Animated.timing(interp, { + toValue: 1, + duration: 100, + useNativeDriver: true, + isInteraction: false, + }).start() + } else { + Animated.timing(interp, { + toValue: 0, + duration: 100, + useNativeDriver: true, + isInteraction: false, + }).start() + } + }, [interp, store.shell.minimalShellMode]) + const transform = { + transform: [{translateY: Animated.multiply(interp, -100)}], + } + + if (!hideOnScroll) { + return {children} + } + return ( + + {children} + + ) + }, +) + const styles = StyleSheet.create({ header: { flexDirection: 'row', @@ -69,11 +118,16 @@ const styles = StyleSheet.create({ paddingTop: 6, paddingBottom: 6, }, + headerFloating: { + position: 'absolute', + top: 0, + width: '100%', + }, titleContainer: { - flexDirection: 'row', - alignItems: 'baseline', + marginLeft: 'auto', marginRight: 'auto', + paddingRight: 10, }, title: { fontWeight: 'bold', diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 940ce19558..dabdc1a86a 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -88,17 +88,17 @@ export const Home = observer(function Home({navIdx, visible}: ScreenParams) { return ( - + {store.me.mainFeed.hasNewLatest && !store.me.mainFeed.isRefreshing ? ( { return ( - + ) diff --git a/src/view/screens/Search.tsx b/src/view/screens/Search.tsx index 996d9cfca4..2caba2dce8 100644 --- a/src/view/screens/Search.tsx +++ b/src/view/screens/Search.tsx @@ -20,6 +20,7 @@ import {WhoToFollow} from '../com/discover/WhoToFollow' import {SuggestedPosts} from '../com/discover/SuggestedPosts' import {ProfileCard} from '../com/profile/ProfileCard' import {usePalette} from '../lib/hooks/usePalette' +import {useOnMainScroll} from '../lib/hooks/useOnMainScroll' import {useAnalytics} from '@segment/analytics-react-native' const MENU_HITSLOP = {left: 10, top: 10, right: 30, bottom: 10} @@ -30,8 +31,9 @@ export const Search = observer(({navIdx, visible, params}: ScreenParams) => { const store = useStores() const {track} = useAnalytics() const scrollElRef = React.useRef(null) + const onMainScroll = useOnMainScroll(store) const textInput = React.useRef(null) - const [lastRenderTime, setRenderTime] = React.useState(0) // used to trigger reloads + const [lastRenderTime, setRenderTime] = React.useState(Date.now()) // used to trigger reloads const [isInputFocused, setIsInputFocused] = React.useState(false) const [query, setQuery] = React.useState('') const autocompleteView = React.useMemo( @@ -52,7 +54,7 @@ export const Search = observer(({navIdx, visible, params}: ScreenParams) => { if (visible) { const now = Date.now() - if (lastRenderTime - now > FIVE_MIN) { + if (now - lastRenderTime > FIVE_MIN) { setRenderTime(Date.now()) // trigger reload of suggestions } store.shell.setMinimalShellMode(false) @@ -83,7 +85,12 @@ export const Search = observer(({navIdx, visible, params}: ScreenParams) => { return ( - + { ) : undefined} - - {query && autocompleteView.searchRes.length ? ( - - {autocompleteView.searchRes.map(item => ( - - ))} - - - ) : query && !autocompleteView.searchRes.length ? ( - - - No results found for {autocompleteView.prefix} - - - ) : isInputFocused ? ( - - - Search for users on the network - - - ) : ( - - - - - - )} - - + {query && autocompleteView.searchRes.length ? ( + <> + {autocompleteView.searchRes.map(item => ( + + ))} + + ) : query && !autocompleteView.searchRes.length ? ( + + + No results found for {autocompleteView.prefix} + + + ) : isInputFocused ? ( + + + Search for users on the network + + + ) : ( + + + + + + )} + + ) }) @@ -176,7 +181,7 @@ const styles = StyleSheet.create({ alignItems: 'center', paddingHorizontal: 12, paddingTop: 4, - paddingBottom: 5, + marginBottom: 14, }, headerMenuBtn: { width: 40, @@ -189,7 +194,7 @@ const styles = StyleSheet.create({ alignItems: 'center', borderRadius: 30, paddingHorizontal: 12, - paddingVertical: 6, + paddingVertical: 8, }, headerSearchIcon: { marginRight: 6, @@ -197,7 +202,7 @@ const styles = StyleSheet.create({ }, headerSearchInput: { flex: 1, - fontSize: 16, + fontSize: 17, }, headerCancelBtn: { width: 60, @@ -208,8 +213,4 @@ const styles = StyleSheet.create({ textAlign: 'center', paddingTop: 10, }, - - outputContainer: { - flex: 1, - }, })