Merge branch 'main' into starter-packs
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,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) {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -97,8 +97,8 @@ export function AviFollowButton({
|
||||
}),
|
||||
a.absolute,
|
||||
{
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
bottom: -1,
|
||||
right: -1,
|
||||
borderWidth: 1,
|
||||
borderColor: t.atoms.bg.backgroundColor,
|
||||
},
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export {Fragment as AviFollowButton} from 'react'
|
||||
import React from 'react'
|
||||
|
||||
export function AviFollowButton({children}: {children: React.ReactNode}) {
|
||||
return children
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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,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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -52,12 +52,12 @@ export function ListsScreen({}: Props) {
|
||||
<View style={s.hContentRegion} testID="listsScreen">
|
||||
<SimpleViewHeader
|
||||
showBackButton={isMobile}
|
||||
style={
|
||||
!isMobile && [
|
||||
pal.border,
|
||||
{borderLeftWidth: hairlineWidth, borderRightWidth: hairlineWidth},
|
||||
]
|
||||
}>
|
||||
style={[
|
||||
pal.border,
|
||||
isMobile
|
||||
? {borderBottomWidth: hairlineWidth}
|
||||
: {borderLeftWidth: hairlineWidth, borderRightWidth: hairlineWidth},
|
||||
]}>
|
||||
<View style={{flex: 1}}>
|
||||
<Text type="title-lg" style={[pal.text, {fontWeight: 'bold'}]}>
|
||||
<Trans>User Lists</Trans>
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
import React from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import Animated from 'react-native-reanimated'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
||||
import {makeRecordUri} from 'lib/strings/url-helpers'
|
||||
import {PostThread as PostThreadComponent} from '../com/post-thread/PostThread'
|
||||
import {ComposePrompt} from 'view/com/composer/Prompt'
|
||||
import {s} from 'lib/styles'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {clamp} from 'lodash'
|
||||
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {
|
||||
RQKEY as POST_THREAD_RQKEY,
|
||||
ThreadNode,
|
||||
} from '#/state/queries/post-thread'
|
||||
import {clamp} from 'lodash'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
|
||||
import {ErrorMessage} from '../com/util/error/ErrorMessage'
|
||||
import {CenteredView} from '../com/util/Views'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {useSession} from '#/state/session'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
||||
import {makeRecordUri} from 'lib/strings/url-helpers'
|
||||
import {s} from 'lib/styles'
|
||||
import {ComposePrompt} from 'view/com/composer/Prompt'
|
||||
import {PostThread as PostThreadComponent} from '../com/post-thread/PostThread'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
|
||||
export function PostThreadScreen({route}: Props) {
|
||||
@@ -35,7 +33,6 @@ export function PostThreadScreen({route}: Props) {
|
||||
const {name, rkey} = route.params
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey)
|
||||
const {data: resolvedUri, error: uriError} = useResolveUriQuery(uri)
|
||||
const [canReply, setCanReply] = React.useState(false)
|
||||
|
||||
useFocusEffect(
|
||||
@@ -45,12 +42,10 @@ export function PostThreadScreen({route}: Props) {
|
||||
)
|
||||
|
||||
const onPressReply = React.useCallback(() => {
|
||||
if (!resolvedUri) {
|
||||
if (!uri) {
|
||||
return
|
||||
}
|
||||
const thread = queryClient.getQueryData<ThreadNode>(
|
||||
POST_THREAD_RQKEY(resolvedUri.uri),
|
||||
)
|
||||
const thread = queryClient.getQueryData<ThreadNode>(POST_THREAD_RQKEY(uri))
|
||||
if (thread?.type !== 'post') {
|
||||
return
|
||||
}
|
||||
@@ -64,25 +59,19 @@ export function PostThreadScreen({route}: Props) {
|
||||
},
|
||||
onPost: () =>
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: POST_THREAD_RQKEY(resolvedUri.uri || ''),
|
||||
queryKey: POST_THREAD_RQKEY(uri),
|
||||
}),
|
||||
})
|
||||
}, [openComposer, queryClient, resolvedUri])
|
||||
}, [openComposer, queryClient, uri])
|
||||
|
||||
return (
|
||||
<View style={s.hContentRegion}>
|
||||
<View style={s.flex1}>
|
||||
{uriError ? (
|
||||
<CenteredView>
|
||||
<ErrorMessage message={String(uriError)} />
|
||||
</CenteredView>
|
||||
) : (
|
||||
<PostThreadComponent
|
||||
uri={resolvedUri?.uri}
|
||||
onPressReply={onPressReply}
|
||||
onCanReply={setCanReply}
|
||||
/>
|
||||
)}
|
||||
<PostThreadComponent
|
||||
uri={uri}
|
||||
onPressReply={onPressReply}
|
||||
onCanReply={setCanReply}
|
||||
/>
|
||||
</View>
|
||||
{isMobile && canReply && hasSession && (
|
||||
<Animated.View
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Image,
|
||||
ImageStyle,
|
||||
Platform,
|
||||
Pressable,
|
||||
StyleProp,
|
||||
StyleSheet,
|
||||
TextInput,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import {ScrollView as RNGHScrollView} from 'react-native-gesture-handler'
|
||||
import {AppBskyActorDefs, AppBskyFeedDefs, moderateProfile} from '@atproto/api'
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
@@ -18,9 +22,11 @@ import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||
import {createHitslop} from '#/lib/constants'
|
||||
import {HITSLOP_10} from '#/lib/constants'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {MagnifyingGlassIcon} from '#/lib/icons'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {augmentSearchQuery} from '#/lib/strings/helpers'
|
||||
import {s} from '#/lib/styles'
|
||||
@@ -46,6 +52,7 @@ import {Pager} from '#/view/com/pager/Pager'
|
||||
import {TabBar} from '#/view/com/pager/TabBar'
|
||||
import {Post} from '#/view/com/post/Post'
|
||||
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
|
||||
import {Link} from '#/view/com/util/Link'
|
||||
import {List} from '#/view/com/util/List'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
||||
@@ -488,6 +495,9 @@ export function SearchScreen(
|
||||
|
||||
const [showAutocomplete, setShowAutocomplete] = React.useState(false)
|
||||
const [searchHistory, setSearchHistory] = React.useState<string[]>([])
|
||||
const [selectedProfiles, setSelectedProfiles] = React.useState<
|
||||
AppBskyActorDefs.ProfileViewBasic[]
|
||||
>([])
|
||||
|
||||
useFocusEffect(
|
||||
useNonReactiveCallback(() => {
|
||||
@@ -504,6 +514,10 @@ export function SearchScreen(
|
||||
if (history !== null) {
|
||||
setSearchHistory(JSON.parse(history))
|
||||
}
|
||||
const profiles = await AsyncStorage.getItem('selectedProfiles')
|
||||
if (profiles !== null) {
|
||||
setSelectedProfiles(JSON.parse(profiles))
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error('Failed to load search history', {message: e})
|
||||
}
|
||||
@@ -562,6 +576,30 @@ export function SearchScreen(
|
||||
[searchHistory, setSearchHistory],
|
||||
)
|
||||
|
||||
const updateSelectedProfiles = React.useCallback(
|
||||
async (profile: AppBskyActorDefs.ProfileViewBasic) => {
|
||||
let newProfiles = [
|
||||
profile,
|
||||
...selectedProfiles.filter(p => p.did !== profile.did),
|
||||
]
|
||||
|
||||
if (newProfiles.length > 5) {
|
||||
newProfiles = newProfiles.slice(0, 5)
|
||||
}
|
||||
|
||||
setSelectedProfiles(newProfiles)
|
||||
try {
|
||||
await AsyncStorage.setItem(
|
||||
'selectedProfiles',
|
||||
JSON.stringify(newProfiles),
|
||||
)
|
||||
} catch (e: any) {
|
||||
logger.error('Failed to save selected profiles', {message: e})
|
||||
}
|
||||
},
|
||||
[selectedProfiles, setSelectedProfiles],
|
||||
)
|
||||
|
||||
const navigateToItem = React.useCallback(
|
||||
(item: string) => {
|
||||
scrollToTopWeb()
|
||||
@@ -598,6 +636,16 @@ export function SearchScreen(
|
||||
[navigateToItem],
|
||||
)
|
||||
|
||||
const handleProfileClick = React.useCallback(
|
||||
(profile: AppBskyActorDefs.ProfileViewBasic) => {
|
||||
// Slight delay to avoid updating during push nav animation.
|
||||
setTimeout(() => {
|
||||
updateSelectedProfiles(profile)
|
||||
}, 400)
|
||||
},
|
||||
[updateSelectedProfiles],
|
||||
)
|
||||
|
||||
const onSoftReset = React.useCallback(() => {
|
||||
if (isWeb) {
|
||||
// Empty params resets the URL to be /search rather than /search?q=
|
||||
@@ -629,6 +677,22 @@ export function SearchScreen(
|
||||
[searchHistory],
|
||||
)
|
||||
|
||||
const handleRemoveProfile = React.useCallback(
|
||||
(profileToRemove: AppBskyActorDefs.ProfileViewBasic) => {
|
||||
const updatedProfiles = selectedProfiles.filter(
|
||||
profile => profile.did !== profileToRemove.did,
|
||||
)
|
||||
setSelectedProfiles(updatedProfiles)
|
||||
AsyncStorage.setItem(
|
||||
'selectedProfiles',
|
||||
JSON.stringify(updatedProfiles),
|
||||
).catch(e => {
|
||||
logger.error('Failed to update selected profiles', {message: e})
|
||||
})
|
||||
},
|
||||
[selectedProfiles],
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={isWeb ? null : {flex: 1}}>
|
||||
<CenteredView
|
||||
@@ -689,12 +753,16 @@ export function SearchScreen(
|
||||
searchText={searchText}
|
||||
onSubmit={onSubmit}
|
||||
onResultPress={onAutocompleteResultPress}
|
||||
onProfileClick={handleProfileClick}
|
||||
/>
|
||||
) : (
|
||||
<SearchHistory
|
||||
searchHistory={searchHistory}
|
||||
selectedProfiles={selectedProfiles}
|
||||
onItemClick={handleHistoryItemClick}
|
||||
onProfileClick={handleProfileClick}
|
||||
onRemoveItemClick={handleRemoveHistoryItem}
|
||||
onRemoveProfileClick={handleRemoveProfile}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
@@ -814,12 +882,14 @@ let AutocompleteResults = ({
|
||||
searchText,
|
||||
onSubmit,
|
||||
onResultPress,
|
||||
onProfileClick,
|
||||
}: {
|
||||
isAutocompleteFetching: boolean
|
||||
autocompleteData: AppBskyActorDefs.ProfileViewBasic[] | undefined
|
||||
searchText: string
|
||||
onSubmit: () => void
|
||||
onResultPress: () => void
|
||||
onProfileClick: (profile: AppBskyActorDefs.ProfileViewBasic) => void
|
||||
}): React.ReactNode => {
|
||||
const moderationOpts = useModerationOpts()
|
||||
const {_} = useLingui()
|
||||
@@ -850,7 +920,10 @@ let AutocompleteResults = ({
|
||||
key={item.did}
|
||||
profile={item}
|
||||
moderation={moderateProfile(item, moderationOpts)}
|
||||
onPress={onResultPress}
|
||||
onPress={() => {
|
||||
onProfileClick(item)
|
||||
onResultPress()
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<View style={{height: 200}} />
|
||||
@@ -861,17 +934,32 @@ let AutocompleteResults = ({
|
||||
}
|
||||
AutocompleteResults = React.memo(AutocompleteResults)
|
||||
|
||||
function truncateText(text: string, maxLength: number) {
|
||||
if (text.length > maxLength) {
|
||||
return text.substring(0, maxLength) + '...'
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
function SearchHistory({
|
||||
searchHistory,
|
||||
selectedProfiles,
|
||||
onItemClick,
|
||||
onProfileClick,
|
||||
onRemoveItemClick,
|
||||
onRemoveProfileClick,
|
||||
}: {
|
||||
searchHistory: string[]
|
||||
selectedProfiles: AppBskyActorDefs.ProfileViewBasic[]
|
||||
onItemClick: (item: string) => void
|
||||
onProfileClick: (profile: AppBskyActorDefs.ProfileViewBasic) => void
|
||||
onRemoveItemClick: (item: string) => void
|
||||
onRemoveProfileClick: (profile: AppBskyActorDefs.ProfileViewBasic) => void
|
||||
}) {
|
||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||
const {isTabletOrDesktop, isMobile} = useWebMediaQueries()
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
|
||||
return (
|
||||
<CenteredView
|
||||
sideBorders={isTabletOrDesktop}
|
||||
@@ -880,12 +968,70 @@ function SearchHistory({
|
||||
height: isWeb ? '100vh' : undefined,
|
||||
}}>
|
||||
<View style={styles.searchHistoryContainer}>
|
||||
{(searchHistory.length > 0 || selectedProfiles.length > 0) && (
|
||||
<Text style={[pal.text, styles.searchHistoryTitle]}>
|
||||
<Trans>Recent Searches</Trans>
|
||||
</Text>
|
||||
)}
|
||||
{selectedProfiles.length > 0 && (
|
||||
<View
|
||||
style={[
|
||||
styles.selectedProfilesContainer,
|
||||
isMobile && styles.selectedProfilesContainerMobile,
|
||||
]}>
|
||||
<RNGHScrollView
|
||||
keyboardShouldPersistTaps="handled"
|
||||
horizontal={true}
|
||||
style={styles.profilesRow}
|
||||
contentContainerStyle={{
|
||||
borderWidth: 0,
|
||||
}}>
|
||||
{selectedProfiles.slice(0, 5).map((profile, index) => (
|
||||
<View
|
||||
key={index}
|
||||
style={[
|
||||
styles.profileItem,
|
||||
isMobile && styles.profileItemMobile,
|
||||
]}>
|
||||
<Link
|
||||
href={makeProfileLink(profile)}
|
||||
title={profile.handle}
|
||||
asAnchor
|
||||
anchorNoUnderline
|
||||
onBeforePress={() => onProfileClick(profile)}
|
||||
style={styles.profilePressable}>
|
||||
<Image
|
||||
source={{uri: profile.avatar}}
|
||||
style={styles.profileAvatar as StyleProp<ImageStyle>}
|
||||
accessibilityIgnoresInvertColors
|
||||
/>
|
||||
<Text style={[pal.text, styles.profileName]}>
|
||||
{truncateText(profile.displayName || '', 12)}
|
||||
</Text>
|
||||
</Link>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Remove profile`)}
|
||||
accessibilityHint={_(
|
||||
msg`Remove profile from search history`,
|
||||
)}
|
||||
onPress={() => onRemoveProfileClick(profile)}
|
||||
hitSlop={createHitslop(6)}
|
||||
style={styles.profileRemoveBtn}>
|
||||
<FontAwesomeIcon
|
||||
icon="xmark"
|
||||
size={14}
|
||||
style={pal.textLight as FontAwesomeIconStyle}
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
))}
|
||||
</RNGHScrollView>
|
||||
</View>
|
||||
)}
|
||||
{searchHistory.length > 0 && (
|
||||
<View style={styles.searchHistoryContent}>
|
||||
<Text style={[pal.text, styles.searchHistoryTitle]}>
|
||||
<Trans>Recent Searches</Trans>
|
||||
</Text>
|
||||
{searchHistory.map((historyItem, index) => (
|
||||
{searchHistory.slice(0, 5).map((historyItem, index) => (
|
||||
<View
|
||||
key={index}
|
||||
style={[
|
||||
@@ -982,11 +1128,57 @@ const styles = StyleSheet.create({
|
||||
width: '100%',
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
selectedProfilesContainer: {
|
||||
marginTop: 10,
|
||||
paddingHorizontal: 12,
|
||||
height: 80,
|
||||
},
|
||||
selectedProfilesContainerMobile: {
|
||||
height: 100,
|
||||
},
|
||||
profilesRow: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'nowrap',
|
||||
},
|
||||
profileItem: {
|
||||
alignItems: 'center',
|
||||
marginRight: 15,
|
||||
width: 78,
|
||||
},
|
||||
profileItemMobile: {
|
||||
width: 70,
|
||||
},
|
||||
profilePressable: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
profileAvatar: {
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderRadius: 45,
|
||||
},
|
||||
profileName: {
|
||||
fontSize: 12,
|
||||
textAlign: 'center',
|
||||
marginTop: 5,
|
||||
},
|
||||
profileRemoveBtn: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 5,
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 10,
|
||||
width: 18,
|
||||
height: 18,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
searchHistoryContent: {
|
||||
padding: 10,
|
||||
paddingHorizontal: 10,
|
||||
borderRadius: 8,
|
||||
},
|
||||
searchHistoryTitle: {
|
||||
fontWeight: 'bold',
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 10,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -60,6 +60,7 @@ import {Text} from 'view/com/util/text/Text'
|
||||
import * as Toast from 'view/com/util/Toast'
|
||||
import {UserAvatar} from 'view/com/util/UserAvatar'
|
||||
import {ScrollView} from 'view/com/util/Views'
|
||||
import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateAccountDialog'
|
||||
import {useTheme} from '#/alf'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
|
||||
@@ -103,10 +104,10 @@ function SettingsAccountCard({
|
||||
/>
|
||||
</View>
|
||||
<View style={[s.flex1]}>
|
||||
<Text type="md-bold" style={pal.text}>
|
||||
<Text type="md-bold" style={pal.text} numberOfLines={1}>
|
||||
{profile?.displayName || account.handle}
|
||||
</Text>
|
||||
<Text type="sm" style={pal.textLight}>
|
||||
<Text type="sm" style={pal.textLight} numberOfLines={1}>
|
||||
{account.handle}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -307,6 +308,11 @@ export function SettingsScreen({}: Props) {
|
||||
Toast.show(_(msg`Legacy storage cleared, you need to restart the app now.`))
|
||||
}, [_])
|
||||
|
||||
const deactivateAccountControl = useDialogControl()
|
||||
const onPressDeactivateAccount = React.useCallback(() => {
|
||||
deactivateAccountControl.open()
|
||||
}, [deactivateAccountControl])
|
||||
|
||||
const {mutate: onPressDeleteChatDeclaration} = useDeleteActorDeclaration()
|
||||
|
||||
return (
|
||||
@@ -381,7 +387,7 @@ export function SettingsScreen({}: Props) {
|
||||
{!currentAccount.emailConfirmed && <EmailConfirmationNotice />}
|
||||
|
||||
<View style={[s.flexRow, styles.heading]}>
|
||||
<Text type="xl-bold" style={pal.text}>
|
||||
<Text type="xl-bold" style={pal.text} numberOfLines={1}>
|
||||
<Trans>Signed in as</Trans>
|
||||
</Text>
|
||||
<View style={s.flex1} />
|
||||
@@ -791,6 +797,29 @@ export function SettingsScreen({}: Props) {
|
||||
<Trans>Export My Data</Trans>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[pal.view, styles.linkCard]}
|
||||
onPress={onPressDeactivateAccount}
|
||||
accessible={true}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Deactivate account`)}
|
||||
accessibilityHint={_(
|
||||
msg`Opens modal for account deactivation confirmation`,
|
||||
)}>
|
||||
<View style={[styles.iconContainer, dangerBg]}>
|
||||
<FontAwesomeIcon
|
||||
icon={'users-slash'}
|
||||
style={dangerText as FontAwesomeIconStyle}
|
||||
size={18}
|
||||
/>
|
||||
</View>
|
||||
<Text type="lg" style={dangerText}>
|
||||
<Trans>Deactivate my account</Trans>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<DeactivateAccountDialog control={deactivateAccountControl} />
|
||||
|
||||
<TouchableOpacity
|
||||
style={[pal.view, styles.linkCard]}
|
||||
onPress={onPressDeleteAccount}
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import React from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import Animated, {FadeIn, FadeInDown, FadeOut} from 'react-native-reanimated'
|
||||
|
||||
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {useComposerState} from 'state/shell/composer'
|
||||
import {
|
||||
EmojiPicker,
|
||||
EmojiPickerState,
|
||||
} from 'view/com/composer/text-input/web/EmojiPicker.web'
|
||||
import {useBreakpoints, useTheme} from '#/alf'
|
||||
import {ComposePost} from '../com/composer/Composer'
|
||||
|
||||
const BOTTOM_BAR_HEIGHT = 61
|
||||
|
||||
export function Composer({}: {winHeight: number}) {
|
||||
const pal = usePalette('default')
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const t = useTheme()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const state = useComposerState()
|
||||
const isActive = !!state
|
||||
useWebBodyScrollLock(isActive)
|
||||
@@ -49,20 +47,13 @@ export function Composer({}: {winHeight: number}) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={styles.mask}
|
||||
aria-modal
|
||||
accessibilityViewIsModal
|
||||
entering={FadeIn.duration(100)}
|
||||
exiting={FadeOut}>
|
||||
<Animated.View
|
||||
entering={FadeInDown.duration(150)}
|
||||
exiting={FadeOut}
|
||||
<View style={styles.mask} aria-modal accessibilityViewIsModal>
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
isMobile && styles.containerMobile,
|
||||
pal.view,
|
||||
pal.border,
|
||||
!gtMobile && styles.containerMobile,
|
||||
t.atoms.bg,
|
||||
t.atoms.border_contrast_medium,
|
||||
]}>
|
||||
<ComposePost
|
||||
replyTo={state.replyTo}
|
||||
@@ -72,9 +63,9 @@ export function Composer({}: {winHeight: number}) {
|
||||
openPicker={onOpenPicker}
|
||||
text={state.text}
|
||||
/>
|
||||
</Animated.View>
|
||||
</View>
|
||||
<EmojiPicker state={pickerState} close={onClosePicker} />
|
||||
</Animated.View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -94,12 +85,12 @@ const styles = StyleSheet.create({
|
||||
maxWidth: 600,
|
||||
width: '100%',
|
||||
paddingVertical: 0,
|
||||
paddingHorizontal: 2,
|
||||
borderRadius: 8,
|
||||
marginBottom: 0,
|
||||
borderWidth: 1,
|
||||
// @ts-ignore web only
|
||||
maxHeight: 'calc(100% - (40px * 2))',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
containerMobile: {
|
||||
borderRadius: 0,
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {Deactivated} from '#/screens/Deactivated'
|
||||
import {Onboarding} from '#/screens/Onboarding'
|
||||
import {SignupQueued} from '#/screens/SignupQueued'
|
||||
import {LoggedOut} from '../com/auth/LoggedOut'
|
||||
import {BottomBarWeb} from './bottom-bar/BottomBarWeb'
|
||||
import {DesktopLeftNav} from './desktop/LeftNav'
|
||||
@@ -102,12 +103,15 @@ function NativeStackNavigator({
|
||||
if ((!PWI_ENABLED || activeRouteRequiresAuth) && !hasSession) {
|
||||
return <LoggedOut />
|
||||
}
|
||||
if (hasSession && currentAccount?.deactivated) {
|
||||
return <Deactivated />
|
||||
if (hasSession && currentAccount?.signupQueued) {
|
||||
return <SignupQueued />
|
||||
}
|
||||
if (showLoggedOut) {
|
||||
return <LoggedOut onDismiss={() => setShowLoggedOut(false)} />
|
||||
}
|
||||
if (currentAccount?.status === 'deactivated') {
|
||||
return <Deactivated />
|
||||
}
|
||||
if (onboardingState.isActive) {
|
||||
return <Onboarding />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user