Use react-native libraries for Export and Import

This commit is contained in:
Brandon Presley 2022-07-09 19:39:11 +12:00
parent d520bd84ae
commit 7b0929bef4
5 changed files with 109 additions and 119 deletions

View File

@ -1,9 +1,18 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import React, {useContext, useEffect, useState} from 'react';
import {NativeModules, StyleSheet, Text, View} from 'react-native';
import React, {useCallback, useContext, useEffect, useState} from 'react';
import {
NativeModules,
PermissionsAndroid,
StyleSheet,
Text,
View,
} from 'react-native';
import {Dirs, FileSystem} from 'react-native-file-access';
import {Button, Snackbar, Switch, TextInput} from 'react-native-paper';
import {DatabaseContext} from './App';
import BatteryDialog from './BatteryDialog';
import Set from './set';
import DocumentPicker from 'react-native-document-picker';
const {getItem, setItem} = AsyncStorage;
@ -14,38 +23,91 @@ export default function SettingsPage() {
const [snackbar, setSnackbar] = useState('');
const [showBattery, setShowBattery] = useState(false);
const [ignoring, setIgnoring] = useState(false);
const [timeoutId, setTimeoutId] = useState(0);
const db = useContext(DatabaseContext);
const refresh = async () => {
const refresh = useCallback(async () => {
setMinutes((await getItem('minutes')) || '');
setSeconds((await getItem('seconds')) || '');
setAlarmEnabled((await getItem('alarmEnabled')) === 'true');
NativeModules.AlarmModule.ignoringBatteryOptimizations(setIgnoring);
};
}, [setIgnoring]);
useEffect(() => {
refresh();
}, []);
}, [refresh]);
const clear = async () => {
setSnackbar('Deleting all data...');
setTimeout(() => setSnackbar(''), 5000);
const toast = useCallback(
(message: string) => {
setSnackbar(message);
clearTimeout(timeoutId);
setTimeoutId(setTimeout(() => setSnackbar(''), 3000));
},
[setSnackbar, timeoutId, setTimeoutId],
);
const clear = useCallback(async () => {
await db.executeSql(`DELETE FROM sets`);
};
toast('All data has been deleted!');
}, [db, toast]);
const exportSets = () => {
NativeModules.ExportModule.sets();
};
const exportSets = useCallback(async () => {
const fileName = 'sets.csv';
const filePath = `${Dirs.DocumentDir}/${fileName}`;
const [result] = await db.executeSql('SELECT * FROM sets');
if (result.rows.length === 0) return;
const sets: Set[] = result.rows.raw();
const data = ['id,name,reps,weight,created,unit']
.concat(
sets.map(
set =>
`${set.id},${set.name},${set.reps},${set.weight},${set.created},${set.unit}`,
),
)
.join('\n');
console.log('SettingsPage.exportSets', {length: sets.length});
const permission = async () => {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
);
return granted === PermissionsAndroid.RESULTS.GRANTED;
};
const importSets = () => {
NativeModules.ImportModule.sets();
};
const granted = await permission();
if (granted) {
await FileSystem.writeFile(filePath, data);
if (!FileSystem.exists(filePath)) return;
await FileSystem.cpExternal(filePath, fileName, 'downloads');
}
toast('Exported data. Check your downloads folder.');
}, [db, toast]);
const changeAlarmEnabled = (enabled: boolean) => {
setAlarmEnabled(enabled);
if (enabled && !ignoring) setShowBattery(true);
setItem('alarmEnabled', enabled ? 'true' : 'false');
};
const importSets = useCallback(async () => {
const result = await DocumentPicker.pickSingle();
const file = await FileSystem.readFile(result.uri);
console.log(`${SettingsPage.name}.${importSets.name}:`, file.length);
const values = file
.split('\n')
.slice(1)
.map(set => {
const cells = set.split(',');
return `('${cells[1]}',${cells[2]},${cells[3]},'${cells[4]}','${cells[5]}')`;
})
.join(',');
await db.executeSql(
`INSERT INTO sets(name,reps,weight,created,unit) VALUES ${values}`,
);
toast('Data imported.');
}, [db, toast]);
const changeAlarmEnabled = useCallback(
(enabled: boolean) => {
setAlarmEnabled(enabled);
if (enabled && !ignoring) setShowBattery(true);
setItem('alarmEnabled', enabled ? 'true' : 'false');
},
[alarmEnabled, setShowBattery],
);
return (
<View style={styles.container}>

View File

@ -1,97 +0,0 @@
package com.massive
import android.annotation.SuppressLint
import android.app.DownloadManager
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.DocumentsContract
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import java.io.File
import java.io.FileReader
import java.io.FileWriter
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
class ExportModule internal constructor(context: ReactApplicationContext?) :
ReactContextBaseJavaModule(context) {
override fun getName(): String {
return "ExportModule"
}
@RequiresApi(Build.VERSION_CODES.O)
@SuppressLint("Recycle", "Range")
@ReactMethod(isBlockingSynchronousMethod = true)
fun sets(): String {
Log.d("ExportModule", "Exporting sets...")
val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val current = LocalDateTime.now()
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val formatted = current.format(formatter)
val file = File(dir, "sets-$formatted.csv")
file.createNewFile()
val writer = FileWriter(file)
writer.appendLine("id,name,reps,weight,created,unit")
val db = MassiveHelper(reactApplicationContext).readableDatabase
db.use {
with(it.query("sets", null, null, null, null, null, null)) {
while (moveToNext()) {
val id = getInt(getColumnIndex("id"))
val name = getString(getColumnIndex("name"))
val reps = getInt(getColumnIndex("reps"))
val weight = getInt(getColumnIndex("weight"))
val created = getString(getColumnIndex("created"))
val unit = getString(getColumnIndex("unit"))
writer.appendLine("$id,$name,$reps,$weight,$created,$unit")
}
}
}
writer.flush()
writer.close()
sendNotification()
return file.path
}
@RequiresApi(Build.VERSION_CODES.M)
private fun sendNotification() {
val notificationManager = NotificationManagerCompat.from(reactApplicationContext)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val importance = NotificationManager.IMPORTANCE_LOW
val channel = NotificationChannel(
CHANNEL_ID,
CHANNEL_ID, importance)
channel.description = "Alarms for rest timings."
notificationManager.createNotificationChannel(channel)
}
val contentIntent = Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
val pendingContent =
PendingIntent.getActivity(
reactApplicationContext,
0,
contentIntent,
PendingIntent.FLAG_IMMUTABLE
)
val builder = NotificationCompat.Builder(reactApplicationContext, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_baseline_arrow_downward_24)
.setContentTitle("Downloaded sets")
.setContentIntent(pendingContent)
.setAutoCancel(true)
notificationManager.notify(NOTIFICATION_ID, builder.build())
}
companion object {
private const val CHANNEL_ID = "Exports"
private const val NOTIFICATION_ID = 2
}
}

