mirror of
https://github.com/tooot-app/app
synced 2025-02-02 11:36:56 +01:00
Video working
This commit is contained in:
parent
2fae98cb9e
commit
aa53533534
@ -75,7 +75,8 @@ const styles = StyleSheet.create({
|
||||
})
|
||||
|
||||
Actioned.propTypes = {
|
||||
action: PropTypes.oneOf(['favourite', 'follow', 'poll', 'reblog']).isRequired,
|
||||
action: PropTypes.oneOf(['favourite', 'follow', 'mention', 'poll', 'reblog'])
|
||||
.isRequired,
|
||||
name: PropTypes.string,
|
||||
emojis: PropTypes.arrayOf(propTypesEmoji),
|
||||
notification: PropTypes.bool
|
||||
|
81
src/components/Toot/Attachment.jsx
Normal file
81
src/components/Toot/Attachment.jsx
Normal file
@ -0,0 +1,81 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import propTypesAttachment from 'src/prop-types/attachment'
|
||||
import { Text, View } from 'react-native'
|
||||
|
||||
import AttachmentImage from './Attachment/AttachmentImage'
|
||||
import AttachmentVideo from './Attachment/AttachmentVideo'
|
||||
|
||||
export default function Attachment ({ media_attachments, sensitive, width }) {
|
||||
let attachment
|
||||
let attachmentHeight
|
||||
// if (width) {}
|
||||
switch (media_attachments[0].type) {
|
||||
case 'unknown':
|
||||
attachment = <Text>文件不支持</Text>
|
||||
attachmentHeight = 25
|
||||
break
|
||||
case 'image':
|
||||
attachment = (
|
||||
<AttachmentImage
|
||||
media_attachments={media_attachments}
|
||||
sensitive={sensitive}
|
||||
width={width}
|
||||
/>
|
||||
)
|
||||
attachmentHeight = width / 2
|
||||
break
|
||||
case 'gifv':
|
||||
attachment = (
|
||||
<AttachmentVideo
|
||||
media_attachments={media_attachments}
|
||||
sensitive={sensitive}
|
||||
width={width}
|
||||
/>
|
||||
)
|
||||
attachmentHeight =
|
||||
(width / media_attachments[0].meta.original.width) *
|
||||
media_attachments[0].meta.original.height
|
||||
break
|
||||
case 'video':
|
||||
attachment = (
|
||||
<AttachmentVideo
|
||||
media_attachments={media_attachments}
|
||||
sensitive={sensitive}
|
||||
width={width}
|
||||
/>
|
||||
)
|
||||
attachmentHeight =
|
||||
(width / media_attachments[0].meta.original.width) *
|
||||
media_attachments[0].meta.original.height
|
||||
break
|
||||
// case 'audio':
|
||||
// attachment = (
|
||||
// <AttachmentAudio
|
||||
// media_attachments={media_attachments}
|
||||
// sensitive={sensitive}
|
||||
// width={width}
|
||||
// />
|
||||
// )
|
||||
// break
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: width + 8,
|
||||
height: attachmentHeight,
|
||||
marginTop: 4,
|
||||
marginLeft: -4
|
||||
}}
|
||||
>
|
||||
{attachment}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
Attachment.propTypes = {
|
||||
media_attachments: PropTypes.arrayOf(propTypesAttachment),
|
||||
sensitive: PropTypes.bool.isRequired,
|
||||
width: PropTypes.number.isRequired
|
||||
}
|
102
src/components/Toot/Attachment/AttachmentImage.jsx
Normal file
102
src/components/Toot/Attachment/AttachmentImage.jsx
Normal file
@ -0,0 +1,102 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import propTypesAttachment from 'src/prop-types/attachment'
|
||||
import { Button, Image, Modal, StyleSheet, Pressable, View } from 'react-native'
|
||||
import ImageViewer from 'react-native-image-zoom-viewer'
|
||||
|
||||
export default function AttachmentImage ({ media_attachments, sensitive, width }) {
|
||||
const [mediaSensitive, setMediaSensitive] = useState(sensitive)
|
||||
const [imageModalVisible, setImageModalVisible] = useState(false)
|
||||
const [imageModalIndex, setImageModalIndex] = useState(0)
|
||||
useEffect(() => {
|
||||
if (sensitive && mediaSensitive === false) {
|
||||
setTimeout(() => {
|
||||
setMediaSensitive(true)
|
||||
}, 10000)
|
||||
}
|
||||
}, [mediaSensitive])
|
||||
|
||||
let images = []
|
||||
media_attachments = media_attachments.map((m, i) => {
|
||||
images.push({
|
||||
url: m.url,
|
||||
width: m.meta.original.width,
|
||||
height: m.meta.original.height
|
||||
})
|
||||
return (
|
||||
<Pressable
|
||||
key={i}
|
||||
style={{ flexGrow: 1, height: width / 2, margin: 4 }}
|
||||
onPress={() => {
|
||||
setImageModalIndex(i)
|
||||
setImageModalVisible(true)
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: m.preview_url }}
|
||||
style={styles.image}
|
||||
blurRadius={mediaSensitive ? width / 5 : 0}
|
||||
/>
|
||||
</Pressable>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={styles.media}>
|
||||
{media_attachments}
|
||||
{mediaSensitive && (
|
||||
<View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
title='Press me'
|
||||
onPress={() => {
|
||||
setMediaSensitive(false)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<Modal
|
||||
visible={imageModalVisible}
|
||||
transparent={true}
|
||||
animationType='fade'
|
||||
>
|
||||
<ImageViewer
|
||||
imageUrls={images}
|
||||
index={imageModalIndex}
|
||||
onSwipeDown={() => setImageModalVisible(false)}
|
||||
enableSwipeDown={true}
|
||||
swipeDownThreshold={100}
|
||||
useNativeDriver={true}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
media: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'stretch',
|
||||
alignContent: 'stretch'
|
||||
},
|
||||
image: {
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}
|
||||
})
|
||||
|
||||
AttachmentImage.propTypes = {
|
||||
media_attachments: PropTypes.arrayOf(propTypesAttachment),
|
||||
sensitive: PropTypes.bool.isRequired,
|
||||
width: PropTypes.number.isRequired
|
||||
}
|
72
src/components/Toot/Attachment/AttachmentVideo.jsx
Normal file
72
src/components/Toot/Attachment/AttachmentVideo.jsx
Normal file
@ -0,0 +1,72 @@
|
||||
import React, { useRef, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import propTypesAttachment from 'src/prop-types/attachment'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { Video } from 'expo-av'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
|
||||
export default function AttachmentVideo ({
|
||||
media_attachments,
|
||||
sensitive,
|
||||
width
|
||||
}) {
|
||||
const videoPlayer = useRef()
|
||||
const [mediaSensitive, setMediaSensitive] = useState(sensitive)
|
||||
const [videoPlay, setVideoPlay] = useState(false)
|
||||
|
||||
const video = media_attachments[0]
|
||||
const videoWidth = width
|
||||
const videoHeight =
|
||||
(width / video.meta.original.width) * video.meta.original.height
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: videoWidth,
|
||||
height: videoHeight
|
||||
}}
|
||||
>
|
||||
<Video
|
||||
ref={videoPlayer}
|
||||
source={{ uri: video.remote_url }}
|
||||
style={{
|
||||
width: videoWidth,
|
||||
height: videoHeight
|
||||
}}
|
||||
resizeMode='cover'
|
||||
usePoster
|
||||
posterSourceThe={{ uri: video.preview_url }}
|
||||
useNativeControls
|
||||
shouldPlay={videoPlay}
|
||||
/>
|
||||
{!videoPlay && (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
setMediaSensitive(false)
|
||||
videoPlayer.current.presentFullscreenPlayer()
|
||||
setVideoPlay(true)
|
||||
}}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.25)'
|
||||
}}
|
||||
>
|
||||
<Feather name='play' size={36} color='black' />
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
AttachmentVideo.propTypes = {
|
||||
media_attachments: PropTypes.arrayOf(propTypesAttachment),
|
||||
sensitive: PropTypes.bool.isRequired,
|
||||
width: PropTypes.number.isRequired
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import propTypesAttachment from 'src/prop-types/attachment'
|
||||
import {
|
||||
Button,
|
||||
Image,
|
||||
Modal,
|
||||
StyleSheet,
|
||||
Text,
|
||||
Pressable,
|
||||
View
|
||||
} from 'react-native'
|
||||
import ImageViewer from 'react-native-image-zoom-viewer'
|
||||
|
||||
export default function Media ({ media_attachments, sensitive, width }) {
|
||||
const [mediaSensitive, setMediaSensitive] = useState(sensitive)
|
||||
const [imageModalVisible, setImageModalVisible] = useState(false)
|
||||
const [imageModalIndex, setImageModalIndex] = useState(0)
|
||||
useEffect(() => {
|
||||
if (sensitive && mediaSensitive === false) {
|
||||
setTimeout(() => {
|
||||
setMediaSensitive(true)
|
||||
}, 10000)
|
||||
}
|
||||
}, [mediaSensitive])
|
||||
|
||||
let media
|
||||
let images = []
|
||||
if (width) {
|
||||
media_attachments = media_attachments.map((m, i) => {
|
||||
switch (m.type) {
|
||||
case 'unknown':
|
||||
return <Text key={i}>文件不支持</Text>
|
||||
case 'image':
|
||||
images.push({
|
||||
url: m.url,
|
||||
width: m.meta.original.width,
|
||||
height: m.meta.original.height
|
||||
})
|
||||
return (
|
||||
<Pressable
|
||||
key={i}
|
||||
style={{ flexGrow: 1, height: width / 2, margin: 4 }}
|
||||
onPress={() => {
|
||||
setImageModalIndex(i)
|
||||
setImageModalVisible(true)
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: m.preview_url }}
|
||||
style={styles.image}
|
||||
blurRadius={mediaSensitive ? width / 5 : 0}
|
||||
/>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
})
|
||||
if (images) {
|
||||
media = (
|
||||
<>
|
||||
<View style={styles.media}>
|
||||
{media_attachments}
|
||||
{mediaSensitive && (
|
||||
<View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
title='Press me'
|
||||
onPress={() => {
|
||||
setMediaSensitive(false)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<Modal
|
||||
visible={imageModalVisible}
|
||||
transparent={true}
|
||||
animationType='fade'
|
||||
>
|
||||
<ImageViewer
|
||||
imageUrls={images}
|
||||
index={imageModalIndex}
|
||||
onSwipeDown={() => setImageModalVisible(false)}
|
||||
enableSwipeDown={true}
|
||||
swipeDownThreshold={100}
|
||||
useNativeDriver={true}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
} else {
|
||||
media = <View style={styles.media}>{media_attachments}</View>
|
||||
}
|
||||
} else {
|
||||
media = <></>
|
||||
}
|
||||
|
||||
return (
|
||||
media_attachments.length > 0 && (
|
||||
<View
|
||||
style={{
|
||||
width: width + 8,
|
||||
height: width / 2,
|
||||
marginTop: 4,
|
||||
marginLeft: -4
|
||||
}}
|
||||
>
|
||||
{media}
|
||||
</View>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
media: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'stretch',
|
||||
alignContent: 'stretch'
|
||||
},
|
||||
image: {
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}
|
||||
})
|
||||
|
||||
Media.propTypes = {
|
||||
media_attachments: PropTypes.arrayOf(propTypesAttachment),
|
||||
sensitive: PropTypes.bool.isRequired,
|
||||
width: PropTypes.number.isRequired
|
||||
}
|
@ -8,7 +8,7 @@ import Avatar from './Toot/Avatar'
|
||||
import Header from './Toot/Header'
|
||||
import Content from './Toot/Content'
|
||||
import Poll from './Toot/Poll'
|
||||
import Media from './Toot/Media'
|
||||
import Attachment from './Toot/Attachment'
|
||||
import Card from './Toot/Card'
|
||||
import Actions from './Toot/Actions'
|
||||
|
||||
@ -53,7 +53,7 @@ export default function TootNotification ({ toot }) {
|
||||
)}
|
||||
{toot.status.poll && <Poll poll={toot.status.poll} />}
|
||||
{toot.status.media_attachments && (
|
||||
<Media
|
||||
<Attachment
|
||||
media_attachments={toot.status.media_attachments}
|
||||
sensitive={toot.status.sensitive}
|
||||
width={Dimensions.get('window').width - 24 - 50 - 8}
|
||||
|
@ -8,7 +8,7 @@ import Avatar from './Toot/Avatar'
|
||||
import Header from './Toot/Header'
|
||||
import Content from './Toot/Content'
|
||||
import Poll from './Toot/Poll'
|
||||
import Media from './Toot/Media'
|
||||
import Attachment from './Toot/Attachment'
|
||||
import Card from './Toot/Card'
|
||||
import Actions from './Toot/Actions'
|
||||
|
||||
@ -50,9 +50,9 @@ export default function TootTimeline ({ toot }) {
|
||||
/>
|
||||
{/* Can pass toot info to next page to speed up performance */}
|
||||
<Pressable
|
||||
onPress={() =>
|
||||
navigation.navigate('Toot', { toot: actualContent.id })
|
||||
}
|
||||
// onPress={() =>
|
||||
// navigation.navigate('Toot', { toot: actualContent.id })
|
||||
// }
|
||||
>
|
||||
{actualContent.content ? (
|
||||
<Content
|
||||
@ -67,8 +67,8 @@ export default function TootTimeline ({ toot }) {
|
||||
<></>
|
||||
)}
|
||||
{actualContent.poll && <Poll poll={actualContent.poll} />}
|
||||
{actualContent.media_attachments && (
|
||||
<Media
|
||||
{actualContent.media_attachments.length > 0 && (
|
||||
<Attachment
|
||||
media_attachments={actualContent.media_attachments}
|
||||
sensitive={actualContent.sensitive}
|
||||
width={Dimensions.get('window').width - 24 - 50 - 8}
|
||||
|
@ -3,6 +3,7 @@ import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
|
||||
import Timeline from 'src/stacks/common/Timeline'
|
||||
import sharedScreens from 'src/stacks/Shared/sharedScreens'
|
||||
|
||||
const Stack = createNativeStackNavigator()
|
||||
|
||||
@ -28,6 +29,8 @@ export default function Notifications () {
|
||||
<Stack.Screen name='Notifications'>
|
||||
{() => <Timeline page='Notifications' />}
|
||||
</Stack.Screen>
|
||||
|
||||
{sharedScreens(Stack)}
|
||||
</Stack.Navigator>
|
||||
)
|
||||
}
|
||||
|
45
src/stacks/Shared/sharedScreens.jsx
Normal file
45
src/stacks/Shared/sharedScreens.jsx
Normal file
@ -0,0 +1,45 @@
|
||||
import React from 'react'
|
||||
|
||||
import Account from 'src/stacks/Shared/Account'
|
||||
import Hashtag from 'src/stacks/Shared/Hashtag'
|
||||
import Toot from 'src/stacks/Shared/Toot'
|
||||
import Webview from 'src/stacks/Shared/Webview'
|
||||
|
||||
export default function sharedScreens (Stack) {
|
||||
return [
|
||||
<Stack.Screen
|
||||
key='Account'
|
||||
name='Account'
|
||||
component={Account}
|
||||
options={{
|
||||
headerTranslucent: true,
|
||||
headerStyle: { backgroundColor: 'rgba(255, 255, 255, 0)' },
|
||||
headerCenter: () => {}
|
||||
}}
|
||||
/>,
|
||||
<Stack.Screen
|
||||
key='Hashtag'
|
||||
name='Hashtag'
|
||||
component={Hashtag}
|
||||
options={({ route }) => ({
|
||||
title: `#${decodeURIComponent(route.params.hashtag)}`
|
||||
})}
|
||||
/>,
|
||||
<Stack.Screen
|
||||
key='Toot'
|
||||
name='Toot'
|
||||
component={Toot}
|
||||
options={() => ({
|
||||
title: '对话'
|
||||
})}
|
||||
/>,
|
||||
<Stack.Screen
|
||||
key='Webview'
|
||||
name='Webview'
|
||||
component={Webview}
|
||||
// options={({ route }) => ({
|
||||
// title: `${route.params.domain}`
|
||||
// })}
|
||||
/>
|
||||
]
|
||||
}
|
@ -22,10 +22,12 @@ export default function Timeline ({
|
||||
const [timelineReady, setTimelineReady] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (state.status === 'idle') {
|
||||
let mounted = true
|
||||
if (state.status === 'idle' && mounted) {
|
||||
dispatch(fetch({ page, hashtag, list, toot, account }))
|
||||
setTimelineReady(true)
|
||||
}
|
||||
return () => (mounted = false)
|
||||
}, [state, dispatch])
|
||||
|
||||
let content
|
||||
|
@ -6,10 +6,7 @@ import SegmentedControl from '@react-native-community/segmented-control'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
|
||||
import Timeline from './Timeline'
|
||||
import Account from 'src/stacks/Shared/Account'
|
||||
import Hashtag from 'src/stacks/Shared/Hashtag'
|
||||
import Toot from 'src/stacks/Shared/Toot'
|
||||
import Webview from 'src/stacks/Shared/Webview'
|
||||
import sharedScreens from 'src/stacks/Shared/sharedScreens'
|
||||
|
||||
const Stack = createNativeStackNavigator()
|
||||
|
||||
@ -93,36 +90,8 @@ export default function TimelinesCombined ({ name, content }) {
|
||||
/>
|
||||
)}
|
||||
</Stack.Screen>
|
||||
<Stack.Screen
|
||||
name='Account'
|
||||
component={Account}
|
||||
options={{
|
||||
headerTranslucent: true,
|
||||
headerStyle: { backgroundColor: 'rgba(255, 255, 255, 0)' },
|
||||
headerCenter: () => {}
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Hashtag'
|
||||
component={Hashtag}
|
||||
options={({ route }) => ({
|
||||
title: `#${decodeURIComponent(route.params.hashtag)}`
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Toot'
|
||||
component={Toot}
|
||||
options={() => ({
|
||||
title: '对话'
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Webview'
|
||||
component={Webview}
|
||||
// options={({ route }) => ({
|
||||
// title: `${route.params.domain}`
|
||||
// })}
|
||||
/>
|
||||
|
||||
{sharedScreens(Stack)}
|
||||
</Stack.Navigator>
|
||||
)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user