Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Tweak rendering of top headers so they always appear even during load (#4982)

Co-authored-by: Eric Bailey <git@esb.lol>

authored by

Hailey
Eric Bailey
and committed by
GitHub
8cddce5f 8651f31e

+118 -128
+10 -53
src/alf/index.tsx
··· 1 1 import React from 'react' 2 - import {Dimensions} from 'react-native' 2 + import {useMediaQuery} from 'react-responsive' 3 3 4 4 import {createThemes, defaultTheme} from '#/alf/themes' 5 5 import {Theme, ThemeName} from '#/alf/types' ··· 12 12 export * from '#/alf/util/platform' 13 13 export * from '#/alf/util/themeSelector' 14 14 15 - type BreakpointName = keyof typeof breakpoints 16 - 17 - /* 18 - * Breakpoints 19 - */ 20 - const breakpoints: { 21 - [key: string]: number 22 - } = { 23 - gtPhone: 500, 24 - gtMobile: 800, 25 - gtTablet: 1300, 26 - } 27 - function getActiveBreakpoints({width}: {width: number}) { 28 - const active: (keyof typeof breakpoints)[] = Object.keys(breakpoints).filter( 29 - breakpoint => width >= breakpoints[breakpoint], 30 - ) 31 - 32 - return { 33 - active: active[active.length - 1], 34 - gtPhone: active.includes('gtPhone'), 35 - gtMobile: active.includes('gtMobile'), 36 - gtTablet: active.includes('gtTablet'), 37 - } 38 - } 39 - 40 15 /* 41 16 * Context 42 17 */ 43 18 export const Context = React.createContext<{ 44 19 themeName: ThemeName 45 20 theme: Theme 46 - breakpoints: { 47 - active: BreakpointName | undefined 48 - gtPhone: boolean 49 - gtMobile: boolean 50 - gtTablet: boolean 51 - } 52 21 }>({ 53 22 themeName: 'light', 54 23 theme: defaultTheme, 55 - breakpoints: { 56 - active: undefined, 57 - gtPhone: false, 58 - gtMobile: false, 59 - gtTablet: false, 60 - }, 61 24 }) 62 25 63 26 export function ThemeProvider({ ··· 74 37 }) 75 38 }, []) 76 39 const theme = themes[themeName] 77 - const [breakpoints, setBreakpoints] = React.useState(() => 78 - getActiveBreakpoints({width: Dimensions.get('window').width}), 79 - ) 80 - 81 - React.useEffect(() => { 82 - const listener = Dimensions.addEventListener('change', ({window}) => { 83 - const bp = getActiveBreakpoints({width: window.width}) 84 - if (bp.active !== breakpoints.active) setBreakpoints(bp) 85 - }) 86 - 87 - return listener.remove 88 - }, [breakpoints, setBreakpoints]) 89 40 90 41 return ( 91 42 <Context.Provider ··· 93 44 () => ({ 94 45 themeName: themeName, 95 46 theme: theme, 96 - breakpoints, 97 47 }), 98 - [theme, themeName, breakpoints], 48 + [theme, themeName], 99 49 )}> 100 50 {children} 101 51 </Context.Provider> ··· 107 57 } 108 58 109 59 export function useBreakpoints() { 110 - return React.useContext(Context).breakpoints 60 + const gtPhone = useMediaQuery({minWidth: 500}) 61 + const gtMobile = useMediaQuery({minWidth: 800}) 62 + const gtTablet = useMediaQuery({minWidth: 1300}) 63 + return { 64 + gtPhone, 65 + gtMobile, 66 + gtTablet, 67 + } 111 68 }
+7 -4
src/screens/Post/PostLikedBy.tsx
··· 1 1 import React from 'react' 2 - import {View} from 'react-native' 3 2 import {msg} from '@lingui/macro' 4 3 import {useLingui} from '@lingui/react' 5 4 import {useFocusEffect} from '@react-navigation/native' ··· 7 6 import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 8 7 import {makeRecordUri} from '#/lib/strings/url-helpers' 9 8 import {useSetMinimalShellMode} from '#/state/shell' 9 + import {isWeb} from 'platform/detection' 10 10 import {PostLikedBy as PostLikedByComponent} from '#/view/com/post-thread/PostLikedBy' 11 11 import {ViewHeader} from '#/view/com/util/ViewHeader' 12 + import {CenteredView} from 'view/com/util/Views' 12 13 import {atoms as a} from '#/alf' 14 + import {ListHeaderDesktop} from '#/components/Lists' 13 15 14 16 type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostLikedBy'> 15 17 export const PostLikedByScreen = ({route}: Props) => { ··· 25 27 ) 26 28 27 29 return ( 28 - <View style={a.flex_1}> 29 - <ViewHeader title={_(msg`Liked By`)} /> 30 + <CenteredView style={a.h_full_vh} sideBorders={true}> 31 + <ListHeaderDesktop title={_(msg`Liked By`)} /> 32 + <ViewHeader title={_(msg`Liked By`)} showBorder={!isWeb} /> 30 33 <PostLikedByComponent uri={uri} /> 31 - </View> 34 + </CenteredView> 32 35 ) 33 36 }
+7 -4
src/screens/Post/PostQuotes.tsx
··· 1 1 import React from 'react' 2 - import {View} from 'react-native' 3 2 import {msg} from '@lingui/macro' 4 3 import {useLingui} from '@lingui/react' 5 4 import {useFocusEffect} from '@react-navigation/native' ··· 7 6 import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 8 7 import {makeRecordUri} from '#/lib/strings/url-helpers' 9 8 import {useSetMinimalShellMode} from '#/state/shell' 9 + import {isWeb} from 'platform/detection' 10 10 import {PostQuotes as PostQuotesComponent} from '#/view/com/post-thread/PostQuotes' 11 11 import {ViewHeader} from '#/view/com/util/ViewHeader' 12 + import {CenteredView} from 'view/com/util/Views' 12 13 import {atoms as a} from '#/alf' 14 + import {ListHeaderDesktop} from '#/components/Lists' 13 15 14 16 type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostQuotes'> 15 17 export const PostQuotesScreen = ({route}: Props) => { ··· 25 27 ) 26 28 27 29 return ( 28 - <View style={a.flex_1}> 29 - <ViewHeader title={_(msg`Quotes`)} /> 30 + <CenteredView style={a.h_full_vh} sideBorders={true}> 31 + <ListHeaderDesktop title={_(msg`Quotes`)} /> 32 + <ViewHeader title={_(msg`Quotes`)} showBorder={!isWeb} /> 30 33 <PostQuotesComponent uri={uri} /> 31 - </View> 34 + </CenteredView> 32 35 ) 33 36 }
+7 -4
src/screens/Post/PostRepostedBy.tsx
··· 1 1 import React from 'react' 2 - import {View} from 'react-native' 3 2 import {msg} from '@lingui/macro' 4 3 import {useLingui} from '@lingui/react' 5 4 import {useFocusEffect} from '@react-navigation/native' ··· 7 6 import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 8 7 import {makeRecordUri} from '#/lib/strings/url-helpers' 9 8 import {useSetMinimalShellMode} from '#/state/shell' 9 + import {isWeb} from 'platform/detection' 10 10 import {PostRepostedBy as PostRepostedByComponent} from '#/view/com/post-thread/PostRepostedBy' 11 11 import {ViewHeader} from '#/view/com/util/ViewHeader' 12 + import {CenteredView} from 'view/com/util/Views' 12 13 import {atoms as a} from '#/alf' 14 + import {ListHeaderDesktop} from '#/components/Lists' 13 15 14 16 type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostRepostedBy'> 15 17 export const PostRepostedByScreen = ({route}: Props) => { ··· 25 27 ) 26 28 27 29 return ( 28 - <View style={a.flex_1}> 29 - <ViewHeader title={_(msg`Reposted By`)} /> 30 + <CenteredView style={a.h_full_vh} sideBorders={true}> 31 + <ListHeaderDesktop title={_(msg`Reposted By`)} /> 32 + <ViewHeader title={_(msg`Reposted By`)} showBorder={!isWeb} /> 30 33 <PostRepostedByComponent uri={uri} /> 31 - </View> 34 + </CenteredView> 32 35 ) 33 36 }
+12 -11
src/view/com/post-thread/PostLikedBy.tsx
··· 1 1 import React, {useCallback, useMemo, useState} from 'react' 2 2 import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api' 3 - import {msg} from '@lingui/macro' 4 - import {useLingui} from '@lingui/react' 5 3 6 4 import {cleanError} from '#/lib/strings/errors' 7 5 import {logger} from '#/logger' 8 6 import {useLikedByQuery} from '#/state/queries/post-liked-by' 9 7 import {useResolveUriQuery} from '#/state/queries/resolve-uri' 10 8 import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' 9 + import {isWeb} from 'platform/detection' 11 10 import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' 12 11 import {List} from '#/view/com/util/List' 13 - import { 14 - ListFooter, 15 - ListHeaderDesktop, 16 - ListMaybePlaceholder, 17 - } from '#/components/Lists' 12 + import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 18 13 19 - function renderItem({item}: {item: GetLikes.Like}) { 20 - return <ProfileCardWithFollowBtn key={item.actor.did} profile={item.actor} /> 14 + function renderItem({item, index}: {item: GetLikes.Like; index: number}) { 15 + return ( 16 + <ProfileCardWithFollowBtn 17 + key={item.actor.did} 18 + profile={item.actor} 19 + noBorder={index === 0 && !isWeb} 20 + /> 21 + ) 21 22 } 22 23 23 24 function keyExtractor(item: GetLikes.Like) { ··· 25 26 } 26 27 27 28 export function PostLikedBy({uri}: {uri: string}) { 28 - const {_} = useLingui() 29 29 const initialNumToRender = useInitialNumToRender() 30 30 31 31 const [isPTRing, setIsPTRing] = useState(false) ··· 78 78 <ListMaybePlaceholder 79 79 isLoading={isLoadingUri || isLoadingLikes} 80 80 isError={isError} 81 + sideBorders={false} 81 82 /> 82 83 ) 83 84 } ··· 91 92 onRefresh={onRefresh} 92 93 onEndReached={onEndReached} 93 94 onEndReachedThreshold={4} 94 - ListHeaderComponent={<ListHeaderDesktop title={_(msg`Liked By`)} />} 95 95 ListFooterComponent={ 96 96 <ListFooter 97 97 isFetchingNextPage={isFetchingNextPage} ··· 103 103 desktopFixedHeight 104 104 initialNumToRender={initialNumToRender} 105 105 windowSize={11} 106 + sideBorders={false} 106 107 /> 107 108 ) 108 109 }
+7 -8
src/view/com/post-thread/PostQuotes.tsx
··· 14 14 import {usePostQuotesQuery} from '#/state/queries/post-quotes' 15 15 import {useResolveUriQuery} from '#/state/queries/resolve-uri' 16 16 import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' 17 + import {isWeb} from 'platform/detection' 17 18 import {Post} from 'view/com/post/Post' 18 - import { 19 - ListFooter, 20 - ListHeaderDesktop, 21 - ListMaybePlaceholder, 22 - } from '#/components/Lists' 19 + import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 23 20 import {List} from '../util/List' 24 21 25 22 function renderItem({ 26 23 item, 24 + index, 27 25 }: { 28 26 item: { 29 27 post: AppBskyFeedDefs.PostView 30 28 moderation: ModerationDecision 31 29 record: AppBskyFeedPost.Record 32 30 } 31 + index: number 33 32 }) { 34 - return <Post post={item.post} /> 33 + return <Post post={item.post} hideTopBorder={index === 0 && !isWeb} /> 35 34 } 36 35 37 36 function keyExtractor(item: { ··· 45 44 export function PostQuotes({uri}: {uri: string}) { 46 45 const {_} = useLingui() 47 46 const initialNumToRender = useInitialNumToRender() 48 - 49 47 const [isPTRing, setIsPTRing] = useState(false) 50 48 51 49 const { ··· 104 102 <ListMaybePlaceholder 105 103 isLoading={isLoadingUri || isLoadingQuotes} 106 104 isError={isError} 105 + sideBorders={false} 107 106 /> 108 107 ) 109 108 } ··· 119 118 onRefresh={onRefresh} 120 119 onEndReached={onEndReached} 121 120 onEndReachedThreshold={4} 122 - ListHeaderComponent={<ListHeaderDesktop title={_(msg`Quotes`)} />} 123 121 ListFooterComponent={ 124 122 <ListFooter 125 123 isFetchingNextPage={isFetchingNextPage} ··· 133 131 desktopFixedHeight 134 132 initialNumToRender={initialNumToRender} 135 133 windowSize={11} 134 + sideBorders={false} 136 135 /> 137 136 ) 138 137 }
+3 -9
src/view/com/post-thread/PostRepostedBy.tsx
··· 1 1 import React, {useCallback, useMemo, useState} from 'react' 2 2 import {AppBskyActorDefs as ActorDefs} from '@atproto/api' 3 - import {msg} from '@lingui/macro' 4 - import {useLingui} from '@lingui/react' 5 3 6 4 import {cleanError} from '#/lib/strings/errors' 7 5 import {logger} from '#/logger' ··· 10 8 import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' 11 9 import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' 12 10 import {List} from '#/view/com/util/List' 13 - import { 14 - ListFooter, 15 - ListHeaderDesktop, 16 - ListMaybePlaceholder, 17 - } from '#/components/Lists' 11 + import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 18 12 19 13 function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) { 20 14 return <ProfileCardWithFollowBtn key={item.did} profile={item} /> ··· 25 19 } 26 20 27 21 export function PostRepostedBy({uri}: {uri: string}) { 28 - const {_} = useLingui() 29 22 const initialNumToRender = useInitialNumToRender() 30 23 31 24 const [isPTRing, setIsPTRing] = useState(false) ··· 78 71 <ListMaybePlaceholder 79 72 isLoading={isLoadingUri || isLoadingRepostedBy} 80 73 isError={isError} 74 + sideBorders={false} 81 75 /> 82 76 ) 83 77 } ··· 93 87 onRefresh={onRefresh} 94 88 onEndReached={onEndReached} 95 89 onEndReachedThreshold={4} 96 - ListHeaderComponent={<ListHeaderDesktop title={_(msg`Reposted By`)} />} 97 90 ListFooterComponent={ 98 91 <ListFooter 99 92 isFetchingNextPage={isFetchingNextPage} ··· 105 98 desktopFixedHeight 106 99 initialNumToRender={initialNumToRender} 107 100 windowSize={11} 101 + sideBorders={false} 108 102 /> 109 103 ) 110 104 }
+18 -8
src/view/com/profile/ProfileFollowers.tsx
··· 8 8 import {useProfileFollowersQuery} from '#/state/queries/profile-followers' 9 9 import {useResolveDidQuery} from '#/state/queries/resolve-uri' 10 10 import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' 11 + import {isWeb} from 'platform/detection' 11 12 import {useSession} from 'state/session' 12 - import { 13 - ListFooter, 14 - ListHeaderDesktop, 15 - ListMaybePlaceholder, 16 - } from '#/components/Lists' 13 + import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 17 14 import {List} from '../util/List' 18 15 import {ProfileCardWithFollowBtn} from './ProfileCard' 19 16 20 - function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) { 21 - return <ProfileCardWithFollowBtn key={item.did} profile={item} /> 17 + function renderItem({ 18 + item, 19 + index, 20 + }: { 21 + item: ActorDefs.ProfileViewBasic 22 + index: number 23 + }) { 24 + return ( 25 + <ProfileCardWithFollowBtn 26 + key={item.did} 27 + profile={item} 28 + noBorder={index === 0 && !isWeb} 29 + /> 30 + ) 22 31 } 23 32 24 33 function keyExtractor(item: ActorDefs.ProfileViewBasic) { ··· 88 97 } 89 98 errorMessage={cleanError(resolveError || error)} 90 99 onRetry={isError ? refetch : undefined} 100 + sideBorders={false} 91 101 /> 92 102 ) 93 103 } ··· 101 111 onRefresh={onRefresh} 102 112 onEndReached={onEndReached} 103 113 onEndReachedThreshold={4} 104 - ListHeaderComponent={<ListHeaderDesktop title={_(msg`Followers`)} />} 105 114 ListFooterComponent={ 106 115 <ListFooter 107 116 isFetchingNextPage={isFetchingNextPage} ··· 113 122 desktopFixedHeight 114 123 initialNumToRender={initialNumToRender} 115 124 windowSize={11} 125 + sideBorders={false} 116 126 /> 117 127 ) 118 128 }
+18 -8
src/view/com/profile/ProfileFollows.tsx
··· 8 8 import {useProfileFollowsQuery} from '#/state/queries/profile-follows' 9 9 import {useResolveDidQuery} from '#/state/queries/resolve-uri' 10 10 import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' 11 + import {isWeb} from 'platform/detection' 11 12 import {useSession} from 'state/session' 12 - import { 13 - ListFooter, 14 - ListHeaderDesktop, 15 - ListMaybePlaceholder, 16 - } from '#/components/Lists' 13 + import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 17 14 import {List} from '../util/List' 18 15 import {ProfileCardWithFollowBtn} from './ProfileCard' 19 16 20 - function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) { 21 - return <ProfileCardWithFollowBtn key={item.did} profile={item} /> 17 + function renderItem({ 18 + item, 19 + index, 20 + }: { 21 + item: ActorDefs.ProfileViewBasic 22 + index: number 23 + }) { 24 + return ( 25 + <ProfileCardWithFollowBtn 26 + key={item.did} 27 + profile={item} 28 + noBorder={index === 0 && !isWeb} 29 + /> 30 + ) 22 31 } 23 32 24 33 function keyExtractor(item: ActorDefs.ProfileViewBasic) { ··· 88 97 } 89 98 errorMessage={cleanError(resolveError || error)} 90 99 onRetry={isError ? refetch : undefined} 100 + sideBorders={false} 91 101 /> 92 102 ) 93 103 } ··· 101 111 onRefresh={onRefresh} 102 112 onEndReached={onEndReached} 103 113 onEndReachedThreshold={4} 104 - ListHeaderComponent={<ListHeaderDesktop title={_(msg`Following`)} />} 105 114 ListFooterComponent={ 106 115 <ListFooter 107 116 isFetchingNextPage={isFetchingNextPage} ··· 113 122 desktopFixedHeight 114 123 initialNumToRender={initialNumToRender} 115 124 windowSize={11} 125 + sideBorders={false} 116 126 /> 117 127 ) 118 128 }
+1 -1
src/view/com/util/Views.web.tsx
··· 47 47 if (!isMobile) { 48 48 style = addStyle(style, styles.container) 49 49 } 50 - if (sideBorders) { 50 + if (sideBorders && !isMobile) { 51 51 style = addStyle(style, { 52 52 borderLeftWidth: StyleSheet.hairlineWidth, 53 53 borderRightWidth: StyleSheet.hairlineWidth,
+14 -9
src/view/screens/ProfileFollowers.tsx
··· 1 1 import React from 'react' 2 - import {View} from 'react-native' 2 + import {msg} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 3 4 import {useFocusEffect} from '@react-navigation/native' 4 - import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' 5 + 6 + import {useSetMinimalShellMode} from '#/state/shell' 7 + import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' 8 + import {isWeb} from 'platform/detection' 9 + import {CenteredView} from 'view/com/util/Views' 10 + import {atoms as a} from '#/alf' 11 + import {ListHeaderDesktop} from '#/components/Lists' 12 + import {ProfileFollowers as ProfileFollowersComponent} from '../com/profile/ProfileFollowers' 5 13 import {ViewHeader} from '../com/util/ViewHeader' 6 - import {ProfileFollowers as ProfileFollowersComponent} from '../com/profile/ProfileFollowers' 7 - import {useSetMinimalShellMode} from '#/state/shell' 8 - import {useLingui} from '@lingui/react' 9 - import {msg} from '@lingui/macro' 10 14 11 15 type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollowers'> 12 16 export const ProfileFollowersScreen = ({route}: Props) => { ··· 21 25 ) 22 26 23 27 return ( 24 - <View style={{flex: 1}}> 25 - <ViewHeader title={_(msg`Followers`)} /> 28 + <CenteredView style={a.h_full_vh} sideBorders={true}> 29 + <ListHeaderDesktop title={_(msg`Followers`)} /> 30 + <ViewHeader title={_(msg`Followers`)} showBorder={!isWeb} /> 26 31 <ProfileFollowersComponent name={name} /> 27 - </View> 32 + </CenteredView> 28 33 ) 29 34 }
+14 -9
src/view/screens/ProfileFollows.tsx
··· 1 1 import React from 'react' 2 - import {View} from 'react-native' 2 + import {msg} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 3 4 import {useFocusEffect} from '@react-navigation/native' 4 - import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' 5 + 6 + import {useSetMinimalShellMode} from '#/state/shell' 7 + import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' 8 + import {isWeb} from 'platform/detection' 9 + import {CenteredView} from 'view/com/util/Views' 10 + import {atoms as a} from '#/alf' 11 + import {ListHeaderDesktop} from '#/components/Lists' 12 + import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows' 5 13 import {ViewHeader} from '../com/util/ViewHeader' 6 - import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows' 7 - import {useSetMinimalShellMode} from '#/state/shell' 8 - import {useLingui} from '@lingui/react' 9 - import {msg} from '@lingui/macro' 10 14 11 15 type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollows'> 12 16 export const ProfileFollowsScreen = ({route}: Props) => { ··· 21 25 ) 22 26 23 27 return ( 24 - <View style={{flex: 1}}> 25 - <ViewHeader title={_(msg`Following`)} /> 28 + <CenteredView style={a.h_full_vh} sideBorders={true}> 29 + <ListHeaderDesktop title={_(msg`Following`)} /> 30 + <ViewHeader title={_(msg`Following`)} showBorder={!isWeb} /> 26 31 <ProfileFollowsComponent name={name} /> 27 - </View> 32 + </CenteredView> 28 33 ) 29 34 }