about summary refs log tree commit diff
path: root/src/view/com/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/util')
-rw-r--r--src/view/com/util/EventStopper.tsx4
-rw-r--r--src/view/com/util/UserAvatar.tsx251
-rw-r--r--src/view/com/util/UserBanner.tsx240
3 files changed, 288 insertions, 207 deletions
diff --git a/src/view/com/util/EventStopper.tsx b/src/view/com/util/EventStopper.tsx
index 8f5f5cf54..24bc47afd 100644
--- a/src/view/com/util/EventStopper.tsx
+++ b/src/view/com/util/EventStopper.tsx
@@ -1,5 +1,5 @@
-import React from 'react'
-import {View, ViewStyle} from 'react-native'
+import {View, type ViewStyle} from 'react-native'
+import type React from 'react'
 
 /**
  * This utility function captures events and stops
diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx
index 2450c111b..326a2fff8 100644
--- a/src/view/com/util/UserAvatar.tsx
+++ b/src/view/com/util/UserAvatar.tsx
@@ -1,4 +1,4 @@
-import React, {memo, useMemo} from 'react'
+import React, {memo, useCallback, useMemo, useState} from 'react'
 import {
   Image,
   Pressable,
@@ -14,36 +14,38 @@ import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useQueryClient} from '@tanstack/react-query'
 
-import {usePalette} from '#/lib/hooks/usePalette'
 import {
   useCameraPermission,
   usePhotoLibraryPermission,
 } from '#/lib/hooks/usePermissions'
+import {compressIfNeeded} from '#/lib/media/manip'
+import {openCamera, openCropper, openPicker} from '#/lib/media/picker'
+import {type PickerImage} from '#/lib/media/picker.shared'
 import {makeProfileLink} from '#/lib/routes/links'
-import {colors} from '#/lib/styles'
 import {logger} from '#/logger'
 import {isAndroid, isNative, isWeb} from '#/platform/detection'
-import {precacheProfile} from '#/state/queries/profile'
+import {
+  type ComposerImage,
+  compressImage,
+  createComposerImage,
+} from '#/state/gallery'
+import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache'
+import {EditImageDialog} from '#/view/com/composer/photos/EditImageDialog'
 import {HighPriorityImage} from '#/view/com/util/images/Image'
-import {tokens, useTheme} from '#/alf'
+import {atoms as a, tokens, useTheme} from '#/alf'
+import {useDialogControl} from '#/components/Dialog'
 import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
 import {
-  Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled,
-  Camera_Stroke2_Corner0_Rounded as Camera,
+  Camera_Filled_Stroke2_Corner0_Rounded as CameraFilledIcon,
+  Camera_Stroke2_Corner0_Rounded as CameraIcon,
 } from '#/components/icons/Camera'
-import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive'
-import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
+import {StreamingLive_Stroke2_Corner0_Rounded as LibraryIcon} from '#/components/icons/StreamingLive'
+import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
 import {Link} from '#/components/Link'
 import {MediaInsetBorder} from '#/components/MediaInsetBorder'
 import * as Menu from '#/components/Menu'
 import {ProfileHoverCard} from '#/components/ProfileHoverCard'
 import type * as bsky from '#/types/bsky'
-import {
-  openCamera,
-  openCropper,
-  openPicker,
-  type RNImage,
-} from '../../../lib/media/picker'
 
 export type UserAvatarType = 'user' | 'algo' | 'list' | 'labeler'
 
@@ -63,7 +65,7 @@ interface UserAvatarProps extends BaseUserAvatarProps {
 }
 
 interface EditableUserAvatarProps extends BaseUserAvatarProps {
-  onSelectNewAvatar: (img: RNImage | null) => void
+  onSelectNewAvatar: (img: PickerImage | null) => void
 }
 
 interface PreviewableUserAvatarProps extends BaseUserAvatarProps {
@@ -195,8 +197,8 @@ let UserAvatar = ({
   onLoad,
   style,
 }: UserAvatarProps): React.ReactNode => {
-  const pal = usePalette('default')
-  const backgroundColor = pal.colors.backgroundLight
+  const t = useTheme()
+  const backgroundColor = t.palette.contrast_25
   const finalShape = overrideShape ?? (type === 'user' ? 'circle' : 'square')
 
   const aviStyle = useMemo(() => {
@@ -221,15 +223,22 @@ let UserAvatar = ({
       return null
     }
     return (
-      <View style={[styles.alertIconContainer, pal.view]}>
+      <View
+        style={[
+          a.absolute,
+          a.right_0,
+          a.bottom_0,
+          a.rounded_full,
+          {backgroundColor: t.palette.white},
+        ]}>
         <FontAwesomeIcon
           icon="exclamation-circle"
-          style={styles.alertIcon}
+          style={{color: t.palette.negative_400}}
           size={Math.floor(size / 3)}
         />
       </View>
     )
-  }, [moderation?.alert, size, pal])
+  }, [moderation?.alert, size, t])
 
   const containerStyle = useMemo(() => {
     return [
@@ -288,14 +297,18 @@ let EditableUserAvatar = ({
   onSelectNewAvatar,
 }: EditableUserAvatarProps): React.ReactNode => {
   const t = useTheme()
-  const pal = usePalette('default')
   const {_} = useLingui()
   const {requestCameraAccessIfNeeded} = useCameraPermission()
   const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
+  const [rawImage, setRawImage] = useState<ComposerImage | undefined>()
+  const editImageDialogControl = useDialogControl()
+
   const sheetWrapper = useSheetWrapper()
 
+  const circular = type !== 'algo' && type !== 'list'
+
   const aviStyle = useMemo(() => {
-    if (type === 'algo' || type === 'list') {
+    if (!circular) {
       return {
         width: size,
         height: size,
@@ -307,7 +320,7 @@ let EditableUserAvatar = ({
       height: size,
       borderRadius: Math.floor(size / 2),
     }
-  }, [type, size])
+  }, [circular, size])
 
   const onOpenCamera = React.useCallback(async () => {
     if (!(await requestCameraAccessIfNeeded())) {
@@ -315,9 +328,11 @@ let EditableUserAvatar = ({
     }
 
     onSelectNewAvatar(
-      await openCamera({
-        aspect: [1, 1],
-      }),
+      await compressIfNeeded(
+        await openCamera({
+          aspect: [1, 1],
+        }),
+      ),
     )
   }, [onSelectNewAvatar, requestCameraAccessIfNeeded])
 
@@ -337,91 +352,129 @@ let EditableUserAvatar = ({
     }
 
     try {
-      const croppedImage = await openCropper({
-        imageUri: item.path,
-        shape: 'circle',
-        aspectRatio: 1,
-      })
-      onSelectNewAvatar(croppedImage)
+      if (isNative) {
+        onSelectNewAvatar(
+          await compressIfNeeded(
+            await openCropper({
+              imageUri: item.path,
+              shape: circular ? 'circle' : 'rectangle',
+              aspectRatio: 1,
+            }),
+          ),
+        )
+      } else {
+        setRawImage(await createComposerImage(item))
+        editImageDialogControl.open()
+      }
     } catch (e: any) {
       // Don't log errors for cancelling selection to sentry on ios or android
       if (!String(e).toLowerCase().includes('cancel')) {
         logger.error('Failed to crop banner', {error: e})
       }
     }
-  }, [onSelectNewAvatar, requestPhotoAccessIfNeeded, sheetWrapper])
+  }, [
+    onSelectNewAvatar,
+    requestPhotoAccessIfNeeded,
+    sheetWrapper,
+    editImageDialogControl,
+    circular,
+  ])
 
   const onRemoveAvatar = React.useCallback(() => {
     onSelectNewAvatar(null)
   }, [onSelectNewAvatar])
 
+  const onChangeEditImage = useCallback(
+    async (image: ComposerImage) => {
+      const compressed = await compressImage(image)
+      onSelectNewAvatar(compressed)
+    },
+    [onSelectNewAvatar],
+  )
+
   return (
-    <Menu.Root>
-      <Menu.Trigger label={_(msg`Edit avatar`)}>
-        {({props}) => (
-          <Pressable {...props} testID="changeAvatarBtn">
-            {avatar ? (
-              <HighPriorityImage
-                testID="userAvatarImage"
-                style={aviStyle}
-                source={{uri: avatar}}
-                accessibilityRole="image"
-              />
-            ) : (
-              <DefaultAvatar type={type} size={size} />
+    <>
+      <Menu.Root>
+        <Menu.Trigger label={_(msg`Edit avatar`)}>
+          {({props}) => (
+            <Pressable {...props} testID="changeAvatarBtn">
+              {avatar ? (
+                <HighPriorityImage
+                  testID="userAvatarImage"
+                  style={aviStyle}
+                  source={{uri: avatar}}
+                  accessibilityRole="image"
+                />
+              ) : (
+                <DefaultAvatar type={type} size={size} />
+              )}
+              <View
+                style={[
+                  styles.editButtonContainer,
+                  t.atoms.bg_contrast_25,
+                  a.border,
+                  t.atoms.border_contrast_low,
+                ]}>
+                <CameraFilledIcon height={14} width={14} style={t.atoms.text} />
+              </View>
+            </Pressable>
+          )}
+        </Menu.Trigger>
+        <Menu.Outer showCancel>
+          <Menu.Group>
+            {isNative && (
+              <Menu.Item
+                testID="changeAvatarCameraBtn"
+                label={_(msg`Upload from Camera`)}
+                onPress={onOpenCamera}>
+                <Menu.ItemText>
+                  <Trans>Upload from Camera</Trans>
+                </Menu.ItemText>
+                <Menu.ItemIcon icon={CameraIcon} />
+              </Menu.Item>
             )}
-            <View style={[styles.editButtonContainer, pal.btn]}>
-              <CameraFilled height={14} width={14} style={t.atoms.text} />
-            </View>
-          </Pressable>
-        )}
-      </Menu.Trigger>
-      <Menu.Outer showCancel>
-        <Menu.Group>
-          {isNative && (
+
             <Menu.Item
-              testID="changeAvatarCameraBtn"
-              label={_(msg`Upload from Camera`)}
-              onPress={onOpenCamera}>
+              testID="changeAvatarLibraryBtn"
+              label={_(msg`Upload from Library`)}
+              onPress={onOpenLibrary}>
               <Menu.ItemText>
-                <Trans>Upload from Camera</Trans>
+                {isNative ? (
+                  <Trans>Upload from Library</Trans>
+                ) : (
+                  <Trans>Upload from Files</Trans>
+                )}
               </Menu.ItemText>
-              <Menu.ItemIcon icon={Camera} />
+              <Menu.ItemIcon icon={LibraryIcon} />
             </Menu.Item>
+          </Menu.Group>
+          {!!avatar && (
+            <>
+              <Menu.Divider />
+              <Menu.Group>
+                <Menu.Item
+                  testID="changeAvatarRemoveBtn"
+                  label={_(msg`Remove Avatar`)}
+                  onPress={onRemoveAvatar}>
+                  <Menu.ItemText>
+                    <Trans>Remove Avatar</Trans>
+                  </Menu.ItemText>
+                  <Menu.ItemIcon icon={TrashIcon} />
+                </Menu.Item>
+              </Menu.Group>
+            </>
           )}
+        </Menu.Outer>
+      </Menu.Root>
 
-          <Menu.Item
-            testID="changeAvatarLibraryBtn"
-            label={_(msg`Upload from Library`)}
-            onPress={onOpenLibrary}>
-            <Menu.ItemText>
-              {isNative ? (
-                <Trans>Upload from Library</Trans>
-              ) : (
-                <Trans>Upload from Files</Trans>
-              )}
-            </Menu.ItemText>
-            <Menu.ItemIcon icon={Library} />
-          </Menu.Item>
-        </Menu.Group>
-        {!!avatar && (
-          <>
-            <Menu.Divider />
-            <Menu.Group>
-              <Menu.Item
-                testID="changeAvatarRemoveBtn"
-                label={_(msg`Remove Avatar`)}
-                onPress={onRemoveAvatar}>
-                <Menu.ItemText>
-                  <Trans>Remove Avatar</Trans>
-                </Menu.ItemText>
-                <Menu.ItemIcon icon={Trash} />
-              </Menu.Item>
-            </Menu.Group>
-          </>
-        )}
-      </Menu.Outer>
-    </Menu.Root>
+      <EditImageDialog
+        control={editImageDialogControl}
+        image={rawImage}
+        onChange={onChangeEditImage}
+        aspectRatio={1}
+        circularCrop={circular}
+      />
+    </>
   )
 }
 EditableUserAvatar = memo(EditableUserAvatar)
@@ -440,7 +493,7 @@ let PreviewableUserAvatar = ({
 
   const onPress = React.useCallback(() => {
     onBeforePress?.()
-    precacheProfile(queryClient, profile)
+    unstableCacheProfileView(queryClient, profile)
   }, [profile, queryClient, onBeforePress])
 
   const avatarEl = (
@@ -494,15 +547,5 @@ const styles = StyleSheet.create({
     borderRadius: 12,
     alignItems: 'center',
     justifyContent: 'center',
-    backgroundColor: colors.gray5,
-  },
-  alertIconContainer: {
-    position: 'absolute',
-    right: 0,
-    bottom: 0,
-    borderRadius: 100,
-  },
-  alertIcon: {
-    color: colors.red3,
   },
 })
diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx
index ab7f25b80..3600f5c24 100644
--- a/src/view/com/util/UserBanner.tsx
+++ b/src/view/com/util/UserBanner.tsx
@@ -1,35 +1,36 @@
-import React from 'react'
+import {useCallback, useState} from 'react'
 import {Pressable, StyleSheet, View} from 'react-native'
 import {Image} from 'expo-image'
 import {type ModerationUI} from '@atproto/api'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
-import {usePalette} from '#/lib/hooks/usePalette'
 import {
   useCameraPermission,
   usePhotoLibraryPermission,
 } from '#/lib/hooks/usePermissions'
-import {colors} from '#/lib/styles'
-import {useTheme} from '#/lib/ThemeContext'
+import {compressIfNeeded} from '#/lib/media/manip'
+import {openCamera, openCropper, openPicker} from '#/lib/media/picker'
+import {type PickerImage} from '#/lib/media/picker.shared'
 import {logger} from '#/logger'
 import {isAndroid, isNative} from '#/platform/detection'
+import {
+  type ComposerImage,
+  compressImage,
+  createComposerImage,
+} from '#/state/gallery'
+import {EditImageDialog} from '#/view/com/composer/photos/EditImageDialog'
 import {EventStopper} from '#/view/com/util/EventStopper'
-import {tokens, useTheme as useAlfTheme} from '#/alf'
+import {atoms as a, tokens, useTheme} from '#/alf'
+import {useDialogControl} from '#/components/Dialog'
 import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
 import {
-  Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled,
-  Camera_Stroke2_Corner0_Rounded as Camera,
+  Camera_Filled_Stroke2_Corner0_Rounded as CameraFilledIcon,
+  Camera_Stroke2_Corner0_Rounded as CameraIcon,
 } from '#/components/icons/Camera'
-import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive'
-import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
+import {StreamingLive_Stroke2_Corner0_Rounded as LibraryIcon} from '#/components/icons/StreamingLive'
+import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
 import * as Menu from '#/components/Menu'
-import {
-  openCamera,
-  openCropper,
-  openPicker,
-  type RNImage,
-} from '../../../lib/media/picker'
 
 export function UserBanner({
   type,
@@ -40,28 +41,30 @@ export function UserBanner({
   type?: 'labeler' | 'default'
   banner?: string | null
   moderation?: ModerationUI
-  onSelectNewBanner?: (img: RNImage | null) => void
+  onSelectNewBanner?: (img: PickerImage | null) => void
 }) {
-  const pal = usePalette('default')
-  const theme = useTheme()
-  const t = useAlfTheme()
+  const t = useTheme()
   const {_} = useLingui()
   const {requestCameraAccessIfNeeded} = useCameraPermission()
   const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
   const sheetWrapper = useSheetWrapper()
+  const [rawImage, setRawImage] = useState<ComposerImage | undefined>()
+  const editImageDialogControl = useDialogControl()
 
-  const onOpenCamera = React.useCallback(async () => {
+  const onOpenCamera = useCallback(async () => {
     if (!(await requestCameraAccessIfNeeded())) {
       return
     }
     onSelectNewBanner?.(
-      await openCamera({
-        aspect: [3, 1],
-      }),
+      await compressIfNeeded(
+        await openCamera({
+          aspect: [3, 1],
+        }),
+      ),
     )
   }, [onSelectNewBanner, requestCameraAccessIfNeeded])
 
-  const onOpenLibrary = React.useCallback(async () => {
+  const onOpenLibrary = useCallback(async () => {
     if (!(await requestPhotoAccessIfNeeded())) {
       return
     }
@@ -71,105 +74,141 @@ export function UserBanner({
     }
 
     try {
-      onSelectNewBanner?.(
-        await openCropper({
-          imageUri: items[0].path,
-          aspectRatio: 3 / 1,
-        }),
-      )
+      if (isNative) {
+        onSelectNewBanner?.(
+          await compressIfNeeded(
+            await openCropper({
+              imageUri: items[0].path,
+              aspectRatio: 3 / 1,
+            }),
+          ),
+        )
+      } else {
+        setRawImage(await createComposerImage(items[0]))
+        editImageDialogControl.open()
+      }
     } catch (e: any) {
       if (!String(e).includes('Canceled')) {
         logger.error('Failed to crop banner', {error: e})
       }
     }
-  }, [onSelectNewBanner, requestPhotoAccessIfNeeded, sheetWrapper])
+  }, [
+    onSelectNewBanner,
+    requestPhotoAccessIfNeeded,
+    sheetWrapper,
+    editImageDialogControl,
+  ])
 
-  const onRemoveBanner = React.useCallback(() => {
+  const onRemoveBanner = useCallback(() => {
     onSelectNewBanner?.(null)
   }, [onSelectNewBanner])
 
+  const onChangeEditImage = useCallback(
+    async (image: ComposerImage) => {
+      const compressed = await compressImage(image)
+      onSelectNewBanner?.(compressed)
+    },
+    [onSelectNewBanner],
+  )
+
   // setUserBanner is only passed as prop on the EditProfile component
   return onSelectNewBanner ? (
-    <EventStopper onKeyDown={true}>
-      <Menu.Root>
-        <Menu.Trigger label={_(msg`Edit avatar`)}>
-          {({props}) => (
-            <Pressable {...props} testID="changeBannerBtn">
-              {banner ? (
-                <Image
-                  testID="userBannerImage"
-                  style={styles.bannerImage}
-                  source={{uri: banner}}
-                  accessible={true}
-                  accessibilityIgnoresInvertColors
-                />
-              ) : (
+    <>
+      <EventStopper onKeyDown={true}>
+        <Menu.Root>
+          <Menu.Trigger label={_(msg`Edit avatar`)}>
+            {({props}) => (
+              <Pressable {...props} testID="changeBannerBtn">
+                {banner ? (
+                  <Image
+                    testID="userBannerImage"
+                    style={styles.bannerImage}
+                    source={{uri: banner}}
+                    accessible={true}
+                    accessibilityIgnoresInvertColors
+                  />
+                ) : (
+                  <View
+                    testID="userBannerFallback"
+                    style={[styles.bannerImage, t.atoms.bg_contrast_25]}
+                  />
+                )}
                 <View
-                  testID="userBannerFallback"
-                  style={[styles.bannerImage, t.atoms.bg_contrast_25]}
-                />
+                  style={[
+                    styles.editButtonContainer,
+                    t.atoms.bg_contrast_25,
+                    a.border,
+                    t.atoms.border_contrast_low,
+                  ]}>
+                  <CameraFilledIcon
+                    height={14}
+                    width={14}
+                    style={t.atoms.text}
+                  />
+                </View>
+              </Pressable>
+            )}
+          </Menu.Trigger>
+          <Menu.Outer showCancel>
+            <Menu.Group>
+              {isNative && (
+                <Menu.Item
+                  testID="changeBannerCameraBtn"
+                  label={_(msg`Upload from Camera`)}
+                  onPress={onOpenCamera}>
+                  <Menu.ItemText>
+                    <Trans>Upload from Camera</Trans>
+                  </Menu.ItemText>
+                  <Menu.ItemIcon icon={CameraIcon} />
+                </Menu.Item>
               )}
-              <View style={[styles.editButtonContainer, pal.btn]}>
-                <CameraFilled height={14} width={14} style={t.atoms.text} />
-              </View>
-            </Pressable>
-          )}
-        </Menu.Trigger>
-        <Menu.Outer showCancel>
-          <Menu.Group>
-            {isNative && (
+
               <Menu.Item
-                testID="changeBannerCameraBtn"
-                label={_(msg`Upload from Camera`)}
-                onPress={onOpenCamera}>
+                testID="changeBannerLibraryBtn"
+                label={_(msg`Upload from Library`)}
+                onPress={onOpenLibrary}>
                 <Menu.ItemText>
-                  <Trans>Upload from Camera</Trans>
+                  {isNative ? (
+                    <Trans>Upload from Library</Trans>
+                  ) : (
+                    <Trans>Upload from Files</Trans>
+                  )}
                 </Menu.ItemText>
-                <Menu.ItemIcon icon={Camera} />
+                <Menu.ItemIcon icon={LibraryIcon} />
               </Menu.Item>
+            </Menu.Group>
+            {!!banner && (
+              <>
+                <Menu.Divider />
+                <Menu.Group>
+                  <Menu.Item
+                    testID="changeBannerRemoveBtn"
+                    label={_(msg`Remove Banner`)}
+                    onPress={onRemoveBanner}>
+                    <Menu.ItemText>
+                      <Trans>Remove Banner</Trans>
+                    </Menu.ItemText>
+                    <Menu.ItemIcon icon={TrashIcon} />
+                  </Menu.Item>
+                </Menu.Group>
+              </>
             )}
+          </Menu.Outer>
+        </Menu.Root>
+      </EventStopper>
 
-            <Menu.Item
-              testID="changeBannerLibraryBtn"
-              label={_(msg`Upload from Library`)}
-              onPress={onOpenLibrary}>
-              <Menu.ItemText>
-                {isNative ? (
-                  <Trans>Upload from Library</Trans>
-                ) : (
-                  <Trans>Upload from Files</Trans>
-                )}
-              </Menu.ItemText>
-              <Menu.ItemIcon icon={Library} />
-            </Menu.Item>
-          </Menu.Group>
-          {!!banner && (
-            <>
-              <Menu.Divider />
-              <Menu.Group>
-                <Menu.Item
-                  testID="changeBannerRemoveBtn"
-                  label={_(msg`Remove Banner`)}
-                  onPress={onRemoveBanner}>
-                  <Menu.ItemText>
-                    <Trans>Remove Banner</Trans>
-                  </Menu.ItemText>
-                  <Menu.ItemIcon icon={Trash} />
-                </Menu.Item>
-              </Menu.Group>
-            </>
-          )}
-        </Menu.Outer>
-      </Menu.Root>
-    </EventStopper>
+      <EditImageDialog
+        control={editImageDialogControl}
+        image={rawImage}
+        onChange={onChangeEditImage}
+        aspectRatio={3}
+      />
+    </>
   ) : banner &&
     !((moderation?.blur && isAndroid) /* android crashes with blur */) ? (
     <Image
       testID="userBannerImage"
-      style={[
-        styles.bannerImage,
-        {backgroundColor: theme.palette.default.backgroundLight},
-      ]}
+      style={[styles.bannerImage, t.atoms.bg_contrast_25]}
       contentFit="cover"
       source={{uri: banner}}
       blurRadius={moderation?.blur ? 100 : 0}
@@ -197,7 +236,6 @@ const styles = StyleSheet.create({
     borderRadius: 12,
     alignItems: 'center',
     justifyContent: 'center',
-    backgroundColor: colors.gray5,
   },
   bannerImage: {
     width: '100%',