From f2919bface428418c9250dce0de5e8d56bd67bd1 Mon Sep 17 00:00:00 2001 From: balzack Date: Sat, 12 Aug 2023 16:31:03 -0700 Subject: [PATCH] adding generic file attachment to mobile app --- .../conversation/topicItem/TopicItem.jsx | 10 ++- .../topicItem/binaryAsset/BinaryAsset.jsx | 55 +++++++++++++ .../binaryAsset/BinaryAsset.styled.js | 53 +++++++++++++ .../binaryAsset/useBinaryAsset.hook.js | 79 +++++++++++++++++++ .../topicItem/binaryThumb/BinaryThumb.jsx | 20 +++++ .../binaryThumb/BinaryThumb.styled.js | 29 +++++++ .../topicItem/useTopicItem.hook.js | 10 ++- 7 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 app/mobile/src/session/conversation/topicItem/binaryAsset/BinaryAsset.jsx create mode 100644 app/mobile/src/session/conversation/topicItem/binaryAsset/BinaryAsset.styled.js create mode 100644 app/mobile/src/session/conversation/topicItem/binaryAsset/useBinaryAsset.hook.js create mode 100644 app/mobile/src/session/conversation/topicItem/binaryThumb/BinaryThumb.jsx create mode 100644 app/mobile/src/session/conversation/topicItem/binaryThumb/BinaryThumb.styled.js diff --git a/app/mobile/src/session/conversation/topicItem/TopicItem.jsx b/app/mobile/src/session/conversation/topicItem/TopicItem.jsx index c85c709b..c50a672b 100644 --- a/app/mobile/src/session/conversation/topicItem/TopicItem.jsx +++ b/app/mobile/src/session/conversation/topicItem/TopicItem.jsx @@ -9,9 +9,11 @@ import avatar from 'images/avatar.png'; import { VideoThumb } from './videoThumb/VideoThumb'; import { AudioThumb } from './audioThumb/AudioThumb'; import { ImageThumb } from './imageThumb/ImageThumb'; +import { BinaryThumb } from './binaryThumb/BinaryThumb'; import { ImageAsset } from './imageAsset/ImageAsset'; import { AudioAsset } from './audioAsset/AudioAsset'; import { VideoAsset } from './videoAsset/VideoAsset'; +import { BinaryAsset } from './binaryAsset/BinaryAsset'; import Carousel from 'react-native-reanimated-carousel'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { SafeAreaView, SafeAreaProvider } from 'react-native-safe-area-context'; @@ -120,7 +122,10 @@ export function TopicItem({ item, focused, focus, hosting, remove, update, block actions.showCarousel(thumb.index)} /> )} { thumb.item.type === 'audio' && ( - actions.showCarousel(thumb.index)} /> + actions.showCarousel(thumb.index)} /> + )} + { thumb.item.type === 'binary' && ( + actions.showCarousel(thumb.index)} /> )} ); @@ -234,6 +239,9 @@ export function TopicItem({ item, focused, focus, hosting, remove, update, block { state.assets[index].type === 'audio' && ( )} + { state.assets[index].type === 'binary' && ( + + )} )} /> diff --git a/app/mobile/src/session/conversation/topicItem/binaryAsset/BinaryAsset.jsx b/app/mobile/src/session/conversation/topicItem/binaryAsset/BinaryAsset.jsx new file mode 100644 index 00000000..fcb1d4a9 --- /dev/null +++ b/app/mobile/src/session/conversation/topicItem/binaryAsset/BinaryAsset.jsx @@ -0,0 +1,55 @@ +import { ActivityIndicator, Alert, View, Text, TouchableOpacity } from 'react-native'; +import { useEffect, useRef } from 'react'; +import Colors from 'constants/Colors'; +import { useBinaryAsset } from './useBinaryAsset.hook'; +import { styles } from './BinaryAsset.styled'; +import MatIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import AntIcons from 'react-native-vector-icons/AntDesign'; + +export function BinaryAsset({ asset, dismiss }) { + + const { state, actions } = useBinaryAsset(); + + + const download = async () => { + try { + const url = asset.encrypted ? `file://${asset.decrypted}` : asset.data; + await actions.download(asset.label, asset.extension, url); + } + catch (err) { + Alert.alert( + 'Download Failed', + 'Please try again.' + ) + } + }; + + return ( + + { asset.label } + + + + + { asset.encrypted && !asset.decrypted && ( + + + { asset.total > 1 && ( + { asset.block } / { asset.total } + )} + + )} + { !state.downloading && (!asset.encrypted || asset.decrypted) && ( + + + + )} + { state.downloading && ( + + )} + + { asset.extension } + + ); +} + diff --git a/app/mobile/src/session/conversation/topicItem/binaryAsset/BinaryAsset.styled.js b/app/mobile/src/session/conversation/topicItem/binaryAsset/BinaryAsset.styled.js new file mode 100644 index 00000000..8cc7b43a --- /dev/null +++ b/app/mobile/src/session/conversation/topicItem/binaryAsset/BinaryAsset.styled.js @@ -0,0 +1,53 @@ +import { StyleSheet } from 'react-native'; +import { Colors } from 'constants/Colors'; + +export const styles = StyleSheet.create({ + container: { + position: 'relative', + borderRadius: 8, + backgroundColor: Colors.grey, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + height: '100%', + }, + label: { + textAlign: 'center', + fontSize: 20, + paddingTop: 8, + paddingLeft: 48, + paddingRight: 48, + color: Colors.white, + }, + action: { + display: 'flex', + flexGrow: 1, + alignItems: 'center', + justifyContent: 'center', + }, + extension: { + textAlign: 'center', + fontSize: 48, + paddingBottom: 8, + paddingLeft: 48, + paddingRight: 48, + color: Colors.white, + }, + close: { + position: 'absolute', + top: 0, + right: 0, + paddingTop: 4, + paddingBottom: 4, + paddingLeft: 8, + paddingRight: 8, + }, + decrypting: { + fontVariant: ["tabular-nums"], + paddingTop: 16, + fontSize: 12, + color: '#888888', + }, +}) + diff --git a/app/mobile/src/session/conversation/topicItem/binaryAsset/useBinaryAsset.hook.js b/app/mobile/src/session/conversation/topicItem/binaryAsset/useBinaryAsset.hook.js new file mode 100644 index 00000000..bb2a38bf --- /dev/null +++ b/app/mobile/src/session/conversation/topicItem/binaryAsset/useBinaryAsset.hook.js @@ -0,0 +1,79 @@ +import { useState, useRef, useEffect, useContext } from 'react'; +import { ConversationContext } from 'context/ConversationContext'; +import { Image } from 'react-native'; +import { useWindowDimensions } from 'react-native'; +import { Platform } from 'react-native'; +import RNFetchBlob from "rn-fetch-blob"; +import Share from 'react-native-share'; + +export function useBinaryAsset() { + + const [state, setState] = useState({ + width: 1, + height: 1, + downloading: false, + }); + + const dimensions = useWindowDimensions(); + + const updateState = (value) => { + setState((s) => ({ ...s, ...value })); + } + + useEffect(() => { + const { width, height } = dimensions; + if (width < height) { + updateState({ width, height: width }); + } + else { + updateState({ widht: height, height }); + } + }, [dimensions]); + + const actions = { + download: async (label, extension, url) => { + if (!state.downloading) { + try { + updateState({ downloading: true }); + + const blob = await RNFetchBlob.config({ fileCache: true }).fetch("GET", url); + const src = blob.path(); + if (Platform.OS === 'ios') { + const path = `${RNFetchBlob.fs.dirs.DocumentDir}` + const dst = `${path}/${label}.${extension.toLowerCase()}` + if (RNFetchBlob.fs.exists(dst)) { + RNFetchBlob.fs.unlink(dst); + } + await RNFetchBlob.fs.mv(src, dst); + try { + await Share.open({ url: dst, message: `${label}.${extension}`, subject: `${label}.${extension}` }) + } + catch (err) { + console.log(err); + } + RNFetchBlob.fs.unlink(dst); + } + else { + const path = `${RNFetchBlob.fs.dirs.DownloadDir}` + const dst = `${path}/${label}.${extension.toLowerCase()}` + if (RNFetchBlob.fs.exists(dst)) { + RNFetchBlob.fs.unlink(dst); + } + await RNFetchBlob.fs.mv(src, dst); + RNFetchBlob.fs.unlink(dst); + } + + updateState({ downloading: false }); + } + catch (err) { + console.log(err); + updateState({ downloading: false }); + throw new Error('download failed'); + } + } + } + }; + + return { state, actions }; +} + diff --git a/app/mobile/src/session/conversation/topicItem/binaryThumb/BinaryThumb.jsx b/app/mobile/src/session/conversation/topicItem/binaryThumb/BinaryThumb.jsx new file mode 100644 index 00000000..f74fadc2 --- /dev/null +++ b/app/mobile/src/session/conversation/topicItem/binaryThumb/BinaryThumb.jsx @@ -0,0 +1,20 @@ +import { View, Text, Image } from 'react-native'; +import { TouchableOpacity } from 'react-native-gesture-handler'; +import { styles } from './BinaryThumb.styled'; +import Colors from 'constants/Colors'; +import AntIcons from 'react-native-vector-icons/AntDesign'; + +export function BinaryThumb({ label, extension, onAssetView }) { + + return ( + + { label } + + + + { extension } + + ); + +} + diff --git a/app/mobile/src/session/conversation/topicItem/binaryThumb/BinaryThumb.styled.js b/app/mobile/src/session/conversation/topicItem/binaryThumb/BinaryThumb.styled.js new file mode 100644 index 00000000..d5827429 --- /dev/null +++ b/app/mobile/src/session/conversation/topicItem/binaryThumb/BinaryThumb.styled.js @@ -0,0 +1,29 @@ +import { StyleSheet } from 'react-native'; +import { Colors } from 'constants/Colors'; + +export const styles = StyleSheet.create({ + canvas: { + borderRadius: 4, + width: 92, + height: 92, + marginRight: 16, + backgroundColor: Colors.grey, + display: 'flex', + alignItems: 'center', + }, + action: { + flexGrow: 1, + }, + label: { + textAlign: 'center', + color: Colors.white, + padding: 4, + fontSize: 14, + }, + extension: { + textAlign: 'center', + color: Colors.white, + fontSize: 20, + } +}) + diff --git a/app/mobile/src/session/conversation/topicItem/useTopicItem.hook.js b/app/mobile/src/session/conversation/topicItem/useTopicItem.hook.js index 52c53863..e3e3c8fe 100644 --- a/app/mobile/src/session/conversation/topicItem/useTopicItem.hook.js +++ b/app/mobile/src/session/conversation/topicItem/useTopicItem.hook.js @@ -61,8 +61,8 @@ export function useTopicItem(item, hosting, remove, contentKey) { const asset = parsed[i]; if (asset.encrypted) { const encrypted = true; - const { type, thumb, label, parts } = asset.encrypted; - assets.push({ type, thumb, label, encrypted, decrypted: null, parts }); + const { type, thumb, label, extension, parts } = asset.encrypted; + assets.push({ type, thumb, label, extension, encrypted, decrypted: null, parts }); } else { const encrypted = false @@ -85,6 +85,12 @@ export function useTopicItem(item, hosting, remove, contentKey) { const full = conversation.actions.getTopicAssetUrl(item.topicId, asset.audio.full); assets.push({ type, label, encrypted, full }); } + else if (asset.binary) { + const type = 'binary'; + const { label, extension } = asset.binary; + const data = conversation.actions.getTopicAssetUrl(item.topicId, asset.binary.data); + assets.push({ type, label, extension, data }); + } } }; }