Move the feed selector to the footer

zio/stable
Paul Frazee 2023-03-17 14:03:16 -05:00
parent 244b06c19d
commit c3ed0dc44c
6 changed files with 121 additions and 40 deletions

View File

@ -1,16 +1,18 @@
import React from 'react'
import {observer} from 'mobx-react-lite'
import {Button} from '../util/forms/Button'
import {Button, ButtonType} from '../util/forms/Button'
import {useStores} from 'state/index'
import * as apilib from 'lib/api/index'
import * as Toast from '../util/Toast'
const FollowButton = observer(
({
type = 'inverted',
did,
declarationCid,
onToggleFollow,
}: {
type?: ButtonType
did: string
declarationCid: string
onToggleFollow?: (v: boolean) => void
@ -42,7 +44,7 @@ const FollowButton = observer(
return (
<Button
type={isFollowing ? 'default' : 'inverted'}
type={isFollowing ? 'default' : type}
onPress={onToggleFollowInner}
label={isFollowing ? 'Unfollow' : 'Follow'}
/>

View File

@ -15,11 +15,13 @@ export interface TabBarProps {
}
interface Props {
tabBarPosition?: 'top' | 'bottom'
renderTabBar: (props: TabBarProps) => JSX.Element
onPageSelected?: (e: PageSelectedEvent) => void
}
export const Pager = ({
children,
tabBarPosition = 'top',
renderTabBar,
onPageSelected,
}: React.PropsWithChildren<Props>) => {
@ -45,7 +47,13 @@ export const Pager = ({
return (
<View>
{renderTabBar({selectedPage, position, offset, onSelect: onTabBarSelect})}
{tabBarPosition === 'top' &&
renderTabBar({
selectedPage,
position,
offset,
onSelect: onTabBarSelect,
})}
<AnimatedPagerView
ref={pagerView}
style={s.h100pct}
@ -64,6 +72,13 @@ export const Pager = ({
)}>
{children}
</AnimatedPagerView>
{tabBarPosition === 'bottom' &&
renderTabBar({
selectedPage,
position,
offset,
onSelect: onTabBarSelect,
})}
</View>
)
}

View File

@ -77,6 +77,7 @@ export const PostMeta = observer(function (opts: PostMetaOpts) {
<View>
<FollowButton
type="default"
did={opts.did}
declarationCid={opts.declarationCid}
onToggleFollow={onToggleFollow}

View File

@ -18,12 +18,16 @@ export function TabBar({
items,
position,
offset,
indicatorPosition = 'bottom',
indicatorColor,
onSelect,
}: {
selectedPage: number
items: string[]
position: Animated.Value
offset: Animated.Value
indicatorPosition?: 'top' | 'bottom'
indicatorColor?: string
onSelect?: (index: number) => void
}) {
const pal = usePalette('default')
@ -36,8 +40,10 @@ export function TabBar({
)
const panX = Animated.add(position, offset)
const underlineStyle = {
backgroundColor: pal.colors.link,
const indicatorStyle = {
backgroundColor: indicatorColor || pal.colors.link,
bottom: indicatorPosition === 'bottom' ? -1 : undefined,
top: indicatorPosition === 'top' ? -1 : undefined,
left: panX.interpolate({
inputRange: items.map((_item, i) => i),
outputRange: itemLayouts.map(l => l.x),
@ -72,12 +78,16 @@ export function TabBar({
return (
<View style={[pal.view, styles.outer]} onLayout={onLayout}>
<Animated.View style={[styles.underline, underlineStyle]} />
<Animated.View style={[styles.indicator, indicatorStyle]} />
{items.map((item, i) => {
const selected = i === selectedPage
return (
<TouchableWithoutFeedback key={i} onPress={() => onPressItem(i)}>
<View style={styles.item} ref={itemRefs[i]}>
<View
style={
indicatorPosition === 'top' ? styles.itemTop : styles.itemBottom
}
ref={itemRefs[i]}>
<Text type="xl-bold" style={selected ? pal.text : pal.textLight}>
{item}
</Text>
@ -94,15 +104,19 @@ const styles = StyleSheet.create({
flexDirection: 'row',
paddingHorizontal: 14,
},
item: {
itemTop: {
paddingTop: 10,
paddingBottom: 10,
marginRight: 24,
},
itemBottom: {
paddingTop: 8,
paddingBottom: 12,
marginRight: 24,
},
underline: {
indicator: {
position: 'absolute',
height: 3,
bottom: -1,
borderRadius: 4,
},
})

View File

@ -1,8 +1,8 @@
import React from 'react'
import {
Animated,
FlatList,
StyleSheet,
TouchableOpacity,
View,
useWindowDimensions,
} from 'react-native'
@ -15,7 +15,6 @@ import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {Feed} from '../com/posts/Feed'
import {LoadLatestBtn} from '../com/util/LoadLatestBtn'
import {WelcomeBanner} from '../com/util/WelcomeBanner'
import {UserAvatar} from 'view/com/util/UserAvatar'
import {TabBar} from 'view/com/util/TabBar'
import {Pager, PageSelectedEvent, TabBarProps} from 'view/com/util/Pager'
import {FAB} from '../com/util/FAB'
@ -23,15 +22,18 @@ import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette'
import {s} from 'lib/styles'
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
import {useAnalytics} from 'lib/analytics'
import {ComposeIcon2} from 'lib/icons'
import {clamp} from 'lodash'
const TAB_BAR_HEIGHT = 82
const BOTTOM_BAR_HEIGHT = 48
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
export const HomeScreen = withAuthRequired((_opts: Props) => {
const store = useStores()
const pal = usePalette('default')
const [selectedPage, setSelectedPage] = React.useState(0)
useFocusEffect(
@ -51,26 +53,15 @@ export const HomeScreen = withAuthRequired((_opts: Props) => {
[store],
)
const onPressAvi = React.useCallback(() => {
store.shell.openDrawer()
}, [store])
const renderTabBar = React.useCallback(
(props: TabBarProps) => {
return (
<View style={[pal.view, pal.border, styles.tabBar]}>
<TouchableOpacity style={styles.tabBarAvi} onPress={onPressAvi}>
<UserAvatar avatar={store.me.avatar} size={32} />
</TouchableOpacity>
<TabBar items={['Suggested', 'Following']} {...props} />
</View>
)
},
[store.me.avatar, pal, onPressAvi],
)
const renderTabBar = React.useCallback((props: TabBarProps) => {
return <FloatingTabBar {...props} />
}, [])
return (
<Pager onPageSelected={onPageSelected} renderTabBar={renderTabBar}>
<Pager
onPageSelected={onPageSelected}
renderTabBar={renderTabBar}
tabBarPosition="bottom">
<AlgoView key="1" />
<View key="2">
<FollowingView />
@ -79,6 +70,46 @@ export const HomeScreen = withAuthRequired((_opts: Props) => {
)
})
const FloatingTabBar = observer((props: TabBarProps) => {
const store = useStores()
const safeAreaInsets = useSafeAreaInsets()
const pal = usePalette('default')
const interp = useAnimatedValue(0)
const pad = React.useMemo(
() => ({
paddingBottom: clamp(safeAreaInsets.bottom, 15, 20),
}),
[safeAreaInsets],
)
React.useEffect(() => {
Animated.timing(interp, {
toValue: store.shell.minimalShellMode ? 0 : 1,
duration: 100,
useNativeDriver: true,
isInteraction: false,
}).start()
}, [interp, store.shell.minimalShellMode])
const transform = {
transform: [
{translateY: Animated.multiply(interp, -1 * BOTTOM_BAR_HEIGHT)},
],
}
return (
<Animated.View
style={[pal.view, pal.border, styles.tabBar, pad, transform]}>
<TabBar
items={['Suggested', 'Following']}
{...props}
indicatorPosition="top"
indicatorColor={pal.colors.link}
/>
</Animated.View>
)
})
const AlgoView = observer(() => {
const store = useStores()
const onMainScroll = useOnMainScroll(store)
@ -270,13 +301,19 @@ const FollowingView = observer(() => {
const styles = StyleSheet.create({
tabBar: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 18,
borderBottomWidth: 1,
paddingHorizontal: 8,
borderTopWidth: 1,
paddingTop: 0,
paddingBottom: 30,
// height: 100,
},
tabBarAvi: {
marginRight: 16,
paddingBottom: 2,
marginRight: 4,
},
})

View File

@ -34,16 +34,24 @@ export const BottomBar = observer(({navigation}: BottomTabBarProps) => {
const minimalShellInterp = useAnimatedValue(0)
const safeAreaInsets = useSafeAreaInsets()
const {track} = useAnalytics()
const {isAtHome, isAtSearch, isAtNotifications} = useNavigationState(
state => {
return {
const {isAtHome, isAtSearch, isAtNotifications, noBorder} =
useNavigationState(state => {
const res = {
isAtHome: getTabState(state, 'Home') !== TabState.Outside,
isAtSearch: getTabState(state, 'Search') !== TabState.Outside,
isAtNotifications:
getTabState(state, 'Notifications') !== TabState.Outside,
noBorder: getTabState(state, 'Home') === TabState.InsideAtRoot,
}
},
)
if (!res.isAtHome && !res.isAtNotifications && !res.isAtSearch) {
// HACK for some reason useNavigationState will give us pre-hydration results
// and not update after, so we force isAtHome if all came back false
// -prf
res.isAtHome = true
res.noBorder = true
}
return res
})
React.useEffect(() => {
if (store.shell.minimalShellMode) {
@ -99,6 +107,7 @@ export const BottomBar = observer(({navigation}: BottomTabBarProps) => {
<Animated.View
style={[
styles.bottomBar,
noBorder && styles.noBorder,
pal.view,
pal.border,
{paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)},
@ -213,6 +222,9 @@ const styles = StyleSheet.create({
paddingLeft: 5,
paddingRight: 10,
},
noBorder: {
borderTopWidth: 0,
},
ctrl: {
flex: 1,
paddingTop: 13,