mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

GIF previews in notifications (#4447)

* gifs in notifications

* remove try/catch

* Limit try/catch scope

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>

authored by samuel.fm

Dan Abramov and committed by
GitHub
3dc34be9 7ddbc392

+143 -59
+64 -34
src/lib/strings/embed-player.ts
··· 1 - import {Dimensions, Platform} from 'react-native' 1 + import {Dimensions} from 'react-native' 2 2 3 3 import {isSafari} from 'lib/browser' 4 4 import {isWeb} from 'platform/detection' 5 + 5 6 const {height: SCREEN_HEIGHT} = Dimensions.get('window') 6 7 7 8 const IFRAME_HOST = isWeb ··· 342 343 } 343 344 } 344 345 345 - if (urlp.hostname === 'media.tenor.com') { 346 - let [_, id, filename] = urlp.pathname.split('/') 347 - 348 - const h = urlp.searchParams.get('hh') 349 - const w = urlp.searchParams.get('ww') 350 - let dimensions 351 - if (h && w) { 352 - dimensions = { 353 - height: Number(h), 354 - width: Number(w), 355 - } 356 - } 346 + const tenorGif = parseTenorGif(urlp) 347 + if (tenorGif.success) { 348 + const {playerUri, dimensions} = tenorGif 357 349 358 - if (id && filename && dimensions && id.includes('AAAAC')) { 359 - if (Platform.OS === 'web') { 360 - if (isSafari) { 361 - id = id.replace('AAAAC', 'AAAP1') 362 - filename = filename.replace('.gif', '.mp4') 363 - } else { 364 - id = id.replace('AAAAC', 'AAAP3') 365 - filename = filename.replace('.gif', '.webm') 366 - } 367 - } else { 368 - id = id.replace('AAAAC', 'AAAAM') 369 - } 370 - 371 - return { 372 - type: 'tenor_gif', 373 - source: 'tenor', 374 - isGif: true, 375 - hideDetails: true, 376 - playerUri: `https://t.gifs.bsky.app/${id}/${filename}`, 377 - dimensions, 378 - } 350 + return { 351 + type: 'tenor_gif', 352 + source: 'tenor', 353 + isGif: true, 354 + hideDetails: true, 355 + playerUri, 356 + dimensions, 379 357 } 380 358 } 381 359 ··· 516 494 } 517 495 } 518 496 } 497 + 498 + export function parseTenorGif(urlp: URL): 499 + | {success: false} 500 + | { 501 + success: true 502 + playerUri: string 503 + dimensions: {height: number; width: number} 504 + } { 505 + if (urlp.hostname !== 'media.tenor.com') { 506 + return {success: false} 507 + } 508 + 509 + let [_, id, filename] = urlp.pathname.split('/') 510 + 511 + if (!id || !filename) { 512 + return {success: false} 513 + } 514 + 515 + if (!id.includes('AAAAC')) { 516 + return {success: false} 517 + } 518 + 519 + const h = urlp.searchParams.get('hh') 520 + const w = urlp.searchParams.get('ww') 521 + 522 + if (!h || !w) { 523 + return {success: false} 524 + } 525 + 526 + const dimensions = { 527 + height: Number(h), 528 + width: Number(w), 529 + } 530 + 531 + if (isWeb) { 532 + if (isSafari) { 533 + id = id.replace('AAAAC', 'AAAP1') 534 + filename = filename.replace('.gif', '.mp4') 535 + } else { 536 + id = id.replace('AAAAC', 'AAAP3') 537 + filename = filename.replace('.gif', '.webm') 538 + } 539 + } else { 540 + id = id.replace('AAAAC', 'AAAAM') 541 + } 542 + 543 + return { 544 + success: true, 545 + playerUri: `https://t.gifs.bsky.app/${id}/${filename}`, 546 + dimensions, 547 + } 548 + }
+40 -7
src/view/com/notifications/FeedItem.tsx
··· 8 8 } from 'react-native' 9 9 import { 10 10 AppBskyActorDefs, 11 + AppBskyEmbedExternal, 11 12 AppBskyEmbedImages, 12 13 AppBskyEmbedRecordWithMedia, 13 14 AppBskyFeedDefs, ··· 51 52 import {PreviewableUserAvatar, UserAvatar} from '../util/UserAvatar' 52 53 53 54 import hairlineWidth = StyleSheet.hairlineWidth 55 + import {parseTenorGif} from '#/lib/strings/embed-player' 54 56 55 57 const MAX_AUTHORS = 5 56 58 ··· 465 467 const pal = usePalette('default') 466 468 if (post && AppBskyFeedPost.isRecord(post?.record)) { 467 469 const text = post.record.text 468 - const images = AppBskyEmbedImages.isView(post.embed) 469 - ? post.embed.images 470 - : AppBskyEmbedRecordWithMedia.isView(post.embed) && 471 - AppBskyEmbedImages.isView(post.embed.media) 472 - ? post.embed.media.images 473 - : undefined 470 + let images 471 + let isGif = false 472 + 473 + if (AppBskyEmbedImages.isView(post.embed)) { 474 + images = post.embed.images 475 + } else if ( 476 + AppBskyEmbedRecordWithMedia.isView(post.embed) && 477 + AppBskyEmbedImages.isView(post.embed.media) 478 + ) { 479 + images = post.embed.media.images 480 + } else if ( 481 + AppBskyEmbedExternal.isView(post.embed) && 482 + post.embed.external.thumb 483 + ) { 484 + let url: URL | undefined 485 + try { 486 + url = new URL(post.embed.external.uri) 487 + } catch {} 488 + if (url) { 489 + const {success} = parseTenorGif(url) 490 + if (success) { 491 + isGif = true 492 + images = [ 493 + { 494 + thumb: post.embed.external.thumb, 495 + alt: post.embed.external.title, 496 + fullsize: post.embed.external.thumb, 497 + }, 498 + ] 499 + } 500 + } 501 + } 502 + 474 503 return ( 475 504 <> 476 505 {text?.length > 0 && <Text style={pal.textLight}>{text}</Text>} 477 506 {images && images.length > 0 && ( 478 - <ImageHorzList images={images} style={styles.additionalPostImages} /> 507 + <ImageHorzList 508 + images={images} 509 + style={styles.additionalPostImages} 510 + gif={isGif} 511 + /> 479 512 )} 480 513 </> 481 514 )
+39 -18
src/view/com/util/images/ImageHorzList.tsx
··· 2 2 import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' 3 3 import {Image} from 'expo-image' 4 4 import {AppBskyEmbedImages} from '@atproto/api' 5 + import {Trans} from '@lingui/macro' 6 + 7 + import {atoms as a} from '#/alf' 8 + import {Text} from '#/components/Typography' 5 9 6 10 interface Props { 7 11 images: AppBskyEmbedImages.ViewImage[] 8 12 style?: StyleProp<ViewStyle> 13 + gif?: boolean 9 14 } 10 15 11 - export function ImageHorzList({images, style}: Props) { 16 + export function ImageHorzList({images, style, gif}: Props) { 12 17 return ( 13 - <View style={[styles.flexRow, style]}> 18 + <View style={[a.flex_row, a.gap_xs, style]}> 14 19 {images.map(({thumb, alt}) => ( 15 - <Image 20 + <View 16 21 key={thumb} 17 - source={{uri: thumb}} 18 - style={styles.image} 19 - accessible={true} 20 - accessibilityIgnoresInvertColors 21 - accessibilityHint={alt} 22 - accessibilityLabel="" 23 - /> 22 + style={[a.relative, a.flex_1, {aspectRatio: 1, maxWidth: 100}]}> 23 + <Image 24 + key={thumb} 25 + source={{uri: thumb}} 26 + style={[a.flex_1, a.rounded_xs]} 27 + accessible={true} 28 + accessibilityIgnoresInvertColors 29 + accessibilityHint={alt} 30 + accessibilityLabel="" 31 + /> 32 + {gif && ( 33 + <View style={styles.altContainer}> 34 + <Text style={styles.alt}> 35 + <Trans>GIF</Trans> 36 + </Text> 37 + </View> 38 + )} 39 + </View> 24 40 ))} 25 41 </View> 26 42 ) 27 43 } 28 44 29 45 const styles = StyleSheet.create({ 30 - flexRow: { 31 - flexDirection: 'row', 32 - gap: 5, 46 + altContainer: { 47 + backgroundColor: 'rgba(0, 0, 0, 0.75)', 48 + borderRadius: 6, 49 + paddingHorizontal: 6, 50 + paddingVertical: 3, 51 + position: 'absolute', 52 + right: 5, 53 + bottom: 5, 54 + zIndex: 2, 33 55 }, 34 - image: { 35 - maxWidth: 100, 36 - aspectRatio: 1, 37 - flex: 1, 38 - borderRadius: 4, 56 + alt: { 57 + color: 'white', 58 + fontSize: 7, 59 + fontWeight: 'bold', 39 60 }, 40 61 })