diff options
author | Paul Frazee <pfrazee@gmail.com> | 2023-06-01 14:46:13 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-01 14:46:13 -0500 |
commit | e9c84a192b3a64c49a227617e8b58c25d3e5d0f3 (patch) | |
tree | 5bf2e6dd51e4b306758069a820a0f8c06b0cd727 /src/state/models/ui/preferences.ts | |
parent | f416798c5fef6a8fb45aa93269f2962463ca3767 (diff) | |
download | voidsky-e9c84a192b3a64c49a227617e8b58c25d3e5d0f3.tar.zst |
Fixes to feed preference and state sync [APP-678] (#829)
* Remove extraneous custom-feed health check * Fixes to custom feed preference sync * Fix lint * Fix to how preferences are synced to enable membership modifications
Diffstat (limited to 'src/state/models/ui/preferences.ts')
-rw-r--r-- | src/state/models/ui/preferences.ts | 189 |
1 files changed, 110 insertions, 79 deletions
diff --git a/src/state/models/ui/preferences.ts b/src/state/models/ui/preferences.ts index dcf6b9a7a..a42f0a837 100644 --- a/src/state/models/ui/preferences.ts +++ b/src/state/models/ui/preferences.ts @@ -1,5 +1,7 @@ import {makeAutoObservable, runInAction} from 'mobx' import {getLocales} from 'expo-localization' +import AwaitLock from 'await-lock' +import isEqual from 'lodash.isequal' import {isObj, hasProp} from 'lib/type-guards' import {RootStoreModel} from '../root-store' import {ComAtprotoLabelDefs, AppBskyActorDefs} from '@atproto/api' @@ -50,8 +52,11 @@ export class PreferencesModel { savedFeeds: string[] = [] pinnedFeeds: string[] = [] + // used to linearize async modifications to state + lock = new AwaitLock() + constructor(public rootStore: RootStoreModel) { - makeAutoObservable(this, {}, {autoBind: true}) + makeAutoObservable(this, {lock: false}, {autoBind: true}) } serialize() { @@ -103,62 +108,72 @@ export class PreferencesModel { /** * This function fetches preferences and sets defaults for missing items. */ - async sync() { - // fetch preferences - let hasSavedFeedsPref = false - const res = await this.rootStore.agent.app.bsky.actor.getPreferences({}) - runInAction(() => { - for (const pref of res.data.preferences) { - if ( - AppBskyActorDefs.isAdultContentPref(pref) && - AppBskyActorDefs.validateAdultContentPref(pref).success - ) { - this.adultContentEnabled = pref.enabled - } else if ( - AppBskyActorDefs.isContentLabelPref(pref) && - AppBskyActorDefs.validateAdultContentPref(pref).success - ) { + async sync({clearCache}: {clearCache?: boolean} = {}) { + await this.lock.acquireAsync() + try { + // fetch preferences + let hasSavedFeedsPref = false + const res = await this.rootStore.agent.app.bsky.actor.getPreferences({}) + runInAction(() => { + for (const pref of res.data.preferences) { if ( - LABEL_GROUPS.includes(pref.label) && - VISIBILITY_VALUES.includes(pref.visibility) + AppBskyActorDefs.isAdultContentPref(pref) && + AppBskyActorDefs.validateAdultContentPref(pref).success ) { - this.contentLabels[pref.label as keyof LabelPreferencesModel] = - pref.visibility as LabelPreference + this.adultContentEnabled = pref.enabled + } else if ( + AppBskyActorDefs.isContentLabelPref(pref) && + AppBskyActorDefs.validateAdultContentPref(pref).success + ) { + if ( + LABEL_GROUPS.includes(pref.label) && + VISIBILITY_VALUES.includes(pref.visibility) + ) { + this.contentLabels[pref.label as keyof LabelPreferencesModel] = + pref.visibility as LabelPreference + } + } else if ( + AppBskyActorDefs.isSavedFeedsPref(pref) && + AppBskyActorDefs.validateSavedFeedsPref(pref).success + ) { + if (!isEqual(this.savedFeeds, pref.saved)) { + this.savedFeeds = pref.saved + } + if (!isEqual(this.pinnedFeeds, pref.pinned)) { + this.pinnedFeeds = pref.pinned + } + hasSavedFeedsPref = true } - } else if ( - AppBskyActorDefs.isSavedFeedsPref(pref) && - AppBskyActorDefs.validateSavedFeedsPref(pref).success - ) { - this.savedFeeds = pref.saved - this.pinnedFeeds = pref.pinned - hasSavedFeedsPref = true } - } - }) - - // set defaults on missing items - if (!hasSavedFeedsPref) { - const {saved, pinned} = await DEFAULT_FEEDS( - this.rootStore.agent.service.toString(), - (handle: string) => - this.rootStore.agent - .resolveHandle({handle}) - .then(({data}) => data.did), - ) - runInAction(() => { - this.savedFeeds = saved - this.pinnedFeeds = pinned - }) - res.data.preferences.push({ - $type: 'app.bsky.actor.defs#savedFeedsPref', - saved, - pinned, }) - await this.rootStore.agent.app.bsky.actor.putPreferences({ - preferences: res.data.preferences, - }) - /* dont await */ this.rootStore.me.savedFeeds.refresh() + + // set defaults on missing items + if (!hasSavedFeedsPref) { + const {saved, pinned} = await DEFAULT_FEEDS( + this.rootStore.agent.service.toString(), + (handle: string) => + this.rootStore.agent + .resolveHandle({handle}) + .then(({data}) => data.did), + ) + runInAction(() => { + this.savedFeeds = saved + this.pinnedFeeds = pinned + }) + res.data.preferences.push({ + $type: 'app.bsky.actor.defs#savedFeedsPref', + saved, + pinned, + }) + await this.rootStore.agent.app.bsky.actor.putPreferences({ + preferences: res.data.preferences, + }) + } + } finally { + this.lock.release() } + + await this.rootStore.me.savedFeeds.updateCache(clearCache) } /** @@ -170,29 +185,44 @@ export class PreferencesModel { * argument and if the callback returns false, the preferences are not updated. * @returns void */ - async update(cb: (prefs: AppBskyActorDefs.Preferences) => boolean | void) { - const res = await this.rootStore.agent.app.bsky.actor.getPreferences({}) - if (cb(res.data.preferences) === false) { - return + async update( + cb: ( + prefs: AppBskyActorDefs.Preferences, + ) => AppBskyActorDefs.Preferences | false, + ) { + await this.lock.acquireAsync() + try { + const res = await this.rootStore.agent.app.bsky.actor.getPreferences({}) + const newPrefs = cb(res.data.preferences) + if (newPrefs === false) { + return + } + await this.rootStore.agent.app.bsky.actor.putPreferences({ + preferences: newPrefs, + }) + } finally { + this.lock.release() } - await this.rootStore.agent.app.bsky.actor.putPreferences({ - preferences: res.data.preferences, - }) } /** * This function resets the preferences to an empty array of no preferences. */ async reset() { - runInAction(() => { - this.contentLabels = new LabelPreferencesModel() - this.contentLanguages = deviceLocales.map(locale => locale.languageCode) - this.savedFeeds = [] - this.pinnedFeeds = [] - }) - await this.rootStore.agent.app.bsky.actor.putPreferences({ - preferences: [], - }) + await this.lock.acquireAsync() + try { + runInAction(() => { + this.contentLabels = new LabelPreferencesModel() + this.contentLanguages = deviceLocales.map(locale => locale.languageCode) + this.savedFeeds = [] + this.pinnedFeeds = [] + }) + await this.rootStore.agent.app.bsky.actor.putPreferences({ + preferences: [], + }) + } finally { + this.lock.release() + } } hasContentLanguage(code2: string) { @@ -231,6 +261,7 @@ export class PreferencesModel { visibility: value, }) } + return prefs }) } @@ -250,6 +281,7 @@ export class PreferencesModel { enabled: v, }) } + return prefs }) } @@ -292,32 +324,31 @@ export class PreferencesModel { return res } - setFeeds(saved: string[], pinned: string[]) { - this.savedFeeds = saved - this.pinnedFeeds = pinned - } - async setSavedFeeds(saved: string[], pinned: string[]) { const oldSaved = this.savedFeeds const oldPinned = this.pinnedFeeds - this.setFeeds(saved, pinned) + this.savedFeeds = saved + this.pinnedFeeds = pinned try { await this.update((prefs: AppBskyActorDefs.Preferences) => { - const existing = prefs.find( + let feedsPref = prefs.find( pref => AppBskyActorDefs.isSavedFeedsPref(pref) && AppBskyActorDefs.validateSavedFeedsPref(pref).success, ) - if (existing) { - existing.saved = saved - existing.pinned = pinned + if (feedsPref) { + feedsPref.saved = saved + feedsPref.pinned = pinned } else { - prefs.push({ + feedsPref = { $type: 'app.bsky.actor.defs#savedFeedsPref', saved, pinned, - }) + } } + return prefs + .filter(pref => !AppBskyActorDefs.isSavedFeedsPref(pref)) + .concat([feedsPref]) }) } catch (e) { runInAction(() => { |