[Threadgate] Tweak threadgate buttons (#9173)
* tweak in-post threadgate button * tweak composer threadgate button * reduce date length slightly * pressed styles * make date length depend on breakpoint * add chevron to label btn * add tiny chevron, special-case button icon width * [Threadgate] Add hint (#9350) * get tooltip working on web * add compatibility layer for working in iOS sheets * add timeout to profile tooltip now that it appears instantly * rm debug code * Update ThreadgateBtn.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * remeasure when keyboard changes --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * fix types * [Threadgate] Refresh dialog (#9342) * wip new ui * update threadgate dialog with new designs * restore nobody option * relayout android sheet when ratio changes * fix ratio changing case in bottom sheet * timebox reached, use setTimeout * update panel styles * missing imports * extract out Panel * tweak layout animation * fix icon color * use same color mechamism for icon as text * restore the header * refreshed toggle styles (#9343) * [Threadgate] Persist settings (#9341) * add persist toggle to threadgate dialog * move state back down * sort out spacing * wire up query * @surfdude29 tweaks * use tiny chevron in WhoCanReply * wait for prefetch before opening * move Panel into the Toggle namespace * default -> pref * use medium date length * rm hover state from web selects, fix border radius * fix key issue in Selects --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
This commit is contained in:
@@ -1,16 +1,17 @@
|
||||
import React from 'react'
|
||||
import {type StyleProp, View, type ViewStyle} from 'react-native'
|
||||
import {useCallback, useMemo, useState} from 'react'
|
||||
import {LayoutAnimation, Text as NestedText, View} from 'react-native'
|
||||
import {
|
||||
type AppBskyFeedDefs,
|
||||
type AppBskyFeedPostgate,
|
||||
AtUri,
|
||||
} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {msg, Plural, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
import isEqual from 'lodash.isequal'
|
||||
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {logger} from '#/logger'
|
||||
import {isIOS} from '#/platform/detection'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useMyListsQuery} from '#/state/queries/my-lists'
|
||||
import {useGetPost} from '#/state/queries/post'
|
||||
@@ -37,13 +38,17 @@ import {
|
||||
} from '#/state/queries/usePostThread'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {atoms as a, useTheme, web} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {Divider} from '#/components/Divider'
|
||||
import * as Toggle from '#/components/forms/Toggle'
|
||||
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||
import {
|
||||
ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon,
|
||||
ChevronTop_Stroke2_Corner0_Rounded as ChevronUpIcon,
|
||||
} from '#/components/icons/Chevron'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||
import {CloseQuote_Stroke2_Corner1_Rounded as QuoteIcon} from '#/components/icons/Quote'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
@@ -52,6 +57,10 @@ export type PostInteractionSettingsFormProps = {
|
||||
onSave: () => void
|
||||
isSaving?: boolean
|
||||
|
||||
isDirty?: boolean
|
||||
persist?: boolean
|
||||
onChangePersist?: (v: boolean) => void
|
||||
|
||||
postgate: AppBskyFeedPostgate.Record
|
||||
onChangePostgate: (v: AppBskyFeedPostgate.Record) => void
|
||||
|
||||
@@ -61,57 +70,39 @@ export type PostInteractionSettingsFormProps = {
|
||||
replySettingsDisabled?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Threadgate settings dialog. Used in the composer.
|
||||
*/
|
||||
export function PostInteractionSettingsControlledDialog({
|
||||
control,
|
||||
...rest
|
||||
}: PostInteractionSettingsFormProps & {
|
||||
control: Dialog.DialogControlProps
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
|
||||
return (
|
||||
<Dialog.Outer control={control}>
|
||||
<Dialog.Outer
|
||||
control={control}
|
||||
nativeOptions={{
|
||||
preventExpansion: true,
|
||||
preventDismiss: rest.isDirty && rest.persist,
|
||||
}}>
|
||||
<Dialog.Handle />
|
||||
<Dialog.ScrollableInner
|
||||
label={_(msg`Edit post interaction settings`)}
|
||||
style={[{maxWidth: 500}, a.w_full]}>
|
||||
<View style={[a.gap_md]}>
|
||||
<Header />
|
||||
<PostInteractionSettingsForm {...rest} />
|
||||
<Text
|
||||
style={[
|
||||
a.pt_sm,
|
||||
a.text_sm,
|
||||
a.leading_snug,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
<Trans>
|
||||
You can set default interaction settings in{' '}
|
||||
<Text style={[a.font_semi_bold, t.atoms.text_contrast_medium]}>
|
||||
Settings → Moderation → Interaction settings
|
||||
</Text>
|
||||
.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
<Dialog.Close />
|
||||
</Dialog.ScrollableInner>
|
||||
<DialogInner {...rest} />
|
||||
</Dialog.Outer>
|
||||
)
|
||||
}
|
||||
|
||||
export function Header() {
|
||||
function DialogInner(props: Omit<PostInteractionSettingsFormProps, 'control'>) {
|
||||
const {_} = useLingui()
|
||||
|
||||
return (
|
||||
<View style={[a.gap_md, a.pb_sm]}>
|
||||
<Text style={[a.text_2xl, a.font_semi_bold]}>
|
||||
<Trans>Post interaction settings</Trans>
|
||||
</Text>
|
||||
<Text style={[a.text_md, a.pb_xs]}>
|
||||
<Trans>Customize who can interact with this post.</Trans>
|
||||
</Text>
|
||||
<Divider />
|
||||
</View>
|
||||
<Dialog.ScrollableInner
|
||||
label={_(msg`Edit post interaction settings`)}
|
||||
style={[web({maxWidth: 400}), a.w_full]}>
|
||||
<Header />
|
||||
<PostInteractionSettingsForm {...props} />
|
||||
<Dialog.Close />
|
||||
</Dialog.ScrollableInner>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -134,12 +125,17 @@ export type PostInteractionSettingsDialogProps = {
|
||||
initialThreadgateView?: AppBskyFeedDefs.ThreadgateView
|
||||
}
|
||||
|
||||
/**
|
||||
* Threadgate settings dialog. Used in the thread.
|
||||
*/
|
||||
export function PostInteractionSettingsDialog(
|
||||
props: PostInteractionSettingsDialogProps,
|
||||
) {
|
||||
const postThreadContext = usePostThreadContext()
|
||||
return (
|
||||
<Dialog.Outer control={props.control}>
|
||||
<Dialog.Outer
|
||||
control={props.control}
|
||||
nativeOptions={{preventExpansion: true}}>
|
||||
<Dialog.Handle />
|
||||
<PostThreadContextProvider context={postThreadContext}>
|
||||
<PostInteractionSettingsDialogControlledInner {...props} />
|
||||
@@ -153,7 +149,7 @@ export function PostInteractionSettingsDialogControlledInner(
|
||||
) {
|
||||
const {_} = useLingui()
|
||||
const {currentAccount} = useSession()
|
||||
const [isSaving, setIsSaving] = React.useState(false)
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
|
||||
const {data: threadgateViewLoaded, isLoading: isLoadingThreadgate} =
|
||||
useThreadgateViewQuery({postUri: props.rootPostUri})
|
||||
@@ -165,28 +161,28 @@ export function PostInteractionSettingsDialogControlledInner(
|
||||
const {mutateAsync: setThreadgateAllow} = useSetThreadgateAllowMutation()
|
||||
|
||||
const [editedPostgate, setEditedPostgate] =
|
||||
React.useState<AppBskyFeedPostgate.Record>()
|
||||
useState<AppBskyFeedPostgate.Record>()
|
||||
const [editedAllowUISettings, setEditedAllowUISettings] =
|
||||
React.useState<ThreadgateAllowUISetting[]>()
|
||||
useState<ThreadgateAllowUISetting[]>()
|
||||
|
||||
const isLoading = isLoadingThreadgate || isLoadingPostgate
|
||||
const threadgateView = threadgateViewLoaded || props.initialThreadgateView
|
||||
const isThreadgateOwnedByViewer = React.useMemo(() => {
|
||||
const isThreadgateOwnedByViewer = useMemo(() => {
|
||||
return currentAccount?.did === new AtUri(props.rootPostUri).host
|
||||
}, [props.rootPostUri, currentAccount?.did])
|
||||
|
||||
const postgateValue = React.useMemo(() => {
|
||||
const postgateValue = useMemo(() => {
|
||||
return (
|
||||
editedPostgate || postgate || createPostgateRecord({post: props.postUri})
|
||||
)
|
||||
}, [postgate, editedPostgate, props.postUri])
|
||||
const allowUIValue = React.useMemo(() => {
|
||||
const allowUIValue = useMemo(() => {
|
||||
return (
|
||||
editedAllowUISettings || threadgateViewToAllowUISetting(threadgateView)
|
||||
)
|
||||
}, [threadgateView, editedAllowUISettings])
|
||||
|
||||
const onSave = React.useCallback(async () => {
|
||||
const onSave = useCallback(async () => {
|
||||
if (!editedPostgate && !editedAllowUISettings) {
|
||||
props.control.close()
|
||||
return
|
||||
@@ -248,15 +244,24 @@ export function PostInteractionSettingsDialogControlledInner(
|
||||
return (
|
||||
<Dialog.ScrollableInner
|
||||
label={_(msg`Edit post interaction settings`)}
|
||||
style={[{maxWidth: 500}, a.w_full]}>
|
||||
<View style={[a.gap_md]}>
|
||||
<Header />
|
||||
|
||||
{isLoading ? (
|
||||
<View style={[a.flex_1, a.py_4xl, a.align_center, a.justify_center]}>
|
||||
<Loader size="xl" />
|
||||
</View>
|
||||
) : (
|
||||
style={[web({maxWidth: 400}), a.w_full]}>
|
||||
{isLoading ? (
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.py_5xl,
|
||||
a.gap_md,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
]}>
|
||||
<Loader size="xl" />
|
||||
<Text style={[a.italic, a.text_center]}>
|
||||
<Trans>Loading post interaction settings...</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<Header />
|
||||
<PostInteractionSettingsForm
|
||||
replySettingsDisabled={!isThreadgateOwnedByViewer}
|
||||
isSaving={isSaving}
|
||||
@@ -266,8 +271,9 @@ export function PostInteractionSettingsDialogControlledInner(
|
||||
threadgateAllowUISettings={allowUIValue}
|
||||
onChangeThreadgateAllowUISettings={setEditedAllowUISettings}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
<Dialog.Close />
|
||||
</Dialog.ScrollableInner>
|
||||
)
|
||||
}
|
||||
@@ -281,11 +287,20 @@ export function PostInteractionSettingsForm({
|
||||
threadgateAllowUISettings,
|
||||
onChangeThreadgateAllowUISettings,
|
||||
replySettingsDisabled,
|
||||
isDirty,
|
||||
persist,
|
||||
onChangePersist,
|
||||
}: PostInteractionSettingsFormProps) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {data: lists} = useMyListsQuery('curate')
|
||||
const [quotesEnabled, setQuotesEnabled] = React.useState(
|
||||
const playHaptic = useHaptics()
|
||||
const [showLists, setShowLists] = useState(false)
|
||||
const {
|
||||
data: lists,
|
||||
isPending: isListsPending,
|
||||
isError: isListsError,
|
||||
} = useMyListsQuery('curate')
|
||||
const [quotesEnabled, setQuotesEnabled] = useState(
|
||||
!(
|
||||
postgate.embeddingRules &&
|
||||
postgate.embeddingRules.find(
|
||||
@@ -294,27 +309,7 @@ export function PostInteractionSettingsForm({
|
||||
),
|
||||
)
|
||||
|
||||
const onPressAudience = (setting: ThreadgateAllowUISetting) => {
|
||||
// remove boolean values
|
||||
let newSelected: ThreadgateAllowUISetting[] =
|
||||
threadgateAllowUISettings.filter(
|
||||
v => v.type !== 'nobody' && v.type !== 'everybody',
|
||||
)
|
||||
// toggle
|
||||
const i = newSelected.findIndex(v => isEqual(v, setting))
|
||||
if (i === -1) {
|
||||
newSelected.push(setting)
|
||||
} else {
|
||||
newSelected.splice(i, 1)
|
||||
}
|
||||
if (newSelected.length === 0) {
|
||||
newSelected.push({type: 'everybody'})
|
||||
}
|
||||
|
||||
onChangeThreadgateAllowUISettings(newSelected)
|
||||
}
|
||||
|
||||
const onChangeQuotesEnabled = React.useCallback(
|
||||
const onChangeQuotesEnabled = useCallback(
|
||||
(enabled: boolean) => {
|
||||
setQuotesEnabled(enabled)
|
||||
onChangePostgate(
|
||||
@@ -330,229 +325,347 @@ export function PostInteractionSettingsForm({
|
||||
const noOneCanReply = !!threadgateAllowUISettings.find(
|
||||
v => v.type === 'nobody',
|
||||
)
|
||||
const everyoneCanReply = !!threadgateAllowUISettings.find(
|
||||
v => v.type === 'everybody',
|
||||
)
|
||||
const numberOfListsSelected = threadgateAllowUISettings.filter(
|
||||
v => v.type === 'list',
|
||||
).length
|
||||
|
||||
const toggleGroupValues = useMemo(() => {
|
||||
const values: string[] = []
|
||||
for (const setting of threadgateAllowUISettings) {
|
||||
switch (setting.type) {
|
||||
case 'everybody':
|
||||
case 'nobody':
|
||||
// no granularity, early return with nothing
|
||||
return []
|
||||
case 'followers':
|
||||
values.push('followers')
|
||||
break
|
||||
case 'following':
|
||||
values.push('following')
|
||||
break
|
||||
case 'mention':
|
||||
values.push('mention')
|
||||
break
|
||||
case 'list':
|
||||
values.push(`list:${setting.list}`)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return values
|
||||
}, [threadgateAllowUISettings])
|
||||
|
||||
const toggleGroupOnChange = (values: string[]) => {
|
||||
const settings: ThreadgateAllowUISetting[] = []
|
||||
|
||||
if (values.length === 0) {
|
||||
settings.push({type: 'everybody'})
|
||||
} else {
|
||||
for (const value of values) {
|
||||
if (value.startsWith('list:')) {
|
||||
const listId = value.slice('list:'.length)
|
||||
settings.push({type: 'list', list: listId})
|
||||
} else {
|
||||
settings.push({type: value as 'followers' | 'following' | 'mention'})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onChangeThreadgateAllowUISettings(settings)
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={[a.flex_1, a.gap_md]}>
|
||||
<View style={[a.gap_lg]}>
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text style={[a.font_semi_bold, a.text_lg]}>
|
||||
<Trans>Quote settings</Trans>
|
||||
</Text>
|
||||
|
||||
<Toggle.Item
|
||||
name="quoteposts"
|
||||
type="checkbox"
|
||||
label={
|
||||
quotesEnabled
|
||||
? _(msg`Click to disable quote posts of this post.`)
|
||||
: _(msg`Click to enable quote posts of this post.`)
|
||||
}
|
||||
value={quotesEnabled}
|
||||
onChange={onChangeQuotesEnabled}
|
||||
style={[a.justify_between, a.pt_xs]}>
|
||||
<Text style={[t.atoms.text_contrast_medium]}>
|
||||
<Trans>Allow quote posts</Trans>
|
||||
</Text>
|
||||
<Toggle.Switch />
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
|
||||
<Divider />
|
||||
|
||||
{replySettingsDisabled && (
|
||||
<View
|
||||
style={[
|
||||
a.px_md,
|
||||
a.py_sm,
|
||||
a.rounded_sm,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.gap_sm,
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<CircleInfo fill={t.atoms.text_contrast_low.color} />
|
||||
<Text
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.leading_snug,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
<Trans>
|
||||
Reply settings are chosen by the author of the thread
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={[a.flex_1, a.gap_lg]}>
|
||||
<View style={[a.gap_lg]}>
|
||||
{replySettingsDisabled && (
|
||||
<View
|
||||
style={[
|
||||
a.px_md,
|
||||
a.py_sm,
|
||||
a.rounded_sm,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.gap_sm,
|
||||
{
|
||||
opacity: replySettingsDisabled ? 0.3 : 1,
|
||||
},
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<Text style={[a.font_semi_bold, a.text_lg]}>
|
||||
<Trans>Reply settings</Trans>
|
||||
<CircleInfo fill={t.atoms.text_contrast_low.color} />
|
||||
<Text
|
||||
style={[a.flex_1, a.leading_snug, t.atoms.text_contrast_medium]}>
|
||||
<Trans>
|
||||
Reply settings are chosen by the author of the thread
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Text style={[a.pt_sm, t.atoms.text_contrast_medium]}>
|
||||
<Trans>Allow replies from:</Trans>
|
||||
</Text>
|
||||
|
||||
<View style={[a.flex_row, a.gap_sm]}>
|
||||
<Selectable
|
||||
label={_(msg`Everybody`)}
|
||||
isSelected={
|
||||
!!threadgateAllowUISettings.find(v => v.type === 'everybody')
|
||||
}
|
||||
onPress={() =>
|
||||
onChangeThreadgateAllowUISettings([{type: 'everybody'}])
|
||||
}
|
||||
style={{flex: 1}}
|
||||
disabled={replySettingsDisabled}
|
||||
/>
|
||||
<Selectable
|
||||
label={_(msg`Nobody`)}
|
||||
isSelected={noOneCanReply}
|
||||
onPress={() =>
|
||||
onChangeThreadgateAllowUISettings([{type: 'nobody'}])
|
||||
}
|
||||
style={{flex: 1}}
|
||||
disabled={replySettingsDisabled}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{!noOneCanReply && (
|
||||
<>
|
||||
<Text style={[a.pt_sm, t.atoms.text_contrast_medium]}>
|
||||
<Trans>Or combine these options:</Trans>
|
||||
</Text>
|
||||
|
||||
<View style={[a.gap_sm]}>
|
||||
<Selectable
|
||||
label={_(msg`Mentioned users`)}
|
||||
isSelected={
|
||||
!!threadgateAllowUISettings.find(
|
||||
v => v.type === 'mention',
|
||||
)
|
||||
}
|
||||
onPress={() => onPressAudience({type: 'mention'})}
|
||||
disabled={replySettingsDisabled}
|
||||
/>
|
||||
<Selectable
|
||||
label={_(msg`Users you follow`)}
|
||||
isSelected={
|
||||
!!threadgateAllowUISettings.find(
|
||||
v => v.type === 'following',
|
||||
)
|
||||
}
|
||||
onPress={() => onPressAudience({type: 'following'})}
|
||||
disabled={replySettingsDisabled}
|
||||
/>
|
||||
<Selectable
|
||||
label={_(msg`Your followers`)}
|
||||
isSelected={
|
||||
!!threadgateAllowUISettings.find(
|
||||
v => v.type === 'followers',
|
||||
)
|
||||
}
|
||||
onPress={() => onPressAudience({type: 'followers'})}
|
||||
disabled={replySettingsDisabled}
|
||||
/>
|
||||
{lists && lists.length > 0
|
||||
? lists.map(list => (
|
||||
<Selectable
|
||||
key={list.uri}
|
||||
label={_(msg`Users in "${list.name}"`)}
|
||||
isSelected={
|
||||
!!threadgateAllowUISettings.find(
|
||||
v => v.type === 'list' && v.list === list.uri,
|
||||
)
|
||||
}
|
||||
onPress={() =>
|
||||
onPressAudience({type: 'list', list: list.uri})
|
||||
}
|
||||
disabled={replySettingsDisabled}
|
||||
/>
|
||||
))
|
||||
: // No loading states to avoid jumps for the common case (no lists)
|
||||
null}
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={[a.gap_sm, {opacity: replySettingsDisabled ? 0.3 : 1}]}>
|
||||
<Text style={[a.text_md, a.font_medium]}>
|
||||
<Trans>Who can reply</Trans>
|
||||
</Text>
|
||||
|
||||
<Toggle.Group
|
||||
label={_(msg`Set who can reply to your post`)}
|
||||
type="radio"
|
||||
maxSelections={1}
|
||||
disabled={replySettingsDisabled}
|
||||
values={
|
||||
everyoneCanReply ? ['everyone'] : noOneCanReply ? ['nobody'] : []
|
||||
}
|
||||
onChange={val => {
|
||||
if (val.includes('everyone')) {
|
||||
onChangeThreadgateAllowUISettings([{type: 'everybody'}])
|
||||
} else if (val.includes('nobody')) {
|
||||
onChangeThreadgateAllowUISettings([{type: 'nobody'}])
|
||||
} else {
|
||||
onChangeThreadgateAllowUISettings([{type: 'mention'}])
|
||||
}
|
||||
}}>
|
||||
<View style={[a.flex_row, a.gap_sm]}>
|
||||
<Toggle.Item
|
||||
name="everyone"
|
||||
type="checkbox"
|
||||
label={_(msg`Allow anyone to reply`)}
|
||||
style={[a.flex_1]}>
|
||||
{({selected}) => (
|
||||
<Toggle.Panel active={selected}>
|
||||
<Toggle.Radio />
|
||||
<Toggle.PanelText>
|
||||
<Trans>Anyone</Trans>
|
||||
</Toggle.PanelText>
|
||||
</Toggle.Panel>
|
||||
)}
|
||||
</Toggle.Item>
|
||||
<Toggle.Item
|
||||
name="nobody"
|
||||
type="checkbox"
|
||||
label={_(msg`Disable replies entirely`)}
|
||||
style={[a.flex_1]}>
|
||||
{({selected}) => (
|
||||
<Toggle.Panel active={selected}>
|
||||
<Toggle.Radio />
|
||||
<Toggle.PanelText>
|
||||
<Trans>Nobody</Trans>
|
||||
</Toggle.PanelText>
|
||||
</Toggle.Panel>
|
||||
)}
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
|
||||
<Toggle.Group
|
||||
label={_(
|
||||
msg`Set precisely which groups of people can reply to your post`,
|
||||
)}
|
||||
values={toggleGroupValues}
|
||||
onChange={toggleGroupOnChange}
|
||||
disabled={replySettingsDisabled}>
|
||||
<Toggle.PanelGroup>
|
||||
<Toggle.Item
|
||||
name="followers"
|
||||
type="checkbox"
|
||||
label={_(msg`Allow your followers to reply`)}
|
||||
hitSlop={0}>
|
||||
{({selected}) => (
|
||||
<Toggle.Panel active={selected} adjacent="trailing">
|
||||
<Toggle.Checkbox />
|
||||
<Toggle.PanelText>
|
||||
<Trans>Your followers</Trans>
|
||||
</Toggle.PanelText>
|
||||
</Toggle.Panel>
|
||||
)}
|
||||
</Toggle.Item>
|
||||
<Toggle.Item
|
||||
name="following"
|
||||
type="checkbox"
|
||||
label={_(msg`Allow people you follow to reply`)}
|
||||
hitSlop={0}>
|
||||
{({selected}) => (
|
||||
<Toggle.Panel active={selected} adjacent="both">
|
||||
<Toggle.Checkbox />
|
||||
<Toggle.PanelText>
|
||||
<Trans>People you follow</Trans>
|
||||
</Toggle.PanelText>
|
||||
</Toggle.Panel>
|
||||
)}
|
||||
</Toggle.Item>
|
||||
<Toggle.Item
|
||||
name="mention"
|
||||
type="checkbox"
|
||||
label={_(msg`Allow people you mention to reply`)}
|
||||
hitSlop={0}>
|
||||
{({selected}) => (
|
||||
<Toggle.Panel active={selected} adjacent="both">
|
||||
<Toggle.Checkbox />
|
||||
<Toggle.PanelText>
|
||||
<Trans>People you mention</Trans>
|
||||
</Toggle.PanelText>
|
||||
</Toggle.Panel>
|
||||
)}
|
||||
</Toggle.Item>
|
||||
|
||||
<Button
|
||||
label={
|
||||
showLists
|
||||
? _(msg`Hide lists`)
|
||||
: _(msg`Show lists of users to select from`)
|
||||
}
|
||||
accessibilityHint={_(msg`Toggle showing lists`)}
|
||||
accessibilityRole="togglebutton"
|
||||
hitSlop={0}
|
||||
onPress={() => {
|
||||
playHaptic('Light')
|
||||
if (isIOS && !showLists) {
|
||||
LayoutAnimation.configureNext({
|
||||
...LayoutAnimation.Presets.linear,
|
||||
duration: 175,
|
||||
})
|
||||
}
|
||||
setShowLists(s => !s)
|
||||
}}>
|
||||
<Toggle.Panel
|
||||
active={numberOfListsSelected > 0}
|
||||
adjacent={showLists ? 'both' : 'leading'}>
|
||||
<Toggle.PanelText>
|
||||
{numberOfListsSelected === 0 ? (
|
||||
<Trans>Select from your lists</Trans>
|
||||
) : (
|
||||
<Trans>
|
||||
Select from your lists{' '}
|
||||
<NestedText style={[a.font_normal, a.italic]}>
|
||||
<Plural
|
||||
value={numberOfListsSelected}
|
||||
other="(# selected)"
|
||||
/>
|
||||
</NestedText>
|
||||
</Trans>
|
||||
)}
|
||||
</Toggle.PanelText>
|
||||
<Toggle.PanelIcon
|
||||
icon={showLists ? ChevronUpIcon : ChevronDownIcon}
|
||||
/>
|
||||
</Toggle.Panel>
|
||||
</Button>
|
||||
{showLists &&
|
||||
(isListsPending ? (
|
||||
<Toggle.Panel>
|
||||
<Toggle.PanelText>
|
||||
<Trans>Loading lists...</Trans>
|
||||
</Toggle.PanelText>
|
||||
</Toggle.Panel>
|
||||
) : isListsError ? (
|
||||
<Toggle.Panel>
|
||||
<Toggle.PanelText>
|
||||
<Trans>
|
||||
An error occurred while loading your lists :/
|
||||
</Trans>
|
||||
</Toggle.PanelText>
|
||||
</Toggle.Panel>
|
||||
) : lists.length === 0 ? (
|
||||
<Toggle.Panel>
|
||||
<Toggle.PanelText>
|
||||
<Trans>You don't have any lists yet.</Trans>
|
||||
</Toggle.PanelText>
|
||||
</Toggle.Panel>
|
||||
) : (
|
||||
lists.map((list, i) => (
|
||||
<Toggle.Item
|
||||
key={list.uri}
|
||||
name={`list:${list.uri}`}
|
||||
type="checkbox"
|
||||
label={_(msg`Allow users in ${list.name} to reply`)}
|
||||
hitSlop={0}>
|
||||
{({selected}) => (
|
||||
<Toggle.Panel
|
||||
active={selected}
|
||||
adjacent={
|
||||
i === lists.length - 1 ? 'leading' : 'both'
|
||||
}>
|
||||
<Toggle.Checkbox />
|
||||
<UserAvatar
|
||||
size={24}
|
||||
type="list"
|
||||
avatar={list.avatar}
|
||||
/>
|
||||
<Toggle.PanelText>{list.name}</Toggle.PanelText>
|
||||
</Toggle.Panel>
|
||||
)}
|
||||
</Toggle.Item>
|
||||
))
|
||||
))}
|
||||
</Toggle.PanelGroup>
|
||||
</Toggle.Group>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Toggle.Item
|
||||
name="quoteposts"
|
||||
type="checkbox"
|
||||
label={
|
||||
quotesEnabled
|
||||
? _(msg`Disable quote posts of this post.`)
|
||||
: _(msg`Enable quote posts of this post.`)
|
||||
}
|
||||
value={quotesEnabled}
|
||||
onChange={onChangeQuotesEnabled}>
|
||||
{({selected}) => (
|
||||
<Toggle.Panel active={selected}>
|
||||
<Toggle.PanelText icon={QuoteIcon}>
|
||||
<Trans>Allow quote posts</Trans>
|
||||
</Toggle.PanelText>
|
||||
<Toggle.Switch />
|
||||
</Toggle.Panel>
|
||||
)}
|
||||
</Toggle.Item>
|
||||
|
||||
{typeof persist !== 'undefined' && (
|
||||
<View style={[{minHeight: 24}, a.justify_center]}>
|
||||
{isDirty ? (
|
||||
<Toggle.Item
|
||||
name="persist"
|
||||
type="checkbox"
|
||||
label={_(msg`Save these options for next time`)}
|
||||
value={persist}
|
||||
onChange={() => onChangePersist?.(!persist)}>
|
||||
<Toggle.Checkbox />
|
||||
<Toggle.LabelText
|
||||
style={[a.text_md, a.font_normal, t.atoms.text]}>
|
||||
<Trans>Save these options for next time</Trans>
|
||||
</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
) : (
|
||||
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
||||
<Trans>These are your default settings</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
<Button
|
||||
disabled={!canSave || isSaving}
|
||||
label={_(msg`Save`)}
|
||||
onPress={onSave}
|
||||
color="primary"
|
||||
size="large"
|
||||
variant="solid"
|
||||
style={a.mt_xl}>
|
||||
<ButtonText>{_(msg`Save`)}</ButtonText>
|
||||
{isSaving && <ButtonIcon icon={Loader} position="right" />}
|
||||
size="large">
|
||||
<ButtonText>
|
||||
<Trans>Save</Trans>
|
||||
</ButtonText>
|
||||
{isSaving && <ButtonIcon icon={Loader} />}
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function Selectable({
|
||||
label,
|
||||
isSelected,
|
||||
onPress,
|
||||
style,
|
||||
disabled,
|
||||
}: {
|
||||
label: string
|
||||
isSelected: boolean
|
||||
onPress: () => void
|
||||
style?: StyleProp<ViewStyle>
|
||||
disabled?: boolean
|
||||
}) {
|
||||
const t = useTheme()
|
||||
function Header() {
|
||||
return (
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onPress={onPress}
|
||||
label={label}
|
||||
accessibilityRole="checkbox"
|
||||
aria-checked={isSelected}
|
||||
accessibilityState={{
|
||||
checked: isSelected,
|
||||
}}
|
||||
style={a.flex_1}>
|
||||
{({hovered, focused}) => (
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
a.rounded_sm,
|
||||
a.p_md,
|
||||
{minHeight: 40}, // for consistency with checkmark icon visible or not
|
||||
t.atoms.bg_contrast_50,
|
||||
(hovered || focused) && t.atoms.bg_contrast_100,
|
||||
isSelected && {
|
||||
backgroundColor: t.palette.primary_100,
|
||||
},
|
||||
style,
|
||||
]}>
|
||||
<Text style={[a.text_sm, isSelected && a.font_semi_bold]}>
|
||||
{label}
|
||||
</Text>
|
||||
{isSelected ? (
|
||||
<Check size="sm" fill={t.palette.primary_500} />
|
||||
) : (
|
||||
<View />
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</Button>
|
||||
<View style={[a.pb_lg]}>
|
||||
<Text style={[a.text_2xl, a.font_bold]}>
|
||||
<Trans>Post interaction settings</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -567,7 +680,7 @@ export function usePrefetchPostInteractionSettings({
|
||||
const agent = useAgent()
|
||||
const getPost = useGetPost()
|
||||
|
||||
return React.useCallback(async () => {
|
||||
return useCallback(async () => {
|
||||
try {
|
||||
await Promise.all([
|
||||
queryClient.prefetchQuery({
|
||||
|
||||
Reference in New Issue
Block a user