about summary refs log tree commit diff
path: root/src/state/models/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/models/ui')
-rw-r--r--src/state/models/ui/preferences.ts84
-rw-r--r--src/state/models/ui/profile.ts24
-rw-r--r--src/state/models/ui/shell.ts18
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