Refresh Files list automatically when an upload finishes
Build APK / build (push) Successful in 5m32s
Build APK / build (push) Successful in 5m32s
Uploads run entirely in ShareUploadService.kt, an Android foreground service with no channel back to Dart once started - by design, so closing the app mid-upload doesn't interrupt it. That meant FilesController had no way to know a batch had finished, so a newly uploaded file only appeared after a manual pull-to-refresh. Adds a one-shot completion signal instead of a full stream: the service publishes into a new UploadEventBus (in-process pub/sub, mirroring SyncStatusBus) once a batch finishes with at least one success, MainActivity forwards it to Dart over a new dev.ayushya.noo/upload_service/status EventChannel, and FilesController (subscribed in its own constructor, same pattern OfflineController already uses for sync completion) calls refreshData() when the event's destination folder matches currentFolderPath. Covers both upload entry points (share-to-Noo and Files' own "+" -> Upload file), since they already share the same ShareUploadView/UploadService path. Move/Copy needed no fix - ItemOperations already calls files.invalidateCache() + refreshData() on success. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,7 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
private val newPickChannelName = "dev.ayushya.noo/pick_intent/new"
|
||||
private val syncServiceChannelName = "dev.ayushya.noo/sync_service"
|
||||
private val syncStatusChannelName = "dev.ayushya.noo/sync_service/status"
|
||||
private val uploadStatusChannelName = "dev.ayushya.noo/upload_service/status"
|
||||
private val notificationPermissionRequestCode = 4202
|
||||
// Lazy, not a field initializer - `packageName` reads through the
|
||||
// Activity's base Context, which isn't attached yet while this class's
|
||||
@@ -66,6 +67,7 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
private var newShareSink: EventChannel.EventSink? = null
|
||||
private var newPickSink: EventChannel.EventSink? = null
|
||||
private var syncStatusListener: ((SyncStatusBus.Status) -> Unit)? = null
|
||||
private var uploadCompletedListener: ((UploadEventBus.Completed) -> Unit)? = null
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
@@ -157,6 +159,29 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
syncStatusListener = null
|
||||
}
|
||||
})
|
||||
|
||||
EventChannel(flutterEngine.dartExecutor.binaryMessenger, uploadStatusChannelName)
|
||||
.setStreamHandler(object : EventChannel.StreamHandler {
|
||||
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
|
||||
val listener: (UploadEventBus.Completed) -> Unit = { completed ->
|
||||
mainHandler.post {
|
||||
events.success(
|
||||
mapOf(
|
||||
"remoteFolder" to completed.remoteFolder,
|
||||
"succeeded" to completed.succeeded,
|
||||
"failed" to completed.failed,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
uploadCompletedListener = listener
|
||||
UploadEventBus.subscribe(listener)
|
||||
}
|
||||
override fun onCancel(arguments: Any?) {
|
||||
uploadCompletedListener?.let { UploadEventBus.unsubscribe(it) }
|
||||
uploadCompletedListener = null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// [accountIdOverride] is the account Dart is asking about - needed
|
||||
|
||||
@@ -185,6 +185,13 @@ class ShareUploadService : Service() {
|
||||
else -> "Uploaded $succeeded of ${batch.files.size} files - $failed failed"
|
||||
}
|
||||
manager().notify(NOTIFICATION_ID, buildFinalNotification(finalText))
|
||||
// batch.remoteFolder, not the locally-normalized cleanFolder - Dart's
|
||||
// FilesController.currentFolderPath never carries the trailing
|
||||
// slash cleanFolder adds, so publishing the original value lets the
|
||||
// Dart side compare them directly with no reformatting of its own.
|
||||
if (succeeded > 0) {
|
||||
UploadEventBus.publishCompleted(batch.remoteFolder, succeeded, failed)
|
||||
}
|
||||
|
||||
if (queue.pendingCount == 0) {
|
||||
stopForeground(STOP_FOREGROUND_DETACH)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package dev.ayushya.noo
|
||||
|
||||
/**
|
||||
* In-memory, in-process pub/sub for "an upload batch just finished" -
|
||||
* [ShareUploadService] publishes into this, `MainActivity`'s
|
||||
* `dev.ayushya.noo/upload_service/status` EventChannel forwards it to Dart
|
||||
* so `FilesController` can refresh the destination folder if it's the one
|
||||
* currently on screen, without a manual pull-to-refresh. See
|
||||
* [SyncStatusBus]'s identical shape/rationale - the service and the
|
||||
* Activity always run in the same process, so plain in-memory pub/sub is
|
||||
* enough. Unlike that bus, this only ever carries discrete "just finished"
|
||||
* events, not ongoing state, so there's nothing to replay to a listener
|
||||
* that subscribes late.
|
||||
*/
|
||||
object UploadEventBus {
|
||||
data class Completed(
|
||||
val remoteFolder: String,
|
||||
val succeeded: Int,
|
||||
val failed: Int,
|
||||
)
|
||||
|
||||
private val listeners = mutableListOf<(Completed) -> Unit>()
|
||||
|
||||
@Synchronized
|
||||
fun subscribe(listener: (Completed) -> Unit) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun unsubscribe(listener: (Completed) -> Unit) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun publishCompleted(remoteFolder: String, succeeded: Int, failed: Int) {
|
||||
val event = Completed(remoteFolder, succeeded, failed)
|
||||
listeners.toList().forEach { it(event) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user