diff --git a/DrawerMenu.tsx b/DrawerMenu.tsx index f821365..aae3fec 100644 --- a/DrawerMenu.tsx +++ b/DrawerMenu.tsx @@ -5,9 +5,9 @@ import {FileSystem} from 'react-native-file-access'; import {Divider, IconButton, Menu} from 'react-native-paper'; import {DatabaseContext, DrawerParamList, SnackbarContext} from './App'; import ConfirmDialog from './ConfirmDialog'; -import {useWrite} from './file'; import {Plan} from './plan'; import Set from './set'; +import {write} from './write'; const setFields = 'id,name,reps,weight,created,unit'; const planFields = 'id,days,workouts'; @@ -18,7 +18,6 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { const db = useContext(DatabaseContext); const {toast} = useContext(SnackbarContext); const {reset} = useNavigation>(); - const {write} = useWrite(); const exportSets = useCallback(async () => { const [result] = await db.executeSql('SELECT * FROM sets'); @@ -34,7 +33,7 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { .join('\n'); console.log(`${DrawerMenu.name}.exportSets`, {length: sets.length}); await write('sets.csv', data); - }, [db, write]); + }, [db]); const exportPlans = useCallback(async () => { const [result] = await db.executeSql('SELECT * FROM plans'); @@ -45,7 +44,7 @@ export default function DrawerMenu({name}: {name: keyof DrawerParamList}) { .join('\n'); console.log(`${DrawerMenu.name}.exportPlans`, {length: sets.length}); await write('plans.csv', data); - }, [db, write]); + }, [db]); const download = useCallback(async () => { setShowMenu(false); diff --git a/android/app/src/main/java/com/massive/DownloadModule.kt b/android/app/src/main/java/com/massive/DownloadModule.kt new file mode 100644 index 0000000..0c9a606 --- /dev/null +++ b/android/app/src/main/java/com/massive/DownloadModule.kt @@ -0,0 +1,51 @@ +package com.massive + +import android.app.DownloadManager +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.NotificationManager.IMPORTANCE_DEFAULT +import android.app.PendingIntent +import android.app.PendingIntent.FLAG_IMMUTABLE +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat +import androidx.core.net.toUri +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import java.io.File + +class DownloadModule internal constructor(context: ReactApplicationContext) : + ReactContextBaseJavaModule(context) { + override fun getName(): String { + return "DownloadModule" + } + + @RequiresApi(Build.VERSION_CODES.O) + @ReactMethod + fun show(name: String) { + val channel = NotificationChannel(CHANNEL_ID, CHANNEL_ID, IMPORTANCE_DEFAULT) + channel.description = "Notifications for downloaded files." + val manager = + reactApplicationContext.getSystemService(NotificationManager::class.java) + manager.createNotificationChannel(channel) + val intent = Intent(DownloadManager.ACTION_VIEW_DOWNLOADS) + val pendingIntent = + PendingIntent.getActivity(reactApplicationContext, 0, intent, FLAG_IMMUTABLE) + val builder = NotificationCompat.Builder(reactApplicationContext, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_baseline_arrow_downward_24) + .setContentTitle("Downloaded $name") + .setContentIntent(pendingIntent) + .setAutoCancel(true) + manager.notify(NOTIFICATION_ID, builder.build()) + } + + companion object { + private const val CHANNEL_ID = "MassiveDownloads" + private const val NOTIFICATION_ID = 3 + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/massive/MassivePackage.kt b/android/app/src/main/java/com/massive/MassivePackage.kt index 25cbf82..6e123f5 100644 --- a/android/app/src/main/java/com/massive/MassivePackage.kt +++ b/android/app/src/main/java/com/massive/MassivePackage.kt @@ -17,6 +17,7 @@ class MassivePackage : ReactPackage { ): List { val modules: MutableList = ArrayList() modules.add(AlarmModule(reactContext)) + modules.add(DownloadModule(reactContext)) return modules } } diff --git a/file.ts b/file.ts deleted file mode 100644 index 87ae5ec..0000000 --- a/file.ts +++ /dev/null @@ -1,26 +0,0 @@ -import {useContext} from 'react'; -import {PermissionsAndroid} from 'react-native'; -import {Dirs, FileSystem} from 'react-native-file-access'; -import {SnackbarContext} from './App'; - -export const useWrite = () => { - const {toast} = useContext(SnackbarContext); - - return { - write: async (name: string, data: string) => { - const filePath = `${Dirs.DocumentDir}/${name}`; - const permission = async () => { - const granted = await PermissionsAndroid.request( - PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, - ); - return granted === PermissionsAndroid.RESULTS.GRANTED; - }; - const granted = await permission(); - if (!granted) return; - await FileSystem.writeFile(filePath, data); - if (!FileSystem.exists(filePath)) return; - await FileSystem.cpExternal(filePath, name, 'downloads'); - toast(`Saved "${name}" in your downloads folder.`, 6000); - }, - }; -}; diff --git a/write.ts b/write.ts new file mode 100644 index 0000000..679acf7 --- /dev/null +++ b/write.ts @@ -0,0 +1,18 @@ +import {NativeModules, PermissionsAndroid} from 'react-native'; +import {Dirs, FileSystem} from 'react-native-file-access'; + +export const write = async (name: string, data: string) => { + const filePath = `${Dirs.DocumentDir}/${name}`; + const permission = async () => { + const granted = await PermissionsAndroid.request( + PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, + ); + return granted === PermissionsAndroid.RESULTS.GRANTED; + }; + const granted = await permission(); + if (!granted) return; + await FileSystem.writeFile(filePath, data); + if (!FileSystem.exists(filePath)) return; + await FileSystem.cpExternal(filePath, name, 'downloads'); + NativeModules.DownloadModule.show(name); +};