aboutsummaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
authorLena <lena@omega>2026-08-23 00:00:00 +0000
committerLena <lena@omega>2026-08-23 00:00:00 +0000
commitca9037ccf3030df287e52b1fa92ab7eb0871eec2 (patch)
tree4075e139a1c49682c4b06641d2d5b8c906f7e2c2 /app/src
parente8c6c4ebb3d76ee280043bb66cf499584c591310 (diff)
downloadrsend-ca9037ccf3030df287e52b1fa92ab7eb0871eec2.tar.gz
app: read the dashboard off the main thread
Every value the dashboard draws comes from an app-private file, and it read all of them during layout, on every resume and at the end of every sync. Scheduler.apply did the same at process start, where it also opens WorkManager's database. Read on a worker thread and draw the result; overlapping refreshes are settled by generation, the same way the log viewer already settles its own.
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/java/invalid/lena/rsend/App.kt7
-rw-r--r--app/src/main/java/invalid/lena/rsend/MainActivity.kt50
2 files changed, 44 insertions, 13 deletions
diff --git a/app/src/main/java/invalid/lena/rsend/App.kt b/app/src/main/java/invalid/lena/rsend/App.kt
index 0251d7d..443229d 100644
--- a/app/src/main/java/invalid/lena/rsend/App.kt
+++ b/app/src/main/java/invalid/lena/rsend/App.kt
@@ -3,6 +3,7 @@ package invalid.lena.rsend
import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
+import kotlin.concurrent.thread
// App creates the notification channel used by the foreground sync service.
class App : Application() {
@@ -13,8 +14,10 @@ class App : Application() {
getSystemService(NotificationManager::class.java).createNotificationChannel(ch)
// Apply on every process start, including one launched by an old
// WorkManager request after an app upgrade. This retires stale unique
- // names without waiting for the dashboard to be opened.
- Scheduler.apply(this)
+ // names without waiting for the dashboard to be opened. Off the main
+ // thread: it reads the config and opens WorkManager's database, and
+ // nothing here has to finish before the first screen draws.
+ thread { Scheduler.apply(this) }
}
companion object {
diff --git a/app/src/main/java/invalid/lena/rsend/MainActivity.kt b/app/src/main/java/invalid/lena/rsend/MainActivity.kt
index 875afdf..06270ca 100644
--- a/app/src/main/java/invalid/lena/rsend/MainActivity.kt
+++ b/app/src/main/java/invalid/lena/rsend/MainActivity.kt
@@ -62,6 +62,10 @@ class MainActivity : AppCompatActivity() {
private var jobState: String = "none"
private var recoveryShown = false
+ // Settles overlapping refreshes: a resume and a finishing sync can start
+ // one each, and only the newest read may draw.
+ private var generation = 0
+
private val notifPerm =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { refresh() }
@@ -221,15 +225,11 @@ class MainActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
- Scheduler.apply(this)
refresh()
- showRecoveryNotice()
}
- private fun showRecoveryNotice() {
- if (recoveryShown) return
- val message = Config.recoveryMessage(this).trim()
- if (message.isEmpty()) return
+ private fun showRecoveryNotice(message: String) {
+ if (recoveryShown || message.isEmpty()) return
recoveryShown = true
AlertDialog.Builder(this)
.setTitle("Configuration recovered")
@@ -277,12 +277,40 @@ class MainActivity : AppCompatActivity() {
scheduleValue.text = "$sched, job $jobState"
}
+ // One read of everything the dashboard draws.
+ private data class Dashboard(
+ val cfg: Config,
+ val lastSync: String,
+ val keyExists: Boolean,
+ val recovery: String,
+ )
+
+ // Every field above comes from an app-private file, so read them on a worker
+ // thread and draw the result. Reading them during layout was the same
+ // main-thread stall the WorkManager query was moved off.
private fun refresh() {
- val cfg = Config.load(this)
+ val current = ++generation
+ thread {
+ Scheduler.apply(this)
+ val d = Dashboard(
+ cfg = Config.load(this),
+ lastSync = LastSync.get(this),
+ keyExists = Keys.exists(this),
+ recovery = Config.recoveryMessage(this).trim(),
+ )
+ runOnUiThread {
+ if (isFinishing || isDestroyed || current != generation) return@runOnUiThread
+ draw(d)
+ }
+ }
+ }
- lastSync.text = "Last sync: ${LastSync.get(this)}"
+ private fun draw(d: Dashboard) {
+ val cfg = d.cfg
- keyValue.text = if (Keys.exists(this)) "Generated, tap to view" else "Not created, tap to create"
+ lastSync.text = "Last sync: ${d.lastSync}"
+
+ keyValue.text = if (d.keyExists) "Generated, tap to view" else "Not created, tap to create"
schedule = cfg.schedule
renderSchedule(cfg.schedule)
@@ -324,6 +352,8 @@ class MainActivity : AppCompatActivity() {
}
}
}
+
+ showRecoveryNotice(d.recovery)
}
// addRow inflates one tappable list row: icon, title, subtitle, chip.
@@ -451,7 +481,6 @@ class MainActivity : AppCompatActivity() {
runOnUiThread {
if (isFinishing || isDestroyed) return@runOnUiThread
if (error == null) {
- Scheduler.apply(this)
refresh()
Toast.makeText(this, "New key generated.", Toast.LENGTH_SHORT).show()
showKey()
@@ -497,7 +526,6 @@ class MainActivity : AppCompatActivity() {
runOnUiThread {
if (isFinishing || isDestroyed) return@runOnUiThread
if (error == null) {
- Scheduler.apply(this)
refresh()
Toast.makeText(this, "Key imported.", Toast.LENGTH_SHORT).show()
showKey()