Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ android {
applicationId = "co.adityarajput.fileflow"
minSdk = 29
targetSdk = 36
versionCode = 10
versionName = "1.8.0"
versionCode = 11
versionName = "1.9.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/java/co/adityarajput/fileflow/data/AppContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package co.adityarajput.fileflow.data

import android.content.Context
import co.adityarajput.fileflow.data.models.Action
import co.adityarajput.fileflow.data.models.Backup
import co.adityarajput.fileflow.data.models.Execution
import co.adityarajput.fileflow.data.models.Rule
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json

class AppContainer(private val context: Context) {
val repository: Repository by lazy {
Expand All @@ -17,6 +19,26 @@ class AppContainer(private val context: Context) {
)
}

suspend fun export() = Json.encodeToString<Backup>(
Backup(
// INFO: May contain servers but creds can't be decrypted anyway.
repository.rules().first(),
repository.groups().first(),
repository.servers().first().map {
it.copy(encryptedPassword = null, encryptedPrivateKey = null)
},
),
)

suspend fun import(json: String) {
repository.deleteRulesGroupsAndServers()
Json.decodeFromString<Backup>(json).let { (rules, groups, servers) ->
repository.upsert(*rules.toTypedArray())
repository.upsert(*groups.toTypedArray())
repository.upsert(*servers.toTypedArray())
}
}

fun seedDemoData() {
runBlocking {
if (repository.rules().first().isEmpty()) {
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/co/adityarajput/fileflow/data/Repository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,10 @@ class Repository(
suspend fun delete(group: Group) = groupDao.delete(group)

suspend fun delete(server: Server) = serverDao.delete(server)

suspend fun deleteRulesGroupsAndServers() {
ruleDao.deleteAll()
groupDao.deleteAll()
serverDao.deleteAll()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ interface GroupDao {

@Delete
suspend fun delete(group: Group)

@Query("DELETE FROM `groups`")
suspend fun deleteAll()
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,7 @@ interface RuleDao {

@Delete
suspend fun delete(rule: Rule)

@Query("DELETE FROM rules")
suspend fun deleteAll()
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ interface ServerDao {

@Delete
suspend fun delete(group: Server)

@Query("DELETE FROM servers")
suspend fun deleteAll()
}
10 changes: 10 additions & 0 deletions app/src/main/java/co/adityarajput/fileflow/data/models/Backup.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package co.adityarajput.fileflow.data.models

import kotlinx.serialization.Serializable

@Serializable
data class Backup(
val rules: List<Rule>,
val groups: List<Group>,
val servers: List<Server>,
)
3 changes: 1 addition & 2 deletions app/src/main/java/co/adityarajput/fileflow/utils/Files.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ sealed class File {
if (it.exists()) FSFile(it) else null
}!!
} catch (e2: Exception) {
Logger.w("Files", "Error while creating SAFFile from path: $path", e1)
Logger.w("Files", "Error while creating FSFile from path: $path", e2)
Logger.w("Files", "Error while creating File from path: $path", e1, e2)
}

return null
Expand Down
9 changes: 5 additions & 4 deletions app/src/main/java/co/adityarajput/fileflow/utils/Logging.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@ object Logger {
if (logs.size >= Constants.LOG_SIZE) logs.removeFirst()
logs.addLast("[${System.currentTimeMillis()}][$tag][INFO] $msg")

ACRA.errorReporter.putCustomData("logs", logs.joinToString("\n"))
ACRA.errorReporter.putCustomData("logs", logs.toList().joinToString("\n"))
}

fun w(tag: String, msg: String, tr: Throwable? = null) {
fun w(tag: String, msg: String, tr: Throwable? = null, tr2: Throwable? = null) {
Log.w(tag, msg, tr)
Log.w(tag, msg, tr2)

if (logs.size >= Constants.LOG_SIZE) logs.removeFirst()
logs.addLast("[${System.currentTimeMillis()}][$tag][WARN] $msg")

ACRA.errorReporter.putCustomData("logs", logs.joinToString("\n"))
ACRA.errorReporter.putCustomData("logs", logs.toList().joinToString("\n"))
}

fun e(tag: String, msg: String, tr: Throwable? = null) {
Expand All @@ -38,6 +39,6 @@ object Logger {
if (logs.size >= Constants.LOG_SIZE) logs.removeFirst()
logs.addLast("[${System.currentTimeMillis()}][$tag][ERROR] $msg")

ACRA.errorReporter.putCustomData("logs", logs.joinToString("\n"))
ACRA.errorReporter.putCustomData("logs", logs.toList().joinToString("\n"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import android.os.Build
import android.os.Handler
import android.os.Looper
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
Expand All @@ -22,6 +24,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import co.adityarajput.fileflow.BuildConfig
import co.adityarajput.fileflow.R
import co.adityarajput.fileflow.data.AppContainer
import co.adityarajput.fileflow.services.Preferences
import co.adityarajput.fileflow.utils.Logger
import co.adityarajput.fileflow.utils.Permission
Expand All @@ -31,6 +34,9 @@ import co.adityarajput.fileflow.viewmodels.AppearanceViewModel
import co.adityarajput.fileflow.views.Brightness
import co.adityarajput.fileflow.views.components.AppBar
import kotlinx.coroutines.launch
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter

private val permissions = listOf(
Permission.UNRESTRICTED_BACKGROUND_USAGE,
Expand Down Expand Up @@ -236,6 +242,96 @@ fun SettingsScreen(
}
}
}
Card(
Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.padding_small)),
) {
Column(
Modifier
.fillMaxWidth()
.padding(
dimensionResource(R.dimen.padding_large),
dimensionResource(R.dimen.padding_medium),
),
Arrangement.spacedBy(dimensionResource(R.dimen.padding_medium)),
) {
Text(
stringResource(R.string.settings_section_3),
fontWeight = FontWeight.Medium,
)
val importSuccess = stringResource(R.string.import_success)
val importLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.OpenDocument(),
) { uri ->
scope.launch {
uri
?.let { context.contentResolver.openInputStream(it) }
?.use {
AppContainer(context).import(it.bufferedReader().readText())
goBack()
Toast
.makeText(context, importSuccess, Toast.LENGTH_SHORT)
.show()
}
}
}
Column(
Modifier
.fillMaxWidth()
.clickable { importLauncher.launch(arrayOf("application/json")) },
) {
Text(
stringResource(R.string.import_backup),
style = MaterialTheme.typography.titleSmall,
)
Text(
stringResource(R.string.import_warning),
style = MaterialTheme.typography.bodySmall,
)
}
val exportSuccess = stringResource(R.string.export_success)
val appNameAndVersion =
"${stringResource(R.string.app_name)}_${BuildConfig.VERSION_NAME}"
val exportLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.CreateDocument("application/json"),
) { uri ->
scope.launch {
uri
?.let { context.contentResolver.openOutputStream(it) }
?.use {
it.write(AppContainer(context).export().toByteArray())
Toast
.makeText(context, exportSuccess, Toast.LENGTH_SHORT)
.show()
}
}
}
Column(
Modifier
.fillMaxWidth()
.clickable {
exportLauncher.launch(
appNameAndVersion + "_${
Instant.now().atZone(ZoneId.systemDefault())
.toLocalDateTime().format(
DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"),
)
}.json",
)
},
) {
Text(
stringResource(R.string.export_backup),
style = MaterialTheme.typography.titleSmall,
)
Text(
stringResource(R.string.export_explanation),
style = MaterialTheme.typography.bodySmall,
)
}
}
}
Card(
Modifier
.fillMaxWidth()
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@
<string name="light">Light</string>
<string name="system">System</string>
<string name="dark">Dark</string>
<string name="settings_section_3">Data Transfer</string>
<string name="import_backup">Import backup</string>
<string name="import_warning">Overwrites your current rules and groups</string>
<string name="import_success">Data imported successfully</string>
<string name="export_backup">Export backup</string>
<string name="export_explanation">Generates a JSON export of your rules and groups</string>
<string name="export_success">Data exported successfully</string>
<string name="manage_groups">Manage groups</string>
<string name="servers">Servers</string>
<string name="manage_servers">Manage servers</string>
Expand Down
8 changes: 4 additions & 4 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[versions]
aboutlibraries = "14.0.1"
aboutlibraries = "14.2.1"
agp = "9.1.1"
kotlin = "2.3.20"
kotlin = "2.3.21"
coreKtx = "1.18.0"
junit = "4.13.2"
junitVersion = "1.3.0"
Expand All @@ -10,9 +10,9 @@ lifecycleRuntimeKtx = "2.10.0"
activityCompose = "1.13.0"
composeBom = "2026.02.00"
appcompat = "1.7.1"
navigationCompose = "2.9.7"
navigationCompose = "2.9.8"
room = "2.8.4"
composeMaterial = "1.6.1"
composeMaterial = "1.6.2"
kotlinxSerializationJson = "1.11.0"
documentfile = "1.1.0"
workRuntimeKtx = "2.11.2"
Expand Down
2 changes: 2 additions & 0 deletions metadata/en-US/changelogs/11.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
• fix: Prevent concurrent modification while logging
• feat: Add import/export functionality
Loading