Remove reliance on WRITE_EXTERNAL_STORAGE

https://developer.android.com/about/versions/11/privacy/storage#permissions-target-11
This commit is contained in:
Brandon Presley 2024-02-12 15:15:34 +13:00
parent bfc1b3d546
commit 5e34bd4570
4 changed files with 61 additions and 42 deletions

View File

@ -478,7 +478,7 @@ export default function SettingsPage() {
), ),
}, },
{ {
name: `Backup directory: ${backupString || "Downloads"}`, name: `Backup directory: ${backupString || "Not set yet!"}`,
renderItem: (name: string) => ( renderItem: (name: string) => (
<Button <Button
style={{ alignSelf: "flex-start" }} style={{ alignSelf: "flex-start" }}
@ -522,15 +522,15 @@ export default function SettingsPage() {
<Button <Button
style={{ alignSelf: "flex-start" }} style={{ alignSelf: "flex-start" }}
onPress={async () => { onPress={async () => {
const result = await check( let target = settings.backupDir
PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE if (!FileSystem.exists(target)) {
); const result = await DocumentPicker.pickDirectory();
if (result === RESULTS.DENIED || result === RESULTS.BLOCKED) { target = result.uri
await request(PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE); setValue("backupDir", result.uri);
} }
const path = Dirs.DatabaseDir + "/massive.db"; const error = await NativeModules.BackupModule.once(target);
await FileSystem.cpExternal(path, "massive.db", "downloads"); if (error) toast(error);
toast("Database exported. Check downloads."); else toast("Database exported.");
}} }}
> >
{name} {name}
@ -543,13 +543,13 @@ export default function SettingsPage() {
<Button <Button
style={{ alignSelf: "flex-start" }} style={{ alignSelf: "flex-start" }}
onPress={async () => { onPress={async () => {
const result = await check( let target = settings.backupDir
PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE if (!target || !FileSystem.exists(target)) {
); const result = await DocumentPicker.pickDirectory();
if (result === RESULTS.DENIED || result === RESULTS.BLOCKED) { target = result.uri
await request(PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE); setValue("backupDir", result.uri);
} }
await NativeModules.BackupModule.exportToCSV(); await NativeModules.BackupModule.exportToCSV(target);
toast("Exported sets as CSV."); toast("Exported sets as CSV.");
}} }}
> >

View File

@ -4,7 +4,6 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" /> <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

View File

@ -7,7 +7,6 @@ import android.content.*
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.facebook.react.bridge.Promise import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactApplicationContext
@ -22,7 +21,6 @@ class BackupModule(context: ReactApplicationContext?) :
val context: ReactApplicationContext = reactApplicationContext val context: ReactApplicationContext = reactApplicationContext
private val copyReceiver = object : BroadcastReceiver() { private val copyReceiver = object : BroadcastReceiver() {
@RequiresApi(Build.VERSION_CODES.O)
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
val targetDir = intent?.getStringExtra("targetDir") val targetDir = intent?.getStringExtra("targetDir")
Log.d("BackupModule", "onReceive $targetDir") Log.d("BackupModule", "onReceive $targetDir")
@ -40,7 +38,28 @@ class BackupModule(context: ReactApplicationContext?) :
} }
} }
@RequiresApi(Build.VERSION_CODES.M) @ReactMethod
fun once(target: String, promise: Promise) {
Log.d("BackupModule", "once $target")
try {
val treeUri: Uri = Uri.parse(target)
val documentFile = context.let { DocumentFile.fromTreeUri(it, treeUri) }
val file = documentFile?.createFile("application/octet-stream", "massive.db")
val output = context.contentResolver?.openOutputStream(file!!.uri)
val sourceFile = File(context.getDatabasePath("massive.db")!!.path)
val input = FileInputStream(sourceFile)
if (output != null) {
input.copyTo(output)
}
output?.flush()
output?.close()
promise.resolve(0)
}
catch (error: Exception) {
promise.reject("ERROR", error)
}
}
@ReactMethod @ReactMethod
fun start(baseUri: String) { fun start(baseUri: String) {
Log.d("BackupModule", "start $baseUri") Log.d("BackupModule", "start $baseUri")
@ -66,7 +85,6 @@ class BackupModule(context: ReactApplicationContext?) :
) )
} }
@RequiresApi(Build.VERSION_CODES.M)
@ReactMethod(isBlockingSynchronousMethod = true) @ReactMethod(isBlockingSynchronousMethod = true)
fun stop() { fun stop() {
val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
@ -77,10 +95,10 @@ class BackupModule(context: ReactApplicationContext?) :
} }
@ReactMethod @ReactMethod
fun exportToCSV(promise: Promise) { fun exportToCSV(target: String, promise: Promise) {
try { try {
val db = DatabaseHelper(reactApplicationContext) val db = DatabaseHelper(reactApplicationContext)
db.exportToCSV() db.exportToCSV(target, reactApplicationContext)
promise.resolve("Export successful!") promise.resolve("Export successful!")
} }
catch (e: Exception) { catch (e: Exception) {

View File

@ -3,7 +3,10 @@ package com.massive
import android.content.Context import android.content.Context
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper import android.database.sqlite.SQLiteOpenHelper
import android.net.Uri
import android.os.Environment import android.os.Environment
import android.util.Log
import androidx.documentfile.provider.DocumentFile
import com.opencsv.CSVWriter import com.opencsv.CSVWriter
import java.io.File import java.io.File
import java.io.FileWriter import java.io.FileWriter
@ -15,30 +18,29 @@ class DatabaseHelper(context: Context) :
private const val DATABASE_VERSION = 1 private const val DATABASE_VERSION = 1
} }
fun exportToCSV() { fun exportToCSV(target: String, context: Context) {
val exportDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) Log.d("DatabaseHelper", "exportToCSV $target")
if (!exportDir.exists()) { val treeUri: Uri = Uri.parse(target)
exportDir.mkdirs() val documentFile = context.let { DocumentFile.fromTreeUri(it, treeUri) }
} val file = documentFile?.createFile("application/octet-stream", "sets.csv") ?: return
val file = File(exportDir, "gym_sets.csv") context.contentResolver.openOutputStream(file.uri).use { outputStream ->
file.createNewFile() val csvWrite = CSVWriter(outputStream?.writer())
val db = this.readableDatabase
val cursor = db.rawQuery("SELECT * FROM sets", null)
csvWrite.writeNext(cursor.columnNames)
val csvWrite = CSVWriter(FileWriter(file)) while(cursor.moveToNext()) {
val db = this.readableDatabase val arrStr = arrayOfNulls<String>(cursor.columnCount)
val cursor = db.rawQuery("SELECT * FROM sets", null) for(i in 0 until cursor.columnCount) {
csvWrite.writeNext(cursor.columnNames) arrStr[i] = cursor.getString(i)
}
while(cursor.moveToNext()) { csvWrite.writeNext(arrStr)
val arrStr = arrayOfNulls<String>(cursor.columnCount)
for(i in 0 until cursor.columnCount) {
arrStr[i] = cursor.getString(i)
} }
csvWrite.writeNext(arrStr)
}
csvWrite.close() csvWrite.close()
cursor.close() cursor.close()
}
} }
override fun onCreate(db: SQLiteDatabase) { override fun onCreate(db: SQLiteDatabase) {