Merge branch 'bluesky-social:main' into patch-3
This commit is contained in:
commit
3ead08ab26
53 changed files with 1575 additions and 1382 deletions
|
@ -29,6 +29,8 @@ import {
|
|||
} from '#/state/queries/app-passwords'
|
||||
import {ErrorScreen} from '../com/util/error/ErrorScreen'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppPasswords'>
|
||||
export function AppPasswords({}: Props) {
|
||||
|
@ -212,23 +214,18 @@ function AppPassword({
|
|||
}) {
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const {openModal} = useModalControls()
|
||||
const control = useDialogControl()
|
||||
const {contentLanguages} = useLanguagePrefs()
|
||||
const deleteMutation = useAppPasswordDeleteMutation()
|
||||
|
||||
const onDelete = React.useCallback(async () => {
|
||||
openModal({
|
||||
name: 'confirm',
|
||||
title: _(msg`Delete app password`),
|
||||
message: _(
|
||||
msg`Are you sure you want to delete the app password "${name}"?`,
|
||||
),
|
||||
async onPressConfirm() {
|
||||
await deleteMutation.mutateAsync({name})
|
||||
Toast.show(_(msg`App password deleted`))
|
||||
},
|
||||
})
|
||||
}, [deleteMutation, openModal, name, _])
|
||||
await deleteMutation.mutateAsync({name})
|
||||
Toast.show(_(msg`App password deleted`))
|
||||
}, [deleteMutation, name, _])
|
||||
|
||||
const onPress = React.useCallback(() => {
|
||||
control.open()
|
||||
}, [control])
|
||||
|
||||
const primaryLocale =
|
||||
contentLanguages.length > 0 ? contentLanguages[0] : 'en-US'
|
||||
|
@ -237,7 +234,7 @@ function AppPassword({
|
|||
<TouchableOpacity
|
||||
testID={testID}
|
||||
style={[styles.item, pal.border]}
|
||||
onPress={onDelete}
|
||||
onPress={onPress}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Delete app password`)}
|
||||
accessibilityHint="">
|
||||
|
@ -260,6 +257,17 @@ function AppPassword({
|
|||
</Text>
|
||||
</View>
|
||||
<FontAwesomeIcon icon={['far', 'trash-can']} style={styles.trashIcon} />
|
||||
|
||||
<Prompt.Basic
|
||||
control={control}
|
||||
title={_(msg`Delete app password?`)}
|
||||
description={_(
|
||||
msg`Are you sure you want to delete the app password "${name}"?`,
|
||||
)}
|
||||
onConfirm={onDelete}
|
||||
confirmButtonCta={_(msg`Delete`)}
|
||||
confirmButtonColor="negative"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import React, {useMemo, useCallback} from 'react'
|
||||
import {Dimensions, StyleSheet, View} from 'react-native'
|
||||
import {StyleSheet, View, Pressable} from 'react-native'
|
||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
import {useIsFocused, useNavigation} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {HeartIcon, HeartIconSolid} from 'lib/icons'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {CommonNavigatorParams} from 'lib/routes/types'
|
||||
import {makeRecordUri} from 'lib/strings/url-helpers'
|
||||
import {s} from 'lib/styles'
|
||||
|
@ -13,7 +11,7 @@ import {FeedDescriptor} from '#/state/queries/post-feed'
|
|||
import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
|
||||
import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader'
|
||||
import {Feed} from 'view/com/posts/Feed'
|
||||
import {TextLink} from 'view/com/util/Link'
|
||||
import {InlineLink} from '#/components/Link'
|
||||
import {ListRef} from 'view/com/util/List'
|
||||
import {Button} from 'view/com/util/forms/Button'
|
||||
import {Text} from 'view/com/util/text/Text'
|
||||
|
@ -29,15 +27,10 @@ import {shareUrl} from 'lib/sharing'
|
|||
import {toShareUrl} from 'lib/strings/url-helpers'
|
||||
import {Haptics} from 'lib/haptics'
|
||||
import {useAnalytics} from 'lib/analytics/analytics'
|
||||
import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown'
|
||||
import {useScrollHandlers} from '#/lib/ScrollContext'
|
||||
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
|
||||
import {makeCustomFeedLink} from 'lib/routes/links'
|
||||
import {pluralize} from 'lib/strings/helpers'
|
||||
import {CenteredView, ScrollView} from 'view/com/util/Views'
|
||||
import {CenteredView} from 'view/com/util/Views'
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
import {sanitizeHandle} from 'lib/strings/handles'
|
||||
import {makeProfileLink} from 'lib/routes/links'
|
||||
import {ComposeIcon2} from 'lib/icons'
|
||||
import {logger} from '#/logger'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
|
@ -59,9 +52,21 @@ import {useComposerControls} from '#/state/shell/composer'
|
|||
import {truncateAndInvalidate} from '#/state/queries/util'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {listenSoftReset} from '#/state/events'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import * as Menu from '#/components/Menu'
|
||||
import {HITSLOP_20} from '#/lib/constants'
|
||||
import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid'
|
||||
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
|
||||
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
|
||||
import {
|
||||
Heart2_Stroke2_Corner0_Rounded as HeartOutline,
|
||||
Heart2_Filled_Stroke2_Corner0_Rounded as HeartFilled,
|
||||
} from '#/components/icons/Heart2'
|
||||
import {Button as NewButton, ButtonText} from '#/components/Button'
|
||||
|
||||
const SECTION_TITLES = ['Posts', 'About']
|
||||
const SECTION_TITLES = ['Posts']
|
||||
|
||||
interface SectionRef {
|
||||
scrollToTop: () => void
|
||||
|
@ -148,7 +153,7 @@ export function ProfileFeedScreenInner({
|
|||
feedInfo: FeedSourceFeedInfo
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
const {hasSession, currentAccount} = useSession()
|
||||
const {openModal} = useModalControls()
|
||||
const {openComposer} = useComposerControls()
|
||||
|
@ -200,9 +205,11 @@ export function ProfileFeedScreenInner({
|
|||
if (isSaved) {
|
||||
await removeFeed({uri: feedInfo.uri})
|
||||
resetRemoveFeed()
|
||||
Toast.show(_(msg`Removed from your feeds`))
|
||||
} else {
|
||||
await saveFeed({uri: feedInfo.uri})
|
||||
resetSaveFeed()
|
||||
Toast.show(_(msg`Saved to your feeds`))
|
||||
}
|
||||
} catch (err) {
|
||||
Toast.show(
|
||||
|
@ -263,130 +270,132 @@ export function ProfileFeedScreenInner({
|
|||
[feedSectionRef],
|
||||
)
|
||||
|
||||
// render
|
||||
// =
|
||||
|
||||
const dropdownItems: DropdownItem[] = React.useMemo(() => {
|
||||
return [
|
||||
hasSession && {
|
||||
testID: 'feedHeaderDropdownToggleSavedBtn',
|
||||
label: isSaved ? _(msg`Remove from my feeds`) : _(msg`Add to my feeds`),
|
||||
onPress: isSavePending || isRemovePending ? undefined : onToggleSaved,
|
||||
icon: isSaved
|
||||
? {
|
||||
ios: {
|
||||
name: 'trash',
|
||||
},
|
||||
android: 'ic_delete',
|
||||
web: ['far', 'trash-can'],
|
||||
}
|
||||
: {
|
||||
ios: {
|
||||
name: 'plus',
|
||||
},
|
||||
android: '',
|
||||
web: 'plus',
|
||||
},
|
||||
},
|
||||
hasSession && {
|
||||
testID: 'feedHeaderDropdownReportBtn',
|
||||
label: _(msg`Report feed`),
|
||||
onPress: onPressReport,
|
||||
icon: {
|
||||
ios: {
|
||||
name: 'exclamationmark.triangle',
|
||||
},
|
||||
android: 'ic_menu_report_image',
|
||||
web: 'circle-exclamation',
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: 'feedHeaderDropdownShareBtn',
|
||||
label: _(msg`Share feed`),
|
||||
onPress: onPressShare,
|
||||
icon: {
|
||||
ios: {
|
||||
name: 'square.and.arrow.up',
|
||||
},
|
||||
android: 'ic_menu_share',
|
||||
web: 'share',
|
||||
},
|
||||
},
|
||||
].filter(Boolean) as DropdownItem[]
|
||||
}, [
|
||||
hasSession,
|
||||
onToggleSaved,
|
||||
onPressReport,
|
||||
onPressShare,
|
||||
isSaved,
|
||||
isSavePending,
|
||||
isRemovePending,
|
||||
_,
|
||||
])
|
||||
|
||||
const renderHeader = useCallback(() => {
|
||||
return (
|
||||
<ProfileSubpageHeader
|
||||
isLoading={false}
|
||||
href={feedInfo.route.href}
|
||||
title={feedInfo?.displayName}
|
||||
avatar={feedInfo?.avatar}
|
||||
isOwner={feedInfo.creatorDid === currentAccount?.did}
|
||||
creator={
|
||||
feedInfo
|
||||
? {did: feedInfo.creatorDid, handle: feedInfo.creatorHandle}
|
||||
: undefined
|
||||
}
|
||||
avatarType="algo">
|
||||
{feedInfo && hasSession && (
|
||||
<>
|
||||
<Button
|
||||
disabled={isSavePending || isRemovePending}
|
||||
type="default"
|
||||
label={isSaved ? _(msg`Unsave`) : _(msg`Save`)}
|
||||
onPress={onToggleSaved}
|
||||
style={styles.btn}
|
||||
/>
|
||||
<Button
|
||||
testID={isPinned ? 'unpinBtn' : 'pinBtn'}
|
||||
disabled={isPinPending || isUnpinPending}
|
||||
type={isPinned ? 'default' : 'inverted'}
|
||||
label={isPinned ? _(msg`Unpin`) : _(msg`Pin to home`)}
|
||||
onPress={onTogglePinned}
|
||||
style={styles.btn}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<NativeDropdown
|
||||
testID="headerDropdownBtn"
|
||||
items={dropdownItems}
|
||||
accessibilityLabel={_(msg`More options`)}
|
||||
accessibilityHint="">
|
||||
<View style={[pal.viewLight, styles.btn]}>
|
||||
<FontAwesomeIcon
|
||||
icon="ellipsis"
|
||||
size={20}
|
||||
color={pal.colors.text}
|
||||
/>
|
||||
<>
|
||||
<ProfileSubpageHeader
|
||||
isLoading={false}
|
||||
href={feedInfo.route.href}
|
||||
title={feedInfo?.displayName}
|
||||
avatar={feedInfo?.avatar}
|
||||
isOwner={feedInfo.creatorDid === currentAccount?.did}
|
||||
creator={
|
||||
feedInfo
|
||||
? {did: feedInfo.creatorDid, handle: feedInfo.creatorHandle}
|
||||
: undefined
|
||||
}
|
||||
avatarType="algo">
|
||||
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||
{feedInfo && hasSession && (
|
||||
<NewButton
|
||||
testID={isPinned ? 'unpinBtn' : 'pinBtn'}
|
||||
disabled={isPinPending || isUnpinPending}
|
||||
size="small"
|
||||
variant="solid"
|
||||
color={isPinned ? 'secondary' : 'primary'}
|
||||
label={isPinned ? _(msg`Unpin from home`) : _(msg`Pin to home`)}
|
||||
onPress={onTogglePinned}>
|
||||
<ButtonText>
|
||||
{isPinned ? _(msg`Unpin`) : _(msg`Pin to Home`)}
|
||||
</ButtonText>
|
||||
</NewButton>
|
||||
)}
|
||||
<Menu.Root>
|
||||
<Menu.Trigger label={_(msg`Open feed options menu`)}>
|
||||
{({props, state}) => {
|
||||
return (
|
||||
<Pressable
|
||||
{...props}
|
||||
hitSlop={HITSLOP_20}
|
||||
style={[
|
||||
a.justify_center,
|
||||
a.align_center,
|
||||
a.rounded_full,
|
||||
{height: 36, width: 36},
|
||||
t.atoms.bg_contrast_50,
|
||||
(state.hovered || state.pressed) && [
|
||||
t.atoms.bg_contrast_100,
|
||||
],
|
||||
]}
|
||||
testID="headerDropdownBtn">
|
||||
<Ellipsis
|
||||
size="lg"
|
||||
fill={t.atoms.text_contrast_medium.color}
|
||||
/>
|
||||
</Pressable>
|
||||
)
|
||||
}}
|
||||
</Menu.Trigger>
|
||||
|
||||
<Menu.Outer>
|
||||
<Menu.Group>
|
||||
{hasSession && (
|
||||
<>
|
||||
<Menu.Item
|
||||
disabled={isSavePending || isRemovePending}
|
||||
testID="feedHeaderDropdownToggleSavedBtn"
|
||||
label={
|
||||
isSaved
|
||||
? _(msg`Remove from my feeds`)
|
||||
: _(msg`Save to my feeds`)
|
||||
}
|
||||
onPress={onToggleSaved}>
|
||||
<Menu.ItemText>
|
||||
{isSaved
|
||||
? _(msg`Remove from my feeds`)
|
||||
: _(msg`Save to my feeds`)}
|
||||
</Menu.ItemText>
|
||||
<Menu.ItemIcon
|
||||
icon={isSaved ? Trash : Plus}
|
||||
position="right"
|
||||
/>
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Item
|
||||
testID="feedHeaderDropdownReportBtn"
|
||||
label={_(msg`Report feed`)}
|
||||
onPress={onPressReport}>
|
||||
<Menu.ItemText>{_(msg`Report feed`)}</Menu.ItemText>
|
||||
<Menu.ItemIcon icon={CircleInfo} position="right" />
|
||||
</Menu.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Menu.Item
|
||||
testID="feedHeaderDropdownShareBtn"
|
||||
label={_(msg`Share feed`)}
|
||||
onPress={onPressShare}>
|
||||
<Menu.ItemText>{_(msg`Share feed`)}</Menu.ItemText>
|
||||
<Menu.ItemIcon icon={Share} position="right" />
|
||||
</Menu.Item>
|
||||
</Menu.Group>
|
||||
</Menu.Outer>
|
||||
</Menu.Root>
|
||||
</View>
|
||||
</NativeDropdown>
|
||||
</ProfileSubpageHeader>
|
||||
</ProfileSubpageHeader>
|
||||
<AboutSection
|
||||
feedOwnerDid={feedInfo.creatorDid}
|
||||
feedRkey={feedInfo.route.params.rkey}
|
||||
feedInfo={feedInfo}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}, [
|
||||
_,
|
||||
hasSession,
|
||||
pal,
|
||||
feedInfo,
|
||||
isPinned,
|
||||
onTogglePinned,
|
||||
onToggleSaved,
|
||||
dropdownItems,
|
||||
currentAccount?.did,
|
||||
isPinPending,
|
||||
isRemovePending,
|
||||
isSavePending,
|
||||
isSaved,
|
||||
isUnpinPending,
|
||||
onPressReport,
|
||||
onPressShare,
|
||||
t,
|
||||
])
|
||||
|
||||
return (
|
||||
|
@ -405,18 +414,6 @@ export function ProfileFeedScreenInner({
|
|||
isFocused={isScreenFocused && isFocused}
|
||||
/>
|
||||
)}
|
||||
{({headerHeight, scrollElRef}) => (
|
||||
<AboutSection
|
||||
feedOwnerDid={feedInfo.creatorDid}
|
||||
feedRkey={feedInfo.route.params.rkey}
|
||||
feedInfo={feedInfo}
|
||||
headerHeight={headerHeight}
|
||||
scrollElRef={
|
||||
scrollElRef as React.MutableRefObject<ScrollView | null>
|
||||
}
|
||||
isOwner={feedInfo.creatorDid === currentAccount?.did}
|
||||
/>
|
||||
)}
|
||||
</PagerWithHeader>
|
||||
{hasSession && (
|
||||
<FAB
|
||||
|
@ -505,21 +502,14 @@ function AboutSection({
|
|||
feedOwnerDid,
|
||||
feedRkey,
|
||||
feedInfo,
|
||||
headerHeight,
|
||||
scrollElRef,
|
||||
isOwner,
|
||||
}: {
|
||||
feedOwnerDid: string
|
||||
feedRkey: string
|
||||
feedInfo: FeedSourceFeedInfo
|
||||
headerHeight: number
|
||||
scrollElRef: React.MutableRefObject<ScrollView | null>
|
||||
isOwner: boolean
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const scrollHandlers = useScrollHandlers()
|
||||
const onScroll = useAnimatedScrollHandler(scrollHandlers)
|
||||
const [likeUri, setLikeUri] = React.useState(feedInfo.likeUri)
|
||||
const {hasSession} = useSession()
|
||||
const {track} = useAnalytics()
|
||||
|
@ -555,24 +545,8 @@ function AboutSection({
|
|||
}, [likeUri, isLiked, feedInfo, likeFeed, unlikeFeed, track, _])
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
ref={scrollElRef}
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={1}
|
||||
contentContainerStyle={{
|
||||
paddingTop: headerHeight,
|
||||
minHeight: Dimensions.get('window').height * 1.5,
|
||||
}}>
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
borderTopWidth: 1,
|
||||
paddingVertical: 20,
|
||||
paddingHorizontal: 20,
|
||||
gap: 12,
|
||||
},
|
||||
pal.border,
|
||||
]}>
|
||||
<View style={[styles.aboutSectionContainer]}>
|
||||
<View style={[a.pt_sm]}>
|
||||
{feedInfo.description ? (
|
||||
<RichText
|
||||
testID="listDescription"
|
||||
|
@ -584,50 +558,34 @@ function AboutSection({
|
|||
<Trans>No description</Trans>
|
||||
</Text>
|
||||
)}
|
||||
<View style={{flexDirection: 'row', alignItems: 'center', gap: 10}}>
|
||||
<Button
|
||||
type="default"
|
||||
testID="toggleLikeBtn"
|
||||
accessibilityLabel={_(msg`Like this feed`)}
|
||||
accessibilityHint=""
|
||||
disabled={!hasSession || isLikePending || isUnlikePending}
|
||||
onPress={onToggleLiked}
|
||||
style={{paddingHorizontal: 10}}>
|
||||
{isLiked ? (
|
||||
<HeartIconSolid size={19} style={s.likeColor} />
|
||||
) : (
|
||||
<HeartIcon strokeWidth={3} size={19} style={pal.textLight} />
|
||||
)}
|
||||
</Button>
|
||||
{typeof likeCount === 'number' && (
|
||||
<TextLink
|
||||
href={makeCustomFeedLink(feedOwnerDid, feedRkey, 'liked-by')}
|
||||
text={_(
|
||||
msg`Liked by ${likeCount} ${pluralize(likeCount, 'user')}`,
|
||||
)}
|
||||
style={[pal.textLight, s.semiBold]}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<Text type="md" style={[pal.textLight]} numberOfLines={1}>
|
||||
{isOwner ? (
|
||||
<Trans>Created by you</Trans>
|
||||
) : (
|
||||
<Trans>
|
||||
Created by{' '}
|
||||
<TextLink
|
||||
text={sanitizeHandle(feedInfo.creatorHandle, '@')}
|
||||
href={makeProfileLink({
|
||||
did: feedInfo.creatorDid,
|
||||
handle: feedInfo.creatorHandle,
|
||||
})}
|
||||
style={pal.textLight}
|
||||
/>
|
||||
</Trans>
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
<View style={[a.flex_row, a.gap_sm, a.align_center, a.pb_sm]}>
|
||||
<NewButton
|
||||
size="small"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
shape="round"
|
||||
label={isLiked ? _(msg`Unlike this feed`) : _(msg`Like this feed`)}
|
||||
testID="toggleLikeBtn"
|
||||
disabled={!hasSession || isLikePending || isUnlikePending}
|
||||
onPress={onToggleLiked}>
|
||||
{isLiked ? (
|
||||
<HeartFilled size="md" fill={s.likeColor.color} />
|
||||
) : (
|
||||
<HeartOutline size="md" fill={t.atoms.text_contrast_medium.color} />
|
||||
)}
|
||||
</NewButton>
|
||||
{typeof likeCount === 'number' && (
|
||||
<InlineLink
|
||||
label={_(msg`View users who like this feed`)}
|
||||
to={makeCustomFeedLink(feedOwnerDid, feedRkey, 'liked-by')}
|
||||
style={[t.atoms.text_contrast_medium, a.font_bold]}>
|
||||
{_(msg`Liked by ${likeCount} ${pluralize(likeCount, 'user')}`)}
|
||||
</InlineLink>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -647,4 +605,9 @@ const styles = StyleSheet.create({
|
|||
paddingVertical: 14,
|
||||
borderRadius: 6,
|
||||
},
|
||||
aboutSectionContainer: {
|
||||
paddingVertical: 4,
|
||||
paddingHorizontal: 16,
|
||||
gap: 12,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -61,6 +61,8 @@ import {logger} from '#/logger'
|
|||
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||
import {listenSoftReset} from '#/state/events'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
|
||||
const SECTION_TITLES_CURATE = ['Posts', 'About']
|
||||
const SECTION_TITLES_MOD = ['About']
|
||||
|
@ -234,7 +236,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
|||
const {_} = useLingui()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {currentAccount} = useSession()
|
||||
const {openModal, closeModal} = useModalControls()
|
||||
const {openModal} = useModalControls()
|
||||
const listMuteMutation = useListMuteMutation()
|
||||
const listBlockMutation = useListBlockMutation()
|
||||
const listDeleteMutation = useListDeleteMutation()
|
||||
|
@ -251,6 +253,10 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
|||
const {mutate: setSavedFeeds} = useSetSaveFeedsMutation()
|
||||
const {track} = useAnalytics()
|
||||
|
||||
const deleteListPromptControl = useDialogControl()
|
||||
const subscribeMutePromptControl = useDialogControl()
|
||||
const subscribeBlockPromptControl = useDialogControl()
|
||||
|
||||
const isPinned = preferences?.feeds?.pinned?.includes(list.uri)
|
||||
const isSaved = preferences?.feeds?.saved?.includes(list.uri)
|
||||
|
||||
|
@ -269,32 +275,19 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
|||
}
|
||||
}, [list.uri, isPinned, pinFeed, unpinFeed, _])
|
||||
|
||||
const onSubscribeMute = useCallback(() => {
|
||||
openModal({
|
||||
name: 'confirm',
|
||||
title: _(msg`Mute these accounts?`),
|
||||
message: _(
|
||||
msg`Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them.`,
|
||||
),
|
||||
confirmBtnText: _(msg`Mute this List`),
|
||||
async onPressConfirm() {
|
||||
try {
|
||||
await listMuteMutation.mutateAsync({uri: list.uri, mute: true})
|
||||
Toast.show(_(msg`List muted`))
|
||||
track('Lists:Mute')
|
||||
} catch {
|
||||
Toast.show(
|
||||
_(
|
||||
msg`There was an issue. Please check your internet connection and try again.`,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
onPressCancel() {
|
||||
closeModal()
|
||||
},
|
||||
})
|
||||
}, [openModal, closeModal, list, listMuteMutation, track, _])
|
||||
const onSubscribeMute = useCallback(async () => {
|
||||
try {
|
||||
await listMuteMutation.mutateAsync({uri: list.uri, mute: true})
|
||||
Toast.show(_(msg`List muted`))
|
||||
track('Lists:Mute')
|
||||
} catch {
|
||||
Toast.show(
|
||||
_(
|
||||
msg`There was an issue. Please check your internet connection and try again.`,
|
||||
),
|
||||
)
|
||||
}
|
||||
}, [list, listMuteMutation, track, _])
|
||||
|
||||
const onUnsubscribeMute = useCallback(async () => {
|
||||
try {
|
||||
|
@ -310,32 +303,19 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
|||
}
|
||||
}, [list, listMuteMutation, track, _])
|
||||
|
||||
const onSubscribeBlock = useCallback(() => {
|
||||
openModal({
|
||||
name: 'confirm',
|
||||
title: _(msg`Block these accounts?`),
|
||||
message: _(
|
||||
msg`Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`,
|
||||
),
|
||||
confirmBtnText: _(msg`Block this List`),
|
||||
async onPressConfirm() {
|
||||
try {
|
||||
await listBlockMutation.mutateAsync({uri: list.uri, block: true})
|
||||
Toast.show(_(msg`List blocked`))
|
||||
track('Lists:Block')
|
||||
} catch {
|
||||
Toast.show(
|
||||
_(
|
||||
msg`There was an issue. Please check your internet connection and try again.`,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
onPressCancel() {
|
||||
closeModal()
|
||||
},
|
||||
})
|
||||
}, [openModal, closeModal, list, listBlockMutation, track, _])
|
||||
const onSubscribeBlock = useCallback(async () => {
|
||||
try {
|
||||
await listBlockMutation.mutateAsync({uri: list.uri, block: true})
|
||||
Toast.show(_(msg`List blocked`))
|
||||
track('Lists:Block')
|
||||
} catch {
|
||||
Toast.show(
|
||||
_(
|
||||
msg`There was an issue. Please check your internet connection and try again.`,
|
||||
),
|
||||
)
|
||||
}
|
||||
}, [list, listBlockMutation, track, _])
|
||||
|
||||
const onUnsubscribeBlock = useCallback(async () => {
|
||||
try {
|
||||
|
@ -358,34 +338,26 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
|||
})
|
||||
}, [openModal, list])
|
||||
|
||||
const onPressDelete = useCallback(() => {
|
||||
openModal({
|
||||
name: 'confirm',
|
||||
title: _(msg`Delete List`),
|
||||
message: _(msg`Are you sure?`),
|
||||
async onPressConfirm() {
|
||||
await listDeleteMutation.mutateAsync({uri: list.uri})
|
||||
const onPressDelete = useCallback(async () => {
|
||||
await listDeleteMutation.mutateAsync({uri: list.uri})
|
||||
|
||||
if (isSaved || isPinned) {
|
||||
const {saved, pinned} = preferences!.feeds
|
||||
if (isSaved || isPinned) {
|
||||
const {saved, pinned} = preferences!.feeds
|
||||
|
||||
setSavedFeeds({
|
||||
saved: isSaved ? saved.filter(uri => uri !== list.uri) : saved,
|
||||
pinned: isPinned ? pinned.filter(uri => uri !== list.uri) : pinned,
|
||||
})
|
||||
}
|
||||
setSavedFeeds({
|
||||
saved: isSaved ? saved.filter(uri => uri !== list.uri) : saved,
|
||||
pinned: isPinned ? pinned.filter(uri => uri !== list.uri) : pinned,
|
||||
})
|
||||
}
|
||||
|
||||
Toast.show(_(msg`List deleted`))
|
||||
track('Lists:Delete')
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack()
|
||||
} else {
|
||||
navigation.navigate('Home')
|
||||
}
|
||||
},
|
||||
})
|
||||
Toast.show(_(msg`List deleted`))
|
||||
track('Lists:Delete')
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack()
|
||||
} else {
|
||||
navigation.navigate('Home')
|
||||
}
|
||||
}, [
|
||||
openModal,
|
||||
list,
|
||||
listDeleteMutation,
|
||||
navigation,
|
||||
|
@ -443,7 +415,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
|||
items.push({
|
||||
testID: 'listHeaderDropdownDeleteBtn',
|
||||
label: _(msg`Delete List`),
|
||||
onPress: onPressDelete,
|
||||
onPress: deleteListPromptControl.open,
|
||||
icon: {
|
||||
ios: {
|
||||
name: 'trash',
|
||||
|
@ -489,7 +461,9 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
|||
items.push({
|
||||
testID: 'listHeaderDropdownMuteBtn',
|
||||
label: isMuting ? _(msg`Un-mute list`) : _(msg`Mute list`),
|
||||
onPress: isMuting ? onUnsubscribeMute : onSubscribeMute,
|
||||
onPress: isMuting
|
||||
? onUnsubscribeMute
|
||||
: subscribeMutePromptControl.open,
|
||||
icon: {
|
||||
ios: {
|
||||
name: isMuting ? 'eye' : 'eye.slash',
|
||||
|
@ -504,7 +478,9 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
|||
items.push({
|
||||
testID: 'listHeaderDropdownBlockBtn',
|
||||
label: isBlocking ? _(msg`Un-block list`) : _(msg`Block list`),
|
||||
onPress: isBlocking ? onUnsubscribeBlock : onSubscribeBlock,
|
||||
onPress: isBlocking
|
||||
? onUnsubscribeBlock
|
||||
: subscribeBlockPromptControl.open,
|
||||
icon: {
|
||||
ios: {
|
||||
name: 'person.fill.xmark',
|
||||
|
@ -517,24 +493,24 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
|||
}
|
||||
return items
|
||||
}, [
|
||||
isOwner,
|
||||
onPressShare,
|
||||
onPressEdit,
|
||||
onPressDelete,
|
||||
onPressReport,
|
||||
_,
|
||||
onPressShare,
|
||||
isOwner,
|
||||
isModList,
|
||||
isPinned,
|
||||
unpinFeed,
|
||||
isPending,
|
||||
list.uri,
|
||||
isCurateList,
|
||||
isMuting,
|
||||
onPressEdit,
|
||||
deleteListPromptControl.open,
|
||||
onPressReport,
|
||||
isPending,
|
||||
unpinFeed,
|
||||
list.uri,
|
||||
isBlocking,
|
||||
isMuting,
|
||||
onUnsubscribeMute,
|
||||
onSubscribeMute,
|
||||
subscribeMutePromptControl.open,
|
||||
onUnsubscribeBlock,
|
||||
onSubscribeBlock,
|
||||
subscribeBlockPromptControl.open,
|
||||
])
|
||||
|
||||
const subscribeDropdownItems: DropdownItem[] = useMemo(() => {
|
||||
|
@ -542,7 +518,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
|||
{
|
||||
testID: 'subscribeDropdownMuteBtn',
|
||||
label: _(msg`Mute accounts`),
|
||||
onPress: onSubscribeMute,
|
||||
onPress: subscribeMutePromptControl.open,
|
||||
icon: {
|
||||
ios: {
|
||||
name: 'speaker.slash',
|
||||
|
@ -554,7 +530,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
|||
{
|
||||
testID: 'subscribeDropdownBlockBtn',
|
||||
label: _(msg`Block accounts`),
|
||||
onPress: onSubscribeBlock,
|
||||
onPress: subscribeBlockPromptControl.open,
|
||||
icon: {
|
||||
ios: {
|
||||
name: 'person.fill.xmark',
|
||||
|
@ -564,7 +540,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
|||
},
|
||||
},
|
||||
]
|
||||
}, [onSubscribeMute, onSubscribeBlock, _])
|
||||
}, [_, subscribeMutePromptControl.open, subscribeBlockPromptControl.open])
|
||||
|
||||
return (
|
||||
<ProfileSubpageHeader
|
||||
|
@ -620,6 +596,38 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
|||
<FontAwesomeIcon icon="ellipsis" size={20} color={pal.colors.text} />
|
||||
</View>
|
||||
</NativeDropdown>
|
||||
|
||||
<Prompt.Basic
|
||||
control={deleteListPromptControl}
|
||||
title={_(msg`Delete this list?`)}
|
||||
description={_(
|
||||
msg`If you delete this list, you won't be able to recover it.`,
|
||||
)}
|
||||
onConfirm={onPressDelete}
|
||||
confirmButtonCta={_(msg`Delete`)}
|
||||
confirmButtonColor="negative"
|
||||
/>
|
||||
|
||||
<Prompt.Basic
|
||||
control={subscribeMutePromptControl}
|
||||
title={_(msg`Mute these accounts?`)}
|
||||
description={_(
|
||||
msg`Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them.`,
|
||||
)}
|
||||
onConfirm={onSubscribeMute}
|
||||
confirmButtonCta={_(msg`Mute list`)}
|
||||
/>
|
||||
|
||||
<Prompt.Basic
|
||||
control={subscribeBlockPromptControl}
|
||||
title={_(msg`Block these accounts?`)}
|
||||
description={_(
|
||||
msg`Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`,
|
||||
)}
|
||||
onConfirm={onSubscribeBlock}
|
||||
confirmButtonCta={_(msg`Block list`)}
|
||||
confirmButtonColor="negative"
|
||||
/>
|
||||
</ProfileSubpageHeader>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ export function Dialogs() {
|
|||
</Prompt.Description>
|
||||
<Prompt.Actions>
|
||||
<Prompt.Cancel>Cancel</Prompt.Cancel>
|
||||
<Prompt.Action>Confirm</Prompt.Action>
|
||||
<Prompt.Action onPress={() => {}}>Confirm</Prompt.Action>
|
||||
</Prompt.Actions>
|
||||
</Prompt.Outer>
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ export function Menus() {
|
|||
<View style={[a.gap_md]}>
|
||||
<View style={[a.flex_row, a.align_start]}>
|
||||
<Menu.Root control={menuControl}>
|
||||
<Menu.Trigger label="Open basic menu" style={[a.flex_1]}>
|
||||
<Menu.Trigger label="Open basic menu">
|
||||
{({state, props}) => {
|
||||
return (
|
||||
<Text
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue