Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Fix web home feed sizing and related issues (close #432) (#475)

* Fix web home feed sizing (close #432)

* Fix lint

* Fix positioning of profile not found error

* Fix load latest on mobile

* Fix overflow issues on mobile web (visible in postthread)

* Fix bottom pad on mobile web

* Remove old comment

authored by

Paul Frazee and committed by
GitHub
91fadadb a79dcd3d

+109 -24
+2 -1
src/lib/styles.ts
··· 1 1 import {StyleProp, StyleSheet, TextStyle} from 'react-native' 2 2 import {Theme, TypographyVariant} from './ThemeContext' 3 + import {isMobileWeb} from 'platform/detection' 3 4 4 5 // 1 is lightest, 2 is light, 3 is mid, 4 is dark, 5 is darkest 5 6 export const colors = { ··· 162 163 // dimensions 163 164 w100pct: {width: '100%'}, 164 165 h100pct: {height: '100%'}, 165 - hContentRegion: {height: '100%'}, 166 + hContentRegion: isMobileWeb ? {flex: 1} : {height: '100%'}, 166 167 167 168 // text align 168 169 textLeft: {textAlign: 'left'},
+52 -6
src/view/com/pager/FeedsTabBar.web.tsx
··· 1 1 import React from 'react' 2 + import {Animated, StyleSheet} from 'react-native' 2 3 import {observer} from 'mobx-react-lite' 3 4 import {TabBar} from 'view/com/pager/TabBar' 4 - import {CenteredView} from 'view/com/util/Views' 5 5 import {RenderTabBarFnProps} from 'view/com/pager/Pager' 6 + import {useStores} from 'state/index' 6 7 import {usePalette} from 'lib/hooks/usePalette' 8 + import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' 7 9 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 8 10 import {FeedsTabBar as FeedsTabBarMobile} from './FeedsTabBarMobile' 9 11 10 12 export const FeedsTabBar = observer( 11 - (props: RenderTabBarFnProps & {onPressSelected: () => void}) => { 12 - const pal = usePalette('default') 13 + ( 14 + props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void}, 15 + ) => { 13 16 const {isDesktop} = useWebMediaQueries() 14 - 15 17 if (!isDesktop) { 16 18 return <FeedsTabBarMobile {...props} /> 19 + } else { 20 + return <FeedsTabBarDesktop {...props} /> 17 21 } 22 + }, 23 + ) 18 24 25 + const FeedsTabBarDesktop = observer( 26 + ( 27 + props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void}, 28 + ) => { 29 + const store = useStores() 30 + const pal = usePalette('default') 31 + const interp = useAnimatedValue(0) 32 + 33 + React.useEffect(() => { 34 + Animated.timing(interp, { 35 + toValue: store.shell.minimalShellMode ? 1 : 0, 36 + duration: 100, 37 + useNativeDriver: true, 38 + isInteraction: false, 39 + }).start() 40 + }, [interp, store.shell.minimalShellMode]) 41 + const transform = { 42 + transform: [ 43 + {translateX: '-50%'}, 44 + {translateY: Animated.multiply(interp, -100)}, 45 + ], 46 + } 19 47 return ( 20 - <CenteredView> 48 + // @ts-ignore the type signature for transform wrong here, translateX and translateY need to be in separate objects -prf 49 + <Animated.View style={[pal.view, styles.tabBar, transform]}> 21 50 <TabBar 22 51 {...props} 23 52 items={['Following', "What's hot"]} 24 53 indicatorPosition="bottom" 25 54 indicatorColor={pal.colors.link} 26 55 /> 27 - </CenteredView> 56 + </Animated.View> 28 57 ) 29 58 }, 30 59 ) 60 + 61 + const styles = StyleSheet.create({ 62 + tabBar: { 63 + position: 'absolute', 64 + zIndex: 1, 65 + left: '50%', 66 + width: 640, 67 + top: 0, 68 + flexDirection: 'row', 69 + alignItems: 'center', 70 + paddingHorizontal: 18, 71 + }, 72 + tabBarAvi: { 73 + marginTop: 1, 74 + marginRight: 18, 75 + }, 76 + })
+20 -6
src/view/com/post-thread/PostThread.tsx
··· 21 21 import {ErrorMessage} from '../util/error/ErrorMessage' 22 22 import {Text} from '../util/text/Text' 23 23 import {s} from 'lib/styles' 24 - import {isDesktopWeb} from 'platform/detection' 24 + import {isDesktopWeb, isMobileWeb} from 'platform/detection' 25 25 import {usePalette} from 'lib/hooks/usePalette' 26 26 import {useNavigation} from '@react-navigation/native' 27 27 import {NavigationProp} from 'lib/routes/types' 28 28 29 29 const REPLY_PROMPT = {_reactKey: '__reply__', _isHighlightedPost: false} 30 - const BOTTOM_BORDER = { 31 - _reactKey: '__bottom_border__', 30 + const BOTTOM_COMPONENT = { 31 + _reactKey: '__bottom_component__', 32 32 _isHighlightedPost: false, 33 33 } 34 34 type YieldedItem = PostThreadItemModel | typeof REPLY_PROMPT ··· 48 48 const navigation = useNavigation<NavigationProp>() 49 49 const posts = React.useMemo(() => { 50 50 if (view.thread) { 51 - return Array.from(flattenThread(view.thread)).concat([BOTTOM_BORDER]) 51 + return Array.from(flattenThread(view.thread)).concat([BOTTOM_COMPONENT]) 52 52 } 53 53 return [] 54 54 }, [view.thread]) ··· 103 103 ({item}: {item: YieldedItem}) => { 104 104 if (item === REPLY_PROMPT) { 105 105 return <ComposePrompt onPressCompose={onPressReply} /> 106 - } else if (item === BOTTOM_BORDER) { 106 + } else if (item === BOTTOM_COMPONENT) { 107 107 // HACK 108 108 // due to some complexities with how flatlist works, this is the easiest way 109 109 // I could find to get a border positioned directly under the last item 110 + // - 111 + // addendum -- it's also the best way to get mobile web to add padding 112 + // at the bottom of the thread since paddingbottom is ignored. yikes. 110 113 // -prf 111 - return <View style={[styles.bottomBorder, pal.border]} /> 114 + return ( 115 + <View 116 + style={[ 117 + styles.bottomBorder, 118 + pal.border, 119 + isMobileWeb && styles.bottomSpacer, 120 + ]} 121 + /> 122 + ) 112 123 } else if (item instanceof PostThreadItemModel) { 113 124 return <PostThreadItem item={item} onPostReply={onRefresh} /> 114 125 } ··· 223 234 }, 224 235 bottomBorder: { 225 236 borderBottomWidth: 1, 237 + }, 238 + bottomSpacer: { 239 + height: 200, 226 240 }, 227 241 })
+1 -1
src/view/com/util/LoadLatestBtn.tsx src/view/com/util/load-latest/LoadLatestBtnMobile.tsx
··· 3 3 import {observer} from 'mobx-react-lite' 4 4 import LinearGradient from 'react-native-linear-gradient' 5 5 import {useSafeAreaInsets} from 'react-native-safe-area-context' 6 - import {Text} from './text/Text' 6 + import {Text} from '../text/Text' 7 7 import {colors, gradients} from 'lib/styles' 8 8 import {clamp} from 'lodash' 9 9 import {useStores} from 'state/index'
+6 -1
src/view/com/util/LoadLatestBtn.web.tsx src/view/com/util/load-latest/LoadLatestBtn.web.tsx
··· 1 1 import React from 'react' 2 2 import {StyleSheet, TouchableOpacity} from 'react-native' 3 - import {Text} from './text/Text' 3 + import {Text} from '../text/Text' 4 4 import {usePalette} from 'lib/hooks/usePalette' 5 5 import {UpIcon} from 'lib/icons' 6 + import {LoadLatestBtn as LoadLatestBtnMobile} from './LoadLatestBtnMobile' 7 + import {isMobileWeb} from 'platform/detection' 6 8 7 9 const HITSLOP = {left: 20, top: 20, right: 20, bottom: 20} 8 10 ··· 14 16 label: string 15 17 }) => { 16 18 const pal = usePalette('default') 19 + if (isMobileWeb) { 20 + return <LoadLatestBtnMobile onPress={onPress} label={label} /> 21 + } 17 22 return ( 18 23 <TouchableOpacity 19 24 style={[pal.view, pal.borderDark, styles.loadLatest]}
+18 -1
src/view/com/util/Views.web.tsx
··· 35 35 export const FlatList = React.forwardRef(function <ItemT>( 36 36 { 37 37 contentContainerStyle, 38 + style, 39 + contentOffset, 38 40 ...props 39 41 }: React.PropsWithChildren<FlatListProps<ItemT>>, 40 42 ref: React.Ref<RNFlatList>, ··· 43 45 contentContainerStyle, 44 46 styles.containerScroll, 45 47 ) 48 + if (contentOffset && contentOffset?.y !== 0) { 49 + // NOTE 50 + // we use paddingTop & contentOffset to space around the floating header 51 + // but reactnative web puts the paddingTop on the wrong element (style instead of the contentContainer) 52 + // so we manually correct it here 53 + // -prf 54 + style = addStyle(style, { 55 + paddingTop: 0, 56 + }) 57 + contentContainerStyle = addStyle(contentContainerStyle, { 58 + paddingTop: Math.abs(contentOffset.y), 59 + }) 60 + } 46 61 return ( 47 62 <RNFlatList 48 - contentContainerStyle={contentContainerStyle} 49 63 ref={ref} 64 + contentContainerStyle={contentContainerStyle} 65 + style={style} 66 + contentOffset={contentOffset} 50 67 {...props} 51 68 /> 52 69 )
+3 -2
src/view/com/util/error/ErrorScreen.tsx
··· 8 8 import {colors} from 'lib/styles' 9 9 import {useTheme} from 'lib/ThemeContext' 10 10 import {usePalette} from 'lib/hooks/usePalette' 11 + import {CenteredView} from '../Views' 11 12 12 13 export function ErrorScreen({ 13 14 title, ··· 25 26 const theme = useTheme() 26 27 const pal = usePalette('error') 27 28 return ( 28 - <View testID={testID} style={[styles.outer, pal.view]}> 29 + <CenteredView testID={testID} style={[styles.outer, pal.view]}> 29 30 <View style={styles.errorIconContainer}> 30 31 <View 31 32 style={[ ··· 72 73 </TouchableOpacity> 73 74 </View> 74 75 )} 75 - </View> 76 + </CenteredView> 76 77 ) 77 78 } 78 79
+1
src/view/com/util/load-latest/LoadLatestBtn.tsx
··· 1 + export * from './LoadLatestBtnMobile'
+5 -5
src/view/screens/Home.tsx
··· 1 1 import React from 'react' 2 - import {FlatList, View, Platform} from 'react-native' 2 + import {FlatList, View} from 'react-native' 3 3 import {useFocusEffect, useIsFocused} from '@react-navigation/native' 4 4 import {observer} from 'mobx-react-lite' 5 5 import useAppState from 'react-native-appstate-hook' ··· 8 8 import {withAuthRequired} from 'view/com/auth/withAuthRequired' 9 9 import {Feed} from '../com/posts/Feed' 10 10 import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState' 11 - import {LoadLatestBtn} from '../com/util/LoadLatestBtn' 11 + import {LoadLatestBtn} from '../com/util/load-latest/LoadLatestBtn' 12 12 import {FeedsTabBar} from '../com/pager/FeedsTabBar' 13 13 import {Pager, RenderTabBarFnProps} from 'view/com/pager/Pager' 14 14 import {FAB} from '../com/util/fab/FAB' ··· 17 17 import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' 18 18 import {useAnalytics} from 'lib/analytics' 19 19 import {ComposeIcon2} from 'lib/icons' 20 - import {isDesktopWeb, isMobileWeb} from 'platform/detection' 20 + import {isDesktopWeb} from 'platform/detection' 21 21 22 - const HEADER_OFFSET = isDesktopWeb ? 0 : isMobileWeb ? 20 : 40 22 + const HEADER_OFFSET = isDesktopWeb ? 50 : 40 23 23 24 24 type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'> 25 25 export const HomeScreen = withAuthRequired((_opts: Props) => { ··· 191 191 onPressTryAgain={onPressTryAgain} 192 192 onScroll={onMainScroll} 193 193 renderEmptyState={renderEmptyState} 194 - headerOffset={Platform.OS === 'web' ? 0 : HEADER_OFFSET} // only offset on mobile 194 + headerOffset={HEADER_OFFSET} 195 195 /> 196 196 {feed.hasNewLatest && !feed.isRefreshing && ( 197 197 <LoadLatestBtn onPress={onPressLoadLatest} label="posts" />
+1 -1
src/view/screens/Notifications.tsx
··· 10 10 import {ViewHeader} from '../com/util/ViewHeader' 11 11 import {Feed} from '../com/notifications/Feed' 12 12 import {InvitedUsers} from '../com/notifications/InvitedUsers' 13 - import {LoadLatestBtn} from 'view/com/util/LoadLatestBtn' 13 + import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' 14 14 import {useStores} from 'state/index' 15 15 import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' 16 16 import {s} from 'lib/styles'