create canvas
This commit is contained in:
@@ -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,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
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 {Button, ButtonText, ButtonIcon} from '#/components/Button'
|
||||||
import {StreamingLive_Stroke2_Corner0_Rounded as StreamingLive} from '#/components/icons/StreamingLive'
|
import {StreamingLive_Stroke2_Corner0_Rounded as StreamingLive} from '#/components/icons/StreamingLive'
|
||||||
import {Camera_Stroke2_Corner0_Rounded as Camera} from '#/components/icons/Camera'
|
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 {openCropper, openPicker} from 'lib/media/picker'
|
||||||
import {Emoji, EmojiName, emojiItems, emojiNames} from './types'
|
import {Emoji, EmojiName, emojiItems, emojiNames} from './types'
|
||||||
import {SelectImageButton} from '#/screens/Onboarding/StepProfile/SelectImageButton'
|
import {SelectImageButton} from '#/screens/Onboarding/StepProfile/SelectImageButton'
|
||||||
|
import ViewShot from 'react-native-view-shot'
|
||||||
|
import {PlaceholderCanvas} from '#/screens/Onboarding/StepProfile/PlaceholderCanvas'
|
||||||
|
|
||||||
interface Avatar {
|
interface Avatar {
|
||||||
image?: {
|
image?: {
|
||||||
@@ -53,6 +55,8 @@ export const useSetAvatar = () => React.useContext(SetAvatarContext)
|
|||||||
|
|
||||||
export function StepProfile() {
|
export function StepProfile() {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
const t = useTheme()
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
const {state, dispatch} = React.useContext(Context)
|
const {state, dispatch} = React.useContext(Context)
|
||||||
const [avatar, setAvatar] = React.useState<Avatar>({
|
const [avatar, setAvatar] = React.useState<Avatar>({
|
||||||
@@ -72,42 +76,46 @@ export function StepProfile() {
|
|||||||
return (
|
return (
|
||||||
<SetAvatarContext.Provider value={setAvatar}>
|
<SetAvatarContext.Provider value={setAvatar}>
|
||||||
<AvatarContext.Provider value={avatar}>
|
<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>
|
<Title>
|
||||||
<Trans>Set your profile picture</Trans>
|
<Trans>Set your profile picture</Trans>
|
||||||
</Title>
|
</Title>
|
||||||
<Description>
|
<Description>
|
||||||
<Trans>
|
<Trans>
|
||||||
Help people know you're not a bot by uploading a picture or
|
Help people know you're not a bot by uploading a picture or
|
||||||
creating an avatar!
|
creating an avatar!
|
||||||
</Trans>
|
</Trans>
|
||||||
</Description>
|
</Description>
|
||||||
|
</View>
|
||||||
<View style={[a.w_full, a.pt_5xl]}>
|
<View style={[a.w_full, a.pt_5xl]}>
|
||||||
<View style={[a.align_center, a.pb_5xl]}>
|
<View style={[a.align_center, a.pb_5xl]}>
|
||||||
<AvatarCircle />
|
<AvatarCircle />
|
||||||
|
</View>
|
||||||
|
<Items type="emojis" />
|
||||||
|
<Items type="colors" />
|
||||||
</View>
|
</View>
|
||||||
<Items type="emojis" />
|
|
||||||
<Items type="colors" />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<OnboardingControls.Portal>
|
<OnboardingControls.Portal>
|
||||||
<Button
|
<Button
|
||||||
key={state.activeStep} // remove focus state on nav
|
key={state.activeStep} // remove focus state on nav
|
||||||
variant="gradient"
|
variant="gradient"
|
||||||
color="gradient_sky"
|
color="gradient_sky"
|
||||||
size="large"
|
size="large"
|
||||||
label={_(msg`Continue to next step`)}
|
label={_(msg`Continue to next step`)}
|
||||||
onPress={onContinue}>
|
onPress={onContinue}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Continue</Trans>
|
<Trans>Continue</Trans>
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
<ButtonIcon icon={ChevronRight} position="right" />
|
<ButtonIcon icon={ChevronRight} position="right" />
|
||||||
</Button>
|
</Button>
|
||||||
</OnboardingControls.Portal>
|
</OnboardingControls.Portal>
|
||||||
</View>
|
</View>
|
||||||
|
<PlaceholderCanvas />
|
||||||
|
</>
|
||||||
</AvatarContext.Provider>
|
</AvatarContext.Provider>
|
||||||
</SetAvatarContext.Provider>
|
</SetAvatarContext.Provider>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user