diff options
Diffstat (limited to 'src/view/com')
-rw-r--r-- | src/view/com/composer/Composer.tsx | 43 | ||||
-rw-r--r-- | src/view/com/feeds/FeedSourceCard.tsx | 254 | ||||
-rw-r--r-- | src/view/com/modals/Confirm.tsx | 132 | ||||
-rw-r--r-- | src/view/com/modals/Modal.tsx | 6 | ||||
-rw-r--r-- | src/view/com/modals/Modal.web.tsx | 5 | ||||
-rw-r--r-- | src/view/com/posts/FeedErrorMessage.tsx | 89 | ||||
-rw-r--r-- | src/view/com/profile/ProfileHeader.tsx | 47 | ||||
-rw-r--r-- | src/view/com/profile/ProfileMenu.tsx | 86 | ||||
-rw-r--r-- | src/view/com/util/forms/PostDropdownBtn.tsx | 43 |
9 files changed, 300 insertions, 405 deletions
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 2855d4232..ef965b271 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -49,7 +49,7 @@ import {SuggestedLanguage} from './select-language/SuggestedLanguage' import {insertMentionAt} from 'lib/strings/mention-manip' import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useModals, useModalControls} from '#/state/modals' +import {useModals} from '#/state/modals' import {useRequireAltTextEnabled} from '#/state/preferences' import { useLanguagePrefs, @@ -63,6 +63,8 @@ import {emitPostCreated} from '#/state/events' import {ThreadgateSetting} from '#/state/queries/threadgate' import {logger} from '#/logger' import {ComposerReplyTo} from 'view/com/composer/ComposerReplyTo' +import * as Prompt from '#/components/Prompt' +import {useDialogStateControlContext} from 'state/dialogs' type Props = ComposerOpts export const ComposePost = observer(function ComposePost({ @@ -76,8 +78,7 @@ export const ComposePost = observer(function ComposePost({ }: Props) { const {currentAccount} = useSession() const {data: currentProfile} = useProfileQuery({did: currentAccount!.did}) - const {isModalActive, activeModals} = useModals() - const {openModal, closeModal} = useModalControls() + const {isModalActive} = useModals() const {closeComposer} = useComposerControls() const {track} = useAnalytics() const pal = usePalette('default') @@ -87,6 +88,9 @@ export const ComposePost = observer(function ComposePost({ const langPrefs = useLanguagePrefs() const setLangPrefs = useLanguagePrefsApi() const textInput = useRef<TextInputRef>(null) + const discardPromptControl = Prompt.usePromptControl() + const {closeAllDialogs} = useDialogStateControlContext() + const [isKeyboardVisible] = useIsKeyboardVisible({iosUseWillEvents: true}) const [isProcessing, setIsProcessing] = useState(false) const [processingState, setProcessingState] = useState('') @@ -134,27 +138,21 @@ export const ComposePost = observer(function ComposePost({ const onPressCancel = useCallback(() => { if (graphemeLength > 0 || !gallery.isEmpty) { - if (activeModals.some(modal => modal.name === 'confirm')) { - closeModal() - } + closeAllDialogs() if (Keyboard) { Keyboard.dismiss() } - openModal({ - name: 'confirm', - title: _(msg`Discard draft`), - onPressConfirm: onClose, - onPressCancel: () => { - closeModal() - }, - message: _(msg`Are you sure you'd like to discard this draft?`), - confirmBtnText: _(msg`Discard`), - confirmBtnStyle: {backgroundColor: colors.red4}, - }) + discardPromptControl.open() } else { onClose() } - }, [openModal, closeModal, activeModals, onClose, graphemeLength, gallery, _]) + }, [ + graphemeLength, + gallery.isEmpty, + closeAllDialogs, + discardPromptControl, + onClose, + ]) // android back button useEffect(() => { if (!isAndroid) { @@ -488,6 +486,15 @@ export const ComposePost = observer(function ComposePost({ <CharProgress count={graphemeLength} /> </View> </View> + + <Prompt.Basic + control={discardPromptControl} + title={_(msg`Discard draft?`)} + description={_(msg`Are you sure you'd like to discard this draft?`)} + onConfirm={onClose} + confirmButtonCta={_(msg`Discard`)} + confirmButtonColor="negative" + /> </KeyboardAvoidingView> ) }) diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx index 4af62f6f6..9300b4159 100644 --- a/src/view/com/feeds/FeedSourceCard.tsx +++ b/src/view/com/feeds/FeedSourceCard.tsx @@ -11,7 +11,6 @@ import {AtUri} from '@atproto/api' import * as Toast from 'view/com/util/Toast' import {sanitizeHandle} from 'lib/strings/handles' import {logger} from '#/logger' -import {useModalControls} from '#/state/modals' import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import { @@ -24,6 +23,7 @@ import { import {useFeedSourceInfoQuery, FeedSourceInfo} from '#/state/queries/feed' import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {useTheme} from '#/alf' +import * as Prompt from '#/components/Prompt' import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped' export function FeedSourceCard({ @@ -85,8 +85,8 @@ export function FeedSourceCardLoaded({ const t = useTheme() const pal = usePalette('default') const {_} = useLingui() + const removePromptControl = Prompt.usePromptControl() const navigation = useNavigationDeduped() - const {openModal} = useModalControls() const {isPending: isSavePending, mutateAsync: saveFeed} = useSaveFeedMutation() @@ -96,40 +96,45 @@ export function FeedSourceCardLoaded({ const isSaved = Boolean(preferences?.feeds?.saved?.includes(feed?.uri || '')) + const onSave = React.useCallback(async () => { + if (!feed) return + + try { + if (pinOnSave) { + await pinFeed({uri: feed.uri}) + } else { + await saveFeed({uri: feed.uri}) + } + Toast.show(_(msg`Added to my feeds`)) + } catch (e) { + Toast.show(_(msg`There was an issue contacting your server`)) + logger.error('Failed to save feed', {message: e}) + } + }, [_, feed, pinFeed, pinOnSave, saveFeed]) + + const onUnsave = React.useCallback(async () => { + if (!feed) return + + try { + await removeFeed({uri: feed.uri}) + // await item.unsave() + Toast.show(_(msg`Removed from my feeds`)) + } catch (e) { + Toast.show(_(msg`There was an issue contacting your server`)) + logger.error('Failed to unsave feed', {message: e}) + } + }, [_, feed, removeFeed]) + const onToggleSaved = React.useCallback(async () => { // Only feeds can be un/saved, lists are handled elsewhere if (feed?.type !== 'feed') return if (isSaved) { - openModal({ - name: 'confirm', - title: _(msg`Remove from my feeds`), - message: _(msg`Remove ${feed?.displayName} from my feeds?`), - onPressConfirm: async () => { - try { - await removeFeed({uri: feed.uri}) - // await item.unsave() - Toast.show(_(msg`Removed from my feeds`)) - } catch (e) { - Toast.show(_(msg`There was an issue contacting your server`)) - logger.error('Failed to unsave feed', {message: e}) - } - }, - }) + removePromptControl.open() } else { - try { - if (pinOnSave) { - await pinFeed({uri: feed.uri}) - } else { - await saveFeed({uri: feed.uri}) - } - Toast.show(_(msg`Added to my feeds`)) - } catch (e) { - Toast.show(_(msg`There was an issue contacting your server`)) - logger.error('Failed to save feed', {message: e}) - } + await onSave() } - }, [isSaved, openModal, feed, removeFeed, saveFeed, _, pinOnSave, pinFeed]) + }, [feed?.type, isSaved, removePromptControl, onSave]) /* * LOAD STATE @@ -167,25 +172,7 @@ export function FeedSourceCardLoaded({ accessibilityRole="button" accessibilityLabel={_(msg`Remove from my feeds`)} accessibilityHint="" - onPress={() => { - openModal({ - name: 'confirm', - title: _(msg`Remove from my feeds`), - message: _(msg`Remove this feed from my feeds?`), - onPressConfirm: async () => { - try { - await removeFeed({uri: feedUri}) - // await item.unsave() - Toast.show(_(msg`Removed from my feeds`)) - } catch (e) { - Toast.show( - _(msg`There was an issue contacting your server`), - ) - logger.error('Failed to unsave feed', {message: e}) - } - }, - }) - }} + onPress={onToggleSaved} hitSlop={15} style={styles.btn}> <FontAwesomeIcon @@ -199,89 +186,104 @@ export function FeedSourceCardLoaded({ ) return ( - <Pressable - testID={`feed-${feed.displayName}`} - accessibilityRole="button" - style={[styles.container, pal.border, style]} - onPress={() => { - if (feed.type === 'feed') { - navigation.push('ProfileFeed', { - name: feed.creatorDid, - rkey: new AtUri(feed.uri).rkey, - }) - } else if (feed.type === 'list') { - navigation.push('ProfileList', { - name: feed.creatorDid, - rkey: new AtUri(feed.uri).rkey, - }) - } - }} - key={feed.uri}> - <View style={[styles.headerContainer]}> - <View style={[s.mr10]}> - <UserAvatar type="algo" size={36} avatar={feed.avatar} /> - </View> - <View style={[styles.headerTextContainer]}> - <Text style={[pal.text, s.bold]} numberOfLines={3}> - {feed.displayName} - </Text> - <Text style={[pal.textLight]} numberOfLines={3}> - {feed.type === 'feed' ? ( - <Trans>Feed by {sanitizeHandle(feed.creatorHandle, '@')}</Trans> - ) : ( - <Trans>List by {sanitizeHandle(feed.creatorHandle, '@')}</Trans> - )} - </Text> - </View> - - {showSaveBtn && feed.type === 'feed' && ( - <View style={[s.justifyCenter]}> - <Pressable - testID={`feed-${feed.displayName}-toggleSave`} - disabled={isSavePending || isPinPending || isRemovePending} - accessibilityRole="button" - accessibilityLabel={ - isSaved ? _(msg`Remove from my feeds`) : _(msg`Add to my feeds`) - } - accessibilityHint="" - onPress={onToggleSaved} - hitSlop={15} - style={styles.btn}> - {isSaved ? ( - <FontAwesomeIcon - icon={['far', 'trash-can']} - size={19} - color={pal.colors.icon} - /> + <> + <Pressable + testID={`feed-${feed.displayName}`} + accessibilityRole="button" + style={[styles.container, pal.border, style]} + onPress={() => { + if (feed.type === 'feed') { + navigation.push('ProfileFeed', { + name: feed.creatorDid, + rkey: new AtUri(feed.uri).rkey, + }) + } else if (feed.type === 'list') { + navigation.push('ProfileList', { + name: feed.creatorDid, + rkey: new AtUri(feed.uri).rkey, + }) + } + }} + key={feed.uri}> + <View style={[styles.headerContainer]}> + <View style={[s.mr10]}> + <UserAvatar type="algo" size={36} avatar={feed.avatar} /> + </View> + <View style={[styles.headerTextContainer]}> + <Text style={[pal.text, s.bold]} numberOfLines={3}> + {feed.displayName} + </Text> + <Text style={[pal.textLight]} numberOfLines={3}> + {feed.type === 'feed' ? ( + <Trans>Feed by {sanitizeHandle(feed.creatorHandle, '@')}</Trans> ) : ( - <FontAwesomeIcon - icon="plus" - size={18} - color={pal.colors.link} - /> + <Trans>List by {sanitizeHandle(feed.creatorHandle, '@')}</Trans> )} - </Pressable> + </Text> </View> - )} - </View> - {showDescription && feed.description ? ( - <RichText - style={[t.atoms.text_contrast_high, styles.description]} - value={feed.description} - numberOfLines={3} - /> - ) : null} + {showSaveBtn && feed.type === 'feed' && ( + <View style={[s.justifyCenter]}> + <Pressable + testID={`feed-${feed.displayName}-toggleSave`} + disabled={isSavePending || isPinPending || isRemovePending} + accessibilityRole="button" + accessibilityLabel={ + isSaved + ? _(msg`Remove from my feeds`) + : _(msg`Add to my feeds`) + } + accessibilityHint="" + onPress={onToggleSaved} + hitSlop={15} + style={styles.btn}> + {isSaved ? ( + <FontAwesomeIcon + icon={['far', 'trash-can']} + size={19} + color={pal.colors.icon} + /> + ) : ( + <FontAwesomeIcon + icon="plus" + size={18} + color={pal.colors.link} + /> + )} + </Pressable> + </View> + )} + </View> + + {showDescription && feed.description ? ( + <RichText + style={[t.atoms.text_contrast_high, styles.description]} + value={feed.description} + numberOfLines={3} + /> + ) : null} - {showLikes && feed.type === 'feed' ? ( - <Text type="sm-medium" style={[pal.text, pal.textLight]}> - <Trans> - Liked by {feed.likeCount || 0}{' '} - {pluralize(feed.likeCount || 0, 'user')} - </Trans> - </Text> - ) : null} - </Pressable> + {showLikes && feed.type === 'feed' ? ( + <Text type="sm-medium" style={[pal.text, pal.textLight]}> + <Trans> + Liked by {feed.likeCount || 0}{' '} + {pluralize(feed.likeCount || 0, 'user')} + </Trans> + </Text> + ) : null} + </Pressable> + + <Prompt.Basic + control={removePromptControl} + title={_(msg`Remove from my feeds?`)} + description={_( + msg`Are you sure you want to remove ${feed.displayName} from your feeds?`, + )} + onConfirm={onUnsave} + confirmButtonCta={_(msg`Remove`)} + confirmButtonColor="negative" + /> + </> ) } diff --git a/src/view/com/modals/Confirm.tsx b/src/view/com/modals/Confirm.tsx deleted file mode 100644 index 307897fb8..000000000 --- a/src/view/com/modals/Confirm.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import React, {useState} from 'react' -import { - ActivityIndicator, - StyleSheet, - TouchableOpacity, - View, -} from 'react-native' -import {Text} from '../util/text/Text' -import {s, colors} from 'lib/styles' -import {ErrorMessage} from '../util/error/ErrorMessage' -import {cleanError} from 'lib/strings/errors' -import {usePalette} from 'lib/hooks/usePalette' -import {isWeb} from 'platform/detection' -import {useLingui} from '@lingui/react' -import {Trans, msg} from '@lingui/macro' -import type {ConfirmModal} from '#/state/modals' -import {useModalControls} from '#/state/modals' - -export const snapPoints = ['50%'] - -export function Component({ - title, - message, - onPressConfirm, - onPressCancel, - confirmBtnText, - confirmBtnStyle, - cancelBtnText, -}: ConfirmModal) { - const pal = usePalette('default') - const {_} = useLingui() - const {closeModal} = useModalControls() - const [isProcessing, setIsProcessing] = useState<boolean>(false) - const [error, setError] = useState<string>('') - const onPress = async () => { - setError('') - setIsProcessing(true) - try { - await onPressConfirm() - closeModal() - return - } catch (e: any) { - setError(cleanError(e)) - setIsProcessing(false) - } - } - return ( - <View testID="confirmModal" style={[pal.view, styles.container]}> - <Text type="title-xl" style={[pal.text, styles.title]}> - {title} - </Text> - {typeof message === 'string' ? ( - <Text type="xl" style={[pal.textLight, styles.description]}> - {message} - </Text> - ) : ( - message() - )} - {error ? ( - <View style={s.mt10}> - <ErrorMessage message={error} /> - </View> - ) : undefined} - <View style={s.flex1} /> - {isProcessing ? ( - <View style={[styles.btn, s.mt10]}> - <ActivityIndicator /> - </View> - ) : ( - <TouchableOpacity - testID="confirmBtn" - onPress={onPress} - style={[styles.btn, confirmBtnStyle]} - accessibilityRole="button" - accessibilityLabel={_(msg({message: 'Confirm', context: 'action'}))} - accessibilityHint=""> - <Text style={[s.white, s.bold, s.f18]}> - {confirmBtnText ?? <Trans context="action">Confirm</Trans>} - </Text> - </TouchableOpacity> - )} - {onPressCancel === undefined ? null : ( - <TouchableOpacity - testID="cancelBtn" - onPress={onPressCancel} - style={[styles.btnCancel, s.mt10]} - accessibilityRole="button" - accessibilityLabel={_(msg({message: 'Cancel', context: 'action'}))} - accessibilityHint=""> - <Text type="button-lg" style={pal.textLight}> - {cancelBtnText ?? <Trans context="action">Cancel</Trans>} - </Text> - </TouchableOpacity> - )} - </View> - ) -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - padding: 10, - paddingBottom: isWeb ? 0 : 60, - }, - title: { - textAlign: 'center', - marginBottom: 12, - }, - description: { - textAlign: 'center', - paddingHorizontal: 22, - marginBottom: 10, - }, - btn: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - borderRadius: 32, - padding: 14, - marginTop: 22, - marginHorizontal: 44, - backgroundColor: colors.blue3, - }, - btnCancel: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - borderRadius: 32, - padding: 14, - marginHorizontal: 20, - }, -}) diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index 100444ff5..e03879c1d 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -6,7 +6,6 @@ import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop' import {usePalette} from 'lib/hooks/usePalette' import {useModals, useModalControls} from '#/state/modals' -import * as ConfirmModal from './Confirm' import * as EditProfileModal from './EditProfile' import * as RepostModal from './Repost' import * as SelfLabelModal from './SelfLabel' @@ -66,10 +65,7 @@ export function ModalsContainer() { let snapPoints: (string | number)[] = DEFAULT_SNAPPOINTS let element - if (activeModal?.name === 'confirm') { - snapPoints = ConfirmModal.snapPoints - element = <ConfirmModal.Component {...activeModal} /> - } else if (activeModal?.name === 'edit-profile') { + if (activeModal?.name === 'edit-profile') { snapPoints = EditProfileModal.snapPoints element = <EditProfileModal.Component {...activeModal} /> } else if (activeModal?.name === 'report') { diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx index 0ced894a1..d72b7e485 100644 --- a/src/view/com/modals/Modal.web.tsx +++ b/src/view/com/modals/Modal.web.tsx @@ -7,7 +7,6 @@ import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock' import {useModals, useModalControls} from '#/state/modals' import type {Modal as ModalIface} from '#/state/modals' -import * as ConfirmModal from './Confirm' import * as EditProfileModal from './EditProfile' import * as ReportModal from './report/Modal' import * as AppealLabelModal from './AppealLabel' @@ -78,9 +77,7 @@ function Modal({modal}: {modal: ModalIface}) { } let element - if (modal.name === 'confirm') { - element = <ConfirmModal.Component {...modal} /> - } else if (modal.name === 'edit-profile') { + if (modal.name === 'edit-profile') { element = <EditProfileModal.Component {...modal} /> } else if (modal.name === 'report') { element = <ReportModal.Component {...modal} /> diff --git a/src/view/com/posts/FeedErrorMessage.tsx b/src/view/com/posts/FeedErrorMessage.tsx index 6d99c32f1..c52090f97 100644 --- a/src/view/com/posts/FeedErrorMessage.tsx +++ b/src/view/com/posts/FeedErrorMessage.tsx @@ -9,13 +9,13 @@ import {usePalette} from 'lib/hooks/usePalette' import {useNavigation} from '@react-navigation/native' import {NavigationProp} from 'lib/routes/types' import {logger} from '#/logger' -import {useModalControls} from '#/state/modals' import {msg as msgLingui, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {FeedDescriptor} from '#/state/queries/post-feed' import {EmptyState} from '../util/EmptyState' import {cleanError} from '#/lib/strings/errors' import {useRemoveFeedMutation} from '#/state/queries/preferences' +import * as Prompt from '#/components/Prompt' export enum KnownError { Block = 'Block', @@ -118,35 +118,29 @@ function FeedgenErrorMessage({ ) const [_, uri] = feedDesc.split('|') const [ownerDid] = safeParseFeedgenUri(uri) - const {openModal, closeModal} = useModalControls() + const removePromptControl = Prompt.usePromptControl() const {mutateAsync: removeFeed} = useRemoveFeedMutation() const onViewProfile = React.useCallback(() => { navigation.navigate('Profile', {name: ownerDid}) }, [navigation, ownerDid]) + const onPressRemoveFeed = React.useCallback(() => { + removePromptControl.open() + }, [removePromptControl]) + const onRemoveFeed = React.useCallback(async () => { - openModal({ - name: 'confirm', - title: _l(msgLingui`Remove feed`), - message: _l(msgLingui`Remove this feed from your saved feeds?`), - async onPressConfirm() { - try { - await removeFeed({uri}) - } catch (err) { - Toast.show( - _l( - msgLingui`There was an an issue removing this feed. Please check your internet connection and try again.`, - ), - ) - logger.error('Failed to remove feed', {message: err}) - } - }, - onPressCancel() { - closeModal() - }, - }) - }, [openModal, closeModal, uri, removeFeed, _l]) + try { + await removeFeed({uri}) + } catch (err) { + Toast.show( + _l( + msgLingui`There was an an issue removing this feed. Please check your internet connection and try again.`, + ), + ) + logger.error('Failed to remove feed', {message: err}) + } + }, [uri, removeFeed, _l]) const cta = React.useMemo(() => { switch (knownError) { @@ -179,27 +173,38 @@ function FeedgenErrorMessage({ }, [knownError, onViewProfile, onRemoveFeed, _l]) return ( - <View - style={[ - pal.border, - pal.viewLight, - { - borderTopWidth: 1, - paddingHorizontal: 20, - paddingVertical: 18, - gap: 12, - }, - ]}> - <Text style={pal.text}>{msg}</Text> + <> + <View + style={[ + pal.border, + pal.viewLight, + { + borderTopWidth: 1, + paddingHorizontal: 20, + paddingVertical: 18, + gap: 12, + }, + ]}> + <Text style={pal.text}>{msg}</Text> - {rawError?.message && ( - <Text style={pal.textLight}> - <Trans>Message from server: {rawError.message}</Trans> - </Text> - )} + {rawError?.message && ( + <Text style={pal.textLight}> + <Trans>Message from server: {rawError.message}</Trans> + </Text> + )} - {cta} - </View> + {cta} + </View> + + <Prompt.Basic + control={removePromptControl} + title={_l(msgLingui`Remove feed?`)} + description={_l(msgLingui`Remove this feed from your saved feeds`)} + onConfirm={onPressRemoveFeed} + confirmButtonCta={_l(msgLingui`Remove`)} + confirmButtonColor="negative" + /> + </> ) } diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index a11fe8374..75e06eb9b 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -52,6 +52,7 @@ import {LabelInfo} from '../util/moderation/LabelInfo' import {useProfileShadow} from 'state/cache/profile-shadow' import {atoms as a} from '#/alf' import {ProfileMenu} from 'view/com/profile/ProfileMenu' +import * as Prompt from '#/components/Prompt' let ProfileHeaderLoading = (_props: {}): React.ReactNode => { const pal = usePalette('default') @@ -104,6 +105,7 @@ let ProfileHeader = ({ const [showSuggestedFollows, setShowSuggestedFollows] = React.useState(false) const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile) const [__, queueUnblock] = useProfileBlockMutationQueue(profile) + const unblockPromptControl = Prompt.usePromptControl() const moderation = useMemo( () => moderateProfile(profile, moderationOpts), [profile, moderationOpts], @@ -176,27 +178,18 @@ let ProfileHeader = ({ }) }, [track, openModal, profile]) - const onPressUnblockAccount = React.useCallback(() => { + const unblockAccount = React.useCallback(async () => { track('ProfileHeader:UnblockAccountButtonClicked') - openModal({ - name: 'confirm', - title: _(msg`Unblock Account`), - message: _( - msg`The account will be able to interact with you after unblocking.`, - ), - onPressConfirm: async () => { - try { - await queueUnblock() - Toast.show(_(msg`Account unblocked`)) - } catch (e: any) { - if (e?.name !== 'AbortError') { - logger.error('Failed to unblock account', {message: e}) - Toast.show(_(msg`There was an issue! ${e.toString()}`)) - } - } - }, - }) - }, [_, openModal, queueUnblock, track]) + try { + await queueUnblock() + Toast.show(_(msg`Account unblocked`)) + } catch (e: any) { + if (e?.name !== 'AbortError') { + logger.error('Failed to unblock account', {message: e}) + Toast.show(_(msg`There was an issue! ${e.toString()}`)) + } + } + }, [_, queueUnblock, track]) const isMe = React.useMemo( () => currentAccount?.did === profile.did, @@ -242,7 +235,7 @@ let ProfileHeader = ({ profile.viewer?.blockingByList ? null : ( <TouchableOpacity testID="unblockBtn" - onPress={onPressUnblockAccount} + onPress={() => unblockPromptControl.open()} style={[styles.btn, styles.mainBtn, pal.btn]} accessibilityRole="button" accessibilityLabel={_(msg`Unblock`)} @@ -475,6 +468,18 @@ let ProfileHeader = ({ /> </View> </TouchableWithoutFeedback> + <Prompt.Basic + control={unblockPromptControl} + title={_(msg`Unblock Account?`)} + description={_( + msg`The account will be able to interact with you after unblocking.`, + )} + onConfirm={unblockAccount} + confirmButtonCta={ + profile.viewer?.blocking ? _(msg`Unblock`) : _(msg`Block`) + } + confirmButtonColor="negative" + /> </View> ) } diff --git a/src/view/com/profile/ProfileMenu.tsx b/src/view/com/profile/ProfileMenu.tsx index d79e1891d..ef102daf9 100644 --- a/src/view/com/profile/ProfileMenu.tsx +++ b/src/view/com/profile/ProfileMenu.tsx @@ -33,6 +33,7 @@ import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/Per import {PeopleRemove2_Stroke2_Corner0_Rounded as UserMinus} from '#/components/icons/PeopleRemove2' import {logger} from '#/logger' import {Shadow} from 'state/cache/types' +import * as Prompt from '#/components/Prompt' let ProfileMenu = ({ profile, @@ -53,6 +54,8 @@ let ProfileMenu = ({ const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile) const [, queueUnfollow] = useProfileFollowMutationQueue(profile) + const blockPromptControl = Prompt.usePromptControl() + const invalidateProfileQuery = React.useCallback(() => { queryClient.invalidateQueries({ queryKey: profileQueryKey(profile.did), @@ -102,49 +105,31 @@ let ProfileMenu = ({ } }, [profile.viewer?.muted, track, queueUnmute, _, queueMute]) - const onPressBlockAccount = React.useCallback(async () => { + const blockAccount = React.useCallback(async () => { if (profile.viewer?.blocking) { track('ProfileHeader:UnblockAccountButtonClicked') - openModal({ - name: 'confirm', - title: _(msg`Unblock Account`), - message: _( - msg`The account will be able to interact with you after unblocking.`, - ), - onPressConfirm: async () => { - try { - await queueUnblock() - Toast.show(_(msg`Account unblocked`)) - } catch (e: any) { - if (e?.name !== 'AbortError') { - logger.error('Failed to unblock account', {message: e}) - Toast.show(_(msg`There was an issue! ${e.toString()}`)) - } - } - }, - }) + try { + await queueUnblock() + Toast.show(_(msg`Account unblocked`)) + } catch (e: any) { + if (e?.name !== 'AbortError') { + logger.error('Failed to unblock account', {message: e}) + Toast.show(_(msg`There was an issue! ${e.toString()}`)) + } + } } else { track('ProfileHeader:BlockAccountButtonClicked') - openModal({ - name: 'confirm', - title: _(msg`Block Account`), - message: _( - msg`Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`, - ), - onPressConfirm: async () => { - try { - await queueBlock() - Toast.show(_(msg`Account blocked`)) - } catch (e: any) { - if (e?.name !== 'AbortError') { - logger.error('Failed to block account', {message: e}) - Toast.show(_(msg`There was an issue! ${e.toString()}`)) - } - } - }, - }) + try { + await queueBlock() + Toast.show(_(msg`Account blocked`)) + } catch (e: any) { + if (e?.name !== 'AbortError') { + logger.error('Failed to block account', {message: e}) + Toast.show(_(msg`There was an issue! ${e.toString()}`)) + } + } } - }, [profile.viewer?.blocking, track, openModal, _, queueUnblock, queueBlock]) + }, [profile.viewer?.blocking, track, _, queueUnblock, queueBlock]) const onPressUnfollowAccount = React.useCallback(async () => { track('ProfileHeader:UnfollowButtonClicked') @@ -268,7 +253,7 @@ let ProfileMenu = ({ ? _(msg`Unblock Account`) : _(msg`Block Account`) } - onPress={onPressBlockAccount}> + onPress={() => blockPromptControl.open()}> <Menu.ItemText> {profile.viewer?.blocking ? ( <Trans>Unblock Account</Trans> @@ -299,6 +284,29 @@ let ProfileMenu = ({ )} </Menu.Outer> </Menu.Root> + + <Prompt.Basic + control={blockPromptControl} + title={ + profile.viewer?.blocking + ? _(msg`Unblock Account?`) + : _(msg`Block Account?`) + } + description={ + profile.viewer?.blocking + ? _( + msg`The account will be able to interact with you after unblocking.`, + ) + : _( + msg`Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`, + ) + } + onConfirm={blockAccount} + confirmButtonCta={ + profile.viewer?.blocking ? _(msg`Unblock`) : _(msg`Block`) + } + confirmButtonColor="negative" + /> </EventStopper> ) } diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx index 3c1a736f3..84a047c40 100644 --- a/src/view/com/util/forms/PostDropdownBtn.tsx +++ b/src/view/com/util/forms/PostDropdownBtn.tsx @@ -14,6 +14,8 @@ import {useTheme} from 'lib/ThemeContext' import {shareUrl} from 'lib/sharing' import * as Toast from '../Toast' import {EventStopper} from '../EventStopper' +import {useDialogControl} from '#/components/Dialog' +import * as Prompt from '#/components/Prompt' import {useModalControls} from '#/state/modals' import {makeProfileLink} from '#/lib/routes/links' import {CommonNavigatorParams} from '#/lib/routes/types' @@ -81,6 +83,8 @@ let PostDropdownBtn = ({ const openLink = useOpenLink() const navigation = useNavigation() const {mutedWordsDialogControl} = useGlobalDialogsControlContext() + const deletePromptControl = useDialogControl() + const hidePromptControl = useDialogControl() const rootUri = record.reply?.root?.uri || postUri const isThreadMuted = mutedThreads.includes(rootUri) @@ -257,16 +261,7 @@ let PostDropdownBtn = ({ <Menu.Item testID="postDropdownHideBtn" label={_(msg`Hide post`)} - onPress={() => { - openModal({ - name: 'confirm', - title: _(msg`Hide this post?`), - message: _( - msg`This will hide this post from your feeds.`, - ), - onPressConfirm: onHidePost, - }) - }}> + onPress={hidePromptControl.open}> <Menu.ItemText>{_(msg`Hide post`)}</Menu.ItemText> <Menu.ItemIcon icon={EyeSlash} position="right" /> </Menu.Item> @@ -298,14 +293,7 @@ let PostDropdownBtn = ({ <Menu.Item testID="postDropdownDeleteBtn" label={_(msg`Delete post`)} - onPress={() => { - openModal({ - name: 'confirm', - title: _(msg`Delete this post?`), - message: _(msg`Are you sure? This cannot be undone.`), - onPressConfirm: onDeletePost, - }) - }}> + onPress={deletePromptControl.open}> <Menu.ItemText>{_(msg`Delete post`)}</Menu.ItemText> <Menu.ItemIcon icon={Trash} position="right" /> </Menu.Item> @@ -335,6 +323,25 @@ let PostDropdownBtn = ({ </Menu.Group> </Menu.Outer> </Menu.Root> + + <Prompt.Basic + control={deletePromptControl} + title={_(msg`Delete this post?`)} + description={_( + msg`If you remove this post, you won't be able to recover it.`, + )} + onConfirm={onDeletePost} + confirmButtonCta={_(msg`Delete`)} + confirmButtonColor="negative" + /> + + <Prompt.Basic + control={hidePromptControl} + title={_(msg`Hide this post?`)} + description={_(msg`This post will be hidden from feeds.`)} + onConfirm={onHidePost} + confirmButtonCta={_(msg`Hide`)} + /> </EventStopper> ) } |