ALF confirmation dialogs (Dialogs Pt. 3) (#3143)
* Improve a11y on ios * Format * Remove android * Fix android * ALF confirmation dialog * Use ALF for Delete Post confirmation organize diff fix text minimize change copy alternative confirm prompt revert type changes add ButtonColor param * small adjustment to buttons in prompt * full width below gtmobile * update hide post dialog * space out dialogs * update dialogs for lists * add example * add to app passwords * Revert some changes * use sharedvalue for `importantForAccessibility` * add back `isOpen` * fix some more types * small adjustment to buttons in prompt * full width below gtmobile * update the rest of the prompts rm old confirm modal rm update prompt feed error prompt feed source card and profile block/unblock composer discard * Update src/view/screens/AppPasswords.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * lint * How about a default * Reverse reverse * Port over confirm dialogs * Add some comments * Remove unused file * complete merge * add testID where needed --------- Co-authored-by: Eric Bailey <git@esb.lol> Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
This commit is contained in:
parent
090b35e52e
commit
9f2f7f221c
19 changed files with 540 additions and 605 deletions
|
@ -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>
|
||||
)
|
||||
})
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon="plus"
|
||||
size={18}
|
||||
color={pal.colors.link}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
<>
|
||||
<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}
|
||||
/>
|
||||
) : (
|
||||
<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>
|
||||
|
||||
<Prompt.Basic
|
||||
control={removePromptControl}
|
||||
title={_(msg`Remove from my feeds?`)}
|
||||
description={_(
|
||||
msg`Are you sure you want to remove ${feed.displayName} from your feeds?`,
|
||||
)}
|
||||
</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>
|
||||
onConfirm={onUnsave}
|
||||
confirmButtonCta={_(msg`Remove`)}
|
||||
confirmButtonColor="negative"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
})
|
|
@ -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') {
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue