diff --git a/package.json b/package.json index e84583e8..404314f0 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@expo/vector-icons": "^12.0.0", "@react-native-community/masked-view": "0.1.10", "@react-native-community/segmented-control": "2.2.1", + "@react-native-community/slider": "3.0.3", "@react-navigation/bottom-tabs": "^5.11.2", "@react-navigation/native": "^5.8.10", "@reduxjs/toolkit": "^1.5.0", @@ -19,6 +20,7 @@ "expo": "^40.0.0", "expo-auth-session": "~3.0.0", "expo-av": "~8.7.0", + "expo-blur": "~8.2.2", "expo-crypto": "~8.4.0", "expo-gl": "~9.2.0", "expo-image-picker": "~9.2.0", @@ -78,4 +80,4 @@ "typescript": "~4.0.0" }, "private": true -} \ No newline at end of file +} diff --git a/src/@types/mastodon.d.ts b/src/@types/mastodon.d.ts index 9021985c..15dce8cb 100644 --- a/src/@types/mastodon.d.ts +++ b/src/@types/mastodon.d.ts @@ -82,7 +82,7 @@ declare namespace Mastodon { | AttachmentVideo | AttachmentGifv | AttachmentAudio - // | AttachmentUnknown + | AttachmentUnknown type AttachmentImage = { // Base @@ -187,8 +187,9 @@ declare namespace Mastodon { // Others remote_url?: string + preview_remote_url?: string // undocumented text_url?: string - meta?: { + meta: { length: string duration: number audio_encode: string @@ -203,20 +204,20 @@ declare namespace Mastodon { blurhash?: string } - // type AttachmentUnknown = { - // // Base - // id: string - // type: 'unknown' - // url: string - // preview_url: string + type AttachmentUnknown = { + // Base + id: string + type: 'unknown' + url: string + preview_url: string - // // Others - // remote_url?: string - // text_url?: string - // meta?: any - // description?: string - // blurhash?: string - // } + // Others + remote_url?: string + text_url?: string + meta?: any + description?: string + blurhash?: string + } type Card = { // Base diff --git a/src/components/Timelines/Timeline/Shared/Attachment.tsx b/src/components/Timelines/Timeline/Shared/Attachment.tsx index 65211a74..9ca24bfd 100644 --- a/src/components/Timelines/Timeline/Shared/Attachment.tsx +++ b/src/components/Timelines/Timeline/Shared/Attachment.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import React, { useCallback, useMemo, useState } from 'react' import { Pressable, StyleSheet, Text, View } from 'react-native' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' @@ -8,6 +8,8 @@ import AttachmentVideo from '@root/components/Timelines/Timeline/Shared/Attachme import { IImageInfo } from 'react-native-image-zoom-viewer/built/image-viewer.type' import { useNavigation } from '@react-navigation/native' import AttachmentUnsupported from './Attachment/Unsupported' +import AttachmentAudio from './Attachment/Audio' +import { Feather } from '@expo/vector-icons' export interface Props { status: Pick @@ -21,13 +23,6 @@ const TimelineAttachment: React.FC = ({ status, contentWidth }) => { const onPressBlurView = useCallback(() => { setSensitiveShown(false) }, []) - useEffect(() => { - if (status.sensitive && sensitiveShown === false) { - setTimeout(() => { - setSensitiveShown(true) - }, 10000) - } - }, [sensitiveShown]) let imageUrls: (IImageInfo & { preview_url: Mastodon.AttachmentImage['preview_url'] @@ -83,7 +78,13 @@ const TimelineAttachment: React.FC = ({ status, contentWidth }) => { /> ) case 'audio': - return 音频不支持 + return ( + + ) default: return } @@ -100,23 +101,43 @@ const TimelineAttachment: React.FC = ({ status, contentWidth }) => { > {attachments} - {status.sensitive && sensitiveShown && ( - + {status.sensitive && + (sensitiveShown ? ( + + + + 显示敏感内容 + + + + ) : ( setSensitiveShown(!sensitiveShown)} > - - 显示敏感内容 - + - - )} + ))} ) } diff --git a/src/components/Timelines/Timeline/Shared/Attachment/Audio.tsx b/src/components/Timelines/Timeline/Shared/Attachment/Audio.tsx new file mode 100644 index 00000000..d710a1dc --- /dev/null +++ b/src/components/Timelines/Timeline/Shared/Attachment/Audio.tsx @@ -0,0 +1,123 @@ +import React, { useCallback, useState } from 'react' +import { Image, Pressable, StyleSheet, View } from 'react-native' +import { Audio } from 'expo-av' +import { ButtonRow } from '@components/Button' +import layoutAnimation from '@root/utils/styles/layoutAnimation' +import { Surface } from 'gl-react-expo' +import { Blurhash } from 'gl-react-blurhash' +import Slider from '@react-native-community/slider' +import { StyleConstants } from '@root/utils/styles/constants' +import { useTheme } from '@root/utils/styles/ThemeManager' + +export interface Props { + sensitiveShown: boolean + audio: Mastodon.AttachmentAudio +} + +const AttachmentAudio: React.FC = ({ sensitiveShown, audio }) => { + layoutAnimation() + + const { theme } = useTheme() + + const [audioPlayer, setAudioPlayer] = useState() + const [audioPlaying, setAudioPlaying] = useState(false) + const [audioPosition, setAudioPosition] = useState(0) + const playAudio = useCallback(async () => { + if (!audioPlayer) { + await Audio.setAudioModeAsync({ interruptionModeIOS: 1 }) + const { sound } = await Audio.Sound.createAsync( + { uri: audio.url }, + {}, + // @ts-ignore + props => setAudioPosition(props.positionMillis) + ) + setAudioPlayer(sound) + } else { + await audioPlayer.setPositionAsync(audioPosition) + audioPlayer.playAsync() + setAudioPlaying(true) + } + }, [audioPlayer, audioPosition]) + const pauseAudio = useCallback(async () => { + audioPlayer!.pauseAsync() + setAudioPlaying(false) + }, [audioPlayer]) + + return ( + <> + + {sensitiveShown ? ( + audio.blurhash && ( + + + + ) + ) : ( + <> + {(audio.preview_url || audio.preview_remote_url) && ( + + )} + + + )} + + + { + audioPlayer?.pauseAsync() + setAudioPlaying(false) + }} + onSlidingComplete={value => { + setAudioPosition(value) + }} + /> + + + ) +} + +const styles = StyleSheet.create({ + background: { position: 'absolute', width: '100%', height: '100%' }, + overlay: { + position: 'absolute', + width: '100%', + height: '100%', + flex: 1, + justifyContent: 'center', + alignItems: 'center' + } +}) + +export default AttachmentAudio diff --git a/src/components/Timelines/Timeline/Shared/Attachment/Unsupported.tsx b/src/components/Timelines/Timeline/Shared/Attachment/Unsupported.tsx index 95590476..a555d3b1 100644 --- a/src/components/Timelines/Timeline/Shared/Attachment/Unsupported.tsx +++ b/src/components/Timelines/Timeline/Shared/Attachment/Unsupported.tsx @@ -6,7 +6,7 @@ import { StyleConstants } from '@root/utils/styles/constants' import openLink from '@root/utils/openLink' export interface Props { - attachment: Mastodon.Attachment + attachment: Mastodon.AttachmentUnknown } const AttachmentUnsupported: React.FC = ({ attachment }) => { diff --git a/yarn.lock b/yarn.lock index 14ef8ca9..cba12e31 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1339,6 +1339,11 @@ resolved "https://registry.yarnpkg.com/@react-native-community/segmented-control/-/segmented-control-2.2.1.tgz#5ca418d78c5f6051353c9586918458713b88a83c" integrity sha512-BzxFbI9Iqv+31yVqEvCTzJYmwb8jOMTf/UPuC4Hj176tmEPqBpuDaGH+rkAFg1miOco3/43RQxiAZO+mkY40Fg== +"@react-native-community/slider@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@react-native-community/slider/-/slider-3.0.3.tgz#830167fd757ba70ac638747ba3169b2dbae60330" + integrity sha512-8IeHfDwJ9/CTUwFs6x90VlobV3BfuPgNLjTgC6dRZovfCWigaZwVNIFFJnHBakK3pW2xErAPwhdvNR4JeNoYbw== + "@react-navigation/bottom-tabs@^5.11.2": version "5.11.2" resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-5.11.2.tgz#5b541612fcecdea2a5024a4028da35e4a727bde6" @@ -2860,6 +2865,11 @@ expo-av@~8.7.0: lodash "^4.17.15" nullthrows "^1.1.0" +expo-blur@~8.2.2: + version "8.2.2" + resolved "https://registry.yarnpkg.com/expo-blur/-/expo-blur-8.2.2.tgz#a7643d893afb7aed5512b25d5df22ee6083832c1" + integrity sha512-Xiklw60RUPIchHKzfvLTIuccVDTIQEAIPv02yJY2xFDujQKjE0NU0/Z5Z+zsEI9QOi82jX9NbR8gQ+8Mm3hDhA== + expo-constants@^9.3.3, expo-constants@~9.3.3: version "9.3.5" resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-9.3.5.tgz#78085763e8ed100a5f2df7c682fd99631aa03d5e"