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

View File

@ -4,7 +4,6 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<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.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

View File

@ -7,7 +7,6 @@ import android.content.*
import android.net.Uri
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.documentfile.provider.DocumentFile
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
@ -22,7 +21,6 @@ class BackupModule(context: ReactApplicationContext?) :
val context: ReactApplicationContext = reactApplicationContext
private val copyReceiver = object : BroadcastReceiver() {
@RequiresApi(Build.VERSION_CODES.O)
override fun onReceive(context: Context?, intent: Intent?) {
val targetDir = intent?.getStringExtra("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
fun start(baseUri: String) {
Log.d("BackupModule", "start $baseUri")
@ -66,7 +85,6 @@ class BackupModule(context: ReactApplicationContext?) :
)
}
@RequiresApi(Build.VERSION_CODES.M)
@ReactMethod(isBlockingSynchronousMethod = true)
fun stop() {
val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
@ -77,10 +95,10 @@ class BackupModule(context: ReactApplicationContext?) :
}
@ReactMethod
fun exportToCSV(promise: Promise) {
fun exportToCSV(target: String, promise: Promise) {
try {
val db = DatabaseHelper(reactApplicationContext)
db.exportToCSV()
db.exportToCSV(target, reactApplicationContext)
promise.resolve("Export successful!")
}
catch (e: Exception) {

View File

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