Image/video border + tweaks (#5324)
* Image/video border (#5253) * Update AutoSizedImage.tsx * Update AutoSizedImage.tsx * Update Gallery.tsx * Update ExternalLinkEmbed.tsx * Update MediaPreview.tsx * Update UserAvatar.tsx * Update ExternalLinkEmbed.tsx * Update ExternalPlayerEmbed.tsx * Update ExternalGifEmbed.tsx * Update GifEmbed.tsx * Update ExternalGifEmbed.tsx * Update GifEmbed.tsx * Update UserAvatar.tsx * Update ExternalPlayerEmbed.tsx * Update ExternalPlayerEmbed.tsx * video * Update QuoteEmbed.tsx * Tweaks, abstract components --------- Co-authored-by: Minseo Lee <itoupluk427@gmail.com>zio/dev^2
parent
c7231537f1
commit
b3381da1c1
|
@ -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 <View style={[a.absolute, a.inset_0, style]}>{children}</View>
|
||||||
|
}
|
|
@ -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 (
|
||||||
|
<Fill
|
||||||
|
style={[
|
||||||
|
a.rounded_sm,
|
||||||
|
a.border,
|
||||||
|
opaque
|
||||||
|
? [t.atoms.border_contrast_low]
|
||||||
|
: [
|
||||||
|
isLight
|
||||||
|
? t.atoms.border_contrast_low
|
||||||
|
: t.atoms.border_contrast_high,
|
||||||
|
{opacity: 0.6},
|
||||||
|
],
|
||||||
|
style,
|
||||||
|
]}>
|
||||||
|
{children}
|
||||||
|
</Fill>
|
||||||
|
)
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import {Trans} from '@lingui/macro'
|
||||||
|
|
||||||
import {parseTenorGif} from '#/lib/strings/embed-player'
|
import {parseTenorGif} from '#/lib/strings/embed-player'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
|
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
|
||||||
|
|
||||||
|
@ -104,6 +105,7 @@ export function ImageItem({
|
||||||
accessibilityHint={alt}
|
accessibilityHint={alt}
|
||||||
accessibilityLabel=""
|
accessibilityLabel=""
|
||||||
/>
|
/>
|
||||||
|
<MediaInsetBorder style={[a.rounded_xs]} />
|
||||||
{children}
|
{children}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {colors} from 'lib/styles'
|
||||||
import {isAndroid, isNative, isWeb} from 'platform/detection'
|
import {isAndroid, isNative, isWeb} from 'platform/detection'
|
||||||
import {precacheProfile} from 'state/queries/profile'
|
import {precacheProfile} from 'state/queries/profile'
|
||||||
import {HighPriorityImage} from 'view/com/util/images/Image'
|
import {HighPriorityImage} from 'view/com/util/images/Image'
|
||||||
import {tokens, useTheme} from '#/alf'
|
import {atoms as a, tokens, useTheme} from '#/alf'
|
||||||
import {
|
import {
|
||||||
Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled,
|
Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled,
|
||||||
Camera_Stroke2_Corner0_Rounded as Camera,
|
Camera_Stroke2_Corner0_Rounded as Camera,
|
||||||
|
@ -27,6 +27,7 @@ import {
|
||||||
import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive'
|
import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive'
|
||||||
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
|
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
|
||||||
import {Link} from '#/components/Link'
|
import {Link} from '#/components/Link'
|
||||||
|
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||||
import * as Menu from '#/components/Menu'
|
import * as Menu from '#/components/Menu'
|
||||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||||
import {openCamera, openCropper, openPicker} from '../../../lib/media/picker'
|
import {openCamera, openCropper, openPicker} from '../../../lib/media/picker'
|
||||||
|
@ -240,6 +241,7 @@ let UserAvatar = ({
|
||||||
onLoad={onLoad}
|
onLoad={onLoad}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<MediaInsetBorder style={[a.rounded_full]} />
|
||||||
{alert}
|
{alert}
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {isNative} from '#/platform/detection'
|
||||||
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
||||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||||
import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/components/icons/ArrowsDiagonal'
|
import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/components/icons/ArrowsDiagonal'
|
||||||
|
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
export function useImageAspectRatio({
|
export function useImageAspectRatio({
|
||||||
|
@ -140,6 +141,7 @@ export function AutoSizedImage({
|
||||||
accessibilityLabel={image.alt}
|
accessibilityLabel={image.alt}
|
||||||
accessibilityHint=""
|
accessibilityHint=""
|
||||||
/>
|
/>
|
||||||
|
<MediaInsetBorder />
|
||||||
|
|
||||||
{(hasAlt || isCropped) && !hideBadge ? (
|
{(hasAlt || isCropped) && !hideBadge ? (
|
||||||
<View
|
<View
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {useLingui} from '@lingui/react'
|
||||||
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
||||||
import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
|
import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
type EventFunction = (index: number) => void
|
type EventFunction = (index: number) => void
|
||||||
|
@ -46,7 +47,7 @@ export const GalleryItem: FC<GalleryItemProps> = ({
|
||||||
onLongPress={onLongPress ? () => onLongPress(index) : undefined}
|
onLongPress={onLongPress ? () => onLongPress(index) : undefined}
|
||||||
style={[
|
style={[
|
||||||
a.flex_1,
|
a.flex_1,
|
||||||
a.rounded_xs,
|
a.rounded_sm,
|
||||||
a.overflow_hidden,
|
a.overflow_hidden,
|
||||||
t.atoms.bg_contrast_25,
|
t.atoms.bg_contrast_25,
|
||||||
imageStyle,
|
imageStyle,
|
||||||
|
@ -62,6 +63,7 @@ export const GalleryItem: FC<GalleryItemProps> = ({
|
||||||
accessibilityHint=""
|
accessibilityHint=""
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
|
<MediaInsetBorder />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
{hasAlt && !hideBadges ? (
|
{hasAlt && !hideBadges ? (
|
||||||
<View
|
<View
|
||||||
|
|
|
@ -5,19 +5,21 @@ import {
|
||||||
LayoutChangeEvent,
|
LayoutChangeEvent,
|
||||||
Pressable,
|
Pressable,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
View,
|
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {Image, ImageLoadEventData} from 'expo-image'
|
import {Image, ImageLoadEventData} from 'expo-image'
|
||||||
import {AppBskyEmbedExternal} from '@atproto/api'
|
import {AppBskyEmbedExternal} from '@atproto/api'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {EmbedPlayerParams, getGifDims} from '#/lib/strings/embed-player'
|
import {EmbedPlayerParams, getGifDims} from '#/lib/strings/embed-player'
|
||||||
import {isIOS, isNative, isWeb} from '#/platform/detection'
|
import {isIOS, isNative, isWeb} from '#/platform/detection'
|
||||||
import {useExternalEmbedsPrefs} from '#/state/preferences'
|
import {useExternalEmbedsPrefs} from '#/state/preferences'
|
||||||
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
|
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({
|
export function ExternalGifEmbed({
|
||||||
link,
|
link,
|
||||||
|
@ -26,6 +28,7 @@ export function ExternalGifEmbed({
|
||||||
link: AppBskyEmbedExternal.ViewExternal
|
link: AppBskyEmbedExternal.ViewExternal
|
||||||
params: EmbedPlayerParams
|
params: EmbedPlayerParams
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
const externalEmbedsPrefs = useExternalEmbedsPrefs()
|
const externalEmbedsPrefs = useExternalEmbedsPrefs()
|
||||||
|
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
@ -113,26 +116,19 @@ export function ExternalGifEmbed({
|
||||||
<Pressable
|
<Pressable
|
||||||
style={[
|
style={[
|
||||||
{height: imageDims.height},
|
{height: imageDims.height},
|
||||||
styles.topRadius,
|
|
||||||
styles.gifContainer,
|
styles.gifContainer,
|
||||||
|
a.rounded_sm,
|
||||||
|
a.overflow_hidden,
|
||||||
|
{
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
onPress={onPlayPress}
|
onPress={onPlayPress}
|
||||||
onLayout={onLayout}
|
onLayout={onLayout}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityHint={_(msg`Plays the GIF`)}
|
accessibilityHint={_(msg`Plays the GIF`)}
|
||||||
accessibilityLabel={_(msg`Play ${link.title}`)}>
|
accessibilityLabel={_(msg`Play ${link.title}`)}>
|
||||||
{(!isPrefetched || !isAnimating) && ( // If we have not loaded or are not animating, show the overlay
|
|
||||||
<View style={[styles.layer, styles.overlayLayer]}>
|
|
||||||
<View style={[styles.overlayContainer, styles.topRadius]}>
|
|
||||||
{!isAnimating || !isPlayerActive ? ( // Play button when not animating or not active
|
|
||||||
<FontAwesomeIcon icon="play" size={42} color="white" />
|
|
||||||
) : (
|
|
||||||
// Activity indicator while gif loads
|
|
||||||
<ActivityIndicator size="large" color="white" />
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<Image
|
<Image
|
||||||
source={{
|
source={{
|
||||||
uri:
|
uri:
|
||||||
|
@ -150,6 +146,35 @@ export function ExternalGifEmbed({
|
||||||
accessibilityHint={link.title}
|
accessibilityHint={link.title}
|
||||||
cachePolicy={isIOS ? 'disk' : 'memory-disk'} // cant control playback with memory-disk on ios
|
cachePolicy={isIOS ? 'disk' : 'memory-disk'} // cant control playback with memory-disk on ios
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{(!isPrefetched || !isAnimating) && (
|
||||||
|
<Fill style={[a.align_center, a.justify_center]}>
|
||||||
|
<Fill
|
||||||
|
style={[
|
||||||
|
t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
|
||||||
|
{
|
||||||
|
opacity: 0.3,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{!isAnimating || !isPlayerActive ? ( // Play button when not animating or not active
|
||||||
|
<PlayButtonIcon />
|
||||||
|
) : (
|
||||||
|
// Activity indicator while gif loads
|
||||||
|
<ActivityIndicator size="large" color="white" />
|
||||||
|
)}
|
||||||
|
</Fill>
|
||||||
|
)}
|
||||||
|
<MediaInsetBorder
|
||||||
|
opaque
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -171,7 +196,6 @@ const styles = StyleSheet.create({
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
||||||
},
|
},
|
||||||
overlayLayer: {
|
overlayLayer: {
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {ExternalGifEmbed} from 'view/com/util/post-embeds/ExternalGifEmbed'
|
||||||
import {ExternalPlayer} from 'view/com/util/post-embeds/ExternalPlayerEmbed'
|
import {ExternalPlayer} from 'view/com/util/post-embeds/ExternalPlayerEmbed'
|
||||||
import {GifEmbed} from 'view/com/util/post-embeds/GifEmbed'
|
import {GifEmbed} from 'view/com/util/post-embeds/GifEmbed'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||||
import {Text} from '../text/Text'
|
import {Text} from '../text/Text'
|
||||||
|
|
||||||
export const ExternalLinkEmbed = ({
|
export const ExternalLinkEmbed = ({
|
||||||
|
@ -36,6 +37,7 @@ export const ExternalLinkEmbed = ({
|
||||||
}) => {
|
}) => {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
|
const t = useTheme()
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
const externalEmbedPrefs = useExternalEmbedsPrefs()
|
const externalEmbedPrefs = useExternalEmbedsPrefs()
|
||||||
|
|
||||||
|
@ -60,11 +62,12 @@ export const ExternalLinkEmbed = ({
|
||||||
<View style={[a.flex_col, a.rounded_sm, a.overflow_hidden]}>
|
<View style={[a.flex_col, a.rounded_sm, a.overflow_hidden]}>
|
||||||
<LinkWrapper link={link} onOpen={onOpen} style={style}>
|
<LinkWrapper link={link} onOpen={onOpen} style={style}>
|
||||||
{imageUri && !embedPlayerParams ? (
|
{imageUri && !embedPlayerParams ? (
|
||||||
|
<View>
|
||||||
<Image
|
<Image
|
||||||
style={{
|
style={{
|
||||||
aspectRatio: 1.91,
|
aspectRatio: 1.91,
|
||||||
borderTopRightRadius: 6,
|
borderTopRightRadius: 8,
|
||||||
borderTopLeftRadius: 6,
|
borderTopLeftRadius: 8,
|
||||||
}}
|
}}
|
||||||
source={{uri: imageUri}}
|
source={{uri: imageUri}}
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
|
@ -73,6 +76,16 @@ export const ExternalLinkEmbed = ({
|
||||||
starterPackParsed ? _(msg`Navigate to starter pack`) : undefined
|
starterPackParsed ? _(msg`Navigate to starter pack`) : undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<MediaInsetBorder
|
||||||
|
opaque
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
{embedPlayerParams?.isGif ? (
|
{embedPlayerParams?.isGif ? (
|
||||||
<ExternalGifEmbed link={link} params={embedPlayerParams} />
|
<ExternalGifEmbed link={link} params={embedPlayerParams} />
|
||||||
|
@ -81,11 +94,18 @@ export const ExternalLinkEmbed = ({
|
||||||
) : undefined}
|
) : undefined}
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
|
a.border_b,
|
||||||
|
a.border_l,
|
||||||
|
a.border_r,
|
||||||
a.flex_1,
|
a.flex_1,
|
||||||
a.py_sm,
|
a.py_sm,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
{
|
{
|
||||||
|
borderBottomRightRadius: 8,
|
||||||
|
borderBottomLeftRadius: 8,
|
||||||
paddingHorizontal: isMobile ? 10 : 14,
|
paddingHorizontal: isMobile ? 10 : 14,
|
||||||
},
|
},
|
||||||
|
!imageUri && !embedPlayerParams && [a.border, a.rounded_sm],
|
||||||
]}>
|
]}>
|
||||||
<Text
|
<Text
|
||||||
type="sm"
|
type="sm"
|
||||||
|
@ -124,8 +144,6 @@ function LinkWrapper({
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
|
||||||
|
|
||||||
const onShareExternal = useCallback(() => {
|
const onShareExternal = useCallback(() => {
|
||||||
if (link.uri && isNative) {
|
if (link.uri && isNative) {
|
||||||
shareUrl(link.uri)
|
shareUrl(link.uri)
|
||||||
|
@ -137,14 +155,7 @@ function LinkWrapper({
|
||||||
asAnchor
|
asAnchor
|
||||||
anchorNoUnderline
|
anchorNoUnderline
|
||||||
href={link.uri}
|
href={link.uri}
|
||||||
style={[
|
style={[a.flex_1, a.rounded_sm, style]}
|
||||||
a.flex_1,
|
|
||||||
a.border,
|
|
||||||
a.rounded_sm,
|
|
||||||
t.atoms.border_contrast_medium,
|
|
||||||
style,
|
|
||||||
]}
|
|
||||||
hoverStyle={t.atoms.border_contrast_high}
|
|
||||||
onBeforePress={onOpen}
|
onBeforePress={onOpen}
|
||||||
onLongPress={onShareExternal}>
|
onLongPress={onShareExternal}>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -25,9 +25,11 @@ import {NavigationProp} from '#/lib/routes/types'
|
||||||
import {EmbedPlayerParams, getPlayerAspect} from '#/lib/strings/embed-player'
|
import {EmbedPlayerParams, getPlayerAspect} from '#/lib/strings/embed-player'
|
||||||
import {isNative} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
import {useExternalEmbedsPrefs} from '#/state/preferences'
|
import {useExternalEmbedsPrefs} from '#/state/preferences'
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
|
import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
|
||||||
|
import {Fill} from '#/components/Fill'
|
||||||
|
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||||
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
|
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
|
||||||
import {EventStopper} from '../EventStopper'
|
import {EventStopper} from '../EventStopper'
|
||||||
|
|
||||||
|
@ -106,6 +108,16 @@ function Player({
|
||||||
style={styles.webview}
|
style={styles.webview}
|
||||||
setSupportMultipleWindows={false} // Prevent any redirects from opening a new window (ads)
|
setSupportMultipleWindows={false} // Prevent any redirects from opening a new window (ads)
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<MediaInsetBorder
|
||||||
|
opaque
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</EventStopper>
|
</EventStopper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -118,6 +130,7 @@ export function ExternalPlayer({
|
||||||
link: AppBskyEmbedExternal.ViewExternal
|
link: AppBskyEmbedExternal.ViewExternal
|
||||||
params: EmbedPlayerParams
|
params: EmbedPlayerParams
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
const windowDims = useWindowDimensions()
|
const windowDims = useWindowDimensions()
|
||||||
|
@ -211,13 +224,38 @@ export function ExternalPlayer({
|
||||||
onAccept={onAcceptConsent}
|
onAccept={onAcceptConsent}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Animated.View ref={viewRef} collapsable={false} style={[aspect]}>
|
<Animated.View
|
||||||
|
ref={viewRef}
|
||||||
|
collapsable={false}
|
||||||
|
style={[aspect, a.rounded_sm]}>
|
||||||
{link.thumb && (!isPlayerActive || isLoading) && (
|
{link.thumb && (!isPlayerActive || isLoading) && (
|
||||||
|
<>
|
||||||
<Image
|
<Image
|
||||||
style={[a.flex_1, styles.topRadius]}
|
style={[a.flex_1, styles.topRadius]}
|
||||||
source={{uri: link.thumb}}
|
source={{uri: link.thumb}}
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
|
<Fill
|
||||||
|
style={[
|
||||||
|
a.rounded_sm,
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<PlaceholderOverlay
|
<PlaceholderOverlay
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
@ -236,14 +274,13 @@ export function ExternalPlayer({
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
topRadius: {
|
topRadius: {
|
||||||
borderTopLeftRadius: 6,
|
borderTopLeftRadius: 8,
|
||||||
borderTopRightRadius: 6,
|
borderTopRightRadius: 8,
|
||||||
},
|
},
|
||||||
overlayContainer: {
|
overlayContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
||||||
},
|
},
|
||||||
overlayLayer: {
|
overlayLayer: {
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
|
@ -252,6 +289,8 @@ const styles = StyleSheet.create({
|
||||||
zIndex: 3,
|
zIndex: 3,
|
||||||
},
|
},
|
||||||
webview: {
|
webview: {
|
||||||
|
borderTopRightRadius: 8,
|
||||||
|
borderTopLeftRadius: 8,
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
},
|
},
|
||||||
gifContainer: {
|
gifContainer: {
|
||||||
|
|
|
@ -18,7 +18,9 @@ import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
||||||
import {EmbedPlayerParams} from 'lib/strings/embed-player'
|
import {EmbedPlayerParams} from 'lib/strings/embed-player'
|
||||||
import {useAutoplayDisabled} from 'state/preferences'
|
import {useAutoplayDisabled} from 'state/preferences'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {Fill} from '#/components/Fill'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
|
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
|
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
|
||||||
|
@ -56,8 +58,6 @@ function PlaybackControls({
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
backgroundColor: !isLoaded
|
backgroundColor: !isLoaded
|
||||||
? t.atoms.bg_contrast_25.backgroundColor
|
? t.atoms.bg_contrast_25.backgroundColor
|
||||||
: !isPlaying
|
|
||||||
? 'rgba(0, 0, 0, 0.3)'
|
|
||||||
: undefined,
|
: undefined,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
@ -86,6 +86,7 @@ export function GifEmbed({
|
||||||
hideAlt?: boolean
|
hideAlt?: boolean
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const autoplayDisabled = useAutoplayDisabled()
|
const autoplayDisabled = useAutoplayDisabled()
|
||||||
|
|
||||||
|
@ -138,6 +139,17 @@ export function GifEmbed({
|
||||||
accessibilityHint={_(msg`Animated GIF`)}
|
accessibilityHint={_(msg`Animated GIF`)}
|
||||||
accessibilityLabel={parsedAlt.alt}
|
accessibilityLabel={parsedAlt.alt}
|
||||||
/>
|
/>
|
||||||
|
{!playerState.isPlaying && (
|
||||||
|
<Fill
|
||||||
|
style={[
|
||||||
|
t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
|
||||||
|
{
|
||||||
|
opacity: 0.3,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<MediaInsetBorder />
|
||||||
{!hideAlt && parsedAlt.isPreferred && <AltText text={parsedAlt.alt} />}
|
{!hideAlt && parsedAlt.isPreferred && <AltText text={parsedAlt.alt} />}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -33,7 +33,7 @@ import {InfoCircleIcon} from 'lib/icons'
|
||||||
import {makeProfileLink} from 'lib/routes/links'
|
import {makeProfileLink} from 'lib/routes/links'
|
||||||
import {precacheProfile} from 'state/queries/profile'
|
import {precacheProfile} from 'state/queries/profile'
|
||||||
import {ComposerOptsQuote} from 'state/shell/composer'
|
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 {RichText} from '#/components/RichText'
|
||||||
import {ContentHider} from '../../../../components/moderation/ContentHider'
|
import {ContentHider} from '../../../../components/moderation/ContentHider'
|
||||||
import {PostAlerts} from '../../../../components/moderation/PostAlerts'
|
import {PostAlerts} from '../../../../components/moderation/PostAlerts'
|
||||||
|
@ -56,6 +56,7 @@ export function MaybeQuoteEmbed({
|
||||||
allowNestedQuotes?: boolean
|
allowNestedQuotes?: boolean
|
||||||
viewContext?: QuoteEmbedViewContext
|
viewContext?: QuoteEmbedViewContext
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
if (
|
if (
|
||||||
|
@ -75,7 +76,8 @@ export function MaybeQuoteEmbed({
|
||||||
)
|
)
|
||||||
} else if (AppBskyEmbedRecord.isViewBlocked(embed.record)) {
|
} else if (AppBskyEmbedRecord.isViewBlocked(embed.record)) {
|
||||||
return (
|
return (
|
||||||
<View style={[styles.errorContainer, pal.borderDark]}>
|
<View
|
||||||
|
style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}>
|
||||||
<InfoCircleIcon size={18} style={pal.text} />
|
<InfoCircleIcon size={18} style={pal.text} />
|
||||||
<Text type="lg" style={pal.text}>
|
<Text type="lg" style={pal.text}>
|
||||||
<Trans>Blocked</Trans>
|
<Trans>Blocked</Trans>
|
||||||
|
@ -84,7 +86,8 @@ export function MaybeQuoteEmbed({
|
||||||
)
|
)
|
||||||
} else if (AppBskyEmbedRecord.isViewNotFound(embed.record)) {
|
} else if (AppBskyEmbedRecord.isViewNotFound(embed.record)) {
|
||||||
return (
|
return (
|
||||||
<View style={[styles.errorContainer, pal.borderDark]}>
|
<View
|
||||||
|
style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}>
|
||||||
<InfoCircleIcon size={18} style={pal.text} />
|
<InfoCircleIcon size={18} style={pal.text} />
|
||||||
<Text type="lg" style={pal.text}>
|
<Text type="lg" style={pal.text}>
|
||||||
<Trans>Deleted</Trans>
|
<Trans>Deleted</Trans>
|
||||||
|
@ -96,7 +99,8 @@ export function MaybeQuoteEmbed({
|
||||||
? embed.record.uri.includes(currentAccount.did)
|
? embed.record.uri.includes(currentAccount.did)
|
||||||
: false
|
: false
|
||||||
return (
|
return (
|
||||||
<View style={[styles.errorContainer, pal.borderDark]}>
|
<View
|
||||||
|
style={[styles.errorContainer, a.border, t.atoms.border_contrast_low]}>
|
||||||
<InfoCircleIcon size={18} style={pal.text} />
|
<InfoCircleIcon size={18} style={pal.text} />
|
||||||
<Text type="lg" style={pal.text}>
|
<Text type="lg" style={pal.text}>
|
||||||
{isViewerOwner ? (
|
{isViewerOwner ? (
|
||||||
|
@ -169,6 +173,7 @@ export function QuoteEmbed({
|
||||||
allowNestedQuotes?: boolean
|
allowNestedQuotes?: boolean
|
||||||
viewContext?: QuoteEmbedViewContext
|
viewContext?: QuoteEmbedViewContext
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const itemUrip = new AtUri(quote.uri)
|
const itemUrip = new AtUri(quote.uri)
|
||||||
|
@ -214,7 +219,7 @@ export function QuoteEmbed({
|
||||||
return (
|
return (
|
||||||
<ContentHider
|
<ContentHider
|
||||||
modui={moderation?.ui('contentList')}
|
modui={moderation?.ui('contentList')}
|
||||||
style={[styles.container, pal.borderDark, style]}
|
style={[styles.container, a.border, t.atoms.border_contrast_low, style]}
|
||||||
childContainerStyle={[a.pt_sm]}>
|
childContainerStyle={[a.pt_sm]}>
|
||||||
<Link
|
<Link
|
||||||
hoverStyle={{borderColor: pal.colors.borderLinkHover}}
|
hoverStyle={{borderColor: pal.colors.borderLinkHover}}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {useActiveVideoNative} from 'view/com/util/post-embeds/ActiveVideoNativeC
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
|
import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
|
||||||
import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker'
|
import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker'
|
||||||
|
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||||
import {
|
import {
|
||||||
AudioCategory,
|
AudioCategory,
|
||||||
PlatformInfo,
|
PlatformInfo,
|
||||||
|
@ -84,6 +85,7 @@ export function VideoEmbedInnerNative({
|
||||||
isMuted={isMuted}
|
isMuted={isMuted}
|
||||||
timeRemaining={timeRemaining}
|
timeRemaining={timeRemaining}
|
||||||
/>
|
/>
|
||||||
|
<MediaInsetBorder />
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {AppBskyEmbedVideo} from '@atproto/api'
|
||||||
import Hls from 'hls.js'
|
import Hls from 'hls.js'
|
||||||
|
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
|
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||||
import {Controls} from './VideoWebControls'
|
import {Controls} from './VideoWebControls'
|
||||||
|
|
||||||
export function VideoEmbedInnerWeb({
|
export function VideoEmbedInnerWeb({
|
||||||
|
@ -119,6 +120,7 @@ export function VideoEmbedInnerWeb({
|
||||||
fullscreenRef={containerRef}
|
fullscreenRef={containerRef}
|
||||||
hasSubtitleTrack={hasSubtitleTrack}
|
hasSubtitleTrack={hasSubtitleTrack}
|
||||||
/>
|
/>
|
||||||
|
<MediaInsetBorder />
|
||||||
</div>
|
</div>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue