Go to whats-hot by default if you have no follows
parent
6578d2bfad
commit
7a754850bc
|
@ -33,6 +33,7 @@ export class MeModel {
|
||||||
clear() {
|
clear() {
|
||||||
this.mainFeed.clear()
|
this.mainFeed.clear()
|
||||||
this.notifications.clear()
|
this.notifications.clear()
|
||||||
|
this.follows.clear()
|
||||||
this.did = ''
|
this.did = ''
|
||||||
this.handle = ''
|
this.handle = ''
|
||||||
this.displayName = ''
|
this.displayName = ''
|
||||||
|
|
|
@ -35,6 +35,12 @@ export class MyFollowsModel {
|
||||||
// public api
|
// public api
|
||||||
// =
|
// =
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.followDidToRecordMap = {}
|
||||||
|
this.lastSync = 0
|
||||||
|
this.myDid = undefined
|
||||||
|
}
|
||||||
|
|
||||||
fetchIfNeeded = bundleAsync(async () => {
|
fetchIfNeeded = bundleAsync(async () => {
|
||||||
if (
|
if (
|
||||||
this.myDid !== this.rootStore.me.did ||
|
this.myDid !== this.rootStore.me.did ||
|
||||||
|
|
|
@ -154,13 +154,13 @@ export class SessionModel {
|
||||||
/**
|
/**
|
||||||
* Sets the active session
|
* Sets the active session
|
||||||
*/
|
*/
|
||||||
setActiveSession(agent: AtpAgent, did: string) {
|
async setActiveSession(agent: AtpAgent, did: string) {
|
||||||
this._log('SessionModel:setActiveSession')
|
this._log('SessionModel:setActiveSession')
|
||||||
this.data = {
|
this.data = {
|
||||||
service: agent.service.toString(),
|
service: agent.service.toString(),
|
||||||
did,
|
did,
|
||||||
}
|
}
|
||||||
this.rootStore.handleSessionChange(agent)
|
await this.rootStore.handleSessionChange(agent)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -304,7 +304,7 @@ export class SessionModel {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setActiveSession(agent, account.did)
|
await this.setActiveSession(agent, account.did)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,7 +337,7 @@ export class SessionModel {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
this.setActiveSession(agent, did)
|
await this.setActiveSession(agent, did)
|
||||||
this._log('SessionModel:login succeeded')
|
this._log('SessionModel:login succeeded')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,7 +376,7 @@ export class SessionModel {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
this.setActiveSession(agent, did)
|
await this.setActiveSession(agent, did)
|
||||||
this._log('SessionModel:createAccount succeeded')
|
this._log('SessionModel:createAccount succeeded')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,23 +7,15 @@ import {
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {CenteredView, FlatList} from '../util/Views'
|
import {CenteredView, FlatList} from '../util/Views'
|
||||||
import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
|
import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
|
||||||
import {ViewHeader} from '../util/ViewHeader'
|
import {ViewHeader} from '../util/ViewHeader'
|
||||||
import {Text} from '../util/text/Text'
|
|
||||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||||
import {Button} from '../util/forms/Button'
|
|
||||||
import {FeedModel} from 'state/models/feed-view'
|
import {FeedModel} from 'state/models/feed-view'
|
||||||
import {FeedSlice} from './FeedSlice'
|
import {FeedSlice} from './FeedSlice'
|
||||||
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
|
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
|
||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
import {useAnalytics} from 'lib/analytics'
|
import {useAnalytics} from 'lib/analytics'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {MagnifyingGlassIcon} from 'lib/icons'
|
|
||||||
import {NavigationProp} from 'lib/routes/types'
|
|
||||||
|
|
||||||
const HEADER_ITEM = {_reactKey: '__header__'}
|
const HEADER_ITEM = {_reactKey: '__header__'}
|
||||||
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
||||||
|
@ -36,6 +28,7 @@ export const Feed = observer(function Feed({
|
||||||
scrollElRef,
|
scrollElRef,
|
||||||
onPressTryAgain,
|
onPressTryAgain,
|
||||||
onScroll,
|
onScroll,
|
||||||
|
renderEmptyState,
|
||||||
testID,
|
testID,
|
||||||
headerOffset = 0,
|
headerOffset = 0,
|
||||||
}: {
|
}: {
|
||||||
|
@ -45,14 +38,12 @@ export const Feed = observer(function Feed({
|
||||||
scrollElRef?: MutableRefObject<FlatList<any> | null>
|
scrollElRef?: MutableRefObject<FlatList<any> | null>
|
||||||
onPressTryAgain?: () => void
|
onPressTryAgain?: () => void
|
||||||
onScroll?: OnScrollCb
|
onScroll?: OnScrollCb
|
||||||
|
renderEmptyState?: () => JSX.Element
|
||||||
testID?: string
|
testID?: string
|
||||||
headerOffset?: number
|
headerOffset?: number
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
|
||||||
const palInverted = usePalette('inverted')
|
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
const [isRefreshing, setIsRefreshing] = React.useState(false)
|
const [isRefreshing, setIsRefreshing] = React.useState(false)
|
||||||
const navigation = useNavigation<NavigationProp>()
|
|
||||||
|
|
||||||
const data = React.useMemo(() => {
|
const data = React.useMemo(() => {
|
||||||
let feedItems: any[] = [HEADER_ITEM]
|
let feedItems: any[] = [HEADER_ITEM]
|
||||||
|
@ -82,6 +73,7 @@ export const Feed = observer(function Feed({
|
||||||
}
|
}
|
||||||
setIsRefreshing(false)
|
setIsRefreshing(false)
|
||||||
}, [feed, track, setIsRefreshing])
|
}, [feed, track, setIsRefreshing])
|
||||||
|
|
||||||
const onEndReached = React.useCallback(async () => {
|
const onEndReached = React.useCallback(async () => {
|
||||||
track('Feed:onEndReached')
|
track('Feed:onEndReached')
|
||||||
try {
|
try {
|
||||||
|
@ -97,37 +89,10 @@ export const Feed = observer(function Feed({
|
||||||
const renderItem = React.useCallback(
|
const renderItem = React.useCallback(
|
||||||
({item}: {item: any}) => {
|
({item}: {item: any}) => {
|
||||||
if (item === EMPTY_FEED_ITEM) {
|
if (item === EMPTY_FEED_ITEM) {
|
||||||
return (
|
if (renderEmptyState) {
|
||||||
<View style={styles.emptyContainer}>
|
return renderEmptyState()
|
||||||
<View style={styles.emptyIconContainer}>
|
}
|
||||||
<MagnifyingGlassIcon
|
return <View />
|
||||||
style={[styles.emptyIcon, pal.text]}
|
|
||||||
size={62}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Text type="xl-medium" style={[s.textCenter, pal.text]}>
|
|
||||||
Your feed is empty! You should follow some accounts to fix this.
|
|
||||||
</Text>
|
|
||||||
<Button
|
|
||||||
type="inverted"
|
|
||||||
style={styles.emptyBtn}
|
|
||||||
onPress={
|
|
||||||
() =>
|
|
||||||
navigation.navigate(
|
|
||||||
'SearchTab',
|
|
||||||
) /* TODO make sure it goes to root of the tab */
|
|
||||||
}>
|
|
||||||
<Text type="lg-medium" style={palInverted.text}>
|
|
||||||
Find accounts
|
|
||||||
</Text>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon="angle-right"
|
|
||||||
style={palInverted.text as FontAwesomeIconStyle}
|
|
||||||
size={14}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
} else if (item === ERROR_FEED_ITEM) {
|
} else if (item === ERROR_FEED_ITEM) {
|
||||||
return (
|
return (
|
||||||
<ErrorMessage
|
<ErrorMessage
|
||||||
|
@ -140,7 +105,7 @@ export const Feed = observer(function Feed({
|
||||||
}
|
}
|
||||||
return <FeedSlice slice={item} showFollowBtn={showPostFollowBtn} />
|
return <FeedSlice slice={item} showFollowBtn={showPostFollowBtn} />
|
||||||
},
|
},
|
||||||
[feed, onPressTryAgain, showPostFollowBtn, pal, palInverted, navigation],
|
[feed, onPressTryAgain, showPostFollowBtn, renderEmptyState],
|
||||||
)
|
)
|
||||||
|
|
||||||
const FeedFooter = React.useCallback(
|
const FeedFooter = React.useCallback(
|
||||||
|
@ -187,21 +152,4 @@ export const Feed = observer(function Feed({
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
feedFooter: {paddingTop: 20},
|
feedFooter: {paddingTop: 20},
|
||||||
emptyContainer: {
|
|
||||||
paddingVertical: 40,
|
|
||||||
paddingHorizontal: 30,
|
|
||||||
},
|
|
||||||
emptyIconContainer: {
|
|
||||||
marginBottom: 16,
|
|
||||||
},
|
|
||||||
emptyIcon: {
|
|
||||||
marginLeft: 'auto',
|
|
||||||
marginRight: 'auto',
|
|
||||||
},
|
|
||||||
emptyBtn: {
|
|
||||||
marginTop: 20,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {StyleSheet, View} from 'react-native'
|
||||||
|
import {useNavigation} from '@react-navigation/native'
|
||||||
|
import {
|
||||||
|
FontAwesomeIcon,
|
||||||
|
FontAwesomeIconStyle,
|
||||||
|
} from '@fortawesome/react-native-fontawesome'
|
||||||
|
import {Text} from '../util/text/Text'
|
||||||
|
import {Button} from '../util/forms/Button'
|
||||||
|
import {MagnifyingGlassIcon} from 'lib/icons'
|
||||||
|
import {NavigationProp} from 'lib/routes/types'
|
||||||
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
import {s} from 'lib/styles'
|
||||||
|
|
||||||
|
export function FollowingEmptyState() {
|
||||||
|
const pal = usePalette('default')
|
||||||
|
const palInverted = usePalette('inverted')
|
||||||
|
const navigation = useNavigation<NavigationProp>()
|
||||||
|
|
||||||
|
const onPressFindAccounts = React.useCallback(() => {
|
||||||
|
navigation.navigate('SearchTab')
|
||||||
|
navigation.popToTop()
|
||||||
|
}, [navigation])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.emptyContainer}>
|
||||||
|
<View style={styles.emptyIconContainer}>
|
||||||
|
<MagnifyingGlassIcon style={[styles.emptyIcon, pal.text]} size={62} />
|
||||||
|
</View>
|
||||||
|
<Text type="xl-medium" style={[s.textCenter, pal.text]}>
|
||||||
|
Your following feed is empty! Find some accounts to follow to fix this.
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
type="inverted"
|
||||||
|
style={styles.emptyBtn}
|
||||||
|
onPress={onPressFindAccounts}>
|
||||||
|
<Text type="lg-medium" style={palInverted.text}>
|
||||||
|
Find accounts to follow
|
||||||
|
</Text>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon="angle-right"
|
||||||
|
style={palInverted.text as FontAwesomeIconStyle}
|
||||||
|
size={14}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
emptyContainer: {
|
||||||
|
// flex: 1,
|
||||||
|
height: '100%',
|
||||||
|
paddingVertical: 40,
|
||||||
|
paddingHorizontal: 30,
|
||||||
|
},
|
||||||
|
emptyIconContainer: {
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
emptyIcon: {
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
},
|
||||||
|
emptyBtn: {
|
||||||
|
marginVertical: 20,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
paddingVertical: 18,
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
borderRadius: 30,
|
||||||
|
},
|
||||||
|
|
||||||
|
feedsTip: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 22,
|
||||||
|
},
|
||||||
|
feedsTipArrow: {
|
||||||
|
marginLeft: 32,
|
||||||
|
marginTop: 8,
|
||||||
|
},
|
||||||
|
})
|
|
@ -1,21 +1,30 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Animated, View} from 'react-native'
|
import {Animated, View} from 'react-native'
|
||||||
import PagerView, {PagerViewOnPageSelectedEvent} from 'react-native-pager-view'
|
import PagerView, {PagerViewOnPageSelectedEvent} from 'react-native-pager-view'
|
||||||
import {TabBarProps} from './TabBar'
|
|
||||||
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
|
|
||||||
export type PageSelectedEvent = PagerViewOnPageSelectedEvent
|
export type PageSelectedEvent = PagerViewOnPageSelectedEvent
|
||||||
const AnimatedPagerView = Animated.createAnimatedComponent(PagerView)
|
const AnimatedPagerView = Animated.createAnimatedComponent(PagerView)
|
||||||
|
|
||||||
|
export interface RenderTabBarFnProps {
|
||||||
|
selectedPage: number
|
||||||
|
position: Animated.Value
|
||||||
|
offset: Animated.Value
|
||||||
|
onSelect?: (index: number) => void
|
||||||
|
}
|
||||||
|
export type RenderTabBarFn = (props: RenderTabBarFnProps) => JSX.Element
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
tabBarPosition?: 'top' | 'bottom'
|
tabBarPosition?: 'top' | 'bottom'
|
||||||
renderTabBar: (props: TabBarProps) => JSX.Element
|
initialPage?: number
|
||||||
|
renderTabBar: RenderTabBarFn
|
||||||
onPageSelected?: (e: PageSelectedEvent) => void
|
onPageSelected?: (e: PageSelectedEvent) => void
|
||||||
}
|
}
|
||||||
export const Pager = ({
|
export const Pager = ({
|
||||||
children,
|
children,
|
||||||
tabBarPosition = 'top',
|
tabBarPosition = 'top',
|
||||||
|
initialPage = 0,
|
||||||
renderTabBar,
|
renderTabBar,
|
||||||
onPageSelected,
|
onPageSelected,
|
||||||
}: React.PropsWithChildren<Props>) => {
|
}: React.PropsWithChildren<Props>) => {
|
||||||
|
@ -51,7 +60,7 @@ export const Pager = ({
|
||||||
<AnimatedPagerView
|
<AnimatedPagerView
|
||||||
ref={pagerView}
|
ref={pagerView}
|
||||||
style={s.h100pct}
|
style={s.h100pct}
|
||||||
initialPage={0}
|
initialPage={initialPage}
|
||||||
onPageSelected={onPageSelectedInner}
|
onPageSelected={onPageSelectedInner}
|
||||||
onPageScroll={Animated.event(
|
onPageScroll={Animated.event(
|
||||||
[
|
[
|
||||||
|
|
|
@ -13,9 +13,14 @@ import {NativeStackScreenProps, HomeTabNavigatorParams} from 'lib/routes/types'
|
||||||
import {FeedModel} from 'state/models/feed-view'
|
import {FeedModel} from 'state/models/feed-view'
|
||||||
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
|
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
|
||||||
import {Feed} from '../com/posts/Feed'
|
import {Feed} from '../com/posts/Feed'
|
||||||
|
import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState'
|
||||||
import {LoadLatestBtn} from '../com/util/LoadLatestBtn'
|
import {LoadLatestBtn} from '../com/util/LoadLatestBtn'
|
||||||
import {TabBar, TabBarProps} from 'view/com/util/TabBar'
|
import {TabBar} from 'view/com/util/TabBar'
|
||||||
import {Pager, PageSelectedEvent} from 'view/com/util/Pager'
|
import {
|
||||||
|
Pager,
|
||||||
|
PageSelectedEvent,
|
||||||
|
RenderTabBarFnProps,
|
||||||
|
} from 'view/com/util/Pager'
|
||||||
import {FAB} from '../com/util/FAB'
|
import {FAB} from '../com/util/FAB'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
@ -63,28 +68,36 @@ export const HomeScreen = withAuthRequired((_opts: Props) => {
|
||||||
}, [store])
|
}, [store])
|
||||||
|
|
||||||
const renderTabBar = React.useCallback(
|
const renderTabBar = React.useCallback(
|
||||||
(props: TabBarProps) => {
|
(props: RenderTabBarFnProps) => {
|
||||||
return <FloatingTabBar {...props} onPressSelected={onPressSelected} />
|
return <FloatingTabBar {...props} onPressSelected={onPressSelected} />
|
||||||
},
|
},
|
||||||
[onPressSelected],
|
[onPressSelected],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const renderFollowingEmptyState = React.useCallback(() => {
|
||||||
|
return <FollowingEmptyState />
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const initialPage = store.me.follows.isEmpty ? 1 : 0
|
||||||
return (
|
return (
|
||||||
<Pager
|
<Pager
|
||||||
onPageSelected={onPageSelected}
|
onPageSelected={onPageSelected}
|
||||||
renderTabBar={renderTabBar}
|
renderTabBar={renderTabBar}
|
||||||
tabBarPosition="bottom">
|
tabBarPosition="bottom"
|
||||||
|
initialPage={initialPage}>
|
||||||
<FeedPage
|
<FeedPage
|
||||||
key="1"
|
key="1"
|
||||||
isPageFocused={selectedPage === 0}
|
isPageFocused={selectedPage === 0}
|
||||||
feed={store.me.mainFeed}
|
feed={store.me.mainFeed}
|
||||||
|
renderEmptyState={renderFollowingEmptyState}
|
||||||
/>
|
/>
|
||||||
<FeedPage key="2" isPageFocused={selectedPage === 1} feed={algoFeed} />
|
<FeedPage key="2" isPageFocused={selectedPage === 1} feed={algoFeed} />
|
||||||
</Pager>
|
</Pager>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const FloatingTabBar = observer((props: TabBarProps) => {
|
const FloatingTabBar = observer(
|
||||||
|
(props: RenderTabBarFnProps & {onPressSelected: () => void}) => {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const safeAreaInsets = useSafeAreaInsets()
|
const safeAreaInsets = useSafeAreaInsets()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
|
@ -122,10 +135,19 @@ const FloatingTabBar = observer((props: TabBarProps) => {
|
||||||
/>
|
/>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const FeedPage = observer(
|
const FeedPage = observer(
|
||||||
({isPageFocused, feed}: {feed: FeedModel; isPageFocused: boolean}) => {
|
({
|
||||||
|
isPageFocused,
|
||||||
|
feed,
|
||||||
|
renderEmptyState,
|
||||||
|
}: {
|
||||||
|
feed: FeedModel
|
||||||
|
isPageFocused: boolean
|
||||||
|
renderEmptyState?: () => JSX.Element
|
||||||
|
}) => {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const onMainScroll = useOnMainScroll(store)
|
const onMainScroll = useOnMainScroll(store)
|
||||||
const {screen, track} = useAnalytics()
|
const {screen, track} = useAnalytics()
|
||||||
|
@ -213,6 +235,7 @@ const FeedPage = observer(
|
||||||
showPostFollowBtn
|
showPostFollowBtn
|
||||||
onPressTryAgain={onPressTryAgain}
|
onPressTryAgain={onPressTryAgain}
|
||||||
onScroll={onMainScroll}
|
onScroll={onMainScroll}
|
||||||
|
renderEmptyState={renderEmptyState}
|
||||||
/>
|
/>
|
||||||
{feed.hasNewLatest && !feed.isRefreshing && (
|
{feed.hasNewLatest && !feed.isRefreshing && (
|
||||||
<LoadLatestBtn onPress={onPressLoadLatest} />
|
<LoadLatestBtn onPress={onPressLoadLatest} />
|
||||||
|
|
Loading…
Reference in New Issue