create canvas

This commit is contained in:
Hailey
2024-02-13 19:14:52 -08:00
parent 70bb2038e8
commit 2291ddfea7
4 changed files with 129 additions and 136 deletions
@@ -0,0 +1,44 @@
import React from 'react'
import {useAvatar} from '#/screens/Onboarding/StepProfile/index'
import ViewShot from 'react-native-view-shot'
import {StyleSheet, View} from 'react-native'
// This component is supposed to be invisible to the user. We only need this for ViewShot to have something to
// "screenshot"
export const PlaceholderCanvas = React.forwardRef(function PlaceholderCanvas(
{},
ref,
) {
const avatar = useAvatar()
const viewshotRef = React.useRef()
const Icon = avatar.placeholder.component
// React.useEffect(() => {
// // @ts-ignore this library doesn't have types
// viewshotRef.current.capture().then(uri => {
// return uri
// })
// }, [avatar, ref])
return (
<View style={styles.imageContainer}>
<ViewShot
ref={viewshotRef}
options={{fileName: 'placeholderAvatar', format: 'jpg', quality: 1.0}}>
<View
style={[{backgroundColor: avatar.backgroundColor}]}
collapsable={false}>
<Icon height={500} width={500} style={{color: 'white'}} />
</View>
</ViewShot>
</View>
)
})
const styles = StyleSheet.create({
imageContainer: {
top: 100,
position: 'absolute',
zIndex: -5,
},
})
@@ -0,0 +1,43 @@
import React from 'react'
import {useAvatar} from '#/screens/Onboarding/StepProfile/index'
const CANVAS_HEIGHT = 24 * 10
const CANVAS_OFFSET = 24 * 1.5
interface PlaceholderCanvasRef {
capture: () => string
}
export const PlaceholderCanvas = React.forwardRef(function PlaceholderCanvas(
{},
ref,
) {
const avatar = useAvatar()
const canvasRef = React.useRef<HTMLCanvasElement>(null)
const capture = React.useCallback(() => {
return ''
}, [])
React.useImperativeHandle(ref, () => ({
capture,
}))
React.useEffect(() => {
// @ts-ignore web only
const path = new Path2D(avatar.placeholder.path)
// @ts-ignore web only
const ctx = canvasRef.current?.getContext('2d')
ctx?.reset()
ctx.fillStyle = avatar.backgroundColor
ctx?.moveTo(0, 0)
ctx?.fillRect(0, 0, CANVAS_HEIGHT, CANVAS_HEIGHT)
ctx.fillStyle = '#fff'
ctx.lineWidth = 0.1
ctx?.translate(CANVAS_OFFSET, CANVAS_OFFSET)
ctx?.scale(7, 7)
ctx?.fill(path)
}, [avatar.backgroundColor, avatar.placeholder.path])
return <canvas ref={canvasRef} height={CANVAS_HEIGHT} width={CANVAS_HEIGHT} />
})
@@ -1,102 +0,0 @@
import React from 'react'
import {Pressable, StyleSheet} from 'react-native'
import {Camera_Stroke2_Corner0_Rounded as Camera} from '#/components/icons/Camera'
import {useAvatar, useSetAvatar} from '#/screens/Onboarding/StepProfile/index'
import {useTheme} from '#/alf'
import {usePhotoLibraryPermission} from 'lib/hooks/usePermissions'
import {openPicker} from 'lib/media/picker.shared'
import {openCropper} from 'lib/media/picker'
export function SelectImageButton() {
const t = useTheme()
const setAvatar = useSetAvatar()
const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
const onCameraPress = React.useCallback(async () => {
if (!(await requestPhotoAccessIfNeeded())) {
return
}
const items = await openPicker({
aspect: [1, 1],
})
const item = items[0]
if (!item) return
const croppedImage = await openCropper({
mediaType: 'photo',
cropperCircleOverlay: true,
height: item.height,
width: item.width,
path: item.path,
})
setAvatar(prev => ({
...prev,
image: croppedImage,
}))
}, [requestPhotoAccessIfNeeded, setAvatar])
return (
<>
<Pressable
accessibilityRole="button"
style={[
styles.imageContainer,
styles.paletteContainer,
t.atoms.border_contrast_high,
]}
onPress={onCameraPress}>
<Camera size="xl" style={[t.atoms.text_contrast_medium]} />
</Pressable>
<Canvas />
</>
)
}
function Canvas() {
const avatar = useAvatar()
const canvasRef = React.useRef<HTMLCanvasElement>(null)
React.useEffect(() => {
// @ts-ignore web only
const path = new Path2D(avatar.placeholder.path)
// @ts-ignore web only
const ctx = canvasRef.current?.getContext('2d')
ctx?.reset()
ctx.fillStyle = avatar.backgroundColor
ctx?.moveTo(0, 0)
ctx?.fillRect(0, 0, 24 * 10, 24 * 10)
ctx.fillStyle = '#fff'
ctx.lineWidth = 0.1
ctx?.translate(24 * 1.5, 24 * 1.5)
ctx?.scale(7, 7)
ctx?.fill(path)
}, [avatar.backgroundColor, avatar.placeholder.path])
return <canvas ref={canvasRef} height={24 * 10} width={24 * 10} />
}
const styles = StyleSheet.create({
imageContainer: {
borderRadius: 100,
borderWidth: 2,
height: 150,
width: 150,
overflow: 'hidden',
alignItems: 'center',
justifyContent: 'center',
},
paletteContainer: {
height: 80,
width: 80,
marginHorizontal: 5,
},
flatListOuter: {
height: 100,
},
flatListContainer: {
paddingLeft: 40,
paddingRight: 40,
},
})
+42 -34
View File
@@ -10,7 +10,7 @@ import {
import {useLingui} from '@lingui/react'
import {msg, Trans} from '@lingui/macro'
import {atoms as a, useTheme} from '#/alf'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonText, ButtonIcon} from '#/components/Button'
import {StreamingLive_Stroke2_Corner0_Rounded as StreamingLive} from '#/components/icons/StreamingLive'
import {Camera_Stroke2_Corner0_Rounded as Camera} from '#/components/icons/Camera'
@@ -31,6 +31,8 @@ import {usePhotoLibraryPermission} from 'lib/hooks/usePermissions'
import {openCropper, openPicker} from 'lib/media/picker'
import {Emoji, EmojiName, emojiItems, emojiNames} from './types'
import {SelectImageButton} from '#/screens/Onboarding/StepProfile/SelectImageButton'
import ViewShot from 'react-native-view-shot'
import {PlaceholderCanvas} from '#/screens/Onboarding/StepProfile/PlaceholderCanvas'
interface Avatar {
image?: {
@@ -53,6 +55,8 @@ export const useSetAvatar = () => React.useContext(SetAvatarContext)
export function StepProfile() {
const {_} = useLingui()
const t = useTheme()
const {gtMobile} = useBreakpoints()
const {track} = useAnalytics()
const {state, dispatch} = React.useContext(Context)
const [avatar, setAvatar] = React.useState<Avatar>({
@@ -72,42 +76,46 @@ export function StepProfile() {
return (
<SetAvatarContext.Provider value={setAvatar}>
<AvatarContext.Provider value={avatar}>
<View style={[a.align_start]}>
<IconCircle icon={StreamingLive} style={[a.mb_2xl]} />
<>
<View style={[a.align_start, t.atoms.bg]}>
<View style={[gtMobile ? a.px_5xl : a.px_xl]}>
<IconCircle icon={StreamingLive} style={[a.mb_2xl]} />
<Title>
<Trans>Set your profile picture</Trans>
</Title>
<Description>
<Trans>
Help people know you're not a bot by uploading a picture or
creating an avatar!
</Trans>
</Description>
<View style={[a.w_full, a.pt_5xl]}>
<View style={[a.align_center, a.pb_5xl]}>
<AvatarCircle />
<Title>
<Trans>Set your profile picture</Trans>
</Title>
<Description>
<Trans>
Help people know you're not a bot by uploading a picture or
creating an avatar!
</Trans>
</Description>
</View>
<View style={[a.w_full, a.pt_5xl]}>
<View style={[a.align_center, a.pb_5xl]}>
<AvatarCircle />
</View>
<Items type="emojis" />
<Items type="colors" />
</View>
<Items type="emojis" />
<Items type="colors" />
</View>
<OnboardingControls.Portal>
<Button
key={state.activeStep} // remove focus state on nav
variant="gradient"
color="gradient_sky"
size="large"
label={_(msg`Continue to next step`)}
onPress={onContinue}>
<ButtonText>
<Trans>Continue</Trans>
</ButtonText>
<ButtonIcon icon={ChevronRight} position="right" />
</Button>
</OnboardingControls.Portal>
</View>
<OnboardingControls.Portal>
<Button
key={state.activeStep} // remove focus state on nav
variant="gradient"
color="gradient_sky"
size="large"
label={_(msg`Continue to next step`)}
onPress={onContinue}>
<ButtonText>
<Trans>Continue</Trans>
</ButtonText>
<ButtonIcon icon={ChevronRight} position="right" />
</Button>
</OnboardingControls.Portal>
</View>
<PlaceholderCanvas />
</>
</AvatarContext.Provider>
</SetAvatarContext.Provider>
)