import React from 'react' import {View} from 'react-native' import ViewShot from 'react-native-view-shot' import {Image} from 'expo-image' import {moderateProfile} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {networkRetry} from '#/lib/async/retry' import {getCanvas} from '#/lib/canvas' import {shareUrl} from '#/lib/sharing' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {isNative} from '#/platform/detection' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useProfileQuery} from '#/state/queries/profile' import {useAgent, useSession} from '#/state/session' import {useComposerControls} from 'state/shell' import {formatCount} from '#/view/com/util/numeric/format' // import {UserAvatar} from '#/view/com/util/UserAvatar' import {Logomark} from '#/view/icons/Logomark' import { atoms as a, ThemeProvider, tokens, useBreakpoints, useTheme, } from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {useContext} from '#/components/dialogs/nudges' import {OnePercent} from '#/components/dialogs/nudges/TenMillion/icons/OnePercent' import {PointOnePercent} from '#/components/dialogs/nudges/TenMillion/icons/PointOnePercent' import {TenPercent} from '#/components/dialogs/nudges/TenMillion/icons/TenPercent' import {Divider} from '#/components/Divider' import {GradientFill} from '#/components/GradientFill' import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' import {Download_Stroke2_Corner0_Rounded as Download} from '#/components/icons/Download' import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' // import {TwentyFivePercent} from '#/components/dialogs/nudges/TenMillion/icons/TwentyFivePercent' const DEBUG = false const RATIO = 8 / 10 const WIDTH = 2000 const HEIGHT = WIDTH * RATIO function getFontSize(count: number) { const length = count.toString().length if (length < 7) { return 80 } else if (length < 5) { return 100 } else { return 70 } } function getPercentBadge(percent: number) { if (percent <= 0.001) { return PointOnePercent } else if (percent <= 0.01) { return OnePercent } else if (percent <= 0.1) { return TenPercent } // else if (percent <= 0.25) { // return TwentyFivePercent // } return null } function Frame({children}: {children: React.ReactNode}) { return ( {children} ) } export function TenMillion() { const {hasSession} = useSession() return hasSession ? : null } export function TenMillionInner() { const t = useTheme() const lightTheme = useTheme('light') const {_, i18n} = useLingui() const {controls} = useContext() const {gtMobile} = useBreakpoints() const {openComposer} = useComposerControls() const {currentAccount} = useSession() const {isLoading: isProfileLoading, data: profile} = useProfileQuery({ did: currentAccount!.did, }) const moderationOpts = useModerationOpts() const moderation = React.useMemo(() => { return profile && moderationOpts ? moderateProfile(profile, moderationOpts) : undefined }, [profile, moderationOpts]) const [uri, setUri] = React.useState(null) const [userNumber, setUserNumber] = React.useState(0) const [error, setError] = React.useState('') const isLoadingData = isProfileLoading || !moderation || !profile || !userNumber const isLoadingImage = !uri const percent = userNumber / 10_000_000 const Badge = getPercentBadge(percent) const agent = useAgent() React.useEffect(() => { async function fetchUserNumber() { if (agent.session?.accessJwt) { const res = await fetch( `https://bsky.social/xrpc/com.atproto.temp.getSignupNumber`, { headers: { Authorization: `Bearer ${agent.session.accessJwt}`, }, }, ) if (!res.ok) { throw new Error('Network request failed') } const data = await res.json() if (data.number) { setUserNumber(data.number) } } } networkRetry(3, fetchUserNumber).catch(() => { setError( _( msg`Oh no! We couldn't fetch your user number. Rest assured, we're glad you're here ❤️`, ), ) }) }, [ _, agent.session?.accessJwt, setUserNumber, controls.tenMillion, setError, ]) const sharePost = () => { if (uri) { controls.tenMillion.close(() => { setTimeout(() => { openComposer({ text: '10 milly, babyyy', imageUris: [ { uri, width: WIDTH, height: HEIGHT, }, ], }) }, 1e3) }) } } const onNativeShare = () => { if (uri) { controls.tenMillion.close(() => { shareUrl(uri) }) } } const download = async () => { if (uri) { const canvas = await getCanvas(uri) const imgHref = canvas .toDataURL('image/png') .replace('image/png', 'image/octet-stream') const link = document.createElement('a') link.setAttribute('download', `Bluesky 10M Users.png`) link.setAttribute('href', imgHref) link.click() } } const imageRef = React.useRef(null) // const captureInProgress = React.useRef(false) // const [cavasRelayout, setCanvasRelayout] = React.useState('key') // const onCanvasReady = async () => { // if ( // imageRef.current && // imageRef.current.capture && // !captureInProgress.current // ) { // captureInProgress.current = true // setCanvasRelayout('updated') // } // } const onCanvasLayout = async () => { if ( imageRef.current && imageRef.current.capture // && // cavasRelayout === 'updated' ) { const uri = await imageRef.current.capture() setUri(uri) } } const canvas = isLoadingData ? null : ( {/* Centered content */} Celebrating {formatCount(i18n, 10000000)} users {' '} 🎉 # {i18n.number(userNumber)} {Badge && ( )} {/* End centered content */} {/* */} {sanitizeDisplayName( profile.displayName || sanitizeHandle(profile.handle), moderation.ui('displayName'), )} {sanitizeHandle(profile.handle, '@')} {profile.createdAt && ( Joined{' '} {i18n.date(profile.createdAt, { dateStyle: 'long', })} )} ) return ( {error ? ( (╯°□°)╯︵ ┻━┻ {error} ) : isLoadingData || isLoadingImage ? ( ) : ( )} {canvas} Thanks for being an early part of Bluesky. We're rebuilding the social internet together. Congratulations, we're glad you're here. {' '} Brag a little! ) }