Merge branch 'consolidate-list-props' into web-layout

This commit is contained in:
Dan Abramov
2023-12-15 04:27:26 +00:00
60 changed files with 1267 additions and 689 deletions
+165
View File
@@ -0,0 +1,165 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {useLingui} from '@lingui/react'
import {Trans, msg} from '@lingui/macro'
import {ScrollView} from '../util/Views'
import {Text} from '../util/text/Text'
import {usePalette} from '#/lib/hooks/usePalette'
import {colors, s} from '#/lib/styles'
import {TextLink} from '../util/Link'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
export function HomeLoggedOutCTA() {
const pal = usePalette('default')
const {_} = useLingui()
const {isMobile} = useWebMediaQueries()
const {requestSwitchToAccount} = useLoggedOutViewControls()
const showCreateAccount = React.useCallback(() => {
requestSwitchToAccount({requestedAccount: 'new'})
}, [requestSwitchToAccount])
const showSignIn = React.useCallback(() => {
requestSwitchToAccount({requestedAccount: 'none'})
}, [requestSwitchToAccount])
return (
<ScrollView style={styles.container} testID="loggedOutCTA">
<View style={[styles.hero, isMobile && styles.heroMobile]}>
<Text style={[styles.title, pal.link]}>
<Trans>Bluesky</Trans>
</Text>
<Text
style={[
styles.subtitle,
isMobile && styles.subtitleMobile,
pal.textLight,
]}>
<Trans>See what's next</Trans>
</Text>
</View>
<View
testID="signinOrCreateAccount"
style={isMobile ? undefined : styles.btnsDesktop}>
<TouchableOpacity
testID="createAccountButton"
style={[
styles.btn,
isMobile && styles.btnMobile,
{backgroundColor: colors.blue3},
]}
onPress={showCreateAccount}
accessibilityRole="button"
accessibilityLabel={_(msg`Create new account`)}
accessibilityHint="Opens flow to create a new Bluesky account">
<Text
style={[
s.white,
styles.btnLabel,
isMobile && styles.btnLabelMobile,
]}>
<Trans>Create a new account</Trans>
</Text>
</TouchableOpacity>
<TouchableOpacity
testID="signInButton"
style={[styles.btn, isMobile && styles.btnMobile, pal.btn]}
onPress={showSignIn}
accessibilityRole="button"
accessibilityLabel={_(msg`Sign in`)}
accessibilityHint="Opens flow to sign into your existing Bluesky account">
<Text
style={[
pal.text,
styles.btnLabel,
isMobile && styles.btnLabelMobile,
]}>
<Trans>Sign In</Trans>
</Text>
</TouchableOpacity>
</View>
<View style={[styles.footer, pal.view, pal.border]}>
<TextLink
type="2xl"
href="https://blueskyweb.xyz"
text={_(msg`Business`)}
style={[styles.footerLink, pal.link]}
/>
<TextLink
type="2xl"
href="https://blueskyweb.xyz/blog"
text={_(msg`Blog`)}
style={[styles.footerLink, pal.link]}
/>
<TextLink
type="2xl"
href="https://blueskyweb.xyz/join"
text={_(msg`Jobs`)}
style={[styles.footerLink, pal.link]}
/>
</View>
</ScrollView>
)
}
const styles = StyleSheet.create({
container: {
height: '100%',
},
hero: {
justifyContent: 'center',
paddingTop: 100,
paddingBottom: 30,
},
heroMobile: {
paddingBottom: 50,
},
title: {
textAlign: 'center',
fontSize: 68,
fontWeight: 'bold',
},
subtitle: {
textAlign: 'center',
fontSize: 48,
fontWeight: 'bold',
},
subtitleMobile: {
fontSize: 42,
},
btnsDesktop: {
flexDirection: 'row',
justifyContent: 'center',
gap: 20,
marginHorizontal: 20,
},
btn: {
borderRadius: 32,
width: 230,
paddingVertical: 12,
marginBottom: 20,
},
btnMobile: {
flex: 1,
width: 'auto',
marginHorizontal: 20,
paddingVertical: 16,
},
btnLabel: {
textAlign: 'center',
fontSize: 18,
},
btnLabelMobile: {
textAlign: 'center',
fontSize: 21,
},
footer: {
flexDirection: 'row',
gap: 20,
justifyContent: 'center',
},
footerLink: {},
})
+3 -1
View File
@@ -33,7 +33,9 @@ export function LoggedOut({onDismiss}: {onDismiss?: () => void}) {
const {requestedAccountSwitchTo} = useLoggedOutView()
const [screenState, setScreenState] = React.useState<ScreenState>(
requestedAccountSwitchTo
? ScreenState.S_Login
? requestedAccountSwitchTo === 'new'
? ScreenState.S_CreateAccount
: ScreenState.S_Login
: ScreenState.S_LoginOrCreateAccount,
)
const {isMobile} = useWebMediaQueries()
+11 -5
View File
@@ -7,6 +7,8 @@ import {usePalette} from 'lib/hooks/usePalette'
import {CenteredView} from '../util/Views'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {Logo} from '#/view/icons/Logo'
import {Logotype} from '#/view/icons/Logotype'
export const SplashScreen = ({
onPressSignin,
@@ -22,11 +24,14 @@ export const SplashScreen = ({
<CenteredView style={[styles.container, pal.view]}>
<ErrorBoundary>
<View style={styles.hero}>
<Text style={[styles.title, pal.link]}>
<Trans>Bluesky</Trans>
</Text>
<Text style={[styles.subtitle, pal.textLight]}>
<Trans>See what's next</Trans>
<Logo width={92} fill="sky" />
<View style={{paddingTop: 40, paddingBottom: 6}}>
<Logotype width={161} />
</View>
<Text type="lg-medium" style={[pal.textLight]}>
<Trans>What's next?</Trans>
</Text>
</View>
<View testID="signinOrCreateAccount" style={styles.btns}>
@@ -65,6 +70,7 @@ const styles = StyleSheet.create({
hero: {
flex: 2,
justifyContent: 'center',
alignItems: 'center',
},
btns: {
paddingBottom: 40,
+10 -10
View File
@@ -10,6 +10,8 @@ import {CenteredView} from '../util/Views'
import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {Trans} from '@lingui/macro'
import {Logo} from '#/view/icons/Logo'
import {Logotype} from '#/view/icons/Logotype'
export const SplashScreen = ({
onDismiss,
@@ -55,14 +57,15 @@ export const SplashScreen = ({
styles.containerInner,
isMobileWeb && styles.containerInnerMobile,
pal.border,
{alignItems: 'center'},
]}>
<ErrorBoundary>
<Text style={isMobileWeb ? styles.titleMobile : styles.title}>
Bluesky
</Text>
<Text style={isMobileWeb ? styles.subtitleMobile : styles.subtitle}>
See what's next
</Text>
<Logo width={92} fill="sky" />
<View style={{paddingTop: 40, paddingBottom: 20}}>
<Logotype width={161} />
</View>
<View testID="signinOrCreateAccount" style={styles.btns}>
<TouchableOpacity
testID="createAccountButton"
@@ -117,8 +120,6 @@ function Footer({styles}: {styles: ReturnType<typeof useStyles>}) {
)
}
const useStyles = () => {
const {isTabletOrMobile} = useWebMediaQueries()
const isMobileWeb = isWeb && isTabletOrMobile
return StyleSheet.create({
container: {
height: '100%',
@@ -161,8 +162,7 @@ const useStyles = () => {
paddingBottom: 30,
},
btns: {
flexDirection: isMobileWeb ? 'column' : 'row',
gap: 20,
gap: 10,
justifyContent: 'center',
paddingBottom: 40,
},
+4 -19
View File
@@ -1,12 +1,5 @@
import React from 'react'
import {
Dimensions,
RefreshControl,
StyleProp,
StyleSheet,
View,
ViewStyle,
} from 'react-native'
import {Dimensions, StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {useQueryClient} from '@tanstack/react-query'
import {List, ListRef} from '../util/List'
import {FeedSourceCardLoaded} from './FeedSourceCard'
@@ -180,22 +173,14 @@ export const ProfileFeedgens = React.forwardRef<
data={items}
keyExtractor={(item: any) => item._reactKey || item.uri}
renderItem={renderItemInner}
refreshControl={
<RefreshControl
refreshing={isPTRing}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
progressViewOffset={headerOffset}
/>
}
refreshing={isPTRing}
onRefresh={onRefresh}
headerOffset={headerOffset}
contentContainerStyle={{
minHeight: Dimensions.get('window').height * 1.5,
}}
style={{paddingTop: headerOffset}}
indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
removeClippedSubviews={true}
contentOffset={{x: 0, y: headerOffset * -1}}
// @ts-ignore our .web version only -prf
desktopFixedHeight
onEndReached={onEndReached}
+3 -14
View File
@@ -2,7 +2,6 @@ import React from 'react'
import {
ActivityIndicator,
Dimensions,
RefreshControl,
StyleProp,
View,
ViewStyle,
@@ -15,7 +14,6 @@ import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
import {ProfileCard} from '../profile/ProfileCard'
import {Button} from '../util/forms/Button'
import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useListMembersQuery} from '#/state/queries/list-members'
import {logger} from '#/logger'
@@ -51,7 +49,6 @@ export function ListMembers({
headerOffset?: number
desktopFixedHeightOffset?: number
}) {
const pal = usePalette('default')
const {track} = useAnalytics()
const [isRefreshing, setIsRefreshing] = React.useState(false)
const {isMobile} = useWebMediaQueries()
@@ -215,24 +212,16 @@ export function ListMembers({
renderItem={renderItem}
ListHeaderComponent={renderHeader}
ListFooterComponent={Footer}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
progressViewOffset={headerOffset}
/>
}
refreshing={isRefreshing}
onRefresh={onRefresh}
headerOffset={headerOffset}
contentContainerStyle={{
minHeight: Dimensions.get('window').height * 1.5,
}}
style={{paddingTop: headerOffset}}
onScrolledDownChange={onScrolledDownChange}
onEndReached={onEndReached}
onEndReachedThreshold={0.6}
removeClippedSubviews={true}
contentOffset={{x: 0, y: headerOffset * -1}}
// @ts-ignore our .web version only -prf
desktopFixedHeight={desktopFixedHeightOffset || true}
/>
+45 -25
View File
@@ -119,31 +119,51 @@ export function MyLists({
[error, onRefresh, renderItem, pal],
)
const FlatListCom = inline ? RNFlatList : List
return (
<View testID={testID} style={style}>
{items.length > 0 && (
<FlatListCom
testID={testID ? `${testID}-flatlist` : undefined}
data={items}
keyExtractor={item => (item.uri ? item.uri : item._reactKey)}
renderItem={renderItemInner}
refreshControl={
<RefreshControl
refreshing={isPTRing}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
/>
}
contentContainerStyle={[s.contentContainer]}
removeClippedSubviews={true}
// @ts-ignore our .web version only -prf
desktopFixedHeight
/>
)}
</View>
)
if (inline) {
return (
<View testID={testID} style={style}>
{items.length > 0 && (
<RNFlatList
testID={testID ? `${testID}-flatlist` : undefined}
data={items}
keyExtractor={item => (item.uri ? item.uri : item._reactKey)}
renderItem={renderItemInner}
refreshControl={
<RefreshControl
refreshing={isPTRing}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
/>
}
contentContainerStyle={[s.contentContainer]}
removeClippedSubviews={true}
// @ts-ignore our .web version only -prf
desktopFixedHeight
/>
)}
</View>
)
} else {
return (
<View testID={testID} style={style}>
{items.length > 0 && (
<List
testID={testID ? `${testID}-flatlist` : undefined}
data={items}
keyExtractor={item => (item.uri ? item.uri : item._reactKey)}
renderItem={renderItemInner}
refreshing={isPTRing}
onRefresh={onRefresh}
contentContainerStyle={[s.contentContainer]}
removeClippedSubviews={true}
// @ts-ignore our .web version only -prf
desktopFixedHeight
/>
)}
</View>
)
}
}
const styles = StyleSheet.create({
+4 -19
View File
@@ -1,12 +1,5 @@
import React from 'react'
import {
Dimensions,
RefreshControl,
StyleProp,
StyleSheet,
View,
ViewStyle,
} from 'react-native'
import {Dimensions, StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {useQueryClient} from '@tanstack/react-query'
import {List, ListRef} from '../util/List'
import {ListCard} from './ListCard'
@@ -182,22 +175,14 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
data={items}
keyExtractor={(item: any) => item._reactKey}
renderItem={renderItemInner}
refreshControl={
<RefreshControl
refreshing={isPTRing}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
progressViewOffset={headerOffset}
/>
}
refreshing={isPTRing}
onRefresh={onRefresh}
headerOffset={headerOffset}
contentContainerStyle={{
minHeight: Dimensions.get('window').height * 1.5,
}}
style={{paddingTop: headerOffset}}
indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
removeClippedSubviews={true}
contentOffset={{x: 0, y: headerOffset * -1}}
// @ts-ignore our .web version only -prf
desktopFixedHeight
onEndReached={onEndReached}
+3 -11
View File
@@ -1,13 +1,12 @@
import React from 'react'
import {CenteredView} from '../util/Views'
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
import {ActivityIndicator, StyleSheet, View} from 'react-native'
import {FeedItem} from './FeedItem'
import {NotificationFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
import {EmptyState} from '../util/EmptyState'
import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {useNotificationFeedQuery} from '#/state/queries/notifications/feed'
import {useUnreadNotificationsApi} from '#/state/queries/notifications/unread'
import {logger} from '#/logger'
@@ -30,7 +29,6 @@ export function Feed({
onScrolledDownChange: (isScrolledDown: boolean) => void
ListHeaderComponent?: () => JSX.Element
}) {
const pal = usePalette('default')
const [isPTRing, setIsPTRing] = React.useState(false)
const moderationOpts = useModerationOpts()
@@ -152,14 +150,8 @@ export function Feed({
renderItem={renderItem}
ListHeaderComponent={ListHeaderComponent}
ListFooterComponent={FeedFooter}
refreshControl={
<RefreshControl
refreshing={isPTRing}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
/>
}
refreshing={isPTRing}
onRefresh={onRefresh}
onEndReached={onEndReached}
onEndReachedThreshold={0.6}
onScrolledDownChange={onScrolledDownChange}
+5 -8
View File
@@ -3,12 +3,9 @@ import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {TabBar} from 'view/com/pager/TabBar'
import {RenderTabBarFnProps} from 'view/com/pager/Pager'
import {usePalette} from 'lib/hooks/usePalette'
import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
import {Link} from '../util/Link'
import {Text} from '../util/text/Text'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
import {s} from 'lib/styles'
import {HITSLOP_10} from 'lib/constants'
import Animated from 'react-native-reanimated'
import {msg} from '@lingui/macro'
@@ -21,17 +18,17 @@ import {usePinnedFeedsInfos} from '#/state/queries/feed'
import {isWeb} from 'platform/detection'
import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from 'lib/routes/types'
import {Logo} from '#/view/icons/Logo'
export function FeedsTabBar(
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
) {
const pal = usePalette('default')
const {isSandbox, hasSession} = useSession()
const {hasSession} = useSession()
const {_} = useLingui()
const setDrawerOpen = useSetDrawerOpen()
const navigation = useNavigation<NavigationProp>()
const {feeds, hasPinnedCustom} = usePinnedFeedsInfos()
const brandBlue = useColorSchemeStyle(s.brandBlue, s.blue3)
const {headerHeight} = useShellLayout()
const {headerMinimalShellTransform} = useMinimalShellMode()
const pinnedDisplayNames = hasSession ? feeds.map(f => f.displayName) : []
@@ -86,9 +83,9 @@ export function FeedsTabBar(
/>
</TouchableOpacity>
</View>
<Text style={[brandBlue, s.bold, styles.title]}>
{isSandbox ? 'SANDBOX' : 'Bluesky'}
</Text>
<View>
<Logo width={30} />
</View>
<View style={[pal.view, {width: 18}]}>
{hasSession && (
<Link
+3 -11
View File
@@ -1,18 +1,16 @@
import React, {useCallback, useMemo, useState} from 'react'
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
import {ActivityIndicator, StyleSheet, View} from 'react-native'
import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
import {CenteredView} from '../util/Views'
import {List} from '../util/List'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
import {usePalette} from 'lib/hooks/usePalette'
import {logger} from '#/logger'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {usePostLikedByQuery} from '#/state/queries/post-liked-by'
import {cleanError} from '#/lib/strings/errors'
export function PostLikedBy({uri}: {uri: string}) {
const pal = usePalette('default')
const [isPTRing, setIsPTRing] = useState(false)
const {
data: resolvedUri,
@@ -88,14 +86,8 @@ export function PostLikedBy({uri}: {uri: string}) {
<List
data={likes}
keyExtractor={item => item.actor.did}
refreshControl={
<RefreshControl
refreshing={isPTRing}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
/>
}
refreshing={isPTRing}
onRefresh={onRefresh}
onEndReached={onEndReached}
renderItem={renderItem}
initialNumToRender={15}
+3 -11
View File
@@ -1,18 +1,16 @@
import React, {useMemo, useCallback, useState} from 'react'
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
import {ActivityIndicator, StyleSheet, View} from 'react-native'
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
import {CenteredView} from '../util/Views'
import {List} from '../util/List'
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {usePalette} from 'lib/hooks/usePalette'
import {logger} from '#/logger'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {usePostRepostedByQuery} from '#/state/queries/post-reposted-by'
import {cleanError} from '#/lib/strings/errors'
export function PostRepostedBy({uri}: {uri: string}) {
const pal = usePalette('default')
const [isPTRing, setIsPTRing] = useState(false)
const {
data: resolvedUri,
@@ -89,14 +87,8 @@ export function PostRepostedBy({uri}: {uri: string}) {
<List
data={repostedBy}
keyExtractor={item => item.did}
refreshControl={
<RefreshControl
refreshing={isPTRing}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
/>
}
refreshing={isPTRing}
onRefresh={onRefresh}
onEndReached={onEndReached}
renderItem={renderItem}
initialNumToRender={15}
+16 -13
View File
@@ -2,7 +2,6 @@ import React, {useEffect, useRef} from 'react'
import {
ActivityIndicator,
Pressable,
RefreshControl,
StyleSheet,
TouchableOpacity,
View,
@@ -157,7 +156,9 @@ function PostThreadLoaded({
// construct content
const posts = React.useMemo(() => {
let arr = [TOP_COMPONENT].concat(
Array.from(flattenThreadSkeleton(sortThread(thread, threadViewPrefs))),
Array.from(
flattenThreadSkeleton(sortThread(thread, threadViewPrefs), hasSession),
),
)
if (arr.length > maxVisible) {
arr = arr.slice(0, maxVisible).concat([LOAD_MORE])
@@ -166,7 +167,7 @@ function PostThreadLoaded({
arr.push(BOTTOM_COMPONENT)
}
return arr
}, [thread, maxVisible, threadViewPrefs])
}, [thread, maxVisible, threadViewPrefs, hasSession])
/**
* NOTE
@@ -347,14 +348,8 @@ function PostThreadLoaded({
}
keyExtractor={item => item._reactKey}
renderItem={renderItem}
refreshControl={
<RefreshControl
refreshing={isPTRing}
onRefresh={onPTR}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
/>
}
refreshing={isPTRing}
onRefresh={onPTR}
onContentSizeChange={onContentSizeChange}
style={s.hContentRegion}
// @ts-ignore our .web version only -prf
@@ -468,20 +463,24 @@ function isThreadPost(v: unknown): v is ThreadPost {
function* flattenThreadSkeleton(
node: ThreadNode,
hasSession: boolean,
): Generator<YieldedItem, void> {
if (node.type === 'post') {
if (node.parent) {
yield* flattenThreadSkeleton(node.parent)
yield* flattenThreadSkeleton(node.parent, hasSession)
} else if (node.ctx.isParentLoading) {
yield PARENT_SPINNER
}
if (!hasSession && node.ctx.depth > 0 && hasPwiOptOut(node)) {
return
}
yield node
if (node.ctx.isHighlightedPost && !node.post.viewer?.replyDisabled) {
yield REPLY_PROMPT
}
if (node.replies?.length) {
for (const reply of node.replies) {
yield* flattenThreadSkeleton(reply)
yield* flattenThreadSkeleton(reply, hasSession)
}
} else if (node.ctx.isChildLoading) {
yield CHILD_SPINNER
@@ -493,6 +492,10 @@ function* flattenThreadSkeleton(
}
}
function hasPwiOptOut(node: ThreadPost) {
return !!node.post.author.labels?.find(l => l.val === '!no-unauthenticated')
}
function hasBranchingReplies(node: ThreadNode) {
if (node.type !== 'post') {
return false
+3 -14
View File
@@ -3,7 +3,6 @@ import {
ActivityIndicator,
AppState,
Dimensions,
RefreshControl,
StyleProp,
StyleSheet,
View,
@@ -16,7 +15,6 @@ import {FeedErrorMessage} from './FeedErrorMessage'
import {FeedSlice} from './FeedSlice'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext'
import {logger} from '#/logger'
import {
@@ -70,7 +68,6 @@ let Feed = ({
ListHeaderComponent?: () => JSX.Element
extraData?: any
}): React.ReactNode => {
const pal = usePalette('default')
const theme = useTheme()
const {track} = useAnalytics()
const queryClient = useQueryClient()
@@ -276,25 +273,17 @@ let Feed = ({
renderItem={renderItem}
ListFooterComponent={FeedFooter}
ListHeaderComponent={ListHeaderComponent}
refreshControl={
<RefreshControl
refreshing={isPTRing}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
progressViewOffset={headerOffset}
/>
}
refreshing={isPTRing}
onRefresh={onRefresh}
headerOffset={headerOffset}
contentContainerStyle={{
minHeight: Dimensions.get('window').height * 1.5,
}}
style={{paddingTop: headerOffset}}
onScrolledDownChange={onScrolledDownChange}
indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
onEndReached={onEndReached}
onEndReachedThreshold={2} // number of posts left to trigger load more
removeClippedSubviews={true}
contentOffset={{x: 0, y: headerOffset * -1}}
extraData={extraData}
// @ts-ignore our .web version only -prf
desktopFixedHeight={
+3
View File
@@ -50,6 +50,9 @@ export function ProfileCard({
return null
}
const moderation = moderateProfile(profile, moderationOpts)
if (moderation.account.filter) {
return null
}
return (
<Link
+3 -11
View File
@@ -1,18 +1,16 @@
import React from 'react'
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
import {ActivityIndicator, StyleSheet, View} from 'react-native'
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
import {CenteredView} from '../util/Views'
import {List} from '../util/List'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {ProfileCardWithFollowBtn} from './ProfileCard'
import {usePalette} from 'lib/hooks/usePalette'
import {useProfileFollowersQuery} from '#/state/queries/profile-followers'
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {logger} from '#/logger'
import {cleanError} from '#/lib/strings/errors'
export function ProfileFollowers({name}: {name: string}) {
const pal = usePalette('default')
const [isPTRing, setIsPTRing] = React.useState(false)
const {
data: resolvedDid,
@@ -90,14 +88,8 @@ export function ProfileFollowers({name}: {name: string}) {
<List
data={followers}
keyExtractor={item => item.did}
refreshControl={
<RefreshControl
refreshing={isPTRing}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
/>
}
refreshing={isPTRing}
onRefresh={onRefresh}
onEndReached={onEndReached}
renderItem={renderItem}
initialNumToRender={15}
+3 -11
View File
@@ -1,18 +1,16 @@
import React from 'react'
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
import {ActivityIndicator, StyleSheet, View} from 'react-native'
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
import {CenteredView} from '../util/Views'
import {List} from '../util/List'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {ProfileCardWithFollowBtn} from './ProfileCard'
import {usePalette} from 'lib/hooks/usePalette'
import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {logger} from '#/logger'
import {cleanError} from '#/lib/strings/errors'
export function ProfileFollows({name}: {name: string}) {
const pal = usePalette('default')
const [isPTRing, setIsPTRing] = React.useState(false)
const {
data: resolvedDid,
@@ -90,14 +88,8 @@ export function ProfileFollows({name}: {name: string}) {
<List
data={follows}
keyExtractor={item => item.did}
refreshControl={
<RefreshControl
refreshing={isPTRing}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
/>
}
refreshing={isPTRing}
onRefresh={onRefresh}
onEndReached={onEndReached}
renderItem={renderItem}
initialNumToRender={15}
+42 -3
View File
@@ -1,27 +1,42 @@
import React, {memo, startTransition} from 'react'
import {FlatListProps} from 'react-native'
import {FlatListProps, RefreshControl} from 'react-native'
import {FlatList_INTERNAL} from './Views'
import {addStyle} from 'lib/styles'
import {useScrollHandlers} from '#/lib/ScrollContext'
import {runOnJS, useSharedValue} from 'react-native-reanimated'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {usePalette} from '#/lib/hooks/usePalette'
export type ListMethods = FlatList_INTERNAL
export type ListProps<ItemT> = Omit<
FlatListProps<ItemT>,
'onScroll' // Use ScrollContext instead.
| 'onScroll' // Use ScrollContext instead.
| 'refreshControl' // Pass refreshing and/or onRefresh instead.
| 'contentOffset' // Pass headerOffset instead.
> & {
onScrolledDownChange?: (isScrolledDown: boolean) => void
headerOffset?: number
refreshing?: boolean
onRefresh?: () => void
}
export type ListRef = React.MutableRefObject<FlatList_INTERNAL | null>
const SCROLLED_DOWN_LIMIT = 200
function ListImpl<ItemT>(
{onScrolledDownChange, ...props}: ListProps<ItemT>,
{
onScrolledDownChange,
refreshing,
onRefresh,
headerOffset,
style,
...props
}: ListProps<ItemT>,
ref: React.Ref<ListMethods>,
) {
const isScrolledDown = useSharedValue(false)
const contextScrollHandlers = useScrollHandlers()
const pal = usePalette('default')
function handleScrolledDownChange(didScrollDown: boolean) {
startTransition(() => {
@@ -49,11 +64,35 @@ function ListImpl<ItemT>(
},
})
let refreshControl
if (refreshing !== undefined || onRefresh !== undefined) {
refreshControl = (
<RefreshControl
refreshing={refreshing ?? false}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
progressViewOffset={headerOffset}
/>
)
}
let contentOffset
if (headerOffset != null) {
style = addStyle(style, {
paddingTop: headerOffset,
})
contentOffset = {x: 0, y: headerOffset * -1}
}
return (
<FlatList_INTERNAL
{...props}
contentOffset={contentOffset}
refreshControl={refreshControl}
onScroll={scrollHandler}
scrollEventThrottle={1}
style={style}
ref={ref}
/>
)
+12 -8
View File
@@ -7,7 +7,7 @@ import {Text} from '../text/Text'
import {ShieldExclamation} from 'lib/icons'
import {describeModerationCause} from 'lib/moderation'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
import {msg, Trans} from '@lingui/macro'
import {useModalControls} from '#/state/modals'
import {isPostMediaBlurred} from 'lib/moderation'
@@ -95,13 +95,17 @@ export function ContentHider({
<Text type="md" style={pal.text}>
{desc.name}
</Text>
{!moderation.noOverride && (
<View style={styles.showBtn}>
<Text type="lg" style={pal.link}>
{override ? 'Hide' : 'Show'}
</Text>
</View>
)}
<View style={styles.showBtn}>
<Text type="lg" style={pal.link}>
{moderation.noOverride ? (
<Trans>Learn more</Trans>
) : override ? (
<Trans>Hide</Trans>
) : (
<Trans>Show</Trans>
)}
</Text>
</View>
</Pressable>
{override && <View style={childContainerStyle}>{children}</View>}
</View>
+41 -23
View File
@@ -22,6 +22,7 @@ import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
import {s} from '#/lib/styles'
import {CenteredView} from '../Views'
export function ScreenHider({
testID,
@@ -53,41 +54,58 @@ export function ScreenHider({
)
}
const isNoPwi =
moderation.cause?.type === 'label' &&
moderation.cause?.labelDef.id === '!no-unauthenticated'
const desc = describeModerationCause(moderation.cause, 'account')
return (
<View style={[styles.container, pal.view, containerStyle]}>
<CenteredView
style={[styles.container, pal.view, containerStyle]}
sideBorders>
<View style={styles.iconContainer}>
<View style={[styles.icon, palInverted.view]}>
<FontAwesomeIcon
icon="exclamation"
icon={isNoPwi ? ['far', 'eye-slash'] : 'exclamation'}
style={pal.textInverted as FontAwesomeIconStyle}
size={24}
/>
</View>
</View>
<Text type="title-2xl" style={[styles.title, pal.text]}>
<Trans>Content Warning</Trans>
{isNoPwi ? (
<Trans>Sign-in Required</Trans>
) : (
<Trans>Content Warning</Trans>
)}
</Text>
<Text type="2xl" style={[styles.description, pal.textLight]}>
<Trans>This {screenDescription} has been flagged:</Trans>
<Text type="2xl-medium" style={[pal.text, s.ml5]}>
{desc.name}.
</Text>
<TouchableWithoutFeedback
onPress={() => {
openModal({
name: 'moderation-details',
context: 'account',
moderation,
})
}}
accessibilityRole="button"
accessibilityLabel={_(msg`Learn more about this warning`)}
accessibilityHint="">
<Text type="2xl" style={pal.link}>
<Trans>Learn More</Trans>
</Text>
</TouchableWithoutFeedback>
{isNoPwi ? (
<Trans>
This account has requested that users sign in to view their profile.
</Trans>
) : (
<>
<Trans>This {screenDescription} has been flagged:</Trans>
<Text type="2xl-medium" style={[pal.text, s.ml5]}>
{desc.name}.
</Text>
<TouchableWithoutFeedback
onPress={() => {
openModal({
name: 'moderation-details',
context: 'account',
moderation,
})
}}
accessibilityRole="button"
accessibilityLabel={_(msg`Learn more about this warning`)}
accessibilityHint="">
<Text type="2xl" style={pal.link}>
<Trans>Learn More</Trans>
</Text>
</TouchableWithoutFeedback>
</>
)}{' '}
</Text>
{isMobile && <View style={styles.spacer} />}
<View style={styles.btnContainer}>
@@ -116,7 +134,7 @@ export function ScreenHider({
</Button>
)}
</View>
</View>
</CenteredView>
)
}