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/Link.tsx20
-rw-r--r--src/view/com/util/PostMeta.tsx6
-rw-r--r--src/view/com/util/UserAvatar.tsx108
-rw-r--r--src/view/com/util/UserInfoText.tsx4
-rw-r--r--src/view/com/util/images/AutoSizedImage.tsx12
-rw-r--r--src/view/com/util/images/ImageLayoutGrid.tsx8
6 files changed, 91 insertions, 67 deletions
diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx
index 6915d3e08..1777f6659 100644
--- a/src/view/com/util/Link.tsx
+++ b/src/view/com/util/Link.tsx
@@ -1,5 +1,4 @@
-import React, {ComponentProps, useMemo} from 'react'
-import {observer} from 'mobx-react-lite'
+import React, {ComponentProps, memo, useMemo} from 'react'
 import {
   Linking,
   GestureResponderEvent,
@@ -28,11 +27,10 @@ import {
   isExternalUrl,
   linkRequiresWarning,
 } from 'lib/strings/url-helpers'
-import {isAndroid} from 'platform/detection'
+import {isAndroid, isWeb} from 'platform/detection'
 import {sanitizeUrl} from '@braintree/sanitize-url'
 import {PressableWithHover} from './PressableWithHover'
 import FixedTouchableHighlight from '../pager/FixedTouchableHighlight'
-import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 
 type Event =
   | React.MouseEvent<HTMLAnchorElement, MouseEvent>
@@ -50,7 +48,7 @@ interface Props extends ComponentProps<typeof TouchableOpacity> {
   anchorNoUnderline?: boolean
 }
 
-export const Link = observer(function Link({
+export const Link = memo(function Link({
   testID,
   style,
   href,
@@ -136,7 +134,7 @@ export const Link = observer(function Link({
   )
 })
 
-export const TextLink = observer(function TextLink({
+export const TextLink = memo(function TextLink({
   testID,
   type = 'md',
   style,
@@ -223,7 +221,7 @@ export const TextLink = observer(function TextLink({
 /**
  * Only acts as a link on desktop web
  */
-interface DesktopWebTextLinkProps extends TextProps {
+interface TextLinkOnWebOnlyProps extends TextProps {
   testID?: string
   type?: TypographyVariant
   style?: StyleProp<TextStyle>
@@ -236,7 +234,7 @@ interface DesktopWebTextLinkProps extends TextProps {
   accessibilityHint?: string
   title?: string
 }
-export const DesktopWebTextLink = observer(function DesktopWebTextLink({
+export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({
   testID,
   type = 'md',
   style,
@@ -245,10 +243,8 @@ export const DesktopWebTextLink = observer(function DesktopWebTextLink({
   numberOfLines,
   lineHeight,
   ...props
-}: DesktopWebTextLinkProps) {
-  const {isDesktop} = useWebMediaQueries()
-
-  if (isDesktop) {
+}: TextLinkOnWebOnlyProps) {
+  if (isWeb) {
     return (
       <TextLink
         testID={testID}
diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx
index b5c47dea5..c5e438f8d 100644
--- a/src/view/com/util/PostMeta.tsx
+++ b/src/view/com/util/PostMeta.tsx
@@ -1,7 +1,7 @@
 import React from 'react'
 import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native'
 import {Text} from './text/Text'
-import {DesktopWebTextLink} from './Link'
+import {TextLinkOnWebOnly} from './Link'
 import {niceDate} from 'lib/strings/time'
 import {usePalette} from 'lib/hooks/usePalette'
 import {TypographyVariant} from 'lib/ThemeContext'
@@ -47,7 +47,7 @@ export const PostMeta = observer(function PostMetaImpl(opts: PostMetaOpts) {
         </View>
       )}
       <View style={styles.maxWidth}>
-        <DesktopWebTextLink
+        <TextLinkOnWebOnly
           type={opts.displayNameType || 'lg-bold'}
           style={[pal.text, opts.displayNameStyle]}
           numberOfLines={1}
@@ -78,7 +78,7 @@ export const PostMeta = observer(function PostMetaImpl(opts: PostMetaOpts) {
       )}
       <TimeElapsed timestamp={opts.timestamp}>
         {({timeElapsed}) => (
-          <DesktopWebTextLink
+          <TextLinkOnWebOnly
             type="md"
             style={pal.textLight}
             lineHeight={1.2}
diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx
index d24e47499..fbc0b5e11 100644
--- a/src/view/com/util/UserAvatar.tsx
+++ b/src/view/com/util/UserAvatar.tsx
@@ -23,14 +23,18 @@ interface BaseUserAvatarProps {
   type?: Type
   size: number
   avatar?: string | null
-  moderation?: ModerationUI
 }
 
 interface UserAvatarProps extends BaseUserAvatarProps {
-  onSelectNewAvatar?: (img: RNImage | null) => void
+  moderation?: ModerationUI
+}
+
+interface EditableUserAvatarProps extends BaseUserAvatarProps {
+  onSelectNewAvatar: (img: RNImage | null) => void
 }
 
 interface PreviewableUserAvatarProps extends BaseUserAvatarProps {
+  moderation?: ModerationUI
   did: string
   handle: string
 }
@@ -106,8 +110,65 @@ export function UserAvatar({
   size,
   avatar,
   moderation,
-  onSelectNewAvatar,
 }: UserAvatarProps) {
+  const pal = usePalette('default')
+
+  const aviStyle = useMemo(() => {
+    if (type === 'algo' || type === 'list') {
+      return {
+        width: size,
+        height: size,
+        borderRadius: size > 32 ? 8 : 3,
+      }
+    }
+    return {
+      width: size,
+      height: size,
+      borderRadius: Math.floor(size / 2),
+    }
+  }, [type, size])
+
+  const alert = useMemo(() => {
+    if (!moderation?.alert) {
+      return null
+    }
+    return (
+      <View style={[styles.alertIconContainer, pal.view]}>
+        <FontAwesomeIcon
+          icon="exclamation-circle"
+          style={styles.alertIcon}
+          size={Math.floor(size / 3)}
+        />
+      </View>
+    )
+  }, [moderation?.alert, size, pal])
+
+  return avatar &&
+    !((moderation?.blur && isAndroid) /* android crashes with blur */) ? (
+    <View style={{width: size, height: size}}>
+      <HighPriorityImage
+        testID="userAvatarImage"
+        style={aviStyle}
+        contentFit="cover"
+        source={{uri: avatar}}
+        blurRadius={moderation?.blur ? BLUR_AMOUNT : 0}
+      />
+      {alert}
+    </View>
+  ) : (
+    <View style={{width: size, height: size}}>
+      <DefaultAvatar type={type} size={size} />
+      {alert}
+    </View>
+  )
+}
+
+export function EditableUserAvatar({
+  type = 'user',
+  size,
+  avatar,
+  onSelectNewAvatar,
+}: EditableUserAvatarProps) {
   const store = useStores()
   const pal = usePalette('default')
   const {requestCameraAccessIfNeeded} = useCameraPermission()
@@ -146,7 +207,7 @@ export function UserAvatar({
               return
             }
 
-            onSelectNewAvatar?.(
+            onSelectNewAvatar(
               await openCamera(store, {
                 width: 1000,
                 height: 1000,
@@ -186,7 +247,7 @@ export function UserAvatar({
               path: item.path,
             })
 
-            onSelectNewAvatar?.(croppedImage)
+            onSelectNewAvatar(croppedImage)
           },
         },
         !!avatar && {
@@ -203,7 +264,7 @@ export function UserAvatar({
             web: 'trash',
           },
           onPress: async () => {
-            onSelectNewAvatar?.(null)
+            onSelectNewAvatar(null)
           },
         },
       ].filter(Boolean) as DropdownItem[],
@@ -216,23 +277,7 @@ export function UserAvatar({
     ],
   )
 
-  const alert = useMemo(() => {
-    if (!moderation?.alert) {
-      return null
-    }
-    return (
-      <View style={[styles.alertIconContainer, pal.view]}>
-        <FontAwesomeIcon
-          icon="exclamation-circle"
-          style={styles.alertIcon}
-          size={Math.floor(size / 3)}
-        />
-      </View>
-    )
-  }, [moderation?.alert, size, pal])
-
-  // onSelectNewAvatar is only passed as prop on the EditProfile component
-  return onSelectNewAvatar ? (
+  return (
     <NativeDropdown
       testID="changeAvatarBtn"
       items={dropdownItems}
@@ -256,23 +301,6 @@ export function UserAvatar({
         />
       </View>
     </NativeDropdown>
-  ) : avatar &&
-    !((moderation?.blur && isAndroid) /* android crashes with blur */) ? (
-    <View style={{width: size, height: size}}>
-      <HighPriorityImage
-        testID="userAvatarImage"
-        style={aviStyle}
-        contentFit="cover"
-        source={{uri: avatar}}
-        blurRadius={moderation?.blur ? BLUR_AMOUNT : 0}
-      />
-      {alert}
-    </View>
-  ) : (
-    <View style={{width: size, height: size}}>
-      <DefaultAvatar type={type} size={size} />
-      {alert}
-    </View>
   )
 }
 
diff --git a/src/view/com/util/UserInfoText.tsx b/src/view/com/util/UserInfoText.tsx
index 695711b2a..e4ca981d9 100644
--- a/src/view/com/util/UserInfoText.tsx
+++ b/src/view/com/util/UserInfoText.tsx
@@ -1,7 +1,7 @@
 import React, {useState, useEffect} from 'react'
 import {AppBskyActorGetProfile as GetProfile} from '@atproto/api'
 import {StyleProp, StyleSheet, TextStyle} from 'react-native'
-import {DesktopWebTextLink} from './Link'
+import {TextLinkOnWebOnly} from './Link'
 import {Text} from './text/Text'
 import {LoadingPlaceholder} from './LoadingPlaceholder'
 import {useStores} from 'state/index'
@@ -65,7 +65,7 @@ export function UserInfoText({
     )
   } else if (profile) {
     inner = (
-      <DesktopWebTextLink
+      <TextLinkOnWebOnly
         type={type}
         style={style}
         lineHeight={1.2}
diff --git a/src/view/com/util/images/AutoSizedImage.tsx b/src/view/com/util/images/AutoSizedImage.tsx
index 035e29c25..6cbcddc32 100644
--- a/src/view/com/util/images/AutoSizedImage.tsx
+++ b/src/view/com/util/images/AutoSizedImage.tsx
@@ -52,20 +52,20 @@ export function AutoSizedImage({
 
   if (onPress || onLongPress || onPressIn) {
     return (
+      // disable a11y rule because in this case we want the tags on the image (#1640)
+      // eslint-disable-next-line react-native-a11y/has-valid-accessibility-descriptors
       <Pressable
         onPress={onPress}
         onLongPress={onLongPress}
         onPressIn={onPressIn}
-        style={[styles.container, style]}
-        accessible={true}
-        accessibilityRole="button"
-        accessibilityLabel={alt || 'Image'}
-        accessibilityHint="Tap to view fully">
+        style={[styles.container, style]}>
         <Image
           style={[styles.image, {aspectRatio}]}
           source={uri}
-          accessible={false} // Must set for `accessibilityLabel` to work
+          accessible={true} // Must set for `accessibilityLabel` to work
           accessibilityIgnoresInvertColors
+          accessibilityLabel={alt}
+          accessibilityHint="Tap to view fully"
         />
         {children}
       </Pressable>
diff --git a/src/view/com/util/images/ImageLayoutGrid.tsx b/src/view/com/util/images/ImageLayoutGrid.tsx
index 2e352d086..4aa6f28de 100644
--- a/src/view/com/util/images/ImageLayoutGrid.tsx
+++ b/src/view/com/util/images/ImageLayoutGrid.tsx
@@ -63,8 +63,8 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
 
     case 4:
       return (
-        <View style={styles.flexRow}>
-          <View style={{flex: 1}}>
+        <>
+          <View style={styles.flexRow}>
             <View style={styles.smallItem}>
               <GalleryItem {...props} index={0} imageStyle={styles.image} />
             </View>
@@ -72,7 +72,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
               <GalleryItem {...props} index={2} imageStyle={styles.image} />
             </View>
           </View>
-          <View style={{flex: 1}}>
+          <View style={styles.flexRow}>
             <View style={styles.smallItem}>
               <GalleryItem {...props} index={1} imageStyle={styles.image} />
             </View>
@@ -80,7 +80,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
               <GalleryItem {...props} index={3} imageStyle={styles.image} />
             </View>
           </View>
-        </View>
+        </>
       )
 
     default: