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 usagezio/stable
parent
28b692a118
commit
8584009bae
|
@ -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",
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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 && (
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue