diff options
Diffstat (limited to 'src/view')
-rw-r--r-- | src/view/com/modals/AppealLabel.tsx | 139 | ||||
-rw-r--r-- | src/view/com/modals/Modal.tsx | 4 | ||||
-rw-r--r-- | src/view/com/modals/Modal.web.tsx | 3 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThreadItem.tsx | 10 | ||||
-rw-r--r-- | src/view/com/profile/ProfileHeader.tsx | 4 | ||||
-rw-r--r-- | src/view/com/util/moderation/LabelInfo.tsx | 60 |
6 files changed, 220 insertions, 0 deletions
diff --git a/src/view/com/modals/AppealLabel.tsx b/src/view/com/modals/AppealLabel.tsx new file mode 100644 index 000000000..2db070bc6 --- /dev/null +++ b/src/view/com/modals/AppealLabel.tsx @@ -0,0 +1,139 @@ +import React, {useState} from 'react' +import {StyleSheet, TouchableOpacity, View} from 'react-native' +import {ComAtprotoModerationDefs} from '@atproto/api' +import {ScrollView, TextInput} from './util' +import {Text} from '../util/text/Text' +import {s, colors} from 'lib/styles' +import {usePalette} from 'lib/hooks/usePalette' +import {Trans, msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useModalControls} from '#/state/modals' +import {CharProgress} from '../composer/char-progress/CharProgress' +import {getAgent} from '#/state/session' +import * as Toast from '../util/Toast' +import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' + +export const snapPoints = ['40%'] + +type ReportComponentProps = + | { + uri: string + cid: string + } + | { + did: string + } + +export function Component(props: ReportComponentProps) { + const pal = usePalette('default') + const [details, setDetails] = useState<string>('') + const {_} = useLingui() + const {closeModal} = useModalControls() + const {isMobile} = useWebMediaQueries() + const isAccountReport = 'did' in props + + const submit = async () => { + try { + const $type = !isAccountReport + ? 'com.atproto.repo.strongRef' + : 'com.atproto.admin.defs#repoRef' + await getAgent().createModerationReport({ + reasonType: ComAtprotoModerationDefs.REASONOTHER, + subject: { + $type, + ...props, + }, + reason: details, + }) + Toast.show("We'll look into your appeal promptly.") + } finally { + closeModal() + } + } + + return ( + <View + style={[ + pal.view, + s.flex1, + isMobile ? {paddingHorizontal: 12} : undefined, + ]} + testID="appealLabelModal"> + <Text + type="2xl-bold" + style={[pal.text, s.textCenter, {paddingBottom: 8}]}> + <Trans>Appeal Decision</Trans> + </Text> + <ScrollView> + <View style={[pal.btn, styles.detailsInputContainer]}> + <TextInput + accessibilityLabel={_(msg`Text input field`)} + accessibilityHint={_( + msg`Please tell us why you think this decision was incorrect.`, + )} + placeholder={_( + msg`Please tell us why you think this decision was incorrect.`, + )} + placeholderTextColor={pal.textLight.color} + value={details} + onChangeText={setDetails} + autoFocus={true} + numberOfLines={3} + multiline={true} + textAlignVertical="top" + maxLength={300} + style={[styles.detailsInput, pal.text]} + /> + <View style={styles.detailsInputBottomBar}> + <View style={styles.charCounter}> + <CharProgress count={details?.length || 0} /> + </View> + </View> + </View> + <TouchableOpacity + testID="confirmBtn" + onPress={submit} + style={styles.btn} + accessibilityRole="button" + accessibilityLabel={_(msg`Confirm`)} + accessibilityHint=""> + <Text style={[s.white, s.bold, s.f18]}> + <Trans>Submit</Trans> + </Text> + </TouchableOpacity> + </ScrollView> + </View> + ) +} + +const styles = StyleSheet.create({ + detailsInputContainer: { + borderRadius: 8, + marginBottom: 8, + }, + detailsInput: { + paddingHorizontal: 12, + paddingTop: 12, + paddingBottom: 12, + borderRadius: 8, + minHeight: 100, + fontSize: 16, + }, + detailsInputBottomBar: { + alignSelf: 'flex-end', + }, + charCounter: { + flexDirection: 'row', + alignItems: 'center', + paddingRight: 10, + paddingBottom: 8, + }, + btn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + borderRadius: 32, + padding: 14, + backgroundColor: colors.blue3, + }, +}) diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index a3e6fb9e5..0384e301c 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -22,6 +22,7 @@ import * as ListAddUserModal from './ListAddRemoveUsers' import * as AltImageModal from './AltImage' import * as EditImageModal from './AltImage' import * as ReportModal from './report/Modal' +import * as AppealLabelModal from './AppealLabel' import * as DeleteAccountModal from './DeleteAccount' import * as ChangeHandleModal from './ChangeHandle' import * as WaitlistModal from './Waitlist' @@ -105,6 +106,9 @@ export function ModalsContainer() { } else if (activeModal?.name === 'report') { snapPoints = ReportModal.snapPoints element = <ReportModal.Component {...activeModal} /> + } else if (activeModal?.name === 'appeal-label') { + snapPoints = AppealLabelModal.snapPoints + element = <AppealLabelModal.Component {...activeModal} /> } else if (activeModal?.name === 'create-or-edit-list') { snapPoints = CreateOrEditListModal.snapPoints element = <CreateOrEditListModal.Component {...activeModal} /> diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx index c39ba1f51..ce1e67fae 100644 --- a/src/view/com/modals/Modal.web.tsx +++ b/src/view/com/modals/Modal.web.tsx @@ -11,6 +11,7 @@ import * as EditProfileModal from './EditProfile' import * as ProfilePreviewModal from './ProfilePreview' import * as ServerInputModal from './ServerInput' import * as ReportModal from './report/Modal' +import * as AppealLabelModal from './AppealLabel' import * as CreateOrEditListModal from './CreateOrEditList' import * as UserAddRemoveLists from './UserAddRemoveLists' import * as ListAddUserModal from './ListAddRemoveUsers' @@ -81,6 +82,8 @@ function Modal({modal}: {modal: ModalIface}) { element = <ServerInputModal.Component {...modal} /> } else if (modal.name === 'report') { element = <ReportModal.Component {...modal} /> + } else if (modal.name === 'appeal-label') { + element = <AppealLabelModal.Component {...modal} /> } else if (modal.name === 'create-or-edit-list') { element = <CreateOrEditListModal.Component {...modal} /> } else if (modal.name === 'user-add-remove-lists') { diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 900839015..a2aa3716e 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -42,6 +42,8 @@ import {useComposerControls} from '#/state/shell/composer' import {useModerationOpts} from '#/state/queries/preferences' import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow' import {ThreadPost} from '#/state/queries/post-thread' +import {LabelInfo} from '../util/moderation/LabelInfo' +import {useSession} from '#/state/session' export function PostThreadItem({ post, @@ -158,6 +160,7 @@ let PostThreadItemLoaded = ({ const pal = usePalette('default') const langPrefs = useLanguagePrefs() const {openComposer} = useComposerControls() + const {currentAccount} = useSession() const [limitLines, setLimitLines] = React.useState( () => countLines(richText?.text) >= MAX_POST_LINES, ) @@ -345,6 +348,13 @@ let PostThreadItemLoaded = ({ includeMute style={styles.alert} /> + {post.author.did === currentAccount?.did ? ( + <LabelInfo + details={{uri: post.uri, cid: post.cid}} + labels={post.labels} + style={{marginBottom: 8}} + /> + ) : null} {richText?.text ? ( <View style={[ diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index e006cac7d..6975e3964 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -54,6 +54,7 @@ import {logger} from '#/logger' import {useSession} from '#/state/session' import {Shadow} from '#/state/cache/types' import {useRequireAuth} from '#/state/session' +import {LabelInfo} from '../util/moderation/LabelInfo' interface Props { profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> | null @@ -619,6 +620,9 @@ let ProfileHeaderLoaded = ({ </> )} <ProfileHeaderAlerts moderation={moderation} /> + {isMe && ( + <LabelInfo details={{did: profile.did}} labels={profile.labels} /> + )} </View> {!isProfilePreview && showSuggestedFollows && ( diff --git a/src/view/com/util/moderation/LabelInfo.tsx b/src/view/com/util/moderation/LabelInfo.tsx new file mode 100644 index 000000000..8fe3765c2 --- /dev/null +++ b/src/view/com/util/moderation/LabelInfo.tsx @@ -0,0 +1,60 @@ +import React from 'react' +import {Pressable, StyleProp, View, ViewStyle} from 'react-native' +import {ComAtprotoLabelDefs} from '@atproto/api' +import {Text} from '../text/Text' +import {usePalette} from 'lib/hooks/usePalette' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useModalControls} from '#/state/modals' + +export function LabelInfo({ + details, + labels, + style, +}: { + details: {did: string} | {uri: string; cid: string} + labels: ComAtprotoLabelDefs.Label[] | undefined + style?: StyleProp<ViewStyle> +}) { + const pal = usePalette('default') + const {_} = useLingui() + const {openModal} = useModalControls() + + if (!labels) { + return null + } + labels = labels.filter(l => !l.val.startsWith('!')) + if (!labels.length) { + return null + } + + return ( + <View + style={[ + pal.viewLight, + { + flexDirection: 'row', + flexWrap: 'wrap', + paddingHorizontal: 12, + paddingVertical: 10, + borderRadius: 8, + }, + style, + ]}> + <Text type="sm" style={pal.text}> + <Trans> + This {'did' in details ? 'account' : 'post'} has been labeled. + </Trans>{' '} + </Text> + <Pressable + accessibilityRole="button" + accessibilityLabel={_(msg`Appeal this decision`)} + accessibilityHint="" + onPress={() => openModal({name: 'appeal-label', ...details})}> + <Text type="sm" style={pal.link}> + <Trans>Appeal this decision.</Trans> + </Text> + </Pressable> + </View> + ) +} |