#100 fixed wrong app album color on startup

This commit is contained in:
Thibault Deckers 2021-10-07 16:38:33 +09:00
parent 051c6f5846
commit 9a2451ea0c
13 changed files with 86 additions and 41 deletions

View file

@ -1,6 +1,6 @@
import 'dart:ui' as ui show Codec; import 'dart:ui' as ui show Codec;
import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/common/services.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@ -39,7 +39,7 @@ class AppIconImage extends ImageProvider<AppIconImageKey> {
Future<ui.Codec> _loadAsync(AppIconImageKey key, DecoderCallback decode) async { Future<ui.Codec> _loadAsync(AppIconImageKey key, DecoderCallback decode) async {
try { try {
final bytes = await AndroidAppService.getAppIcon(key.packageName, key.size); final bytes = await androidAppService.getAppIcon(key.packageName, key.size);
return await decode(bytes.isEmpty ? kTransparentImage : bytes); return await decode(bytes.isEmpty ? kTransparentImage : bytes);
} catch (error) { } catch (error) {
debugPrint('$runtimeType _loadAsync failed with packageName=$packageName, error=$error'); debugPrint('$runtimeType _loadAsync failed with packageName=$packageName, error=$error');

View file

@ -10,10 +10,35 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
class AndroidAppService { abstract class AndroidAppService {
Future<Set<Package>> getPackages();
Future<Uint8List> getAppIcon(String packageName, double size);
Future<bool> copyToClipboard(String uri, String? label);
Future<bool> edit(String uri, String mimeType);
Future<bool> open(String uri, String mimeType);
Future<bool> openMap(LatLng latLng);
Future<bool> setAs(String uri, String mimeType);
Future<bool> shareEntries(Iterable<AvesEntry> entries);
Future<bool> shareSingle(String uri, String mimeType);
Future<bool> canPinToHomeScreen();
Future<void> pinToHomeScreen(String label, AvesEntry? entry, Set<CollectionFilter> filters);
}
class PlatformAndroidAppService implements AndroidAppService {
static const platform = MethodChannel('deckers.thibault/aves/app'); static const platform = MethodChannel('deckers.thibault/aves/app');
static Future<Set<Package>> getPackages() async { @override
Future<Set<Package>> getPackages() async {
try { try {
final result = await platform.invokeMethod('getPackages'); final result = await platform.invokeMethod('getPackages');
final packages = (result as List).cast<Map>().map((map) => Package.fromMap(map)).toSet(); final packages = (result as List).cast<Map>().map((map) => Package.fromMap(map)).toSet();
@ -29,7 +54,8 @@ class AndroidAppService {
return {}; return {};
} }
static Future<Uint8List> getAppIcon(String packageName, double size) async { @override
Future<Uint8List> getAppIcon(String packageName, double size) async {
try { try {
final result = await platform.invokeMethod('getAppIcon', <String, dynamic>{ final result = await platform.invokeMethod('getAppIcon', <String, dynamic>{
'packageName': packageName, 'packageName': packageName,
@ -42,7 +68,8 @@ class AndroidAppService {
return Uint8List(0); return Uint8List(0);
} }
static Future<bool> copyToClipboard(String uri, String? label) async { @override
Future<bool> copyToClipboard(String uri, String? label) async {
try { try {
final result = await platform.invokeMethod('copyToClipboard', <String, dynamic>{ final result = await platform.invokeMethod('copyToClipboard', <String, dynamic>{
'uri': uri, 'uri': uri,
@ -55,7 +82,8 @@ class AndroidAppService {
return false; return false;
} }
static Future<bool> edit(String uri, String mimeType) async { @override
Future<bool> edit(String uri, String mimeType) async {
try { try {
final result = await platform.invokeMethod('edit', <String, dynamic>{ final result = await platform.invokeMethod('edit', <String, dynamic>{
'uri': uri, 'uri': uri,
@ -68,7 +96,8 @@ class AndroidAppService {
return false; return false;
} }
static Future<bool> open(String uri, String mimeType) async { @override
Future<bool> open(String uri, String mimeType) async {
try { try {
final result = await platform.invokeMethod('open', <String, dynamic>{ final result = await platform.invokeMethod('open', <String, dynamic>{
'uri': uri, 'uri': uri,
@ -81,7 +110,8 @@ class AndroidAppService {
return false; return false;
} }
static Future<bool> openMap(LatLng latLng) async { @override
Future<bool> openMap(LatLng latLng) async {
final latitude = roundToPrecision(latLng.latitude, decimals: 6); final latitude = roundToPrecision(latLng.latitude, decimals: 6);
final longitude = roundToPrecision(latLng.longitude, decimals: 6); final longitude = roundToPrecision(latLng.longitude, decimals: 6);
final geoUri = 'geo:$latitude,$longitude?q=$latitude,$longitude'; final geoUri = 'geo:$latitude,$longitude?q=$latitude,$longitude';
@ -97,7 +127,8 @@ class AndroidAppService {
return false; return false;
} }
static Future<bool> setAs(String uri, String mimeType) async { @override
Future<bool> setAs(String uri, String mimeType) async {
try { try {
final result = await platform.invokeMethod('setAs', <String, dynamic>{ final result = await platform.invokeMethod('setAs', <String, dynamic>{
'uri': uri, 'uri': uri,
@ -110,7 +141,8 @@ class AndroidAppService {
return false; return false;
} }
static Future<bool> shareEntries(Iterable<AvesEntry> entries) async { @override
Future<bool> shareEntries(Iterable<AvesEntry> entries) async {
// loosen mime type to a generic one, so we can share with badly defined apps // loosen mime type to a generic one, so we can share with badly defined apps
// e.g. Google Lens declares receiving "image/jpeg" only, but it can actually handle more formats // e.g. Google Lens declares receiving "image/jpeg" only, but it can actually handle more formats
final urisByMimeType = groupBy<AvesEntry, String>(entries, (e) => e.mimeTypeAnySubtype).map((k, v) => MapEntry(k, v.map((e) => e.uri).toList())); final urisByMimeType = groupBy<AvesEntry, String>(entries, (e) => e.mimeTypeAnySubtype).map((k, v) => MapEntry(k, v.map((e) => e.uri).toList()));
@ -125,7 +157,8 @@ class AndroidAppService {
return false; return false;
} }
static Future<bool> shareSingle(String uri, String mimeType) async { @override
Future<bool> shareSingle(String uri, String mimeType) async {
try { try {
final result = await platform.invokeMethod('share', <String, dynamic>{ final result = await platform.invokeMethod('share', <String, dynamic>{
'urisByMimeType': { 'urisByMimeType': {
@ -142,9 +175,10 @@ class AndroidAppService {
// app shortcuts // app shortcuts
// this ability will not change over the lifetime of the app // this ability will not change over the lifetime of the app
static bool? _canPin; bool? _canPin;
static Future<bool> canPinToHomeScreen() async { @override
Future<bool> canPinToHomeScreen() async {
if (_canPin != null) return SynchronousFuture(_canPin!); if (_canPin != null) return SynchronousFuture(_canPin!);
try { try {
@ -159,7 +193,8 @@ class AndroidAppService {
return false; return false;
} }
static Future<void> pinToHomeScreen(String label, AvesEntry? entry, Set<CollectionFilter> filters) async { @override
Future<void> pinToHomeScreen(String label, AvesEntry? entry, Set<CollectionFilter> filters) async {
Uint8List? iconBytes; Uint8List? iconBytes;
if (entry != null) { if (entry != null) {
final size = entry.isVideo ? 0.0 : 256.0; final size = entry.isVideo ? 0.0 : 256.0;

View file

@ -1,5 +1,6 @@
import 'package:aves/model/availability.dart'; import 'package:aves/model/availability.dart';
import 'package:aves/model/metadata_db.dart'; import 'package:aves/model/metadata_db.dart';
import 'package:aves/services/android_app_service.dart';
import 'package:aves/services/device_service.dart'; import 'package:aves/services/device_service.dart';
import 'package:aves/services/media/embedded_data_service.dart'; import 'package:aves/services/media/embedded_data_service.dart';
import 'package:aves/services/media/media_file_service.dart'; import 'package:aves/services/media/media_file_service.dart';
@ -18,6 +19,7 @@ final p.Context pContext = getIt<p.Context>();
final AvesAvailability availability = getIt<AvesAvailability>(); final AvesAvailability availability = getIt<AvesAvailability>();
final MetadataDb metadataDb = getIt<MetadataDb>(); final MetadataDb metadataDb = getIt<MetadataDb>();
final AndroidAppService androidAppService = getIt<AndroidAppService>();
final DeviceService deviceService = getIt<DeviceService>(); final DeviceService deviceService = getIt<DeviceService>();
final EmbeddedDataService embeddedDataService = getIt<EmbeddedDataService>(); final EmbeddedDataService embeddedDataService = getIt<EmbeddedDataService>();
final MediaFileService mediaFileService = getIt<MediaFileService>(); final MediaFileService mediaFileService = getIt<MediaFileService>();
@ -33,6 +35,7 @@ void initPlatformServices() {
getIt.registerLazySingleton<AvesAvailability>(() => LiveAvesAvailability()); getIt.registerLazySingleton<AvesAvailability>(() => LiveAvesAvailability());
getIt.registerLazySingleton<MetadataDb>(() => SqfliteMetadataDb()); getIt.registerLazySingleton<MetadataDb>(() => SqfliteMetadataDb());
getIt.registerLazySingleton<AndroidAppService>(() => PlatformAndroidAppService());
getIt.registerLazySingleton<DeviceService>(() => PlatformDeviceService()); getIt.registerLazySingleton<DeviceService>(() => PlatformDeviceService());
getIt.registerLazySingleton<EmbeddedDataService>(() => PlatformEmbeddedDataService()); getIt.registerLazySingleton<EmbeddedDataService>(() => PlatformEmbeddedDataService());
getIt.registerLazySingleton<MediaFileService>(() => PlatformMediaFileService()); getIt.registerLazySingleton<MediaFileService>(() => PlatformMediaFileService());

View file

@ -1,4 +1,3 @@
import 'package:aves/services/android_app_service.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/utils/change_notifier.dart'; import 'package:aves/utils/change_notifier.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
@ -36,15 +35,15 @@ class AndroidFileUtils {
// from Aves // from Aves
videoCapturesPath = pContext.join(dcimPath, 'Video Captures'); videoCapturesPath = pContext.join(dcimPath, 'Video Captures');
_initialized = true; // include package fetching in initialization
} // to avoid app album color flickering
Future<void> initAppNames() async {
if (_packages.isEmpty) { if (_packages.isEmpty) {
_packages = await AndroidAppService.getPackages(); _packages = await androidAppService.getPackages();
_potentialAppDirs = _launcherPackages.expand((package) => package.potentialDirs).toList(); _potentialAppDirs = _launcherPackages.expand((package) => package.potentialDirs).toList();
appNameChangeNotifier.notifyListeners(); appNameChangeNotifier.notifyListeners();
} }
_initialized = true;
} }
bool isCameraPath(String path) => path.startsWith(dcimPath) && (path.endsWith('${separator}Camera') || path.endsWith('${separator}100ANDRO')); bool isCameraPath(String path) => path.startsWith(dcimPath) && (path.endsWith('${separator}Camera') || path.endsWith('${separator}100ANDRO'));

View file

@ -9,7 +9,7 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
import 'package:aves/model/source/enums.dart'; import 'package:aves/model/source/enums.dart';
import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/collection/entry_set_action_delegate.dart'; import 'package:aves/widgets/collection/entry_set_action_delegate.dart';
import 'package:aves/widgets/collection/filter_bar.dart'; import 'package:aves/widgets/collection/filter_bar.dart';
@ -61,7 +61,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
vsync: this, vsync: this,
); );
_isSelectingNotifier.addListener(_onActivityChange); _isSelectingNotifier.addListener(_onActivityChange);
_canAddShortcutsLoader = AndroidAppService.canPinToHomeScreen(); _canAddShortcutsLoader = androidAppService.canPinToHomeScreen();
_registerWidget(widget); _registerWidget(widget);
WidgetsBinding.instance!.addPostFrameCallback((_) => _onFilterChanged()); WidgetsBinding.instance!.addPostFrameCallback((_) => _onFilterChanged());
} }
@ -363,7 +363,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
final name = result.item2; final name = result.item2;
if (name.isEmpty) return; if (name.isEmpty) return;
unawaited(AndroidAppService.pinToHomeScreen(name, coverEntry, filters)); unawaited(androidAppService.pinToHomeScreen(name, coverEntry, filters));
} }
void _goToSearch() { void _goToSearch() {

View file

@ -9,7 +9,6 @@ import 'package:aves/model/highlight.dart';
import 'package:aves/model/selection.dart'; import 'package:aves/model/selection.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
import 'package:aves/services/android_app_service.dart';
import 'package:aves/services/common/image_op_events.dart'; import 'package:aves/services/common/image_op_events.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/services/media/enums.dart'; import 'package:aves/services/media/enums.dart';
@ -66,7 +65,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
void _share(BuildContext context) { void _share(BuildContext context) {
final selection = context.read<Selection<AvesEntry>>(); final selection = context.read<Selection<AvesEntry>>();
final selectedItems = _getExpandedSelectedItems(selection); final selectedItems = _getExpandedSelectedItems(selection);
AndroidAppService.shareEntries(selectedItems).then((success) { androidAppService.shareEntries(selectedItems).then((success) {
if (!success) showNoMatchingAppDialog(context); if (!success) showNoMatchingAppDialog(context);
}); });
} }

View file

@ -1,5 +1,5 @@
import 'package:aves/image_providers/app_icon_image_provider.dart'; import 'package:aves/image_providers/app_icon_image_provider.dart';
import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
import 'package:aves/widgets/viewer/info/common.dart'; import 'package:aves/widgets/viewer/info/common.dart';
@ -21,7 +21,7 @@ class _DebugAndroidAppSectionState extends State<DebugAndroidAppSection> with Au
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loader = AndroidAppService.getPackages(); _loader = androidAppService.getPackages();
} }
@override @override

View file

@ -66,7 +66,6 @@ class _HomePageState extends State<HomePage> {
} }
await androidFileUtils.init(); await androidFileUtils.init();
unawaited(androidFileUtils.initAppNames());
var appMode = AppMode.main; var appMode = AppMode.main;
final intentData = widget.intentData ?? await ViewerService.getIntentData(); final intentData = widget.intentData ?? await ViewerService.getIntentData();

View file

@ -2,7 +2,6 @@ import 'dart:async';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/ref/mime_types.dart'; import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/android_app_service.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart';
import 'package:aves/widgets/common/behaviour/routes.dart'; import 'package:aves/widgets/common/behaviour/routes.dart';
@ -56,10 +55,10 @@ class EmbeddedDataOpener extends StatelessWidget with FeedbackMixin {
final uri = fields['uri']!; final uri = fields['uri']!;
if (!MimeTypes.isImage(mimeType) && !MimeTypes.isVideo(mimeType)) { if (!MimeTypes.isImage(mimeType) && !MimeTypes.isVideo(mimeType)) {
// open with another app // open with another app
unawaited(AndroidAppService.open(uri, mimeType).then((success) { unawaited(androidAppService.open(uri, mimeType).then((success) {
if (!success) { if (!success) {
// fallback to sharing, so that the file can be saved somewhere // fallback to sharing, so that the file can be saved somewhere
AndroidAppService.shareSingle(uri, mimeType).then((success) { androidAppService.shareSingle(uri, mimeType).then((success) {
if (!success) showNoMatchingAppDialog(context); if (!success) showNoMatchingAppDialog(context);
}); });
} }

View file

@ -10,7 +10,6 @@ import 'package:aves/model/highlight.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
import 'package:aves/ref/mime_types.dart'; import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/android_app_service.dart';
import 'package:aves/services/common/image_op_events.dart'; import 'package:aves/services/common/image_op_events.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/services/media/enums.dart'; import 'package:aves/services/media/enums.dart';
@ -39,7 +38,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
entry.toggleFavourite(); entry.toggleFavourite();
break; break;
case EntryAction.copyToClipboard: case EntryAction.copyToClipboard:
AndroidAppService.copyToClipboard(entry.uri, entry.bestTitle).then((success) { androidAppService.copyToClipboard(entry.uri, entry.bestTitle).then((success) {
showFeedback(context, success ? context.l10n.genericSuccessFeedback : context.l10n.genericFailureFeedback); showFeedback(context, success ? context.l10n.genericSuccessFeedback : context.l10n.genericFailureFeedback);
}); });
break; break;
@ -68,17 +67,17 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
_flip(context, entry); _flip(context, entry);
break; break;
case EntryAction.edit: case EntryAction.edit:
AndroidAppService.edit(entry.uri, entry.mimeType).then((success) { androidAppService.edit(entry.uri, entry.mimeType).then((success) {
if (!success) showNoMatchingAppDialog(context); if (!success) showNoMatchingAppDialog(context);
}); });
break; break;
case EntryAction.open: case EntryAction.open:
AndroidAppService.open(entry.uri, entry.mimeTypeAnySubtype).then((success) { androidAppService.open(entry.uri, entry.mimeTypeAnySubtype).then((success) {
if (!success) showNoMatchingAppDialog(context); if (!success) showNoMatchingAppDialog(context);
}); });
break; break;
case EntryAction.openMap: case EntryAction.openMap:
AndroidAppService.openMap(entry.latLng!).then((success) { androidAppService.openMap(entry.latLng!).then((success) {
if (!success) showNoMatchingAppDialog(context); if (!success) showNoMatchingAppDialog(context);
}); });
break; break;
@ -86,12 +85,12 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
_rotateScreen(context); _rotateScreen(context);
break; break;
case EntryAction.setAs: case EntryAction.setAs:
AndroidAppService.setAs(entry.uri, entry.mimeType).then((success) { androidAppService.setAs(entry.uri, entry.mimeType).then((success) {
if (!success) showNoMatchingAppDialog(context); if (!success) showNoMatchingAppDialog(context);
}); });
break; break;
case EntryAction.share: case EntryAction.share:
AndroidAppService.shareEntries({entry}).then((success) { androidAppService.shareEntries({entry}).then((success) {
if (!success) showNoMatchingAppDialog(context); if (!success) showNoMatchingAppDialog(context);
}); });
break; break;

View file

@ -3,7 +3,7 @@ import 'dart:async';
import 'package:aves/model/actions/video_actions.dart'; import 'package:aves/model/actions/video_actions.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/theme/format.dart'; import 'package:aves/theme/format.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
@ -74,7 +74,7 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi
scale: scale, scale: scale,
child: IconButton( child: IconButton(
icon: const Icon(AIcons.openOutside), icon: const Icon(AIcons.openOutside),
onPressed: () => AndroidAppService.open(entry.uri, entry.mimeTypeAnySubtype), onPressed: () => androidAppService.open(entry.uri, entry.mimeTypeAnySubtype),
tooltip: context.l10n.viewerOpenTooltip, tooltip: context.l10n.viewerOpenTooltip,
), ),
), ),

View file

@ -0,0 +1,9 @@
import 'package:aves/services/android_app_service.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
class FakeAndroidAppService extends Fake implements AndroidAppService {
@override
Future<Set<Package>> getPackages() => SynchronousFuture({});
}

View file

@ -8,6 +8,7 @@ import 'package:aves/model/metadata_db.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/enums.dart'; import 'package:aves/model/source/enums.dart';
import 'package:aves/model/source/media_store_source.dart'; import 'package:aves/model/source/media_store_source.dart';
import 'package:aves/services/android_app_service.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:aves/services/device_service.dart'; import 'package:aves/services/device_service.dart';
import 'package:aves/services/media/media_file_service.dart'; import 'package:aves/services/media/media_file_service.dart';
@ -21,6 +22,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import '../fake/android_app_service.dart';
import '../fake/availability.dart'; import '../fake/availability.dart';
import '../fake/device_service.dart'; import '../fake/device_service.dart';
import '../fake/media_file_service.dart'; import '../fake/media_file_service.dart';
@ -42,6 +44,7 @@ void main() {
getIt.registerLazySingleton<AvesAvailability>(() => FakeAvesAvailability()); getIt.registerLazySingleton<AvesAvailability>(() => FakeAvesAvailability());
getIt.registerLazySingleton<MetadataDb>(() => FakeMetadataDb()); getIt.registerLazySingleton<MetadataDb>(() => FakeMetadataDb());
getIt.registerLazySingleton<AndroidAppService>(() => FakeAndroidAppService());
getIt.registerLazySingleton<DeviceService>(() => FakeDeviceService()); getIt.registerLazySingleton<DeviceService>(() => FakeDeviceService());
getIt.registerLazySingleton<MediaFileService>(() => FakeMediaFileService()); getIt.registerLazySingleton<MediaFileService>(() => FakeMediaFileService());
getIt.registerLazySingleton<MediaStoreService>(() => FakeMediaStoreService()); getIt.registerLazySingleton<MediaStoreService>(() => FakeMediaStoreService());