diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/state/models/shell-ui.ts | 12 | ||||
-rw-r--r-- | src/view/com/modals/Modal.tsx | 8 | ||||
-rw-r--r-- | src/view/com/modals/ReportPost.tsx | 94 | ||||
-rw-r--r-- | src/view/com/util/DropdownBtn.tsx | 9 | ||||
-rw-r--r-- | src/view/com/util/forms/RadioButton.tsx | 54 | ||||
-rw-r--r-- | src/view/com/util/forms/RadioGroup.tsx | 34 | ||||
-rw-r--r-- | src/view/index.ts | 2 |
7 files changed, 211 insertions, 2 deletions
diff --git a/src/state/models/shell-ui.ts b/src/state/models/shell-ui.ts index fa2e78d5b..d1f458546 100644 --- a/src/state/models/shell-ui.ts +++ b/src/state/models/shell-ui.ts @@ -51,6 +51,14 @@ export class ServerInputModal { } } +export class ReportPostModal { + name = 'report-post' + + constructor(public postUrl: string) { + makeAutoObservable(this) + } +} + interface LightboxModel { canSwipeLeft: boolean canSwipeRight: boolean @@ -127,6 +135,7 @@ export class ShellUiModel { | EditProfileModal | CreateSceneModal | ServerInputModal + | ReportPostModal | undefined isLightboxActive = false activeLightbox: @@ -154,7 +163,8 @@ export class ShellUiModel { | ConfirmModal | EditProfileModal | CreateSceneModal - | ServerInputModal, + | ServerInputModal + | ReportPostModal, ) { this.isModalActive = true this.activeModal = modal diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index 610d30eba..e1d2eacaf 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -12,6 +12,7 @@ import * as EditProfileModal from './EditProfile' import * as CreateSceneModal from './CreateScene' import * as InviteToSceneModal from './InviteToScene' import * as ServerInputModal from './ServerInput' +import * as ReportPostModal from './ReportPost' const CLOSED_SNAPPOINTS = ['10%'] @@ -70,6 +71,13 @@ export const Modal = observer(function Modal() { {...(store.shell.activeModal as models.ServerInputModal)} /> ) + } else if (store.shell.activeModal?.name === 'report-post') { + snapPoints = ReportPostModal.snapPoints + element = ( + <ReportPostModal.Component + {...(store.shell.activeModal as models.ReportPostModal)} + /> + ) } else { element = <View /> } diff --git a/src/view/com/modals/ReportPost.tsx b/src/view/com/modals/ReportPost.tsx new file mode 100644 index 000000000..5f5a41801 --- /dev/null +++ b/src/view/com/modals/ReportPost.tsx @@ -0,0 +1,94 @@ +import React, {useState} from 'react' +import { + ActivityIndicator, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native' +import LinearGradient from 'react-native-linear-gradient' +import {useStores} from '../../../state' +import {s, colors, gradients} from '../../lib/styles' +import {RadioGroup, RadioGroupItem} from '../util/forms/RadioGroup' +import {ErrorMessage} from '../util/ErrorMessage' + +const ITEMS: RadioGroupItem[] = [ + {key: 'spam', label: 'Spam or excessive repeat posts'}, + {key: 'abuse', label: 'Abusive, rude, or hateful'}, + {key: 'copyright', label: 'Contains copyrighted material'}, + {key: 'illegal', label: 'Contains illegal content'}, +] + +export const snapPoints = ['50%'] + +export function Component({postUrl}: {postUrl: string}) { + const store = useStores() + const [isProcessing, setIsProcessing] = useState<boolean>(false) + const [error, setError] = useState<string>('') + const [issue, setIssue] = useState<string>('') + const onSelectIssue = (v: string) => setIssue(v) + const onPress = async () => { + setError('') + setIsProcessing(true) + try { + // TODO + store.shell.closeModal() + return + } catch (e: any) { + setError(e.toString()) + setIsProcessing(false) + } + } + return ( + <View style={[s.flex1, s.pl10, s.pr10]}> + <Text style={styles.title}>Report post</Text> + <Text style={styles.description}>What is the issue with this post?</Text> + <RadioGroup items={ITEMS} onSelect={onSelectIssue} /> + {error ? ( + <View style={s.mt10}> + <ErrorMessage message={error} /> + </View> + ) : undefined} + {isProcessing ? ( + <View style={[styles.btn, s.mt10]}> + <ActivityIndicator /> + </View> + ) : issue ? ( + <TouchableOpacity style={s.mt10} onPress={onPress}> + <LinearGradient + colors={[gradients.primary.start, gradients.primary.end]} + start={{x: 0, y: 0}} + end={{x: 1, y: 1}} + style={[styles.btn]}> + <Text style={[s.white, s.bold, s.f18]}>Send Report</Text> + </LinearGradient> + </TouchableOpacity> + ) : undefined} + </View> + ) +} + +const styles = StyleSheet.create({ + title: { + textAlign: 'center', + fontWeight: 'bold', + fontSize: 24, + marginBottom: 12, + }, + description: { + textAlign: 'center', + fontSize: 17, + paddingHorizontal: 22, + color: colors.gray5, + marginBottom: 10, + }, + btn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + borderRadius: 32, + padding: 14, + backgroundColor: colors.gray1, + }, +}) diff --git a/src/view/com/util/DropdownBtn.tsx b/src/view/com/util/DropdownBtn.tsx index d2b82c919..0ca7e2cf1 100644 --- a/src/view/com/util/DropdownBtn.tsx +++ b/src/view/com/util/DropdownBtn.tsx @@ -15,7 +15,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {colors} from '../../lib/styles' import {toShareUrl} from '../../../lib/strings' import {useStores} from '../../../state' -import {ConfirmModal} from '../../../state/models/shell-ui' +import {ReportPostModal, ConfirmModal} from '../../../state/models/shell-ui' import {TABS_ENABLED} from '../../../build-flags' const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10} @@ -116,6 +116,13 @@ export function PostDropdownBtn({ Share.share({url: toShareUrl(itemHref)}) }, }, + { + icon: 'circle-exclamation', + label: 'Report post', + onPress() { + store.shell.openModal(new ReportPostModal(itemHref)) + }, + }, isAuthor ? { icon: ['far', 'trash-can'], diff --git a/src/view/com/util/forms/RadioButton.tsx b/src/view/com/util/forms/RadioButton.tsx new file mode 100644 index 000000000..b311a4260 --- /dev/null +++ b/src/view/com/util/forms/RadioButton.tsx @@ -0,0 +1,54 @@ +import React from 'react' +import {StyleSheet, Text, TouchableOpacity, View} from 'react-native' +import {colors} from '../../../lib/styles' + +export function RadioButton({ + label, + isSelected, + onPress, +}: { + label: string + isSelected: boolean + onPress: () => void +}) { + return ( + <TouchableOpacity style={styles.outer} onPress={onPress}> + <View style={styles.circle}> + {isSelected ? <View style={styles.circleFill} /> : undefined} + </View> + <Text style={styles.label}>{label}</Text> + </TouchableOpacity> + ) +} + +const styles = StyleSheet.create({ + outer: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 5, + borderRadius: 8, + borderWidth: 1, + borderColor: colors.gray2, + paddingHorizontal: 10, + paddingVertical: 8, + }, + circle: { + width: 30, + height: 30, + borderRadius: 15, + padding: 4, + borderWidth: 1, + borderColor: colors.gray3, + marginRight: 10, + }, + circleFill: { + width: 20, + height: 20, + borderRadius: 10, + backgroundColor: colors.blue3, + }, + label: { + flex: 1, + fontSize: 17, + }, +}) diff --git a/src/view/com/util/forms/RadioGroup.tsx b/src/view/com/util/forms/RadioGroup.tsx new file mode 100644 index 000000000..6684cde5c --- /dev/null +++ b/src/view/com/util/forms/RadioGroup.tsx @@ -0,0 +1,34 @@ +import React, {useState} from 'react' +import {View} from 'react-native' +import {RadioButton} from './RadioButton' + +export interface RadioGroupItem { + label: string + key: string +} + +export function RadioGroup({ + items, + onSelect, +}: { + items: RadioGroupItem[] + onSelect: (key: string) => void +}) { + const [selection, setSelection] = useState<string>('') + const onSelectInner = (key: string) => { + setSelection(key) + onSelect(key) + } + return ( + <View> + {items.map(item => ( + <RadioButton + key={item.key} + label={item.label} + isSelected={item.key === selection} + onPress={() => onSelectInner(item.key)} + /> + ))} + </View> + ) +} diff --git a/src/view/index.ts b/src/view/index.ts index 8f119cd16..26695e5ce 100644 --- a/src/view/index.ts +++ b/src/view/index.ts @@ -21,6 +21,7 @@ import {faBookmark as farBookmark} from '@fortawesome/free-regular-svg-icons/faB import {faCamera} from '@fortawesome/free-solid-svg-icons/faCamera' import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck' import {faCircleCheck} from '@fortawesome/free-regular-svg-icons/faCircleCheck' +import {faCircleExclamation} from '@fortawesome/free-solid-svg-icons/faCircleExclamation' import {faCircleUser} from '@fortawesome/free-regular-svg-icons/faCircleUser' import {faClone} from '@fortawesome/free-solid-svg-icons/faClone' import {faClone as farClone} from '@fortawesome/free-regular-svg-icons/faClone' @@ -86,6 +87,7 @@ export function setup() { faCamera, faCheck, faCircleCheck, + faCircleExclamation, faCircleUser, faClone, farClone, |