about summary refs log tree commit diff
path: root/src/view/com/modals
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/modals')
-rw-r--r--src/view/com/modals/ContentFilteringSettings.tsx282
-rw-r--r--src/view/com/modals/EditImage.tsx5
-rw-r--r--src/view/com/modals/InviteCodes.tsx90
-rw-r--r--src/view/com/modals/ListAddRemoveUser.tsx367
-rw-r--r--src/view/com/modals/ProfilePreview.tsx6
-rw-r--r--src/view/com/modals/lang-settings/LanguageToggle.tsx66
-rw-r--r--src/view/com/modals/lang-settings/PostLanguagesSettings.tsx2
7 files changed, 414 insertions, 404 deletions
diff --git a/src/view/com/modals/ContentFilteringSettings.tsx b/src/view/com/modals/ContentFilteringSettings.tsx
index 588b21353..d2bf278f5 100644
--- a/src/view/com/modals/ContentFilteringSettings.tsx
+++ b/src/view/com/modals/ContentFilteringSettings.tsx
@@ -17,159 +17,161 @@ import * as Toast from '../util/Toast'
 
 export const snapPoints = ['90%']
 
-export const Component = observer(({}: {}) => {
-  const store = useStores()
-  const {isMobile} = useWebMediaQueries()
-  const pal = usePalette('default')
+export const Component = observer(
+  function ContentFilteringSettingsImpl({}: {}) {
+    const store = useStores()
+    const {isMobile} = useWebMediaQueries()
+    const pal = usePalette('default')
 
-  React.useEffect(() => {
-    store.preferences.sync()
-  }, [store])
+    React.useEffect(() => {
+      store.preferences.sync()
+    }, [store])
 
-  const onToggleAdultContent = React.useCallback(async () => {
-    if (isIOS) {
-      return
-    }
-    try {
-      await store.preferences.setAdultContentEnabled(
-        !store.preferences.adultContentEnabled,
-      )
-    } catch (e) {
-      Toast.show('There was an issue syncing your preferences with the server')
-      store.log.error('Failed to update preferences with server', {e})
-    }
-  }, [store])
+    const onToggleAdultContent = React.useCallback(async () => {
+      if (isIOS) {
+        return
+      }
+      try {
+        await store.preferences.setAdultContentEnabled(
+          !store.preferences.adultContentEnabled,
+        )
+      } catch (e) {
+        Toast.show(
+          'There was an issue syncing your preferences with the server',
+        )
+        store.log.error('Failed to update preferences with server', {e})
+      }
+    }, [store])
 
-  const onPressDone = React.useCallback(() => {
-    store.shell.closeModal()
-  }, [store])
+    const onPressDone = React.useCallback(() => {
+      store.shell.closeModal()
+    }, [store])
 
-  return (
-    <View testID="contentFilteringModal" style={[pal.view, styles.container]}>
-      <Text style={[pal.text, styles.title]}>Content Filtering</Text>
-      <ScrollView style={styles.scrollContainer}>
-        <View style={s.mb10}>
-          {isIOS ? (
-            store.preferences.adultContentEnabled ? null : (
-              <Text type="md" style={pal.textLight}>
-                Adult content can only be enabled via the Web at{' '}
-                <TextLink
-                  style={pal.link}
-                  href="https://bsky.app"
-                  text="bsky.app"
-                />
-                .
-              </Text>
-            )
-          ) : (
-            <ToggleButton
-              type="default-light"
-              label="Enable Adult Content"
-              isSelected={store.preferences.adultContentEnabled}
-              onPress={onToggleAdultContent}
-              style={styles.toggleBtn}
-            />
-          )}
+    return (
+      <View testID="contentFilteringModal" style={[pal.view, styles.container]}>
+        <Text style={[pal.text, styles.title]}>Content Filtering</Text>
+        <ScrollView style={styles.scrollContainer}>
+          <View style={s.mb10}>
+            {isIOS ? (
+              store.preferences.adultContentEnabled ? null : (
+                <Text type="md" style={pal.textLight}>
+                  Adult content can only be enabled via the Web at{' '}
+                  <TextLink
+                    style={pal.link}
+                    href="https://bsky.app"
+                    text="bsky.app"
+                  />
+                  .
+                </Text>
+              )
+            ) : (
+              <ToggleButton
+                type="default-light"
+                label="Enable Adult Content"
+                isSelected={store.preferences.adultContentEnabled}
+                onPress={onToggleAdultContent}
+                style={styles.toggleBtn}
+              />
+            )}
+          </View>
+          <ContentLabelPref
+            group="nsfw"
+            disabled={!store.preferences.adultContentEnabled}
+          />
+          <ContentLabelPref
+            group="nudity"
+            disabled={!store.preferences.adultContentEnabled}
+          />
+          <ContentLabelPref
+            group="suggestive"
+            disabled={!store.preferences.adultContentEnabled}
+          />
+          <ContentLabelPref
+            group="gore"
+            disabled={!store.preferences.adultContentEnabled}
+          />
+          <ContentLabelPref group="hate" />
+          <ContentLabelPref group="spam" />
+          <ContentLabelPref group="impersonation" />
+          <View style={{height: isMobile ? 60 : 0}} />
+        </ScrollView>
+        <View
+          style={[
+            styles.btnContainer,
+            isMobile && styles.btnContainerMobile,
+            pal.borderDark,
+          ]}>
+          <Pressable
+            testID="sendReportBtn"
+            onPress={onPressDone}
+            accessibilityRole="button"
+            accessibilityLabel="Done"
+            accessibilityHint="">
+            <LinearGradient
+              colors={[gradients.blueLight.start, gradients.blueLight.end]}
+              start={{x: 0, y: 0}}
+              end={{x: 1, y: 1}}
+              style={[styles.btn]}>
+              <Text style={[s.white, s.bold, s.f18]}>Done</Text>
+            </LinearGradient>
+          </Pressable>
         </View>
-        <ContentLabelPref
-          group="nsfw"
-          disabled={!store.preferences.adultContentEnabled}
-        />
-        <ContentLabelPref
-          group="nudity"
-          disabled={!store.preferences.adultContentEnabled}
-        />
-        <ContentLabelPref
-          group="suggestive"
-          disabled={!store.preferences.adultContentEnabled}
-        />
-        <ContentLabelPref
-          group="gore"
-          disabled={!store.preferences.adultContentEnabled}
-        />
-        <ContentLabelPref group="hate" />
-        <ContentLabelPref group="spam" />
-        <ContentLabelPref group="impersonation" />
-        <View style={{height: isMobile ? 60 : 0}} />
-      </ScrollView>
-      <View
-        style={[
-          styles.btnContainer,
-          isMobile && styles.btnContainerMobile,
-          pal.borderDark,
-        ]}>
-        <Pressable
-          testID="sendReportBtn"
-          onPress={onPressDone}
-          accessibilityRole="button"
-          accessibilityLabel="Done"
-          accessibilityHint="">
-          <LinearGradient
-            colors={[gradients.blueLight.start, gradients.blueLight.end]}
-            start={{x: 0, y: 0}}
-            end={{x: 1, y: 1}}
-            style={[styles.btn]}>
-            <Text style={[s.white, s.bold, s.f18]}>Done</Text>
-          </LinearGradient>
-        </Pressable>
       </View>
-    </View>
-  )
-})
+    )
+  },
+)
 
 // TODO: Refactor this component to pass labels down to each tab
