about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2024-10-14 16:06:23 -0500
committerGitHub <noreply@github.com>2024-10-14 16:06:23 -0500
commit4c3c10d7f892777e48faccd534441ac7d88df042 (patch)
tree1d33ddaea5f4a7365325c5c951344dd994d1c8b4 /src
parent2d88463453abfad1e9e45bbd6cdbcd5824a7e770 (diff)
downloadvoidsky-4c3c10d7f892777e48faccd534441ac7d88df042.tar.zst
Link cards (#5677)
* New link card styles

* Cleanup of consituent parts, add hover state

* Fix gif alt text view

* Fix alt text view more

* Remove dupe

* Update remove button

* Remove added margin on gif
Diffstat (limited to 'src')
-rw-r--r--src/view/com/composer/ExternalEmbedRemoveBtn.tsx38
-rw-r--r--src/view/com/composer/GifAltText.tsx4
-rw-r--r--src/view/com/util/post-embeds/ExternalGifEmbed.tsx40
-rw-r--r--src/view/com/util/post-embeds/ExternalLinkEmbed.tsx219
-rw-r--r--src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx55
-rw-r--r--src/view/com/util/post-embeds/GifEmbed.tsx28
6 files changed, 153 insertions, 231 deletions
diff --git a/src/view/com/composer/ExternalEmbedRemoveBtn.tsx b/src/view/com/composer/ExternalEmbedRemoveBtn.tsx
index 57ccc2943..0dfa3ce09 100644
--- a/src/view/com/composer/ExternalEmbedRemoveBtn.tsx
+++ b/src/view/com/composer/ExternalEmbedRemoveBtn.tsx
@@ -1,34 +1,26 @@
 import React from 'react'
-import {TouchableOpacity} from 'react-native'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {View} from 'react-native'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
-import {s} from 'lib/styles'
+import {atoms as a} from '#/alf'
+import {Button, ButtonIcon} from '#/components/Button'
+import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
 
 export function ExternalEmbedRemoveBtn({onRemove}: {onRemove: () => void}) {
   const {_} = useLingui()
 
   return (
-    <TouchableOpacity
-      style={{
-        position: 'absolute',
-        top: 10,
-        right: 10,
-        height: 36,
-        width: 36,
-        backgroundColor: 'rgba(0, 0, 0, 0.75)',
-        borderRadius: 18,
-        alignItems: 'center',
-        justifyContent: 'center',
-        zIndex: 1,
-      }}
-      onPress={onRemove}
-      accessibilityRole="button"
-      accessibilityLabel={_(msg`Remove attachment`)}
-      accessibilityHint={_(msg`Removes the attachment`)}
-      onAccessibilityEscape={onRemove}>
-      <FontAwesomeIcon size={18} icon="xmark" style={s.white} />
-    </TouchableOpacity>
+    <View style={[a.absolute, a.pt_sm, a.pr_sm, {top: 0, right: 0}]}>
+      <Button
+        label={_(msg`Remove attachment`)}
+        onPress={onRemove}
+        size="small"
+        variant="solid"
+        color="secondary"
+        shape="round">
+        <ButtonIcon icon={X} size="sm" />
+      </Button>
+    </View>
   )
 }
diff --git a/src/view/com/composer/GifAltText.tsx b/src/view/com/composer/GifAltText.tsx
index 732bd4bd6..143d7b826 100644
--- a/src/view/com/composer/GifAltText.tsx
+++ b/src/view/com/composer/GifAltText.tsx
@@ -13,7 +13,7 @@ import {isAndroid} from '#/platform/detection'
 import {useResolveGifQuery} from '#/state/queries/resolve-link'
 import {Gif} from '#/state/queries/tenor'
 import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper'
-import {atoms as a, native, useTheme} from '#/alf'
+import {atoms as a, useTheme} from '#/alf'
 import {Button, ButtonText} from '#/components/Button'
 import * as Dialog from '#/components/Dialog'
 import {DialogControlProps} from '#/components/Dialog'
@@ -213,7 +213,7 @@ function AltTextInner({
               isPreferredAltText={true}
               params={params}
               hideAlt
-              style={[native({maxHeight: 225})]}
+              style={[{height: 225}]}
             />
           </View>
         </View>
diff --git a/src/view/com/util/post-embeds/ExternalGifEmbed.tsx b/src/view/com/util/post-embeds/ExternalGifEmbed.tsx
index 6f1c88dcd..6db4d6fef 100644
--- a/src/view/com/util/post-embeds/ExternalGifEmbed.tsx
+++ b/src/view/com/util/post-embeds/ExternalGifEmbed.tsx
@@ -4,7 +4,6 @@ import {
   GestureResponderEvent,
   LayoutChangeEvent,
   Pressable,
-  StyleSheet,
 } from 'react-native'
 import {Image, ImageLoadEventData} from 'expo-image'
 import {AppBskyEmbedExternal} from '@atproto/api'
@@ -18,7 +17,6 @@ import {atoms as a, useTheme} from '#/alf'
 import {useDialogControl} from '#/components/Dialog'
 import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
 import {Fill} from '#/components/Fill'
-import {MediaInsetBorder} from '#/components/MediaInsetBorder'
 import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
 
 export function ExternalGifEmbed({
@@ -116,8 +114,7 @@ export function ExternalGifEmbed({
       <Pressable
         style={[
           {height: imageDims.height},
-          styles.gifContainer,
-          a.rounded_md,
+          a.w_full,
           a.overflow_hidden,
           {
             borderBottomLeftRadius: 0,
@@ -166,42 +163,7 @@ export function ExternalGifEmbed({
             )}
           </Fill>
         )}
-        <MediaInsetBorder
-          opaque
-          style={[
-            {
-              borderBottomLeftRadius: 0,
-              borderBottomRightRadius: 0,
-            },
-          ]}
-        />
       </Pressable>
     </>
   )
 }
-
-const styles = StyleSheet.create({
-  topRadius: {
-    borderTopLeftRadius: 6,
-    borderTopRightRadius: 6,
-  },
-  layer: {
-    position: 'absolute',
-    top: 0,
-    left: 0,
-    right: 0,
-    bottom: 0,
-  },
-  overlayContainer: {
-    flex: 1,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  overlayLayer: {
-    zIndex: 2,
-  },
-  gifContainer: {
-    width: '100%',
-    overflow: 'hidden',
-  },
-})
diff --git a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
index eb03385d0..0399667b0 100644
--- a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
+++ b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
@@ -6,8 +6,6 @@ import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
 import {parseAltFromGIFDescription} from '#/lib/gif-alt-text'
-import {usePalette} from '#/lib/hooks/usePalette'
-import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
 import {shareUrl} from '#/lib/sharing'
 import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player'
 import {
@@ -17,13 +15,14 @@ import {
 import {toNiceDomain} from '#/lib/strings/url-helpers'
 import {isNative} from '#/platform/detection'
 import {useExternalEmbedsPrefs} from '#/state/preferences'
-import {Link} from '#/view/com/util/Link'
 import {ExternalGifEmbed} from '#/view/com/util/post-embeds/ExternalGifEmbed'
 import {ExternalPlayer} from '#/view/com/util/post-embeds/ExternalPlayerEmbed'
 import {GifEmbed} from '#/view/com/util/post-embeds/GifEmbed'
 import {atoms as a, useTheme} from '#/alf'
-import {MediaInsetBorder} from '#/components/MediaInsetBorder'
-import {Text} from '../text/Text'
+import {Divider} from '#/components/Divider'
+import {Earth_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
+import {Link} from '#/components/Link'
+import {Text} from '#/components/Typography'
 
 export const ExternalLinkEmbed = ({
   link,
@@ -37,16 +36,13 @@ export const ExternalLinkEmbed = ({
   hideAlt?: boolean
 }) => {
   const {_} = useLingui()
-  const pal = usePalette('default')
   const t = useTheme()
-  const {isMobile} = useWebMediaQueries()
   const externalEmbedPrefs = useExternalEmbedsPrefs()
-
+  const niceUrl = toNiceDomain(link.uri)
   const starterPackParsed = parseStarterPackUri(link.uri)
   const imageUri = starterPackParsed
     ? getStarterPackOgCard(starterPackParsed.name, starterPackParsed.rkey)
     : link.thumb
-
   const embedPlayerParams = React.useMemo(() => {
     const params = parseEmbedPlayerFromUrl(link.uri)
 
@@ -54,122 +50,131 @@ export const ExternalLinkEmbed = ({
       return params
     }
   }, [link.uri, externalEmbedPrefs])
+  const hasMedia = Boolean(imageUri || embedPlayerParams)
+
+  const onShareExternal = useCallback(() => {
+    if (link.uri && isNative) {
+      shareUrl(link.uri)
+    }
+  }, [link.uri])
 
   if (embedPlayerParams?.source === 'tenor') {
     const parsedAlt = parseAltFromGIFDescription(link.description)
     return (
-      <GifEmbed
-        params={embedPlayerParams}
-        thumb={link.thumb}
-        altText={parsedAlt.alt}
-        isPreferredAltText={parsedAlt.isPreferred}
-        hideAlt={hideAlt}
-      />
+      <View style={style}>
+        <GifEmbed
+          params={embedPlayerParams}
+          thumb={link.thumb}
+          altText={parsedAlt.alt}
+          isPreferredAltText={parsedAlt.isPreferred}
+          hideAlt={hideAlt}
+        />
+      </View>
     )
   }
 
   return (
-    <View style={[a.flex_col, a.rounded_md, a.w_full]}>
-      <LinkWrapper link={link} onOpen={onOpen} style={style}>
-        {imageUri && !embedPlayerParams ? (
-          <View>
+    <Link
+      label={link.title || _(msg`Open link to ${niceUrl}`)}
+      to={link.uri}
+      onPress={onOpen}
+      onLongPress={onShareExternal}>
+      {({hovered}) => (
+        <View
+          style={[
+            a.transition_color,
+            a.flex_col,
+            a.rounded_md,
+            a.overflow_hidden,
+            a.w_full,
+            a.border,
+            style,
+            hovered
+              ? t.atoms.border_contrast_high
+              : t.atoms.border_contrast_low,
+          ]}>
+          {imageUri && !embedPlayerParams ? (
             <Image
               style={{
                 aspectRatio: 1.91,
-                borderTopRightRadius: a.rounded_md.borderRadius,
-                borderTopLeftRadius: a.rounded_md.borderRadius,
               }}
               source={{uri: imageUri}}
               accessibilityIgnoresInvertColors
-              accessibilityLabel={starterPackParsed ? link.title : undefined}
-              accessibilityHint={
-                starterPackParsed ? _(msg`Navigate to starter pack`) : undefined
-              }
             />
-            <MediaInsetBorder
-              opaque
-              style={[
-                {
-                  borderBottomLeftRadius: 0,
-                  borderBottomRightRadius: 0,
-                },
-              ]}
-            />
-          </View>
-        ) : undefined}
-        {embedPlayerParams?.isGif ? (
-          <ExternalGifEmbed link={link} params={embedPlayerParams} />
-        ) : embedPlayerParams ? (
-          <ExternalPlayer link={link} params={embedPlayerParams} />
-        ) : undefined}
-        <View
-          style={[
-            a.border_b,
-            a.border_l,
-            a.border_r,
-            a.flex_1,
-            a.py_sm,
-            t.atoms.border_contrast_low,
-            {
-              borderBottomRightRadius: a.rounded_md.borderRadius,
-              borderBottomLeftRadius: a.rounded_md.borderRadius,
-              paddingHorizontal: isMobile ? 10 : 14,
-            },
-            !imageUri && !embedPlayerParams && [a.border, a.rounded_md],
-          ]}>
-          <Text
-            type="sm"
-            numberOfLines={1}
-            style={[pal.textLight, {marginVertical: 2}]}>
-            {toNiceDomain(link.uri)}
-          </Text>
-
-          {!embedPlayerParams?.isGif && !embedPlayerParams?.dimensions && (
-            <Text emoji type="lg-bold" numberOfLines={3} style={[pal.text]}>
-              {link.title || link.uri}
-            </Text>
-          )}
-          {link.description ? (
-            <Text
-              emoji
-              type="md"
-              numberOfLines={link.thumb ? 2 : 4}
-              style={[pal.text, a.mt_xs]}>
-              {link.description}
-            </Text>
           ) : undefined}
-        </View>
-      </LinkWrapper>
-    </View>
-  )
-}
 
-function LinkWrapper({
-  link,
-  onOpen,
-  style,
-  children,
-}: {
-  link: AppBskyEmbedExternal.ViewExternal
-  onOpen?: () => void
-  style?: StyleProp<ViewStyle>
-  children: React.ReactNode
-}) {
-  const onShareExternal = useCallback(() => {
-    if (link.uri && isNative) {
-      shareUrl(link.uri)
-    }
-  }, [link.uri])
+          {embedPlayerParams?.isGif ? (
+            <ExternalGifEmbed link={link} params={embedPlayerParams} />
+          ) : embedPlayerParams ? (
+            <ExternalPlayer link={link} params={embedPlayerParams} />
+          ) : undefined}
 
-  return (
-    <Link
-      asAnchor
-      anchorNoUnderline
-      href={link.uri}
-      style={[a.flex_1, a.rounded_sm, style]}
-      onBeforePress={onOpen}
-      onLongPress={onShareExternal}>
-      {children}
+          <View
+            style={[
+              a.flex_1,
+              a.pt_sm,
+              {gap: 3},
+              hasMedia && a.border_t,
+              hovered
+                ? t.atoms.border_contrast_high
+                : t.atoms.border_contrast_low,
+            ]}>
+            <View style={[{gap: 3}, a.pb_xs, a.px_md]}>
+              {!embedPlayerParams?.isGif && !embedPlayerParams?.dimensions && (
+                <Text
+                  emoji
+                  numberOfLines={3}
+                  style={[a.text_md, a.font_bold, a.leading_snug]}>
+                  {link.title || link.uri}
+                </Text>
+              )}
+              {link.description ? (
+                <Text
+                  emoji
+                  numberOfLines={link.thumb ? 2 : 4}
+                  style={[a.text_sm, a.leading_snug]}>
+                  {link.description}
+                </Text>
+              ) : undefined}
+            </View>
+            <View style={[a.px_md]}>
+              <Divider />
+              <View
+                style={[
+                  a.flex_row,
+                  a.align_center,
+                  a.gap_2xs,
+                  a.pb_sm,
+                  {
+                    paddingTop: 6, // off menu
+                  },
+                ]}>
+                <Globe
+                  size="xs"
+                  style={[
+                    a.transition_color,
+                    hovered
+                      ? t.atoms.text_contrast_medium
+                      : t.atoms.text_contrast_low,
+                  ]}
+                />
+                <Text
+                  numberOfLines={1}
+                  style={[
+                    a.transition_color,
+                    a.text_xs,
+                    a.leading_tight,
+                    hovered
+                      ? t.atoms.text_contrast_high
+                      : t.atoms.text_contrast_medium,
+                  ]}>
+                  {toNiceDomain(link.uri)}
+                </Text>
+              </View>
+            </View>
+          </View>
+        </View>
+      )}
     </Link>
   )
 }
diff --git a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
index 6d5eacd1a..8ac7ee499 100644
--- a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
+++ b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
@@ -29,7 +29,6 @@ import {atoms as a, useTheme} from '#/alf'
 import {useDialogControl} from '#/components/Dialog'
 import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
 import {Fill} from '#/components/Fill'
-import {MediaInsetBorder} from '#/components/MediaInsetBorder'
 import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
 import {EventStopper} from '../EventStopper'
 
@@ -59,7 +58,7 @@ function PlaceholderOverlay({
         accessibilityLabel={_(msg`Play Video`)}
         accessibilityHint={_(msg`Play Video`)}
         onPress={onPress}
-        style={[styles.overlayContainer, styles.topRadius]}>
+        style={[styles.overlayContainer]}>
         {!isPlayerActive ? (
           <PlayButtonIcon />
         ) : (
@@ -108,16 +107,6 @@ function Player({
         style={styles.webview}
         setSupportMultipleWindows={false} // Prevent any redirects from opening a new window (ads)
       />
-
-      <MediaInsetBorder
-        opaque
-        style={[
-          {
-            borderBottomLeftRadius: 0,
-            borderBottomRightRadius: 0,
-          },
-        ]}
-      />
     </EventStopper>
   )
 }
@@ -227,66 +216,34 @@ export function ExternalPlayer({
       <Animated.View
         ref={viewRef}
         collapsable={false}
-        style={[
-          aspect,
-          a.rounded_md,
-          a.overflow_hidden,
-          {
-            borderBottomLeftRadius: 0,
-            borderBottomRightRadius: 0,
-          },
-        ]}>
+        style={[aspect, a.overflow_hidden]}>
         {link.thumb && (!isPlayerActive || isLoading) ? (
           <>
             <Image
-              style={[a.flex_1, styles.topRadius]}
+              style={[a.flex_1]}
               source={{uri: link.thumb}}
               accessibilityIgnoresInvertColors
             />
             <Fill
               style={[
-                a.rounded_md,
                 t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
                 {
-                  borderBottomLeftRadius: 0,
-                  borderBottomRightRadius: 0,
                   opacity: 0.3,
                 },
               ]}
             />
-            <MediaInsetBorder
-              opaque
-              style={[
-                {
-                  borderBottomLeftRadius: 0,
-                  borderBottomRightRadius: 0,
-                },
-              ]}
-            />
           </>
         ) : (
           <Fill
             style={[
-              a.rounded_md,
               {
                 backgroundColor:
                   t.name === 'light' ? t.palette.contrast_975 : 'black',
-                borderBottomLeftRadius: 0,
-                borderBottomRightRadius: 0,
                 opacity: 0.3,
               },
             ]}
           />
         )}
-        <MediaInsetBorder
-          opaque
-          style={[
-            {
-              borderBottomLeftRadius: 0,
-              borderBottomRightRadius: 0,
-            },
-          ]}
-        />
         <PlaceholderOverlay
           isLoading={isLoading}
           isPlayerActive={isPlayerActive}
@@ -303,10 +260,6 @@ export function ExternalPlayer({
 }
 
 const styles = StyleSheet.create({
-  topRadius: {
-    borderTopLeftRadius: a.rounded_md.borderRadius,
-    borderTopRightRadius: a.rounded_md.borderRadius,
-  },
   overlayContainer: {
     flex: 1,
     justifyContent: 'center',
@@ -319,8 +272,6 @@ const styles = StyleSheet.create({
     zIndex: 3,
   },
   webview: {
-    borderTopRightRadius: a.rounded_md.borderRadius,
-    borderTopLeftRadius: a.rounded_md.borderRadius,
     backgroundColor: 'transparent',
   },
   gifContainer: {
diff --git a/src/view/com/util/post-embeds/GifEmbed.tsx b/src/view/com/util/post-embeds/GifEmbed.tsx
index fc66278c9..4dbc7d588 100644
--- a/src/view/com/util/post-embeds/GifEmbed.tsx
+++ b/src/view/com/util/post-embeds/GifEmbed.tsx
@@ -18,7 +18,6 @@ import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
 import {atoms as a, useTheme} from '#/alf'
 import {Fill} from '#/components/Fill'
 import {Loader} from '#/components/Loader'
-import {MediaInsetBorder} from '#/components/MediaInsetBorder'
 import * as Prompt from '#/components/Prompt'
 import {Text} from '#/components/Typography'
 import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
@@ -51,7 +50,6 @@ function PlaybackControls({
         a.inset_0,
         a.w_full,
         a.h_full,
-        a.rounded_md,
         {
           zIndex: 2,
           backgroundColor: !isLoaded
@@ -114,12 +112,27 @@ export function GifEmbed({
   }, [])
 
   return (
-    <View style={[a.rounded_md, a.overflow_hidden, a.mt_sm, style]}>
+    <View
+      style={[
+        a.rounded_md,
+        a.overflow_hidden,
+        a.border,
+        t.atoms.border_contrast_low,
+        {aspectRatio: params.dimensions!.width / params.dimensions!.height},
+        style,
+      ]}>
       <View
         style={[
-          a.rounded_md,
-          a.overflow_hidden,
-          {aspectRatio: params.dimensions!.width / params.dimensions!.height},
+          a.absolute,
+          /*
+           * Aspect ratio was being clipped weirdly on web -esb
+           */
+          {
+            top: -2,
+            bottom: -2,
+            left: -2,
+            right: -2,
+          },
         ]}>
         <PlaybackControls
           onPress={onPress}
@@ -129,7 +142,7 @@ export function GifEmbed({
         <GifView
           source={params.playerUri}
           placeholderSource={thumb}
-          style={[a.flex_1, a.rounded_md]}
+          style={[a.flex_1]}
           autoplay={!autoplayDisabled}
           onPlayerStateChange={onPlayerStateChange}
           ref={playerRef}
@@ -146,7 +159,6 @@ export function GifEmbed({
             ]}
           />
         )}
-        <MediaInsetBorder />
         {!hideAlt && isPreferredAltText && <AltText text={altText} />}
       </View>
     </View>