loader/completer review
This commit is contained in:
parent
f0e048e340
commit
b5fea82fce
12 changed files with 79 additions and 86 deletions
|
@ -461,17 +461,17 @@ class AvesEntry with AvesEntryBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> delete() {
|
Future<bool> delete() {
|
||||||
final completer = Completer<bool>();
|
final opCompleter = Completer<bool>();
|
||||||
mediaEditService.delete(entries: {this}).listen(
|
mediaEditService.delete(entries: {this}).listen(
|
||||||
(event) => completer.complete(event.success && !event.skipped),
|
(event) => opCompleter.complete(event.success && !event.skipped),
|
||||||
onError: completer.completeError,
|
onError: opCompleter.completeError,
|
||||||
onDone: () {
|
onDone: () {
|
||||||
if (!completer.isCompleted) {
|
if (!opCompleter.isCompleted) {
|
||||||
completer.complete(false);
|
opCompleter.complete(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return completer.future;
|
return opCompleter.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when the MIME type or the image itself changed (e.g. after rotation)
|
// when the MIME type or the image itself changed (e.g. after rotation)
|
||||||
|
|
|
@ -23,6 +23,7 @@ class MediaStoreSource extends CollectionSource {
|
||||||
int? _lastGeneration;
|
int? _lastGeneration;
|
||||||
SourceScope _loadedScope, _targetScope;
|
SourceScope _loadedScope, _targetScope;
|
||||||
bool _canAnalyze = true;
|
bool _canAnalyze = true;
|
||||||
|
Future<void>? _essentialLoader;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
set canAnalyze(bool enabled) => _canAnalyze = enabled;
|
set canAnalyze(bool enabled) => _canAnalyze = enabled;
|
||||||
|
@ -41,7 +42,8 @@ class MediaStoreSource extends CollectionSource {
|
||||||
}) async {
|
}) async {
|
||||||
_targetScope = scope;
|
_targetScope = scope;
|
||||||
await reportService.log('$runtimeType init target scope=$scope');
|
await reportService.log('$runtimeType init target scope=$scope');
|
||||||
await _loadEssentials();
|
_essentialLoader ??= _loadEssentials();
|
||||||
|
await _essentialLoader;
|
||||||
addDirectories(albums: settings.pinnedFilters.whereType<AlbumFilter>().map((v) => v.album).toSet());
|
addDirectories(albums: settings.pinnedFilters.whereType<AlbumFilter>().map((v) => v.album).toSet());
|
||||||
await updateGeneration();
|
await updateGeneration();
|
||||||
unawaited(_loadEntries(
|
unawaited(_loadEntries(
|
||||||
|
@ -50,11 +52,7 @@ class MediaStoreSource extends CollectionSource {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _areEssentialsLoaded = false;
|
|
||||||
|
|
||||||
Future<void> _loadEssentials() async {
|
Future<void> _loadEssentials() async {
|
||||||
if (_areEssentialsLoaded) return;
|
|
||||||
|
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
state = SourceState.loading;
|
state = SourceState.loading;
|
||||||
await localMediaDb.init();
|
await localMediaDb.init();
|
||||||
|
@ -72,7 +70,6 @@ class MediaStoreSource extends CollectionSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await loadDates();
|
await loadDates();
|
||||||
_areEssentialsLoaded = true;
|
|
||||||
debugPrint('$runtimeType load essentials complete in ${stopwatch.elapsed.inMilliseconds}ms');
|
debugPrint('$runtimeType load essentials complete in ${stopwatch.elapsed.inMilliseconds}ms');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,7 +257,7 @@ class MediaStoreSource extends CollectionSource {
|
||||||
// sometimes yields an entry with its temporary path: `/data/sec/camera/!@#$%^..._temp.jpg`
|
// sometimes yields an entry with its temporary path: `/data/sec/camera/!@#$%^..._temp.jpg`
|
||||||
@override
|
@override
|
||||||
Future<Set<String>> refreshUris(Set<String> changedUris, {AnalysisController? analysisController}) async {
|
Future<Set<String>> refreshUris(Set<String> changedUris, {AnalysisController? analysisController}) async {
|
||||||
if (!canRefresh || !_areEssentialsLoaded || !isReady) return changedUris;
|
if (!canRefresh || _essentialLoader == null || !isReady) return changedUris;
|
||||||
|
|
||||||
state = SourceState.loading;
|
state = SourceState.loading;
|
||||||
|
|
||||||
|
|
|
@ -24,18 +24,18 @@ mixin TrashMixin on SourceBase {
|
||||||
if (expiredEntries.isEmpty) return {};
|
if (expiredEntries.isEmpty) return {};
|
||||||
|
|
||||||
final processed = <ImageOpEvent>{};
|
final processed = <ImageOpEvent>{};
|
||||||
final completer = Completer<Set<String>>();
|
final opCompleter = Completer<Set<String>>();
|
||||||
mediaEditService.delete(entries: expiredEntries).listen(
|
mediaEditService.delete(entries: expiredEntries).listen(
|
||||||
processed.add,
|
processed.add,
|
||||||
onError: completer.completeError,
|
onError: opCompleter.completeError,
|
||||||
onDone: () async {
|
onDone: () async {
|
||||||
final successOps = processed.where((e) => e.success).toSet();
|
final successOps = processed.where((e) => e.success).toSet();
|
||||||
final deletedOps = successOps.where((e) => !e.skipped).toSet();
|
final deletedOps = successOps.where((e) => !e.skipped).toSet();
|
||||||
final deletedUris = deletedOps.map((event) => event.uri).toSet();
|
final deletedUris = deletedOps.map((event) => event.uri).toSet();
|
||||||
completer.complete(deletedUris);
|
opCompleter.complete(deletedUris);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return await completer.future;
|
return await opCompleter.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Set<AvesEntry>> recoverUntrackedTrashItems() async {
|
Future<Set<AvesEntry>> recoverUntrackedTrashItems() async {
|
||||||
|
|
|
@ -104,21 +104,21 @@ class PlatformAppService implements AppService {
|
||||||
@override
|
@override
|
||||||
Future<Map<String, dynamic>> edit(String uri, String mimeType) async {
|
Future<Map<String, dynamic>> edit(String uri, String mimeType) async {
|
||||||
try {
|
try {
|
||||||
final completer = Completer<Map?>();
|
final opCompleter = Completer<Map?>();
|
||||||
_stream.receiveBroadcastStream(<String, dynamic>{
|
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'edit',
|
'op': 'edit',
|
||||||
'uri': uri,
|
'uri': uri,
|
||||||
'mimeType': mimeType,
|
'mimeType': mimeType,
|
||||||
}).listen(
|
}).listen(
|
||||||
(data) => completer.complete(data as Map?),
|
(data) => opCompleter.complete(data as Map?),
|
||||||
onError: completer.completeError,
|
onError: opCompleter.completeError,
|
||||||
onDone: () {
|
onDone: () {
|
||||||
if (!completer.isCompleted) completer.complete({'error': 'cancelled'});
|
if (!opCompleter.isCompleted) opCompleter.complete({'error': 'cancelled'});
|
||||||
},
|
},
|
||||||
cancelOnError: true,
|
cancelOnError: true,
|
||||||
);
|
);
|
||||||
// `await` here, so that `completeError` will be caught below
|
// `await` here, so that `completeError` will be caught below
|
||||||
final result = await completer.future;
|
final result = await opCompleter.future;
|
||||||
if (result == null) return {'error': 'cancelled'};
|
if (result == null) return {'error': 'cancelled'};
|
||||||
return result.cast<String, dynamic>();
|
return result.cast<String, dynamic>();
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
|
|
|
@ -24,32 +24,32 @@ class ServicePolicy {
|
||||||
int priority = ServiceCallPriority.normal,
|
int priority = ServiceCallPriority.normal,
|
||||||
Object? key,
|
Object? key,
|
||||||
}) {
|
}) {
|
||||||
Completer<T> completer;
|
Completer<T> taskCompleter;
|
||||||
_Task<T> task;
|
_Task<T> task;
|
||||||
key ??= platformCall.hashCode;
|
key ??= platformCall.hashCode;
|
||||||
final toResume = _paused.remove(key);
|
final toResume = _paused.remove(key);
|
||||||
if (toResume != null) {
|
if (toResume != null) {
|
||||||
priority = toResume.$1;
|
priority = toResume.$1;
|
||||||
task = toResume.$2 as _Task<T>;
|
task = toResume.$2 as _Task<T>;
|
||||||
completer = task.completer;
|
taskCompleter = task.completer;
|
||||||
} else {
|
} else {
|
||||||
completer = Completer<T>();
|
taskCompleter = Completer<T>();
|
||||||
task = _Task<T>(
|
task = _Task<T>(
|
||||||
() async {
|
() async {
|
||||||
try {
|
try {
|
||||||
completer.complete(await platformCall());
|
taskCompleter.complete(await platformCall());
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
completer.completeError(error, stack);
|
taskCompleter.completeError(error, stack);
|
||||||
}
|
}
|
||||||
_runningQueue.remove(key);
|
_runningQueue.remove(key);
|
||||||
_pickNext();
|
_pickNext();
|
||||||
},
|
},
|
||||||
completer,
|
taskCompleter,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_getQueue(priority)[key] = task;
|
_getQueue(priority)[key] = task;
|
||||||
_pickNext();
|
_pickNext();
|
||||||
return completer.future;
|
return taskCompleter.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<T>? resume<T>(Object key) {
|
Future<T>? resume<T>(Object key) {
|
||||||
|
|
|
@ -47,23 +47,23 @@ class IntentService {
|
||||||
|
|
||||||
static Future<Set<CollectionFilter>?> pickCollectionFilters(Set<CollectionFilter>? initialFilters) async {
|
static Future<Set<CollectionFilter>?> pickCollectionFilters(Set<CollectionFilter>? initialFilters) async {
|
||||||
try {
|
try {
|
||||||
final completer = Completer<Set<CollectionFilter>?>();
|
final opCompleter = Completer<Set<CollectionFilter>?>();
|
||||||
_stream.receiveBroadcastStream(<String, dynamic>{
|
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'pickCollectionFilters',
|
'op': 'pickCollectionFilters',
|
||||||
'initialFilters': initialFilters?.map((filter) => filter.toJson()).toList(),
|
'initialFilters': initialFilters?.map((filter) => filter.toJson()).toList(),
|
||||||
}).listen(
|
}).listen(
|
||||||
(data) {
|
(data) {
|
||||||
final result = (data as List?)?.cast<String>().map(CollectionFilter.fromJson).nonNulls.toSet();
|
final result = (data as List?)?.cast<String>().map(CollectionFilter.fromJson).nonNulls.toSet();
|
||||||
completer.complete(result);
|
opCompleter.complete(result);
|
||||||
},
|
},
|
||||||
onError: completer.completeError,
|
onError: opCompleter.completeError,
|
||||||
onDone: () {
|
onDone: () {
|
||||||
if (!completer.isCompleted) completer.complete(null);
|
if (!opCompleter.isCompleted) opCompleter.complete(null);
|
||||||
},
|
},
|
||||||
cancelOnError: true,
|
cancelOnError: true,
|
||||||
);
|
);
|
||||||
// `await` here, so that `completeError` will be caught below
|
// `await` here, so that `completeError` will be caught below
|
||||||
return await completer.future;
|
return await opCompleter.future;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,7 @@ class PlatformMediaFetchService implements MediaFetchService {
|
||||||
BytesReceivedCallback? onBytesReceived,
|
BytesReceivedCallback? onBytesReceived,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final completer = Completer<Uint8List>.sync();
|
final opCompleter = Completer<Uint8List>();
|
||||||
final sink = OutputBuffer();
|
final sink = OutputBuffer();
|
||||||
var bytesReceived = 0;
|
var bytesReceived = 0;
|
||||||
_byteStream.receiveBroadcastStream(<String, dynamic>{
|
_byteStream.receiveBroadcastStream(<String, dynamic>{
|
||||||
|
@ -139,20 +139,20 @@ class PlatformMediaFetchService implements MediaFetchService {
|
||||||
try {
|
try {
|
||||||
onBytesReceived(bytesReceived, sizeBytes);
|
onBytesReceived(bytesReceived, sizeBytes);
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
completer.completeError(error, stack);
|
opCompleter.completeError(error, stack);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: completer.completeError,
|
onError: opCompleter.completeError,
|
||||||
onDone: () {
|
onDone: () {
|
||||||
sink.close();
|
sink.close();
|
||||||
completer.complete(sink.bytes);
|
opCompleter.complete(sink.bytes);
|
||||||
},
|
},
|
||||||
cancelOnError: true,
|
cancelOnError: true,
|
||||||
);
|
);
|
||||||
// `await` here, so that `completeError` will be caught below
|
// `await` here, so that `completeError` will be caught below
|
||||||
return await completer.future;
|
return await opCompleter.future;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
if (_isUnknownVisual(mimeType)) {
|
if (_isUnknownVisual(mimeType)) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
|
|
|
@ -265,20 +265,20 @@ class PlatformStorageService implements StorageService {
|
||||||
@override
|
@override
|
||||||
Future<bool> requestDirectoryAccess(String path) async {
|
Future<bool> requestDirectoryAccess(String path) async {
|
||||||
try {
|
try {
|
||||||
final completer = Completer<bool>();
|
final opCompleter = Completer<bool>();
|
||||||
_stream.receiveBroadcastStream(<String, dynamic>{
|
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'requestDirectoryAccess',
|
'op': 'requestDirectoryAccess',
|
||||||
'path': path,
|
'path': path,
|
||||||
}).listen(
|
}).listen(
|
||||||
(data) => completer.complete(data as bool),
|
(data) => opCompleter.complete(data as bool),
|
||||||
onError: completer.completeError,
|
onError: opCompleter.completeError,
|
||||||
onDone: () {
|
onDone: () {
|
||||||
if (!completer.isCompleted) completer.complete(false);
|
if (!opCompleter.isCompleted) opCompleter.complete(false);
|
||||||
},
|
},
|
||||||
cancelOnError: true,
|
cancelOnError: true,
|
||||||
);
|
);
|
||||||
// `await` here, so that `completeError` will be caught below
|
// `await` here, so that `completeError` will be caught below
|
||||||
return await completer.future;
|
return await opCompleter.future;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
|
@ -289,21 +289,21 @@ class PlatformStorageService implements StorageService {
|
||||||
@override
|
@override
|
||||||
Future<bool> requestMediaFileAccess(List<String> uris, List<String> mimeTypes) async {
|
Future<bool> requestMediaFileAccess(List<String> uris, List<String> mimeTypes) async {
|
||||||
try {
|
try {
|
||||||
final completer = Completer<bool>();
|
final opCompleter = Completer<bool>();
|
||||||
_stream.receiveBroadcastStream(<String, dynamic>{
|
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'requestMediaFileAccess',
|
'op': 'requestMediaFileAccess',
|
||||||
'uris': uris,
|
'uris': uris,
|
||||||
'mimeTypes': mimeTypes,
|
'mimeTypes': mimeTypes,
|
||||||
}).listen(
|
}).listen(
|
||||||
(data) => completer.complete(data as bool),
|
(data) => opCompleter.complete(data as bool),
|
||||||
onError: completer.completeError,
|
onError: opCompleter.completeError,
|
||||||
onDone: () {
|
onDone: () {
|
||||||
if (!completer.isCompleted) completer.complete(false);
|
if (!opCompleter.isCompleted) opCompleter.complete(false);
|
||||||
},
|
},
|
||||||
cancelOnError: true,
|
cancelOnError: true,
|
||||||
);
|
);
|
||||||
// `await` here, so that `completeError` will be caught below
|
// `await` here, so that `completeError` will be caught below
|
||||||
return await completer.future;
|
return await opCompleter.future;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
final message = e.message;
|
final message = e.message;
|
||||||
// mute issue in the specific case when an item:
|
// mute issue in the specific case when an item:
|
||||||
|
@ -320,22 +320,22 @@ class PlatformStorageService implements StorageService {
|
||||||
@override
|
@override
|
||||||
Future<bool?> createFile(String name, String mimeType, Uint8List bytes) async {
|
Future<bool?> createFile(String name, String mimeType, Uint8List bytes) async {
|
||||||
try {
|
try {
|
||||||
final completer = Completer<bool?>();
|
final opCompleter = Completer<bool?>();
|
||||||
_stream.receiveBroadcastStream(<String, dynamic>{
|
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'createFile',
|
'op': 'createFile',
|
||||||
'name': name,
|
'name': name,
|
||||||
'mimeType': mimeType,
|
'mimeType': mimeType,
|
||||||
'bytes': bytes,
|
'bytes': bytes,
|
||||||
}).listen(
|
}).listen(
|
||||||
(data) => completer.complete(data as bool?),
|
(data) => opCompleter.complete(data as bool?),
|
||||||
onError: completer.completeError,
|
onError: opCompleter.completeError,
|
||||||
onDone: () {
|
onDone: () {
|
||||||
if (!completer.isCompleted) completer.complete(false);
|
if (!opCompleter.isCompleted) opCompleter.complete(false);
|
||||||
},
|
},
|
||||||
cancelOnError: true,
|
cancelOnError: true,
|
||||||
);
|
);
|
||||||
// `await` here, so that `completeError` will be caught below
|
// `await` here, so that `completeError` will be caught below
|
||||||
return await completer.future;
|
return await opCompleter.future;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
|
@ -345,7 +345,7 @@ class PlatformStorageService implements StorageService {
|
||||||
@override
|
@override
|
||||||
Future<Uint8List> openFile([String? mimeType]) async {
|
Future<Uint8List> openFile([String? mimeType]) async {
|
||||||
try {
|
try {
|
||||||
final completer = Completer<Uint8List>.sync();
|
final opCompleter = Completer<Uint8List>();
|
||||||
final sink = OutputBuffer();
|
final sink = OutputBuffer();
|
||||||
_stream.receiveBroadcastStream(<String, dynamic>{
|
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'openFile',
|
'op': 'openFile',
|
||||||
|
@ -355,15 +355,15 @@ class PlatformStorageService implements StorageService {
|
||||||
final chunk = data as Uint8List;
|
final chunk = data as Uint8List;
|
||||||
sink.add(chunk);
|
sink.add(chunk);
|
||||||
},
|
},
|
||||||
onError: completer.completeError,
|
onError: opCompleter.completeError,
|
||||||
onDone: () {
|
onDone: () {
|
||||||
sink.close();
|
sink.close();
|
||||||
completer.complete(sink.bytes);
|
opCompleter.complete(sink.bytes);
|
||||||
},
|
},
|
||||||
cancelOnError: true,
|
cancelOnError: true,
|
||||||
);
|
);
|
||||||
// `await` here, so that `completeError` will be caught below
|
// `await` here, so that `completeError` will be caught below
|
||||||
return await completer.future;
|
return await opCompleter.future;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:aves/model/app_inventory.dart';
|
import 'package:aves/model/app_inventory.dart';
|
||||||
import 'package:aves/model/vaults/vaults.dart';
|
import 'package:aves/model/vaults/vaults.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
|
@ -7,8 +9,6 @@ import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
final AndroidFileUtils androidFileUtils = AndroidFileUtils._private();
|
final AndroidFileUtils androidFileUtils = AndroidFileUtils._private();
|
||||||
|
|
||||||
enum _State { uninitialized, initializing, initialized }
|
|
||||||
|
|
||||||
class AndroidFileUtils {
|
class AndroidFileUtils {
|
||||||
// cf https://developer.android.com/reference/android/content/ContentResolver#SCHEME_CONTENT
|
// cf https://developer.android.com/reference/android/content/ContentResolver#SCHEME_CONTENT
|
||||||
static const contentScheme = 'content';
|
static const contentScheme = 'content';
|
||||||
|
@ -29,16 +29,13 @@ class AndroidFileUtils {
|
||||||
late final String dcimPath, downloadPath, moviesPath, picturesPath, avesVideoCapturesPath;
|
late final String dcimPath, downloadPath, moviesPath, picturesPath, avesVideoCapturesPath;
|
||||||
late final Set<String> videoCapturesPaths;
|
late final Set<String> videoCapturesPaths;
|
||||||
Set<StorageVolume> storageVolumes = {};
|
Set<StorageVolume> storageVolumes = {};
|
||||||
_State _initialized = _State.uninitialized;
|
Future<void>? _loader;
|
||||||
|
|
||||||
AndroidFileUtils._private();
|
AndroidFileUtils._private();
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
if (_initialized == _State.uninitialized) {
|
_loader ??= _doInit();
|
||||||
_initialized = _State.initializing;
|
await _loader;
|
||||||
await _doInit();
|
|
||||||
_initialized = _State.initialized;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _doInit() async {
|
Future<void> _doInit() async {
|
||||||
|
|
|
@ -138,7 +138,7 @@ mixin FeedbackMixin {
|
||||||
VoidCallback? onCancel,
|
VoidCallback? onCancel,
|
||||||
Future<void> Function(Set<T> processed)? onDone,
|
Future<void> Function(Set<T> processed)? onDone,
|
||||||
}) async {
|
}) async {
|
||||||
final completer = Completer();
|
final opCompleter = Completer();
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
|
@ -149,12 +149,12 @@ mixin FeedbackMixin {
|
||||||
onDone: (processed) async {
|
onDone: (processed) async {
|
||||||
Navigator.maybeOf(context)?.pop();
|
Navigator.maybeOf(context)?.pop();
|
||||||
await onDone?.call(processed);
|
await onDone?.call(processed);
|
||||||
completer.complete();
|
opCompleter.complete();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
routeSettings: const RouteSettings(name: ReportOverlay.routeName),
|
routeSettings: const RouteSettings(name: ReportOverlay.routeName),
|
||||||
);
|
);
|
||||||
return completer.future;
|
return opCompleter.future;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -309,18 +309,18 @@ class _HomePageState extends State<HomePage> {
|
||||||
final album = viewerEntry.directory;
|
final album = viewerEntry.directory;
|
||||||
if (album != null) {
|
if (album != null) {
|
||||||
// wait for collection to pass the `loading` state
|
// wait for collection to pass the `loading` state
|
||||||
final completer = Completer();
|
final loadingCompleter = Completer();
|
||||||
final stateNotifier = source.stateNotifier;
|
final stateNotifier = source.stateNotifier;
|
||||||
void _onSourceStateChanged() {
|
void _onSourceStateChanged() {
|
||||||
if (stateNotifier.value != SourceState.loading) {
|
if (stateNotifier.value != SourceState.loading) {
|
||||||
stateNotifier.removeListener(_onSourceStateChanged);
|
stateNotifier.removeListener(_onSourceStateChanged);
|
||||||
completer.complete();
|
loadingCompleter.complete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stateNotifier.addListener(_onSourceStateChanged);
|
stateNotifier.addListener(_onSourceStateChanged);
|
||||||
_onSourceStateChanged();
|
_onSourceStateChanged();
|
||||||
await completer.future;
|
await loadingCompleter.future;
|
||||||
|
|
||||||
collection = CollectionLens(
|
collection = CollectionLens(
|
||||||
source: source,
|
source: source,
|
||||||
|
|
|
@ -13,29 +13,28 @@ typedef HistogramLevels = Map<HistogramChannel, List<double>>;
|
||||||
|
|
||||||
mixin HistogramMixin {
|
mixin HistogramMixin {
|
||||||
HistogramLevels _levels = {};
|
HistogramLevels _levels = {};
|
||||||
Completer? _completer;
|
Future<void>? _loader;
|
||||||
|
|
||||||
static const int bins = 256;
|
static const int bins = 256;
|
||||||
|
|
||||||
Future<HistogramLevels> getHistogramLevels(ImageInfo info, bool forceUpdate) async {
|
Future<HistogramLevels> getHistogramLevels(ImageInfo info, bool forceUpdate) async {
|
||||||
if (_levels.isEmpty || forceUpdate) {
|
if (_levels.isEmpty || forceUpdate) {
|
||||||
if (_completer == null) {
|
_loader ??= _getLevels(info);
|
||||||
_completer = Completer();
|
await _loader;
|
||||||
final data = (await info.image.toByteData(format: ImageByteFormat.rawStraightRgba))!;
|
_loader = null;
|
||||||
_levels = switch (settings.overlayHistogramStyle) {
|
|
||||||
OverlayHistogramStyle.rgb => await compute<ByteData, HistogramLevels>(_computeRgbLevels, data),
|
|
||||||
OverlayHistogramStyle.luminance => await compute<ByteData, HistogramLevels>(_computeLuminanceLevels, data),
|
|
||||||
_ => <HistogramChannel, List<double>>{},
|
|
||||||
};
|
|
||||||
_completer?.complete();
|
|
||||||
} else {
|
|
||||||
await _completer?.future;
|
|
||||||
_completer = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return _levels;
|
return _levels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _getLevels(ImageInfo info) async {
|
||||||
|
final data = (await info.image.toByteData(format: ImageByteFormat.rawStraightRgba))!;
|
||||||
|
_levels = switch (settings.overlayHistogramStyle) {
|
||||||
|
OverlayHistogramStyle.rgb => await compute<ByteData, HistogramLevels>(_computeRgbLevels, data),
|
||||||
|
OverlayHistogramStyle.luminance => await compute<ByteData, HistogramLevels>(_computeLuminanceLevels, data),
|
||||||
|
_ => <HistogramChannel, List<double>>{},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static HistogramLevels _computeRgbLevels(ByteData data) {
|
static HistogramLevels _computeRgbLevels(ByteData data) {
|
||||||
final redLevels = List.filled(bins, 0);
|
final redLevels = List.filled(bins, 0);
|
||||||
final greenLevels = List.filled(bins, 0);
|
final greenLevels = List.filled(bins, 0);
|
||||||
|
|
Loading…
Reference in a new issue