-const ContentLabelPref = observer(
-  ({
-    group,
-    disabled,
-  }: {
-    group: keyof typeof CONFIGURABLE_LABEL_GROUPS
-    disabled?: boolean
-  }) => {
-    const store = useStores()
-    const pal = usePalette('default')
+const ContentLabelPref = observer(function ContentLabelPrefImpl({
+  group,
+  disabled,
+}: {
+  group: keyof typeof CONFIGURABLE_LABEL_GROUPS
+  disabled?: boolean
+}) {
+  const store = useStores()
+  const pal = usePalette('default')
 
-    const onChange = React.useCallback(
-      async (v: LabelPreference) => {
-        try {
-          await store.preferences.setContentLabelPref(group, v)
-        } catch (e) {
-          Toast.show(
-            'There was an issue syncing your preferences with the server',
-          )
-          store.log.error('Failed to update preferences with server', {e})
-        }
-      },
-      [store, group],
-    )
+  const onChange = React.useCallback(
+    async (v: LabelPreference) => {
+      try {
+        await store.preferences.setContentLabelPref(group, v)
+      } catch (e) {
+        Toast.show(
+          'There was an issue syncing your preferences with the server',
+        )
+        store.log.error('Failed to update preferences with server', {e})
+      }
+    },
+    [store, group],
+  )
 
-    return (
-      <View style={[styles.contentLabelPref, pal.border]}>
-        <View style={s.flex1}>
-          <Text type="md-medium" style={[pal.text]}>
-            {CONFIGURABLE_LABEL_GROUPS[group].title}
-          </Text>
-          {typeof CONFIGURABLE_LABEL_GROUPS[group].subtitle === 'string' && (
-            <Text type="sm" style={[pal.textLight]}>
-              {CONFIGURABLE_LABEL_GROUPS[group].subtitle}
-            </Text>
-          )}
-        </View>
-        {disabled ? (
-          <Text type="sm-bold" style={pal.textLight}>
-            Hide
+  return (
+    <View style={[styles.contentLabelPref, pal.border]}>
+      <View style={s.flex1}>
+        <Text type="md-medium" style={[pal.text]}>
+          {CONFIGURABLE_LABEL_GROUPS[group].title}
+        </Text>
+        {typeof CONFIGURABLE_LABEL_GROUPS[group].subtitle === 'string' && (
+          <Text type="sm" style={[pal.textLight]}>
+            {CONFIGURABLE_LABEL_GROUPS[group].subtitle}
           </Text>
-        ) : (
-          <SelectGroup
-            current={store.preferences.contentLabels[group]}
-            onChange={onChange}
-            group={group}
-          />
         )}
       </View>
-    )
-  },
-)
+      {disabled ? (
+        <Text type="sm-bold" style={pal.textLight}>
+          Hide
+        </Text>
+      ) : (
+        <SelectGroup
+          current={store.preferences.contentLabels[group]}
+          onChange={onChange}
+          group={group}
+        />
+      )}
+    </View>
+  )
+})
 
 interface SelectGroupProps {
   current: LabelPreference
diff --git a/src/view/com/modals/EditImage.tsx b/src/view/com/modals/EditImage.tsx
index e4cfbac35..dcb6668c7 100644
--- a/src/view/com/modals/EditImage.tsx
+++ b/src/view/com/modals/EditImage.tsx
@@ -46,7 +46,10 @@ interface Props {
   gallery: GalleryModel
 }
 
-export const Component = observer(function ({image, gallery}: Props) {
+export const Component = observer(function EditImageImpl({
+  image,
+  gallery,
+}: Props) {
   const pal = usePalette('default')
   const theme = useTheme()
   const store = useStores()
diff --git a/src/view/com/modals/InviteCodes.tsx b/src/view/com/modals/InviteCodes.tsx
index ba3cc382b..33ffc86a2 100644
--- a/src/view/com/modals/InviteCodes.tsx
+++ b/src/view/com/modals/InviteCodes.tsx
@@ -79,50 +79,56 @@ export function Component({}: {}) {
   )
 }
 
-const InviteCode = observer(
-  ({testID, code, used}: {testID: string; code: string; used?: boolean}) => {
-    const pal = usePalette('default')
-    const store = useStores()
-    const {invitesAvailable} = store.me
+const InviteCode = observer(function InviteCodeImpl({
+  testID,
+  code,
+  used,
+}: {
+  testID: string
+  code: string
+  used?: boolean
+}) {
+  const pal = usePalette('default')
+  const store = useStores()
+  const {invitesAvailable} = store.me
 
-    const onPress = React.useCallback(() => {
-      Clipboard.setString(code)
-      Toast.show('Copied to clipboard')
-      store.invitedUsers.setInviteCopied(code)
-    }, [store, code])
+  const onPress = React.useCallback(() => {
+    Clipboard.setString(code)
+    Toast.show('Copied to clipboard')
+    store.invitedUsers.setInviteCopied(code)
+  }, [store, code])
 
-    return (
-      <TouchableOpacity
-        testID={testID}
-        style={[styles.inviteCode, pal.border]}
-        onPress={onPress}
-        accessibilityRole="button"
-        accessibilityLabel={
-          invitesAvailable === 1
-            ? 'Invite codes: 1 available'
-            : `Invite codes: ${invitesAvailable} available`
-        }
-        accessibilityHint="Opens list of invite codes">
-        <Text
-          testID={`${testID}-code`}
-          type={used ? 'md' : 'md-bold'}
-          style={used ? [pal.textLight, styles.strikeThrough] : pal.text}>
-          {code}
-        </Text>
-        <View style={styles.flex1} />
-        {!used && store.invitedUsers.isInviteCopied(code) && (
-          <Text style={[pal.textLight, styles.codeCopied]}>Copied</Text>
-        )}
-        {!used && (
-          <FontAwesomeIcon
-            icon={['far', 'clone']}
-            style={pal.text as FontAwesomeIconStyle}
-          />
-        )}
-      </TouchableOpacity>
-    )
-  },
-)
+  return (
+    <TouchableOpacity
+      testID={testID}
+      style={[styles.inviteCode, pal.border]}
+      onPress={onPress}
+      accessibilityRole="button"
+      accessibilityLabel={
+        invitesAvailable === 1
+          ? 'Invite codes: 1 available'
+          : `Invite codes: ${invitesAvailable} available`
+      }
+      accessibilityHint="Opens list of invite codes">
+      <Text
+        testID={`${testID}-code`}
+        type={used ? 'md' : 'md-bold'}
+        style={used ? [pal.textLight, styles.strikeThrough] : pal.text}>
+        {code}
+      </Text>
+      <View style={styles.flex1} />
+      {!used && store.invitedUsers.isInviteCopied(code) && (
+        <Text style={[pal.textLight, styles.codeCopied]}>Copied</Text>
+      )}
+      {!used && (
+        <FontAwesomeIcon
+          icon={['far', 'clone']}
+          style={pal.text as FontAwesomeIconStyle}
+        />
+      )}
+    </TouchableOpacity>
+  )
+})
 
 const styles = StyleSheet.create({
   container: {
diff --git a/src/view/com/modals/ListAddRemoveUser.tsx b/src/view/com/modals/ListAddRemoveUser.tsx
index e00509285..58d6a529c 100644
--- a/src/view/com/modals/ListAddRemoveUser.tsx
+++ b/src/view/com/modals/ListAddRemoveUser.tsx
@@ -24,210 +24,207 @@ import isEqual from 'lodash.isequal'
 
 export const snapPoints = ['fullscreen']
 
-export const Component = observer(
-  ({
-    subject,
-    displayName,
-    onUpdate,
-  }: {
-    subject: string
-    displayName: string
-    onUpdate?: () => void
-  }) => {
-    const store = useStores()
-    const pal = usePalette('default')
-    const palPrimary = usePalette('primary')
-    const palInverted = usePalette('inverted')
-    const [originalSelections, setOriginalSelections] = React.useState<
-      string[]
-    >([])
-    const [selected, setSelected] = React.useState<string[]>([])
-    const [membershipsLoaded, setMembershipsLoaded] = React.useState(false)
+export const Component = observer(function ListAddRemoveUserImpl({
+  subject,
+  displayName,
+  onUpdate,
+}: {
+  subject: string
+  displayName: string
+  onUpdate?: () => void
+}) {
+  const store = useStores()
+  const pal = usePalette('default')
+  const palPrimary = usePalette('primary')
+  const palInverted = usePalette('inverted')
+  const [originalSelections, setOriginalSelections] = React.useState<string[]>(
+    [],
+  )
+  const [selected, setSelected] = React.useState<string[]>([])
+  const [membershipsLoaded, setMembershipsLoaded] = React.useState(false)
 
-    const listsList: ListsListModel = React.useMemo(
-      () => new ListsListModel(store, store.me.did),
-      [store],
-    )
-    const memberships: ListMembershipModel = React.useMemo(
-      () => new ListMembershipModel(store, subject),
-      [store, subject],
+  const listsList: ListsListModel = React.useMemo(
+    () => new ListsListModel(store, store.me.did),
+    [store],
+  )
+  const memberships: ListMembershipModel = React.useMemo(
+    () => new ListMembershipModel(store, subject),
+    [store, subject],
+  )
+  React.useEffect(() => {
+    listsList.refresh()
+    memberships.fetch().then(
+      () => {
+        const ids = memberships.memberships.map(m => m.value.list)
+        setOriginalSelections(ids)
+        setSelected(ids)
+        setMembershipsLoaded(true)
+      },
+      err => {
+        store.log.error('Failed to fetch memberships', {err})
+      },
     )
-    React.useEffect(() => {
-      listsList.refresh()
-      memberships.fetch().then(
-        () => {
-          const ids = memberships.memberships.map(m => m.value.list)
-          setOriginalSelections(ids)
-          setSelected(ids)
-          setMembershipsLoaded(true)
-        },
-        err => {
-          store.log.error('Failed to fetch memberships', {err})
-        },
-      )
-    }, [memberships, listsList, store, setSelected, setMembershipsLoaded])
+  }, [memberships, listsList, store, setSelected, setMembershipsLoaded])
 
-    const onPressCancel = useCallback(() => {
-      store.shell.closeModal()
-    }, [store])
+  const onPressCancel = useCallback(() => {
+    store.shell.closeModal()
+  }, [store])
 
-    const onPressSave = useCallback(async () => {
-      try {
-        await memberships.updateTo(selected)
-      } catch (err) {
-        store.log.error('Failed to update memberships', {err})
-        return
-      }
-      Toast.show('Lists updated')
-      onUpdate?.()
-      store.shell.closeModal()
-    }, [store, selected, memberships, onUpdate])
-
-    const onPressNewMuteList = useCallback(() => {
-      store.shell.openModal({
-        name: 'create-or-edit-mute-list',
-        onSave: (_uri: string) => {
-          listsList.refresh()
-        },
-      })
-    }, [store, listsList])
+  const onPressSave = useCallback(async () => {
+    try {
+      await memberships.updateTo(selected)
+    } catch (err) {
+      store.log.error('Failed to update memberships', {err})
+      return
+    }
+    Toast.show('Lists updated')
+    onUpdate?.()
+    store.shell.closeModal()
+  }, [store, selected, memberships, onUpdate])
 
-    const onToggleSelected = useCallback(
-      (uri: string) => {
-        if (selected.includes(uri)) {
-          setSelected(selected.filter(uri2 => uri2 !== uri))
-        } else {
-          setSelected([...selected, uri])
-        }
+  const onPressNewMuteList = useCallback(() => {
+    store.shell.openModal({
+      name: 'create-or-edit-mute-list',
+      onSave: (_uri: string) => {
+        listsList.refresh()
       },
-      [selected, setSelected],
-    )
+    })
+  }, [store, listsList])
 
-    const renderItem = useCallback(
-      (list: GraphDefs.ListView) => {
-        const isSelected = selected.includes(list.uri)
-        return (
-          <Pressable
-            testID={`toggleBtn-${list.name}`}
-            style={[
-              styles.listItem,
-              pal.border,
-              {opacity: membershipsLoaded ? 1 : 0.5},
-            ]}
-            accessibilityLabel={`${isSelected ? 'Remove from' : 'Add to'} ${
-              list.name
-            }`}
-            accessibilityHint=""
-            disabled={!membershipsLoaded}
-            onPress={() => onToggleSelected(list.uri)}>
-            <View style={styles.listItemAvi}>
-              <UserAvatar size={40} avatar={list.avatar} />
-            </View>
-            <View style={styles.listItemContent}>
-              <Text
-                type="lg"
-                style={[s.bold, pal.text]}
-                numberOfLines={1}
-                lineHeight={1.2}>
-                {sanitizeDisplayName(list.name)}
-              </Text>
-              <Text type="md" style={[pal.textLight]} numberOfLines={1}>
-                {list.purpose === 'app.bsky.graph.defs#modlist' && 'Mute list'}{' '}
-                by{' '}
-                {list.creator.did === store.me.did
-                  ? 'you'
-                  : sanitizeHandle(list.creator.handle, '@')}
-              </Text>
-            </View>
-            {membershipsLoaded && (
-              <View
-                style={
-                  isSelected
-                    ? [styles.checkbox, palPrimary.border, palPrimary.view]
-                    : [styles.checkbox, pal.borderDark]
-                }>
-                {isSelected && (
-                  <FontAwesomeIcon
-                    icon="check"
-                    style={palInverted.text as FontAwesomeIconStyle}
-                  />
-                )}
-              </View>
-            )}
-          </Pressable>
-        )
-      },
-      [
-        pal,
-        palPrimary,
-        palInverted,
-        onToggleSelected,
-        selected,
-        store.me.did,
-        membershipsLoaded,
-      ],
-    )
+  const onToggleSelected = useCallback(
+    (uri: string) => {
+      if (selected.includes(uri)) {
+        setSelected(selected.filter(uri2 => uri2 !== uri))
+      } else {
+        setSelected([...selected, uri])
+      }
+    },
+    [selected, setSelected],
+  )
 
-    const renderEmptyState = React.useCallback(() => {
+  const renderItem = useCallback(
+    (list: GraphDefs.ListView) => {
+      const isSelected = selected.includes(list.uri)
       return (
-        <EmptyStateWithButton
-          icon="users-slash"
-          message="You can subscribe to mute lists to automatically mute all of the users they include. Mute lists are public but your subscription to a mute list is private."
-          buttonLabel="New Mute List"
-          onPress={onPressNewMuteList}
-        />
+        <Pressable
+          testID={`toggleBtn-${list.name}`}
+          style={[
+            styles.listItem,
+            pal.border,
+            {opacity: membershipsLoaded ? 1 : 0.5},
+          ]}
+          accessibilityLabel={`${isSelected ? 'Remove from' : 'Add to'} ${
+            list.name
+          }`}
+          accessibilityHint=""
+          disabled={!membershipsLoaded}
+          onPress={() => onToggleSelected(list.uri)}>
+          <View style={styles.listItemAvi}>
+            <UserAvatar size={40} avatar={list.avatar} />
+          </View>
+          <View style={styles.listItemContent}>
+            <Text
+              type="lg"
+              style={[s.bold, pal.text]}
+              numberOfLines={1}
+              lineHeight={1.2}>
+              {sanitizeDisplayName(list.name)}
+            </Text>
+            <Text type="md" style={[pal.textLight]} numberOfLines={1}>
+              {list.purpose === 'app.bsky.graph.defs#modlist' && 'Mute list'} by{' '}
+              {list.creator.did === store.me.did
+                ? 'you'
+                : sanitizeHandle(list.creator.handle, '@')}
+            </Text>
+          </View>
+          {membershipsLoaded && (
+            <View
+              style={
+                isSelected
+                  ? [styles.checkbox, palPrimary.border, palPrimary.view]
+                  : [styles.checkbox, pal.borderDark]
+              }>
+              {isSelected && (
+                <FontAwesomeIcon
+                  icon="check"
+                  style={palInverted.text as FontAwesomeIconStyle}
+                />
+              )}
+            </View>
+          )}
+        </Pressable>
       )
-    }, [onPressNewMuteList])
-
-    // Only show changes button if there are some items on the list to choose from AND user has made changes in selection
-    const canSaveChanges =
-      !listsList.isEmpty && !isEqual(selected, originalSelections)
+    },
+    [
+      pal,
+      palPrimary,
+      palInverted,
+      onToggleSelected,
+      selected,
+      store.me.did,
+      membershipsLoaded,
+    ],
+  )
 
+  const renderEmptyState = React.useCallback(() => {
     return (
-      <View testID="listAddRemoveUserModal" style={s.hContentRegion}>
-        <Text style={[styles.title, pal.text]}>Add {displayName} to Lists</Text>
-        <ListsList
-          listsList={listsList}
-          showAddBtns
-          onPressCreateNew={onPressNewMuteList}
-          renderItem={renderItem}
-          renderEmptyState={renderEmptyState}
-          style={[styles.list, pal.border]}
+      <EmptyStateWithButton
+        icon="users-slash"
+        message="You can subscribe to mute lists to automatically mute all of the users they include. Mute lists are public but your subscription to a mute list is private."
+        buttonLabel="New Mute List"
+        onPress={onPressNewMuteList}
+      />
+    )
+  }, [onPressNewMuteList])
+
+  // Only show changes button if there are some items on the list to choose from AND user has made changes in selection
+  const canSaveChanges =
+    !listsList.isEmpty && !isEqual(selected, originalSelections)
+
+  return (
+    <View testID="listAddRemoveUserModal" style={s.hContentRegion}>
+      <Text style={[styles.title, pal.text]}>Add {displayName} to Lists</Text>
+      <ListsList
+        listsList={listsList}
+        showAddBtns
+        onPressCreateNew={onPressNewMuteList}
+        renderItem={renderItem}
+        renderEmptyState={renderEmptyState}
+        style={[styles.list, pal.border]}
+      />
+      <View style={[styles.btns, pal.border]}>
+        <Button
+          testID="cancelBtn"
+          type="default"
+          onPress={onPressCancel}
+          style={styles.footerBtn}
+          accessibilityLabel="Cancel"
+          accessibilityHint=""
+          onAccessibilityEscape={onPressCancel}
+          label="Cancel"
         />
-        <View style={[styles.btns, pal.border]}>
+        {canSaveChanges && (
           <Button
-            testID="cancelBtn"
-            type="default"
-            onPress={onPressCancel}
+            testID="saveBtn"
+            type="primary"
+            onPress={onPressSave}
             style={styles.footerBtn}
-            accessibilityLabel="Cancel"
+            accessibilityLabel="Save changes"
             accessibilityHint=""
-            onAccessibilityEscape={onPressCancel}
-            label="Cancel"
+            onAccessibilityEscape={onPressSave}
+            label="Save Changes"
           />
-          {canSaveChanges && (
-            <Button
-              testID="saveBtn"
-              type="primary"
-              onPress={onPressSave}
-              style={styles.footerBtn}
-              accessibilityLabel="Save changes"
-              accessibilityHint=""
-              onAccessibilityEscape={onPressSave}
-              label="Save Changes"
-            />
-          )}
+        )}
 
-          {(listsList.isLoading || !membershipsLoaded) && (
-            <View style={styles.loadingContainer}>
-              <ActivityIndicator />
-            </View>
-          )}
-        </View>
+        {(listsList.isLoading || !membershipsLoaded) && (
+          <View style={styles.loadingContainer}>
+            <ActivityIndicator />
+          </View>
+        )}
       </View>
-    )
-  },
-)
+    </View>
+  )
+})
 
 const styles = StyleSheet.create({
   container: {
diff --git a/src/view/com/modals/ProfilePreview.tsx b/src/view/com/modals/ProfilePreview.tsx
index 65b584866..6f189cf1a 100644
--- a/src/view/com/modals/ProfilePreview.tsx
+++ b/src/view/com/modals/ProfilePreview.tsx
@@ -14,7 +14,11 @@ import {s} from 'lib/styles'
 
 export const snapPoints = [520, '100%']
 
-export const Component = observer(({did}: {did: string}) => {
+export const Component = observer(function ProfilePreviewImpl({
+  did,
+}: {
+  did: string
+}) {
   const store = useStores()
   const pal = usePalette('default')
   const [model] = useState(new ProfileModel(store, {actor: did}))
diff --git a/src/view/com/modals/lang-settings/LanguageToggle.tsx b/src/view/com/modals/lang-settings/LanguageToggle.tsx
index df1b405ca..187b46e8c 100644
--- a/src/view/com/modals/lang-settings/LanguageToggle.tsx
+++ b/src/view/com/modals/lang-settings/LanguageToggle.tsx
@@ -5,43 +5,41 @@ import {observer} from 'mobx-react-lite'
 import {ToggleButton} from 'view/com/util/forms/ToggleButton'
 import {useStores} from 'state/index'
 
-export const LanguageToggle = observer(
-  ({
-    code2,
-    name,
-    onPress,
-    langType,
-  }: {
-    code2: string
-    name: string
-    onPress: () => void
-    langType: 'contentLanguages' | 'postLanguages'
-  }) => {
-    const pal = usePalette('default')
-    const store = useStores()
+export const LanguageToggle = observer(function LanguageToggleImpl({
+  code2,
+  name,
+  onPress,
+  langType,
+}: {
+  code2: string
+  name: string
+  onPress: () => void
+  langType: 'contentLanguages' | 'postLanguages'
+}) {
+  const pal = usePalette('default')
+  const store = useStores()
 
-    const isSelected = store.preferences[langType].includes(code2)
+  const isSelected = store.preferences[langType].includes(code2)
 
-    // enforce a max of 3 selections for post languages
-    let isDisabled = false
-    if (
-      langType === 'postLanguages' &&
-      store.preferences[langType].length >= 3 &&
-      !isSelected
-    ) {
-      isDisabled = true
-    }
+  // enforce a max of 3 selections for post languages
+  let isDisabled = false
+  if (
+    langType === 'postLanguages' &&
+    store.preferences[langType].length >= 3 &&
+    !isSelected
+  ) {
+    isDisabled = true
+  }
 
-    return (
-      <ToggleButton
-        label={name}
-        isSelected={isSelected}
-        onPress={isDisabled ? undefined : onPress}
-        style={[pal.border, styles.languageToggle, isDisabled && styles.dimmed]}
-      />
-    )
-  },
-)
+  return (
+    <ToggleButton
+      label={name}
+      isSelected={isSelected}
+      onPress={isDisabled ? undefined : onPress}
+      style={[pal.border, styles.languageToggle, isDisabled && styles.dimmed]}
+    />
+  )
+})
 
 const styles = StyleSheet.create({
   languageToggle: {
diff --git a/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx b/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx
index 1ee5c9d1f..d74d884cc 100644
--- a/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx
+++ b/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx
@@ -13,7 +13,7 @@ import {ToggleButton} from 'view/com/util/forms/ToggleButton'
 
 export const snapPoints = ['100%']
 
-export const Component = observer(() => {
+export const Component = observer(function PostLanguagesSettingsImpl() {
   const store = useStores()
   const pal = usePalette('default')
   const {isMobile} = useWebMediaQueries()