Update settings screen
This commit is contained in:
@@ -2,11 +2,17 @@ import {View} from 'react-native'
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
import {type CommonNavigatorParams, type NativeStackScreenProps} from '#/lib/routes/types'
|
||||||
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {
|
import {
|
||||||
usePreferencesQuery,
|
usePreferencesQuery,
|
||||||
useSetThreadViewPreferencesMutation,
|
useSetThreadViewPreferencesMutation,
|
||||||
} from '#/state/queries/preferences'
|
} from '#/state/queries/preferences'
|
||||||
|
import {
|
||||||
|
normalizeSort,
|
||||||
|
normalizeView,
|
||||||
|
useThreadPreferences,
|
||||||
|
} from '#/state/queries/preferences/useThreadPreferences'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import * as Toggle from '#/components/forms/Toggle'
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
import {Beaker_Stroke2_Corner2_Rounded as BeakerIcon} from '#/components/icons/Beaker'
|
import {Beaker_Stroke2_Corner2_Rounded as BeakerIcon} from '#/components/icons/Beaker'
|
||||||
@@ -18,6 +24,130 @@ import * as SettingsList from './components/SettingsList'
|
|||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PreferencesThreads'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PreferencesThreads'>
|
||||||
export function ThreadPreferencesScreen({}: Props) {
|
export function ThreadPreferencesScreen({}: Props) {
|
||||||
|
const gate = useGate()
|
||||||
|
|
||||||
|
return gate('post_threads_v2_unspecced') ? (
|
||||||
|
<ThreadPreferencesV2 />
|
||||||
|
) : (
|
||||||
|
<ThreadPreferencesV1 />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ThreadPreferencesV2() {
|
||||||
|
const t = useTheme()
|
||||||
|
const {_} = useLingui()
|
||||||
|
const {
|
||||||
|
sort,
|
||||||
|
setSort,
|
||||||
|
view,
|
||||||
|
setView,
|
||||||
|
prioritizeFollowedUsers,
|
||||||
|
setPrioritizeFollowedUsers,
|
||||||
|
} = useThreadPreferences({save: true})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout.Screen testID="threadPreferencesScreen">
|
||||||
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>Thread Preferences</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
|
<Layout.Content>
|
||||||
|
<SettingsList.Container>
|
||||||
|
<SettingsList.Group>
|
||||||
|
<SettingsList.ItemIcon icon={BubblesIcon} />
|
||||||
|
<SettingsList.ItemText>
|
||||||
|
<Trans>Sort replies</Trans>
|
||||||
|
</SettingsList.ItemText>
|
||||||
|
<View style={[a.w_full, a.gap_md]}>
|
||||||
|
<Text style={[a.flex_1, t.atoms.text_contrast_medium]}>
|
||||||
|
<Trans>Sort replies to the same post by:</Trans>
|
||||||
|
</Text>
|
||||||
|
<Toggle.Group
|
||||||
|
label={_(msg`Sort replies by`)}
|
||||||
|
type="radio"
|
||||||
|
values={sort ? [sort] : []}
|
||||||
|
onChange={values => setSort(normalizeSort(values[0]))}>
|
||||||
|
<View style={[a.gap_sm, a.flex_1]}>
|
||||||
|
<Toggle.Item name="top" label={_(msg`Top replies first`)}>
|
||||||
|
<Toggle.Radio />
|
||||||
|
<Toggle.LabelText>
|
||||||
|
<Trans>Top replies first</Trans>
|
||||||
|
</Toggle.LabelText>
|
||||||
|
</Toggle.Item>
|
||||||
|
<Toggle.Item
|
||||||
|
name="oldest"
|
||||||
|
label={_(msg`Oldest replies first`)}>
|
||||||
|
<Toggle.Radio />
|
||||||
|
<Toggle.LabelText>
|
||||||
|
<Trans>Oldest replies first</Trans>
|
||||||
|
</Toggle.LabelText>
|
||||||
|
</Toggle.Item>
|
||||||
|
<Toggle.Item
|
||||||
|
name="newest"
|
||||||
|
label={_(msg`Newest replies first`)}>
|
||||||
|
<Toggle.Radio />
|
||||||
|
<Toggle.LabelText>
|
||||||
|
<Trans>Newest replies first</Trans>
|
||||||
|
</Toggle.LabelText>
|
||||||
|
</Toggle.Item>
|
||||||
|
</View>
|
||||||
|
</Toggle.Group>
|
||||||
|
</View>
|
||||||
|
</SettingsList.Group>
|
||||||
|
|
||||||
|
<SettingsList.Group contentContainerStyle={{minHeight: 0}}>
|
||||||
|
<SettingsList.ItemIcon icon={PersonGroupIcon} />
|
||||||
|
<SettingsList.ItemText>
|
||||||
|
<Trans>Prioritize your Follows</Trans>
|
||||||
|
</SettingsList.ItemText>
|
||||||
|
<Toggle.Item
|
||||||
|
type="checkbox"
|
||||||
|
name="prioritize-follows"
|
||||||
|
label={_(msg`Prioritize your Follows`)}
|
||||||
|
value={prioritizeFollowedUsers}
|
||||||
|
onChange={value => setPrioritizeFollowedUsers(value)}
|
||||||
|
style={[a.w_full, a.gap_md]}>
|
||||||
|
<Toggle.LabelText style={[a.flex_1]}>
|
||||||
|
<Trans>
|
||||||
|
Show replies by people you follow before all other replies
|
||||||
|
</Trans>
|
||||||
|
</Toggle.LabelText>
|
||||||
|
<Toggle.Platform />
|
||||||
|
</Toggle.Item>
|
||||||
|
</SettingsList.Group>
|
||||||
|
|
||||||
|
<SettingsList.Group>
|
||||||
|
<SettingsList.ItemIcon icon={BeakerIcon} />
|
||||||
|
<SettingsList.ItemText>
|
||||||
|
<Trans>Tree view</Trans>
|
||||||
|
</SettingsList.ItemText>
|
||||||
|
<Toggle.Item
|
||||||
|
type="checkbox"
|
||||||
|
name="threaded-mode"
|
||||||
|
label={_(msg`Threaded mode`)}
|
||||||
|
value={view === 'tree'}
|
||||||
|
onChange={value =>
|
||||||
|
setView(normalizeView({treeViewEnabled: value}))
|
||||||
|
}
|
||||||
|
style={[a.w_full, a.gap_md]}>
|
||||||
|
<Toggle.LabelText style={[a.flex_1]}>
|
||||||
|
<Trans>Show replies as threaded</Trans>
|
||||||
|
</Toggle.LabelText>
|
||||||
|
<Toggle.Platform />
|
||||||
|
</Toggle.Item>
|
||||||
|
</SettingsList.Group>
|
||||||
|
</SettingsList.Container>
|
||||||
|
</Layout.Content>
|
||||||
|
</Layout.Screen>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ThreadPreferencesV1() {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import {useMemo, useState} from 'react'
|
import {useCallback, useMemo, useState} from 'react'
|
||||||
import {type AppBskyUnspeccedGetPostThreadV2} from '@atproto/api'
|
import {type AppBskyUnspeccedGetPostThreadV2} from '@atproto/api'
|
||||||
|
import debounce from 'lodash.debounce'
|
||||||
|
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
import {logger} from '#/logger'
|
||||||
|
import {
|
||||||
|
usePreferencesQuery,
|
||||||
|
useSetThreadViewPreferencesMutation,
|
||||||
|
} from '#/state/queries/preferences'
|
||||||
|
import {type ThreadViewPreferences} from '#/state/queries/preferences/types'
|
||||||
import {type Literal} from '#/types/utils'
|
import {type Literal} from '#/types/utils'
|
||||||
|
|
||||||
export type ThreadSortOption = Literal<
|
export type ThreadSortOption = Literal<
|
||||||
@@ -11,59 +17,101 @@ export type ThreadSortOption = Literal<
|
|||||||
export type ThreadViewOption = 'linear' | 'tree'
|
export type ThreadViewOption = 'linear' | 'tree'
|
||||||
export type ThreadPreferences = {
|
export type ThreadPreferences = {
|
||||||
isLoaded: boolean
|
isLoaded: boolean
|
||||||
|
isSaving: boolean
|
||||||
sort: ThreadSortOption
|
sort: ThreadSortOption
|
||||||
setSort: (sort: ThreadSortOption) => void
|
setSort: (sort: string) => void
|
||||||
view: ThreadViewOption
|
view: ThreadViewOption
|
||||||
setView: (view: ThreadViewOption) => void
|
setView: (view: ThreadViewOption) => void
|
||||||
prioritizeFollowedUsers: boolean
|
prioritizeFollowedUsers: boolean
|
||||||
setPrioritizeFollowedUsers: (prioritize: boolean) => void
|
setPrioritizeFollowedUsers: (prioritize: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useThreadPreferences(): ThreadPreferences {
|
export function useThreadPreferences({
|
||||||
|
save,
|
||||||
|
}: {save?: boolean} = {}): ThreadPreferences {
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
const nextThreadPreferences = preferences?.threadViewPrefs
|
const serverPrefs = preferences?.threadViewPrefs
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create local state representations of server state
|
* Create local state representations of server state
|
||||||
*/
|
*/
|
||||||
const [sort, setSort] = useState(
|
const [sort, setSort] = useState(normalizeSort(serverPrefs?.sort || 'top'))
|
||||||
migrateFromSortV1(nextThreadPreferences?.sort || 'top'),
|
|
||||||
)
|
|
||||||
const [view, setView] = useState(
|
const [view, setView] = useState(
|
||||||
computeView({
|
normalizeView({
|
||||||
treeViewEnabled: !!nextThreadPreferences?.lab_treeViewEnabled,
|
treeViewEnabled: !!serverPrefs?.lab_treeViewEnabled,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const [prioritizeFollowedUsers, setPrioritizeFollowedUsers] = useState(
|
const [prioritizeFollowedUsers, setPrioritizeFollowedUsers] = useState(
|
||||||
!!nextThreadPreferences?.prioritizeFollowedUsers,
|
!!serverPrefs?.prioritizeFollowedUsers,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache existing and if we get a server update, reset local state
|
* If we get a server update, update local state
|
||||||
*/
|
*/
|
||||||
const [prevServerPrefs, setPrevServerPrefs] = useState(nextThreadPreferences)
|
const [prevServerPrefs, setPrevServerPrefs] = useState(serverPrefs)
|
||||||
if (nextThreadPreferences && prevServerPrefs !== nextThreadPreferences) {
|
const isLoaded = !!prevServerPrefs
|
||||||
setPrevServerPrefs(nextThreadPreferences)
|
if (serverPrefs && prevServerPrefs !== serverPrefs) {
|
||||||
|
setPrevServerPrefs(serverPrefs)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Reset
|
* Update
|
||||||
*/
|
*/
|
||||||
setSort(migrateFromSortV1(nextThreadPreferences.sort))
|
setSort(normalizeSort(serverPrefs.sort))
|
||||||
setPrioritizeFollowedUsers(nextThreadPreferences.prioritizeFollowedUsers)
|
setPrioritizeFollowedUsers(serverPrefs.prioritizeFollowedUsers)
|
||||||
setView(
|
setView(
|
||||||
computeView({
|
normalizeView({
|
||||||
treeViewEnabled: !!nextThreadPreferences.lab_treeViewEnabled,
|
treeViewEnabled: !!serverPrefs.lab_treeViewEnabled,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLoaded = !!prevServerPrefs
|
const [isSaving, setIsSaving] = useState(false)
|
||||||
|
const {mutateAsync} = useSetThreadViewPreferencesMutation()
|
||||||
|
const savePrefs = useMemo(() => {
|
||||||
|
return debounce(async (prefs: ThreadViewPreferences) => {
|
||||||
|
try {
|
||||||
|
setIsSaving(true)
|
||||||
|
await mutateAsync(prefs)
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('useThreadPreferences failed to save', {
|
||||||
|
safeMessage: e,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
setIsSaving(false)
|
||||||
|
}
|
||||||
|
}, 4e3)
|
||||||
|
}, [mutateAsync])
|
||||||
|
|
||||||
|
if (save && !isSaving) {
|
||||||
|
if (
|
||||||
|
serverPrefs?.sort !== sort ||
|
||||||
|
serverPrefs?.prioritizeFollowedUsers !== prioritizeFollowedUsers ||
|
||||||
|
serverPrefs?.lab_treeViewEnabled !== (view === 'tree' ? true : false)
|
||||||
|
) {
|
||||||
|
savePrefs({
|
||||||
|
sort,
|
||||||
|
prioritizeFollowedUsers,
|
||||||
|
lab_treeViewEnabled: view === 'tree',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wraped for easier migration
|
||||||
|
*/
|
||||||
|
const setSortWrapped = useCallback(
|
||||||
|
(next: string) => {
|
||||||
|
setSort(normalizeSort(next))
|
||||||
|
},
|
||||||
|
[setSort],
|
||||||
|
)
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
isLoaded,
|
isLoaded,
|
||||||
|
isSaving,
|
||||||
sort,
|
sort,
|
||||||
setSort,
|
setSort: setSortWrapped,
|
||||||
prioritizeFollowedUsers,
|
prioritizeFollowedUsers,
|
||||||
setPrioritizeFollowedUsers,
|
setPrioritizeFollowedUsers,
|
||||||
view,
|
view,
|
||||||
@@ -71,8 +119,9 @@ export function useThreadPreferences(): ThreadPreferences {
|
|||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
isLoaded,
|
isLoaded,
|
||||||
|
isSaving,
|
||||||
sort,
|
sort,
|
||||||
setSort,
|
setSortWrapped,
|
||||||
prioritizeFollowedUsers,
|
prioritizeFollowedUsers,
|
||||||
setPrioritizeFollowedUsers,
|
setPrioritizeFollowedUsers,
|
||||||
view,
|
view,
|
||||||
@@ -84,7 +133,7 @@ export function useThreadPreferences(): ThreadPreferences {
|
|||||||
/**
|
/**
|
||||||
* Migrates user thread preferences from the old sort values to V2
|
* Migrates user thread preferences from the old sort values to V2
|
||||||
*/
|
*/
|
||||||
function migrateFromSortV1(sort: string): ThreadSortOption {
|
export function normalizeSort(sort: string): ThreadSortOption {
|
||||||
switch (sort) {
|
switch (sort) {
|
||||||
case 'oldest':
|
case 'oldest':
|
||||||
return 'oldest'
|
return 'oldest'
|
||||||
@@ -98,7 +147,7 @@ function migrateFromSortV1(sort: string): ThreadSortOption {
|
|||||||
/**
|
/**
|
||||||
* Transforms existing treeViewEnabled preference into a ThreadViewOption
|
* Transforms existing treeViewEnabled preference into a ThreadViewOption
|
||||||
*/
|
*/
|
||||||
function computeView({
|
export function normalizeView({
|
||||||
treeViewEnabled,
|
treeViewEnabled,
|
||||||
}: {
|
}: {
|
||||||
treeViewEnabled: boolean
|
treeViewEnabled: boolean
|
||||||
|
|||||||
Reference in New Issue
Block a user