diff options
Diffstat (limited to 'src/state/models/ui')
-rw-r--r-- | src/state/models/ui/preferences.ts | 84 | ||||
-rw-r--r-- | src/state/models/ui/profile.ts | 24 | ||||
-rw-r--r-- | src/state/models/ui/shell.ts | 18 |
3 files changed, 119 insertions, 7 deletions
diff --git a/src/state/models/ui/preferences.ts b/src/state/models/ui/preferences.ts index fcd33af8e..1471420fc 100644 --- a/src/state/models/ui/preferences.ts +++ b/src/state/models/ui/preferences.ts @@ -1,7 +1,8 @@ -import {makeAutoObservable} from 'mobx' +import {makeAutoObservable, runInAction} from 'mobx' import {getLocales} from 'expo-localization' import {isObj, hasProp} from 'lib/type-guards' -import {ComAtprotoLabelDefs} from '@atproto/api' +import {RootStoreModel} from '../root-store' +import {ComAtprotoLabelDefs, AppBskyActorDefs} from '@atproto/api' import {LabelValGroup} from 'lib/labeling/types' import {getLabelValueGroup} from 'lib/labeling/helpers' import { @@ -15,6 +16,15 @@ import {isIOS} from 'platform/detection' const deviceLocales = getLocales() export type LabelPreference = 'show' | 'warn' | 'hide' +const LABEL_GROUPS = [ + 'nsfw', + 'nudity', + 'suggestive', + 'gore', + 'hate', + 'spam', + 'impersonation', +] export class LabelPreferencesModel { nsfw: LabelPreference = 'hide' @@ -36,7 +46,7 @@ export class PreferencesModel { deviceLocales?.map?.(locale => locale.languageCode) || [] contentLabels = new LabelPreferencesModel() - constructor() { + constructor(public rootStore: RootStoreModel) { makeAutoObservable(this, {}, {autoBind: true}) } @@ -65,6 +75,35 @@ export class PreferencesModel { } } + async sync() { + 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 + ) { + if (LABEL_GROUPS.includes(pref.label)) { + this.contentLabels[pref.label] = pref.visibility + } + } + } + }) + } + + async update(cb: (prefs: AppBskyActorDefs.Preferences) => void) { + const res = await this.rootStore.agent.app.bsky.actor.getPreferences({}) + cb(res.data.preferences) + await this.rootStore.agent.app.bsky.actor.putPreferences({ + preferences: res.data.preferences, + }) + } + hasContentLanguage(code2: string) { return this.contentLanguages.includes(code2) } @@ -79,11 +118,48 @@ export class PreferencesModel { } } - setContentLabelPref( + async setContentLabelPref( key: keyof LabelPreferencesModel, value: LabelPreference, ) { this.contentLabels[key] = value + + await this.update((prefs: AppBskyActorDefs.Preferences) => { + const existing = prefs.find( + pref => + AppBskyActorDefs.isContentLabelPref(pref) && + AppBskyActorDefs.validateAdultContentPref(pref).success && + pref.label === key, + ) + if (existing) { + existing.visibility = value + } else { + prefs.push({ + $type: 'app.bsky.actor.defs#contentLabelPref', + label: key, + visibility: value, + }) + } + }) + } + + async setAdultContentEnabled(v: boolean) { + this.adultContentEnabled = v + await this.update((prefs: AppBskyActorDefs.Preferences) => { + const existing = prefs.find( + pref => + AppBskyActorDefs.isAdultContentPref(pref) && + AppBskyActorDefs.validateAdultContentPref(pref).success, + ) + if (existing) { + existing.enabled = v + } else { + prefs.push({ + $type: 'app.bsky.actor.defs#adultContentPref', + enabled: v, + }) + } + }) } getLabelPreference(labels: ComAtprotoLabelDefs.Label[] | undefined): { diff --git a/src/state/models/ui/profile.ts b/src/state/models/ui/profile.ts index 855955d12..4f604bfc0 100644 --- a/src/state/models/ui/profile.ts +++ b/src/state/models/ui/profile.ts @@ -1,20 +1,23 @@ import {makeAutoObservable} from 'mobx' +import {AppBskyFeedDefs} from '@atproto/api' import {RootStoreModel} from '../root-store' import {ProfileModel} from '../content/profile' import {PostsFeedModel} from '../feeds/posts' import {ActorFeedsModel} from '../feeds/algo/actor' -import {AppBskyFeedDefs} from '@atproto/api' +import {ListsListModel} from '../lists/lists-list' export enum Sections { Posts = 'Posts', PostsWithReplies = 'Posts & replies', CustomAlgorithms = 'Algos', + Lists = 'Lists', } const USER_SELECTOR_ITEMS = [ Sections.Posts, Sections.PostsWithReplies, Sections.CustomAlgorithms, + Sections.Lists, ] export interface ProfileUiParams { @@ -30,6 +33,7 @@ export class ProfileUiModel { profile: ProfileModel feed: PostsFeedModel algos: ActorFeedsModel + lists: ListsListModel // ui state selectedViewIndex = 0 @@ -52,14 +56,17 @@ export class ProfileUiModel { limit: 10, }) this.algos = new ActorFeedsModel(rootStore, {actor: params.user}) + this.lists = new ListsListModel(rootStore, params.user) } - get currentView(): PostsFeedModel | ActorFeedsModel { + get currentView(): PostsFeedModel | ActorFeedsModel | ListsListModel { if ( this.selectedView === Sections.Posts || this.selectedView === Sections.PostsWithReplies ) { return this.feed + } else if (this.selectedView === Sections.Lists) { + return this.lists } if (this.selectedView === Sections.CustomAlgorithms) { return this.algos @@ -121,6 +128,12 @@ export class ProfileUiModel { } else if (this.feed.isEmpty) { arr = arr.concat([ProfileUiModel.EMPTY_ITEM]) } + } else if (this.selectedView === Sections.Lists) { + if (this.lists.hasContent) { + arr = this.lists.lists + } else if (this.lists.isEmpty) { + arr = arr.concat([ProfileUiModel.EMPTY_ITEM]) + } } else { // fallback, add empty item, to show empty message arr = arr.concat([ProfileUiModel.EMPTY_ITEM]) @@ -135,6 +148,8 @@ export class ProfileUiModel { this.selectedView === Sections.PostsWithReplies ) { return this.feed.hasContent && this.feed.hasMore && this.feed.isLoading + } else if (this.selectedView === Sections.Lists) { + return this.lists.hasContent && this.lists.hasMore && this.lists.isLoading } return false } @@ -155,6 +170,11 @@ export class ProfileUiModel { .setup() .catch(err => this.rootStore.log.error('Failed to fetch feed', err)), ]) + // HACK: need to use the DID as a param, not the username -prf + this.lists.source = this.profile.did + this.lists + .loadMore() + .catch(err => this.rootStore.log.error('Failed to fetch lists', err)) } async update() { diff --git a/src/state/models/ui/shell.ts b/src/state/models/ui/shell.ts index 67f8e16d4..9b9a176be 100644 --- a/src/state/models/ui/shell.ts +++ b/src/state/models/ui/shell.ts @@ -5,6 +5,7 @@ import {ProfileModel} from '../content/profile' import {isObj, hasProp} from 'lib/type-guards' import {Image as RNImage} from 'react-native-image-crop-picker' import {ImageModel} from '../media/image' +import {ListModel} from '../content/list' import {GalleryModel} from '../media/gallery' export interface ConfirmModal { @@ -38,6 +39,19 @@ export interface ReportAccountModal { did: string } +export interface CreateOrEditMuteListModal { + name: 'create-or-edit-mute-list' + list?: ListModel + onSave?: (uri: string) => void +} + +export interface ListAddRemoveUserModal { + name: 'list-add-remove-user' + subject: string + displayName: string + onUpdate?: () => void +} + export interface EditImageModal { name: 'edit-image' image: ImageModel @@ -102,9 +116,11 @@ export type Modal = | ContentFilteringSettingsModal | ContentLanguagesSettingsModal - // Reporting + // Moderation | ReportAccountModal | ReportPostModal + | CreateMuteListModal + | ListAddRemoveUserModal // Posts | AltTextImageModal |