From cbb817b5b707042afefbf8ca46a7104d62349492 Mon Sep 17 00:00:00 2001
From: Hailey <me@haileyok.com>
Date: Mon, 22 Apr 2024 18:54:15 -0700
Subject: GIF Viewer (#3605)

* ios player

autoplay after recycle

remove all items from AVPlayer queue

recurururururursion

use managers in the view

add prefetch

make sure player items stay in order

add controller and item managers

start of the view

create module, ios

* android player

smoother

basic caching

prep cache

somewhat works

backup

other files

android impl

blegh

lets go

touchup

add prefetch to js

use caching

* bogus testing commit

* add dims to type

* save

* add the dimensions to the embed info

* add a new case

* add a new case

* limit this case to giphy

* use gate

* Revert "bogus testing commit"

This reverts commit b3c8751b71f7108de9aa843b22ded4e0249fa854.

* add web player base

* flip mp4/webp

* basic mp4 player for web

* move some stuff into `ExternalLinkEmbed` instead

* use a class component for web

* remove extra component

* add `onPlayerStateChange` event type on web

* layer properly

* fix tests

* add new test

* about ready. native portions done, a few touch ups on web needed

show placeholder on ios

fix type

rm log

display thumbnail until video is ready to play

add oncanplay, playsinline

remove unused method

add `isLoaded` change event

release player when finished

apply gc to the view

cleanup logs

android gc

rm log

automatic gc for assets

make `nativeRef` private

remove unnecessary `await`

cleanup

rev log

only play on prepare whenever needed

rm unused

perfperfperf

rm var

comment + android width

native height calculations

rm pressable

add event dispatcher on android

add event dispatcher on ios

* ready to test ios

fix autoplay ios

clean

oops

* autoplay on web

* normalize across all platforms

add check for `ALT:`

separate gif embed logic to another file

handle permissions requests

flatten web styles

normalize styles

normalize styles

prefetch functions

pause animatable on foreground android

nits

one more oops

idk where that code went

lint

rethink the usage

wrap up

android

clear bg

update gradle

more android

rename dir

update android namespace

web

ios

add deps

use webp

rm unused

update types

use webp on mobile

* rm gate from types

* remove unused event param

* only start placeholder op if doesn't exist in disk cache

* fix gifs animating on app resume android

* remove comment

* add `isLoaded` for ios

* add `isLoaded` to Android

* onload for web

* add visual loading state

* rm a log

* implement isloaded for android

* dialogs

* replace `webpSource` with `source`

* update prop name

* Move to Tenor for GIFs (#3654)

* update some urls

* right order for dimensions

* add GIF coder for ios

* remove giphy check

* rewrite tenor urls

* remove all the unnecessary stuff for consent

* rm print

* rm log

* check if id and filename are strings

* full size playback controls

* pass tests

* add accessibility to gifs

* use `onPlay` and `onPause`

* rm unused logic for description

* add accessibility label to the controls

* add gif into to external embed in composer

* make it optional

* gif dimensions

* make the jsx look nicer

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
---
 src/view/com/util/post-embeds/GifEmbed.tsx | 140 +++++++++++++++++++++++++++++
 1 file changed, 140 insertions(+)
 create mode 100644 src/view/com/util/post-embeds/GifEmbed.tsx

(limited to 'src/view/com/util/post-embeds/GifEmbed.tsx')

diff --git a/src/view/com/util/post-embeds/GifEmbed.tsx b/src/view/com/util/post-embeds/GifEmbed.tsx
new file mode 100644
index 000000000..32bd75df0
--- /dev/null
+++ b/src/view/com/util/post-embeds/GifEmbed.tsx
@@ -0,0 +1,140 @@
+import React from 'react'
+import {Pressable, View} from 'react-native'
+import {AppBskyEmbedExternal} from '@atproto/api'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {EmbedPlayerParams} from 'lib/strings/embed-player'
+import {useAutoplayDisabled} from 'state/preferences'
+import {atoms as a, useTheme} from '#/alf'
+import {Loader} from '#/components/Loader'
+import {GifView} from '../../../../../modules/expo-bluesky-gif-view'
+import {GifViewStateChangeEvent} from '../../../../../modules/expo-bluesky-gif-view/src/GifView.types'
+
+function PlaybackControls({
+  onPress,
+  isPlaying,
+  isLoaded,
+}: {
+  onPress: () => void
+  isPlaying: boolean
+  isLoaded: boolean
+}) {
+  const {_} = useLingui()
+  const t = useTheme()
+
+  return (
+    <Pressable
+      accessibilityRole="button"
+      accessibilityHint={_(msg`Play or pause the GIF`)}
+      accessibilityLabel={isPlaying ? _(msg`Pause`) : _(msg`Play`)}
+      style={[
+        a.absolute,
+        a.align_center,
+        a.justify_center,
+        !isLoaded && a.border,
+        t.atoms.border_contrast_medium,
+        a.inset_0,
+        a.w_full,
+        a.h_full,
+        {
+          zIndex: 2,
+          backgroundColor: !isLoaded
+            ? t.atoms.bg_contrast_25.backgroundColor
+            : !isPlaying
+            ? 'rgba(0, 0, 0, 0.3)'
+            : undefined,
+        },
+      ]}
+      onPress={onPress}>
+      {!isLoaded ? (
+        <View>
+          <View style={[a.align_center, a.justify_center]}>
+            <Loader size="xl" />
+          </View>
+        </View>
+      ) : !isPlaying ? (
+        <View
+          style={[
+            a.rounded_full,
+            a.align_center,
+            a.justify_center,
+            {
+              backgroundColor: t.palette.primary_500,
+              width: 60,
+              height: 60,
+            },
+          ]}>
+          <FontAwesomeIcon
+            icon="play"
+            size={42}
+            color="white"
+            style={{marginLeft: 8}}
+          />
+        </View>
+      ) : undefined}
+    </Pressable>
+  )
+}
+
+export function GifEmbed({
+  params,
+  link,
+}: {
+  params: EmbedPlayerParams
+  link: AppBskyEmbedExternal.ViewExternal
+}) {
+  const {_} = useLingui()
+  const autoplayDisabled = useAutoplayDisabled()
+
+  const playerRef = React.useRef<GifView>(null)
+
+  const [playerState, setPlayerState] = React.useState<{
+    isPlaying: boolean
+    isLoaded: boolean
+  }>({
+    isPlaying: !autoplayDisabled,
+    isLoaded: false,
+  })
+
+  const onPlayerStateChange = React.useCallback(
+    (e: GifViewStateChangeEvent) => {
+      setPlayerState(e.nativeEvent)
+    },
+    [],
+  )
+
+  const onPress = React.useCallback(() => {
+    playerRef.current?.toggleAsync()
+  }, [])
+
+  return (
+    <View style={[a.rounded_sm, a.overflow_hidden, a.mt_sm]}>
+      <View
+        style={[
+          a.rounded_sm,
+          a.overflow_hidden,
+          {
+            aspectRatio: params.dimensions!.width / params.dimensions!.height,
+          },
+        ]}>
+        <PlaybackControls
+          onPress={onPress}
+          isPlaying={playerState.isPlaying}
+          isLoaded={playerState.isLoaded}
+        />
+        <GifView
+          source={params.playerUri}
+          placeholderSource={link.thumb}
+          style={[a.flex_1, a.rounded_sm]}
+          autoplay={!autoplayDisabled}
+          onPlayerStateChange={onPlayerStateChange}
+          ref={playerRef}
+          accessibilityHint={_(msg`Animated GIF`)}
+          accessibilityLabel={link.description.replace('ALT: ', '')}
+        />
+      </View>
+    </View>
+  )
+}
-- 
cgit 1.4.1

