Add sets exporting
This commit is contained in:
parent
76e6ffbc25
commit
3e09f38ef0
3
App.tsx
3
App.tsx
|
@ -6,7 +6,7 @@ import {
|
||||||
NavigationContainer,
|
NavigationContainer,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import React, {useEffect} from 'react';
|
import React, {useEffect} from 'react';
|
||||||
import {NativeModules, StatusBar, useColorScheme} from 'react-native';
|
import {StatusBar, useColorScheme} from 'react-native';
|
||||||
import {setupSchema} from './db';
|
import {setupSchema} from './db';
|
||||||
import Exercises from './Exercises';
|
import Exercises from './Exercises';
|
||||||
import Home from './Home';
|
import Home from './Home';
|
||||||
|
@ -28,7 +28,6 @@ const App = () => {
|
||||||
AsyncStorage.getItem('minutes').then(async minutes => {
|
AsyncStorage.getItem('minutes').then(async minutes => {
|
||||||
if (!minutes) await AsyncStorage.setItem('minutes', '3');
|
if (!minutes) await AsyncStorage.setItem('minutes', '3');
|
||||||
});
|
});
|
||||||
console.log(NativeModules.ExportModule.sets());
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
12
Settings.tsx
12
Settings.tsx
|
@ -1,7 +1,7 @@
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import {NativeStackScreenProps} from '@react-navigation/native-stack';
|
import {NativeStackScreenProps} from '@react-navigation/native-stack';
|
||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import {StyleSheet, Text, View} from 'react-native';
|
import {NativeModules, StyleSheet, Text, View} from 'react-native';
|
||||||
import {Button, Switch, TextInput} from 'react-native-paper';
|
import {Button, Switch, TextInput} from 'react-native-paper';
|
||||||
import {RootStackParamList} from './App';
|
import {RootStackParamList} from './App';
|
||||||
import {getDb} from './db';
|
import {getDb} from './db';
|
||||||
|
@ -32,6 +32,10 @@ export default function Settings({
|
||||||
await db.executeSql(`DELETE FROM sets`);
|
await db.executeSql(`DELETE FROM sets`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const exportSets = () => {
|
||||||
|
NativeModules.ExportModule.sets();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
@ -56,6 +60,12 @@ export default function Settings({
|
||||||
value={alarmEnabled}
|
value={alarmEnabled}
|
||||||
onValueChange={setAlarmEnabled}
|
onValueChange={setAlarmEnabled}
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
style={{alignSelf: 'flex-start'}}
|
||||||
|
icon="arrow-down"
|
||||||
|
onPress={exportSets}>
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
style={{alignSelf: 'flex-start', marginTop: 'auto'}}
|
style={{alignSelf: 'flex-start', marginTop: 'auto'}}
|
||||||
icon="trash"
|
icon="trash"
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".MainApplication"
|
android:name=".MainApplication"
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:exported="true" android:process=":remote" android:name=".AlarmActivity" />
|
<activity android:exported="true" android:process=":remote" android:name=".AlarmActivity" />
|
||||||
|
<activity android:exported="true" android:process=":remote" android:name=".ExportActivity" />
|
||||||
<service android:name=".StopTimer" android:exported="true" android:process=":remote" />
|
<service android:name=".StopTimer" android:exported="true" android:process=":remote" />
|
||||||
<service android:name=".AlarmService" android:exported="true" />
|
<service android:name=".AlarmService" android:exported="true" />
|
||||||
<service android:name=".TimerService" android:exported="true" />
|
<service android:name=".TimerService" android:exported="true" />
|
||||||
|
|
123
android/app/src/main/java/com/massive/ExportActivity.kt
Normal file
123
android/app/src/main/java/com/massive/ExportActivity.kt
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
package com.massive
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.database.Cursor
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.DocumentsContract
|
||||||
|
import android.provider.OpenableColumns
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import java.io.*
|
||||||
|
|
||||||
|
|
||||||
|
class ExportActivity : Activity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
Log.d("ExportActivity", "Started ExportActivity.")
|
||||||
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||||
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
type = "text/csv"
|
||||||
|
putExtra(Intent.EXTRA_TITLE, "sets.csv")
|
||||||
|
}
|
||||||
|
startActivityForResult(intent, CREATE_FILE, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("Range")
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
Log.d("ExportActivity", "Got activity result: requestCode=$requestCode,resultCode=$resultCode")
|
||||||
|
data?.data?.also { uri ->
|
||||||
|
contentResolver.openFileDescriptor(uri, "w")?.use { fd ->
|
||||||
|
FileWriter(fd.fileDescriptor).use { fw ->
|
||||||
|
Log.d("ExportActivity", "Got file writer: $fw")
|
||||||
|
fw.write("id,name,reps,weight,created,unit\n")
|
||||||
|
val db = MassiveHelper(applicationContext).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"))
|
||||||
|
fw.appendLine("$id,$name,$reps,$weight,$created,$unit\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fw.flush()
|
||||||
|
fw.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun getFile(context: Context, uri: Uri): File? {
|
||||||
|
val destinationFilename =
|
||||||
|
File(context.filesDir.path + File.separatorChar + queryName(context, uri))
|
||||||
|
try {
|
||||||
|
context.contentResolver.openInputStream(uri).use { ins ->
|
||||||
|
if (ins != null) {
|
||||||
|
createFileFromStream(
|
||||||
|
ins,
|
||||||
|
destinationFilename
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
Log.e("Save File", ex.message!!)
|
||||||
|
ex.printStackTrace()
|
||||||
|
}
|
||||||
|
return destinationFilename
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createFileFromStream(ins: InputStream, destination: File?) {
|
||||||
|
try {
|
||||||
|
FileOutputStream(destination).use { os ->
|
||||||
|
val buffer = ByteArray(4096)
|
||||||
|
var length: Int
|
||||||
|
while (ins.read(buffer).also { length = it } > 0) {
|
||||||
|
os.write(buffer, 0, length)
|
||||||
|
}
|
||||||
|
os.flush()
|
||||||
|
}
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
Log.e("Save File", ex.message!!)
|
||||||
|
ex.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun queryName(context: Context, uri: Uri): String {
|
||||||
|
val returnCursor: Cursor = context.contentResolver.query(uri, null, null, null, null)!!
|
||||||
|
val nameIndex: Int = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||||
|
returnCursor.moveToFirst()
|
||||||
|
val name: String = returnCursor.getString(nameIndex)
|
||||||
|
returnCursor.close()
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val CHANNEL_ID = "Exports"
|
||||||
|
private const val NOTIFICATION_ID = 2
|
||||||
|
|
||||||
|
// Request code for selecting a PDF document.
|
||||||
|
const val PICK_PDF_FILE = 2
|
||||||
|
|
||||||
|
// Request code for creating a PDF document.
|
||||||
|
const val CREATE_FILE = 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
package com.massive
|
package com.massive
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.DownloadManager
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
|
@ -15,8 +17,8 @@ import androidx.core.app.NotificationManagerCompat
|
||||||
import com.facebook.react.bridge.ReactApplicationContext
|
import com.facebook.react.bridge.ReactApplicationContext
|
||||||
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
||||||
import com.facebook.react.bridge.ReactMethod
|
import com.facebook.react.bridge.ReactMethod
|
||||||
import okhttp3.internal.notify
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileWriter
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@ -35,14 +37,13 @@ class ExportModule internal constructor(context: ReactApplicationContext?) :
|
||||||
val current = LocalDateTime.now()
|
val current = LocalDateTime.now()
|
||||||
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
||||||
val formatted = current.format(formatter)
|
val formatted = current.format(formatter)
|
||||||
val sets = File(dir, "sets$formatted.csv")
|
val file = File(dir, "sets-$formatted.csv")
|
||||||
sets.createNewFile()
|
file.createNewFile()
|
||||||
sets.setWritable(true)
|
val writer = FileWriter(file)
|
||||||
sets.setReadable(true)
|
writer.write("id,name,reps,weight,created,unit\n")
|
||||||
sets.writeText("id,name,reps,weight,created,unit\n")
|
|
||||||
val db = MassiveHelper(reactApplicationContext).readableDatabase
|
val db = MassiveHelper(reactApplicationContext).readableDatabase
|
||||||
db.use {
|
db.use {
|
||||||
with (it.query("sets", null, null, null, null, null, null)) {
|
with(it.query("sets", null, null, null, null, null, null)) {
|
||||||
while (moveToNext()) {
|
while (moveToNext()) {
|
||||||
val id = getInt(getColumnIndex("id"))
|
val id = getInt(getColumnIndex("id"))
|
||||||
val name = getString(getColumnIndex("name"))
|
val name = getString(getColumnIndex("name"))
|
||||||
|
@ -50,33 +51,42 @@ class ExportModule internal constructor(context: ReactApplicationContext?) :
|
||||||
val weight = getInt(getColumnIndex("weight"))
|
val weight = getInt(getColumnIndex("weight"))
|
||||||
val created = getString(getColumnIndex("created"))
|
val created = getString(getColumnIndex("created"))
|
||||||
val unit = getString(getColumnIndex("unit"))
|
val unit = getString(getColumnIndex("unit"))
|
||||||
sets.appendText("$id,$name,$reps,$weight,$created,$unit\n")
|
writer.appendLine("$id,$name,$reps,$weight,$created,$unit\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
writer.flush()
|
||||||
|
writer.close()
|
||||||
|
sendNotification()
|
||||||
|
return file.path
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.M)
|
||||||
|
private fun sendNotification() {
|
||||||
val notificationManager = NotificationManagerCompat.from(reactApplicationContext)
|
val notificationManager = NotificationManagerCompat.from(reactApplicationContext)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val importance = NotificationManager.IMPORTANCE_LOW
|
val importance = NotificationManager.IMPORTANCE_LOW
|
||||||
val channel = NotificationChannel(CHANNEL_ID, CHANNEL_ID, importance)
|
val channel = NotificationChannel(
|
||||||
|
CHANNEL_ID,
|
||||||
|
CHANNEL_ID, importance)
|
||||||
channel.description = "Alarms for rest timings."
|
channel.description = "Alarms for rest timings."
|
||||||
notificationManager.createNotificationChannel(channel)
|
notificationManager.createNotificationChannel(channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
val contentIntent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
val contentIntent = Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
|
||||||
type = "text/csv"
|
|
||||||
}
|
|
||||||
val pendingContent =
|
val pendingContent =
|
||||||
PendingIntent.getActivity(reactApplicationContext, 0, contentIntent, PendingIntent.FLAG_IMMUTABLE)
|
PendingIntent.getActivity(
|
||||||
|
reactApplicationContext,
|
||||||
|
0,
|
||||||
|
contentIntent,
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
val builder = NotificationCompat.Builder(reactApplicationContext, CHANNEL_ID)
|
val builder = NotificationCompat.Builder(reactApplicationContext, CHANNEL_ID)
|
||||||
.setSmallIcon(R.drawable.ic_baseline_arrow_downward_24)
|
.setSmallIcon(R.drawable.ic_baseline_arrow_downward_24)
|
||||||
.setContentTitle("Downloaded sets")
|
.setContentTitle("Downloaded sets")
|
||||||
.setContentIntent(pendingContent)
|
.setContentIntent(pendingContent)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
|
||||||
return sets.absolutePath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user