From a6b6e519a1116e759b275f754c67dc1d6c462719 Mon Sep 17 00:00:00 2001 From: David Prevost Date: Sat, 19 Oct 2024 22:21:04 -0400 Subject: [PATCH 1/5] feat: keep recording on reboot + allow to delete them --- example/src/App.tsx | 43 ++++++++++++++++++++++++++++ example/src/assets/icons/delete.png | Bin 0 -> 7907 bytes example/src/assets/icons/index.ts | 1 + example/src/constants/Audios.ts | 20 +++++++++++-- example/src/styles.ts | 3 +- 5 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 example/src/assets/icons/delete.png diff --git a/example/src/App.tsx b/example/src/App.tsx index 2369424..ce9db4e 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -18,6 +18,7 @@ import React, { } from 'react'; import { ActivityIndicator, + Alert, Image, Linking, Pressable, @@ -35,11 +36,13 @@ import { Icons } from './assets'; import { generateAudioList, playbackSpeedSequence, + getRecordedAudios, type ListItem, } from './constants'; import stylesheet from './styles'; import { Colors } from './theme'; import FastImage from 'react-native-fast-image'; +import fs from 'react-native-fs'; const RenderListItem = React.memo( ({ @@ -248,6 +251,37 @@ const AppContainer = () => { ); }; + const handleDeleteRecordings = async () => { + const recordings = await getRecordedAudios(); + + const deleteRecordings = async () => { + await Promise.all(recordings.map(async recording => fs.unlink(recording))) + .then(() => { + Alert.alert( + 'All recording deleted', + 'All recordings have been deleted successfully! Reboot the app to see the changes.', + [{ text: 'Dismiss' }] + ); + }) + .catch(error => { + Alert.alert( + 'Error deleting recordings', + 'Below error happened while deleting recordings:\n' + error, + [{ text: 'Dismiss' }] + ); + }); + }; + + Alert.alert( + 'Delete all recording', + `Continue to delete all ${recordings.length} recordings.\n App restart is required!`, + [ + { text: 'Cancel', style: 'cancel' }, + { text: 'OK', onPress: deleteRecordings }, + ] + ); + }; + return ( { style={styles.simformImage} resizeMode="contain" /> + + + {list.map(item => ( diff --git a/example/src/assets/icons/delete.png b/example/src/assets/icons/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..68a0eb2eb64cc079be9ba52f858a9c4eddb41cc5 GIT binary patch literal 7907 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 RiKK4;pk4T#rQ3 => { + const recordingSavingPath = Platform.select({ ios: fs.DocumentDirectoryPath, default: fs.CachesDirectoryPath }) + + const items = await fs.readDir(recordingSavingPath) + return items.filter(item => item.path.endsWith('.m4a')).map(item => item.path) +} + /** * Generate a list of file objects with information about successfully copied files (Android) * or all files (iOS). * @returns {Promise} A Promise that resolves to the list of file objects. */ export const generateAudioList = async (): Promise => { - const audioAssets = await copyFilesToNativeResources(); + const audioAssetPaths = (await copyFilesToNativeResources()).map(value => `${filePath}/${value}`); + const recordedAudios = await getRecordedAudios() // Generate the final list based on the copied or available files - return audioAssets?.map?.((value, index) => ({ + return [...audioAssetPaths, ...recordedAudios].map?.((value, index) => ({ fromCurrentUser: index % 2 !== 0, - path: `${filePath}/${value}`, + path: value, })); + }; diff --git a/example/src/styles.ts b/example/src/styles.ts index 088fbfc..6797ff1 100644 --- a/example/src/styles.ts +++ b/example/src/styles.ts @@ -92,7 +92,8 @@ const styles = (params: StyleSheetParams = {}) => }, simformImageContainer: { alignItems: 'center', - justifyContent: 'center', + justifyContent: 'space-around', + flexDirection: 'row', }, loadingText: { color: Colors.black, From 0bfe0c476299a5a3f32a706e0c32196ff90abb5f Mon Sep 17 00:00:00 2001 From: David Prevost Date: Tue, 22 Oct 2024 22:27:33 -0400 Subject: [PATCH 2/5] fix: refresh the list instead of needing to reboot --- example/src/App.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index ce9db4e..893f6db 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -257,11 +257,9 @@ const AppContainer = () => { const deleteRecordings = async () => { await Promise.all(recordings.map(async recording => fs.unlink(recording))) .then(() => { - Alert.alert( - 'All recording deleted', - 'All recordings have been deleted successfully! Reboot the app to see the changes.', - [{ text: 'Dismiss' }] - ); + generateAudioList().then(audioListArray => { + setList(audioListArray); + }); }) .catch(error => { Alert.alert( From 2c3648a021b6c7da8ff5848218ed1082425cbb06 Mon Sep 17 00:00:00 2001 From: David Prevost Date: Tue, 22 Oct 2024 22:57:44 -0400 Subject: [PATCH 3/5] fix: disable delete button when no recording --- example/src/App.tsx | 20 +++++++++++++++++--- example/src/styles.ts | 12 ++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index 893f6db..4ac39a8 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -227,6 +227,7 @@ const AppContainer = () => { const [shouldScroll, setShouldScroll] = useState(true); const [currentPlaying, setCurrentPlaying] = useState(''); const [list, setList] = useState([]); + const [nbOfRecording, setNumberOfRecording] = useState(0); const [currentPlaybackSpeed, setCurrentPlaybackSpeed] = useState(1.0); @@ -241,6 +242,12 @@ const AppContainer = () => { }); }, []); + useEffect(() => { + getRecordedAudios().then(recordedAudios => + setNumberOfRecording(recordedAudios.length) + ); + }, [list]); + const changeSpeed = () => { setCurrentPlaybackSpeed( prev => @@ -291,20 +298,27 @@ const AppContainer = () => { - + + style={[ + styles.deleteRecordingContainer, + { opacity: nbOfRecording ? 1 : 0.5 }, + ]} + onPress={handleDeleteRecordings} + disabled={!nbOfRecording}> + + {'Delete recorded audio files'} + diff --git a/example/src/styles.ts b/example/src/styles.ts index 6797ff1..edca6b6 100644 --- a/example/src/styles.ts +++ b/example/src/styles.ts @@ -90,11 +90,19 @@ const styles = (params: StyleSheetParams = {}) => width: '100%', tintColor: Colors.pink, }, - simformImageContainer: { + headerContainer: { + alignItems: 'center', + }, + deleteRecordingContainer: { alignItems: 'center', - justifyContent: 'space-around', flexDirection: 'row', }, + deleteRecordingTitle: { + fontSize: scale(20), + fontWeight: 'bold', + color: Colors.pink, + paddingLeft: scale(8), + }, loadingText: { color: Colors.black, }, From 23365d5e357c890c5aec1700c76b98f4a7e4cb52 Mon Sep 17 00:00:00 2001 From: David Prevost <77302423+dprevost-LMI@users.noreply.github.com> Date: Wed, 23 Oct 2024 07:24:03 -0400 Subject: [PATCH 4/5] Update example/src/App.tsx Co-authored-by: kuldip-simform --- example/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index 4ac39a8..22194ce 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -279,7 +279,7 @@ const AppContainer = () => { Alert.alert( 'Delete all recording', - `Continue to delete all ${recordings.length} recordings.\n App restart is required!`, + `Continue to delete all ${recordings.length} recordings.`, [ { text: 'Cancel', style: 'cancel' }, { text: 'OK', onPress: deleteRecordings }, From c164249bcbbc5d63a4861559f7eb24057039c0e9 Mon Sep 17 00:00:00 2001 From: David Prevost Date: Wed, 23 Oct 2024 07:34:30 -0400 Subject: [PATCH 5/5] fix: using pink `tintColor` so it also work on light theme --- example/src/App.tsx | 2 +- example/src/styles.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index 22194ce..fb85122 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -313,7 +313,7 @@ const AppContainer = () => { disabled={!nbOfRecording}> diff --git a/example/src/styles.ts b/example/src/styles.ts index edca6b6..8cb9eb3 100644 --- a/example/src/styles.ts +++ b/example/src/styles.ts @@ -54,6 +54,12 @@ const styles = (params: StyleSheetParams = {}) => tintColor: Colors.white, alignSelf: 'flex-end', }, + pinkButtonImage: { + height: scale(22), + width: scale(22), + tintColor: Colors.pink, + alignSelf: 'flex-end', + }, staticWaveformView: { flex: 1, height: scale(75),