247 lines
6.8 KiB
TypeScript
247 lines
6.8 KiB
TypeScript
import {useCallback, useState} from 'react'
|
|
import {Keyboard, Pressable, View} from 'react-native'
|
|
import {msg} from '@lingui/core/macro'
|
|
import {useLingui} from '@lingui/react'
|
|
import {Trans} from '@lingui/react/macro'
|
|
|
|
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
|
import {
|
|
useCameraPermission,
|
|
usePhotoLibraryPermission,
|
|
useVideoLibraryPermission,
|
|
} from '#/lib/hooks/usePermissions'
|
|
import {openCamera, openUnifiedPicker} from '#/lib/media/picker'
|
|
import {useCurrentAccountProfile} from '#/state/queries/useCurrentAccountProfile'
|
|
import {MAX_GALLERY_IMAGES} from '#/view/com/composer/state/composer'
|
|
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
|
import {atoms as a, native, useTheme, web} from '#/alf'
|
|
import {Button} from '#/components/Button'
|
|
import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
|
|
import {Camera_Stroke2_Corner0_Rounded as CameraIcon} from '#/components/icons/Camera'
|
|
import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image'
|
|
import {SubtleHover} from '#/components/SubtleHover'
|
|
import {Text} from '#/components/Typography'
|
|
import {useAnalytics} from '#/analytics'
|
|
import {IS_NATIVE} from '#/env'
|
|
|
|
export function ComposerPrompt() {
|
|
const t = useTheme()
|
|
const ax = useAnalytics()
|
|
const {_} = useLingui()
|
|
const {openComposer} = useOpenComposer()
|
|
const profile = useCurrentAccountProfile()
|
|
const [hover, setHover] = useState(false)
|
|
const {requestCameraAccessIfNeeded} = useCameraPermission()
|
|
const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
|
|
const {requestVideoAccessIfNeeded} = useVideoLibraryPermission()
|
|
const sheetWrapper = useSheetWrapper()
|
|
|
|
const onPress = useCallback(() => {
|
|
ax.metric('composerPrompt:press', {})
|
|
openComposer({logContext: 'Fab'})
|
|
}, [ax, openComposer])
|
|
|
|
const onPressImage = useCallback(async () => {
|
|
ax.metric('composerPrompt:gallery:press', {})
|
|
|
|
// On web, open the composer with the gallery picker auto-opening
|
|
if (!IS_NATIVE) {
|
|
openComposer({openGallery: true, logContext: 'Fab'})
|
|
return
|
|
}
|
|
|
|
try {
|
|
const [photoAccess, videoAccess] = await Promise.all([
|
|
requestPhotoAccessIfNeeded(),
|
|
requestVideoAccessIfNeeded(),
|
|
])
|
|
|
|
if (!photoAccess && !videoAccess) {
|
|
return
|
|
}
|
|
|
|
if (Keyboard.isVisible()) {
|
|
Keyboard.dismiss()
|
|
}
|
|
|
|
const selectionCountRemaining = MAX_GALLERY_IMAGES
|
|
const {assets, canceled} = await sheetWrapper(
|
|
openUnifiedPicker({selectionCountRemaining}),
|
|
)
|
|
|
|
if (canceled) {
|
|
return
|
|
}
|
|
|
|
if (assets.length > 0) {
|
|
const imageUris = assets
|
|
.filter(asset => asset.mimeType?.startsWith('image/'))
|
|
.slice(0, MAX_GALLERY_IMAGES)
|
|
.map(asset => ({
|
|
uri: asset.uri,
|
|
width: asset.width,
|
|
height: asset.height,
|
|
}))
|
|
|
|
if (imageUris.length > 0) {
|
|
openComposer({imageUris, logContext: 'Fab'})
|
|
}
|
|
}
|
|
} catch (err: any) {
|
|
if (!String(err).toLowerCase().includes('cancel')) {
|
|
ax.logger.error('Error opening image picker', {error: err})
|
|
}
|
|
}
|
|
}, [
|
|
ax,
|
|
openComposer,
|
|
requestPhotoAccessIfNeeded,
|
|
requestVideoAccessIfNeeded,
|
|
sheetWrapper,
|
|
])
|
|
|
|
const onPressCamera = useCallback(async () => {
|
|
ax.metric('composerPrompt:camera:press', {})
|
|
|
|
try {
|
|
if (!(await requestCameraAccessIfNeeded())) {
|
|
return
|
|
}
|
|
|
|
if (IS_NATIVE && Keyboard.isVisible()) {
|
|
Keyboard.dismiss()
|
|
}
|
|
|
|
const image = await openCamera({
|
|
mediaTypes: 'images',
|
|
})
|
|
if (!image) {
|
|
return
|
|
}
|
|
|
|
const imageUris = [
|
|
{
|
|
uri: image.path,
|
|
width: image.width,
|
|
height: image.height,
|
|
},
|
|
]
|
|
|
|
openComposer({
|
|
imageUris: IS_NATIVE ? imageUris : undefined,
|
|
logContext: 'Fab',
|
|
})
|
|
} catch (err: any) {
|
|
if (!String(err).toLowerCase().includes('cancel')) {
|
|
ax.logger.error('Error opening camera', {error: err})
|
|
}
|
|
}
|
|
}, [ax, openComposer, requestCameraAccessIfNeeded])
|
|
|
|
if (!profile) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<Pressable
|
|
onPress={onPress}
|
|
android_ripple={null}
|
|
accessibilityRole="button"
|
|
accessibilityLabel={_(msg`Compose new post`)}
|
|
accessibilityHint={_(msg`Opens the post composer`)}
|
|
onPointerEnter={() => setHover(true)}
|
|
onPointerLeave={() => setHover(false)}
|
|
style={({pressed}) => [
|
|
a.relative,
|
|
a.flex_row,
|
|
a.align_start,
|
|
{
|
|
paddingLeft: 18,
|
|
paddingRight: 15,
|
|
},
|
|
a.py_md,
|
|
native({
|
|
paddingTop: 10,
|
|
paddingBottom: 10,
|
|
}),
|
|
web({
|
|
cursor: 'pointer',
|
|
}),
|
|
pressed && web({outline: 'none'}),
|
|
]}>
|
|
<SubtleHover hover={hover} />
|
|
<UserAvatar
|
|
avatar={profile.avatar}
|
|
size={42}
|
|
type={profile.associated?.labeler ? 'labeler' : 'user'}
|
|
/>
|
|
<View
|
|
style={[
|
|
a.flex_1,
|
|
a.ml_md,
|
|
a.flex_row,
|
|
a.align_center,
|
|
a.justify_between,
|
|
{
|
|
height: 40,
|
|
},
|
|
]}>
|
|
<Text
|
|
style={[
|
|
t.atoms.text_contrast_medium,
|
|
a.text_md,
|
|
{includeFontPadding: false},
|
|
]}>
|
|
<Trans>What's up?</Trans>
|
|
</Text>
|
|
<View style={[a.flex_row, a.gap_md]}>
|
|
{IS_NATIVE && (
|
|
<Button
|
|
onPress={e => {
|
|
e.stopPropagation()
|
|
onPressCamera()
|
|
}}
|
|
label={_(msg`Open camera`)}
|
|
accessibilityHint={_(msg`Opens device camera`)}
|
|
variant="ghost"
|
|
shape="round">
|
|
{({hovered, pressed, focused}) => (
|
|
<CameraIcon
|
|
size="lg"
|
|
style={{
|
|
color:
|
|
hovered || pressed || focused
|
|
? t.palette.primary_500
|
|
: t.palette.contrast_300,
|
|
}}
|
|
/>
|
|
)}
|
|
</Button>
|
|
)}
|
|
<Button
|
|
onPress={e => {
|
|
e.stopPropagation()
|
|
onPressImage()
|
|
}}
|
|
label={_(msg`Add image`)}
|
|
accessibilityHint={_(msg`Opens image picker`)}
|
|
variant="ghost"
|
|
shape="round">
|
|
{({hovered, pressed, focused}) => (
|
|
<ImageIcon
|
|
size="lg"
|
|
style={{
|
|
color:
|
|
hovered || pressed || focused
|
|
? t.palette.primary_500
|
|
: t.palette.contrast_300,
|
|
}}
|
|
/>
|
|
)}
|
|
</Button>
|
|
</View>
|
|
</View>
|
|
</Pressable>
|
|
)
|
|
}
|