ALF lightbox
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M3 4a1 1 0 0 1 1-1h13a1 1 0 0 1 .707.293l3 3A1 1 0 0 1 21 7v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm6 15h6v-5H9v5Zm8 0v-6a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v6H5V5h2v3a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V5.414l2 2V19h-2ZM15 5H9v2h6V5Z" clip-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 361 B |
+41
-1
@@ -27,6 +27,18 @@ export const atoms = {
|
|||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
},
|
},
|
||||||
|
top_0: {
|
||||||
|
top: 0,
|
||||||
|
},
|
||||||
|
left_0: {
|
||||||
|
left: 0,
|
||||||
|
},
|
||||||
|
right_0: {
|
||||||
|
right: 0,
|
||||||
|
},
|
||||||
|
bottom_0: {
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
z_10: {
|
z_10: {
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
},
|
},
|
||||||
@@ -48,14 +60,20 @@ export const atoms = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Width
|
* Height / Width
|
||||||
*/
|
*/
|
||||||
w_full: {
|
w_full: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
|
max_w_full: {
|
||||||
|
maxWidth: '100%',
|
||||||
|
},
|
||||||
h_full: {
|
h_full: {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
},
|
},
|
||||||
|
max_h_full: {
|
||||||
|
maxHeight: '100%',
|
||||||
|
},
|
||||||
h_full_vh: web({
|
h_full_vh: web({
|
||||||
height: '100vh',
|
height: '100vh',
|
||||||
}),
|
}),
|
||||||
@@ -878,6 +896,9 @@ export const atoms = {
|
|||||||
pointer_events_auto: {
|
pointer_events_auto: {
|
||||||
pointerEvents: 'auto',
|
pointerEvents: 'auto',
|
||||||
},
|
},
|
||||||
|
pointer_events_box_none: {
|
||||||
|
pointerEvents: 'box-none',
|
||||||
|
},
|
||||||
user_select_none: {
|
user_select_none: {
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
},
|
},
|
||||||
@@ -935,4 +956,23 @@ export const atoms = {
|
|||||||
transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)',
|
transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)',
|
||||||
transitionDuration: '100ms',
|
transitionDuration: '100ms',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Opacity
|
||||||
|
*/
|
||||||
|
opacity_0: {
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
opacity_25: {
|
||||||
|
opacity: 0.25,
|
||||||
|
},
|
||||||
|
opacity_50: {
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
opacity_75: {
|
||||||
|
opacity: 0.75,
|
||||||
|
},
|
||||||
|
opacity_100: {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import {createSinglePathSVG} from './TEMPLATE'
|
||||||
|
|
||||||
|
export const FloppyDisk_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M3 4a1 1 0 0 1 1-1h13a1 1 0 0 1 .707.293l3 3A1 1 0 0 1 21 7v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm6 15h6v-5H9v5Zm8 0v-6a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v6H5V5h2v3a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V5.414l2 2V19h-2ZM15 5H9v2h6V5Z',
|
||||||
|
})
|
||||||
@@ -9,13 +9,7 @@
|
|||||||
// https://github.com/jobtoday/react-native-image-viewing
|
// https://github.com/jobtoday/react-native-image-viewing
|
||||||
|
|
||||||
import React, {useCallback, useState} from 'react'
|
import React, {useCallback, useState} from 'react'
|
||||||
import {
|
import {LayoutAnimation, PixelRatio, Platform, View} from 'react-native'
|
||||||
LayoutAnimation,
|
|
||||||
PixelRatio,
|
|
||||||
Platform,
|
|
||||||
StyleSheet,
|
|
||||||
View,
|
|
||||||
} from 'react-native'
|
|
||||||
import {Gesture} from 'react-native-gesture-handler'
|
import {Gesture} from 'react-native-gesture-handler'
|
||||||
import PagerView from 'react-native-pager-view'
|
import PagerView from 'react-native-pager-view'
|
||||||
import Animated, {
|
import Animated, {
|
||||||
@@ -39,16 +33,18 @@ import {
|
|||||||
useSafeAreaFrame,
|
useSafeAreaFrame,
|
||||||
useSafeAreaInsets,
|
useSafeAreaInsets,
|
||||||
} from 'react-native-safe-area-context'
|
} from 'react-native-safe-area-context'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {Trans} from '@lingui/macro'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {Dimensions} from '#/lib/media/types'
|
import {Dimensions} from '#/lib/media/types'
|
||||||
import {colors, s} from '#/lib/styles'
|
|
||||||
import {isIOS} from '#/platform/detection'
|
import {isIOS} from '#/platform/detection'
|
||||||
import {Lightbox} from '#/state/lightbox'
|
import {Lightbox} from '#/state/lightbox'
|
||||||
import {Button} from '#/view/com/util/forms/Button'
|
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
|
||||||
import {ScrollView} from '#/view/com/util/Views'
|
import {ScrollView} from '#/view/com/util/Views'
|
||||||
|
import {atoms as a, ThemeProvider, useTheme} from '#/alf'
|
||||||
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
|
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ShareIcon} from '#/components/icons/ArrowOutOfBox'
|
||||||
|
import {FloppyDisk_Stroke2_Corner0_Rounded as SaveIcon} from '#/components/icons/FloppyDisk'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
import {PlatformInfo} from '../../../../../modules/expo-bluesky-swiss-army'
|
import {PlatformInfo} from '../../../../../modules/expo-bluesky-swiss-army'
|
||||||
import {ImageSource, Transform} from './@types'
|
import {ImageSource, Transform} from './@types'
|
||||||
import ImageDefaultHeader from './components/ImageDefaultHeader'
|
import ImageDefaultHeader from './components/ImageDefaultHeader'
|
||||||
@@ -124,14 +120,19 @@ export default function ImageViewRoot({
|
|||||||
}, [onRequestClose, openProgress])
|
}, [onRequestClose, openProgress])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// Keep it always mounted to avoid flicker on the first frame.
|
<ThemeProvider theme="dark">
|
||||||
|
{/* Keep it always mounted to avoid flicker on the first frame. */}
|
||||||
<SafeAreaView
|
<SafeAreaView
|
||||||
style={[styles.screen, !activeLightbox && styles.screenHidden]}
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
a.inset_0,
|
||||||
|
!activeLightbox && [a.opacity_0, a.pointer_events_none],
|
||||||
|
]}
|
||||||
edges={EDGES}
|
edges={EDGES}
|
||||||
aria-modal
|
aria-modal
|
||||||
accessibilityViewIsModal
|
accessibilityViewIsModal
|
||||||
aria-hidden={!activeLightbox}>
|
aria-hidden={!activeLightbox}>
|
||||||
<Animated.View ref={ref} style={{flex: 1}} collapsable={false}>
|
<Animated.View ref={ref} style={[a.flex_1]} collapsable={false}>
|
||||||
{activeLightbox && (
|
{activeLightbox && (
|
||||||
<ImageView
|
<ImageView
|
||||||
key={activeLightbox.id}
|
key={activeLightbox.id}
|
||||||
@@ -146,6 +147,7 @@ export default function ImageViewRoot({
|
|||||||
)}
|
)}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
</ThemeProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,11 +210,7 @@ function ImageView({
|
|||||||
show && openProgress.value === 1 ? 1 : 0,
|
show && openProgress.value === 1 ? 1 : 0,
|
||||||
FAST_SPRING,
|
FAST_SPRING,
|
||||||
),
|
),
|
||||||
transform: [
|
transform: [{translateY: withClampedSpring(show ? 0 : -30, FAST_SPRING)}],
|
||||||
{
|
|
||||||
translateY: withClampedSpring(show ? 0 : -30, FAST_SPRING),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const animatedFooterStyle = useAnimatedStyle(() => {
|
const animatedFooterStyle = useAnimatedStyle(() => {
|
||||||
@@ -224,11 +222,7 @@ function ImageView({
|
|||||||
show && openProgress.value === 1 ? 1 : 0,
|
show && openProgress.value === 1 ? 1 : 0,
|
||||||
FAST_SPRING,
|
FAST_SPRING,
|
||||||
),
|
),
|
||||||
transform: [
|
transform: [{translateY: withClampedSpring(show ? 0 : 30, FAST_SPRING)}],
|
||||||
{
|
|
||||||
translateY: withClampedSpring(show ? 0 : 30, FAST_SPRING),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -261,9 +255,14 @@ function ImageView({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View style={[styles.container, containerStyle]}>
|
<Animated.View style={[a.flex_1, containerStyle]}>
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[styles.backdrop, backdropStyle]}
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
a.inset_0,
|
||||||
|
{backgroundColor: '#000'},
|
||||||
|
backdropStyle,
|
||||||
|
]}
|
||||||
renderToHardwareTextureAndroid
|
renderToHardwareTextureAndroid
|
||||||
/>
|
/>
|
||||||
<PagerView
|
<PagerView
|
||||||
@@ -277,7 +276,7 @@ function ImageView({
|
|||||||
setIsDragging(e.nativeEvent.pageScrollState !== 'idle')
|
setIsDragging(e.nativeEvent.pageScrollState !== 'idle')
|
||||||
}}
|
}}
|
||||||
overdrag={true}
|
overdrag={true}
|
||||||
style={styles.pager}>
|
style={[a.flex_1]}>
|
||||||
{images.map((imageSrc, i) => (
|
{images.map((imageSrc, i) => (
|
||||||
<View key={imageSrc.uri}>
|
<View key={imageSrc.uri}>
|
||||||
<LightboxImage
|
<LightboxImage
|
||||||
@@ -297,7 +296,14 @@ function ImageView({
|
|||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</PagerView>
|
</PagerView>
|
||||||
<View style={styles.controls}>
|
<View
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
a.inset_0,
|
||||||
|
a.gap_xl,
|
||||||
|
a.z_10,
|
||||||
|
a.pointer_events_box_none,
|
||||||
|
]}>
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={animatedHeaderStyle}
|
style={animatedHeaderStyle}
|
||||||
renderToHardwareTextureAndroid>
|
renderToHardwareTextureAndroid>
|
||||||
@@ -486,11 +492,20 @@ function LightboxFooter({
|
|||||||
onPressSave: (uri: string) => void
|
onPressSave: (uri: string) => void
|
||||||
onPressShare: (uri: string) => void
|
onPressShare: (uri: string) => void
|
||||||
}) {
|
}) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const t = useTheme()
|
||||||
const {alt: altText, uri} = images[index]
|
const {alt: altText, uri} = images[index]
|
||||||
const isMomentumScrolling = React.useRef(false)
|
const isMomentumScrolling = React.useRef(false)
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.footerScrollView}
|
style={[
|
||||||
|
a.flex_1,
|
||||||
|
a.max_h_full,
|
||||||
|
a.w_full,
|
||||||
|
a.absolute,
|
||||||
|
a.bottom_0,
|
||||||
|
{backgroundColor: '#000d'},
|
||||||
|
]}
|
||||||
scrollEnabled={isAltExpanded}
|
scrollEnabled={isAltExpanded}
|
||||||
onMomentumScrollBegin={() => {
|
onMomentumScrollBegin={() => {
|
||||||
isMomentumScrolling.current = true
|
isMomentumScrolling.current = true
|
||||||
@@ -498,15 +513,12 @@ function LightboxFooter({
|
|||||||
onMomentumScrollEnd={() => {
|
onMomentumScrollEnd={() => {
|
||||||
isMomentumScrolling.current = false
|
isMomentumScrolling.current = false
|
||||||
}}
|
}}
|
||||||
contentContainerStyle={{
|
contentContainerStyle={[a.py_md, a.px_2xl]}>
|
||||||
paddingVertical: 12,
|
|
||||||
paddingHorizontal: 24,
|
|
||||||
}}>
|
|
||||||
<SafeAreaView edges={['bottom']}>
|
<SafeAreaView edges={['bottom']}>
|
||||||
{altText ? (
|
{altText ? (
|
||||||
<View accessibilityRole="button" style={styles.footerText}>
|
<View accessibilityRole="button" style={[isIOS ? a.pb_xl : a.pb_lg]}>
|
||||||
<Text
|
<Text
|
||||||
style={[s.gray3]}
|
style={[t.atoms.text_contrast_high]}
|
||||||
numberOfLines={isAltExpanded ? undefined : 3}
|
numberOfLines={isAltExpanded ? undefined : 3}
|
||||||
selectable
|
selectable
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@@ -524,104 +536,37 @@ function LightboxFooter({
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
<View style={styles.footerBtns}>
|
<ThemeProvider theme="light">
|
||||||
|
<View style={[a.w_full, a.flex_row, a.justify_center, a.gap_sm]}>
|
||||||
<Button
|
<Button
|
||||||
type="primary-outline"
|
label={_(msg`Save`)}
|
||||||
style={styles.footerBtn}
|
color="secondary_inverted"
|
||||||
|
variant="solid"
|
||||||
|
size="small"
|
||||||
onPress={() => onPressSave(uri)}>
|
onPress={() => onPressSave(uri)}>
|
||||||
<FontAwesomeIcon icon={['far', 'floppy-disk']} style={s.white} />
|
<ButtonIcon icon={SaveIcon} />
|
||||||
<Text type="xl" style={s.white}>
|
<ButtonText>
|
||||||
<Trans context="action">Save</Trans>
|
<Trans context="action">Save</Trans>
|
||||||
</Text>
|
</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="primary-outline"
|
label={_(msg`Share`)}
|
||||||
style={styles.footerBtn}
|
color="secondary_inverted"
|
||||||
|
variant="solid"
|
||||||
|
size="small"
|
||||||
onPress={() => onPressShare(uri)}>
|
onPress={() => onPressShare(uri)}>
|
||||||
<FontAwesomeIcon icon="arrow-up-from-bracket" style={s.white} />
|
<ButtonIcon icon={ShareIcon} />
|
||||||
<Text type="xl" style={s.white}>
|
<ButtonText>
|
||||||
<Trans context="action">Share</Trans>
|
<Trans context="action">Share</Trans>
|
||||||
</Text>
|
</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
|
</ThemeProvider>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
screen: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
bottom: 0,
|
|
||||||
right: 0,
|
|
||||||
},
|
|
||||||
screenHidden: {
|
|
||||||
opacity: 0,
|
|
||||||
pointerEvents: 'none',
|
|
||||||
},
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
backdrop: {
|
|
||||||
backgroundColor: '#000',
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
},
|
|
||||||
controls: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
gap: 20,
|
|
||||||
zIndex: 1,
|
|
||||||
pointerEvents: 'box-none',
|
|
||||||
},
|
|
||||||
pager: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
position: 'absolute',
|
|
||||||
width: '100%',
|
|
||||||
top: 0,
|
|
||||||
pointerEvents: 'box-none',
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
position: 'absolute',
|
|
||||||
width: '100%',
|
|
||||||
maxHeight: '100%',
|
|
||||||
bottom: 0,
|
|
||||||
},
|
|
||||||
footerScrollView: {
|
|
||||||
backgroundColor: '#000d',
|
|
||||||
flex: 1,
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: 0,
|
|
||||||
width: '100%',
|
|
||||||
maxHeight: '100%',
|
|
||||||
},
|
|
||||||
footerText: {
|
|
||||||
paddingBottom: isIOS ? 20 : 16,
|
|
||||||
},
|
|
||||||
footerBtns: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'center',
|
|
||||||
gap: 8,
|
|
||||||
},
|
|
||||||
footerBtn: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 8,
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
borderColor: colors.white,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
function interpolatePx(
|
function interpolatePx(
|
||||||
px: number,
|
px: number,
|
||||||
inputRange: readonly number[],
|
inputRange: readonly number[],
|
||||||
|
|||||||
Reference in New Issue
Block a user