Merge branch 'consolidate-list-props' into web-layout
This commit is contained in:
@@ -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: {},
|
||||
})
|
||||
@@ -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()
|
||||
|
||||
@@ -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,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,
|
||||
},
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,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={
|
||||
|
||||
@@ -50,6 +50,9 @@ export function ProfileCard({
|
||||
return null
|
||||
}
|
||||
const moderation = moderateProfile(profile, moderationOpts)
|
||||
if (moderation.account.filter) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user