Add first round of labeling tools (#467)

* Rework notifications to sync locally in full and give users better control

* Fix positioning of load more btn on web

* Improve behavior of load more notifications btn

* Fix to post rendering

* Fix notification fetch abort condition

* Add start of post-hiding by labels

* Create a standard postcontainer and improve show/hide UI on posts

* Add content hiding to expanded post form

* Improve label rendering to give more context to users when appropriate

* Fix rendering bug

* Add user/profile labeling

* Implement content filtering preferences

* Filter notifications by content prefs

* Update test-pds config

* Bump deps
This commit is contained in:
Paul Frazee 2023-04-12 18:26:38 -07:00 committed by GitHub
parent a20d034ba5
commit 2fed6c4021
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 1292 additions and 530 deletions

View file

@ -0,0 +1,185 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import LinearGradient from 'react-native-linear-gradient'
import {observer} from 'mobx-react-lite'
import {useStores} from 'state/index'
import {LabelPreference} from 'state/models/ui/preferences'
import {s, colors, gradients} from 'lib/styles'
import {Text} from '../util/text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {CONFIGURABLE_LABEL_GROUPS} from 'lib/labeling/const'
export const snapPoints = [500]
export function Component({}: {}) {
const store = useStores()
const pal = usePalette('default')
const onPressDone = React.useCallback(() => {
store.shell.closeModal()
}, [store])
return (
<View testID="reportPostModal" style={[pal.view, styles.container]}>
<Text style={[pal.text, styles.title]}>Content Filtering</Text>
<ContentLabelPref group="nsfw" />
<ContentLabelPref group="gore" />
<ContentLabelPref group="hate" />
<ContentLabelPref group="spam" />
<ContentLabelPref group="impersonation" />
<View style={s.flex1} />
<TouchableOpacity testID="sendReportBtn" onPress={onPressDone}>
<LinearGradient
colors={[gradients.blueLight.start, gradients.blueLight.end]}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[styles.btn]}>
<Text style={[s.white, s.bold, s.f18]}>Done</Text>
</LinearGradient>
</TouchableOpacity>
</View>
)
}
const ContentLabelPref = observer(
({group}: {group: keyof typeof CONFIGURABLE_LABEL_GROUPS}) => {
const store = useStores()
const pal = usePalette('default')
return (
<View style={[styles.contentLabelPref, pal.border]}>
<Text type="md-medium" style={[pal.text]}>
{CONFIGURABLE_LABEL_GROUPS[group].title}
</Text>
<SelectGroup
current={store.preferences.contentLabels[group]}
onChange={v => store.preferences.setContentLabelPref(group, v)}
/>
</View>
)
},
)
function SelectGroup({
current,
onChange,
}: {
current: LabelPreference
onChange: (v: LabelPreference) => void
}) {
return (
<View style={styles.selectableBtns}>
<SelectableBtn
current={current}
value="hide"
label="Hide"
left
onChange={onChange}
/>
<SelectableBtn
current={current}
value="warn"
label="Warn"
onChange={onChange}
/>
<SelectableBtn
current={current}
value="show"
label="Show"
right
onChange={onChange}
/>
</View>
)
}
function SelectableBtn({
current,
value,
label,
left,
right,
onChange,
}: {
current: string
value: LabelPreference
label: string
left?: boolean
right?: boolean
onChange: (v: LabelPreference) => void
}) {
const pal = usePalette('default')
const palPrimary = usePalette('inverted')
return (
<TouchableOpacity
style={[
styles.selectableBtn,
left && styles.selectableBtnLeft,
right && styles.selectableBtnRight,
pal.border,
current === value ? palPrimary.view : pal.view,
]}
onPress={() => onChange(value)}>
<Text style={current === value ? palPrimary.text : pal.text}>
{label}
</Text>
</TouchableOpacity>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: 10,
paddingBottom: 40,
},
title: {
textAlign: 'center',
fontWeight: 'bold',
fontSize: 24,
marginBottom: 12,
},
description: {
paddingHorizontal: 2,
marginBottom: 10,
},
contentLabelPref: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: 10,
paddingLeft: 4,
marginBottom: 10,
borderTopWidth: 1,
},
selectableBtns: {
flexDirection: 'row',
},
selectableBtn: {
flexDirection: 'row',
justifyContent: 'center',
borderWidth: 1,
borderLeftWidth: 0,
paddingHorizontal: 10,
paddingVertical: 10,
},
selectableBtnLeft: {
borderTopLeftRadius: 8,
borderBottomLeftRadius: 8,
borderLeftWidth: 1,
},
selectableBtnRight: {
borderTopRightRadius: 8,
borderBottomRightRadius: 8,
},
btn: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
borderRadius: 32,
padding: 14,
backgroundColor: colors.gray1,
},
})

View file

@ -1,9 +1,10 @@
import React, {useRef, useEffect} from 'react'
import {View} from 'react-native'
import {StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import BottomSheet from '@gorhom/bottom-sheet'
import {useStores} from 'state/index'
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
import {usePalette} from 'lib/hooks/usePalette'
import * as ConfirmModal from './Confirm'
import * as EditProfileModal from './EditProfile'
@ -15,8 +16,7 @@ import * as DeleteAccountModal from './DeleteAccount'
import * as ChangeHandleModal from './ChangeHandle'
import * as WaitlistModal from './Waitlist'
import * as InviteCodesModal from './InviteCodes'
import {usePalette} from 'lib/hooks/usePalette'
import {StyleSheet} from 'react-native'
import * as ContentFilteringSettingsModal from './ContentFilteringSettings'
const DEFAULT_SNAPPOINTS = ['90%']
@ -77,6 +77,9 @@ export const ModalsContainer = observer(function ModalsContainer() {
} else if (activeModal?.name === 'invite-codes') {
snapPoints = InviteCodesModal.snapPoints
element = <InviteCodesModal.Component />
} else if (activeModal?.name === 'content-filtering-settings') {
snapPoints = ContentFilteringSettingsModal.snapPoints
element = <ContentFilteringSettingsModal.Component />
} else {
return <View />
}

View file

@ -17,6 +17,7 @@ import * as CropImageModal from './crop-image/CropImage.web'
import * as ChangeHandleModal from './ChangeHandle'
import * as WaitlistModal from './Waitlist'
import * as InviteCodesModal from './InviteCodes'
import * as ContentFilteringSettingsModal from './ContentFilteringSettings'
export const ModalsContainer = observer(function ModalsContainer() {
const store = useStores()
@ -75,6 +76,8 @@ function Modal({modal}: {modal: ModalIface}) {
element = <WaitlistModal.Component />
} else if (modal.name === 'invite-codes') {
element = <InviteCodesModal.Component />
} else if (modal.name === 'content-filtering-settings') {
element = <ContentFilteringSettingsModal.Component />
} else {
return null
}