View File

@ -17,8 +17,7 @@ class MassivePackage : ReactPackage {
): List<NativeModule> {
val modules: MutableList<NativeModule> = ArrayList()
modules.add(AlarmModule(reactContext))
modules.add(ExportModule(reactContext))
modules.add(ImportModule(reactContext))
return modules
}
}
}

View File

@ -17,6 +17,7 @@
"@react-navigation/native": "^6.0.10",
"@react-navigation/native-stack": "^6.6.2",
"@types/d3-shape": "^3.1.0",
"@types/react-native-base64": "^0.2.0",
"@types/react-native-sqlite-storage": "^5.0.2",
"@types/react-native-svg-charts": "^5.0.12",
"@types/react-native-vector-icons": "^6.4.11",
@ -24,6 +25,9 @@
"react": "18.0.0",
"react-devtools": "^4.24.7",
"react-native": "0.69.1",
"react-native-base64": "^0.2.1",
"react-native-document-picker": "^8.1.1",
"react-native-file-access": "^2.4.3",
"react-native-gesture-handler": "^2.5.0",
"react-native-linear-gradient": "^2.6.2",
"react-native-modal-datetime-picker": "^13.1.2",

View File

@ -1870,6 +1870,11 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
"@types/react-native-base64@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@types/react-native-base64/-/react-native-base64-0.2.0.tgz#c934e7c3cd549d4613bbfa7929a6845ab07a20af"
integrity sha512-5kUtS7Kf8Xl4zEwbqLITEuQReQTby4UOGfnsEeBFJEVmUfT+ygOv/Qmv0v6El0iV1eDhXS+/0i7CGR9d3/nRSA==
"@types/react-native-sqlite-storage@^5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@types/react-native-sqlite-storage/-/react-native-sqlite-storage-5.0.2.tgz#eefcc9ea6ff73043bb2945023fa8ee721683b61b"
@ -6841,6 +6846,11 @@ react-is@^16.13.0, react-is@^16.13.1, react-is@^16.7.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-native-base64@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/react-native-base64/-/react-native-base64-0.2.1.tgz#3d0e73a649c4c0129f7b7695d3912456aebae847"
integrity sha512-eHgt/MA8y5ZF0aHfZ1aTPcIkDWxza9AaEk4GcpIX+ZYfZ04RcaNahO+527KR7J44/mD3efYfM23O2C1N44ByWA==
react-native-codegen@^0.69.1:
version "0.69.1"
resolved "https://registry.yarnpkg.com/react-native-codegen/-/react-native-codegen-0.69.1.tgz#3632be2f24464e6fad8dd11a25d1b6f3bc2c7d0b"
@ -6851,6 +6861,18 @@ react-native-codegen@^0.69.1:
jscodeshift "^0.13.1"
nullthrows "^1.1.1"
react-native-document-picker@^8.1.1:
version "8.1.1"
resolved "https://registry.yarnpkg.com/react-native-document-picker/-/react-native-document-picker-8.1.1.tgz#642bbe25752cc428b96416318f8dc07cef29ee10"
integrity sha512-mH0oghd7ndgU9/1meVJdqts1sAkOfUQW1qbrqTTsvR5f2K9r0BAj/X02dve5IBMOMZvlGd7qWrNVuIFg5AUXWg==
dependencies:
invariant "^2.2.4"
react-native-file-access@^2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/react-native-file-access/-/react-native-file-access-2.4.3.tgz#83c632ee3e6a403662e7c92f10de0d88539c42ba"
integrity sha512-9f/z5dUSZgl1js+7jl43vkUrfProNuWo6rNRXV2AXdm1dckokYjeai/Mj6x+XMDyaBtRYztNKNqwZJhe5kHrNA==
react-native-gesture-handler@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.5.0.tgz#61385583570ed0a45a9ed142425e35f8fe8274fb"