From 5069f4c3ef8a127e74ed0b1321fb4a7bdd824c6e Mon Sep 17 00:00:00 2001
From: Aditya Rajput
Date: Tue, 7 Apr 2026 00:03:22 +0530
Subject: [PATCH 1/3] Add shortcuts that execute groups of rules
---
.../6.json | 128 +++++++++++
app/src/main/AndroidManifest.xml | 15 ++
.../co/adityarajput/fileflow/AlarmReceiver.kt | 2 +-
.../co/adityarajput/fileflow/Constants.kt | 10 +
.../fileflow/FileFlowApplication.kt | 2 +
.../adityarajput/fileflow/ShortcutActivity.kt | 18 ++
.../adityarajput/fileflow/ShortcutReceiver.kt | 40 ++++
.../fileflow/data/AppContainer.kt | 1 +
.../adityarajput/fileflow/data/Converters.kt | 6 +
.../fileflow/data/FileFlowDatabase.kt | 7 +-
.../co/adityarajput/fileflow/data/GroupDao.kt | 23 ++
.../adityarajput/fileflow/data/Repository.kt | 23 ++
.../fileflow/data/models/Group.kt | 20 ++
.../adityarajput/fileflow/utils/Shortcuts.kt | 48 ++++
.../fileflow/viewmodels/GroupsViewModel.kt | 37 +++
.../fileflow/viewmodels/Provider.kt | 11 +
.../viewmodels/UpsertGroupViewModel.kt | 79 +++++++
.../viewmodels/UpsertRuleViewModel.kt | 16 +-
.../adityarajput/fileflow/views/Navigator.kt | 17 ++
.../views/components/DeleteGroupDialog.kt | 42 ++++
.../fileflow/views/components/Tile.kt | 47 ++--
.../fileflow/views/screens/GroupsScreen.kt | 140 ++++++++++++
.../fileflow/views/screens/SettingsScreen.kt | 26 +++
.../views/screens/UpsertGroupScreen.kt | 214 ++++++++++++++++++
.../views/screens/UpsertRuleScreen.kt | 16 +-
app/src/main/res/drawable/group.xml | 10 +
app/src/main/res/drawable/play.xml | 10 +
.../main/res/mipmap-anydpi/ic_shortcut.xml | 14 ++
app/src/main/res/values/plurals.xml | 4 +
app/src/main/res/values/strings.xml | 27 +++
30 files changed, 1012 insertions(+), 41 deletions(-)
create mode 100644 app/schemas/co.adityarajput.fileflow.data.FileFlowDatabase/6.json
create mode 100644 app/src/main/java/co/adityarajput/fileflow/ShortcutActivity.kt
create mode 100644 app/src/main/java/co/adityarajput/fileflow/ShortcutReceiver.kt
create mode 100644 app/src/main/java/co/adityarajput/fileflow/data/GroupDao.kt
create mode 100644 app/src/main/java/co/adityarajput/fileflow/data/models/Group.kt
create mode 100644 app/src/main/java/co/adityarajput/fileflow/utils/Shortcuts.kt
create mode 100644 app/src/main/java/co/adityarajput/fileflow/viewmodels/GroupsViewModel.kt
create mode 100644 app/src/main/java/co/adityarajput/fileflow/viewmodels/UpsertGroupViewModel.kt
create mode 100644 app/src/main/java/co/adityarajput/fileflow/views/components/DeleteGroupDialog.kt
create mode 100644 app/src/main/java/co/adityarajput/fileflow/views/screens/GroupsScreen.kt
create mode 100644 app/src/main/java/co/adityarajput/fileflow/views/screens/UpsertGroupScreen.kt
create mode 100644 app/src/main/res/drawable/group.xml
create mode 100644 app/src/main/res/drawable/play.xml
create mode 100644 app/src/main/res/mipmap-anydpi/ic_shortcut.xml
diff --git a/app/schemas/co.adityarajput.fileflow.data.FileFlowDatabase/6.json b/app/schemas/co.adityarajput.fileflow.data.FileFlowDatabase/6.json
new file mode 100644
index 0000000..2fe6a04
--- /dev/null
+++ b/app/schemas/co.adityarajput.fileflow.data.FileFlowDatabase/6.json
@@ -0,0 +1,128 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 6,
+ "identityHash": "b0e045c438054bcca59175062168639b",
+ "entities": [
+ {
+ "tableName": "rules",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`action` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `executions` INTEGER NOT NULL, `interval` INTEGER DEFAULT 3600000, `cronString` TEXT DEFAULT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "action",
+ "columnName": "action",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "enabled",
+ "columnName": "enabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "executions",
+ "columnName": "executions",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "interval",
+ "columnName": "interval",
+ "affinity": "INTEGER",
+ "defaultValue": "3600000"
+ },
+ {
+ "fieldPath": "cronString",
+ "columnName": "cronString",
+ "affinity": "TEXT",
+ "defaultValue": "NULL"
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "executions",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`fileName` TEXT NOT NULL, `verb` TEXT NOT NULL DEFAULT 'MOVE', `timestamp` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "fileName",
+ "columnName": "fileName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "verb",
+ "columnName": "verb",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'MOVE'"
+ },
+ {
+ "fieldPath": "timestamp",
+ "columnName": "timestamp",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "groups",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `ruleIds` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ruleIds",
+ "columnName": "ruleIds",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b0e045c438054bcca59175062168639b')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f6c78d3..dcd8b94 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -35,6 +35,14 @@
+
+
@@ -42,5 +50,12 @@
+
+
+
+
+
diff --git a/app/src/main/java/co/adityarajput/fileflow/AlarmReceiver.kt b/app/src/main/java/co/adityarajput/fileflow/AlarmReceiver.kt
index 0f0fd28..e35ca71 100644
--- a/app/src/main/java/co/adityarajput/fileflow/AlarmReceiver.kt
+++ b/app/src/main/java/co/adityarajput/fileflow/AlarmReceiver.kt
@@ -22,7 +22,7 @@ class AlarmReceiver : BroadcastReceiver() {
if (rule == null || !rule.enabled)
return@launch
- Logger.d("Worker", "Executing $rule")
+ Logger.d("AlarmReceiver", "Executing $rule")
rule.action.execute(context) {
repository.registerExecution(
rule,
diff --git a/app/src/main/java/co/adityarajput/fileflow/Constants.kt b/app/src/main/java/co/adityarajput/fileflow/Constants.kt
index 81f1236..fd7391e 100644
--- a/app/src/main/java/co/adityarajput/fileflow/Constants.kt
+++ b/app/src/main/java/co/adityarajput/fileflow/Constants.kt
@@ -12,7 +12,17 @@ object Constants {
const val EXTRA_RULE_ID = "extra_rule_id"
const val MAX_CRON_EXECUTIONS_PER_HOUR = 4
+ const val ACTION_EXECUTE_GROUP = "co.adityarajput.fileflow.EXECUTE_GROUP"
+ const val EXTRA_GROUP_ID = "extra_group_id"
+
const val LOG_SIZE = 100
const val ONE_HOUR_IN_MILLIS = 3_600_000L
+
+ /**
+ * Max amount of app shortcuts visible when launcher app icon is long-pressed.
+ *
+ * There *is* a `ShortcutManagerCompat.getMaxShortcutCountPerActivity` method, but it *lies*.
+ */
+ const val MAX_SHORTCUTS = 4
}
diff --git a/app/src/main/java/co/adityarajput/fileflow/FileFlowApplication.kt b/app/src/main/java/co/adityarajput/fileflow/FileFlowApplication.kt
index 198411b..9abf137 100644
--- a/app/src/main/java/co/adityarajput/fileflow/FileFlowApplication.kt
+++ b/app/src/main/java/co/adityarajput/fileflow/FileFlowApplication.kt
@@ -4,6 +4,7 @@ import android.app.Application
import co.adityarajput.fileflow.data.AppContainer
import co.adityarajput.fileflow.utils.isDebugBuild
import co.adityarajput.fileflow.utils.scheduleWork
+import co.adityarajput.fileflow.utils.upsertShortcuts
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -23,6 +24,7 @@ class FileFlowApplication : Application() {
CoroutineScope(Dispatchers.IO).launch {
scheduleWork()
+ upsertShortcuts()
}
}
}
diff --git a/app/src/main/java/co/adityarajput/fileflow/ShortcutActivity.kt b/app/src/main/java/co/adityarajput/fileflow/ShortcutActivity.kt
new file mode 100644
index 0000000..6e46729
--- /dev/null
+++ b/app/src/main/java/co/adityarajput/fileflow/ShortcutActivity.kt
@@ -0,0 +1,18 @@
+package co.adityarajput.fileflow
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+
+class ShortcutActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ sendBroadcast(
+ Intent(Constants.ACTION_EXECUTE_GROUP).apply {
+ setPackage(packageName)
+ putExtra(Constants.EXTRA_GROUP_ID, intent.getIntExtra(Constants.EXTRA_GROUP_ID, -1))
+ },
+ )
+ finish()
+ }
+}
diff --git a/app/src/main/java/co/adityarajput/fileflow/ShortcutReceiver.kt b/app/src/main/java/co/adityarajput/fileflow/ShortcutReceiver.kt
new file mode 100644
index 0000000..15725c3
--- /dev/null
+++ b/app/src/main/java/co/adityarajput/fileflow/ShortcutReceiver.kt
@@ -0,0 +1,40 @@
+package co.adityarajput.fileflow
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import co.adityarajput.fileflow.data.AppContainer
+import co.adityarajput.fileflow.data.models.Execution
+import co.adityarajput.fileflow.utils.Logger
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+class ShortcutReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ Logger.d("ShortcutReceiver", "Received intent with action: ${intent.action}")
+
+ if (intent.action != Constants.ACTION_EXECUTE_GROUP)
+ return
+
+ CoroutineScope(Dispatchers.IO).launch {
+ val repository = AppContainer(context).repository
+ val groupId = intent.getIntExtra(Constants.EXTRA_GROUP_ID, -1)
+ val (group, rules) = repository.group(groupId)
+
+ if (group == null)
+ return@launch
+
+ Logger.d("ShortcutReceiver", "Executing $group")
+ for (rule in rules) {
+ Logger.d("ShortcutReceiver", "Executing $rule")
+ rule.action.execute(context) {
+ repository.registerExecution(
+ rule,
+ Execution(it, rule.action.verb),
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/co/adityarajput/fileflow/data/AppContainer.kt b/app/src/main/java/co/adityarajput/fileflow/data/AppContainer.kt
index 657be46..29846d8 100644
--- a/app/src/main/java/co/adityarajput/fileflow/data/AppContainer.kt
+++ b/app/src/main/java/co/adityarajput/fileflow/data/AppContainer.kt
@@ -12,6 +12,7 @@ class AppContainer(private val context: Context) {
Repository(
FileFlowDatabase.getDatabase(context).ruleDao(),
FileFlowDatabase.getDatabase(context).executionDao(),
+ FileFlowDatabase.getDatabase(context).groupDao(),
)
}
diff --git a/app/src/main/java/co/adityarajput/fileflow/data/Converters.kt b/app/src/main/java/co/adityarajput/fileflow/data/Converters.kt
index 893cb1a..1756f91 100644
--- a/app/src/main/java/co/adityarajput/fileflow/data/Converters.kt
+++ b/app/src/main/java/co/adityarajput/fileflow/data/Converters.kt
@@ -25,4 +25,10 @@ class Converters {
Action.entries[0]
}
}
+
+ @TypeConverter
+ fun fromIntList(list: List) = list.joinToString(",")
+
+ @TypeConverter
+ fun toIntList(value: String) = value.split(",").mapNotNull { it.toIntOrNull() }
}
diff --git a/app/src/main/java/co/adityarajput/fileflow/data/FileFlowDatabase.kt b/app/src/main/java/co/adityarajput/fileflow/data/FileFlowDatabase.kt
index de3a8b7..bc52d73 100644
--- a/app/src/main/java/co/adityarajput/fileflow/data/FileFlowDatabase.kt
+++ b/app/src/main/java/co/adityarajput/fileflow/data/FileFlowDatabase.kt
@@ -4,22 +4,25 @@ import android.content.Context
import androidx.room.*
import androidx.room.migration.AutoMigrationSpec
import co.adityarajput.fileflow.data.models.Execution
+import co.adityarajput.fileflow.data.models.Group
import co.adityarajput.fileflow.data.models.Rule
@Database(
- [Rule::class, Execution::class],
- version = 5,
+ [Rule::class, Execution::class, Group::class],
+ version = 6,
autoMigrations = [
AutoMigration(1, 2),
AutoMigration(2, 3, FileFlowDatabase.DeleteEColumnAV::class),
AutoMigration(3, 4),
AutoMigration(4, 5),
+ AutoMigration(5, 6),
],
)
@TypeConverters(Converters::class)
abstract class FileFlowDatabase : RoomDatabase() {
abstract fun ruleDao(): RuleDao
abstract fun executionDao(): ExecutionDao
+ abstract fun groupDao(): GroupDao
@DeleteColumn("executions", "actionVerb")
class DeleteEColumnAV : AutoMigrationSpec
diff --git a/app/src/main/java/co/adityarajput/fileflow/data/GroupDao.kt b/app/src/main/java/co/adityarajput/fileflow/data/GroupDao.kt
new file mode 100644
index 0000000..184282a
--- /dev/null
+++ b/app/src/main/java/co/adityarajput/fileflow/data/GroupDao.kt
@@ -0,0 +1,23 @@
+package co.adityarajput.fileflow.data
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Query
+import androidx.room.Upsert
+import co.adityarajput.fileflow.data.models.Group
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface GroupDao {
+ @Upsert
+ suspend fun upsert(vararg groups: Group)
+
+ @Query("SELECT * from `groups` ORDER BY id ASC")
+ fun list(): Flow>
+
+ @Query("SELECT * from `groups` WHERE id = :id")
+ fun get(id: Int): Group?
+
+ @Delete
+ suspend fun delete(group: Group)
+}
diff --git a/app/src/main/java/co/adityarajput/fileflow/data/Repository.kt b/app/src/main/java/co/adityarajput/fileflow/data/Repository.kt
index fe88e9d..a09b4ba 100644
--- a/app/src/main/java/co/adityarajput/fileflow/data/Repository.kt
+++ b/app/src/main/java/co/adityarajput/fileflow/data/Repository.kt
@@ -1,23 +1,44 @@
package co.adityarajput.fileflow.data
+import co.adityarajput.fileflow.data.models.ALL_RULES
import co.adityarajput.fileflow.data.models.Execution
+import co.adityarajput.fileflow.data.models.Group
import co.adityarajput.fileflow.data.models.Rule
import co.adityarajput.fileflow.utils.Logger
+import kotlinx.coroutines.flow.first
class Repository(
private val ruleDao: RuleDao,
private val executionDao: ExecutionDao,
+ private val groupDao: GroupDao,
) {
suspend fun upsert(vararg rules: Rule) = ruleDao.upsert(*rules)
suspend fun upsert(vararg executions: Execution) = executionDao.upsert(*executions)
+ suspend fun upsert(vararg groups: Group) = groupDao.upsert(*groups)
+
fun rules() = ruleDao.list()
fun rule(id: Int) = ruleDao.get(id)
fun executions() = executionDao.list()
+ fun groups() = groupDao.list()
+
+ suspend fun group(id: Int): Pair> {
+ val rules = ruleDao.list().first()
+
+ if (id == ALL_RULES.id)
+ return ALL_RULES to rules
+
+ val group = groupDao.get(id) ?: return null to emptyList()
+
+ return group to
+ (if (group.ruleIds.isEmpty()) rules
+ else rules.filter { it.id in group.ruleIds })
+ }
+
suspend fun registerExecution(rule: Rule, execution: Execution) {
ruleDao.registerExecution(rule.id)
executionDao.upsert(execution)
@@ -32,4 +53,6 @@ class Repository(
suspend fun toggle(rule: Rule) = ruleDao.toggle(rule.id)
suspend fun delete(rule: Rule) = ruleDao.delete(rule)
+
+ suspend fun delete(group: Group) = groupDao.delete(group)
}
diff --git a/app/src/main/java/co/adityarajput/fileflow/data/models/Group.kt b/app/src/main/java/co/adityarajput/fileflow/data/models/Group.kt
new file mode 100644
index 0000000..bac39f7
--- /dev/null
+++ b/app/src/main/java/co/adityarajput/fileflow/data/models/Group.kt
@@ -0,0 +1,20 @@
+package co.adityarajput.fileflow.data.models
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import kotlinx.serialization.Serializable
+
+@Serializable
+@Entity(tableName = "groups")
+data class Group(
+ val name: String,
+
+ val ruleIds: List,
+
+ @PrimaryKey(autoGenerate = true)
+ val id: Int = 0,
+) {
+ fun shortcutId() = hashCode().toString()
+}
+
+val ALL_RULES = Group("all", emptyList(), -420)
diff --git a/app/src/main/java/co/adityarajput/fileflow/utils/Shortcuts.kt b/app/src/main/java/co/adityarajput/fileflow/utils/Shortcuts.kt
new file mode 100644
index 0000000..2cda670
--- /dev/null
+++ b/app/src/main/java/co/adityarajput/fileflow/utils/Shortcuts.kt
@@ -0,0 +1,48 @@
+package co.adityarajput.fileflow.utils
+
+import android.content.Context
+import android.content.Intent
+import androidx.core.content.pm.ShortcutInfoCompat
+import androidx.core.content.pm.ShortcutManagerCompat
+import androidx.core.graphics.drawable.IconCompat
+import co.adityarajput.fileflow.Constants
+import co.adityarajput.fileflow.R
+import co.adityarajput.fileflow.ShortcutActivity
+import co.adityarajput.fileflow.data.AppContainer
+import co.adityarajput.fileflow.data.models.ALL_RULES
+import co.adityarajput.fileflow.data.models.Group
+import kotlinx.coroutines.flow.first
+import kotlin.math.max
+
+suspend fun Context.upsertShortcuts() {
+ val groups = AppContainer(this).repository.groups().first().ifEmpty { listOf(ALL_RULES) }
+
+ ShortcutManagerCompat.getDynamicShortcuts(this)
+ .filter { it.id !in groups.map(Group::shortcutId) }
+ .map { it.id }
+ .let {
+ ShortcutManagerCompat.disableShortcuts(
+ this,
+ it,
+ getString(R.string.group_edited_or_deleted),
+ )
+ ShortcutManagerCompat.removeDynamicShortcuts(this, it)
+ }
+
+ groups.forEach {
+ ShortcutManagerCompat.pushDynamicShortcut(
+ this,
+ ShortcutInfoCompat.Builder(this, it.shortcutId())
+ .setRank(max(0, it.id))
+ .setShortLabel("Execute ${it.name}")
+ .setIcon(IconCompat.createWithResource(this, R.mipmap.ic_shortcut))
+ .setIntent(
+ Intent(this, ShortcutActivity::class.java).apply {
+ action = Constants.ACTION_EXECUTE_GROUP
+ putExtra(Constants.EXTRA_GROUP_ID, it.id)
+ },
+ )
+ .build(),
+ )
+ }
+}
diff --git a/app/src/main/java/co/adityarajput/fileflow/viewmodels/GroupsViewModel.kt b/app/src/main/java/co/adityarajput/fileflow/viewmodels/GroupsViewModel.kt
new file mode 100644
index 0000000..26cd8f2
--- /dev/null
+++ b/app/src/main/java/co/adityarajput/fileflow/viewmodels/GroupsViewModel.kt
@@ -0,0 +1,37 @@
+package co.adityarajput.fileflow.viewmodels
+
+import android.content.Context
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import co.adityarajput.fileflow.data.Repository
+import co.adityarajput.fileflow.data.models.Group
+import co.adityarajput.fileflow.utils.Logger
+import co.adityarajput.fileflow.utils.upsertShortcuts
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+class GroupsViewModel(private val repository: Repository) : ViewModel() {
+ data class State(val groups: List? = null)
+
+ val state: StateFlow = repository.groups()
+ .map { State(it) }
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), State())
+
+ var showDeleteDialog by mutableStateOf(false)
+
+ var selectedGroup by mutableStateOf(null)
+
+ fun deleteGroup(context: Context) {
+ viewModelScope.launch {
+ Logger.d("GroupsViewModel", "Deleting $selectedGroup")
+ repository.delete(selectedGroup!!)
+ context.upsertShortcuts()
+ }
+ }
+}
diff --git a/app/src/main/java/co/adityarajput/fileflow/viewmodels/Provider.kt b/app/src/main/java/co/adityarajput/fileflow/viewmodels/Provider.kt
index 2ee5383..989ac58 100644
--- a/app/src/main/java/co/adityarajput/fileflow/viewmodels/Provider.kt
+++ b/app/src/main/java/co/adityarajput/fileflow/viewmodels/Provider.kt
@@ -7,6 +7,7 @@ import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import co.adityarajput.fileflow.Constants.SETTINGS
import co.adityarajput.fileflow.FileFlowApplication
+import co.adityarajput.fileflow.data.models.Group
import co.adityarajput.fileflow.data.models.Rule
import kotlinx.serialization.json.Json
@@ -22,6 +23,7 @@ object Provider {
}
initializer { RulesViewModel(fileFlowApplication().container.repository) }
initializer { ExecutionsViewModel(fileFlowApplication().container.repository) }
+ initializer { GroupsViewModel(fileFlowApplication().container.repository) }
}
fun createURVM(ruleString: String) = viewModelFactory {
@@ -32,6 +34,15 @@ object Provider {
)
}
}
+
+ fun createUGVM(groupString: String) = viewModelFactory {
+ initializer {
+ UpsertGroupViewModel(
+ Json.decodeFromString(groupString),
+ fileFlowApplication().container.repository,
+ )
+ }
+ }
}
fun CreationExtras.fileFlowApplication() =
diff --git a/app/src/main/java/co/adityarajput/fileflow/viewmodels/UpsertGroupViewModel.kt b/app/src/main/java/co/adityarajput/fileflow/viewmodels/UpsertGroupViewModel.kt
new file mode 100644
index 0000000..b06ab4b
--- /dev/null
+++ b/app/src/main/java/co/adityarajput/fileflow/viewmodels/UpsertGroupViewModel.kt
@@ -0,0 +1,79 @@
+package co.adityarajput.fileflow.viewmodels
+
+import android.content.Context
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import co.adityarajput.fileflow.data.Repository
+import co.adityarajput.fileflow.data.models.Group
+import co.adityarajput.fileflow.data.models.Rule
+import co.adityarajput.fileflow.utils.Logger
+import co.adityarajput.fileflow.utils.upsertShortcuts
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+class UpsertGroupViewModel(
+ group: Group?,
+ private val repository: Repository,
+) : ViewModel() {
+ data class State(
+ val values: Values = Values(),
+ val error: GroupFormError? = GroupFormError.from(values),
+ )
+
+ data class Values(
+ val groupId: Int = 0,
+ val name: String = "",
+ val ruleIds: List = emptyList(),
+ val allRules: Boolean = false,
+ ) {
+ companion object {
+ fun from(group: Group) =
+ Values(group.id, group.name, group.ruleIds, group.ruleIds.isEmpty())
+ }
+
+ fun toGroup() = Group(name, if (allRules) emptyList() else ruleIds, id = groupId)
+ }
+
+ var state by mutableStateOf(
+ if (group == null) State()
+ else State(values = Values.from(group)),
+ )
+
+ val rules: StateFlow?> = repository.rules()
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
+
+ fun updateForm(values: Values = state.values) {
+ state = State(values, GroupFormError.from(values))
+ }
+
+ suspend fun submitForm(context: Context) {
+ if (GroupFormError.from(state.values) == null) {
+ val group = state.values.toGroup()
+ Logger.d(
+ "UpsertRuleViewModel",
+ "${if (state.values.groupId == 0) "Adding" else "Updating"} $group",
+ )
+ repository.upsert(group)
+ context.upsertShortcuts()
+ }
+ }
+}
+
+enum class GroupFormError {
+ BLANK_NAME, NO_RULES;
+
+ companion object {
+ fun from(values: UpsertGroupViewModel.Values): GroupFormError? {
+ if (values.name.isBlank())
+ return BLANK_NAME
+ if (values.ruleIds.isEmpty() && !values.allRules)
+ return NO_RULES
+
+ return null
+ }
+ }
+}
diff --git a/app/src/main/java/co/adityarajput/fileflow/viewmodels/UpsertRuleViewModel.kt b/app/src/main/java/co/adityarajput/fileflow/viewmodels/UpsertRuleViewModel.kt
index a88f869..c37541d 100644
--- a/app/src/main/java/co/adityarajput/fileflow/viewmodels/UpsertRuleViewModel.kt
+++ b/app/src/main/java/co/adityarajput/fileflow/viewmodels/UpsertRuleViewModel.kt
@@ -22,8 +22,8 @@ class UpsertRuleViewModel(
data class State(
val page: FormPage = FormPage.ACTION,
val values: Values = Values(),
- val error: FormError? = FormError.from(values),
- val warning: FormWarning? = null,
+ val error: RuleFormError? = RuleFormError.from(values),
+ val warning: RuleFormWarning? = null,
)
data class Values(
@@ -124,13 +124,13 @@ class UpsertRuleViewModel(
}
var predictedDestFileNames: List? = null
- var warning: FormWarning? = null
+ var warning: RuleFormWarning? = null
try {
val regex = Regex(values.srcFileNamePattern)
val matchingSrcFiles = currentSrcFiles
?.filter { regex.matches(it.name!!) }
- ?.also { if (it.isEmpty()) warning = FormWarning.NO_MATCHES_IN_SRC }
+ ?.also { if (it.isEmpty()) warning = RuleFormWarning.NO_MATCHES_IN_SRC }
if (values.destFileNameTemplate.isNotBlank()) {
if (matchingSrcFiles != null && values.actionBase is Action.MOVE) {
@@ -155,7 +155,7 @@ class UpsertRuleViewModel(
}
suspend fun submitForm(context: Context) {
- if (FormError.from(state.values) == null) {
+ if (RuleFormError.from(state.values) == null) {
val rule = state.values.toRule()
Logger.d(
"UpsertRuleViewModel",
@@ -179,12 +179,12 @@ enum class FormPage {
fun previous() = entries[ordinal - 1]
}
-enum class FormError {
+enum class RuleFormError {
BLANK_FIELDS, INVALID_REGEX, INVALID_TEMPLATE, MUST_END_IN_ZIP,
INTERVAL_TOO_SHORT, INTERVAL_TOO_LONG, INVALID_CRON_STRING, CRON_TOO_FREQUENT;
companion object {
- fun from(values: UpsertRuleViewModel.Values): FormError? {
+ fun from(values: UpsertRuleViewModel.Values): RuleFormError? {
try {
if (values.src.isBlank() || values.srcFileNamePattern.isBlank())
return BLANK_FIELDS
@@ -232,4 +232,4 @@ enum class FormError {
}
}
-enum class FormWarning { NO_MATCHES_IN_SRC }
+enum class RuleFormWarning { NO_MATCHES_IN_SRC }
diff --git a/app/src/main/java/co/adityarajput/fileflow/views/Navigator.kt b/app/src/main/java/co/adityarajput/fileflow/views/Navigator.kt
index 7c06e41..af2803f 100644
--- a/app/src/main/java/co/adityarajput/fileflow/views/Navigator.kt
+++ b/app/src/main/java/co/adityarajput/fileflow/views/Navigator.kt
@@ -51,12 +51,25 @@ fun Navigator(controller: NavHostController, appearanceViewModel: AppearanceView
composable(Routes.EXECUTIONS.name) { ExecutionsScreen(controller::popBackStack) }
composable(Routes.SETTINGS.name) {
SettingsScreen(
+ { controller.navigate(Routes.GROUPS.name) },
{ controller.navigate(Routes.LICENSES.name) },
{ controller.navigate(Routes.ABOUT.name) },
controller::popBackStack,
appearanceViewModel,
)
}
+ composable(Routes.GROUPS.name) {
+ GroupsScreen(
+ { controller.navigate(UpsertGroupRoute(it)) },
+ controller::popBackStack,
+ )
+ }
+ composable {
+ UpsertGroupScreen(
+ it.toRoute().groupString,
+ controller::popBackStack,
+ )
+ }
composable(Routes.LICENSES.name) { LicensesScreen(controller::popBackStack) }
composable(Routes.ABOUT.name) { AboutScreen(controller::popBackStack) }
}
@@ -67,9 +80,13 @@ enum class Routes {
RULES,
EXECUTIONS,
SETTINGS,
+ GROUPS,
LICENSES,
ABOUT,
}
@Serializable
data class UpsertRuleRoute(val ruleString: String = "null")
+
+@Serializable
+data class UpsertGroupRoute(val groupString: String = "null")
diff --git a/app/src/main/java/co/adityarajput/fileflow/views/components/DeleteGroupDialog.kt b/app/src/main/java/co/adityarajput/fileflow/views/components/DeleteGroupDialog.kt
new file mode 100644
index 0000000..d5300ec
--- /dev/null
+++ b/app/src/main/java/co/adityarajput/fileflow/views/components/DeleteGroupDialog.kt
@@ -0,0 +1,42 @@
+package co.adityarajput.fileflow.views.components
+
+import androidx.compose.foundation.layout.Row
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import co.adityarajput.fileflow.R
+import co.adityarajput.fileflow.viewmodels.GroupsViewModel
+
+@Composable
+fun DeleteGroupDialog(viewModel: GroupsViewModel) {
+ val context = LocalContext.current
+ val hideDialog = { viewModel.showDeleteDialog = false }
+
+ AlertDialog(
+ hideDialog,
+ title = { Text(stringResource(R.string.delete_group)) },
+ text = { Text(stringResource(R.string.delete_group_confirmation)) },
+ confirmButton = {
+ Row {
+ TextButton(
+ {
+ viewModel.deleteGroup(context)
+ hideDialog()
+ },
+ colors = ButtonDefaults.textButtonColors(
+ contentColor = MaterialTheme.colorScheme.tertiary,
+ ),
+ ) {
+ Text(stringResource(R.string.delete))
+ }
+ }
+ },
+ dismissButton = {
+ TextButton(hideDialog) {
+ Text(stringResource(R.string.cancel), fontWeight = FontWeight.Normal)
+ }
+ },
+ )
+}
diff --git a/app/src/main/java/co/adityarajput/fileflow/views/components/Tile.kt b/app/src/main/java/co/adityarajput/fileflow/views/components/Tile.kt
index 377bc1a..d45c41f 100644
--- a/app/src/main/java/co/adityarajput/fileflow/views/components/Tile.kt
+++ b/app/src/main/java/co/adityarajput/fileflow/views/components/Tile.kt
@@ -17,8 +17,8 @@ import co.adityarajput.fileflow.R
@Composable
fun Tile(
title: String,
- leading: String,
- trailing: String,
+ leading: String? = null,
+ trailing: String? = null,
content: @Composable (() -> Unit) = { },
onClick: () -> Unit = {},
buttons: @Composable RowScope.() -> Unit = {},
@@ -42,26 +42,29 @@ fun Tile(
.padding(dimensionResource(R.dimen.padding_large)),
Arrangement.spacedBy(dimensionResource(R.dimen.padding_small)),
) {
- Row(
- Modifier.fillMaxWidth(),
- Arrangement.SpaceBetween,
- Alignment.CenterVertically,
- ) {
- Text(
- leading,
- style = MaterialTheme.typography.bodySmall.copy(
- MaterialTheme.colorScheme.onSurfaceVariant,
- 11.sp,
- ),
- )
- Text(
- trailing,
- style = MaterialTheme.typography.bodySmall.copy(
- MaterialTheme.colorScheme.onSurfaceVariant,
- 8.sp,
- ),
- )
- }
+ if (leading != null || trailing != null)
+ Row(
+ Modifier.fillMaxWidth(),
+ Arrangement.SpaceBetween,
+ Alignment.CenterVertically,
+ ) {
+ if (leading != null)
+ Text(
+ leading,
+ style = MaterialTheme.typography.bodySmall.copy(
+ MaterialTheme.colorScheme.onSurfaceVariant,
+ 11.sp,
+ ),
+ )
+ if (trailing != null)
+ Text(
+ trailing,
+ style = MaterialTheme.typography.bodySmall.copy(
+ MaterialTheme.colorScheme.onSurfaceVariant,
+ 8.sp,
+ ),
+ )
+ }
Text(
title,
style = MaterialTheme.typography.titleSmall,
diff --git a/app/src/main/java/co/adityarajput/fileflow/views/screens/GroupsScreen.kt b/app/src/main/java/co/adityarajput/fileflow/views/screens/GroupsScreen.kt
new file mode 100644
index 0000000..fdc4181
--- /dev/null
+++ b/app/src/main/java/co/adityarajput/fileflow/views/screens/GroupsScreen.kt
@@ -0,0 +1,140 @@
+package co.adityarajput.fileflow.views.screens
+
+import android.widget.Toast
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.pluralStringResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import co.adityarajput.fileflow.Constants
+import co.adityarajput.fileflow.R
+import co.adityarajput.fileflow.viewmodels.GroupsViewModel
+import co.adityarajput.fileflow.viewmodels.Provider
+import co.adityarajput.fileflow.views.components.AppBar
+import co.adityarajput.fileflow.views.components.DeleteGroupDialog
+import co.adityarajput.fileflow.views.components.Tile
+import kotlinx.serialization.json.Json
+
+@Composable
+fun GroupsScreen(
+ goToUpsertGroupScreen: (String) -> Unit,
+ goBack: () -> Unit,
+ viewModel: GroupsViewModel = viewModel(factory = Provider.Factory),
+) {
+ val context = LocalContext.current
+ val groups = viewModel.state.collectAsState().value.groups
+
+ Scaffold(
+ topBar = {
+ AppBar(
+ stringResource(R.string.groups),
+ true,
+ goBack,
+ )
+ },
+ floatingActionButton = {
+ val toastText = stringResource(R.string.shortcuts_limited, Constants.MAX_SHORTCUTS)
+ FloatingActionButton(
+ {
+ if ((groups?.size ?: 0) < Constants.MAX_SHORTCUTS)
+ goToUpsertGroupScreen("null")
+ else
+ Toast.makeText(context, toastText, Toast.LENGTH_LONG).show()
+ },
+ containerColor = MaterialTheme.colorScheme.primary,
+ contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ ) {
+ Icon(
+ painterResource(R.drawable.add),
+ stringResource(R.string.add_group),
+ )
+ }
+ },
+ ) { paddingValues ->
+ if (groups == null) {
+ Box(Modifier.fillMaxSize(), Alignment.Center) { CircularProgressIndicator() }
+ } else if (groups.isEmpty()) {
+ Box(
+ Modifier
+ .fillMaxSize()
+ .padding(horizontal = dimensionResource(R.dimen.padding_extra_large)),
+ Alignment.Center,
+ ) {
+ Text(
+ stringResource(R.string.no_groups),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.bodyLarge,
+ )
+ }
+ } else {
+ LazyColumn(
+ Modifier
+ .padding(paddingValues)
+ .padding(dimensionResource(R.dimen.padding_small))
+ .fillMaxSize(),
+ ) {
+ items(groups, { it.id }) {
+ Tile(
+ it.name,
+ content = {
+ Text(
+ if (it.ruleIds.isEmpty())
+ stringResource(R.string.all_rules)
+ else
+ pluralStringResource(
+ R.plurals.rule,
+ it.ruleIds.size,
+ it.ruleIds.size,
+ ),
+ style = MaterialTheme.typography.bodySmall,
+ fontWeight = FontWeight.Normal,
+ )
+ },
+ onClick = {
+ if (viewModel.selectedGroup == it) viewModel.selectedGroup = null
+ else viewModel.selectedGroup = it
+ },
+ buttons = {
+ IconButton({ goToUpsertGroupScreen(Json.encodeToString(it)) }) {
+ Icon(
+ painterResource(R.drawable.edit),
+ stringResource(R.string.edit_group),
+ )
+ }
+ IconButton(
+ { viewModel.showDeleteDialog = true },
+ colors = IconButtonDefaults.iconButtonColors(
+ contentColor = MaterialTheme.colorScheme.tertiary,
+ ),
+ ) {
+ Icon(
+ painterResource(R.drawable.delete),
+ stringResource(R.string.delete_group),
+ )
+ }
+ },
+ expanded = viewModel.selectedGroup == it,
+ )
+ }
+ item { Box(Modifier.height(100.dp)) {} }
+ }
+ }
+ if (viewModel.selectedGroup != null && viewModel.showDeleteDialog)
+ DeleteGroupDialog(viewModel)
+ }
+}
diff --git a/app/src/main/java/co/adityarajput/fileflow/views/screens/SettingsScreen.kt b/app/src/main/java/co/adityarajput/fileflow/views/screens/SettingsScreen.kt
index b5a086d..bb11673 100644
--- a/app/src/main/java/co/adityarajput/fileflow/views/screens/SettingsScreen.kt
+++ b/app/src/main/java/co/adityarajput/fileflow/views/screens/SettingsScreen.kt
@@ -41,6 +41,7 @@ private val permissions = listOf(
@Composable
fun SettingsScreen(
+ goToGroupsScreen: () -> Unit = {},
goToLicensesScreen: () -> Unit = {},
goToAboutScreen: () -> Unit = {},
goBack: () -> Unit = {},
@@ -188,6 +189,31 @@ fun SettingsScreen(
}
}
}
+ Card(
+ Modifier
+ .fillMaxWidth()
+ .padding(dimensionResource(R.dimen.padding_small)),
+ ) {
+ Row(
+ Modifier
+ .fillMaxWidth()
+ .clickable { goToGroupsScreen() }
+ .padding(
+ dimensionResource(R.dimen.padding_large),
+ dimensionResource(R.dimen.padding_medium),
+ ),
+ Arrangement.spacedBy(dimensionResource(R.dimen.padding_small)),
+ ) {
+ Icon(
+ painterResource(R.drawable.group),
+ stringResource(R.string.groups),
+ )
+ Text(
+ stringResource(R.string.manage_groups),
+ fontWeight = FontWeight.Medium,
+ )
+ }
+ }
Card(
Modifier
.fillMaxWidth()
diff --git a/app/src/main/java/co/adityarajput/fileflow/views/screens/UpsertGroupScreen.kt b/app/src/main/java/co/adityarajput/fileflow/views/screens/UpsertGroupScreen.kt
new file mode 100644
index 0000000..f4aaff7
--- /dev/null
+++ b/app/src/main/java/co/adityarajput/fileflow/views/screens/UpsertGroupScreen.kt
@@ -0,0 +1,214 @@
+package co.adityarajput.fileflow.views.screens
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.lifecycle.viewmodel.compose.viewModel
+import co.adityarajput.fileflow.R
+import co.adityarajput.fileflow.viewmodels.Provider
+import co.adityarajput.fileflow.viewmodels.UpsertGroupViewModel
+import co.adityarajput.fileflow.views.components.AppBar
+import co.adityarajput.fileflow.views.components.Tile
+import co.adityarajput.fileflow.views.textFieldColors
+import kotlinx.coroutines.launch
+
+@Composable
+fun UpsertGroupScreen(
+ groupString: String,
+ goBack: () -> Unit,
+ viewModel: UpsertGroupViewModel = viewModel(factory = Provider.createUGVM((groupString))),
+) {
+ val context = LocalContext.current
+ val coroutineScope = rememberCoroutineScope()
+ val rules = viewModel.rules.collectAsState().value
+
+ Scaffold(
+ topBar = {
+ AppBar(
+ stringResource(
+ if (viewModel.state.values.groupId == 0) R.string.add_group
+ else R.string.edit_group,
+ ),
+ true,
+ goBack,
+ )
+ },
+ ) { paddingValues ->
+ Column(
+ Modifier.padding(paddingValues),
+ Arrangement.SpaceBetween,
+ ) {
+ Column(
+ Modifier
+ .fillMaxWidth()
+ .weight(1f)
+ .padding(dimensionResource(R.dimen.padding_small))
+ .padding(
+ dimensionResource(R.dimen.padding_large),
+ dimensionResource(R.dimen.padding_medium),
+ ),
+ Arrangement.spacedBy(dimensionResource(R.dimen.padding_medium)),
+ ) {
+ OutlinedTextField(
+ viewModel.state.values.name,
+ { viewModel.updateForm(viewModel.state.values.copy(name = it)) },
+ Modifier.fillMaxWidth(),
+ label = { Text(stringResource(R.string.group_name)) },
+ placeholder = { Text(stringResource(R.string.group_name_placeholder)) },
+ singleLine = true,
+ colors = textFieldColors,
+ )
+ Text(
+ stringResource(R.string.rules_to_include),
+ style = MaterialTheme.typography.bodyLarge,
+ fontWeight = FontWeight.Normal,
+ )
+ val allRules = viewModel.state.values.allRules
+ Row(
+ Modifier
+ .fillMaxWidth()
+ .selectable(allRules) {
+ viewModel.updateForm(
+ viewModel.state.values.copy(allRules = !allRules),
+ )
+ },
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ RadioButton(
+ allRules,
+ null,
+ Modifier.padding(horizontal = dimensionResource(R.dimen.padding_small)),
+ )
+ Text(
+ stringResource(R.string.all_rules),
+ style = MaterialTheme.typography.labelMedium,
+ fontWeight = FontWeight.Normal,
+ )
+ }
+ Row(
+ Modifier
+ .fillMaxWidth()
+ .selectable(!allRules) {
+ viewModel.updateForm(
+ viewModel.state.values.copy(allRules = !allRules),
+ )
+ },
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ RadioButton(
+ !allRules,
+ null,
+ Modifier.padding(horizontal = dimensionResource(R.dimen.padding_small)),
+ )
+ Text(
+ stringResource(R.string.some_rules),
+ style = MaterialTheme.typography.labelMedium,
+ fontWeight = FontWeight.Normal,
+ )
+ }
+ AnimatedVisibility(!allRules) {
+ if (rules == null) {
+ Box(
+ Modifier
+ .fillMaxWidth()
+ .align(Alignment.CenterHorizontally),
+ ) {
+ CircularProgressIndicator(Modifier.align(Alignment.Center))
+ }
+ } else if (rules.isEmpty()) {
+ Text(
+ stringResource(R.string.no_rules_while_grouping),
+ Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.bodyLarge,
+ )
+ } else {
+ val selectedRuleIds = viewModel.state.values.ruleIds
+ LazyColumn(
+ Modifier
+ .fillMaxWidth()
+ .padding(horizontal = dimensionResource(R.dimen.padding_small)),
+ ) {
+ items(rules, { it.id }) {
+ Tile(
+ it.action.srcFileNamePattern,
+ stringResource(it.action.verb.resource),
+ null,
+ {
+ Text(
+ it.getDescription(),
+ style = MaterialTheme.typography.bodySmall,
+ )
+ },
+ {
+ viewModel.updateForm(
+ viewModel.state.values.copy(
+ ruleIds = if (it.id in selectedRuleIds) selectedRuleIds - it.id
+ else selectedRuleIds + it.id,
+ ),
+ )
+ },
+ { Checkbox(it.id in selectedRuleIds, null) },
+ true,
+ )
+ }
+ }
+ }
+ }
+ }
+ Row(
+ Modifier
+ .fillMaxWidth()
+ .padding(horizontal = dimensionResource(R.dimen.padding_large)),
+ Arrangement.Center,
+ Alignment.Bottom,
+ ) {
+ TextButton(
+ { goBack() },
+ Modifier
+ .fillMaxWidth(0.5f)
+ .padding(end = dimensionResource(R.dimen.padding_small)),
+ colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.primary),
+ ) {
+ Text(
+ stringResource(R.string.cancel),
+ style = MaterialTheme.typography.bodyLarge,
+ fontWeight = FontWeight.Normal,
+ )
+ }
+ TextButton(
+ {
+ coroutineScope.launch {
+ viewModel.submitForm(context)
+ goBack()
+ }
+ },
+ Modifier
+ .fillMaxWidth()
+ .padding(start = dimensionResource(R.dimen.padding_small)),
+ viewModel.state.error == null,
+ colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.secondary),
+ ) {
+ Text(
+ if (viewModel.state.values.groupId == 0) stringResource(R.string.add)
+ else stringResource(R.string.save),
+ style = MaterialTheme.typography.bodyLarge,
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/co/adityarajput/fileflow/views/screens/UpsertRuleScreen.kt b/app/src/main/java/co/adityarajput/fileflow/views/screens/UpsertRuleScreen.kt
index 33655d1..597fc17 100644
--- a/app/src/main/java/co/adityarajput/fileflow/views/screens/UpsertRuleScreen.kt
+++ b/app/src/main/java/co/adityarajput/fileflow/views/screens/UpsertRuleScreen.kt
@@ -529,10 +529,10 @@ private fun ColumnScope.ActionPage(viewModel: UpsertRuleViewModel) {
style = MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.Normal,
)
- if (viewModel.state.error == FormError.INVALID_REGEX) ErrorText(R.string.invalid_regex)
- else if (viewModel.state.error == FormError.INVALID_TEMPLATE) ErrorText(R.string.invalid_template)
- else if (viewModel.state.error == FormError.MUST_END_IN_ZIP) ErrorText(R.string.must_end_in_zip)
- else if (viewModel.state.warning == FormWarning.NO_MATCHES_IN_SRC) WarningText(R.string.pattern_doesnt_match_src_files)
+ if (viewModel.state.error == RuleFormError.INVALID_REGEX) ErrorText(R.string.invalid_regex)
+ else if (viewModel.state.error == RuleFormError.INVALID_TEMPLATE) ErrorText(R.string.invalid_template)
+ else if (viewModel.state.error == RuleFormError.MUST_END_IN_ZIP) ErrorText(R.string.must_end_in_zip)
+ else if (viewModel.state.warning == RuleFormWarning.NO_MATCHES_IN_SRC) WarningText(R.string.pattern_doesnt_match_src_files)
}
@OptIn(ExperimentalMaterial3Api::class)
@@ -669,8 +669,8 @@ private fun SchedulePage(viewModel: UpsertRuleViewModel) {
style = MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.Normal,
)
- if (viewModel.state.error == FormError.INTERVAL_TOO_SHORT) ErrorText(R.string.interval_too_short)
- else if (viewModel.state.error == FormError.INTERVAL_TOO_LONG) ErrorText(R.string.interval_too_long)
+ if (viewModel.state.error == RuleFormError.INTERVAL_TOO_SHORT) ErrorText(R.string.interval_too_short)
+ else if (viewModel.state.error == RuleFormError.INTERVAL_TOO_LONG) ErrorText(R.string.interval_too_long)
}
}
// endregion
@@ -782,8 +782,8 @@ private fun SchedulePage(viewModel: UpsertRuleViewModel) {
)
}
}
- if (viewModel.state.error == FormError.INVALID_CRON_STRING) ErrorText(R.string.invalid_cron_string)
- else if (viewModel.state.error == FormError.CRON_TOO_FREQUENT) ErrorText(R.string.cron_too_frequent)
+ if (viewModel.state.error == RuleFormError.INVALID_CRON_STRING) ErrorText(R.string.invalid_cron_string)
+ else if (viewModel.state.error == RuleFormError.CRON_TOO_FREQUENT) ErrorText(R.string.cron_too_frequent)
}
}
// endregion
diff --git a/app/src/main/res/drawable/group.xml b/app/src/main/res/drawable/group.xml
new file mode 100644
index 0000000..23e4037
--- /dev/null
+++ b/app/src/main/res/drawable/group.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/play.xml b/app/src/main/res/drawable/play.xml
new file mode 100644
index 0000000..7013afe
--- /dev/null
+++ b/app/src/main/res/drawable/play.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi/ic_shortcut.xml b/app/src/main/res/mipmap-anydpi/ic_shortcut.xml
new file mode 100644
index 0000000..f71d374
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi/ic_shortcut.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/plurals.xml b/app/src/main/res/values/plurals.xml
index 1cf6872..04a6dfd 100644
--- a/app/src/main/res/values/plurals.xml
+++ b/app/src/main/res/values/plurals.xml
@@ -1,5 +1,9 @@
+
+ - %1$d rule
+ - %1$d rules
+
- %1$d execution
- %1$d executions
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f456413..5405233 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -25,6 +25,18 @@
+
+ Groups
+ "Groups are collections of rules executable via 'shortcuts'.\n\nTap and hold FileFlow's app icon on your device's Apps Screen to access the shortcuts.\n\nNo groups currently added.\nTap + to get started."
+ Only the first %1$d app shortcuts are visible in the launcher. Please delete existing groups first.
+ Add group
+ Edit group
+
+ Delete group
+ Deletion is permanent; your group will be lost. Included rules will not be affected.
+
+
+
Licenses
@@ -82,6 +94,7 @@
Light
System
Dark
+ Manage groups
Logs copied to clipboard
Logs
Copy logs
@@ -90,6 +103,20 @@
About FileFlow
+
+ Group linked to this shortcut was edited/deleted.
+
+
+
+ Group name
+ Enter a name for the group
+ Rules to include:
+ All rules, current and future
+ Some rules
+ No rules added.\nPlease create some rules first.
+ Added to group
+
+
Cancel
Back
From f0248b1beef6d8cd9128d3a89c432be00f939a6a Mon Sep 17 00:00:00 2001
From: Aditya Rajput
Date: Tue, 7 Apr 2026 15:29:46 +0530
Subject: [PATCH 2/3] Auto-fetch initial filenames when editing rules
---
.../java/co/adityarajput/fileflow/viewmodels/Provider.kt | 1 +
.../adityarajput/fileflow/viewmodels/UpsertRuleViewModel.kt | 5 +++++
2 files changed, 6 insertions(+)
diff --git a/app/src/main/java/co/adityarajput/fileflow/viewmodels/Provider.kt b/app/src/main/java/co/adityarajput/fileflow/viewmodels/Provider.kt
index 989ac58..40c1781 100644
--- a/app/src/main/java/co/adityarajput/fileflow/viewmodels/Provider.kt
+++ b/app/src/main/java/co/adityarajput/fileflow/viewmodels/Provider.kt
@@ -29,6 +29,7 @@ object Provider {
fun createURVM(ruleString: String) = viewModelFactory {
initializer {
UpsertRuleViewModel(
+ fileFlowApplication(),
Json.decodeFromString(ruleString),
fileFlowApplication().container.repository,
)
diff --git a/app/src/main/java/co/adityarajput/fileflow/viewmodels/UpsertRuleViewModel.kt b/app/src/main/java/co/adityarajput/fileflow/viewmodels/UpsertRuleViewModel.kt
index c37541d..e25b20f 100644
--- a/app/src/main/java/co/adityarajput/fileflow/viewmodels/UpsertRuleViewModel.kt
+++ b/app/src/main/java/co/adityarajput/fileflow/viewmodels/UpsertRuleViewModel.kt
@@ -16,6 +16,7 @@ import java.time.ZonedDateTime
import java.util.concurrent.TimeUnit
class UpsertRuleViewModel(
+ context: Context,
rule: Rule?,
private val repository: Repository,
) : ViewModel() {
@@ -112,6 +113,10 @@ class UpsertRuleViewModel(
var folderPickerState by mutableStateOf(null)
+ init {
+ updateForm(context)
+ }
+
fun updateForm(context: Context, values: Values = state.values, page: FormPage = state.page) {
var currentSrcFiles: List? = null
try {
From f3d4ce05919ed80639159f1f5c9df4de3492400c Mon Sep 17 00:00:00 2001
From: Aditya Rajput
Date: Tue, 7 Apr 2026 15:49:50 +0530
Subject: [PATCH 3/3] Update metadata
---
README.md | 2 ++
app/build.gradle.kts | 4 ++--
app/src/main/res/values/strings.xml | 2 +-
metadata/en-US/changelogs/7.txt | 6 ++++++
metadata/en-US/full_description.txt | 2 +-
metadata/en-US/images/phoneScreenshots/1.png | Bin 110981 -> 136663 bytes
metadata/en-US/images/phoneScreenshots/2.png | Bin 86651 -> 93316 bytes
metadata/en-US/images/phoneScreenshots/3.png | Bin 116113 -> 141785 bytes
8 files changed, 12 insertions(+), 4 deletions(-)
create mode 100644 metadata/en-US/changelogs/7.txt
diff --git a/README.md b/README.md
index 3dd1bf0..f469804 100644
--- a/README.md
+++ b/README.md
@@ -19,8 +19,10 @@ FileFlow scans your files periodically and organizes them according to your rule
- **Actions** - Choose what to do to your files ⚙
1. Copy, move, or rename files 📁
2. Delete stale files 🗑
+ 3. Create zip archives 🗜
- **Schedule** - Choose when and how often rules run ⏰
- **History** - Recent executions are stored (locally) ⏳
+- **Shortcuts** - Execute groups of rules from your homescreen 🚀
- **Free, open-source & private**
- No ads, subscriptions, or in-app purchases 🆓
- Licensed under the [GPLv3](https://github.com/BURG3R5/FileFlow/blob/dev/LICENSE) 📃
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 5448da9..fd198c4 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -22,8 +22,8 @@ android {
applicationId = "co.adityarajput.fileflow"
minSdk = 29
targetSdk = 36
- versionCode = 6
- versionName = "1.4.0"
+ versionCode = 7
+ versionName = "1.5.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5405233..8e29ce9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,7 +1,7 @@
FileFlow
FileFlow
- 1.4.0
+ 1.5.0
About
diff --git a/metadata/en-US/changelogs/7.txt b/metadata/en-US/changelogs/7.txt
new file mode 100644
index 0000000..9386740
--- /dev/null
+++ b/metadata/en-US/changelogs/7.txt
@@ -0,0 +1,6 @@
+• feat: Add four special references for templates (check project wiki for details)
+• feat: Add "ZIP" action
+• feat: Add shortcuts that execute groups of rules
+• fix: Auto-fetch initial filenames when editing rules
+
+App shortcuts are now supported, enabling quick execution of groups of rules. Head to "Manage groups" under the app settings to set up shortcuts.
diff --git a/metadata/en-US/full_description.txt b/metadata/en-US/full_description.txt
index 06ec76e..0cac337 100644
--- a/metadata/en-US/full_description.txt
+++ b/metadata/en-US/full_description.txt
@@ -1 +1 @@
-FileFlow scans your files periodically and organizes them according to your rules.
Features:
- Rules: Use regex to precisely target files.
- Actions: Choose what to do with the files - copy, move, rename, or delete.
- Schedule: Choose when and how often rules run.
- History: Recent executions are stored (locally).
- Private: Fully offline; your data never leaves your device.
- Lightweight: Runs in the background with minimal battery and memory usage.
+FileFlow scans your files periodically and organizes them according to your rules.
Features:
- Rules: Use regex to precisely target files.
- Actions: Choose what to do with the files - copy, move, rename, zip, or delete.
- Schedule: Choose when and how often rules run.
- History: Recent executions are stored (locally).
- Shortcuts: Execute groups of rules from your homescreen.
- Private: Fully offline; your data never leaves your device.
- Lightweight: Runs in the background with minimal battery and memory usage.
diff --git a/metadata/en-US/images/phoneScreenshots/1.png b/metadata/en-US/images/phoneScreenshots/1.png
index b711b61a6231ea346521d6ae8814e0cb9e348a1c..988fb1bb58776353b4d2aa32fe0ac0603783708a 100644
GIT binary patch
literal 136663
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 |