Use react-native libraries for Export and Import
This commit is contained in:
parent
d520bd84ae
commit
7b0929bef4
|
@ -1,9 +1,18 @@
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import React, {useContext, useEffect, useState} from 'react';
|
import React, {useCallback, useContext, useEffect, useState} from 'react';
|
||||||
import {NativeModules, StyleSheet, Text, View} from 'react-native';
|
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 {Button, Snackbar, Switch, TextInput} from 'react-native-paper';
|
||||||
import {DatabaseContext} from './App';
|
import {DatabaseContext} from './App';
|
||||||
import BatteryDialog from './BatteryDialog';
|
import BatteryDialog from './BatteryDialog';
|
||||||
|
import Set from './set';
|
||||||
|
import DocumentPicker from 'react-native-document-picker';
|
||||||
|
|
||||||
const {getItem, setItem} = AsyncStorage;
|
const {getItem, setItem} = AsyncStorage;
|
||||||
|
|
||||||
|
@ -14,38 +23,91 @@ export default function SettingsPage() {
|
||||||
const [snackbar, setSnackbar] = useState('');
|
const [snackbar, setSnackbar] = useState('');
|
||||||
const [showBattery, setShowBattery] = useState(false);
|
const [showBattery, setShowBattery] = useState(false);
|
||||||
const [ignoring, setIgnoring] = useState(false);
|
const [ignoring, setIgnoring] = useState(false);
|
||||||
|
const [timeoutId, setTimeoutId] = useState(0);
|
||||||
const db = useContext(DatabaseContext);
|
const db = useContext(DatabaseContext);
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = useCallback(async () => {
|
||||||
setMinutes((await getItem('minutes')) || '');
|
setMinutes((await getItem('minutes')) || '');
|
||||||
setSeconds((await getItem('seconds')) || '');
|
setSeconds((await getItem('seconds')) || '');
|
||||||
setAlarmEnabled((await getItem('alarmEnabled')) === 'true');
|
setAlarmEnabled((await getItem('alarmEnabled')) === 'true');
|
||||||
NativeModules.AlarmModule.ignoringBatteryOptimizations(setIgnoring);
|
NativeModules.AlarmModule.ignoringBatteryOptimizations(setIgnoring);
|
||||||
};
|
}, [setIgnoring]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refresh();
|
refresh();
|
||||||
}, []);
|
}, [refresh]);
|
||||||
|
|
||||||
const clear = async () => {
|
const toast = useCallback(
|
||||||
setSnackbar('Deleting all data...');
|
(message: string) => {
|
||||||
setTimeout(() => setSnackbar(''), 5000);
|
setSnackbar(message);
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
setTimeoutId(setTimeout(() => setSnackbar(''), 3000));
|
||||||
|
},
|
||||||
|
[setSnackbar, timeoutId, setTimeoutId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const clear = useCallback(async () => {
|
||||||
await db.executeSql(`DELETE FROM sets`);
|
await db.executeSql(`DELETE FROM sets`);
|
||||||
|
toast('All data has been deleted!');
|
||||||
|
}, [db, toast]);
|
||||||
|
|
||||||
|
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 exportSets = () => {
|
const granted = await permission();
|
||||||
NativeModules.ExportModule.sets();
|
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 importSets = () => {
|
const importSets = useCallback(async () => {
|
||||||
NativeModules.ImportModule.sets();
|
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 = (enabled: boolean) => {
|
const changeAlarmEnabled = useCallback(
|
||||||
|
(enabled: boolean) => {
|
||||||
setAlarmEnabled(enabled);
|
setAlarmEnabled(enabled);
|
||||||
if (enabled && !ignoring) setShowBattery(true);
|
if (enabled && !ignoring) setShowBattery(true);
|
||||||
setItem('alarmEnabled', enabled ? 'true' : 'false');
|
setItem('alarmEnabled', enabled ? 'true' : 'false');
|
||||||
};
|
},
|
||||||
|
[alarmEnabled, setShowBattery],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,7 +17,6 @@ class MassivePackage : ReactPackage {
|
||||||
): List<NativeModule> {
|
): List<NativeModule> {
|
||||||
val modules: MutableList<NativeModule> = ArrayList()
|
val modules: MutableList<NativeModule> = ArrayList()
|
||||||
modules.add(AlarmModule(reactContext))
|
modules.add(AlarmModule(reactContext))
|
||||||
modules.add(ExportModule(reactContext))
|
|
||||||
modules.add(ImportModule(reactContext))
|
modules.add(ImportModule(reactContext))
|
||||||
return modules
|
return modules
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
"@react-navigation/native": "^6.0.10",
|
"@react-navigation/native": "^6.0.10",
|
||||||
"@react-navigation/native-stack": "^6.6.2",
|
"@react-navigation/native-stack": "^6.6.2",
|
||||||
"@types/d3-shape": "^3.1.0",
|
"@types/d3-shape": "^3.1.0",
|
||||||
|
"@types/react-native-base64": "^0.2.0",
|
||||||
"@types/react-native-sqlite-storage": "^5.0.2",
|
"@types/react-native-sqlite-storage": "^5.0.2",
|
||||||
"@types/react-native-svg-charts": "^5.0.12",
|
"@types/react-native-svg-charts": "^5.0.12",
|
||||||
"@types/react-native-vector-icons": "^6.4.11",
|
"@types/react-native-vector-icons": "^6.4.11",
|
||||||
|
@ -24,6 +25,9 @@
|
||||||
"react": "18.0.0",
|
"react": "18.0.0",
|
||||||
"react-devtools": "^4.24.7",
|
"react-devtools": "^4.24.7",
|
||||||
"react-native": "0.69.1",
|
"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-gesture-handler": "^2.5.0",
|
||||||
"react-native-linear-gradient": "^2.6.2",
|
"react-native-linear-gradient": "^2.6.2",
|
||||||
"react-native-modal-datetime-picker": "^13.1.2",
|
"react-native-modal-datetime-picker": "^13.1.2",
|
||||||
|
|
22
yarn.lock
22
yarn.lock
|
@ -1870,6 +1870,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
|
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
|
||||||
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
|
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":
|
"@types/react-native-sqlite-storage@^5.0.2":
|
||||||
version "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"
|
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"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
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:
|
react-native-codegen@^0.69.1:
|
||||||
version "0.69.1"
|
version "0.69.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-codegen/-/react-native-codegen-0.69.1.tgz#3632be2f24464e6fad8dd11a25d1b6f3bc2c7d0b"
|
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"
|
jscodeshift "^0.13.1"
|
||||||
nullthrows "^1.1.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:
|
react-native-gesture-handler@^2.5.0:
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.5.0.tgz#61385583570ed0a45a9ed142425e35f8fe8274fb"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user