An ATproto social media client -- with an independent Appview.

Disable avi

+91 -44
+1
assets/icons/download_stroke2_corner0_rounded.svg
···
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12 3a1 1 0 0 1 1 1v8.086l1.793-1.793a1 1 0 1 1 1.414 1.414l-3.5 3.5a1 1 0 0 1-1.414 0l-3.5-3.5a1 1 0 1 1 1.414-1.414L11 12.086V4a1 1 0 0 1 1-1ZM4 14a1 1 0 0 1 1 1v4h14v-4a1 1 0 1 1 2 0v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1Z" clip-rule="evenodd"/></svg>
+1 -1
src/components/Dialog/index.tsx
··· 256 borderTopLeftRadius: 40, 257 borderTopRightRadius: 40, 258 }, 259 - flatten(style), 260 ]} 261 contentContainerStyle={a.pb_4xl} 262 ref={ref}>
··· 256 borderTopLeftRadius: 40, 257 borderTopRightRadius: 40, 258 }, 259 + style, 260 ]} 261 contentContainerStyle={a.pb_4xl} 262 ref={ref}>
+84 -43
src/components/dialogs/nudges/TenMillion.tsx
··· 7 import {useLingui} from '@lingui/react' 8 9 import {getCanvas} from '#/lib/canvas' 10 import {sanitizeDisplayName} from '#/lib/strings/display-names' 11 import {sanitizeHandle} from '#/lib/strings/handles' 12 - import {isNative} from '#/platform/detection' 13 import {useModerationOpts} from '#/state/preferences/moderation-opts' 14 import {useProfileQuery} from '#/state/queries/profile' 15 import {useSession} from '#/state/session' 16 import {useComposerControls} from 'state/shell' 17 import {formatCount} from '#/view/com/util/numeric/format' 18 - import {UserAvatar} from '#/view/com/util/UserAvatar' 19 import {Logomark} from '#/view/icons/Logomark' 20 import { 21 atoms as a, ··· 30 import {Divider} from '#/components/Divider' 31 import {GradientFill} from '#/components/GradientFill' 32 import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' 33 import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image' 34 import {Loader} from '#/components/Loader' 35 import {Text} from '#/components/Typography' ··· 88 const isLoadingData = isProfileLoading || !moderation || !profile 89 const isLoadingImage = !uri 90 91 - const userNumber = 56738 // TODO 92 93 - const captureInProgress = React.useRef(false) 94 - const imageRef = React.useRef<ViewShot>(null) 95 - 96 - const share = () => { 97 if (uri) { 98 controls.tenMillion.close(() => { 99 setTimeout(() => { ··· 112 } 113 } 114 115 - const onCanvasReady = async () => { 116 - if ( 117 - imageRef.current && 118 - imageRef.current.capture && 119 - !captureInProgress.current 120 - ) { 121 - captureInProgress.current = true 122 - const uri = await imageRef.current.capture() 123 - setUri(uri) 124 } 125 } 126 ··· 137 } 138 } 139 140 const canvas = isLoadingData ? null : ( 141 <View 142 style={[ ··· 160 options={{width: WIDTH, height: HEIGHT}} 161 style={[a.absolute, a.inset_0]}> 162 <View 163 style={[ 164 a.absolute, 165 a.inset_0, ··· 170 bottom: -1, 171 left: -1, 172 right: -1, 173 - paddingVertical: 32, 174 paddingHorizontal: 48, 175 }, 176 ]}> ··· 208 <View 209 style={[ 210 { 211 - paddingBottom: 48, 212 }, 213 ]}> 214 <Text ··· 216 a.text_md, 217 a.font_bold, 218 a.text_center, 219 - a.pb_xs, 220 lightTheme.atoms.text_contrast_medium, 221 ]}> 222 <Trans> ··· 224 </Trans>{' '} 225 🎉 226 </Text> 227 - <Text 228 - style={[ 229 - a.relative, 230 - a.text_center, 231 - { 232 - fontStyle: 'italic', 233 - fontSize: getFontSize(userNumber), 234 - fontWeight: '900', 235 - letterSpacing: -2, 236 - }, 237 - ]}> 238 <Text 239 style={[ 240 a.absolute, ··· 242 color: t.palette.primary_500, 243 fontSize: 32, 244 left: -18, 245 - top: 8, 246 }, 247 ]}> 248 # 249 </Text> 250 - {i18n.number(userNumber)} 251 - </Text> 252 </View> 253 {/* End centered content */} 254 ··· 264 }, 265 ]}> 266 <View style={[a.flex_row, a.align_center, a.gap_sm]}> 267 <UserAvatar 268 size={36} 269 avatar={profile.avatar} 270 moderation={moderation.ui('avatar')} 271 onLoad={onCanvasReady} 272 /> 273 <View style={[a.gap_2xs, a.flex_1]}> 274 - <Text style={[a.text_sm, a.font_bold]}> 275 {sanitizeDisplayName( 276 profile.displayName || 277 sanitizeHandle(profile.handle), ··· 283 style={[ 284 a.text_sm, 285 a.font_semibold, 286 lightTheme.atoms.text_contrast_medium, 287 ]}> 288 {sanitizeHandle(profile.handle, '@')} ··· 293 style={[ 294 a.text_sm, 295 a.font_semibold, 296 lightTheme.atoms.text_contrast_low, 297 ]}> 298 - {i18n.date(profile.createdAt, { 299 - dateStyle: 'long', 300 - })} 301 </Text> 302 )} 303 </View> ··· 315 316 return ( 317 <Dialog.Outer control={controls.tenMillion}> 318 - <Dialog.Handle /> 319 - 320 <Dialog.ScrollableInner 321 label={_(msg`Ten Million`)} 322 style={[ 323 { 324 padding: 0, 325 }, 326 ]}> 327 <View ··· 355 <Text 356 style={[ 357 a.text_5xl, 358 a.pb_lg, 359 { 360 fontWeight: '900', ··· 387 </Text> 388 389 <Button 390 - label={_(msg`Share image externally`)} 391 size="large" 392 variant="solid" 393 color="secondary" 394 shape="square" 395 - onPress={download}> 396 - <ButtonIcon icon={Share} /> 397 </Button> 398 <Button 399 label={_(msg`Share image in post`)} 400 size="large" 401 variant="solid" 402 color="primary" 403 - onPress={share}> 404 <ButtonText>{_(msg`Share post`)}</ButtonText> 405 <ButtonIcon position="right" icon={ImageIcon} /> 406 </Button>
··· 7 import {useLingui} from '@lingui/react' 8 9 import {getCanvas} from '#/lib/canvas' 10 + import {shareUrl} from '#/lib/sharing' 11 import {sanitizeDisplayName} from '#/lib/strings/display-names' 12 import {sanitizeHandle} from '#/lib/strings/handles' 13 + import {isAndroid, isNative, isWeb} from '#/platform/detection' 14 import {useModerationOpts} from '#/state/preferences/moderation-opts' 15 import {useProfileQuery} from '#/state/queries/profile' 16 import {useSession} from '#/state/session' 17 import {useComposerControls} from 'state/shell' 18 import {formatCount} from '#/view/com/util/numeric/format' 19 + // import {UserAvatar} from '#/view/com/util/UserAvatar' 20 import {Logomark} from '#/view/icons/Logomark' 21 import { 22 atoms as a, ··· 31 import {Divider} from '#/components/Divider' 32 import {GradientFill} from '#/components/GradientFill' 33 import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' 34 + import {Download_Stroke2_Corner0_Rounded as Download} from '#/components/icons/Download' 35 import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image' 36 import {Loader} from '#/components/Loader' 37 import {Text} from '#/components/Typography' ··· 90 const isLoadingData = isProfileLoading || !moderation || !profile 91 const isLoadingImage = !uri 92 93 + const userNumber = 10_000_000 // TODO 94 95 + const sharePost = () => { 96 if (uri) { 97 controls.tenMillion.close(() => { 98 setTimeout(() => { ··· 111 } 112 } 113 114 + const onNativeShare = () => { 115 + if (uri) { 116 + controls.tenMillion.close(() => { 117 + shareUrl(uri) 118 + }) 119 } 120 } 121 ··· 132 } 133 } 134 135 + const imageRef = React.useRef<ViewShot>(null) 136 + // const captureInProgress = React.useRef(false) 137 + // const [cavasRelayout, setCanvasRelayout] = React.useState('key') 138 + // const onCanvasReady = async () => { 139 + // if ( 140 + // imageRef.current && 141 + // imageRef.current.capture && 142 + // !captureInProgress.current 143 + // ) { 144 + // captureInProgress.current = true 145 + // setCanvasRelayout('updated') 146 + // } 147 + // } 148 + const onCanvasLayout = async () => { 149 + if ( 150 + imageRef.current && 151 + imageRef.current.capture // && 152 + // cavasRelayout === 'updated' 153 + ) { 154 + console.log('LAYOUT') 155 + const uri = await imageRef.current.capture() 156 + setUri(uri) 157 + } 158 + } 159 + 160 const canvas = isLoadingData ? null : ( 161 <View 162 style={[ ··· 180 options={{width: WIDTH, height: HEIGHT}} 181 style={[a.absolute, a.inset_0]}> 182 <View 183 + // key={cavasRelayout} 184 + onLayout={onCanvasLayout} 185 style={[ 186 a.absolute, 187 a.inset_0, ··· 192 bottom: -1, 193 left: -1, 194 right: -1, 195 + paddingVertical: 48, 196 paddingHorizontal: 48, 197 }, 198 ]}> ··· 230 <View 231 style={[ 232 { 233 + paddingBottom: 32, 234 }, 235 ]}> 236 <Text ··· 238 a.text_md, 239 a.font_bold, 240 a.text_center, 241 + a.pb_md, 242 lightTheme.atoms.text_contrast_medium, 243 ]}> 244 <Trans> ··· 246 </Trans>{' '} 247 🎉 248 </Text> 249 + <View> 250 <Text 251 style={[ 252 a.absolute, ··· 254 color: t.palette.primary_500, 255 fontSize: 32, 256 left: -18, 257 + top: isAndroid ? -8 : isWeb ? 5 : -5, 258 + fontWeight: '900', 259 }, 260 ]}> 261 # 262 </Text> 263 + <Text 264 + style={[ 265 + a.relative, 266 + a.text_center, 267 + { 268 + fontStyle: 'italic', 269 + fontSize: getFontSize(userNumber), 270 + lineHeight: getFontSize(userNumber), 271 + fontWeight: '900', 272 + letterSpacing: -2, 273 + }, 274 + ]}> 275 + {i18n.number(userNumber)} 276 + </Text> 277 + </View> 278 </View> 279 {/* End centered content */} 280 ··· 290 }, 291 ]}> 292 <View style={[a.flex_row, a.align_center, a.gap_sm]}> 293 + {/* 294 <UserAvatar 295 size={36} 296 avatar={profile.avatar} 297 moderation={moderation.ui('avatar')} 298 onLoad={onCanvasReady} 299 /> 300 + */} 301 <View style={[a.gap_2xs, a.flex_1]}> 302 + <Text style={[a.text_sm, a.font_bold, a.leading_tight]}> 303 {sanitizeDisplayName( 304 profile.displayName || 305 sanitizeHandle(profile.handle), ··· 311 style={[ 312 a.text_sm, 313 a.font_semibold, 314 + , 315 + a.leading_tight, 316 lightTheme.atoms.text_contrast_medium, 317 ]}> 318 {sanitizeHandle(profile.handle, '@')} ··· 323 style={[ 324 a.text_sm, 325 a.font_semibold, 326 + , 327 + a.leading_tight, 328 lightTheme.atoms.text_contrast_low, 329 ]}> 330 + <Trans> 331 + Joined{' '} 332 + {i18n.date(profile.createdAt, { 333 + dateStyle: 'long', 334 + })} 335 + </Trans> 336 </Text> 337 )} 338 </View> ··· 350 351 return ( 352 <Dialog.Outer control={controls.tenMillion}> 353 <Dialog.ScrollableInner 354 label={_(msg`Ten Million`)} 355 style={[ 356 { 357 padding: 0, 358 + paddingTop: 0, 359 }, 360 ]}> 361 <View ··· 389 <Text 390 style={[ 391 a.text_5xl, 392 + a.leading_tight, 393 a.pb_lg, 394 { 395 fontWeight: '900', ··· 422 </Text> 423 424 <Button 425 + disabled={isLoadingImage} 426 + label={ 427 + isNative 428 + ? _(msg`Share image externally`) 429 + : _(msg`Download image`) 430 + } 431 size="large" 432 variant="solid" 433 color="secondary" 434 shape="square" 435 + onPress={isNative ? onNativeShare : download}> 436 + <ButtonIcon icon={isNative ? Share : Download} /> 437 </Button> 438 <Button 439 + disabled={isLoadingImage} 440 label={_(msg`Share image in post`)} 441 size="large" 442 variant="solid" 443 color="primary" 444 + onPress={sharePost}> 445 <ButtonText>{_(msg`Share post`)}</ButtonText> 446 <ButtonIcon position="right" icon={ImageIcon} /> 447 </Button>
+5
src/components/icons/Download.tsx
···
··· 1 + import {createSinglePathSVG} from './TEMPLATE' 2 + 3 + export const Download_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 + path: 'M12 3a1 1 0 0 1 1 1v8.086l1.793-1.793a1 1 0 1 1 1.414 1.414l-3.5 3.5a1 1 0 0 1-1.414 0l-3.5-3.5a1 1 0 1 1 1.414-1.414L11 12.086V4a1 1 0 0 1 1-1ZM4 14a1 1 0 0 1 1 1v4h14v-4a1 1 0 1 1 2 0v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1Z', 5 + })