diff --git a/src/components/Fill.tsx b/src/components/Fill.tsx
new file mode 100644
index 0000000000..ac74f1660f
--- /dev/null
+++ b/src/components/Fill.tsx
@@ -0,0 +1,11 @@
+import React from 'react'
+import {View} from 'react-native'
+
+import {atoms as a, ViewStyleProp} from '#/alf'
+
+export function Fill({
+ children,
+ style,
+}: {children?: React.ReactNode} & ViewStyleProp) {
+ return {children}
+}
diff --git a/src/components/MediaInsetBorder.tsx b/src/components/MediaInsetBorder.tsx
new file mode 100644
index 0000000000..839d79caeb
--- /dev/null
+++ b/src/components/MediaInsetBorder.tsx
@@ -0,0 +1,42 @@
+import React from 'react'
+
+import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
+import {Fill} from '#/components/Fill'
+
+/**
+ * Applies and thin border within a bounding box. Used to contrast media from
+ * bg of the container.
+ */
+export function MediaInsetBorder({
+ children,
+ style,
+ opaque,
+}: {
+ children?: React.ReactNode
+ /**
+ * Used where this border needs to match adjacent borders, such as in
+ * external link previews
+ */
+ opaque?: boolean
+} & ViewStyleProp) {
+ const t = useTheme()
+ const isLight = t.name === 'light'
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/components/MediaPreview.tsx b/src/components/MediaPreview.tsx
index f2ebb4584e..28609c6f49 100644
--- a/src/components/MediaPreview.tsx
+++ b/src/components/MediaPreview.tsx
@@ -11,6 +11,7 @@ import {Trans} from '@lingui/macro'
import {parseTenorGif} from '#/lib/strings/embed-player'
import {atoms as a, useTheme} from '#/alf'
+import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {Text} from '#/components/Typography'
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
@@ -104,6 +105,7 @@ export function ImageItem({
accessibilityHint={alt}
accessibilityLabel=""
/>
+
{children}
)
diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx
index eb46a8bdb4..4ad37ebd75 100644
--- a/src/view/com/util/UserAvatar.tsx
+++ b/src/view/com/util/UserAvatar.tsx
@@ -19,7 +19,7 @@ import {colors} from 'lib/styles'
import {isAndroid, isNative, isWeb} from 'platform/detection'
import {precacheProfile} from 'state/queries/profile'
import {HighPriorityImage} from 'view/com/util/images/Image'
-import {tokens, useTheme} from '#/alf'
+import {atoms as a, tokens, useTheme} from '#/alf'
import {
Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled,
Camera_Stroke2_Corner0_Rounded as Camera,
@@ -27,6 +27,7 @@ import {
import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive'
import {Trash_Stroke2_Corner0_Rounded as Trash} 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 {openCamera, openCropper, openPicker} from '../../../lib/media/picker'
@@ -240,6 +241,7 @@ let UserAvatar = ({
onLoad={onLoad}
/>
)}
+
{alert}
) : (
diff --git a/src/view/com/util/images/AutoSizedImage.tsx b/src/view/com/util/images/AutoSizedImage.tsx
index 932a182803..9abbe2875f 100644
--- a/src/view/com/util/images/AutoSizedImage.tsx
+++ b/src/view/com/util/images/AutoSizedImage.tsx
@@ -11,6 +11,7 @@ import {isNative} from '#/platform/detection'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/components/icons/ArrowsDiagonal'
+import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {Text} from '#/components/Typography'
export function useImageAspectRatio({
@@ -140,6 +141,7 @@ export function AutoSizedImage({
accessibilityLabel={image.alt}
accessibilityHint=""
/>
+
{(hasAlt || isCropped) && !hideBadge ? (
void
@@ -46,7 +47,7 @@ export const GalleryItem: FC = ({
onLongPress={onLongPress ? () => onLongPress(index) : undefined}
style={[
a.flex_1,
- a.rounded_xs,
+ a.rounded_sm,
a.overflow_hidden,
t.atoms.bg_contrast_25,
imageStyle,
@@ -62,6 +63,7 @@ export const GalleryItem: FC = ({
accessibilityHint=""
accessibilityIgnoresInvertColors
/>
+
{hasAlt && !hideBadges ? (
- {(!isPrefetched || !isAnimating) && ( // If we have not loaded or are not animating, show the overlay
-
-
- {!isAnimating || !isPlayerActive ? ( // Play button when not animating or not active
-
- ) : (
- // Activity indicator while gif loads
-
- )}
-
-
- )}
+
+ {(!isPrefetched || !isAnimating) && (
+
+
+
+ {!isAnimating || !isPlayerActive ? ( // Play button when not animating or not active
+
+ ) : (
+ // Activity indicator while gif loads
+
+ )}
+
+ )}
+
>
)
@@ -171,7 +196,6 @@ const styles = StyleSheet.create({
flex: 1,
justifyContent: 'center',
alignItems: 'center',
- backgroundColor: 'rgba(0,0,0,0.5)',
},
overlayLayer: {
zIndex: 2,
diff --git a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
index cc742c8c01..0bd65de8b2 100644
--- a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
+++ b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
@@ -21,6 +21,7 @@ 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'
export const ExternalLinkEmbed = ({
@@ -36,6 +37,7 @@ export const ExternalLinkEmbed = ({
}) => {
const {_} = useLingui()
const pal = usePalette('default')
+ const t = useTheme()
const {isMobile} = useWebMediaQueries()
const externalEmbedPrefs = useExternalEmbedsPrefs()
@@ -60,19 +62,30 @@ export const ExternalLinkEmbed = ({
{imageUri && !embedPlayerParams ? (
-
+
+
+
+
) : undefined}
{embedPlayerParams?.isGif ? (
@@ -81,11 +94,18 @@ export const ExternalLinkEmbed = ({
) : undefined}
children: React.ReactNode
}) {
- const t = useTheme()
-
const onShareExternal = useCallback(() => {
if (link.uri && isNative) {
shareUrl(link.uri)
@@ -137,14 +155,7 @@ function LinkWrapper({
asAnchor
anchorNoUnderline
href={link.uri}
- style={[
- a.flex_1,
- a.border,
- a.rounded_sm,
- t.atoms.border_contrast_medium,
- style,
- ]}
- hoverStyle={t.atoms.border_contrast_high}
+ style={[a.flex_1, a.rounded_sm, style]}
onBeforePress={onOpen}
onLongPress={onShareExternal}>
{children}
diff --git a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
index 111867fc61..a1e509ef47 100644
--- a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
+++ b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx
@@ -25,9 +25,11 @@ import {NavigationProp} from '#/lib/routes/types'
import {EmbedPlayerParams, getPlayerAspect} from '#/lib/strings/embed-player'
import {isNative} from '#/platform/detection'
import {useExternalEmbedsPrefs} from '#/state/preferences'
-import {atoms as a} from '#/alf'
+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'
@@ -106,6 +108,16 @@ function Player({
style={styles.webview}
setSupportMultipleWindows={false} // Prevent any redirects from opening a new window (ads)
/>
+
+
)
}
@@ -118,6 +130,7 @@ export function ExternalPlayer({
link: AppBskyEmbedExternal.ViewExternal
params: EmbedPlayerParams
}) {
+ const t = useTheme()
const navigation = useNavigation()
const insets = useSafeAreaInsets()
const windowDims = useWindowDimensions()
@@ -211,13 +224,38 @@ export function ExternalPlayer({
onAccept={onAcceptConsent}
/>
-
+
{link.thumb && (!isPlayerActive || isLoading) && (
-
+ <>
+
+
+
+ >
)}
}) {
+ const t = useTheme()
const {_} = useLingui()
const autoplayDisabled = useAutoplayDisabled()
@@ -138,6 +139,17 @@ export function GifEmbed({
accessibilityHint={_(msg`Animated GIF`)}
accessibilityLabel={parsedAlt.alt}
/>
+ {!playerState.isPlaying && (
+
+ )}
+
{!hideAlt && parsedAlt.isPreferred && }
diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx
index 87a6edebde..d50119025f 100644
--- a/src/view/com/util/post-embeds/QuoteEmbed.tsx
+++ b/src/view/com/util/post-embeds/QuoteEmbed.tsx
@@ -33,7 +33,7 @@ import {InfoCircleIcon} from 'lib/icons'
import {makeProfileLink} from 'lib/routes/links'
import {precacheProfile} from 'state/queries/profile'
import {ComposerOptsQuote} from 'state/shell/composer'
-import {atoms as a} from '#/alf'
+import {atoms as a, useTheme} from '#/alf'
import {RichText} from '#/components/RichText'
import {ContentHider} from '../../../../components/moderation/ContentHider'
import {PostAlerts} from '../../../../components/moderation/PostAlerts'
@@ -56,6 +56,7 @@ export function MaybeQuoteEmbed({
allowNestedQuotes?: boolean
viewContext?: QuoteEmbedViewContext
}) {
+ const t = useTheme()
const pal = usePalette('default')
const {currentAccount} = useSession()
if (
@@ -75,7 +76,8 @@ export function MaybeQuoteEmbed({
)
} else if (AppBskyEmbedRecord.isViewBlocked(embed.record)) {
return (
-
+
Blocked
@@ -84,7 +86,8 @@ export function MaybeQuoteEmbed({
)
} else if (AppBskyEmbedRecord.isViewNotFound(embed.record)) {
return (
-
+
Deleted
@@ -96,7 +99,8 @@ export function MaybeQuoteEmbed({
? embed.record.uri.includes(currentAccount.did)
: false
return (
-
+
{isViewerOwner ? (
@@ -169,6 +173,7 @@ export function QuoteEmbed({
allowNestedQuotes?: boolean
viewContext?: QuoteEmbedViewContext
}) {
+ const t = useTheme()
const queryClient = useQueryClient()
const pal = usePalette('default')
const itemUrip = new AtUri(quote.uri)
@@ -214,7 +219,7 @@ export function QuoteEmbed({
return (
+
)
}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx
index 441be75724..5f569a818b 100644
--- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx
@@ -4,6 +4,7 @@ import {AppBskyEmbedVideo} from '@atproto/api'
import Hls from 'hls.js'
import {atoms as a} from '#/alf'
+import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {Controls} from './VideoWebControls'
export function VideoEmbedInnerWeb({
@@ -119,6 +120,7 @@ export function VideoEmbedInnerWeb({
fullscreenRef={containerRef}
hasSubtitleTrack={hasSubtitleTrack}
/>
+
)