diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/App.native.tsx | 26 | ||||
-rw-r--r-- | src/App.web.tsx | 28 | ||||
-rw-r--r-- | src/Navigation.tsx | 5 | ||||
-rw-r--r-- | src/lib/ThemeContext.tsx | 54 | ||||
-rw-r--r-- | src/lib/api/feed-manip.ts | 5 | ||||
-rw-r--r-- | src/lib/constants.ts | 107 | ||||
-rw-r--r-- | src/lib/react-query.ts | 3 | ||||
-rw-r--r-- | src/view/com/auth/onboarding/RecommendedFeeds.tsx | 70 | ||||
-rw-r--r-- | src/view/com/auth/onboarding/RecommendedFeedsItem.tsx | 11 | ||||
-rw-r--r-- | src/view/shell/Drawer.tsx | 9 | ||||
-rw-r--r-- | src/view/shell/bottom-bar/BottomBarWeb.tsx | 28 |
11 files changed, 160 insertions, 186 deletions
diff --git a/src/App.native.tsx b/src/App.native.tsx index 09782a875..d43155bf3 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -16,6 +16,8 @@ import * as notifications from 'lib/notifications/notifications' import * as analytics from 'lib/analytics/analytics' import * as Toast from './view/com/util/Toast' import {handleLink} from './Navigation' +import {QueryClientProvider} from '@tanstack/react-query' +import {queryClient} from 'lib/react-query' SplashScreen.preventAutoHideAsync() @@ -51,17 +53,19 @@ const App = observer(function AppImpl() { return null } return ( - <ThemeProvider theme={rootStore.shell.colorMode}> - <RootSiblingParent> - <analytics.Provider> - <RootStoreProvider value={rootStore}> - <GestureHandlerRootView style={s.h100pct}> - <Shell /> - </GestureHandlerRootView> - </RootStoreProvider> - </analytics.Provider> - </RootSiblingParent> - </ThemeProvider> + <QueryClientProvider client={queryClient}> + <ThemeProvider theme={rootStore.shell.colorMode}> + <RootSiblingParent> + <analytics.Provider> + <RootStoreProvider value={rootStore}> + <GestureHandlerRootView style={s.h100pct}> + <Shell /> + </GestureHandlerRootView> + </RootStoreProvider> + </analytics.Provider> + </RootSiblingParent> + </ThemeProvider> + </QueryClientProvider> ) }) diff --git a/src/App.web.tsx b/src/App.web.tsx index 41a7189d3..a9123cc58 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -9,6 +9,8 @@ import {Shell} from './view/shell/index' import {ToastContainer} from './view/com/util/Toast.web' import {ThemeProvider} from 'lib/ThemeContext' import {observer} from 'mobx-react-lite' +import {QueryClientProvider} from '@tanstack/react-query' +import {queryClient} from 'lib/react-query' const App = observer(function AppImpl() { const [rootStore, setRootStore] = useState<RootStoreModel | undefined>( @@ -30,18 +32,20 @@ const App = observer(function AppImpl() { } return ( - <ThemeProvider theme={rootStore.shell.colorMode}> - <RootSiblingParent> - <analytics.Provider> - <RootStoreProvider value={rootStore}> - <SafeAreaProvider> - <Shell /> - </SafeAreaProvider> - <ToastContainer /> - </RootStoreProvider> - </analytics.Provider> - </RootSiblingParent> - </ThemeProvider> + <QueryClientProvider client={queryClient}> + <ThemeProvider theme={rootStore.shell.colorMode}> + <RootSiblingParent> + <analytics.Provider> + <RootStoreProvider value={rootStore}> + <SafeAreaProvider> + <Shell /> + </SafeAreaProvider> + <ToastContainer /> + </RootStoreProvider> + </analytics.Provider> + </RootSiblingParent> + </ThemeProvider> + </QueryClientProvider> ) }) diff --git a/src/Navigation.tsx b/src/Navigation.tsx index dac70dfc7..c16ff3a8c 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -348,7 +348,6 @@ const MyProfileTabNavigator = observer(function MyProfileTabNavigatorImpl() { component={ProfileScreen} initialParams={{ name: store.me.did, - hideBackButton: true, }} /> {commonScreens(MyProfileTab as typeof HomeTab)} @@ -362,7 +361,9 @@ const MyProfileTabNavigator = observer(function MyProfileTabNavigatorImpl() { */ const FlatNavigator = observer(function FlatNavigatorImpl() { const pal = usePalette('default') - const unreadCountLabel = useStores().me.notifications.unreadCountLabel + const store = useStores() + const unreadCountLabel = store.me.notifications.unreadCountLabel + const title = (page: string) => bskyTitle(page, unreadCountLabel) return ( <Flat.Navigator diff --git a/src/lib/ThemeContext.tsx b/src/lib/ThemeContext.tsx index fe25dde54..483c50c42 100644 --- a/src/lib/ThemeContext.tsx +++ b/src/lib/ThemeContext.tsx @@ -1,8 +1,9 @@ +import {isWeb} from 'platform/detection' import React, {ReactNode, createContext, useContext} from 'react' import { AppState, TextStyle, - useColorScheme, + useColorScheme as useColorScheme_BUGGY, ViewStyle, ColorSchemeName, } from 'react-native' @@ -92,33 +93,44 @@ export const ThemeContext = createContext<Theme>(defaultTheme) export const useTheme = () => useContext(ThemeContext) -export const ThemeProvider: React.FC<ThemeProviderProps> = ({ - theme, - children, -}) => { - const colorSchemeFromRN = useColorScheme() - const [nativeColorScheme, setNativeColorScheme] = - React.useState<ColorSchemeName>(colorSchemeFromRN) +function getTheme(theme: ColorSchemeName) { + return theme === 'dark' ? darkTheme : defaultTheme +} + +/** + * With RN iOS, we can only "trust" the color scheme reported while the app is + * active. This is a workaround until the bug is fixed upstream. + * + * @see https://github.com/bluesky-social/social-app/pull/1417#issuecomment-1719868504 + * @see https://github.com/facebook/react-native/pull/39439 + */ +function useColorScheme_FIXED() { + const colorScheme = useColorScheme_BUGGY() + const [currentColorScheme, setCurrentColorScheme] = + React.useState<ColorSchemeName>(colorScheme) React.useEffect(() => { + // we don't need to be updating state on web + if (isWeb) return const subscription = AppState.addEventListener('change', state => { const isActive = state === 'active' - if (!isActive) return - - setNativeColorScheme(colorSchemeFromRN) + setCurrentColorScheme(colorScheme) }) return () => subscription.remove() - }, [colorSchemeFromRN]) + }, [colorScheme]) + + return isWeb ? colorScheme : currentColorScheme +} - const value = - theme === 'system' - ? nativeColorScheme === 'dark' - ? darkTheme - : defaultTheme - : theme === 'dark' - ? darkTheme - : defaultTheme +export const ThemeProvider: React.FC<ThemeProviderProps> = ({ + theme, + children, +}) => { + const colorScheme = useColorScheme_FIXED() + const themeValue = getTheme(theme === 'system' ? colorScheme : theme) - return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider> + return ( + <ThemeContext.Provider value={themeValue}>{children}</ThemeContext.Provider> + ) } diff --git a/src/lib/api/feed-manip.ts b/src/lib/api/feed-manip.ts index 60b0f2641..149859ea9 100644 --- a/src/lib/api/feed-manip.ts +++ b/src/lib/api/feed-manip.ts @@ -281,7 +281,10 @@ export class FeedTuner { function getSelfReplyUri(item: FeedViewPost): string | undefined { if (item.reply) { - if (AppBskyFeedDefs.isPostView(item.reply.parent)) { + if ( + AppBskyFeedDefs.isPostView(item.reply.parent) && + !AppBskyFeedDefs.isReasonRepost(item.reason) // don't thread reposted self-replies + ) { return item.reply.parent.author.did === item.post.author.did ? item.reply.parent.uri : undefined diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 94551e6ef..001cdf8c3 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -148,110 +148,3 @@ export const HITSLOP_10 = createHitslop(10) export const HITSLOP_20 = createHitslop(20) export const HITSLOP_30 = createHitslop(30) export const BACK_HITSLOP = HITSLOP_30 - -export const RECOMMENDED_FEEDS = [ - { - did: 'did:plc:hsqwcidfez66lwm3gxhfv5in', - rkey: 'aaaf2pqeodmpy', - }, - { - did: 'did:plc:gekdk2nd47gkk3utfz2xf7cn', - rkey: 'aaap4tbjcfe5y', - }, - { - did: 'did:plc:5rw2on4i56btlcajojaxwcat', - rkey: 'aaao6g552b33o', - }, - { - did: 'did:plc:jfhpnnst6flqway4eaeqzj2a', - rkey: 'for-science', - }, - { - did: 'did:plc:7q4nnnxawajbfaq7to5dpbsy', - rkey: 'bsky-news', - }, - { - did: 'did:plc:jcoy7v3a2t4rcfdh6i4kza25', - rkey: 'astro', - }, - { - did: 'did:plc:tenurhgjptubkk5zf5qhi3og', - rkey: 'h-nba', - }, - { - did: 'did:plc:vpkhqolt662uhesyj6nxm7ys', - rkey: 'devfeed', - }, - { - did: 'did:plc:cndfx4udwgvpjaakvxvh7wm5', - rkey: 'flipboard-tech', - }, - { - did: 'did:plc:w4xbfzo7kqfes5zb7r6qv3rw', - rkey: 'blacksky', - }, - { - did: 'did:plc:lptjvw6ut224kwrj7ub3sqbe', - rkey: 'aaaotfjzjplna', - }, - { - did: 'did:plc:gkvpokm7ec5j5yxls6xk4e3z', - rkey: 'formula-one', - }, - { - did: 'did:plc:q6gjnaw2blty4crticxkmujt', - rkey: 'positivifeed', - }, - { - did: 'did:plc:l72uci4styb4jucsgcrrj5ap', - rkey: 'aaao5dzfm36u4', - }, - { - did: 'did:plc:k3jkadxv5kkjgs6boyon7m6n', - rkey: 'aaaavlyvqzst2', - }, - { - did: 'did:plc:nkahctfdi6bxk72umytfwghw', - rkey: 'aaado2uvfsc6w', - }, - { - did: 'did:plc:epihigio3d7un7u3gpqiy5gv', - rkey: 'aaaekwsc7zsvs', - }, - { - did: 'did:plc:qiknc4t5rq7yngvz7g4aezq7', - rkey: 'aaaejxlobe474', - }, - { - did: 'did:plc:mlq4aycufcuolr7ax6sezpc4', - rkey: 'aaaoudweck6uy', - }, - { - did: 'did:plc:rcez5hcvq3vzlu5x7xrjyccg', - rkey: 'aaadzjxbcddzi', - }, - { - did: 'did:plc:lnxbuzaenlwjrncx6sc4cfdr', - rkey: 'aaab2vesjtszc', - }, - { - did: 'did:plc:x3cya3wkt4n6u4ihmvpsc5if', - rkey: 'aaacynbxwimok', - }, - { - did: 'did:plc:abv47bjgzjgoh3yrygwoi36x', - rkey: 'aaagt6amuur5e', - }, - { - did: 'did:plc:ffkgesg3jsv2j7aagkzrtcvt', - rkey: 'aaacjerk7gwek', - }, - { - did: 'did:plc:geoqe3qls5mwezckxxsewys2', - rkey: 'aaai43yetqshu', - }, - { - did: 'did:plc:2wqomm3tjqbgktbrfwgvrw34', - rkey: 'authors', - }, -] diff --git a/src/lib/react-query.ts b/src/lib/react-query.ts new file mode 100644 index 000000000..2a8f1d759 --- /dev/null +++ b/src/lib/react-query.ts @@ -0,0 +1,3 @@ +import {QueryClient} from '@tanstack/react-query' + +export const queryClient = new QueryClient() diff --git a/src/view/com/auth/onboarding/RecommendedFeeds.tsx b/src/view/com/auth/onboarding/RecommendedFeeds.tsx index 99cdcafd0..8e29a5895 100644 --- a/src/view/com/auth/onboarding/RecommendedFeeds.tsx +++ b/src/view/com/auth/onboarding/RecommendedFeeds.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {FlatList, StyleSheet, View} from 'react-native' +import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native' import {observer} from 'mobx-react-lite' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {TabletOrDesktop, Mobile} from 'view/com/util/layouts/Breakpoints' @@ -10,7 +10,10 @@ import {Button} from 'view/com/util/forms/Button' import {RecommendedFeedsItem} from './RecommendedFeedsItem' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {usePalette} from 'lib/hooks/usePalette' -import {RECOMMENDED_FEEDS} from 'lib/constants' +import {useQuery} from '@tanstack/react-query' +import {useStores} from 'state/index' +import {CustomFeedModel} from 'state/models/feeds/custom-feed' +import {ErrorMessage} from 'view/com/util/error/ErrorMessage' type Props = { next: () => void @@ -18,8 +21,31 @@ type Props = { export const RecommendedFeeds = observer(function RecommendedFeedsImpl({ next, }: Props) { + const store = useStores() const pal = usePalette('default') const {isTabletOrMobile} = useWebMediaQueries() + const {isLoading, data: recommendedFeeds} = useQuery({ + staleTime: Infinity, // fixed list rn, never refetch + queryKey: ['onboarding', 'recommended_feeds'], + async queryFn() { + try { + const { + data: {feeds}, + success, + } = await store.agent.app.bsky.feed.getSuggestedFeeds() + + if (!success) return + + return (feeds.length ? feeds : []).map(feed => { + return new CustomFeedModel(store, feed) + }) + } catch (e) { + return + } + }, + }) + + const hasFeeds = recommendedFeeds && recommendedFeeds.length const title = ( <> @@ -86,12 +112,20 @@ export const RecommendedFeeds = observer(function RecommendedFeedsImpl({ horizontal titleStyle={isTabletOrMobile ? undefined : {minWidth: 470}} contentStyle={{paddingHorizontal: 0}}> - <FlatList - data={RECOMMENDED_FEEDS} - renderItem={({item}) => <RecommendedFeedsItem {...item} />} - keyExtractor={item => item.did + item.rkey} - style={{flex: 1}} - /> + {hasFeeds ? ( + <FlatList + data={recommendedFeeds} + renderItem={({item}) => <RecommendedFeedsItem item={item} />} + keyExtractor={item => item.uri} + style={{flex: 1}} + /> + ) : isLoading ? ( + <View> + <ActivityIndicator size="large" /> + </View> + ) : ( + <ErrorMessage message="Failed to load recommended feeds" /> + )} </TitleColumnLayout> </TabletOrDesktop> <Mobile> @@ -106,12 +140,20 @@ export const RecommendedFeeds = observer(function RecommendedFeedsImpl({ pinned feeds. </Text> - <FlatList - data={RECOMMENDED_FEEDS} - renderItem={({item}) => <RecommendedFeedsItem {...item} />} - keyExtractor={item => item.did + item.rkey} - style={{flex: 1}} - /> + {hasFeeds ? ( + <FlatList + data={recommendedFeeds} + renderItem={({item}) => <RecommendedFeedsItem item={item} />} + keyExtractor={item => item.uri} + style={{flex: 1}} + /> + ) : isLoading ? ( + <View> + <ActivityIndicator size="large" /> + </View> + ) : ( + <ErrorMessage message="Failed to load recommended feeds" /> + )} <Button onPress={next} diff --git a/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx b/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx index e5d12273a..d130dc138 100644 --- a/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx +++ b/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx @@ -8,22 +8,17 @@ import {UserAvatar} from 'view/com/util/UserAvatar' import * as Toast from 'view/com/util/Toast' import {HeartIcon} from 'lib/icons' import {usePalette} from 'lib/hooks/usePalette' -import {useCustomFeed} from 'lib/hooks/useCustomFeed' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {makeRecordUri} from 'lib/strings/url-helpers' import {sanitizeHandle} from 'lib/strings/handles' +import {CustomFeedModel} from 'state/models/feeds/custom-feed' export const RecommendedFeedsItem = observer(function RecommendedFeedsItemImpl({ - did, - rkey, + item, }: { - did: string - rkey: string + item: CustomFeedModel }) { const {isMobile} = useWebMediaQueries() const pal = usePalette('default') - const uri = makeRecordUri(did, 'app.bsky.feed.generator', rkey) - const item = useCustomFeed(uri) if (!item) return null const onToggle = async () => { if (item.isSaved) { diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx index 3379d0501..67092938e 100644 --- a/src/view/shell/Drawer.tsx +++ b/src/view/shell/Drawer.tsx @@ -64,8 +64,13 @@ export const DrawerContent = observer(function DrawerContentImpl() { const state = navigation.getState() store.shell.closeDrawer() if (isWeb) { - // @ts-ignore must be Home, Search, Notifications, or MyProfile - navigation.navigate(tab) + // hack because we have flat navigator for web and MyProfile does not exist on the web navigator -ansh + if (tab === 'MyProfile') { + navigation.navigate('Profile', {name: store.me.handle}) + } else { + // @ts-ignore must be Home, Search, Notifications, or MyProfile + navigation.navigate(tab) + } } else { const tabState = getTabState(state, tab) if (tabState === TabState.InsideAtRoot) { diff --git a/src/view/shell/bottom-bar/BottomBarWeb.tsx b/src/view/shell/bottom-bar/BottomBarWeb.tsx index ee575c217..af70d3364 100644 --- a/src/view/shell/bottom-bar/BottomBarWeb.tsx +++ b/src/view/shell/bottom-bar/BottomBarWeb.tsx @@ -18,10 +18,12 @@ import { SatelliteDishIcon, SatelliteDishIconSolid, UserIcon, + UserIconSolid, } from 'lib/icons' import {Link} from 'view/com/util/Link' import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' import {makeProfileLink} from 'lib/routes/links' +import {CommonNavigatorParams} from 'lib/routes/types' export const BottomBarWeb = observer(function BottomBarWebImpl() { const store = useStores() @@ -89,13 +91,16 @@ export const BottomBarWeb = observer(function BottomBarWebImpl() { }} </NavItem> <NavItem routeName="Profile" href={makeProfileLink(store.me)}> - {() => ( - <UserIcon - size={28} - strokeWidth={1.5} - style={[styles.ctrlIcon, pal.text, styles.profileIcon]} - /> - )} + {({isActive}) => { + const Icon = isActive ? UserIconSolid : UserIcon + return ( + <Icon + size={28} + strokeWidth={1.5} + style={[styles.ctrlIcon, pal.text, styles.profileIcon]} + /> + ) + }} </NavItem> </Animated.View> ) @@ -107,7 +112,14 @@ const NavItem: React.FC<{ routeName: string }> = ({children, href, routeName}) => { const currentRoute = useNavigationState(getCurrentRoute) - const isActive = isTab(currentRoute.name, routeName) + const store = useStores() + const isActive = + currentRoute.name === 'Profile' + ? isTab(currentRoute.name, routeName) && + (currentRoute.params as CommonNavigatorParams['Profile']).name === + store.me.handle + : isTab(currentRoute.name, routeName) + return ( <Link href={href} style={styles.ctrl}> {children({isActive})} |