Merge branch 'main' into starter-packs

This commit is contained in:
Hailey
2024-06-04 11:19:37 -07:00
51 changed files with 1817 additions and 577 deletions
+125 -36
View File
@@ -9,6 +9,7 @@ import React, {
import {
ActivityIndicator,
Keyboard,
LayoutChangeEvent,
StyleSheet,
TouchableOpacity,
View,
@@ -116,7 +117,7 @@ export const ComposePost = observer(function ComposePost({
const {closeComposer} = useComposerControls()
const {track} = useAnalytics()
const pal = usePalette('default')
const {isTabletOrDesktop, isMobile} = useWebMediaQueries()
const {isMobile} = useWebMediaQueries()
const {_} = useLingui()
const requireAltTextEnabled = useRequireAltTextEnabled()
const langPrefs = useLanguagePrefs()
@@ -170,24 +171,8 @@ export const ComposePost = observer(function ComposePost({
[insets, isKeyboardVisible],
)
const hasScrolled = useSharedValue(0)
const scrollHandler = useAnimatedScrollHandler({
onScroll: event => {
hasScrolled.value = withTiming(event.contentOffset.y > 0 ? 1 : 0)
},
})
const topBarAnimatedStyle = useAnimatedStyle(() => {
return {
borderColor: interpolateColor(
hasScrolled.value,
[0, 1],
['transparent', t.atoms.border_contrast_medium.borderColor],
),
}
})
const onPressCancel = useCallback(() => {
if (graphemeLength > 0 || !gallery.isEmpty) {
if (graphemeLength > 0 || !gallery.isEmpty || extGif) {
closeAllDialogs()
if (Keyboard) {
Keyboard.dismiss()
@@ -197,6 +182,7 @@ export const ComposePost = observer(function ComposePost({
onClose()
}
}, [
extGif,
graphemeLength,
gallery.isEmpty,
closeAllDialogs,
@@ -395,23 +381,26 @@ export const ComposePost = observer(function ComposePost({
[setExtLink],
)
const {
scrollHandler,
onScrollViewContentSizeChange,
onScrollViewLayout,
topBarAnimatedStyle,
bottomBarAnimatedStyle,
} = useAnimatedBorders()
return (
<>
<KeyboardAvoidingView
testID="composePostView"
behavior="padding"
style={a.flex_1}
keyboardVerticalOffset={replyTo ? 120 : isAndroid ? 180 : 150}>
keyboardVerticalOffset={replyTo ? 115 : isAndroid ? 180 : 162}>
<View
style={[a.flex_1, viewStyles]}
aria-modal
accessibilityViewIsModal>
<Animated.View
style={[
styles.topbar,
topBarAnimatedStyle,
isWeb && isTabletOrDesktop && styles.topbarDesktop,
]}>
<Animated.View style={topBarAnimatedStyle}>
<View style={styles.topbarInner}>
<TouchableOpacity
testID="composerDiscardButton"
@@ -509,7 +498,9 @@ export const ComposePost = observer(function ComposePost({
<Animated.ScrollView
onScroll={scrollHandler}
style={styles.scrollView}
keyboardShouldPersistTaps="always">
keyboardShouldPersistTaps="always"
onContentSizeChange={onScrollViewContentSizeChange}
onLayout={onScrollViewLayout}>
{replyTo ? <ComposerReplyTo replyTo={replyTo} /> : undefined}
<View
@@ -575,7 +566,11 @@ export const ComposePost = observer(function ComposePost({
<KeyboardStickyView
offset={{closed: isIOS ? -insets.bottom : 0, opened: 0}}>
{replyTo ? null : (
<ThreadgateBtn threadgate={threadgate} onChange={setThreadgate} />
<ThreadgateBtn
threadgate={threadgate}
onChange={setThreadgate}
style={bottomBarAnimatedStyle}
/>
)}
<View
style={[
@@ -625,15 +620,108 @@ export function useComposerCancelRef() {
return useRef<CancelRef>(null)
}
function useAnimatedBorders() {
const t = useTheme()
const hasScrolledTop = useSharedValue(0)
const hasScrolledBottom = useSharedValue(0)
const contentOffset = useSharedValue(0)
const scrollViewHeight = useSharedValue(Infinity)
const contentHeight = useSharedValue(0)
/**
* Make sure to run this on the UI thread!
*/
const showHideBottomBorder = useCallback(
({
newContentHeight,
newContentOffset,
newScrollViewHeight,
}: {
newContentHeight?: number
newContentOffset?: number
newScrollViewHeight?: number
}) => {
'worklet'
if (typeof newContentHeight === 'number')
contentHeight.value = Math.floor(newContentHeight)
if (typeof newContentOffset === 'number')
contentOffset.value = Math.floor(newContentOffset)
if (typeof newScrollViewHeight === 'number')
scrollViewHeight.value = Math.floor(newScrollViewHeight)
hasScrolledBottom.value = withTiming(
contentHeight.value - contentOffset.value - 5 > scrollViewHeight.value
? 1
: 0,
)
},
[contentHeight, contentOffset, scrollViewHeight, hasScrolledBottom],
)
const scrollHandler = useAnimatedScrollHandler({
onScroll: event => {
'worklet'
hasScrolledTop.value = withTiming(event.contentOffset.y > 0 ? 1 : 0)
showHideBottomBorder({
newContentOffset: event.contentOffset.y,
newContentHeight: event.contentSize.height,
newScrollViewHeight: event.layoutMeasurement.height,
})
},
})
const onScrollViewContentSizeChange = useCallback(
(_width: number, height: number) => {
'worklet'
showHideBottomBorder({
newContentHeight: height,
})
},
[showHideBottomBorder],
)
const onScrollViewLayout = useCallback(
(evt: LayoutChangeEvent) => {
'worklet'
showHideBottomBorder({
newScrollViewHeight: evt.nativeEvent.layout.height,
})
},
[showHideBottomBorder],
)
const topBarAnimatedStyle = useAnimatedStyle(() => {
return {
borderBottomWidth: hairlineWidth,
borderColor: interpolateColor(
hasScrolledTop.value,
[0, 1],
['transparent', t.atoms.border_contrast_medium.borderColor],
),
}
})
const bottomBarAnimatedStyle = useAnimatedStyle(() => {
return {
borderTopWidth: hairlineWidth,
borderColor: interpolateColor(
hasScrolledBottom.value,
[0, 1],
['transparent', t.atoms.border_contrast_medium.borderColor],
),
}
})
return {
scrollHandler,
onScrollViewContentSizeChange,
onScrollViewLayout,
topBarAnimatedStyle,
bottomBarAnimatedStyle,
}
}
const styles = StyleSheet.create({
topbar: {
borderBottomWidth: StyleSheet.hairlineWidth,
},
topbarDesktop: {
paddingTop: 10,
paddingBottom: 10,
height: 50,
},
topbarInner: {
flexDirection: 'row',
alignItems: 'center',
@@ -698,7 +786,8 @@ const styles = StyleSheet.create({
bottomBar: {
flexDirection: 'row',
paddingVertical: 4,
paddingLeft: 8,
// should be 8 but due to visual alignment we have to fudge it
paddingLeft: 7,
paddingRight: 16,
alignItems: 'center',
borderTopWidth: hairlineWidth,
@@ -1,4 +1,4 @@
import React, {useCallback} from 'react'
import React, {useCallback, useRef} from 'react'
import {Keyboard} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -7,7 +7,6 @@ import {logEvent} from '#/lib/statsig/statsig'
import {Gif} from '#/state/queries/tenor'
import {atoms as a, useTheme} from '#/alf'
import {Button} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
import {GifSelectDialog} from '#/components/dialogs/GifSelect'
import {GifSquare_Stroke2_Corner0_Rounded as GifIcon} from '#/components/icons/Gif'
@@ -19,14 +18,14 @@ type Props = {
export function SelectGifBtn({onClose, onSelectGif, disabled}: Props) {
const {_} = useLingui()
const control = useDialogControl()
const ref = useRef<{open: () => void}>(null)
const t = useTheme()
const onPressSelectGif = useCallback(async () => {
logEvent('composer:gif:open', {})
Keyboard.dismiss()
control.open()
}, [control])
ref.current?.open()
}, [])
return (
<>
@@ -44,7 +43,7 @@ export function SelectGifBtn({onClose, onSelectGif, disabled}: Props) {
</Button>
<GifSelectDialog
control={control}
controlRef={ref}
onClose={onClose}
onSelectGif={onSelectGif}
/>
@@ -1,5 +1,6 @@
import React from 'react'
import {Keyboard, View} from 'react-native'
import {Keyboard, StyleProp, ViewStyle} from 'react-native'
import Animated, {AnimatedStyle} from 'react-native-reanimated'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -16,9 +17,11 @@ import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
export function ThreadgateBtn({
threadgate,
onChange,
style,
}: {
threadgate: ThreadgateSetting[]
onChange: (v: ThreadgateSetting[]) => void
style?: StyleProp<AnimatedStyle<ViewStyle>>
}) {
const {track} = useAnalytics()
const {_} = useLingui()
@@ -46,7 +49,7 @@ export function ThreadgateBtn({
: _(msg`Some people can reply`)
return (
<View style={[a.flex_row, a.py_xs, a.px_sm, t.atoms.bg]}>
<Animated.View style={[a.flex_row, a.p_sm, t.atoms.bg, style]}>
<Button
variant="solid"
color="secondary"
@@ -59,6 +62,6 @@ export function ThreadgateBtn({
/>
<ButtonText>{label}</ButtonText>
</Button>
</View>
</Animated.View>
)
}
+9 -13
View File
@@ -7,23 +7,22 @@ import {
View,
ViewStyle,
} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query'
import {cleanError} from '#/lib/strings/errors'
import {useTheme} from '#/lib/ThemeContext'
import {logger} from '#/logger'
import {isNative} from '#/platform/detection'
import {isNative, isWeb} from '#/platform/detection'
import {hydrateFeedGenerator} from '#/state/queries/feed'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {RQKEY, useProfileFeedgensQuery} from '#/state/queries/profile-feedgens'
import {usePalette} from 'lib/hooks/usePalette'
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {EmptyState} from 'view/com/util/EmptyState'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {List, ListRef} from '../util/List'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
import {Text} from '../util/text/Text'
import {FeedSourceCardLoaded} from './FeedSourceCard'
const LOADING = {_reactKey: '__loading__'}
@@ -52,7 +51,6 @@ export const ProfileFeedgens = React.forwardRef<
{did, scrollElRef, headerOffset, enabled, style, testID, setScrollViewTag},
ref,
) {
const pal = usePalette('default')
const {_} = useLingui()
const theme = useTheme()
const [isPTRing, setIsPTRing] = React.useState(false)
@@ -138,13 +136,11 @@ export const ProfileFeedgens = React.forwardRef<
({item, index}: ListRenderItemInfo<any>) => {
if (item === EMPTY) {
return (
<View
<EmptyState
icon="hashtag"
message={_(msg`You have no feeds.`)}
testID="listsEmpty"
style={[{padding: 18, borderTopWidth: 1}, pal.border]}>
<Text style={pal.textLight}>
<Trans>You have no feeds.</Trans>
</Text>
</View>
/>
)
} else if (item === ERROR_ITEM) {
return (
@@ -170,13 +166,13 @@ export const ProfileFeedgens = React.forwardRef<
preferences={preferences}
style={styles.item}
showLikes
hideTopBorder={index === 0}
hideTopBorder={index === 0 && !isWeb}
/>
)
}
return null
},
[error, refetch, onPressRetryLoadMore, pal, preferences, _],
[error, refetch, onPressRetryLoadMore, preferences, _],
)
React.useEffect(() => {
+9 -10
View File
@@ -9,7 +9,8 @@ import {
ViewStyle,
} from 'react-native'
import {AppBskyGraphDefs as GraphDefs} from '@atproto/api'
import {Trans} from '@lingui/macro'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {cleanError} from '#/lib/strings/errors'
import {logger} from '#/logger'
@@ -17,11 +18,10 @@ import {MyListsFilter, useMyListsQuery} from '#/state/queries/my-lists'
import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette'
import {s} from 'lib/styles'
import {EmptyState} from 'view/com/util/EmptyState'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {List} from '../util/List'
import {Text} from '../util/text/Text'
import {ListCard} from './ListCard'
import hairlineWidth = StyleSheet.hairlineWidth
const LOADING = {_reactKey: '__loading__'}
const EMPTY = {_reactKey: '__empty__'}
@@ -42,6 +42,7 @@ export function MyLists({
}) {
const pal = usePalette('default')
const {track} = useAnalytics()
const {_} = useLingui()
const [isPTRing, setIsPTRing] = React.useState(false)
const {data, isFetching, isFetched, isError, error, refetch} =
useMyListsQuery(filter)
@@ -83,14 +84,12 @@ export function MyLists({
({item, index}: {item: any; index: number}) => {
if (item === EMPTY) {
return (
<View
<EmptyState
key={item._reactKey}
icon="list-ul"
message={_(msg`You have no lists.`)}
testID="listsEmpty"
style={[{padding: 18, borderTopWidth: hairlineWidth}, pal.border]}>
<Text style={pal.textLight}>
<Trans>You have no lists.</Trans>
</Text>
</View>
/>
)
} else if (item === ERROR_ITEM) {
return (
@@ -118,7 +117,7 @@ export function MyLists({
/>
)
},
[error, onRefresh, renderItem, pal],
[error, onRefresh, renderItem, _],
)
if (inline) {
+10 -12
View File
@@ -7,22 +7,21 @@ import {
View,
ViewStyle,
} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query'
import {cleanError} from '#/lib/strings/errors'
import {useTheme} from '#/lib/ThemeContext'
import {logger} from '#/logger'
import {isNative} from '#/platform/detection'
import {isNative, isWeb} from '#/platform/detection'
import {RQKEY, useProfileListsQuery} from '#/state/queries/profile-lists'
import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette'
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {EmptyState} from 'view/com/util/EmptyState'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {List, ListRef} from '../util/List'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
import {Text} from '../util/text/Text'
import {ListCard} from './ListCard'
const LOADING = {_reactKey: '__loading__'}
@@ -49,7 +48,6 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
{did, scrollElRef, headerOffset, enabled, style, testID, setScrollViewTag},
ref,
) {
const pal = usePalette('default')
const theme = useTheme()
const {track} = useAnalytics()
const {_} = useLingui()
@@ -142,11 +140,11 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
({item, index}: ListRenderItemInfo<any>) => {
if (item === EMPTY) {
return (
<View testID="listsEmpty" style={[{padding: 18}, pal.border]}>
<Text style={pal.textLight}>
<Trans>You have no lists.</Trans>
</Text>
</View>
<EmptyState
icon="list-ul"
message={_(msg`You have no lists.`)}
testID="listsEmpty"
/>
)
} else if (item === ERROR_ITEM) {
return (
@@ -172,11 +170,11 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
list={item}
testID={`list-${item.name}`}
style={styles.item}
noBorder={index === 0}
noBorder={index === 0 && !isWeb}
/>
)
},
[error, refetch, onPressRetryLoadMore, pal, _],
[error, refetch, onPressRetryLoadMore, _],
)
React.useEffect(() => {
+53 -1
View File
@@ -18,7 +18,13 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {cleanError} from 'lib/strings/errors'
import {colors, gradients, s} from 'lib/styles'
import {useTheme} from 'lib/ThemeContext'
import {isAndroid} from 'platform/detection'
import {isAndroid, isWeb} from 'platform/detection'
import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateAccountDialog'
import {atoms as a, useTheme as useNewTheme} from '#/alf'
import {useDialogControl} from '#/components/Dialog'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {InlineLinkText} from '#/components/Link'
import {Text as NewText} from '#/components/Typography'
import {resetToTab} from '../../../Navigation'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {Text} from '../util/text/Text'
@@ -30,6 +36,7 @@ export const snapPoints = isAndroid ? ['90%'] : ['55%']
export function Component({}: {}) {
const pal = usePalette('default')
const theme = useTheme()
const t = useNewTheme()
const {currentAccount} = useSession()
const agent = useAgent()
const {removeAccount} = useSessionApi()
@@ -41,6 +48,7 @@ export function Component({}: {}) {
const [password, setPassword] = React.useState<string>('')
const [isProcessing, setIsProcessing] = React.useState<boolean>(false)
const [error, setError] = React.useState<string>('')
const deactivateAccountControl = useDialogControl()
const onPressSendEmail = async () => {
setError('')
setIsProcessing(true)
@@ -168,6 +176,50 @@ export function Component({}: {}) {
</TouchableOpacity>
</>
)}
<View style={[!isWeb && a.px_xl]}>
<View
style={[
a.w_full,
a.flex_row,
a.gap_sm,
a.mt_lg,
a.p_lg,
a.rounded_sm,
t.atoms.bg_contrast_25,
]}>
<CircleInfo
size="md"
style={[
a.relative,
{
top: -1,
},
]}
/>
<NewText style={[a.leading_snug, a.flex_1]}>
<Trans>
You can also temporarily deactivate your account instead,
and reactivate it at any time.
</Trans>{' '}
<InlineLinkText
label={_(
msg`Click here for more information on deactivating your account`,
)}
to="#"
onPress={e => {
e.preventDefault()
deactivateAccountControl.open()
return false
}}>
<Trans>Click here for more information.</Trans>
</InlineLinkText>
</NewText>
</View>
</View>
<DeactivateAccountDialog control={deactivateAccountControl} />
</>
) : (
<>
+30 -29
View File
@@ -6,28 +6,30 @@ import {
View,
} from 'react-native'
import {AppBskyGraphDefs as GraphDefs} from '@atproto/api'
import {Text} from '../util/text/Text'
import {UserAvatar} from '../util/UserAvatar'
import {MyLists} from '../lists/MyLists'
import {Button} from '../util/forms/Button'
import * as Toast from '../util/Toast'
import {sanitizeDisplayName} from 'lib/strings/display-names'
import {sanitizeHandle} from 'lib/strings/handles'
import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {isWeb, isAndroid, isMobileWeb} from 'platform/detection'
import {Trans, msg} from '@lingui/macro'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {cleanError} from '#/lib/strings/errors'
import {useModalControls} from '#/state/modals'
import {
useDangerousListMembershipsQuery,
getMembership,
ListMembersip,
useDangerousListMembershipsQuery,
useListMembershipAddMutation,
useListMembershipRemoveMutation,
} from '#/state/queries/list-memberships'
import {cleanError} from '#/lib/strings/errors'
import {useSession} from '#/state/session'
import {usePalette} from 'lib/hooks/usePalette'
import {sanitizeDisplayName} from 'lib/strings/display-names'
import {sanitizeHandle} from 'lib/strings/handles'
import {s} from 'lib/styles'
import {isAndroid, isMobileWeb, isWeb} from 'platform/detection'
import {MyLists} from '../lists/MyLists'
import {Button} from '../util/forms/Button'
import {Text} from '../util/text/Text'
import * as Toast from '../util/Toast'
import {UserAvatar} from '../util/UserAvatar'
import hairlineWidth = StyleSheet.hairlineWidth
export const snapPoints = ['fullscreen']
@@ -61,12 +63,23 @@ export function Component({
return [pal.border, {height: screenHeight / 1.5}]
}
return [pal.border, {flex: 1}]
return [pal.border, {flex: 1, borderTopWidth: hairlineWidth}]
}, [pal.border, screenHeight])
return (
<View testID="userAddRemoveListsModal" style={s.hContentRegion}>
<Text style={[styles.title, pal.text]}>
<Text
style={[
{
textAlign: 'center',
fontWeight: 'bold',
fontSize: 20,
marginBottom: 12,
paddingHorizontal: 12,
},
pal.text,
]}
numberOfLines={1}>
<Trans>Update {displayName} in Lists</Trans>
</Text>
<MyLists
@@ -175,9 +188,7 @@ function ListItem({
style={[
styles.listItem,
pal.border,
{
borderTopWidth: index === 0 ? 0 : 1,
},
index !== 0 && {borderTopWidth: hairlineWidth},
]}>
<View style={styles.listItemAvi}>
<UserAvatar size={40} avatar={list.avatar} type="list" />
@@ -229,16 +240,6 @@ const styles = StyleSheet.create({
container: {
paddingHorizontal: isWeb ? 0 : 16,
},
title: {
textAlign: 'center',
fontWeight: 'bold',
fontSize: 24,
marginBottom: 10,
},
list: {
flex: 1,
borderTopWidth: 1,
},
btns: {
position: 'relative',
flexDirection: 'row',
@@ -247,7 +248,7 @@ const styles = StyleSheet.create({
gap: 10,
paddingTop: 10,
paddingBottom: isAndroid ? 10 : 0,
borderTopWidth: 1,
borderTopWidth: hairlineWidth,
},
footerBtn: {
paddingHorizontal: 24,
+6 -11
View File
@@ -40,7 +40,6 @@ import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe'
import {PostAlerts} from '../../../components/moderation/PostAlerts'
import {PostHider} from '../../../components/moderation/PostHider'
import {getTranslatorLink, isPostInLanguage} from '../../../locale/helpers'
import {AviFollowButton} from '../posts/AviFollowButton'
import {WhoCanReply} from '../threadgate/WhoCanReply'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {Link, TextLink} from '../util/Link'
@@ -472,16 +471,12 @@ let PostThreadItemLoaded = ({
{/* If we are in threaded mode, the avatar is rendered in PostMeta */}
{!isThreadedChild && (
<View style={styles.layoutAvi}>
<AviFollowButton author={post.author} moderation={moderation}>
<PreviewableUserAvatar
size={38}
profile={post.author}
moderation={moderation.ui('avatar')}
type={
post.author.associated?.labeler ? 'labeler' : 'user'
}
/>
</AviFollowButton>
<PreviewableUserAvatar
size={38}
profile={post.author}
moderation={moderation.ui('avatar')}
type={post.author.associated?.labeler ? 'labeler' : 'user'}
/>
{showChildReplyLine && (
<View
+2 -2
View File
@@ -97,8 +97,8 @@ export function AviFollowButton({
}),
a.absolute,
{
bottom: 0,
right: 0,
bottom: -1,
right: -1,
borderWidth: 1,
borderColor: t.atoms.bg.backgroundColor,
},
+5 -1
View File
@@ -1 +1,5 @@
export {Fragment as AviFollowButton} from 'react'
import React from 'react'
export function AviFollowButton({children}: {children: React.ReactNode}) {
return children
}
+1 -9
View File
@@ -32,7 +32,6 @@ import {
import {useSession} from '#/state/session'
import {useAnalytics} from 'lib/analytics/analytics'
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useTheme} from 'lib/ThemeContext'
import {List, ListRef} from '../util/List'
import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
@@ -102,7 +101,6 @@ let Feed = ({
const checkForNewRef = React.useRef<(() => void) | null>(null)
const lastFetchRef = React.useRef<number>(Date.now())
const [feedType, feedUri] = feed.split('|')
const {isTabletOrMobile} = useWebMediaQueries()
const opts = React.useMemo(
() => ({enabled, ignoreFilterFor}),
@@ -314,15 +312,9 @@ let Feed = ({
// -prf
return <DiscoverFallbackHeader />
}
return (
<FeedSlice
slice={item}
hideTopBorder={index === 0 && isTabletOrMobile}
/>
)
return <FeedSlice slice={item} hideTopBorder={index === 0 && !isWeb} />
},
[
isTabletOrMobile,
renderEmptyState,
feed,
error,
+7 -8
View File
@@ -43,6 +43,7 @@ import {Text} from '../util/text/Text'
import {PreviewableUserAvatar} from '../util/UserAvatar'
import {AviFollowButton} from './AviFollowButton'
import hairlineWidth = StyleSheet.hairlineWidth
import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost'
interface FeedItemProps {
record: AppBskyFeedPost.Record
@@ -251,13 +252,10 @@ let FeedItemInner = ({
)}`,
)}
onBeforePress={onOpenReposter}>
<FontAwesomeIcon
icon="retweet"
style={{
marginRight: 4,
color: pal.colors.textLight,
minWidth: 16,
}}
<Repost
style={{color: pal.colors.textLight, marginRight: 3}}
width={14}
height={14}
/>
<Text
type="sm-bold"
@@ -463,9 +461,10 @@ const styles = StyleSheet.create({
},
includeReason: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 2,
marginBottom: 2,
marginLeft: -20,
marginLeft: -18,
},
layout: {
flexDirection: 'row',
+6 -3
View File
@@ -8,6 +8,7 @@ import {
import {Text} from './text/Text'
import {UserGroupIcon} from 'lib/icons'
import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection'
export function EmptyState({
testID,
@@ -22,7 +23,9 @@ export function EmptyState({
}) {
const pal = usePalette('default')
return (
<View testID={testID} style={[styles.container, pal.border, style]}>
<View
testID={testID}
style={[styles.container, isWeb && pal.border, style]}>
<View style={styles.iconContainer}>
{icon === 'user-group' ? (
<UserGroupIcon size="64" style={styles.icon} />
@@ -48,9 +51,9 @@ export function EmptyState({
const styles = StyleSheet.create({
container: {
paddingVertical: 20,
paddingVertical: 24,
paddingHorizontal: 36,
borderTopWidth: 1,
borderTopWidth: isWeb ? 1 : undefined,
},
iconContainer: {
flexDirection: 'row',
+12 -9
View File
@@ -32,14 +32,17 @@ interface AddedProps {
desktopFixedHeight?: boolean | number
}
export const CenteredView = React.forwardRef(function CenteredView({
style,
sideBorders,
topBorder,
...props
}: React.PropsWithChildren<
ViewProps & {sideBorders?: boolean; topBorder?: boolean}
>) {
export const CenteredView = React.forwardRef(function CenteredView(
{
style,
sideBorders,
topBorder,
...props
}: React.PropsWithChildren<
ViewProps & {sideBorders?: boolean; topBorder?: boolean}
>,
ref: React.Ref<View>,
) {
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
if (!isMobile) {
@@ -58,7 +61,7 @@ export const CenteredView = React.forwardRef(function CenteredView({
})
style = addStyle(style, pal.border)
}
return <View style={style} {...props} />
return <View ref={ref} style={style} {...props} />
})
export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>(
+22 -19
View File
@@ -2,7 +2,6 @@ import React from 'react'
import {
StyleProp,
StyleSheet,
TextStyle,
TouchableOpacity,
View,
ViewStyle,
@@ -32,7 +31,7 @@ import {InfoCircleIcon} from 'lib/icons'
import {makeProfileLink} from 'lib/routes/links'
import {precacheProfile} from 'state/queries/profile'
import {ComposerOptsQuote} from 'state/shell/composer'
import {atoms as a, flatten} from '#/alf'
import {atoms as a} from '#/alf'
import {RichText} from '#/components/RichText'
import {ContentHider} from '../../../../components/moderation/ContentHider'
import {PostAlerts} from '../../../../components/moderation/PostAlerts'
@@ -46,12 +45,12 @@ export function MaybeQuoteEmbed({
embed,
onOpen,
style,
textStyle,
allowNestedQuotes,
}: {
embed: AppBskyEmbedRecord.View
onOpen?: () => void
style?: StyleProp<ViewStyle>
textStyle?: StyleProp<TextStyle>
allowNestedQuotes?: boolean
}) {
const pal = usePalette('default')
if (
@@ -65,7 +64,7 @@ export function MaybeQuoteEmbed({
postRecord={embed.record.value}
onOpen={onOpen}
style={style}
textStyle={textStyle}
allowNestedQuotes={allowNestedQuotes}
/>
)
} else if (AppBskyEmbedRecord.isViewBlocked(embed.record)) {
@@ -95,13 +94,13 @@ function QuoteEmbedModerated({
postRecord,
onOpen,
style,
textStyle,
allowNestedQuotes,
}: {
viewRecord: AppBskyEmbedRecord.ViewRecord
postRecord: AppBskyFeedPost.Record
onOpen?: () => void
style?: StyleProp<ViewStyle>
textStyle?: StyleProp<TextStyle>
allowNestedQuotes?: boolean
}) {
const moderationOpts = useModerationOpts()
const moderation = React.useMemo(() => {
@@ -126,7 +125,7 @@ function QuoteEmbedModerated({
moderation={moderation}
onOpen={onOpen}
style={style}
textStyle={textStyle}
allowNestedQuotes={allowNestedQuotes}
/>
)
}
@@ -136,13 +135,13 @@ export function QuoteEmbed({
moderation,
onOpen,
style,
textStyle,
allowNestedQuotes,
}: {
quote: ComposerOptsQuote
moderation?: ModerationDecision
onOpen?: () => void
style?: StyleProp<ViewStyle>
textStyle?: StyleProp<TextStyle>
allowNestedQuotes?: boolean
}) {
const queryClient = useQueryClient()
const pal = usePalette('default')
@@ -161,16 +160,20 @@ export function QuoteEmbed({
const embed = React.useMemo(() => {
const e = quote.embeds?.[0]
if (AppBskyEmbedImages.isView(e) || AppBskyEmbedExternal.isView(e)) {
if (allowNestedQuotes) {
return e
} else if (
AppBskyEmbedRecordWithMedia.isView(e) &&
(AppBskyEmbedImages.isView(e.media) ||
AppBskyEmbedExternal.isView(e.media))
) {
return e.media
} else {
if (AppBskyEmbedImages.isView(e) || AppBskyEmbedExternal.isView(e)) {
return e
} else if (
AppBskyEmbedRecordWithMedia.isView(e) &&
(AppBskyEmbedImages.isView(e.media) ||
AppBskyEmbedExternal.isView(e.media))
) {
return e.media
}
}
}, [quote.embeds])
}, [quote.embeds, allowNestedQuotes])
const onBeforePress = React.useCallback(() => {
precacheProfile(queryClient, quote.author)
@@ -201,7 +204,7 @@ export function QuoteEmbed({
{richText ? (
<RichText
value={richText}
style={[a.text_md, flatten(textStyle)]}
style={a.text_md}
numberOfLines={20}
disableLinks
/>
+4 -9
View File
@@ -4,7 +4,6 @@ import {
StyleProp,
StyleSheet,
Text,
TextStyle,
View,
ViewStyle,
} from 'react-native'
@@ -42,13 +41,13 @@ export function PostEmbeds({
moderation,
onOpen,
style,
quoteTextStyle,
allowNestedQuotes,
}: {
embed?: Embed
moderation?: ModerationDecision
onOpen?: () => void
style?: StyleProp<ViewStyle>
quoteTextStyle?: StyleProp<TextStyle>
allowNestedQuotes?: boolean
}) {
const pal = usePalette('default')
const {openLightbox} = useLightboxControls()
@@ -63,11 +62,7 @@ export function PostEmbeds({
moderation={moderation}
onOpen={onOpen}
/>
<MaybeQuoteEmbed
embed={embed.record}
onOpen={onOpen}
textStyle={quoteTextStyle}
/>
<MaybeQuoteEmbed embed={embed.record} onOpen={onOpen} />
</View>
)
}
@@ -98,8 +93,8 @@ export function PostEmbeds({
<MaybeQuoteEmbed
embed={embed}
style={style}
textStyle={quoteTextStyle}
onOpen={onOpen}
allowNestedQuotes={allowNestedQuotes}
/>
)
}