Skip to content

Commit 7291617

Browse files
authored
feat: Add shortcuts that execute groups of rules (#25)
2 parents 2be7c86 + f3d4ce0 commit 7291617

37 files changed

+1030
-45
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ FileFlow scans your files periodically and organizes them according to your rule
1919
- **Actions** - Choose what to do to your files ⚙
2020
1. Copy, move, or rename files 📁
2121
2. Delete stale files 🗑
22+
3. Create zip archives 🗜
2223
- **Schedule** - Choose when and how often rules run ⏰
2324
- **History** - Recent executions are stored (locally) ⏳
25+
- **Shortcuts** - Execute groups of rules from your homescreen 🚀
2426
- **Free, open-source & private**
2527
- No ads, subscriptions, or in-app purchases 🆓
2628
- Licensed under the [GPLv3](https://github.com/BURG3R5/FileFlow/blob/dev/LICENSE) 📃

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ android {
2222
applicationId = "co.adityarajput.fileflow"
2323
minSdk = 29
2424
targetSdk = 36
25-
versionCode = 6
26-
versionName = "1.4.0"
25+
versionCode = 7
26+
versionName = "1.5.0"
2727

2828
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2929
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
{
2+
"formatVersion": 1,
3+
"database": {
4+
"version": 6,
5+
"identityHash": "b0e045c438054bcca59175062168639b",
6+
"entities": [
7+
{
8+
"tableName": "rules",
9+
"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)",
10+
"fields": [
11+
{
12+
"fieldPath": "action",
13+
"columnName": "action",
14+
"affinity": "TEXT",
15+
"notNull": true
16+
},
17+
{
18+
"fieldPath": "enabled",
19+
"columnName": "enabled",
20+
"affinity": "INTEGER",
21+
"notNull": true
22+
},
23+
{
24+
"fieldPath": "executions",
25+
"columnName": "executions",
26+
"affinity": "INTEGER",
27+
"notNull": true
28+
},
29+
{
30+
"fieldPath": "interval",
31+
"columnName": "interval",
32+
"affinity": "INTEGER",
33+
"defaultValue": "3600000"
34+
},
35+
{
36+
"fieldPath": "cronString",
37+
"columnName": "cronString",
38+
"affinity": "TEXT",
39+
"defaultValue": "NULL"
40+
},
41+
{
42+
"fieldPath": "id",
43+
"columnName": "id",
44+
"affinity": "INTEGER",
45+
"notNull": true
46+
}
47+
],
48+
"primaryKey": {
49+
"autoGenerate": true,
50+
"columnNames": [
51+
"id"
52+
]
53+
}
54+
},
55+
{
56+
"tableName": "executions",
57+
"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)",
58+
"fields": [
59+
{
60+
"fieldPath": "fileName",
61+
"columnName": "fileName",
62+
"affinity": "TEXT",
63+
"notNull": true
64+
},
65+
{
66+
"fieldPath": "verb",
67+
"columnName": "verb",
68+
"affinity": "TEXT",
69+
"notNull": true,
70+
"defaultValue": "'MOVE'"
71+
},
72+
{
73+
"fieldPath": "timestamp",
74+
"columnName": "timestamp",
75+
"affinity": "INTEGER",
76+
"notNull": true
77+
},
78+
{
79+
"fieldPath": "id",
80+
"columnName": "id",
81+
"affinity": "INTEGER",
82+
"notNull": true
83+
}
84+
],
85+
"primaryKey": {
86+
"autoGenerate": true,
87+
"columnNames": [
88+
"id"
89+
]
90+
}
91+
},
92+
{
93+
"tableName": "groups",
94+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `ruleIds` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
95+
"fields": [
96+
{
97+
"fieldPath": "name",
98+
"columnName": "name",
99+
"affinity": "TEXT",
100+
"notNull": true
101+
},
102+
{
103+
"fieldPath": "ruleIds",
104+
"columnName": "ruleIds",
105+
"affinity": "TEXT",
106+
"notNull": true
107+
},
108+
{
109+
"fieldPath": "id",
110+
"columnName": "id",
111+
"affinity": "INTEGER",
112+
"notNull": true
113+
}
114+
],
115+
"primaryKey": {
116+
"autoGenerate": true,
117+
"columnNames": [
118+
"id"
119+
]
120+
}
121+
}
122+
],
123+
"setupQueries": [
124+
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
125+
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b0e045c438054bcca59175062168639b')"
126+
]
127+
}
128+
}

app/src/main/AndroidManifest.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,27 @@
3535
<category android:name="android.intent.category.LAUNCHER" />
3636
</intent-filter>
3737
</activity>
38+
<activity
39+
android:name=".ShortcutActivity"
40+
android:excludeFromRecents="true"
41+
android:exported="true"
42+
android:launchMode="singleInstance"
43+
android:noHistory="true"
44+
android:theme="@android:style/Theme.NoDisplay" />
45+
3846
<receiver
3947
android:name=".AlarmReceiver"
4048
android:exported="false">
4149
<intent-filter>
4250
<action android:name="co.adityarajput.fileflow.EXECUTE_RULE" />
4351
</intent-filter>
4452
</receiver>
53+
<receiver
54+
android:name=".ShortcutReceiver"
55+
android:exported="false">
56+
<intent-filter>
57+
<action android:name="co.adityarajput.fileflow.EXECUTE_GROUP" />
58+
</intent-filter>
59+
</receiver>
4560
</application>
4661
</manifest>

app/src/main/java/co/adityarajput/fileflow/AlarmReceiver.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class AlarmReceiver : BroadcastReceiver() {
2222
if (rule == null || !rule.enabled)
2323
return@launch
2424

25-
Logger.d("Worker", "Executing $rule")
25+
Logger.d("AlarmReceiver", "Executing $rule")
2626
rule.action.execute(context) {
2727
repository.registerExecution(
2828
rule,

app/src/main/java/co/adityarajput/fileflow/Constants.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,17 @@ object Constants {
1212
const val EXTRA_RULE_ID = "extra_rule_id"
1313
const val MAX_CRON_EXECUTIONS_PER_HOUR = 4
1414

15+
const val ACTION_EXECUTE_GROUP = "co.adityarajput.fileflow.EXECUTE_GROUP"
16+
const val EXTRA_GROUP_ID = "extra_group_id"
17+
1518
const val LOG_SIZE = 100
1619

1720
const val ONE_HOUR_IN_MILLIS = 3_600_000L
21+
22+
/**
23+
* Max amount of app shortcuts visible when launcher app icon is long-pressed.
24+
*
25+
* There *is* a `ShortcutManagerCompat.getMaxShortcutCountPerActivity` method, but it *lies*.
26+
*/
27+
const val MAX_SHORTCUTS = 4
1828
}

app/src/main/java/co/adityarajput/fileflow/FileFlowApplication.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.app.Application
44
import co.adityarajput.fileflow.data.AppContainer
55
import co.adityarajput.fileflow.utils.isDebugBuild
66
import co.adityarajput.fileflow.utils.scheduleWork
7+
import co.adityarajput.fileflow.utils.upsertShortcuts
78
import kotlinx.coroutines.CoroutineScope
89
import kotlinx.coroutines.Dispatchers
910
import kotlinx.coroutines.launch
@@ -23,6 +24,7 @@ class FileFlowApplication : Application() {
2324

2425
CoroutineScope(Dispatchers.IO).launch {
2526
scheduleWork()
27+
upsertShortcuts()
2628
}
2729
}
2830
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package co.adityarajput.fileflow
2+
3+
import android.app.Activity
4+
import android.content.Intent
5+
import android.os.Bundle
6+
7+
class ShortcutActivity : Activity() {
8+
override fun onCreate(savedInstanceState: Bundle?) {
9+
super.onCreate(savedInstanceState)
10+
sendBroadcast(
11+
Intent(Constants.ACTION_EXECUTE_GROUP).apply {
12+
setPackage(packageName)
13+
putExtra(Constants.EXTRA_GROUP_ID, intent.getIntExtra(Constants.EXTRA_GROUP_ID, -1))
14+
},
15+
)
16+
finish()
17+
}
18+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package co.adityarajput.fileflow
2+
3+
import android.content.BroadcastReceiver
4+
import android.content.Context
5+
import android.content.Intent
6+
import co.adityarajput.fileflow.data.AppContainer
7+
import co.adityarajput.fileflow.data.models.Execution
8+
import co.adityarajput.fileflow.utils.Logger
9+
import kotlinx.coroutines.CoroutineScope
10+
import kotlinx.coroutines.Dispatchers
11+
import kotlinx.coroutines.launch
12+
13+
class ShortcutReceiver : BroadcastReceiver() {
14+
override fun onReceive(context: Context, intent: Intent) {
15+
Logger.d("ShortcutReceiver", "Received intent with action: ${intent.action}")
16+
17+
if (intent.action != Constants.ACTION_EXECUTE_GROUP)
18+
return
19+
20+
CoroutineScope(Dispatchers.IO).launch {
21+
val repository = AppContainer(context).repository
22+
val groupId = intent.getIntExtra(Constants.EXTRA_GROUP_ID, -1)
23+
val (group, rules) = repository.group(groupId)
24+
25+
if (group == null)
26+
return@launch
27+
28+
Logger.d("ShortcutReceiver", "Executing $group")
29+
for (rule in rules) {
30+
Logger.d("ShortcutReceiver", "Executing $rule")
31+
rule.action.execute(context) {
32+
repository.registerExecution(
33+
rule,
34+
Execution(it, rule.action.verb),
35+
)
36+
}
37+
}
38+
}
39+
}
40+
}

app/src/main/java/co/adityarajput/fileflow/data/AppContainer.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class AppContainer(private val context: Context) {
1212
Repository(
1313
FileFlowDatabase.getDatabase(context).ruleDao(),
1414
FileFlowDatabase.getDatabase(context).executionDao(),
15+
FileFlowDatabase.getDatabase(context).groupDao(),
1516
)
1617
}
1718

0 commit comments

Comments
 (0)