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:
@@ -3,6 +3,7 @@ import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../models/nextcloud_item.dart';
|
||||
import '../services/upload_service.dart';
|
||||
import 'folder_browser.dart';
|
||||
import 'session_controller.dart';
|
||||
|
||||
@@ -107,6 +108,22 @@ class FilesController extends ChangeNotifier
|
||||
session.addAccountClearedListener(_onAccountCleared);
|
||||
session.addAccountActivatedListener(_onAccountActivated);
|
||||
session.addAccountReadyListener(_restoreDisplayPrefs);
|
||||
// Refreshes automatically once an upload finishes, so a file shared/
|
||||
// uploaded into the folder currently on screen shows up without a
|
||||
// manual pull-to-refresh - uploads run in an Android foreground
|
||||
// service (see UploadService's doc comment) with no other way back to
|
||||
// this controller.
|
||||
_uploadSub = UploadService.completions.listen(_onUploadCompleted);
|
||||
}
|
||||
|
||||
StreamSubscription<UploadCompletion>? _uploadSub;
|
||||
|
||||
void _onUploadCompleted(UploadCompletion completion) {
|
||||
// refreshData() always hits the network regardless of cache freshness
|
||||
// (see its own doc comment), so there's nothing to invalidate first -
|
||||
// it overwrites this folder's cache entry once the fetch lands.
|
||||
if (completion.remoteFolder != _currentFolderPath) return;
|
||||
refreshData();
|
||||
}
|
||||
|
||||
// Getters
|
||||
@@ -711,6 +728,7 @@ class FilesController extends ChangeNotifier
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_cacheRefreshTimer?.cancel();
|
||||
_uploadSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,27 @@ import '../providers/session_controller.dart';
|
||||
import 'native_channel.dart';
|
||||
import 'share_intent_service.dart';
|
||||
|
||||
/// One upload batch finishing - see [UploadService.completions].
|
||||
class UploadCompletion {
|
||||
final String remoteFolder;
|
||||
final int succeeded;
|
||||
final int failed;
|
||||
|
||||
const UploadCompletion({
|
||||
required this.remoteFolder,
|
||||
required this.succeeded,
|
||||
required this.failed,
|
||||
});
|
||||
|
||||
factory UploadCompletion.fromMap(Map<dynamic, dynamic> map) {
|
||||
return UploadCompletion(
|
||||
remoteFolder: map['remoteFolder'] as String? ?? '/',
|
||||
succeeded: (map['succeeded'] as num?)?.toInt() ?? 0,
|
||||
failed: (map['failed'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Hands a "Share to Noo" upload off to `ShareUploadService.kt`'s Android
|
||||
/// foreground service, which does the actual prepare+upload entirely on its
|
||||
/// own from there - including surviving the app being closed - with a
|
||||
@@ -14,11 +35,15 @@ import 'share_intent_service.dart';
|
||||
/// real Android Service does.
|
||||
class UploadService {
|
||||
static const _channel = MethodChannel('dev.ayushya.noo/upload_service');
|
||||
static const _statusChannel = EventChannel(
|
||||
'dev.ayushya.noo/upload_service/status',
|
||||
);
|
||||
|
||||
/// Starts uploading [files] into the active account's current folder.
|
||||
/// Fire-and-forget from Dart's perspective - once this returns, the
|
||||
/// service owns the rest and reports progress/completion/cancellation
|
||||
/// through its own notification, not back to the app.
|
||||
/// through its own notification, not back to the app. [completions] is
|
||||
/// the one thing it does report back, for `FilesController` to act on.
|
||||
static Future<void> startUpload(
|
||||
SessionController session,
|
||||
FilesController files,
|
||||
@@ -37,4 +62,15 @@ class UploadService {
|
||||
'remoteFolder': files.currentFolderPath,
|
||||
});
|
||||
}
|
||||
|
||||
/// Fires once per finished batch (at least one file uploaded) from
|
||||
/// `UploadEventBus` (Kotlin) - lets `FilesController` refresh the
|
||||
/// destination folder if it's the one currently on screen, instead of the
|
||||
/// list only updating on a manual pull-to-refresh. One shared stream,
|
||||
/// deliberately: see `SyncService.statusStream`'s identical doc comment -
|
||||
/// `EventChannel.receiveBroadcastStream()` opens its own native
|
||||
/// subscription per call, and the native side only keeps the latest one.
|
||||
static final Stream<UploadCompletion> completions = _statusChannel
|
||||
.receiveBroadcastStream()
|
||||
.map((event) => UploadCompletion.fromMap(event as Map<dynamic, dynamic>));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user