From 66a0f8e848342bb5e5ceda36f5095f3ad2b0de29 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Sun, 18 Dec 2022 17:28:28 -0600 Subject: [PATCH] Add WIP 'report post' modal --- src/state/models/shell-ui.ts | 12 +++- src/view/com/modals/Modal.tsx | 8 +++ src/view/com/modals/ReportPost.tsx | 94 +++++++++++++++++++++++++ src/view/com/util/DropdownBtn.tsx | 9 ++- src/view/com/util/forms/RadioButton.tsx | 54 ++++++++++++++ src/view/com/util/forms/RadioGroup.tsx | 34 +++++++++ src/view/index.ts | 2 + 7 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 src/view/com/modals/ReportPost.tsx create mode 100644 src/view/com/util/forms/RadioButton.tsx create mode 100644 src/view/com/util/forms/RadioGroup.tsx diff --git a/src/state/models/shell-ui.ts b/src/state/models/shell-ui.ts index fa2e78d5..d1f45854 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 610d30eb..e1d2eaca 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 = ( + + ) } else { element = } diff --git a/src/view/com/modals/ReportPost.tsx b/src/view/com/modals/ReportPost.tsx new file mode 100644 index 00000000..5f5a4180 --- /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(false) + const [error, setError] = useState('') + const [issue, setIssue] = useState('') + 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 ( + + Report post + What is the issue with this post? + + {error ? ( + + + + ) : undefined} + {isProcessing ? ( + + + + ) : issue ? ( + + + Send Report + + + ) : undefined} + + ) +} + +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 d2b82c91..0ca7e2cf 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 00000000..b311a426 --- /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 ( + + + {isSelected ? : undefined} + + {label} + + ) +} + +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 00000000..6684cde5 --- /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('') + const onSelectInner = (key: string) => { + setSelection(key) + onSelect(key) + } + return ( + + {items.map(item => ( + onSelectInner(item.key)} + /> + ))} + + ) +} diff --git a/src/view/index.ts b/src/view/index.ts index 8f119cd1..26695e5c 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,