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/create-account.ts216
-rw-r--r--src/state/models/ui/my-feeds.ts182
-rw-r--r--src/state/models/ui/preferences.ts702
-rw-r--r--src/state/models/ui/profile.ts257
-rw-r--r--src/state/models/ui/saved-feeds.ts155
-rw-r--r--src/state/models/ui/search.ts69
-rw-r--r--src/state/models/ui/shell.ts376
7 files changed, 0 insertions, 1957 deletions
diff --git a/src/state/models/ui/create-account.ts b/src/state/models/ui/create-account.ts
deleted file mode 100644
index 1711b530f..000000000
--- a/src/state/models/ui/create-account.ts
+++ /dev/null
@@ -1,216 +0,0 @@
-import {makeAutoObservable} from 'mobx'
-import {RootStoreModel} from '../root-store'
-import {ServiceDescription} from '../session'
-import {DEFAULT_SERVICE} from 'state/index'
-import {ComAtprotoServerCreateAccount} from '@atproto/api'
-import * as EmailValidator from 'email-validator'
-import {createFullHandle} from 'lib/strings/handles'
-import {cleanError} from 'lib/strings/errors'
-import {getAge} from 'lib/strings/time'
-import {track} from 'lib/analytics/analytics'
-import {logger} from '#/logger'
-
-const DEFAULT_DATE = new Date(Date.now() - 60e3 * 60 * 24 * 365 * 20) // default to 20 years ago
-
-export class CreateAccountModel {
-  step: number = 1
-  isProcessing = false
-  isFetchingServiceDescription = false
-  didServiceDescriptionFetchFail = false
-  error = ''
-
-  serviceUrl = DEFAULT_SERVICE
-  serviceDescription: ServiceDescription | undefined = undefined
-  userDomain = ''
-  inviteCode = ''
-  email = ''
-  password = ''
-  handle = ''
-  birthDate = DEFAULT_DATE
-
-  constructor(public rootStore: RootStoreModel) {
-    makeAutoObservable(this, {}, {autoBind: true})
-  }
-
-  get isAge13() {
-    return getAge(this.birthDate) >= 13
-  }
-
-  get isAge18() {
-    return getAge(this.birthDate) >= 18
-  }
-
-  // form state controls
-  // =
-
-  next() {
-    this.error = ''
-    if (this.step === 2) {
-      if (!this.isAge13) {
-        this.error =
-          'Unfortunately, you do not meet the requirements to create an account.'
-        return
-      }
-    }
-    this.step++
-  }
-
-  back() {
-    this.error = ''
-    this.step--
-  }
-
-  setStep(v: number) {
-    this.step = v
-  }
-
-  async fetchServiceDescription() {
-    this.setError('')
-    this.setIsFetchingServiceDescription(true)
-    this.setDidServiceDescriptionFetchFail(false)
-    this.setServiceDescription(undefined)
-    if (!this.serviceUrl) {
-      return
-    }
-    try {
-      const desc = await this.rootStore.session.describeService(this.serviceUrl)
-      this.setServiceDescription(desc)
-      this.setUserDomain(desc.availableUserDomains[0])
-    } catch (err: any) {
-      logger.warn(
-        `Failed to fetch service description for ${this.serviceUrl}`,
-        {error: err},
-      )
-      this.setError(
-        'Unable to contact your service. Please check your Internet connection.',
-      )
-      this.setDidServiceDescriptionFetchFail(true)
-    } finally {
-      this.setIsFetchingServiceDescription(false)
-    }
-  }
-
-  async submit() {
-    if (!this.email) {
-      this.setStep(2)
-      return this.setError('Please enter your email.')
-    }
-    if (!EmailValidator.validate(this.email)) {
-      this.setStep(2)
-      return this.setError('Your email appears to be invalid.')
-    }
-    if (!this.password) {
-      this.setStep(2)
-      return this.setError('Please choose your password.')
-    }
-    if (!this.handle) {
-      this.setStep(3)
-      return this.setError('Please choose your handle.')
-    }
-    this.setError('')
-    this.setIsProcessing(true)
-
-    try {
-      this.rootStore.onboarding.start() // start now to avoid flashing the wrong view
-      await this.rootStore.session.createAccount({
-        service: this.serviceUrl,
-        email: this.email,
-        handle: createFullHandle(this.handle, this.userDomain),
-        password: this.password,
-        inviteCode: this.inviteCode.trim(),
-      })
-      /* dont await */ this.rootStore.preferences.setBirthDate(this.birthDate)
-      track('Create Account')
-    } catch (e: any) {
-      this.rootStore.onboarding.skip() // undo starting the onboard
-      let errMsg = e.toString()
-      if (e instanceof ComAtprotoServerCreateAccount.InvalidInviteCodeError) {
-        errMsg =
-          'Invite code not accepted. Check that you input it correctly and try again.'
-      }
-      logger.error('Failed to create account', {error: e})
-      this.setIsProcessing(false)
-      this.setError(cleanError(errMsg))
-      throw e
-    }
-  }
-
-  // form state accessors
-  // =
-
-  get canBack() {
-    return this.step > 1
-  }
-
-  get canNext() {
-    if (this.step === 1) {
-      return !!this.serviceDescription
-    } else if (this.step === 2) {
-      return (
-        (!this.isInviteCodeRequired || this.inviteCode) &&
-        !!this.email &&
-        !!this.password
-      )
-    }
-    return !!this.handle
-  }
-
-  get isServiceDescribed() {
-    return !!this.serviceDescription
-  }
-
-  get isInviteCodeRequired() {
-    return this.serviceDescription?.inviteCodeRequired
-  }
-
-  // setters
-  // =
-
-  setIsProcessing(v: boolean) {
-    this.isProcessing = v
-  }
-
-  setIsFetchingServiceDescription(v: boolean) {
-    this.isFetchingServiceDescription = v
-  }
-
-  setDidServiceDescriptionFetchFail(v: boolean) {
-    this.didServiceDescriptionFetchFail = v
-  }
-
-  setError(v: string) {
-    this.error = v
-  }
-
-  setServiceUrl(v: string) {
-    this.serviceUrl = v
-  }
-
-  setServiceDescription(v: ServiceDescription | undefined) {
-    this.serviceDescription = v
-  }
-
-  setUserDomain(v: string) {
-    this.userDomain = v
-  }
-
-  setInviteCode(v: string) {
-    this.inviteCode = v
-  }
-
-  setEmail(v: string) {
-    this.email = v
-  }
-
-  setPassword(v: string) {
-    this.password = v
-  }
-
-  setHandle(v: string) {
-    this.handle = v
-  }
-
-  setBirthDate(v: Date) {
-    this.birthDate = v
-  }
-}
diff --git a/src/state/models/ui/my-feeds.ts b/src/state/models/ui/my-feeds.ts
deleted file mode 100644
index ade686338..000000000
--- a/src/state/models/ui/my-feeds.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-import {makeAutoObservable, reaction} from 'mobx'
-import {SavedFeedsModel} from './saved-feeds'
-import {FeedsDiscoveryModel} from '../discovery/feeds'
-import {FeedSourceModel} from '../content/feed-source'
-import {RootStoreModel} from '../root-store'
-
-export type MyFeedsItem =
-  | {
-      _reactKey: string
-      type: 'spinner'
-    }
-  | {
-      _reactKey: string
-      type: 'saved-feeds-loading'
-      numItems: number
-    }
-  | {
-      _reactKey: string
-      type: 'discover-feeds-loading'
-    }
-  | {
-      _reactKey: string
-      type: 'error'
-      error: string
-    }
-  | {
-      _reactKey: string
-      type: 'saved-feeds-header'
-    }
-  | {
-      _reactKey: string
-      type: 'saved-feed'
-      feed: FeedSourceModel
-    }
-  | {
-      _reactKey: string
-      type: 'saved-feeds-load-more'
-    }
-  | {
-      _reactKey: string
-      type: 'discover-feeds-header'
-    }
-  | {
-      _reactKey: string
-      type: 'discover-feeds-no-results'
-    }
-  | {
-      _reactKey: string
-      type: 'discover-feed'
-      feed: FeedSourceModel
-    }
-
-export class MyFeedsUIModel {
-  saved: SavedFeedsModel
-  discovery: FeedsDiscoveryModel
-
-  constructor(public rootStore: RootStoreModel) {
-    makeAutoObservable(this)
-    this.saved = new SavedFeedsModel(this.rootStore)
-    this.discovery = new FeedsDiscoveryModel(this.rootStore)
-  }
-
-  get isRefreshing() {
-    return !this.saved.isLoading && this.saved.isRefreshing
-  }
-
-  get isLoading() {
-    return this.saved.isLoading || this.discovery.isLoading
-  }
-
-  async setup() {
-    if (!this.saved.hasLoaded) {
-      await this.saved.refresh()
-    }
-    if (!this.discovery.hasLoaded) {
-      await this.discovery.refresh()
-    }
-  }
-
-  clear() {
-    this.saved.clear()
-    this.discovery.clear()
-  }
-
-  registerListeners() {
-    const dispose1 = reaction(
-      () => this.rootStore.preferences.savedFeeds,
-      () => this.saved.refresh(),
-    )
-    const dispose2 = reaction(
-      () => this.rootStore.preferences.pinnedFeeds,
-      () => this.saved.refresh(),
-    )
-    return () => {
-      dispose1()
-      dispose2()
-    }
-  }
-
-  async refresh() {
-    return Promise.all([this.saved.refresh(), this.discovery.refresh()])
-  }
-
-  async loadMore() {
-    return this.discovery.loadMore()
-  }
-
-  get items() {
-    let items: MyFeedsItem[] = []
-
-    items.push({
-      _reactKey: '__saved_feeds_header__',
-      type: 'saved-feeds-header',
-    })
-    if (this.saved.isLoading && !this.saved.hasContent) {
-      items.push({
-        _reactKey: '__saved_feeds_loading__',
-        type: 'saved-feeds-loading',
-        numItems: this.rootStore.preferences.savedFeeds.length || 3,
-      })
-    } else if (this.saved.hasError) {
-      items.push({
-        _reactKey: '__saved_feeds_error__',
-        type: 'error',
-        error: this.saved.error,
-      })
-    } else {
-      const savedSorted = this.saved.all
-        .slice()
-        .sort((a, b) => a.displayName.localeCompare(b.displayName))
-      items = items.concat(
-        savedSorted.map(feed => ({
-          _reactKey: `saved-${feed.uri}`,
-          type: 'saved-feed',
-          feed,
-        })),
-      )
-      items.push({
-        _reactKey: '__saved_feeds_load_more__',
-        type: 'saved-feeds-load-more',
-      })
-    }
-
-    items.push({
-      _reactKey: '__discover_feeds_header__',
-      type: 'discover-feeds-header',
-    })
-    if (this.discovery.isLoading && !this.discovery.hasContent) {
-      items.push({
-        _reactKey: '__discover_feeds_loading__',
-        type: 'discover-feeds-loading',
-      })
-    } else if (this.discovery.hasError) {
-      items.push({
-        _reactKey: '__discover_feeds_error__',
-        type: 'error',
-        error: this.discovery.error,
-      })
-    } else if (this.discovery.isEmpty) {
-      items.push({
-        _reactKey: '__discover_feeds_no_results__',
-        type: 'discover-feeds-no-results',
-      })
-    } else {
-      items = items.concat(
-        this.discovery.feeds.map(feed => ({
-          _reactKey: `discover-${feed.uri}`,
-          type: 'discover-feed',
-          feed,
-        })),
-      )
-      if (this.discovery.isLoading) {
-        items.push({
-          _reactKey: '__discover_feeds_loading_more__',
-          type: 'spinner',
-        })
-      }
-    }
-
-    return items
-  }
-}
diff --git a/src/state/models/ui/preferences.ts b/src/state/models/ui/preferences.ts
deleted file mode 100644
index 6e43198a3..000000000
--- a/src/state/models/ui/preferences.ts
+++ /dev/null
@@ -1,702 +0,0 @@
-import {makeAutoObservable, runInAction} from 'mobx'
-import {
-  LabelPreference as APILabelPreference,
-  BskyFeedViewPreference,
-  BskyThreadViewPreference,
-} from '@atproto/api'
-import AwaitLock from 'await-lock'
-import isEqual from 'lodash.isequal'
-import {isObj, hasProp} from 'lib/type-guards'
-import {RootStoreModel} from '../root-store'
-import {ModerationOpts} from '@atproto/api'
-import {DEFAULT_FEEDS} from 'lib/constants'
-import {deviceLocales} from 'platform/detection'
-import {getAge} from 'lib/strings/time'
-import {FeedTuner} from 'lib/api/feed-manip'
-import {LANGUAGES} from '../../../locale/languages'
-import {logger} from '#/logger'
-
-// TEMP we need to permanently convert 'show' to 'ignore', for now we manually convert -prf
-export type LabelPreference = APILabelPreference | 'show'
-export type FeedViewPreference = BskyFeedViewPreference & {
-  lab_mergeFeedEnabled?: boolean | undefined
-}
-export type ThreadViewPreference = BskyThreadViewPreference & {
-  lab_treeViewEnabled?: boolean | undefined
-}
-const LABEL_GROUPS = [
-  'nsfw',
-  'nudity',
-  'suggestive',
-  'gore',
-  'hate',
-  'spam',
-  'impersonation',
-]
-const VISIBILITY_VALUES = ['ignore', 'warn', 'hide']
-const DEFAULT_LANG_CODES = (deviceLocales || [])
-  .concat(['en', 'ja', 'pt', 'de'])
-  .slice(0, 6)
-const THREAD_SORT_VALUES = ['oldest', 'newest', 'most-likes', 'random']
-
-interface LegacyPreferences {
-  hideReplies?: boolean
-  hideRepliesByLikeCount?: number
-  hideReposts?: boolean
-  hideQuotePosts?: boolean
-}
-
-export class LabelPreferencesModel {
-  nsfw: LabelPreference = 'hide'
-  nudity: LabelPreference = 'warn'
-  suggestive: LabelPreference = 'warn'
-  gore: LabelPreference = 'warn'
-  hate: LabelPreference = 'hide'
-  spam: LabelPreference = 'hide'
-  impersonation: LabelPreference = 'warn'
-
-  constructor() {
-    makeAutoObservable(this, {}, {autoBind: true})
-  }
-}
-
-export class PreferencesModel {
-  adultContentEnabled = false
-  primaryLanguage: string = deviceLocales[0] || 'en'
-  contentLanguages: string[] = deviceLocales || []
-  postLanguage: string = deviceLocales[0] || 'en'
-  postLanguageHistory: string[] = DEFAULT_LANG_CODES
-  contentLabels = new LabelPreferencesModel()
-  savedFeeds: string[] = []
-  pinnedFeeds: string[] = []
-  birthDate: Date | undefined = undefined
-  homeFeed: FeedViewPreference = {
-    hideReplies: false,
-    hideRepliesByUnfollowed: false,
-    hideRepliesByLikeCount: 0,
-    hideReposts: false,
-    hideQuotePosts: false,
-    lab_mergeFeedEnabled: false, // experimental
-  }
-  thread: ThreadViewPreference = {
-    sort: 'oldest',
-    prioritizeFollowedUsers: true,
-    lab_treeViewEnabled: false, // experimental
-  }
-  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
-  lock = new AwaitLock()
-
-  constructor(public rootStore: RootStoreModel) {
-    makeAutoObservable(this, {lock: false}, {autoBind: true})
-  }
-
-  get userAge(): number | undefined {
-    if (!this.birthDate) {
-      return undefined
-    }
-    return getAge(this.birthDate)
-  }
-
-  serialize() {
-    return {
-      primaryLanguage: this.primaryLanguage,
-      contentLanguages: this.contentLanguages,
-      postLanguage: this.postLanguage,
-      postLanguageHistory: this.postLanguageHistory,
-      contentLabels: this.contentLabels,
-      savedFeeds: this.savedFeeds,
-      pinnedFeeds: this.pinnedFeeds,
-      requireAltTextEnabled: this.requireAltTextEnabled,
-    }
-  }
-
-  /**
-   * The function hydrates an object with properties related to content languages, labels, saved feeds,
-   * and pinned feeds that it gets from the parameter `v` (probably local storage)
-   * @param {unknown} v - the data object to hydrate from
-   */
-  hydrate(v: unknown) {
-    if (isObj(v)) {
-      if (
-        hasProp(v, 'primaryLanguage') &&
-        typeof v.primaryLanguage === 'string'
-      ) {
-        this.primaryLanguage = v.primaryLanguage
-      } else {
-        // default to the device languages
-        this.primaryLanguage = deviceLocales[0] || 'en'
-      }
-      // check if content languages in preferences exist, otherwise default to device languages
-      if (
-        hasProp(v, 'contentLanguages') &&
-        Array.isArray(v.contentLanguages) &&
-        typeof v.contentLanguages.every(item => typeof item === 'string')
-      ) {
-        this.contentLanguages = v.contentLanguages
-      } else {
-        // default to the device languages
-        this.contentLanguages = deviceLocales
-      }
-      if (hasProp(v, 'postLanguage') && typeof v.postLanguage === 'string') {
-        this.postLanguage = v.postLanguage
-      } else {
-        // default to the device languages
-        this.postLanguage = deviceLocales[0] || 'en'
-      }
-      if (
-        hasProp(v, 'postLanguageHistory') &&
-        Array.isArray(v.postLanguageHistory) &&
-        typeof v.postLanguageHistory.every(item => typeof item === 'string')
-      ) {
-        this.postLanguageHistory = v.postLanguageHistory
-          .concat(DEFAULT_LANG_CODES)
-          .slice(0, 6)
-      } else {
-        // default to a starter set
-        this.postLanguageHistory = DEFAULT_LANG_CODES
-      }
-      // check if content labels in preferences exist, then hydrate
-      if (hasProp(v, 'contentLabels') && typeof v.contentLabels === 'object') {
-        Object.assign(this.contentLabels, v.contentLabels)
-      }
-      // check if saved feeds in preferences, then hydrate
-      if (
-        hasProp(v, 'savedFeeds') &&
-        Array.isArray(v.savedFeeds) &&
-        typeof v.savedFeeds.every(item => typeof item === 'string')
-      ) {
-        this.savedFeeds = v.savedFeeds
-      }
-      // check if pinned feeds in preferences exist, then hydrate
-      if (
-        hasProp(v, 'pinnedFeeds') &&
-        Array.isArray(v.pinnedFeeds) &&
-        typeof v.pinnedFeeds.every(item => typeof item === 'string')
-      ) {
-        this.pinnedFeeds = v.pinnedFeeds
-      }
-      // check if requiring alt text is enabled in preferences, then hydrate
-      if (
-        hasProp(v, 'requireAltTextEnabled') &&
-        typeof v.requireAltTextEnabled === 'boolean'
-      ) {
-        this.requireAltTextEnabled = v.requireAltTextEnabled
-      }
-      // grab legacy values
-      this.legacyPreferences = getLegacyPreferences(v)
-    }
-  }
-
-  /**
-   * This function fetches preferences and sets defaults for missing items.
-   */
-  async sync() {
-    await this.lock.acquireAsync()
-    try {
-      // fetch preferences
-      const prefs = await this.rootStore.agent.getPreferences()
-
-      runInAction(() => {
-        if (prefs.feedViewPrefs.home) {
-          this.homeFeed = prefs.feedViewPrefs.home
-        }
-        this.thread = prefs.threadViewPrefs
-        this.adultContentEnabled = prefs.adultContentEnabled
-        for (const label in prefs.contentLabels) {
-          if (
-            LABEL_GROUPS.includes(label) &&
-            VISIBILITY_VALUES.includes(prefs.contentLabels[label])
-          ) {
-            this.contentLabels[label as keyof LabelPreferencesModel] =
-              prefs.contentLabels[label]
-          }
-        }
-        if (prefs.feeds.saved && !isEqual(this.savedFeeds, prefs.feeds.saved)) {
-          this.savedFeeds = prefs.feeds.saved
-        }
-        if (
-          prefs.feeds.pinned &&
-          !isEqual(this.pinnedFeeds, prefs.feeds.pinned)
-        ) {
-          this.pinnedFeeds = prefs.feeds.pinned
-        }
-        this.birthDate = prefs.birthDate
-      })
-
-      // sync legacy values if needed
-      await this.syncLegacyPreferences()
-
-      // set defaults on missing items
-      if (typeof prefs.feeds.saved === 'undefined') {
-        try {
-          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
-          })
-          await this.rootStore.agent.setSavedFeeds(saved, pinned)
-        } catch (error) {
-          logger.error('Failed to set default feeds', {error})
-        }
-      }
-    } finally {
-      this.lock.release()
-    }
-  }
-
-  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.
-   */
-  async reset() {
-    await this.lock.acquireAsync()
-    try {
-      runInAction(() => {
-        this.contentLabels = new LabelPreferencesModel()
-        this.contentLanguages = deviceLocales
-        this.postLanguage = deviceLocales ? deviceLocales.join(',') : 'en'
-        this.postLanguageHistory = DEFAULT_LANG_CODES
-        this.savedFeeds = []
-        this.pinnedFeeds = []
-      })
-      await this.rootStore.agent.app.bsky.actor.putPreferences({
-        preferences: [],
-      })
-    } finally {
-      this.lock.release()
-    }
-  }
-
-  // languages
-  // =
-
-  hasContentLanguage(code2: string) {
-    return this.contentLanguages.includes(code2)
-  }
-
-  toggleContentLanguage(code2: string) {
-    if (this.hasContentLanguage(code2)) {
-      this.contentLanguages = this.contentLanguages.filter(
-        lang => lang !== code2,
-      )
-    } else {
-      this.contentLanguages = this.contentLanguages.concat([code2])
-    }
-  }
-
-  /**
-   * A getter that splits `this.postLanguage` into an array of strings.
-   *
-   * This was previously the main field on this model, but now we're
-   * concatenating lang codes to make multi-selection a little better.
-   */
-  get postLanguages() {
-    // filter out empty strings if exist
-    return this.postLanguage.split(',').filter(Boolean)
-  }
-
-  hasPostLanguage(code2: string) {
-    return this.postLanguages.includes(code2)
-  }
-
-  togglePostLanguage(code2: string) {
-    if (this.hasPostLanguage(code2)) {
-      this.postLanguage = this.postLanguages
-        .filter(lang => lang !== code2)
-        .join(',')
-    } else {
-      // sort alphabetically for deterministic comparison in context menu
-      this.postLanguage = this.postLanguages
-        .concat([code2])
-        .sort((a, b) => a.localeCompare(b))
-        .join(',')
-    }
-  }
-
-  setPostLanguage(commaSeparatedLangCodes: string) {
-    this.postLanguage = commaSeparatedLangCodes
-  }
-
-  /**
-   * Saves whatever language codes are currently selected into a history array,
-   * which is then used to populate the language selector menu.
-   */
-  savePostLanguageToHistory() {
-    // filter out duplicate `this.postLanguage` if exists, and prepend
-    // value to start of array
-    this.postLanguageHistory = [this.postLanguage]
-      .concat(
-        this.postLanguageHistory.filter(
-          commaSeparatedLangCodes =>
-            commaSeparatedLangCodes !== this.postLanguage,
-        ),
-      )
-      .slice(0, 6)
-  }
-
-  getReadablePostLanguages() {
-    const all = this.postLanguages.map(code2 => {
-      const lang = LANGUAGES.find(l => l.code2 === code2)
-      return lang ? lang.name : code2
-    })
-    return all.join(', ')
-  }
-
-  // moderation
-  // =
-
-  async setContentLabelPref(
-    key: keyof LabelPreferencesModel,
-    value: LabelPreference,
-  ) {
-    this.contentLabels[key] = value
-    await this.rootStore.agent.setContentLabelPref(key, value)
-  }
-
-  async setAdultContentEnabled(v: boolean) {
-    this.adultContentEnabled = v
-    await this.rootStore.agent.setAdultContentEnabled(v)
-  }
-
-  get moderationOpts(): ModerationOpts {
-    return {
-      userDid: this.rootStore.session.currentSession?.did || '',
-      adultContentEnabled: this.adultContentEnabled,
-      labels: {
-        // TEMP translate old settings until this UI can be migrated -prf
-        porn: tempfixLabelPref(this.contentLabels.nsfw),
-        sexual: tempfixLabelPref(this.contentLabels.suggestive),
-        nudity: tempfixLabelPref(this.contentLabels.nudity),
-        nsfl: tempfixLabelPref(this.contentLabels.gore),
-        corpse: tempfixLabelPref(this.contentLabels.gore),
-        gore: tempfixLabelPref(this.contentLabels.gore),
-        torture: tempfixLabelPref(this.contentLabels.gore),
-        'self-harm': tempfixLabelPref(this.contentLabels.gore),
-        'intolerant-race': tempfixLabelPref(this.contentLabels.hate),
-        'intolerant-gender': tempfixLabelPref(this.contentLabels.hate),
-        'intolerant-sexual-orientation': tempfixLabelPref(
-          this.contentLabels.hate,
-        ),
-        'intolerant-religion': tempfixLabelPref(this.contentLabels.hate),
-        intolerant: tempfixLabelPref(this.contentLabels.hate),
-        'icon-intolerant': tempfixLabelPref(this.contentLabels.hate),
-        spam: tempfixLabelPref(this.contentLabels.spam),
-        impersonation: tempfixLabelPref(this.contentLabels.impersonation),
-        scam: 'warn',
-      },
-      labelers: [
-        {
-          labeler: {
-            did: '',
-            displayName: 'Bluesky Social',
-          },
-          labels: {},
-        },
-      ],
-    }
-  }
-
-  // feeds
-  // =
-
-  isPinnedFeed(uri: string) {
-    return this.pinnedFeeds.includes(uri)
-  }
-
-  async _optimisticUpdateSavedFeeds(
-    saved: string[],
-    pinned: string[],
-    cb: () => Promise<{saved: string[]; pinned: string[]}>,
-  ) {
-    const oldSaved = this.savedFeeds
-    const oldPinned = this.pinnedFeeds
-    this.savedFeeds = saved
-    this.pinnedFeeds = pinned
-    await this.lock.acquireAsync()
-    try {
-      const res = await cb()
-      runInAction(() => {
-        this.savedFeeds = res.saved
-        this.pinnedFeeds = res.pinned
-      })
-    } catch (e) {
-      runInAction(() => {
-        this.savedFeeds = oldSaved
-        this.pinnedFeeds = oldPinned
-      })
-      throw e
-    } finally {
-      this.lock.release()
-    }
-  }
-
-  async setSavedFeeds(saved: string[], pinned: string[]) {
-    return this._optimisticUpdateSavedFeeds(saved, pinned, () =>
-      this.rootStore.agent.setSavedFeeds(saved, pinned),
-    )
-  }
-
-  async addSavedFeed(v: string) {
-    return this._optimisticUpdateSavedFeeds(
-      [...this.savedFeeds.filter(uri => uri !== v), v],
-      this.pinnedFeeds,
-      () => this.rootStore.agent.addSavedFeed(v),
-    )
-  }
-
-  async removeSavedFeed(v: string) {
-    return this._optimisticUpdateSavedFeeds(
-      this.savedFeeds.filter(uri => uri !== v),
-      this.pinnedFeeds.filter(uri => uri !== v),
-      () => this.rootStore.agent.removeSavedFeed(v),
-    )
-  }
-
-  async addPinnedFeed(v: string) {
-    return this._optimisticUpdateSavedFeeds(
-      [...this.savedFeeds.filter(uri => uri !== v), v],
-      [...this.pinnedFeeds.filter(uri => uri !== v), v],
-      () => this.rootStore.agent.addPinnedFeed(v),
-    )
-  }
-
-  async removePinnedFeed(v: string) {
-    return this._optimisticUpdateSavedFeeds(
-      this.savedFeeds,
-      this.pinnedFeeds.filter(uri => uri !== v),
-      () => this.rootStore.agent.removePinnedFeed(v),
-    )
-  }
-
-  // other
-  // =
-
-  async setBirthDate(birthDate: Date) {
-    this.birthDate = birthDate
-    await this.lock.acquireAsync()
-    try {
-      await this.rootStore.agent.setPersonalDetails({birthDate})
-    } finally {
-      this.lock.release()
-    }
-  }
-
-  async toggleHomeFeedHideReplies() {
-    this.homeFeed.hideReplies = !this.homeFeed.hideReplies
-    await this.lock.acquireAsync()
-    try {
-      await this.rootStore.agent.setFeedViewPrefs('home', {
-        hideReplies: this.homeFeed.hideReplies,
-      })
-    } finally {
-      this.lock.release()
-    }
-  }
-
-  async toggleHomeFeedHideRepliesByUnfollowed() {
-    this.homeFeed.hideRepliesByUnfollowed =
-      !this.homeFeed.hideRepliesByUnfollowed
-    await this.lock.acquireAsync()
-    try {
-      await this.rootStore.agent.setFeedViewPrefs('home', {
-        hideRepliesByUnfollowed: this.homeFeed.hideRepliesByUnfollowed,
-      })
-    } finally {
-      this.lock.release()
-    }
-  }
-
-  async setHomeFeedHideRepliesByLikeCount(threshold: number) {
-    this.homeFeed.hideRepliesByLikeCount = threshold
-    await this.lock.acquireAsync()
-    try {
-      await this.rootStore.agent.setFeedViewPrefs('home', {
-        hideRepliesByLikeCount: this.homeFeed.hideRepliesByLikeCount,
-      })
-    } finally {
-      this.lock.release()
-    }
-  }
-
-  async toggleHomeFeedHideReposts() {
-    this.homeFeed.hideReposts = !this.homeFeed.hideReposts
-    await this.lock.acquireAsync()
-    try {
-      await this.rootStore.agent.setFeedViewPrefs('home', {
-        hideReposts: this.homeFeed.hideReposts,
-      })
-    } finally {
-      this.lock.release()
-    }
-  }
-
-  async toggleHomeFeedHideQuotePosts() {
-    this.homeFeed.hideQuotePosts = !this.homeFeed.hideQuotePosts
-    await this.lock.acquireAsync()
-    try {
-      await this.rootStore.agent.setFeedViewPrefs('home', {
-        hideQuotePosts: this.homeFeed.hideQuotePosts,
-      })
-    } finally {
-      this.lock.release()
-    }
-  }
-
-  async toggleHomeFeedMergeFeedEnabled() {
-    this.homeFeed.lab_mergeFeedEnabled = !this.homeFeed.lab_mergeFeedEnabled
-    await this.lock.acquireAsync()
-    try {
-      await this.rootStore.agent.setFeedViewPrefs('home', {
-        lab_mergeFeedEnabled: this.homeFeed.lab_mergeFeedEnabled,
-      })
-    } finally {
-      this.lock.release()
-    }
-  }
-
-  async setThreadSort(v: string) {
-    if (THREAD_SORT_VALUES.includes(v)) {
-      this.thread.sort = v
-      await this.lock.acquireAsync()
-      try {
-        await this.rootStore.agent.setThreadViewPrefs({sort: v})
-      } finally {
-        this.lock.release()
-      }
-    }
-  }
-
-  async togglePrioritizedFollowedUsers() {
-    this.thread.prioritizeFollowedUsers = !this.thread.prioritizeFollowedUsers
-    await this.lock.acquireAsync()
-    try {
-      await this.rootStore.agent.setThreadViewPrefs({
-        prioritizeFollowedUsers: this.thread.prioritizeFollowedUsers,
-      })
-    } finally {
-      this.lock.release()
-    }
-  }
-
-  async toggleThreadTreeViewEnabled() {
-    this.thread.lab_treeViewEnabled = !this.thread.lab_treeViewEnabled
-    await this.lock.acquireAsync()
-    try {
-      await this.rootStore.agent.setThreadViewPrefs({
-        lab_treeViewEnabled: this.thread.lab_treeViewEnabled,
-      })
-    } finally {
-      this.lock.release()
-    }
-  }
-
-  toggleRequireAltTextEnabled() {
-    this.requireAltTextEnabled = !this.requireAltTextEnabled
-  }
-
-  setPrimaryLanguage(lang: string) {
-    this.primaryLanguage = lang
-  }
-
-  getFeedTuners(
-    feedType: 'home' | 'following' | 'author' | 'custom' | 'list' | 'likes',
-  ) {
-    if (feedType === 'custom') {
-      return [
-        FeedTuner.dedupReposts,
-        FeedTuner.preferredLangOnly(this.contentLanguages),
-      ]
-    }
-    if (feedType === 'list') {
-      return [FeedTuner.dedupReposts]
-    }
-    if (feedType === 'home' || feedType === 'following') {
-      const feedTuners = []
-
-      if (this.homeFeed.hideReposts) {
-        feedTuners.push(FeedTuner.removeReposts)
-      } else {
-        feedTuners.push(FeedTuner.dedupReposts)
-      }
-
-      if (this.homeFeed.hideReplies) {
-        feedTuners.push(FeedTuner.removeReplies)
-      } else {
-        feedTuners.push(
-          FeedTuner.thresholdRepliesOnly({
-            userDid: this.rootStore.session.data?.did || '',
-            minLikes: this.homeFeed.hideRepliesByLikeCount,
-            followedOnly: !!this.homeFeed.hideRepliesByUnfollowed,
-          }),
-        )
-      }
-
-      if (this.homeFeed.hideQuotePosts) {
-        feedTuners.push(FeedTuner.removeQuotePosts)
-      }
-
-      return feedTuners
-    }
-    return []
-  }
-}
-
-// TEMP we need to permanently convert 'show' to 'ignore', for now we manually convert -prf
-function tempfixLabelPref(pref: LabelPreference): APILabelPreference {
-  if (pref === 'show') {
-    return 'ignore'
-  }
-  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
-}
diff --git a/src/state/models/ui/profile.ts b/src/state/models/ui/profile.ts
deleted file mode 100644
index f96340c65..000000000
--- a/src/state/models/ui/profile.ts
+++ /dev/null
@@ -1,257 +0,0 @@
-import {makeAutoObservable, runInAction} from 'mobx'
-import {RootStoreModel} from '../root-store'
-import {ProfileModel} from '../content/profile'
-import {PostsFeedModel} from '../feeds/posts'
-import {ActorFeedsModel} from '../lists/actor-feeds'
-import {ListsListModel} from '../lists/lists-list'
-import {logger} from '#/logger'
-
-export enum Sections {
-  PostsNoReplies = 'Posts',
-  PostsWithReplies = 'Posts & replies',
-  PostsWithMedia = 'Media',
-  Likes = 'Likes',
-  CustomAlgorithms = 'Feeds',
-  Lists = 'Lists',
-}
-
-export interface ProfileUiParams {
-  user: string
-}
-
-export class ProfileUiModel {
-  static LOADING_ITEM = {_reactKey: '__loading__'}
-  static END_ITEM = {_reactKey: '__end__'}
-  static EMPTY_ITEM = {_reactKey: '__empty__'}
-
-  isAuthenticatedUser = false
-
-  // data
-  profile: ProfileModel
-  feed: PostsFeedModel
-  algos: ActorFeedsModel
-  lists: ListsListModel
-
-  // ui state
-  selectedViewIndex = 0
-
-  constructor(
-    public rootStore: RootStoreModel,
-    public params: ProfileUiParams,
-  ) {
-    makeAutoObservable(
-      this,
-      {
-        rootStore: false,
-        params: false,
-      },
-      {autoBind: true},
-    )
-    this.profile = new ProfileModel(rootStore, {actor: params.user})
-    this.feed = new PostsFeedModel(rootStore, 'author', {
-      actor: params.user,
-      limit: 10,
-      filter: 'posts_no_replies',
-    })
-    this.algos = new ActorFeedsModel(rootStore, {actor: params.user})
-    this.lists = new ListsListModel(rootStore, params.user)
-  }
-
-  get currentView(): PostsFeedModel | ActorFeedsModel | ListsListModel {
-    if (
-      this.selectedView === Sections.PostsNoReplies ||
-      this.selectedView === Sections.PostsWithReplies ||
-      this.selectedView === Sections.PostsWithMedia ||
-      this.selectedView === Sections.Likes
-    ) {
-      return this.feed
-    } else if (this.selectedView === Sections.Lists) {
-      return this.lists
-    }
-    if (this.selectedView === Sections.CustomAlgorithms) {
-      return this.algos
-    }
-    throw new Error(`Invalid selector value: ${this.selectedViewIndex}`)
-  }
-
-  get isInitialLoading() {
-    const view = this.currentView
-    return view.isLoading && !view.isRefreshing && !view.hasContent
-  }
-
-  get isRefreshing() {
-    return this.profile.isRefreshing || this.currentView.isRefreshing
-  }
-
-  get selectorItems() {
-    const items = [
-      Sections.PostsNoReplies,
-      Sections.PostsWithReplies,
-      Sections.PostsWithMedia,
-      this.isAuthenticatedUser && Sections.Likes,
-    ].filter(Boolean) as string[]
-    if (this.algos.hasLoaded && !this.algos.isEmpty) {
-      items.push(Sections.CustomAlgorithms)
-    }
-    if (this.lists.hasLoaded && !this.lists.isEmpty) {
-      items.push(Sections.Lists)
-    }
-    return items
-  }
-
-  get selectedView() {
-    // If, for whatever reason, the selected view index is not available, default back to posts
-    // This can happen when the user was focused on a view but performed an action that caused
-    // the view to disappear (e.g. deleting the last list in their list of lists https://imgflip.com/i/7txu1y)
-    return this.selectorItems[this.selectedViewIndex] || Sections.PostsNoReplies
-  }
-
-  get uiItems() {
-    let arr: any[] = []
-    // if loading, return loading item to show loading spinner
-    if (this.isInitialLoading) {
-      arr = arr.concat([ProfileUiModel.LOADING_ITEM])
-    } else if (this.currentView.hasError) {
-      // if error, return error item to show error message
-      arr = arr.concat([
-        {
-          _reactKey: '__error__',
-          error: this.currentView.error,
-        },
-      ])
-    } else {
-      if (
-        this.selectedView === Sections.PostsNoReplies ||
-        this.selectedView === Sections.PostsWithReplies ||
-        this.selectedView === Sections.PostsWithMedia ||
-        this.selectedView === Sections.Likes
-      ) {
-        if (this.feed.hasContent) {
-          arr = this.feed.slices.slice()
-          if (!this.feed.hasMore) {
-            arr = arr.concat([ProfileUiModel.END_ITEM])
-          }
-        } else if (this.feed.isEmpty) {
-          arr = arr.concat([ProfileUiModel.EMPTY_ITEM])
-        }
-      } else if (this.selectedView === Sections.CustomAlgorithms) {
-        if (this.algos.hasContent) {
-          arr = this.algos.feeds
-        } else if (this.algos.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])
-      }
-    }
-    return arr
-  }
-
-  get showLoadingMoreFooter() {
-    if (
-      this.selectedView === Sections.PostsNoReplies ||
-      this.selectedView === Sections.PostsWithReplies ||
-      this.selectedView === Sections.PostsWithMedia ||
-      this.selectedView === Sections.Likes
-    ) {
-      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
-  }
-
-  // public api
-  // =
-
-  setSelectedViewIndex(index: number) {
-    // ViewSelector fires onSelectView on mount
-    if (index === this.selectedViewIndex) return
-
-    this.selectedViewIndex = index
-
-    if (
-      this.selectedView === Sections.PostsNoReplies ||
-      this.selectedView === Sections.PostsWithReplies ||
-      this.selectedView === Sections.PostsWithMedia
-    ) {
-      let filter = 'posts_no_replies'
-      if (this.selectedView === Sections.PostsWithReplies) {
-        filter = 'posts_with_replies'
-      } else if (this.selectedView === Sections.PostsWithMedia) {
-        filter = 'posts_with_media'
-      }
-
-      this.feed = new PostsFeedModel(
-        this.rootStore,
-        'author',
-        {
-          actor: this.params.user,
-          limit: 10,
-          filter,
-        },
-        {
-          isSimpleFeed: ['posts_with_media'].includes(filter),
-        },
-      )
-
-      this.feed.setup()
-    } else if (this.selectedView === Sections.Likes) {
-      this.feed = new PostsFeedModel(
-        this.rootStore,
-        'likes',
-        {
-          actor: this.params.user,
-          limit: 10,
-        },
-        {
-          isSimpleFeed: true,
-        },
-      )
-
-      this.feed.setup()
-    }
-  }
-
-  async setup() {
-    await Promise.all([
-      this.profile
-        .setup()
-        .catch(err => logger.error('Failed to fetch profile', {error: err})),
-      this.feed
-        .setup()
-        .catch(err => logger.error('Failed to fetch feed', {error: err})),
-    ])
-    runInAction(() => {
-      this.isAuthenticatedUser =
-        this.profile.did === this.rootStore.session.currentSession?.did
-    })
-    this.algos.refresh()
-    // HACK: need to use the DID as a param, not the username -prf
-    this.lists.source = this.profile.did
-    this.lists
-      .loadMore()
-      .catch(err => logger.error('Failed to fetch lists', {error: err}))
-  }
-
-  async refresh() {
-    await Promise.all([this.profile.refresh(), this.currentView.refresh()])
-  }
-
-  async loadMore() {
-    if (
-      !this.currentView.isLoading &&
-      !this.currentView.hasError &&
-      !this.currentView.isEmpty
-    ) {
-      await this.currentView.loadMore()
-    }
-  }
-}
diff --git a/src/state/models/ui/saved-feeds.ts b/src/state/models/ui/saved-feeds.ts
deleted file mode 100644
index 624da4f5f..000000000
--- a/src/state/models/ui/saved-feeds.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-import {makeAutoObservable, runInAction} from 'mobx'
-import {RootStoreModel} from '../root-store'
-import {bundleAsync} from 'lib/async/bundle'
-import {cleanError} from 'lib/strings/errors'
-import {FeedSourceModel} from '../content/feed-source'
-import {track} from 'lib/analytics/analytics'
-import {logger} from '#/logger'
-
-export class SavedFeedsModel {
-  // state
-  isLoading = false
-  isRefreshing = false
-  hasLoaded = false
-  error = ''
-
-  // data
-  all: FeedSourceModel[] = []
-
-  constructor(public rootStore: RootStoreModel) {
-    makeAutoObservable(
-      this,
-      {
-        rootStore: false,
-      },
-      {autoBind: true},
-    )
-  }
-
-  get hasContent() {
-    return this.all.length > 0
-  }
-
-  get hasError() {
-    return this.error !== ''
-  }
-
-  get isEmpty() {
-    return this.hasLoaded && !this.hasContent
-  }
-
-  get pinned(): FeedSourceModel[] {
-    return this.rootStore.preferences.savedFeeds
-      .filter(feed => this.rootStore.preferences.isPinnedFeed(feed))
-      .map(uri => this.all.find(f => f.uri === uri))
-      .filter(Boolean) as FeedSourceModel[]
-  }
-
-  get unpinned(): FeedSourceModel[] {
-    return this.rootStore.preferences.savedFeeds
-      .filter(feed => !this.rootStore.preferences.isPinnedFeed(feed))
-      .map(uri => this.all.find(f => f.uri === uri))
-      .filter(Boolean) as FeedSourceModel[]
-  }
-
-  get pinnedFeedNames() {
-    return this.pinned.map(f => f.displayName)
-  }
-
-  // public api
-  // =
-
-  clear() {
-    this.all = []
-  }
-
-  /**
-   * Refresh the preferences then reload all feed infos
-   */
-  refresh = bundleAsync(async () => {
-    this._xLoading(true)
-    try {
-      await this.rootStore.preferences.sync()
-      const uris = dedup(
-        this.rootStore.preferences.pinnedFeeds.concat(
-          this.rootStore.preferences.savedFeeds,
-        ),
-      )
-      const feeds = uris.map(uri => new FeedSourceModel(this.rootStore, uri))
-      await Promise.all(feeds.map(f => f.setup()))
-      runInAction(() => {
-        this.all = feeds
-        this._updatePinSortOrder()
-      })
-      this._xIdle()
-    } catch (e: any) {
-      this._xIdle(e)
-    }
-  })
-
-  async reorderPinnedFeeds(feeds: FeedSourceModel[]) {
-    this._updatePinSortOrder(feeds.map(f => f.uri))
-    await this.rootStore.preferences.setSavedFeeds(
-      this.rootStore.preferences.savedFeeds,
-      feeds.filter(feed => feed.isPinned).map(feed => feed.uri),
-    )
-  }
-
-  async movePinnedFeed(item: FeedSourceModel, direction: 'up' | 'down') {
-    const pinned = this.rootStore.preferences.pinnedFeeds.slice()
-    const index = pinned.indexOf(item.uri)
-    if (index === -1) {
-      return
-    }
-    if (direction === 'up' && index !== 0) {
-      ;[pinned[index], pinned[index - 1]] = [pinned[index - 1], pinned[index]]
-    } else if (direction === 'down' && index < pinned.length - 1) {
-      ;[pinned[index], pinned[index + 1]] = [pinned[index + 1], pinned[index]]
-    }
-    this._updatePinSortOrder(pinned.concat(this.unpinned.map(f => f.uri)))
-    await this.rootStore.preferences.setSavedFeeds(
-      this.rootStore.preferences.savedFeeds,
-      pinned,
-    )
-    track('CustomFeed:Reorder', {
-      name: item.displayName,
-      uri: item.uri,
-      index: pinned.indexOf(item.uri),
-    })
-  }
-
-  // state transitions
-  // =
-
-  _xLoading(isRefreshing = false) {
-    this.isLoading = true
-    this.isRefreshing = isRefreshing
-    this.error = ''
-  }
-
-  _xIdle(err?: any) {
-    this.isLoading = false
-    this.isRefreshing = false
-    this.hasLoaded = true
-    this.error = cleanError(err)
-    if (err) {
-      logger.error('Failed to fetch user feeds', {err})
-    }
-  }
-
-  // helpers
-  // =
-
-  _updatePinSortOrder(order?: string[]) {
-    order ??= this.rootStore.preferences.pinnedFeeds.concat(
-      this.rootStore.preferences.savedFeeds,
-    )
-    this.all.sort((a, b) => {
-      return order!.indexOf(a.uri) - order!.indexOf(b.uri)
-    })
-  }
-}
-
-function dedup(strings: string[]): string[] {
-  return Array.from(new Set(strings))
-}
diff --git a/src/state/models/ui/search.ts b/src/state/models/ui/search.ts
deleted file mode 100644
index 2b2036751..000000000
--- a/src/state/models/ui/search.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import {makeAutoObservable, runInAction} from 'mobx'
-import {searchProfiles, searchPosts} from 'lib/api/search'
-import {PostThreadModel} from '../content/post-thread'
-import {AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api'
-import {RootStoreModel} from '../root-store'
-
-export class SearchUIModel {
-  isPostsLoading = false
-  isProfilesLoading = false
-  query: string = ''
-  posts: PostThreadModel[] = []
-  profiles: AppBskyActorDefs.ProfileView[] = []
-
-  constructor(public rootStore: RootStoreModel) {
-    makeAutoObservable(this)
-  }
-
-  async fetch(q: string) {
-    this.posts = []
-    this.profiles = []
-    this.query = q
-    if (!q.trim()) {
-      return
-    }
-
-    this.isPostsLoading = true
-    this.isProfilesLoading = true
-
-    const [postsSearch, profilesSearch] = await Promise.all([
-      searchPosts(q).catch(_e => []),
-      searchProfiles(q).catch(_e => []),
-    ])
-
-    let posts: AppBskyFeedDefs.PostView[] = []
-    if (postsSearch?.length) {
-      do {
-        const res = await this.rootStore.agent.app.bsky.feed.getPosts({
-          uris: postsSearch
-            .splice(0, 25)
-            .map(p => `at://${p.user.did}/${p.tid}`),
-        })
-        posts = posts.concat(res.data.posts)
-      } while (postsSearch.length)
-    }
-    runInAction(() => {
-      this.posts = posts.map(post =>
-        PostThreadModel.fromPostView(this.rootStore, post),
-      )
-      this.isPostsLoading = false
-    })
-
-    let profiles: AppBskyActorDefs.ProfileView[] = []
-    if (profilesSearch?.length) {
-      do {
-        const res = await this.rootStore.agent.getProfiles({
-          actors: profilesSearch.splice(0, 25).map(p => p.did),
-        })
-        profiles = profiles.concat(res.data.profiles)
-      } while (profilesSearch.length)
-    }
-
-    this.rootStore.me.follows.hydrateMany(profiles)
-
-    runInAction(() => {
-      this.profiles = profiles
-      this.isProfilesLoading = false
-    })
-  }
-}
diff --git a/src/state/models/ui/shell.ts b/src/state/models/ui/shell.ts
deleted file mode 100644
index 343fff86d..000000000
--- a/src/state/models/ui/shell.ts
+++ /dev/null
@@ -1,376 +0,0 @@
-import {AppBskyEmbedRecord, AppBskyActorDefs, ModerationUI} from '@atproto/api'
-import {RootStoreModel} from '../root-store'
-import {makeAutoObservable, runInAction} from 'mobx'
-import {ProfileModel} from '../content/profile'
-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'
-import {StyleProp, ViewStyle} from 'react-native'
-import {
-  shouldRequestEmailConfirmation,
-  setEmailConfirmationRequested,
-} from '#/state/shell/reminders'
-
-export type ColorMode = 'system' | 'light' | 'dark'
-
-export function isColorMode(v: unknown): v is ColorMode {
-  return v === 'system' || v === 'light' || v === 'dark'
-}
-
-export interface ConfirmModal {
-  name: 'confirm'
-  title: string
-  message: string | (() => JSX.Element)
-  onPressConfirm: () => void | Promise<void>
-  onPressCancel?: () => void | Promise<void>
-  confirmBtnText?: string
-  confirmBtnStyle?: StyleProp<ViewStyle>
-  cancelBtnText?: string
-}
-
-export interface EditProfileModal {
-  name: 'edit-profile'
-  profileView: ProfileModel
-  onUpdate?: () => void
-}
-
-export interface ProfilePreviewModal {
-  name: 'profile-preview'
-  did: string
-}
-
-export interface ServerInputModal {
-  name: 'server-input'
-  initialService: string
-  onSelect: (url: string) => void
-}
-
-export interface ModerationDetailsModal {
-  name: 'moderation-details'
-  context: 'account' | 'content'
-  moderation: ModerationUI
-}
-
-export type ReportModal = {
-  name: 'report'
-} & (
-  | {
-      uri: string
-      cid: string
-    }
-  | {did: string}
-)
-
-export interface CreateOrEditListModal {
-  name: 'create-or-edit-list'
-  purpose?: string
-  list?: ListModel
-  onSave?: (uri: string) => void
-}
-
-export interface UserAddRemoveListsModal {
-  name: 'user-add-remove-lists'
-  subject: string
-  displayName: string
-  onAdd?: (listUri: string) => void
-  onRemove?: (listUri: string) => void
-}
-
-export interface ListAddUserModal {
-  name: 'list-add-user'
-  list: ListModel
-  onAdd?: (profile: AppBskyActorDefs.ProfileViewBasic) => void
-}
-
-export interface EditImageModal {
-  name: 'edit-image'
-  image: ImageModel
-  gallery: GalleryModel
-}
-
-export interface CropImageModal {
-  name: 'crop-image'
-  uri: string
-  onSelect: (img?: RNImage) => void
-}
-
-export interface AltTextImageModal {
-  name: 'alt-text-image'
-  image: ImageModel
-}
-
-export interface DeleteAccountModal {
-  name: 'delete-account'
-}
-
-export interface RepostModal {
-  name: 'repost'
-  onRepost: () => void
-  onQuote: () => void
-  isReposted: boolean
-}
-
-export interface SelfLabelModal {
-  name: 'self-label'
-  labels: string[]
-  hasMedia: boolean
-  onChange: (labels: string[]) => void
-}
-
-export interface ChangeHandleModal {
-  name: 'change-handle'
-  onChanged: () => void
-}
-
-export interface WaitlistModal {
-  name: 'waitlist'
-}
-
-export interface InviteCodesModal {
-  name: 'invite-codes'
-}
-
-export interface AddAppPasswordModal {
-  name: 'add-app-password'
-}
-
-export interface ContentFilteringSettingsModal {
-  name: 'content-filtering-settings'
-}
-
-export interface ContentLanguagesSettingsModal {
-  name: 'content-languages-settings'
-}
-
-export interface PostLanguagesSettingsModal {
-  name: 'post-languages-settings'
-}
-
-export interface BirthDateSettingsModal {
-  name: 'birth-date-settings'
-}
-
-export interface VerifyEmailModal {
-  name: 'verify-email'
-  showReminder?: boolean
-}
-
-export interface ChangeEmailModal {
-  name: 'change-email'
-}
-
-export interface SwitchAccountModal {
-  name: 'switch-account'
-}
-
-export interface LinkWarningModal {
-  name: 'link-warning'
-  text: string
-  href: string
-}
-
-export type Modal =
-  // Account
-  | AddAppPasswordModal
-  | ChangeHandleModal
-  | DeleteAccountModal
-  | EditProfileModal
-  | ProfilePreviewModal
-  | BirthDateSettingsModal
-  | VerifyEmailModal
-  | ChangeEmailModal
-  | SwitchAccountModal
-
-  // Curation
-  | ContentFilteringSettingsModal
-  | ContentLanguagesSettingsModal
-  | PostLanguagesSettingsModal
-
-  // Moderation
-  | ModerationDetailsModal
-  | ReportModal
-
-  // Lists
-  | CreateOrEditListModal
-  | UserAddRemoveListsModal
-  | ListAddUserModal
-
-  // Posts
-  | AltTextImageModal
-  | CropImageModal
-  | EditImageModal
-  | ServerInputModal
-  | RepostModal
-  | SelfLabelModal
-
-  // Bluesky access
-  | WaitlistModal
-  | InviteCodesModal
-
-  // Generic
-  | ConfirmModal
-  | LinkWarningModal
-
-interface LightboxModel {}
-
-export class ProfileImageLightbox implements LightboxModel {
-  name = 'profile-image'
-  constructor(public profileView: ProfileModel) {
-    makeAutoObservable(this)
-  }
-}
-
-interface ImagesLightboxItem {
-  uri: string
-  alt?: string
-}
-
-export class ImagesLightbox implements LightboxModel {
-  name = 'images'
-  constructor(public images: ImagesLightboxItem[], public index: number) {
-    makeAutoObservable(this)
-  }
-  setIndex(index: number) {
-    this.index = index
-  }
-}
-
-export interface ComposerOptsPostRef {
-  uri: string
-  cid: string
-  text: string
-  author: {
-    handle: string
-    displayName?: string
-    avatar?: string
-  }
-}
-export interface ComposerOptsQuote {
-  uri: string
-  cid: string
-  text: string
-  indexedAt: string
-  author: {
-    did: string
-    handle: string
-    displayName?: string
-    avatar?: string
-  }
-  embeds?: AppBskyEmbedRecord.ViewRecord['embeds']
-}
-export interface ComposerOpts {
-  replyTo?: ComposerOptsPostRef
-  onPost?: () => void
-  quote?: ComposerOptsQuote
-  mention?: string // handle of user to mention
-}
-
-export class ShellUiModel {
-  isModalActive = false
-  activeModals: Modal[] = []
-  isLightboxActive = false
-  activeLightbox: ProfileImageLightbox | ImagesLightbox | null = null
-  isComposerActive = false
-  composerOpts: ComposerOpts | undefined
-  tickEveryMinute = Date.now()
-
-  constructor(public rootStore: RootStoreModel) {
-    makeAutoObservable(this, {
-      rootStore: false,
-    })
-
-    this.setupClock()
-    this.setupLoginModals()
-  }
-
-  /**
-   * returns true if something was closed
-   * (used by the android hardware back btn)
-   */
-  closeAnyActiveElement(): boolean {
-    if (this.isLightboxActive) {
-      this.closeLightbox()
-      return true
-    }
-    if (this.isModalActive) {
-      this.closeModal()
-      return true
-    }
-    if (this.isComposerActive) {
-      this.closeComposer()
-      return true
-    }
-    return false
-  }
-
-  /**
-   * used to clear out any modals, eg for a navigation
-   */
-  closeAllActiveElements() {
-    if (this.isLightboxActive) {
-      this.closeLightbox()
-    }
-    while (this.isModalActive) {
-      this.closeModal()
-    }
-    if (this.isComposerActive) {
-      this.closeComposer()
-    }
-  }
-
-  openModal(modal: Modal) {
-    this.rootStore.emitNavigation()
-    this.isModalActive = true
-    this.activeModals.push(modal)
-  }
-
-  closeModal() {
-    this.activeModals.pop()
-    this.isModalActive = this.activeModals.length > 0
-  }
-
-  openLightbox(lightbox: ProfileImageLightbox | ImagesLightbox) {
-    this.rootStore.emitNavigation()
-    this.isLightboxActive = true
-    this.activeLightbox = lightbox
-  }
-
-  closeLightbox() {
-    this.isLightboxActive = false
-    this.activeLightbox = null
-  }
-
-  openComposer(opts: ComposerOpts) {
-    this.rootStore.emitNavigation()
-    this.isComposerActive = true
-    this.composerOpts = opts
-  }
-
-  closeComposer() {
-    this.isComposerActive = false
-    this.composerOpts = undefined
-  }
-
-  setupClock() {
-    setInterval(() => {
-      runInAction(() => {
-        this.tickEveryMinute = Date.now()
-      })
-    }, 60_000)
-  }
-
-  setupLoginModals() {
-    this.rootStore.onSessionReady(() => {
-      if (
-        shouldRequestEmailConfirmation(
-          this.rootStore.session,
-          this.rootStore.onboarding,
-        )
-      ) {
-        this.openModal({name: 'verify-email', showReminder: true})
-        setEmailConfirmationRequested()
-      }
-    })
-  }
-}