mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	Video working
This commit is contained in:
		| @@ -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> | ||||
|   ) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user