diff --git a/src/state/models/me.ts b/src/state/models/me.ts index 077c6559..192e8f19 100644 --- a/src/state/models/me.ts +++ b/src/state/models/me.ts @@ -33,6 +33,7 @@ export class MeModel { clear() { this.mainFeed.clear() this.notifications.clear() + this.follows.clear() this.did = '' this.handle = '' this.displayName = '' diff --git a/src/state/models/my-follows.ts b/src/state/models/my-follows.ts index 732c2fe7..bf1bf960 100644 --- a/src/state/models/my-follows.ts +++ b/src/state/models/my-follows.ts @@ -35,6 +35,12 @@ export class MyFollowsModel { // public api // = + clear() { + this.followDidToRecordMap = {} + this.lastSync = 0 + this.myDid = undefined + } + fetchIfNeeded = bundleAsync(async () => { if ( this.myDid !== this.rootStore.me.did || diff --git a/src/state/models/session.ts b/src/state/models/session.ts index efd7a5fa..e131b2b2 100644 --- a/src/state/models/session.ts +++ b/src/state/models/session.ts @@ -154,13 +154,13 @@ export class SessionModel { /** * Sets the active session */ - setActiveSession(agent: AtpAgent, did: string) { + async setActiveSession(agent: AtpAgent, did: string) { this._log('SessionModel:setActiveSession') this.data = { service: agent.service.toString(), did, } - this.rootStore.handleSessionChange(agent) + await this.rootStore.handleSessionChange(agent) } /** @@ -304,7 +304,7 @@ export class SessionModel { return false } - this.setActiveSession(agent, account.did) + await this.setActiveSession(agent, account.did) return true } @@ -337,7 +337,7 @@ export class SessionModel { }, ) - this.setActiveSession(agent, did) + await this.setActiveSession(agent, did) 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') } diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index d23455b5..c910b70e 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -7,23 +7,15 @@ import { StyleSheet, ViewStyle, } 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 {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {ViewHeader} from '../util/ViewHeader' -import {Text} from '../util/text/Text' import {ErrorMessage} from '../util/error/ErrorMessage' -import {Button} from '../util/forms/Button' import {FeedModel} from 'state/models/feed-view' import {FeedSlice} from './FeedSlice' import {OnScrollCb} from 'lib/hooks/useOnMainScroll' import {s} from 'lib/styles' 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 EMPTY_FEED_ITEM = {_reactKey: '__empty__'} @@ -36,6 +28,7 @@ export const Feed = observer(function Feed({ scrollElRef, onPressTryAgain, onScroll, + renderEmptyState, testID, headerOffset = 0, }: { @@ -45,14 +38,12 @@ export const Feed = observer(function Feed({ scrollElRef?: MutableRefObject | null> onPressTryAgain?: () => void onScroll?: OnScrollCb + renderEmptyState?: () => JSX.Element testID?: string headerOffset?: number }) { - const pal = usePalette('default') - const palInverted = usePalette('inverted') const {track} = useAnalytics() const [isRefreshing, setIsRefreshing] = React.useState(false) - const navigation = useNavigation() const data = React.useMemo(() => { let feedItems: any[] = [HEADER_ITEM] @@ -82,6 +73,7 @@ export const Feed = observer(function Feed({ } setIsRefreshing(false) }, [feed, track, setIsRefreshing]) + const onEndReached = React.useCallback(async () => { track('Feed:onEndReached') try { @@ -97,37 +89,10 @@ export const Feed = observer(function Feed({ const renderItem = React.useCallback( ({item}: {item: any}) => { if (item === EMPTY_FEED_ITEM) { - return ( - - - - - - Your feed is empty! You should follow some accounts to fix this. - - - - ) + if (renderEmptyState) { + return renderEmptyState() + } + return } else if (item === ERROR_FEED_ITEM) { return ( }, - [feed, onPressTryAgain, showPostFollowBtn, pal, palInverted, navigation], + [feed, onPressTryAgain, showPostFollowBtn, renderEmptyState], ) const FeedFooter = React.useCallback( @@ -187,21 +152,4 @@ export const Feed = observer(function Feed({ const styles = StyleSheet.create({ 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', - }, }) diff --git a/src/view/com/posts/FollowingEmptyState.tsx b/src/view/com/posts/FollowingEmptyState.tsx new file mode 100644 index 00000000..acd035f2 --- /dev/null +++ b/src/view/com/posts/FollowingEmptyState.tsx @@ -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() + + const onPressFindAccounts = React.useCallback(() => { + navigation.navigate('SearchTab') + navigation.popToTop() + }, [navigation]) + + return ( + + + + + + Your following feed is empty! Find some accounts to follow to fix this. + + + + ) +} +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, + }, +}) diff --git a/src/view/com/util/Pager.tsx b/src/view/com/util/Pager.tsx index c4f17ce6..d71cb7f7 100644 --- a/src/view/com/util/Pager.tsx +++ b/src/view/com/util/Pager.tsx @@ -1,21 +1,30 @@ import React from 'react' import {Animated, View} from 'react-native' import PagerView, {PagerViewOnPageSelectedEvent} from 'react-native-pager-view' -import {TabBarProps} from './TabBar' import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' import {s} from 'lib/styles' export type PageSelectedEvent = PagerViewOnPageSelectedEvent 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 { tabBarPosition?: 'top' | 'bottom' - renderTabBar: (props: TabBarProps) => JSX.Element + initialPage?: number + renderTabBar: RenderTabBarFn onPageSelected?: (e: PageSelectedEvent) => void } export const Pager = ({ children, tabBarPosition = 'top', + initialPage = 0, renderTabBar, onPageSelected, }: React.PropsWithChildren) => { @@ -51,7 +60,7 @@ export const Pager = ({ { }, [store]) const renderTabBar = React.useCallback( - (props: TabBarProps) => { + (props: RenderTabBarFnProps) => { return }, [onPressSelected], ) + const renderFollowingEmptyState = React.useCallback(() => { + return + }, []) + + const initialPage = store.me.follows.isEmpty ? 1 : 0 return ( + tabBarPosition="bottom" + initialPage={initialPage}> ) }) -const FloatingTabBar = observer((props: TabBarProps) => { - const store = useStores() - const safeAreaInsets = useSafeAreaInsets() - const pal = usePalette('default') - const interp = useAnimatedValue(0) +const FloatingTabBar = observer( + (props: RenderTabBarFnProps & {onPressSelected: () => void}) => { + 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], - ) + 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)}, - ], - } + 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 ( - - - - ) -}) + return ( + + + + ) + }, +) const FeedPage = observer( - ({isPageFocused, feed}: {feed: FeedModel; isPageFocused: boolean}) => { + ({ + isPageFocused, + feed, + renderEmptyState, + }: { + feed: FeedModel + isPageFocused: boolean + renderEmptyState?: () => JSX.Element + }) => { const store = useStores() const onMainScroll = useOnMainScroll(store) const {screen, track} = useAnalytics() @@ -213,6 +235,7 @@ const FeedPage = observer( showPostFollowBtn onPressTryAgain={onPressTryAgain} onScroll={onMainScroll} + renderEmptyState={renderEmptyState} /> {feed.hasNewLatest && !feed.isRefreshing && (