diff --git a/app/mobile/ios/Podfile.lock b/app/mobile/ios/Podfile.lock index e4f6a2d6..53838531 100644 --- a/app/mobile/ios/Podfile.lock +++ b/app/mobile/ios/Podfile.lock @@ -246,6 +246,11 @@ PODS: - ReactCommon/turbomodule/core - react-native-sqlite-storage (6.0.1): - React-Core + - react-native-video (5.2.1): + - React-Core + - react-native-video/Video (= 5.2.1) + - react-native-video/Video (5.2.1): + - React-Core - React-perflogger (0.69.5) - React-RCTActionSheet (0.69.5): - React-Core/RCTActionSheetHeaders (= 0.69.5) @@ -384,6 +389,7 @@ DEPENDENCIES: - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - react-native-sqlite-storage (from `../node_modules/react-native-sqlite-storage`) + - react-native-video (from `../node_modules/react-native-video`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) @@ -465,6 +471,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-safe-area-context" react-native-sqlite-storage: :path: "../node_modules/react-native-sqlite-storage" + react-native-video: + :path: "../node_modules/react-native-video" React-perflogger: :path: "../node_modules/react-native/ReactCommon/reactperflogger" React-RCTActionSheet: @@ -529,6 +537,7 @@ SPEC CHECKSUMS: React-logger: 15c734997c06fe9c9b88e528fb7757601e7a56df react-native-safe-area-context: b456e1c40ec86f5593d58b275bd0e9603169daca react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261 + react-native-video: c26780b224543c62d5e1b2a7244a5cd1b50e8253 React-perflogger: 367418425c5e4a9f0f80385ee1eaacd2a7348f8e React-RCTActionSheet: e4885e7136f98ded1137cd3daccc05eaed97d5a6 React-RCTAnimation: 7c5a74f301c9b763343ba98a3dd776ed2676993f diff --git a/app/mobile/package.json b/app/mobile/package.json index ba60b4be..81af89f3 100644 --- a/app/mobile/package.json +++ b/app/mobile/package.json @@ -27,6 +27,7 @@ "react-native-safe-area-context": "^4.3.3", "react-native-safe-area-view": "^1.1.1", "react-native-sqlite-storage": "^6.0.1", + "react-native-video": "^5.2.1", "react-native-web": "~0.18.7", "react-router-dom": "6", "react-router-native": "^6.3.0" diff --git a/app/mobile/src/session/conversation/addTopic/AddTopic.jsx b/app/mobile/src/session/conversation/addTopic/AddTopic.jsx index 7cc24200..8ec89f9d 100644 --- a/app/mobile/src/session/conversation/addTopic/AddTopic.jsx +++ b/app/mobile/src/session/conversation/addTopic/AddTopic.jsx @@ -7,6 +7,8 @@ import MaterialIcons from '@expo/vector-icons/MaterialCommunityIcons'; import Colors from 'constants/Colors'; import { SafeAreaView } from 'react-native-safe-area-context'; import ImagePicker from 'react-native-image-crop-picker' +import { VideoFile } from './videoFile/VideoFile'; +import { ImageFile } from './imageFile/ImageFile'; export function AddTopic() { @@ -14,7 +16,7 @@ export function AddTopic() { const addImage = async () => { try { - const full = await ImagePicker.openPicker({ mediaType: 'photo', includeBase64: true }); + const full = await ImagePicker.openPicker({ mediaType: 'photo' }); actions.addImage(full.path); } catch (err) { @@ -22,6 +24,16 @@ export function AddTopic() { } } + const addVideo = async () => { + try { + const full = await ImagePicker.openPicker({ mediaType: 'video' }); + actions.addVideo(full.path); + } + catch (err) { + console.log(err); + } + } + const remove = (item) => { Alert.alert( `Removing ${item.type} from message.`, @@ -40,11 +52,17 @@ export function AddTopic() { const renderAsset = ({ item }) => { if (item.type === 'image') { return ( - remove(item)}> - - + remove(item)} /> ); } + if (item.type === 'video') { + return ( + remove(item)} + setPosition={(position) => actions.setVideoPosition(item.key, position)} + /> + ) + } else { return ( @@ -67,7 +85,7 @@ export function AddTopic() { - + diff --git a/app/mobile/src/session/conversation/addTopic/AddTopic.styled.js b/app/mobile/src/session/conversation/addTopic/AddTopic.styled.js index 5ce2051c..6c4c7d82 100644 --- a/app/mobile/src/session/conversation/addTopic/AddTopic.styled.js +++ b/app/mobile/src/session/conversation/addTopic/AddTopic.styled.js @@ -60,8 +60,8 @@ export const styles = StyleSheet.create({ }, carousel: { paddingTop: 8, - paddingLeft: 16, paddingRight: 16, + paddingLeft: 16, }, }) diff --git a/app/mobile/src/session/conversation/addTopic/imageFile/ImageFile.jsx b/app/mobile/src/session/conversation/addTopic/imageFile/ImageFile.jsx new file mode 100644 index 00000000..e1320c88 --- /dev/null +++ b/app/mobile/src/session/conversation/addTopic/imageFile/ImageFile.jsx @@ -0,0 +1,24 @@ +import { useRef, useEffect } from 'react'; +import { TouchableOpacity, View, Image } from 'react-native'; +import { useImageFile } from './useImageFile.hook'; +import { styles } from './ImageFile.styled'; +import Icons from '@expo/vector-icons/AntDesign'; +import Colors from 'constants/Colors'; + +export function ImageFile({ path, setPosition, remove }) { + + const { state, actions } = useImageFile(); + + useEffect(() => { + Image.getSize(path, actions.setInfo); + }, [path]); + + return ( + + + + + + + ); +} diff --git a/app/mobile/src/session/conversation/addTopic/imageFile/ImageFile.styled.js b/app/mobile/src/session/conversation/addTopic/imageFile/ImageFile.styled.js new file mode 100644 index 00000000..9c879c72 --- /dev/null +++ b/app/mobile/src/session/conversation/addTopic/imageFile/ImageFile.styled.js @@ -0,0 +1,17 @@ +import { StyleSheet } from 'react-native'; +import { Colors } from 'constants/Colors'; + +export const styles = StyleSheet.create({ + overlay: { + marginRight: 16, + position: 'absolute', + bottom: 0, + right: 0, + padding: 2, + borderTopLeftRadius: 4, + backgroundColor: Colors.white, + borderWidth: 1, + borderColor: Colors.divider, + }, +}) + diff --git a/app/mobile/src/session/conversation/addTopic/imageFile/useImageFile.hook.js b/app/mobile/src/session/conversation/addTopic/imageFile/useImageFile.hook.js new file mode 100644 index 00000000..c82d5c5c --- /dev/null +++ b/app/mobile/src/session/conversation/addTopic/imageFile/useImageFile.hook.js @@ -0,0 +1,22 @@ +import { useState, useRef, useEffect, useContext } from 'react'; +import { ConversationContext } from 'context/ConversationContext'; + +export function useImageFile() { + + const [state, setState] = useState({ + ratio: 1, + }); + + const updateState = (value) => { + setState((s) => ({ ...s, ...value })); + } + + const actions = { + setInfo: (width, height) => { + updateState({ ratio: width / height }); + }, + }; + + return { state, actions }; +} + diff --git a/app/mobile/src/session/conversation/addTopic/useAddTopic.hook.js b/app/mobile/src/session/conversation/addTopic/useAddTopic.hook.js index 459559bf..49f51236 100644 --- a/app/mobile/src/session/conversation/addTopic/useAddTopic.hook.js +++ b/app/mobile/src/session/conversation/addTopic/useAddTopic.hook.js @@ -27,6 +27,38 @@ export function useAddTopic(cardId, channelId) { updateState({ assets: [ ...state.assets, asset ] }); }); }, + addVideo: (data) => { + assetId.current++; + const asset = { key: assetId.current, type: 'video', data: data, ratio: 1, duration: 0, position: 0, ref: null }; + updateState({ assets: [ ...state.assets, asset ] }); + }, + setVideoInfo: (key, width, height, duration) => { + updateState({ assets: state.assets.map((item) => { + if(item.key === key) { + return { ...item, ratio: width / height, duration }; + } + return item; + }) + }); + }, + setVideoRef: (key, ref) => { + updateState({ assets: state.assets.map((item) => { + if(item.key === key) { + return { ...item, ref }; + } + return item; + }) + }); + }, + setVideoPosition: (key, position) => { + updateState({ assets: state.assets.map((item) => { + if(item.key === key) { + return { ...item, position }; + } + return item; + }) + }); + }, removeAsset: (key) => { updateState({ assets: state.assets.filter(item => (item.key !== key))}); }, diff --git a/app/mobile/src/session/conversation/addTopic/videoFile/VideoFile.jsx b/app/mobile/src/session/conversation/addTopic/videoFile/VideoFile.jsx new file mode 100644 index 00000000..3cc7303b --- /dev/null +++ b/app/mobile/src/session/conversation/addTopic/videoFile/VideoFile.jsx @@ -0,0 +1,33 @@ +import { useRef, useEffect } from 'react'; +import { TouchableOpacity, View } from 'react-native'; +import Video from 'react-native-video'; +import { useVideoFile } from './useVideoFile.hook'; +import { styles } from './VideoFile.styled'; +import Icons from '@expo/vector-icons/MaterialCommunityIcons'; +import Colors from 'constants/Colors'; + +export function VideoFile({ path, setPosition, remove }) { + + const { state, actions } = useVideoFile(); + + const video = useRef(); + + useEffect(() => { + if (video.current) { + video.current.seek(state.position); + setPosition(state.position); + } + }, [state.position]); + + return ( + + + ); +} diff --git a/app/mobile/src/session/conversation/addTopic/videoFile/VideoFile.styled.js b/app/mobile/src/session/conversation/addTopic/videoFile/VideoFile.styled.js new file mode 100644 index 00000000..9c879c72 --- /dev/null +++ b/app/mobile/src/session/conversation/addTopic/videoFile/VideoFile.styled.js @@ -0,0 +1,17 @@ +import { StyleSheet } from 'react-native'; +import { Colors } from 'constants/Colors'; + +export const styles = StyleSheet.create({ + overlay: { + marginRight: 16, + position: 'absolute', + bottom: 0, + right: 0, + padding: 2, + borderTopLeftRadius: 4, + backgroundColor: Colors.white, + borderWidth: 1, + borderColor: Colors.divider, + }, +}) + diff --git a/app/mobile/src/session/conversation/addTopic/videoFile/useVideoFile.hook.js b/app/mobile/src/session/conversation/addTopic/videoFile/useVideoFile.hook.js new file mode 100644 index 00000000..1f0eb273 --- /dev/null +++ b/app/mobile/src/session/conversation/addTopic/videoFile/useVideoFile.hook.js @@ -0,0 +1,30 @@ +import { useState, useRef, useEffect, useContext } from 'react'; +import { ConversationContext } from 'context/ConversationContext'; + +export function useVideoFile() { + + const [state, setState] = useState({ + duration: 0, + position: 0, + ratio: 1, + }); + + const updateState = (value) => { + setState((s) => ({ ...s, ...value })); + } + + const actions = { + setInfo: (width, height, duration) => { + updateState({ ratio: width / height, duration: Math.floor(duration) }); + }, + setNextPosition: () => { + if (state.duration) { + const position = (state.position + 1) % state.duration; + updateState({ position }); + } + }, + }; + + return { state, actions }; +} + diff --git a/app/mobile/yarn.lock b/app/mobile/yarn.lock index 1872e022..50973132 100644 --- a/app/mobile/yarn.lock +++ b/app/mobile/yarn.lock @@ -1686,7 +1686,7 @@ resolved "https://registry.npmjs.org/@react-native/assets/-/assets-1.0.0.tgz" integrity sha512-KrwSpS1tKI70wuKl68DwJZYEvXktDHdZMG0k2AXD/rJVSlB23/X2CB2cutVR0HwNMJIal9HOUOBB2rVfa6UGtQ== -"@react-native/normalize-color@2.0.0", "@react-native/normalize-color@^2.0.0": +"@react-native/normalize-color@*", "@react-native/normalize-color@2.0.0", "@react-native/normalize-color@^2.0.0": version "2.0.0" resolved "https://registry.npmjs.org/@react-native/normalize-color/-/normalize-color-2.0.0.tgz" integrity sha512-Wip/xsc5lw8vsBlmY2MO/gFLp3MvuZ2baBZjDeTjjndMgM0h5sxz7AZR62RDPGgstp8Np7JzjvVqVT7tpFZqsw== @@ -2911,6 +2911,15 @@ depd@~1.1.2: resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== +deprecated-react-native-prop-types@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-2.3.0.tgz#c10c6ee75ff2b6de94bb127f142b814e6e08d9ab" + integrity sha512-pWD0voFtNYxrVqvBMYf5gq3NA2GCpfodS1yNynTPc93AYA/KEMGeWDqqeUB6R2Z9ZofVhks2aeJXiuQqKNpesA== + dependencies: + "@react-native/normalize-color" "*" + invariant "*" + prop-types "*" + destroy@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" @@ -2933,6 +2942,11 @@ electron-to-chromium@^1.4.202: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.242.tgz" integrity sha512-nPdgMWtjjWGCtreW/2adkrB2jyHjClo9PtVhR6rW+oxa4E4Wom642Tn+5LslHP3XPL5MCpkn5/UEY60EXylNeQ== +eme-encryption-scheme-polyfill@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/eme-encryption-scheme-polyfill/-/eme-encryption-scheme-polyfill-2.1.1.tgz#91c823ed584e8ec5a9f03a6a676def8f80c57a4c" + integrity sha512-njD17wcUrbqCj0ArpLu5zWXtaiupHb/2fIUQGdInf83GlI+Q6mmqaPGLdrke4savKAu15J/z1Tg/ivDgl14g0g== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" @@ -3783,7 +3797,7 @@ internal-ip@4.3.0: default-gateway "^4.2.0" ipaddr.js "^1.9.0" -invariant@^2.2.4: +invariant@*, invariant@^2.2.4: version "2.2.4" resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -4252,6 +4266,11 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +keymirror@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/keymirror/-/keymirror-0.1.1.tgz#918889ea13f8d0a42e7c557250eee713adc95c35" + integrity sha512-vIkZAFWoDijgQT/Nvl2AHCMmnegN2ehgTPYuyy2hWQkQSntI0S7ESYqdLkoSe1HyEBFHHkCgSIvVdSEiWwKvCg== + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz" @@ -5383,7 +5402,7 @@ prompts@^2.3.2, prompts@^2.4.0: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.7.2: +prop-types@*, prop-types@^15.7.2: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -5552,6 +5571,16 @@ react-native-sqlite-storage@^6.0.1: resolved "https://registry.npmjs.org/react-native-sqlite-storage/-/react-native-sqlite-storage-6.0.1.tgz" integrity sha512-1tDFjrint6X6qSYKf3gDyz+XB+X79jfiL6xTugKHPRtF0WvqMtVgdLuNqZunIXjNEvNtNVEbXaeZ6MsguFu00A== +react-native-video@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/react-native-video/-/react-native-video-5.2.1.tgz#a17e856759d7e17eee9cbd9df0d05ba22e88d457" + integrity sha512-aJlr9MeTuQ0LpZ4n+EC9RvhoKeiPbLtI2Rxy8u7zo/wzGevbRpWHSBj9xZ5YDBXnAVXzuqyNIkGhdw7bfdIBZw== + dependencies: + deprecated-react-native-prop-types "^2.2.0" + keymirror "^0.1.1" + prop-types "^15.7.2" + shaka-player "^2.5.9" + react-native-web@~0.18.7: version "0.18.9" resolved "https://registry.npmjs.org/react-native-web/-/react-native-web-0.18.9.tgz" @@ -6024,6 +6053,13 @@ setprototypeof@1.2.0: resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== +shaka-player@^2.5.9: + version "2.5.23" + resolved "https://registry.yarnpkg.com/shaka-player/-/shaka-player-2.5.23.tgz#db92d1c6cf2314f0180a2cec11b0e2f2560336f5" + integrity sha512-3MC9k0OXJGw8AZ4n/ZNCZS2yDxx+3as5KgH6Tx4Q5TRboTBBCu6dYPI5vp1DxKeyU12MBN1Zcbs7AKzXv2EnCg== + dependencies: + eme-encryption-scheme-polyfill "^2.0.1" + shallow-clone@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz"