Move home feed and thread preferences to server (#1507)

* Move home feed and thread preferences to server

* Fix thread usage of prefs

* Remove log

* Bump @atproto/api@0.6.16

* Improve type usage
zio/stable
Paul Frazee 2023-09-21 21:00:32 -07:00 committed by GitHub
parent 28b692a118
commit 8584009bae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 205 additions and 162 deletions

View File

@ -25,7 +25,7 @@
"build:apk": "eas build -p android --profile dev-android-apk" "build:apk": "eas build -p android --profile dev-android-apk"
}, },
"dependencies": { "dependencies": {
"@atproto/api": "^0.6.14", "@atproto/api": "^0.6.16",
"@bam.tech/react-native-image-resizer": "^3.0.4", "@bam.tech/react-native-image-resizer": "^3.0.4",
"@braintree/sanitize-url": "^6.0.2", "@braintree/sanitize-url": "^6.0.2",
"@emoji-mart/react": "^1.1.1", "@emoji-mart/react": "^1.1.1",

View File

@ -109,7 +109,7 @@ export class MergeFeedAPI implements FeedAPI {
} }
_captureFeedsIfNeeded() { _captureFeedsIfNeeded() {
if (!this.rootStore.preferences.homeFeedMergeFeedEnabled) { if (!this.rootStore.preferences.homeFeed.lab_mergeFeedEnabled) {
return return
} }
if (this.customFeeds.length === 0) { if (this.customFeeds.length === 0) {

View File

@ -8,6 +8,7 @@ import {AtUri} from '@atproto/api'
import {RootStoreModel} from '../root-store' import {RootStoreModel} from '../root-store'
import * as apilib from 'lib/api/index' import * as apilib from 'lib/api/index'
import {cleanError} from 'lib/strings/errors' import {cleanError} from 'lib/strings/errors'
import {ThreadViewPreference} from '../ui/preferences'
import {PostThreadItemModel} from './post-thread-item' import {PostThreadItemModel} from './post-thread-item'
export class PostThreadModel { export class PostThreadModel {
@ -241,7 +242,7 @@ export class PostThreadModel {
res.data.thread as AppBskyFeedDefs.ThreadViewPost, res.data.thread as AppBskyFeedDefs.ThreadViewPost,
thread.uri, thread.uri,
) )
sortThread(thread, this.rootStore.preferences) sortThread(thread, this.rootStore.preferences.thread)
this.thread = thread this.thread = thread
} }
} }
@ -263,16 +264,11 @@ function pruneReplies(post: MaybePost) {
} }
} }
interface SortSettings {
threadDefaultSort: string
threadFollowedUsersFirst: boolean
}
type MaybeThreadItem = type MaybeThreadItem =
| PostThreadItemModel | PostThreadItemModel
| AppBskyFeedDefs.NotFoundPost | AppBskyFeedDefs.NotFoundPost
| AppBskyFeedDefs.BlockedPost | AppBskyFeedDefs.BlockedPost
function sortThread(item: MaybeThreadItem, opts: SortSettings) { function sortThread(item: MaybeThreadItem, opts: ThreadViewPreference) {
if ('notFound' in item) { if ('notFound' in item) {
return return
} }
@ -301,7 +297,7 @@ function sortThread(item: MaybeThreadItem, opts: SortSettings) {
if (modScore(a.moderation) !== modScore(b.moderation)) { if (modScore(a.moderation) !== modScore(b.moderation)) {
return modScore(a.moderation) - modScore(b.moderation) return modScore(a.moderation) - modScore(b.moderation)
} }
if (opts.threadFollowedUsersFirst) { if (opts.prioritizeFollowedUsers) {
const af = a.post.author.viewer?.following const af = a.post.author.viewer?.following
const bf = b.post.author.viewer?.following const bf = b.post.author.viewer?.following
if (af && !bf) { if (af && !bf) {
@ -310,17 +306,17 @@ function sortThread(item: MaybeThreadItem, opts: SortSettings) {
return 1 return 1
} }
} }
if (opts.threadDefaultSort === 'oldest') { if (opts.sort === 'oldest') {
return a.post.indexedAt.localeCompare(b.post.indexedAt) return a.post.indexedAt.localeCompare(b.post.indexedAt)
} else if (opts.threadDefaultSort === 'newest') { } else if (opts.sort === 'newest') {
return b.post.indexedAt.localeCompare(a.post.indexedAt) return b.post.indexedAt.localeCompare(a.post.indexedAt)
} else if (opts.threadDefaultSort === 'most-likes') { } else if (opts.sort === 'most-likes') {
if (a.post.likeCount === b.post.likeCount) { if (a.post.likeCount === b.post.likeCount) {
return b.post.indexedAt.localeCompare(a.post.indexedAt) // newest return b.post.indexedAt.localeCompare(a.post.indexedAt) // newest
} else { } else {
return (b.post.likeCount || 0) - (a.post.likeCount || 0) // most likes return (b.post.likeCount || 0) - (a.post.likeCount || 0) // most likes
} }
} else if (opts.threadDefaultSort === 'random') { } else if (opts.sort === 'random') {
return 0.5 - Math.random() // this is vaguely criminal but we can get away with it return 0.5 - Math.random() // this is vaguely criminal but we can get away with it
} }
return b.post.indexedAt.localeCompare(a.post.indexedAt) return b.post.indexedAt.localeCompare(a.post.indexedAt)

View File

@ -1,5 +1,9 @@
import {makeAutoObservable, runInAction} from 'mobx' import {makeAutoObservable, runInAction} from 'mobx'
import {LabelPreference as APILabelPreference} from '@atproto/api' import {
LabelPreference as APILabelPreference,
BskyFeedViewPreference,
BskyThreadViewPreference,
} from '@atproto/api'
import AwaitLock from 'await-lock' import AwaitLock from 'await-lock'
import isEqual from 'lodash.isequal' import isEqual from 'lodash.isequal'
import {isObj, hasProp} from 'lib/type-guards' import {isObj, hasProp} from 'lib/type-guards'
@ -13,6 +17,12 @@ import {LANGUAGES} from '../../../locale/languages'
// TEMP we need to permanently convert 'show' to 'ignore', for now we manually convert -prf // TEMP we need to permanently convert 'show' to 'ignore', for now we manually convert -prf
export type LabelPreference = APILabelPreference | 'show' export type LabelPreference = APILabelPreference | 'show'
export type FeedViewPreference = BskyFeedViewPreference & {
lab_mergeFeedEnabled?: boolean | undefined
}
export type ThreadViewPreference = BskyThreadViewPreference & {
lab_treeViewEnabled?: boolean | undefined
}
const LABEL_GROUPS = [ const LABEL_GROUPS = [
'nsfw', 'nsfw',
'nudity', 'nudity',
@ -28,6 +38,13 @@ const DEFAULT_LANG_CODES = (deviceLocales || [])
.slice(0, 6) .slice(0, 6)
const THREAD_SORT_VALUES = ['oldest', 'newest', 'most-likes', 'random'] const THREAD_SORT_VALUES = ['oldest', 'newest', 'most-likes', 'random']
interface LegacyPreferences {
hideReplies?: boolean
hideRepliesByLikeCount?: number
hideReposts?: boolean
hideQuotePosts?: boolean
}
export class LabelPreferencesModel { export class LabelPreferencesModel {
nsfw: LabelPreference = 'hide' nsfw: LabelPreference = 'hide'
nudity: LabelPreference = 'warn' nudity: LabelPreference = 'warn'
@ -52,17 +69,24 @@ export class PreferencesModel {
savedFeeds: string[] = [] savedFeeds: string[] = []
pinnedFeeds: string[] = [] pinnedFeeds: string[] = []
birthDate: Date | undefined = undefined birthDate: Date | undefined = undefined
homeFeedRepliesEnabled: boolean = true homeFeed: FeedViewPreference = {
homeFeedRepliesByFollowedOnlyEnabled: boolean = true hideReplies: false,
homeFeedRepliesThreshold: number = 0 hideRepliesByUnfollowed: false,
homeFeedRepostsEnabled: boolean = true hideRepliesByLikeCount: 0,
homeFeedQuotePostsEnabled: boolean = true hideReposts: false,
homeFeedMergeFeedEnabled: boolean = false hideQuotePosts: false,
threadDefaultSort: string = 'oldest' lab_mergeFeedEnabled: false, // experimental
threadFollowedUsersFirst: boolean = true }
threadTreeViewEnabled: boolean = false thread: ThreadViewPreference = {
sort: 'oldest',
prioritizeFollowedUsers: true,
lab_treeViewEnabled: false, // experimental
}
requireAltTextEnabled: boolean = false requireAltTextEnabled: boolean = false
// used to help with transitions from device-stored to server-stored preferences
legacyPreferences: LegacyPreferences | undefined
// used to linearize async modifications to state // used to linearize async modifications to state
lock = new AwaitLock() lock = new AwaitLock()
@ -86,16 +110,6 @@ export class PreferencesModel {
contentLabels: this.contentLabels, contentLabels: this.contentLabels,
savedFeeds: this.savedFeeds, savedFeeds: this.savedFeeds,
pinnedFeeds: this.pinnedFeeds, pinnedFeeds: this.pinnedFeeds,
homeFeedRepliesEnabled: this.homeFeedRepliesEnabled,
homeFeedRepliesByFollowedOnlyEnabled:
this.homeFeedRepliesByFollowedOnlyEnabled,
homeFeedRepliesThreshold: this.homeFeedRepliesThreshold,
homeFeedRepostsEnabled: this.homeFeedRepostsEnabled,
homeFeedQuotePostsEnabled: this.homeFeedQuotePostsEnabled,
homeFeedMergeFeedEnabled: this.homeFeedMergeFeedEnabled,
threadDefaultSort: this.threadDefaultSort,
threadFollowedUsersFirst: this.threadFollowedUsersFirst,
threadTreeViewEnabled: this.threadTreeViewEnabled,
requireAltTextEnabled: this.requireAltTextEnabled, requireAltTextEnabled: this.requireAltTextEnabled,
} }
} }
@ -165,71 +179,6 @@ export class PreferencesModel {
) { ) {
this.pinnedFeeds = v.pinnedFeeds this.pinnedFeeds = v.pinnedFeeds
} }
// check if home feed replies are enabled in preferences, then hydrate
if (
hasProp(v, 'homeFeedRepliesEnabled') &&
typeof v.homeFeedRepliesEnabled === 'boolean'
) {
this.homeFeedRepliesEnabled = v.homeFeedRepliesEnabled
}
// check if home feed replies "followed only" are enabled in preferences, then hydrate
if (
hasProp(v, 'homeFeedRepliesByFollowedOnlyEnabled') &&
typeof v.homeFeedRepliesByFollowedOnlyEnabled === 'boolean'
) {
this.homeFeedRepliesByFollowedOnlyEnabled =
v.homeFeedRepliesByFollowedOnlyEnabled
}
// check if home feed replies threshold is enabled in preferences, then hydrate
if (
hasProp(v, 'homeFeedRepliesThreshold') &&
typeof v.homeFeedRepliesThreshold === 'number'
) {
this.homeFeedRepliesThreshold = v.homeFeedRepliesThreshold
}
// check if home feed reposts are enabled in preferences, then hydrate
if (
hasProp(v, 'homeFeedRepostsEnabled') &&
typeof v.homeFeedRepostsEnabled === 'boolean'
) {
this.homeFeedRepostsEnabled = v.homeFeedRepostsEnabled
}
// check if home feed quote posts are enabled in preferences, then hydrate
if (
hasProp(v, 'homeFeedQuotePostsEnabled') &&
typeof v.homeFeedQuotePostsEnabled === 'boolean'
) {
this.homeFeedQuotePostsEnabled = v.homeFeedQuotePostsEnabled
}
// check if home feed mergefeed is enabled in preferences, then hydrate
if (
hasProp(v, 'homeFeedMergeFeedEnabled') &&
typeof v.homeFeedMergeFeedEnabled === 'boolean'
) {
this.homeFeedMergeFeedEnabled = v.homeFeedMergeFeedEnabled
}
// check if thread sort order is set in preferences, then hydrate
if (
hasProp(v, 'threadDefaultSort') &&
typeof v.threadDefaultSort === 'string' &&
THREAD_SORT_VALUES.includes(v.threadDefaultSort)
) {
this.threadDefaultSort = v.threadDefaultSort
}
// check if thread followed-users-first is enabled in preferences, then hydrate
if (
hasProp(v, 'threadFollowedUsersFirst') &&
typeof v.threadFollowedUsersFirst === 'boolean'
) {
this.threadFollowedUsersFirst = v.threadFollowedUsersFirst
}
// check if thread treeview is enabled in preferences, then hydrate
if (
hasProp(v, 'threadTreeViewEnabled') &&
typeof v.threadTreeViewEnabled === 'boolean'
) {
this.threadTreeViewEnabled = v.threadTreeViewEnabled
}
// check if requiring alt text is enabled in preferences, then hydrate // check if requiring alt text is enabled in preferences, then hydrate
if ( if (
hasProp(v, 'requireAltTextEnabled') && hasProp(v, 'requireAltTextEnabled') &&
@ -237,6 +186,8 @@ export class PreferencesModel {
) { ) {
this.requireAltTextEnabled = v.requireAltTextEnabled this.requireAltTextEnabled = v.requireAltTextEnabled
} }
// grab legacy values
this.legacyPreferences = getLegacyPreferences(v)
} }
} }
@ -250,6 +201,10 @@ export class PreferencesModel {
const prefs = await this.rootStore.agent.getPreferences() const prefs = await this.rootStore.agent.getPreferences()
runInAction(() => { runInAction(() => {
if (prefs.feedViewPrefs.home) {
this.homeFeed = prefs.feedViewPrefs.home
}
this.thread = prefs.threadViewPrefs
this.adultContentEnabled = prefs.adultContentEnabled this.adultContentEnabled = prefs.adultContentEnabled
for (const label in prefs.contentLabels) { for (const label in prefs.contentLabels) {
if ( if (
@ -272,6 +227,9 @@ export class PreferencesModel {
this.birthDate = prefs.birthDate this.birthDate = prefs.birthDate
}) })
// sync legacy values if needed
await this.syncLegacyPreferences()
// set defaults on missing items // set defaults on missing items
if (typeof prefs.feeds.saved === 'undefined') { if (typeof prefs.feeds.saved === 'undefined') {
try { try {
@ -298,6 +256,14 @@ export class PreferencesModel {
await this.rootStore.me.savedFeeds.updateCache(clearCache) await this.rootStore.me.savedFeeds.updateCache(clearCache)
} }
async syncLegacyPreferences() {
if (this.legacyPreferences) {
this.homeFeed = {...this.homeFeed, ...this.legacyPreferences}
this.legacyPreferences = undefined
await this.rootStore.agent.setFeedViewPrefs('home', this.homeFeed)
}
}
/** /**
* This function resets the preferences to an empty array of no preferences. * This function resets the preferences to an empty array of no preferences.
*/ */
@ -510,43 +476,68 @@ export class PreferencesModel {
await this.rootStore.agent.setPersonalDetails({birthDate}) await this.rootStore.agent.setPersonalDetails({birthDate})
} }
toggleHomeFeedRepliesEnabled() { async toggleHomeFeedHideReplies() {
this.homeFeedRepliesEnabled = !this.homeFeedRepliesEnabled this.homeFeed.hideReplies = !this.homeFeed.hideReplies
await this.rootStore.agent.setFeedViewPrefs('home', {
hideReplies: this.homeFeed.hideReplies,
})
} }
toggleHomeFeedRepliesByFollowedOnlyEnabled() { async toggleHomeFeedHideRepliesByUnfollowed() {
this.homeFeedRepliesByFollowedOnlyEnabled = this.homeFeed.hideRepliesByUnfollowed =
!this.homeFeedRepliesByFollowedOnlyEnabled !this.homeFeed.hideRepliesByUnfollowed
await this.rootStore.agent.setFeedViewPrefs('home', {
hideRepliesByUnfollowed: this.homeFeed.hideRepliesByUnfollowed,
})
} }
setHomeFeedRepliesThreshold(threshold: number) { async setHomeFeedHideRepliesByLikeCount(threshold: number) {
this.homeFeedRepliesThreshold = threshold this.homeFeed.hideRepliesByLikeCount = threshold
await this.rootStore.agent.setFeedViewPrefs('home', {
hideRepliesByLikeCount: this.homeFeed.hideRepliesByLikeCount,
})
} }
toggleHomeFeedRepostsEnabled() { async toggleHomeFeedHideReposts() {
this.homeFeedRepostsEnabled = !this.homeFeedRepostsEnabled this.homeFeed.hideReposts = !this.homeFeed.hideReposts
await this.rootStore.agent.setFeedViewPrefs('home', {
hideReposts: this.homeFeed.hideReposts,
})
} }
toggleHomeFeedQuotePostsEnabled() { async toggleHomeFeedHideQuotePosts() {
this.homeFeedQuotePostsEnabled = !this.homeFeedQuotePostsEnabled this.homeFeed.hideQuotePosts = !this.homeFeed.hideQuotePosts
await this.rootStore.agent.setFeedViewPrefs('home', {
hideQuotePosts: this.homeFeed.hideQuotePosts,
})
} }
toggleHomeFeedMergeFeedEnabled() { async toggleHomeFeedMergeFeedEnabled() {
this.homeFeedMergeFeedEnabled = !this.homeFeedMergeFeedEnabled this.homeFeed.lab_mergeFeedEnabled = !this.homeFeed.lab_mergeFeedEnabled
await this.rootStore.agent.setFeedViewPrefs('home', {
lab_mergeFeedEnabled: this.homeFeed.lab_mergeFeedEnabled,
})
} }
setThreadDefaultSort(v: string) { async setThreadSort(v: string) {
if (THREAD_SORT_VALUES.includes(v)) { if (THREAD_SORT_VALUES.includes(v)) {
this.threadDefaultSort = v this.thread.sort = v
await this.rootStore.agent.setThreadViewPrefs({sort: v})
} }
} }
toggleThreadFollowedUsersFirst() { async togglePrioritizedFollowedUsers() {
this.threadFollowedUsersFirst = !this.threadFollowedUsersFirst this.thread.prioritizeFollowedUsers = !this.thread.prioritizeFollowedUsers
await this.rootStore.agent.setThreadViewPrefs({
prioritizeFollowedUsers: this.thread.prioritizeFollowedUsers,
})
} }
toggleThreadTreeViewEnabled() { async toggleThreadTreeViewEnabled() {
this.threadTreeViewEnabled = !this.threadTreeViewEnabled this.thread.lab_treeViewEnabled = !this.thread.lab_treeViewEnabled
await this.rootStore.agent.setThreadViewPrefs({
lab_treeViewEnabled: this.thread.lab_treeViewEnabled,
})
} }
toggleRequireAltTextEnabled() { toggleRequireAltTextEnabled() {
@ -560,13 +551,6 @@ export class PreferencesModel {
getFeedTuners( getFeedTuners(
feedType: 'home' | 'following' | 'author' | 'custom' | 'likes', feedType: 'home' | 'following' | 'author' | 'custom' | 'likes',
) { ) {
const areRepliesEnabled = this.homeFeedRepliesEnabled
const areRepliesByFollowedOnlyEnabled =
this.homeFeedRepliesByFollowedOnlyEnabled
const repliesThreshold = this.homeFeedRepliesThreshold
const areRepostsEnabled = this.homeFeedRepostsEnabled
const areQuotePostsEnabled = this.homeFeedQuotePostsEnabled
if (feedType === 'custom') { if (feedType === 'custom') {
return [ return [
FeedTuner.dedupReposts, FeedTuner.dedupReposts,
@ -576,25 +560,25 @@ export class PreferencesModel {
if (feedType === 'home' || feedType === 'following') { if (feedType === 'home' || feedType === 'following') {
const feedTuners = [] const feedTuners = []
if (areRepostsEnabled) { if (this.homeFeed.hideReposts) {
feedTuners.push(FeedTuner.dedupReposts)
} else {
feedTuners.push(FeedTuner.removeReposts) feedTuners.push(FeedTuner.removeReposts)
} else {
feedTuners.push(FeedTuner.dedupReposts)
} }
if (areRepliesEnabled) { if (this.homeFeed.hideReplies) {
feedTuners.push(FeedTuner.removeReplies)
} else {
feedTuners.push( feedTuners.push(
FeedTuner.thresholdRepliesOnly({ FeedTuner.thresholdRepliesOnly({
userDid: this.rootStore.session.data?.did || '', userDid: this.rootStore.session.data?.did || '',
minLikes: repliesThreshold, minLikes: this.homeFeed.hideRepliesByLikeCount,
followedOnly: areRepliesByFollowedOnlyEnabled, followedOnly: !!this.homeFeed.hideRepliesByUnfollowed,
}), }),
) )
} else {
feedTuners.push(FeedTuner.removeReplies)
} }
if (!areQuotePostsEnabled) { if (this.homeFeed.hideQuotePosts) {
feedTuners.push(FeedTuner.removeQuotePosts) feedTuners.push(FeedTuner.removeQuotePosts)
} }
@ -611,3 +595,37 @@ function tempfixLabelPref(pref: LabelPreference): APILabelPreference {
} }
return pref return pref
} }
function getLegacyPreferences(
v: Record<string, unknown>,
): LegacyPreferences | undefined {
const legacyPreferences: LegacyPreferences = {}
if (
hasProp(v, 'homeFeedRepliesEnabled') &&
typeof v.homeFeedRepliesEnabled === 'boolean'
) {
legacyPreferences.hideReplies = !v.homeFeedRepliesEnabled
}
if (
hasProp(v, 'homeFeedRepliesThreshold') &&
typeof v.homeFeedRepliesThreshold === 'number'
) {
legacyPreferences.hideRepliesByLikeCount = v.homeFeedRepliesThreshold
}
if (
hasProp(v, 'homeFeedRepostsEnabled') &&
typeof v.homeFeedRepostsEnabled === 'boolean'
) {
legacyPreferences.hideReposts = !v.homeFeedRepostsEnabled
}
if (
hasProp(v, 'homeFeedQuotePostsEnabled') &&
typeof v.homeFeedQuotePostsEnabled === 'boolean'
) {
legacyPreferences.hideQuotePosts = !v.homeFeedQuotePostsEnabled
}
if (Object.keys(legacyPreferences).length) {
return legacyPreferences
}
return undefined
}

View File

@ -76,7 +76,7 @@ export const PostThreadScreen = withAuthRequired(
uri={uri} uri={uri}
view={view} view={view}
onPressReply={onPressReply} onPressReply={onPressReply}
treeView={store.preferences.threadTreeViewEnabled} treeView={!!store.preferences.thread.lab_treeViewEnabled}
/> />
</View> </View>
{isMobile && !store.shell.minimalShellMode && ( {isMobile && !store.shell.minimalShellMode && (

View File

@ -13,11 +13,23 @@ import {ToggleButton} from 'view/com/util/forms/ToggleButton'
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
import {ViewHeader} from 'view/com/util/ViewHeader' import {ViewHeader} from 'view/com/util/ViewHeader'
import {CenteredView} from 'view/com/util/Views' import {CenteredView} from 'view/com/util/Views'
import debounce from 'lodash.debounce'
function RepliesThresholdInput({enabled}: {enabled: boolean}) { function RepliesThresholdInput({enabled}: {enabled: boolean}) {
const store = useStores() const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')
const [value, setValue] = useState(store.preferences.homeFeedRepliesThreshold) const [value, setValue] = useState(
store.preferences.homeFeed.hideRepliesByLikeCount,
)
const save = React.useMemo(
() =>
debounce(
threshold =>
store.preferences.setHomeFeedHideRepliesByLikeCount(threshold),
500,
), // debouce for 500ms
[store],
)
return ( return (
<View style={[!enabled && styles.dimmed]}> <View style={[!enabled && styles.dimmed]}>
@ -26,7 +38,7 @@ function RepliesThresholdInput({enabled}: {enabled: boolean}) {
onValueChange={(v: number | number[]) => { onValueChange={(v: number | number[]) => {
const threshold = Math.floor(Array.isArray(v) ? v[0] : v) const threshold = Math.floor(Array.isArray(v) ? v[0] : v)
setValue(threshold) setValue(threshold)
store.preferences.setHomeFeedRepliesThreshold(threshold) save(threshold)
}} }}
minimumValue={0} minimumValue={0}
maximumValue={25} maximumValue={25}
@ -88,16 +100,16 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
<ToggleButton <ToggleButton
testID="toggleRepliesBtn" testID="toggleRepliesBtn"
type="default-light" type="default-light"
label={store.preferences.homeFeedRepliesEnabled ? 'Yes' : 'No'} label={store.preferences.homeFeed.hideReplies ? 'No' : 'Yes'}
isSelected={store.preferences.homeFeedRepliesEnabled} isSelected={!store.preferences.homeFeed.hideReplies}
onPress={store.preferences.toggleHomeFeedRepliesEnabled} onPress={store.preferences.toggleHomeFeedHideReplies}
/> />
</View> </View>
<View <View
style={[ style={[
pal.viewLight, pal.viewLight,
styles.card, styles.card,
!store.preferences.homeFeedRepliesEnabled && styles.dimmed, store.preferences.homeFeed.hideReplies && styles.dimmed,
]}> ]}>
<Text type="title-sm" style={[pal.text, s.pb5]}> <Text type="title-sm" style={[pal.text, s.pb5]}>
Reply Filters Reply Filters
@ -108,12 +120,10 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
<ToggleButton <ToggleButton
type="default-light" type="default-light"
label="Followed users only" label="Followed users only"
isSelected={ isSelected={store.preferences.homeFeed.hideRepliesByUnfollowed}
store.preferences.homeFeedRepliesByFollowedOnlyEnabled
}
onPress={ onPress={
store.preferences.homeFeedRepliesEnabled !store.preferences.homeFeed.hideReplies
? store.preferences.toggleHomeFeedRepliesByFollowedOnlyEnabled ? store.preferences.toggleHomeFeedHideRepliesByUnfollowed
: undefined : undefined
} }
style={[s.mb10]} style={[s.mb10]}
@ -123,7 +133,7 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
feed. feed.
</Text> </Text>
<RepliesThresholdInput <RepliesThresholdInput
enabled={store.preferences.homeFeedRepliesEnabled} enabled={!store.preferences.homeFeed.hideReplies}
/> />
</View> </View>
@ -136,9 +146,9 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
</Text> </Text>
<ToggleButton <ToggleButton
type="default-light" type="default-light"
label={store.preferences.homeFeedRepostsEnabled ? 'Yes' : 'No'} label={store.preferences.homeFeed.hideReposts ? 'No' : 'Yes'}
isSelected={store.preferences.homeFeedRepostsEnabled} isSelected={!store.preferences.homeFeed.hideReposts}
onPress={store.preferences.toggleHomeFeedRepostsEnabled} onPress={store.preferences.toggleHomeFeedHideReposts}
/> />
</View> </View>
@ -152,9 +162,9 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
</Text> </Text>
<ToggleButton <ToggleButton
type="default-light" type="default-light"
label={store.preferences.homeFeedQuotePostsEnabled ? 'Yes' : 'No'} label={store.preferences.homeFeed.hideQuotePosts ? 'No' : 'Yes'}
isSelected={store.preferences.homeFeedQuotePostsEnabled} isSelected={!store.preferences.homeFeed.hideQuotePosts}
onPress={store.preferences.toggleHomeFeedQuotePostsEnabled} onPress={store.preferences.toggleHomeFeedHideQuotePosts}
/> />
</View> </View>
@ -169,8 +179,10 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
</Text> </Text>
<ToggleButton <ToggleButton
type="default-light" type="default-light"
label={store.preferences.homeFeedMergeFeedEnabled ? 'Yes' : 'No'} label={
isSelected={store.preferences.homeFeedMergeFeedEnabled} store.preferences.homeFeed.lab_mergeFeedEnabled ? 'Yes' : 'No'
}
isSelected={!!store.preferences.homeFeed.lab_mergeFeedEnabled}
onPress={store.preferences.toggleHomeFeedMergeFeedEnabled} onPress={store.preferences.toggleHomeFeedMergeFeedEnabled}
/> />
</View> </View>

View File

@ -59,8 +59,8 @@ export const PreferencesThreads = observer(function PreferencesThreadsImpl({
{key: 'most-likes', label: 'Most-liked replies first'}, {key: 'most-likes', label: 'Most-liked replies first'},
{key: 'random', label: 'Random (aka "Poster\'s Roulette")'}, {key: 'random', label: 'Random (aka "Poster\'s Roulette")'},
]} ]}
onSelect={store.preferences.setThreadDefaultSort} onSelect={store.preferences.setThreadSort}
initialSelection={store.preferences.threadDefaultSort} initialSelection={store.preferences.thread.sort}
/> />
</View> </View>
</View> </View>
@ -74,9 +74,11 @@ export const PreferencesThreads = observer(function PreferencesThreadsImpl({
</Text> </Text>
<ToggleButton <ToggleButton
type="default-light" type="default-light"
label={store.preferences.threadFollowedUsersFirst ? 'Yes' : 'No'} label={
isSelected={store.preferences.threadFollowedUsersFirst} store.preferences.thread.prioritizeFollowedUsers ? 'Yes' : 'No'
onPress={store.preferences.toggleThreadFollowedUsersFirst} }
isSelected={store.preferences.thread.prioritizeFollowedUsers}
onPress={store.preferences.togglePrioritizedFollowedUsers}
/> />
</View> </View>
@ -91,8 +93,10 @@ export const PreferencesThreads = observer(function PreferencesThreadsImpl({
</Text> </Text>
<ToggleButton <ToggleButton
type="default-light" type="default-light"
label={store.preferences.threadTreeViewEnabled ? 'Yes' : 'No'} label={
isSelected={store.preferences.threadTreeViewEnabled} store.preferences.thread.lab_treeViewEnabled ? 'Yes' : 'No'
}
isSelected={!!store.preferences.thread.lab_treeViewEnabled}
onPress={store.preferences.toggleThreadTreeViewEnabled} onPress={store.preferences.toggleThreadTreeViewEnabled}
/> />
</View> </View>

View File

@ -47,6 +47,19 @@
tlds "^1.234.0" tlds "^1.234.0"
typed-emitter "^2.1.0" typed-emitter "^2.1.0"
"@atproto/api@^0.6.16":
version "0.6.16"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.6.16.tgz#0e5f259a8eb8af239b4e77bf70d7e770b33f4eeb"
integrity sha512-DpG994bdwk7NWJSb36Af+0+FRWMFZgzTcrK0rN2tvlsMh6wBF/RdErjHKuoL8wcogGzbI2yp8eOqsA00lyoisw==
dependencies:
"@atproto/common-web" "^0.2.0"
"@atproto/lexicon" "^0.2.1"
"@atproto/syntax" "^0.1.1"
"@atproto/xrpc" "^0.3.1"
multiformats "^9.9.0"
tlds "^1.234.0"
typed-emitter "^2.1.0"
"@atproto/bsky@^0.0.5": "@atproto/bsky@^0.0.5":
version "0.0.5" version "0.0.5"
resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.5.tgz#4667977158a112f27aeab14fedb3ca1e3ebbd873" resolved "https://registry.yarnpkg.com/@atproto/bsky/-/bsky-0.0.5.tgz#4667977158a112f27aeab14fedb3ca1e3ebbd873"