Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Change many border widths from `1` to `hairlineWidth` (#4294)

* feed items

* update some more

* moar

* profile card

* composer and notifications

* settings screen

* remove border from first item in feeds

* remove border from first item in feeds

* more removal of top border

* fix flatlist rendering

* oops

* scroll to top fab

* a.border

* centeredview/list

* placeholder

* web sidebar

* search posts

* feeds list

* user lists

* list header

* account list width 1

* hide top border feedgens

* same for lists

* fix tab bar web desktop

* wait...

* show the border on desktop web

* fix lists

* fix lists

* round

authored by

Hailey and committed by
GitHub
89c9fd3b 8569e2e3

+226 -143
+7 -6
src/alf/atoms.ts
··· 1 - import {Platform} from 'react-native' 1 + import {Platform, StyleSheet} from 'react-native' 2 2 3 3 import * as tokens from '#/alf/tokens' 4 4 import {native, web} from '#/alf/util/platform' 5 + import hairlineWidth = StyleSheet.hairlineWidth 5 6 6 7 export const atoms = { 7 8 /* ··· 277 278 borderWidth: 0, 278 279 }, 279 280 border: { 280 - borderWidth: 1, 281 + borderWidth: hairlineWidth, 281 282 }, 282 283 border_t: { 283 - borderTopWidth: 1, 284 + borderTopWidth: hairlineWidth, 284 285 }, 285 286 border_b: { 286 - borderBottomWidth: 1, 287 + borderBottomWidth: hairlineWidth, 287 288 }, 288 289 border_l: { 289 - borderLeftWidth: 1, 290 + borderLeftWidth: hairlineWidth, 290 291 }, 291 292 border_r: { 292 - borderRightWidth: 1, 293 + borderRightWidth: hairlineWidth, 293 294 }, 294 295 295 296 /*
+2 -2
src/components/AccountList.tsx
··· 37 37 style={[ 38 38 a.rounded_md, 39 39 a.overflow_hidden, 40 - a.border, 40 + {borderWidth: 1}, 41 41 t.atoms.border_contrast_low, 42 42 ]}> 43 43 {accounts.map(account => ( ··· 48 48 isCurrentAccount={account.did === currentAccount?.did} 49 49 isPendingAccount={account.did === pendingDid} 50 50 /> 51 - <View style={[a.border_b, t.atoms.border_contrast_low]} /> 51 + <View style={[{borderBottomWidth: 1}, t.atoms.border_contrast_low]} /> 52 52 </React.Fragment> 53 53 ))} 54 54 <Button
+3 -2
src/view/com/composer/Composer.tsx
··· 90 90 import {TextInput, TextInputRef} from './text-input/TextInput' 91 91 import {ThreadgateBtn} from './threadgate/ThreadgateBtn' 92 92 import {useExternalLinkFetch} from './useExternalLinkFetch' 93 + import hairlineWidth = StyleSheet.hairlineWidth 93 94 94 95 type CancelRef = { 95 96 onPressCancel: () => void ··· 657 658 marginBottom: 6, 658 659 }, 659 660 errorIcon: { 660 - borderWidth: 1, 661 + borderWidth: hairlineWidth, 661 662 borderColor: colors.red4, 662 663 color: colors.red4, 663 664 borderRadius: 30, ··· 692 693 paddingLeft: 15, 693 694 paddingRight: 20, 694 695 alignItems: 'center', 695 - borderTopWidth: 1, 696 + borderTopWidth: hairlineWidth, 696 697 }, 697 698 })
+9 -7
src/view/com/composer/Prompt.tsx
··· 1 1 import React from 'react' 2 2 import {StyleSheet, TouchableOpacity} from 'react-native' 3 - import {UserAvatar} from '../util/UserAvatar' 4 - import {Text} from '../util/text/Text' 5 - import {usePalette} from 'lib/hooks/usePalette' 6 - import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 7 - import {Trans, msg} from '@lingui/macro' 3 + import {msg, Trans} from '@lingui/macro' 8 4 import {useLingui} from '@lingui/react' 9 - import {useSession} from '#/state/session' 5 + 10 6 import {useProfileQuery} from '#/state/queries/profile' 7 + import {useSession} from '#/state/session' 8 + import {usePalette} from 'lib/hooks/usePalette' 9 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 10 + import {Text} from '../util/text/Text' 11 + import {UserAvatar} from '../util/UserAvatar' 12 + import hairlineWidth = StyleSheet.hairlineWidth 11 13 12 14 export function ComposePrompt({onPressCompose}: {onPressCompose: () => void}) { 13 15 const {currentAccount} = useSession() ··· 47 49 paddingBottom: 10, 48 50 flexDirection: 'row', 49 51 alignItems: 'center', 50 - borderTopWidth: 1, 52 + borderTopWidth: hairlineWidth, 51 53 }, 52 54 labelMobile: { 53 55 paddingLeft: 12,
+13 -3
src/view/com/feeds/FeedSourceCard.tsx
··· 25 25 import {RichText} from '#/components/RichText' 26 26 import {Text} from '../util/text/Text' 27 27 import {UserAvatar} from '../util/UserAvatar' 28 + import hairlineWidth = StyleSheet.hairlineWidth 28 29 29 30 export function FeedSourceCard({ 30 31 feedUri, ··· 34 35 showLikes = false, 35 36 pinOnSave = false, 36 37 showMinimalPlaceholder, 38 + hideTopBorder, 37 39 }: { 38 40 feedUri: string 39 41 style?: StyleProp<ViewStyle> ··· 42 44 showLikes?: boolean 43 45 pinOnSave?: boolean 44 46 showMinimalPlaceholder?: boolean 47 + hideTopBorder?: boolean 45 48 }) { 46 49 const {data: preferences} = usePreferencesQuery() 47 50 const {data: feed} = useFeedSourceInfoQuery({uri: feedUri}) ··· 57 60 showLikes={showLikes} 58 61 pinOnSave={pinOnSave} 59 62 showMinimalPlaceholder={showMinimalPlaceholder} 63 + hideTopBorder={hideTopBorder} 60 64 /> 61 65 ) 62 66 } ··· 71 75 showLikes = false, 72 76 pinOnSave = false, 73 77 showMinimalPlaceholder, 78 + hideTopBorder, 74 79 }: { 75 80 feedUri: string 76 81 feed?: FeedSourceInfo ··· 81 86 showLikes?: boolean 82 87 pinOnSave?: boolean 83 88 showMinimalPlaceholder?: boolean 89 + hideTopBorder?: boolean 84 90 }) { 85 91 const t = useTheme() 86 92 const pal = usePalette('default') ··· 149 155 style={[ 150 156 pal.border, 151 157 { 152 - borderTopWidth: showMinimalPlaceholder ? 0 : 1, 158 + borderTopWidth: showMinimalPlaceholder || hideTopBorder ? 0 : 1, 153 159 flexDirection: 'row', 154 160 alignItems: 'center', 155 161 flex: 1, ··· 191 197 <Pressable 192 198 testID={`feed-${feed.displayName}`} 193 199 accessibilityRole="button" 194 - style={[styles.container, pal.border, style]} 200 + style={[ 201 + styles.container, 202 + pal.border, 203 + style, 204 + {borderTopWidth: hideTopBorder ? 0 : hairlineWidth}, 205 + ]} 195 206 onPress={() => { 196 207 if (feed.type === 'feed') { 197 208 navigation.push('ProfileFeed', { ··· 295 306 paddingVertical: 20, 296 307 flexDirection: 'column', 297 308 flex: 1, 298 - borderTopWidth: 1, 299 309 gap: 14, 300 310 }, 301 311 headerContainer: {
+3 -1
src/view/com/feeds/ProfileFeedgens.tsx
··· 1 1 import React from 'react' 2 2 import { 3 3 findNodeHandle, 4 + ListRenderItemInfo, 4 5 StyleProp, 5 6 StyleSheet, 6 7 View, ··· 134 135 // = 135 136 136 137 const renderItemInner = React.useCallback( 137 - ({item}: {item: any}) => { 138 + ({item, index}: ListRenderItemInfo<any>) => { 138 139 if (item === EMPTY) { 139 140 return ( 140 141 <View ··· 169 170 preferences={preferences} 170 171 style={styles.item} 171 172 showLikes 173 + hideTopBorder={index === 0} 172 174 /> 173 175 ) 174 176 }
+12 -10
src/view/com/lists/ListCard.tsx
··· 1 1 import React from 'react' 2 2 import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' 3 - import {AtUri, AppBskyGraphDefs, RichText} from '@atproto/api' 4 - import {Link} from '../util/Link' 5 - import {Text} from '../util/text/Text' 6 - import {RichText as RichTextCom} from '#/components/RichText' 7 - import {UserAvatar} from '../util/UserAvatar' 8 - import {s} from 'lib/styles' 9 - import {usePalette} from 'lib/hooks/usePalette' 3 + import {AppBskyGraphDefs, AtUri, RichText} from '@atproto/api' 4 + import {Trans} from '@lingui/macro' 5 + 10 6 import {useSession} from '#/state/session' 7 + import {usePalette} from 'lib/hooks/usePalette' 8 + import {makeProfileLink} from 'lib/routes/links' 11 9 import {sanitizeDisplayName} from 'lib/strings/display-names' 12 10 import {sanitizeHandle} from 'lib/strings/handles' 13 - import {makeProfileLink} from 'lib/routes/links' 14 - import {Trans} from '@lingui/macro' 11 + import {s} from 'lib/styles' 15 12 import {atoms as a} from '#/alf' 13 + import {RichText as RichTextCom} from '#/components/RichText' 14 + import {Link} from '../util/Link' 15 + import {Text} from '../util/text/Text' 16 + import {UserAvatar} from '../util/UserAvatar' 17 + import hairlineWidth = StyleSheet.hairlineWidth 16 18 17 19 export const ListCard = ({ 18 20 testID, ··· 132 134 133 135 const styles = StyleSheet.create({ 134 136 outer: { 135 - borderTopWidth: 1, 137 + borderTopWidth: hairlineWidth, 136 138 paddingHorizontal: 6, 137 139 }, 138 140 outerNoBorder: {
+10 -8
src/view/com/lists/MyLists.tsx
··· 9 9 ViewStyle, 10 10 } from 'react-native' 11 11 import {AppBskyGraphDefs as GraphDefs} from '@atproto/api' 12 - import {ListCard} from './ListCard' 12 + import {Trans} from '@lingui/macro' 13 + 14 + import {cleanError} from '#/lib/strings/errors' 15 + import {logger} from '#/logger' 13 16 import {MyListsFilter, useMyListsQuery} from '#/state/queries/my-lists' 14 - import {ErrorMessage} from '../util/error/ErrorMessage' 15 - import {Text} from '../util/text/Text' 16 17 import {useAnalytics} from 'lib/analytics/analytics' 17 18 import {usePalette} from 'lib/hooks/usePalette' 18 - import {List} from '../util/List' 19 19 import {s} from 'lib/styles' 20 - import {logger} from '#/logger' 21 - import {Trans} from '@lingui/macro' 22 - import {cleanError} from '#/lib/strings/errors' 20 + import {ErrorMessage} from '../util/error/ErrorMessage' 21 + import {List} from '../util/List' 22 + import {Text} from '../util/text/Text' 23 + import {ListCard} from './ListCard' 24 + import hairlineWidth = StyleSheet.hairlineWidth 23 25 24 26 const LOADING = {_reactKey: '__loading__'} 25 27 const EMPTY = {_reactKey: '__empty__'} ··· 84 86 <View 85 87 key={item._reactKey} 86 88 testID="listsEmpty" 87 - style={[{padding: 18, borderTopWidth: 1}, pal.border]}> 89 + style={[{padding: 18, borderTopWidth: hairlineWidth}, pal.border]}> 88 90 <Text style={pal.textLight}> 89 91 <Trans>You have no lists.</Trans> 90 92 </Text>
+4 -4
src/view/com/lists/ProfileLists.tsx
··· 1 1 import React from 'react' 2 2 import { 3 3 findNodeHandle, 4 + ListRenderItemInfo, 4 5 StyleProp, 5 6 StyleSheet, 6 7 View, ··· 138 139 // = 139 140 140 141 const renderItemInner = React.useCallback( 141 - ({item}: {item: any}) => { 142 + ({item, index}: ListRenderItemInfo<any>) => { 142 143 if (item === EMPTY) { 143 144 return ( 144 - <View 145 - testID="listsEmpty" 146 - style={[{padding: 18, borderTopWidth: 1}, pal.border]}> 145 + <View testID="listsEmpty" style={[{padding: 18}, pal.border]}> 147 146 <Text style={pal.textLight}> 148 147 <Trans>You have no lists.</Trans> 149 148 </Text> ··· 173 172 list={item} 174 173 testID={`list-${item.name}`} 175 174 style={styles.item} 175 + noBorder={index === 0} 176 176 /> 177 177 ) 178 178 },
+24 -9
src/view/com/notifications/Feed.tsx
··· 1 1 import React from 'react' 2 - import {ActivityIndicator, StyleSheet, View} from 'react-native' 2 + import { 3 + ActivityIndicator, 4 + ListRenderItemInfo, 5 + StyleSheet, 6 + View, 7 + } from 'react-native' 3 8 import {msg} from '@lingui/macro' 4 9 import {useLingui} from '@lingui/react' 5 10 ··· 17 22 import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' 18 23 import {CenteredView} from '../util/Views' 19 24 import {FeedItem} from './FeedItem' 25 + import hairlineWidth = StyleSheet.hairlineWidth 26 + import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' 27 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 20 28 21 29 const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} 22 30 const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'} ··· 33 41 onScrolledDownChange: (isScrolledDown: boolean) => void 34 42 ListHeaderComponent?: () => JSX.Element 35 43 }) { 44 + const initialNumToRender = useInitialNumToRender() 45 + 36 46 const [isPTRing, setIsPTRing] = React.useState(false) 37 47 const pal = usePalette('default') 48 + const {isTabletOrMobile} = useWebMediaQueries() 38 49 39 50 const {_} = useLingui() 40 51 const moderationOpts = useModerationOpts() ··· 97 108 fetchNextPage() 98 109 }, [fetchNextPage]) 99 110 100 - // TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf 101 - // VirtualizedList: You have a large list that is slow to update - make sure your 102 - // renderItem function renders components that follow React performance best practices 103 - // like PureComponent, shouldComponentUpdate, etc 104 111 const renderItem = React.useCallback( 105 - ({item}: {item: any}) => { 112 + ({item, index}: ListRenderItemInfo<any>) => { 106 113 if (item === EMPTY_FEED_ITEM) { 107 114 return ( 108 115 <EmptyState ··· 122 129 ) 123 130 } else if (item === LOADING_ITEM) { 124 131 return ( 125 - <View style={[pal.border, {borderTopWidth: 1}]}> 132 + <View style={[pal.border, {borderTopWidth: hairlineWidth}]}> 126 133 <NotificationFeedLoadingPlaceholder /> 127 134 </View> 128 135 ) 129 136 } 130 - return <FeedItem item={item} moderationOpts={moderationOpts!} /> 137 + return ( 138 + <FeedItem 139 + item={item} 140 + moderationOpts={moderationOpts!} 141 + hideTopBorder={index === 0 && isTabletOrMobile} 142 + /> 143 + ) 131 144 }, 132 - [onPressRetryLoadMore, moderationOpts, _, pal.border], 145 + [moderationOpts, isTabletOrMobile, _, onPressRetryLoadMore, pal.border], 133 146 ) 134 147 135 148 const FeedFooter = React.useCallback( ··· 170 183 contentContainerStyle={s.contentContainer} 171 184 // @ts-ignore our .web version only -prf 172 185 desktopFixedHeight 186 + initialNumToRender={initialNumToRender} 187 + windowSize={11} 173 188 /> 174 189 </View> 175 190 )
+4 -1
src/view/com/notifications/FeedItem.tsx
··· 47 47 import {Text} from '../util/text/Text' 48 48 import {TimeElapsed} from '../util/TimeElapsed' 49 49 import {PreviewableUserAvatar, UserAvatar} from '../util/UserAvatar' 50 + import hairlineWidth = StyleSheet.hairlineWidth 50 51 51 52 const MAX_AUTHORS = 5 52 53 ··· 61 62 let FeedItem = ({ 62 63 item, 63 64 moderationOpts, 65 + hideTopBorder, 64 66 }: { 65 67 item: FeedNotification 66 68 moderationOpts: ModerationOpts 69 + hideTopBorder?: boolean 67 70 }): React.ReactNode => { 68 71 const queryClient = useQueryClient() 69 72 const pal = usePalette('default') ··· 188 191 backgroundColor: pal.colors.unreadNotifBg, 189 192 borderColor: pal.colors.unreadNotifBorder, 190 193 }, 194 + {borderTopWidth: hideTopBorder ? 0 : hairlineWidth}, 191 195 ]} 192 196 href={itemHref} 193 197 noFeedback ··· 480 484 outer: { 481 485 padding: 10, 482 486 paddingRight: 15, 483 - borderTopWidth: 1, 484 487 flexDirection: 'row', 485 488 }, 486 489 layoutIcon: {
+2 -2
src/view/com/pager/PagerWithHeader.tsx
··· 67 67 const height = evt.nativeEvent.layout.height 68 68 if (height > 0) { 69 69 // The rounding is necessary to prevent jumps on iOS 70 - setTabBarHeight(Math.round(height)) 70 + setTabBarHeight(Math.round(height * 2) / 2) 71 71 } 72 72 }) 73 73 const onHeaderOnlyLayout = useNonReactiveCallback((height: number) => { 74 74 if (height > 0) { 75 75 // The rounding is necessary to prevent jumps on iOS 76 - setHeaderOnlyHeight(Math.round(height)) 76 + setHeaderOnlyHeight(Math.round(height * 2) / 2) 77 77 } 78 78 }) 79 79
+2 -1
src/view/com/pager/TabBar.tsx
··· 7 7 import {PressableWithHover} from '../util/PressableWithHover' 8 8 import {Text} from '../util/text/Text' 9 9 import {DraggableScrollView} from './DraggableScrollView' 10 + import hairlineWidth = StyleSheet.hairlineWidth 10 11 11 12 export interface TabBarProps { 12 13 testID?: string ··· 207 208 left: 0, 208 209 right: 0, 209 210 bottom: -1, 210 - borderBottomWidth: 1, 211 + borderBottomWidth: hairlineWidth, 211 212 }, 212 213 })
+6 -5
src/view/com/post-thread/PostThreadItem.tsx
··· 50 50 import {PostMeta} from '../util/PostMeta' 51 51 import {Text} from '../util/text/Text' 52 52 import {PreviewableUserAvatar} from '../util/UserAvatar' 53 + import hairlineWidth = StyleSheet.hairlineWidth 53 54 54 55 export function PostThreadItem({ 55 56 post, ··· 623 624 { 624 625 flexDirection: 'row', 625 626 paddingHorizontal: isMobile ? 10 : 6, 626 - borderTopWidth: depth === 1 ? 1 : 0, 627 + borderTopWidth: depth === 1 ? hairlineWidth : 0, 627 628 }, 628 629 ]}> 629 630 {Array.from(Array(depth - 1)).map((_, n: number) => ( ··· 704 705 705 706 const styles = StyleSheet.create({ 706 707 outer: { 707 - borderTopWidth: 1, 708 + borderTopWidth: hairlineWidth, 708 709 paddingLeft: 8, 709 710 }, 710 711 outerHighlighted: { ··· 714 715 paddingRight: 8, 715 716 }, 716 717 outerHighlightedRoot: { 717 - borderTopWidth: 1, 718 + borderTopWidth: hairlineWidth, 718 719 paddingTop: 16, 719 720 }, 720 721 noTopBorder: { ··· 766 767 expandedInfo: { 767 768 flexDirection: 'row', 768 769 padding: 10, 769 - borderTopWidth: 1, 770 - borderBottomWidth: 1, 770 + borderTopWidth: hairlineWidth, 771 + borderBottomWidth: hairlineWidth, 771 772 marginTop: 5, 772 773 marginBottom: 10, 773 774 },
+2 -1
src/view/com/post/Post.tsx
··· 36 36 import {Text} from '../util/text/Text' 37 37 import {PreviewableUserAvatar} from '../util/UserAvatar' 38 38 import {UserInfoText} from '../util/UserInfoText' 39 + import hairlineWidth = StyleSheet.hairlineWidth 39 40 40 41 export function Post({ 41 42 post, ··· 242 243 paddingRight: 15, 243 244 paddingBottom: 5, 244 245 paddingLeft: 10, 245 - borderTopWidth: 1, 246 + borderTopWidth: hairlineWidth, 246 247 // @ts-ignore web only -prf 247 248 cursor: 'pointer', 248 249 },
+15 -6
src/view/com/posts/Feed.tsx
··· 3 3 ActivityIndicator, 4 4 AppState, 5 5 Dimensions, 6 + ListRenderItemInfo, 6 7 StyleProp, 7 8 StyleSheet, 8 9 View, ··· 31 32 import {useSession} from '#/state/session' 32 33 import {useAnalytics} from 'lib/analytics/analytics' 33 34 import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' 35 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 34 36 import {useTheme} from 'lib/ThemeContext' 35 37 import {List, ListRef} from '../util/List' 36 38 import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' ··· 100 102 const checkForNewRef = React.useRef<(() => void) | null>(null) 101 103 const lastFetchRef = React.useRef<number>(Date.now()) 102 104 const [feedType, feedUri] = feed.split('|') 105 + const {isTabletOrMobile} = useWebMediaQueries() 103 106 104 107 const opts = React.useMemo( 105 108 () => ({enabled, ignoreFilterFor}), ··· 279 282 // = 280 283 281 284 const renderItem = React.useCallback( 282 - ({item}: {item: any}) => { 285 + ({item, index}: ListRenderItemInfo<any>) => { 283 286 if (item === EMPTY_FEED_ITEM) { 284 287 return renderEmptyState() 285 288 } else if (item === ERROR_ITEM) { ··· 311 314 // -prf 312 315 return <DiscoverFallbackHeader /> 313 316 } 314 - return <FeedSlice slice={item} /> 317 + return ( 318 + <FeedSlice 319 + slice={item} 320 + hideTopBorder={index === 0 && isTabletOrMobile} 321 + /> 322 + ) 315 323 }, 316 324 [ 325 + isTabletOrMobile, 326 + renderEmptyState, 317 327 feed, 318 - feedUri, 319 328 error, 320 329 onPressTryAgain, 321 - onPressRetryLoadMore, 322 - renderEmptyState, 323 - _, 324 330 savedFeedConfig, 331 + _, 332 + onPressRetryLoadMore, 333 + feedUri, 325 334 ], 326 335 ) 327 336
+6 -5
src/view/com/posts/FeedItem.tsx
··· 42 42 import {Text} from '../util/text/Text' 43 43 import {PreviewableUserAvatar} from '../util/UserAvatar' 44 44 import {AviFollowButton} from './AviFollowButton' 45 + import hairlineWidth = StyleSheet.hairlineWidth 45 46 46 47 interface FeedItemProps { 47 48 record: AppBskyFeedPost.Record ··· 53 54 isThreadLastChild?: boolean 54 55 isThreadParent?: boolean 55 56 feedContext: string | undefined 57 + hideTopBorder?: boolean 56 58 } 57 59 58 60 export function FeedItem({ ··· 66 68 isThreadChild, 67 69 isThreadLastChild, 68 70 isThreadParent, 71 + hideTopBorder, 69 72 }: FeedItemProps & {post: AppBskyFeedDefs.PostView}): React.ReactNode { 70 73 const postShadowed = usePostShadow(post) 71 74 const richText = useMemo( ··· 95 98 isThreadChild={isThreadChild} 96 99 isThreadLastChild={isThreadLastChild} 97 100 isThreadParent={isThreadParent} 101 + hideTopBorder={hideTopBorder} 98 102 /> 99 103 ) 100 104 } ··· 113 117 isThreadChild, 114 118 isThreadLastChild, 115 119 isThreadParent, 120 + hideTopBorder, 116 121 }: FeedItemProps & { 117 122 richText: RichTextAPI 118 123 post: Shadow<AppBskyFeedDefs.PostView> ··· 186 191 isThreadLastChild || (!isThreadChild && !isThreadParent) 187 192 ? 8 188 193 : undefined, 194 + borderTopWidth: hideTopBorder || isThreadChild ? 0 : hairlineWidth, 189 195 }, 190 - isThreadChild ? styles.outerSmallTop : undefined, 191 196 ] 192 197 193 198 return ( ··· 445 450 446 451 const styles = StyleSheet.create({ 447 452 outer: { 448 - borderTopWidth: 1, 449 453 paddingLeft: 10, 450 454 paddingRight: 15, 451 455 // @ts-ignore web only -prf 452 456 cursor: 'pointer', 453 457 overflow: 'hidden', 454 - }, 455 - outerSmallTop: { 456 - borderTopWidth: 0, 457 458 }, 458 459 replyLine: { 459 460 width: 2,
+9 -1
src/view/com/posts/FeedSlice.tsx
··· 11 11 import {Text} from '../util/text/Text' 12 12 import {FeedItem} from './FeedItem' 13 13 14 - let FeedSlice = ({slice}: {slice: FeedPostSlice}): React.ReactNode => { 14 + let FeedSlice = ({ 15 + slice, 16 + hideTopBorder, 17 + }: { 18 + slice: FeedPostSlice 19 + hideTopBorder?: boolean 20 + }): React.ReactNode => { 15 21 if (slice.isThread && slice.items.length > 3) { 16 22 const last = slice.items.length - 1 17 23 return ( ··· 27 33 moderation={slice.items[0].moderation} 28 34 isThreadParent={isThreadParentAt(slice.items, 0)} 29 35 isThreadChild={isThreadChildAt(slice.items, 0)} 36 + hideTopBorder={hideTopBorder} 30 37 /> 31 38 <FeedItem 32 39 key={slice.items[1]._reactKey} ··· 75 82 isThreadLastChild={ 76 83 isThreadChildAt(slice.items, i) && slice.items.length === i + 1 77 84 } 85 + hideTopBorder={hideTopBorder && i === 0} 78 86 /> 79 87 ))} 80 88 </>
+2 -1
src/view/com/profile/ProfileCard.tsx
··· 25 25 import {Text} from '../util/text/Text' 26 26 import {PreviewableUserAvatar} from '../util/UserAvatar' 27 27 import {FollowButton} from './FollowButton' 28 + import hairlineWidth = StyleSheet.hairlineWidth 28 29 29 30 export function ProfileCard({ 30 31 testID, ··· 280 281 281 282 const styles = StyleSheet.create({ 282 283 outer: { 283 - borderTopWidth: 1, 284 + borderTopWidth: hairlineWidth, 284 285 paddingHorizontal: 6, 285 286 paddingVertical: 4, 286 287 },
+15 -13
src/view/com/profile/ProfileSubpageHeader.tsx
··· 1 1 import React from 'react' 2 2 import {Pressable, StyleSheet, View} from 'react-native' 3 3 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 4 + import {msg, Trans} from '@lingui/macro' 5 + import {useLingui} from '@lingui/react' 4 6 import {useNavigation} from '@react-navigation/native' 7 + 8 + import {emitSoftReset} from '#/state/events' 9 + import {ImagesLightbox, useLightboxControls} from '#/state/lightbox' 10 + import {useSetDrawerOpen} from '#/state/shell' 11 + import {BACK_HITSLOP} from 'lib/constants' 5 12 import {usePalette} from 'lib/hooks/usePalette' 6 13 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 7 - import {Text} from '../util/text/Text' 8 - import {TextLink} from '../util/Link' 9 - import {UserAvatar, UserAvatarType} from '../util/UserAvatar' 10 - import {LoadingPlaceholder} from '../util/LoadingPlaceholder' 11 - import {CenteredView} from '../util/Views' 12 - import {sanitizeHandle} from 'lib/strings/handles' 13 14 import {makeProfileLink} from 'lib/routes/links' 14 15 import {NavigationProp} from 'lib/routes/types' 15 - import {BACK_HITSLOP} from 'lib/constants' 16 + import {sanitizeHandle} from 'lib/strings/handles' 16 17 import {isNative} from 'platform/detection' 17 - import {useLightboxControls, ImagesLightbox} from '#/state/lightbox' 18 - import {useLingui} from '@lingui/react' 19 - import {Trans, msg} from '@lingui/macro' 20 - import {useSetDrawerOpen} from '#/state/shell' 21 - import {emitSoftReset} from '#/state/events' 18 + import {TextLink} from '../util/Link' 19 + import {LoadingPlaceholder} from '../util/LoadingPlaceholder' 20 + import {Text} from '../util/text/Text' 21 + import {UserAvatar, UserAvatarType} from '../util/UserAvatar' 22 + import {CenteredView} from '../util/Views' 23 + import hairlineWidth = StyleSheet.hairlineWidth 22 24 23 25 export function ProfileSubpageHeader({ 24 26 isLoading, ··· 79 81 { 80 82 flexDirection: 'row', 81 83 alignItems: 'center', 82 - borderBottomWidth: 1, 84 + borderBottomWidth: hairlineWidth, 83 85 paddingTop: isNative ? 0 : 8, 84 86 paddingBottom: 8, 85 87 paddingHorizontal: isMobile ? 12 : 14,
+2 -1
src/view/com/util/LoadingPlaceholder.tsx
··· 15 15 import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '#/components/icons/Bubble' 16 16 import {Heart2_Stroke2_Corner0_Rounded as HeartIconOutline} from '#/components/icons/Heart2' 17 17 import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost' 18 + import hairlineWidth = StyleSheet.hairlineWidth 18 19 19 20 export function LoadingPlaceholder({ 20 21 width, ··· 233 234 { 234 235 paddingHorizontal: 12, 235 236 paddingVertical: 18, 236 - borderTopWidth: showTopBorder ? 1 : 0, 237 + borderTopWidth: showTopBorder ? hairlineWidth : 0, 237 238 }, 238 239 pal.border, 239 240 style,
+3 -2
src/view/com/util/ViewHeader.tsx
··· 15 15 import {useTheme} from '#/alf' 16 16 import {Text} from './text/Text' 17 17 import {CenteredView} from './Views' 18 + import hairlineWidth = StyleSheet.hairlineWidth 18 19 19 20 const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20} 20 21 ··· 156 157 styles.desktopHeader, 157 158 pal.border, 158 159 { 159 - borderBottomWidth: showBorder ? 1 : 0, 160 + borderBottomWidth: showBorder ? hairlineWidth : 0, 160 161 }, 161 162 {display: 'flex', flexDirection: 'column'}, 162 163 ]}> ··· 245 246 marginRight: 'auto', 246 247 }, 247 248 border: { 248 - borderBottomWidth: 1, 249 + borderBottomWidth: hairlineWidth, 249 250 }, 250 251 titleContainer: { 251 252 marginLeft: 'auto',
+5 -4
src/view/com/util/Views.web.tsx
··· 26 26 import {usePalette} from 'lib/hooks/usePalette' 27 27 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 28 28 import {addStyle} from 'lib/styles' 29 + import hairlineWidth = StyleSheet.hairlineWidth 29 30 30 31 interface AddedProps { 31 32 desktopFixedHeight?: boolean | number ··· 46 47 } 47 48 if (sideBorders) { 48 49 style = addStyle(style, { 49 - borderLeftWidth: 1, 50 - borderRightWidth: 1, 50 + borderLeftWidth: hairlineWidth, 51 + borderRightWidth: hairlineWidth, 51 52 }) 52 53 style = addStyle(style, pal.border) 53 54 } ··· 159 160 160 161 const styles = StyleSheet.create({ 161 162 contentContainer: { 162 - borderLeftWidth: 1, 163 - borderRightWidth: 1, 163 + borderLeftWidth: hairlineWidth, 164 + borderRightWidth: hairlineWidth, 164 165 // @ts-ignore web only 165 166 minHeight: '100vh', 166 167 },
+2 -1
src/view/com/util/load-latest/LoadLatestBtn.tsx
··· 13 13 import {colors} from '#/lib/styles' 14 14 import {isWeb} from '#/platform/detection' 15 15 import {useSession} from '#/state/session' 16 + import hairlineWidth = StyleSheet.hairlineWidth 16 17 17 18 const AnimatedTouchableOpacity = 18 19 Animated.createAnimatedComponent(TouchableOpacity) ··· 73 74 // @ts-ignore 'fixed' is web only -prf 74 75 position: isWeb ? 'fixed' : 'absolute', 75 76 left: 18, 76 - borderWidth: 1, 77 + borderWidth: hairlineWidth, 77 78 width: 52, 78 79 height: 52, 79 80 borderRadius: 26,
+3 -2
src/view/com/util/post-embeds/QuoteEmbed.tsx
··· 39 39 import {PostMeta} from '../PostMeta' 40 40 import {Text} from '../text/Text' 41 41 import {PostEmbeds} from '.' 42 + import hairlineWidth = StyleSheet.hairlineWidth 42 43 43 44 export function MaybeQuoteEmbed({ 44 45 embed, ··· 247 248 marginTop: 8, 248 249 paddingVertical: 12, 249 250 paddingHorizontal: 12, 250 - borderWidth: 1, 251 + borderWidth: hairlineWidth, 251 252 }, 252 253 quotePost: { 253 254 flex: 1, ··· 262 263 marginTop: 8, 263 264 paddingVertical: 14, 264 265 paddingHorizontal: 14, 265 - borderWidth: 1, 266 + borderWidth: hairlineWidth, 266 267 }, 267 268 alert: { 268 269 marginBottom: 6,
+2 -1
src/view/com/util/post-embeds/index.tsx
··· 27 27 import {ExternalLinkEmbed} from './ExternalLinkEmbed' 28 28 import {ListEmbed} from './ListEmbed' 29 29 import {MaybeQuoteEmbed} from './QuoteEmbed' 30 + import hairlineWidth = StyleSheet.hairlineWidth 30 31 31 32 type Embed = 32 33 | AppBskyEmbedRecord.View ··· 187 188 fontWeight: 'bold', 188 189 }, 189 190 customFeedOuter: { 190 - borderWidth: 1, 191 + borderWidth: hairlineWidth, 191 192 borderRadius: 8, 192 193 marginTop: 4, 193 194 paddingHorizontal: 12,
+3 -2
src/view/screens/Feeds.tsx
··· 52 52 import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline' 53 53 import {ListMagnifyingGlass_Stroke2_Corner0_Rounded} from '#/components/icons/ListMagnifyingGlass' 54 54 import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkle' 55 + import hairlineWidth = StyleSheet.hairlineWidth 55 56 56 57 type Props = NativeStackScreenProps<FeedsTabNavigatorParams, 'Feeds'> 57 58 ··· 856 857 paddingHorizontal: 16, 857 858 paddingVertical: 14, 858 859 gap: 12, 859 - borderBottomWidth: 1, 860 + borderBottomWidth: hairlineWidth, 860 861 }, 861 862 savedFeedMobile: { 862 863 paddingVertical: 10, 863 864 }, 864 865 offlineSlug: { 865 - borderWidth: 1, 866 + borderWidth: hairlineWidth, 866 867 borderRadius: 4, 867 868 paddingHorizontal: 4, 868 869 paddingVertical: 2,
+18 -13
src/view/screens/Lists.tsx
··· 1 1 import React from 'react' 2 - import {View} from 'react-native' 2 + import {StyleSheet, View} from 'react-native' 3 + import {AtUri} from '@atproto/api' 4 + import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 5 + import {Trans} from '@lingui/macro' 3 6 import {useFocusEffect, useNavigation} from '@react-navigation/native' 4 - import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 5 - import {AtUri} from '@atproto/api' 6 - import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' 7 - import {MyLists} from '#/view/com/lists/MyLists' 8 - import {Text} from 'view/com/util/text/Text' 9 - import {Button} from 'view/com/util/forms/Button' 10 - import {NavigationProp} from 'lib/routes/types' 7 + 8 + import {useModalControls} from '#/state/modals' 9 + import {useSetMinimalShellMode} from '#/state/shell' 11 10 import {usePalette} from 'lib/hooks/usePalette' 12 11 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 12 + import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' 13 + import {NavigationProp} from 'lib/routes/types' 14 + import {s} from 'lib/styles' 15 + import {MyLists} from '#/view/com/lists/MyLists' 16 + import {Button} from 'view/com/util/forms/Button' 13 17 import {SimpleViewHeader} from 'view/com/util/SimpleViewHeader' 14 - import {s} from 'lib/styles' 15 - import {useSetMinimalShellMode} from '#/state/shell' 16 - import {useModalControls} from '#/state/modals' 17 - import {Trans} from '@lingui/macro' 18 + import {Text} from 'view/com/util/text/Text' 19 + import hairlineWidth = StyleSheet.hairlineWidth 18 20 19 21 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'> 20 22 export function ListsScreen({}: Props) { ··· 51 53 <SimpleViewHeader 52 54 showBackButton={isMobile} 53 55 style={ 54 - !isMobile && [pal.border, {borderLeftWidth: 1, borderRightWidth: 1}] 56 + !isMobile && [ 57 + pal.border, 58 + {borderLeftWidth: hairlineWidth, borderRightWidth: hairlineWidth}, 59 + ] 55 60 }> 56 61 <View style={{flex: 1}}> 57 62 <Text type="title-lg" style={[pal.text, {fontWeight: 'bold'}]}>
+28 -23
src/view/screens/Notifications.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 + import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 3 5 import {useFocusEffect, useIsFocused} from '@react-navigation/native' 4 6 import {useQueryClient} from '@tanstack/react-query' 7 + 8 + import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 9 + import {logger} from '#/logger' 10 + import {isNative} from '#/platform/detection' 11 + import {emitSoftReset, listenSoftReset} from '#/state/events' 12 + import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed' 13 + import { 14 + useUnreadNotifications, 15 + useUnreadNotificationsApi, 16 + } from '#/state/queries/notifications/unread' 17 + import {truncateAndInvalidate} from '#/state/queries/util' 18 + import {useSetMinimalShellMode} from '#/state/shell' 19 + import {useComposerControls} from '#/state/shell/composer' 20 + import {useAnalytics} from 'lib/analytics/analytics' 21 + import {usePalette} from 'lib/hooks/usePalette' 22 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 23 + import {ComposeIcon2} from 'lib/icons' 5 24 import { 6 25 NativeStackScreenProps, 7 26 NotificationsTabNavigatorParams, 8 27 } from 'lib/routes/types' 9 - import {ViewHeader} from '../com/util/ViewHeader' 10 - import {Feed} from '../com/notifications/Feed' 28 + import {colors, s} from 'lib/styles' 11 29 import {TextLink} from 'view/com/util/Link' 12 30 import {ListMethods} from 'view/com/util/List' 13 31 import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' 14 - import {MainScrollProvider} from '../com/util/MainScrollProvider' 15 - import {usePalette} from 'lib/hooks/usePalette' 16 - import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 17 - import {s, colors} from 'lib/styles' 18 - import {useAnalytics} from 'lib/analytics/analytics' 19 - import {logger} from '#/logger' 20 - import {useSetMinimalShellMode} from '#/state/shell' 21 - import {Trans, msg} from '@lingui/macro' 22 - import {useLingui} from '@lingui/react' 23 - import { 24 - useUnreadNotifications, 25 - useUnreadNotificationsApi, 26 - } from '#/state/queries/notifications/unread' 27 - import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed' 28 - import {listenSoftReset, emitSoftReset} from '#/state/events' 29 - import {truncateAndInvalidate} from '#/state/queries/util' 30 - import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 31 - import {isNative} from '#/platform/detection' 32 + import {Feed} from '../com/notifications/Feed' 32 33 import {FAB} from '../com/util/fab/FAB' 33 - import {ComposeIcon2} from 'lib/icons' 34 - import {useComposerControls} from '#/state/shell/composer' 34 + import {MainScrollProvider} from '../com/util/MainScrollProvider' 35 + import {ViewHeader} from '../com/util/ViewHeader' 35 36 36 37 type Props = NativeStackScreenProps< 37 38 NotificationsTabNavigatorParams, ··· 145 146 146 147 return ( 147 148 <View testID="notificationsScreen" style={s.hContentRegion}> 148 - <ViewHeader title={_(msg`Notifications`)} canGoBack={false} /> 149 + <ViewHeader 150 + title={_(msg`Notifications`)} 151 + canGoBack={false} 152 + showBorder={true} 153 + /> 149 154 <MainScrollProvider> 150 155 <Feed 151 156 onScrolledDownChange={setIsScrolledDown}
+3 -2
src/view/screens/ProfileList.tsx
··· 65 65 import * as Prompt from '#/components/Prompt' 66 66 import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' 67 67 import {RichText} from '#/components/RichText' 68 + import hairlineWidth = StyleSheet.hairlineWidth 68 69 69 70 const SECTION_TITLES_CURATE = ['Posts', 'About'] 70 71 const SECTION_TITLES_MOD = ['About'] ··· 802 803 <View 803 804 style={[ 804 805 { 805 - borderTopWidth: 1, 806 + borderTopWidth: hairlineWidth, 806 807 padding: isMobile ? 14 : 20, 807 808 gap: 12, 808 809 }, ··· 953 954 marginTop: 10, 954 955 paddingHorizontal: 18, 955 956 paddingVertical: 14, 956 - borderTopWidth: 1, 957 + borderTopWidth: hairlineWidth, 957 958 }, 958 959 ]}> 959 960 <Text type="title-lg" style={[pal.text, s.mb10]}>
+2 -1
src/view/screens/Settings/index.tsx
··· 66 66 import {navigate, resetToTab} from '#/Navigation' 67 67 import {Email2FAToggle} from './Email2FAToggle' 68 68 import {ExportCarDialog} from './ExportCarDialog' 69 + import hairlineWidth = StyleSheet.hairlineWidth 69 70 70 71 function SettingsAccountCard({ 71 72 account, ··· 317 318 showBackButton={isMobile} 318 319 style={[ 319 320 pal.border, 320 - {borderBottomWidth: 1}, 321 + {borderBottomWidth: hairlineWidth}, 321 322 !isMobile && {borderLeftWidth: 1, borderRightWidth: 1}, 322 323 ]}> 323 324 <View style={{flex: 1}}>
+2 -1
src/view/shell/bottom-bar/BottomBarStyles.tsx
··· 1 1 import {StyleSheet} from 'react-native' 2 2 3 3 import {colors} from 'lib/styles' 4 + import hairlineWidth = StyleSheet.hairlineWidth 4 5 5 6 export const styles = StyleSheet.create({ 6 7 bottomBar: { ··· 9 10 left: 0, 10 11 right: 0, 11 12 flexDirection: 'row', 12 - borderTopWidth: 1, 13 + borderTopWidth: hairlineWidth, 13 14 paddingLeft: 5, 14 15 paddingRight: 10, 15 16 },
+3 -2
src/view/shell/desktop/RightNav.tsx
··· 13 13 import {Text} from 'view/com/util/text/Text' 14 14 import {DesktopFeeds} from './Feeds' 15 15 import {DesktopSearch} from './Search' 16 + import hairlineWidth = StyleSheet.hairlineWidth 16 17 17 18 export function DesktopRightNav({routeName}: {routeName: string}) { 18 19 const pal = usePalette('default') ··· 130 131 marginBottom: 10, 131 132 }, 132 133 desktopFeedsContainer: { 133 - borderTopWidth: 1, 134 - borderBottomWidth: 1, 134 + borderTopWidth: hairlineWidth, 135 + borderBottomWidth: hairlineWidth, 135 136 marginTop: 18, 136 137 marginBottom: 18, 137 138 },