service review
This commit is contained in:
parent
9fdb42892e
commit
4ae828710d
43 changed files with 227 additions and 282 deletions
|
@ -53,18 +53,16 @@ class MainActivity : FlutterActivity() {
|
|||
|
||||
// dart -> platform -> dart
|
||||
MethodChannel(messenger, AppAdapterHandler.CHANNEL).setMethodCallHandler(AppAdapterHandler(this))
|
||||
MethodChannel(messenger, AppShortcutHandler.CHANNEL).setMethodCallHandler(AppShortcutHandler(this))
|
||||
MethodChannel(messenger, DebugHandler.CHANNEL).setMethodCallHandler(DebugHandler(this))
|
||||
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler())
|
||||
MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this))
|
||||
MethodChannel(messenger, ImageFileHandler.CHANNEL).setMethodCallHandler(ImageFileHandler(this))
|
||||
MethodChannel(messenger, GeocodingHandler.CHANNEL).setMethodCallHandler(GeocodingHandler(this))
|
||||
MethodChannel(messenger, GlobalSearchHandler.CHANNEL).setMethodCallHandler(GlobalSearchHandler(this))
|
||||
MethodChannel(messenger, MediaFileHandler.CHANNEL).setMethodCallHandler(MediaFileHandler(this))
|
||||
MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(this))
|
||||
MethodChannel(messenger, MetadataEditHandler.CHANNEL).setMethodCallHandler(MetadataEditHandler(this))
|
||||
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this))
|
||||
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this))
|
||||
MethodChannel(messenger, TimeHandler.CHANNEL).setMethodCallHandler(TimeHandler())
|
||||
MethodChannel(messenger, WindowHandler.CHANNEL).setMethodCallHandler(WindowHandler(this))
|
||||
|
||||
// result streaming: dart -> platform ->->-> dart
|
||||
|
|
|
@ -4,17 +4,25 @@ import android.content.*
|
|||
import android.content.pm.ApplicationInfo
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.DecodeFormat
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import deckers.thibault.aves.MainActivity
|
||||
import deckers.thibault.aves.R
|
||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
|
||||
import deckers.thibault.aves.model.FieldMap
|
||||
import deckers.thibault.aves.utils.BitmapUtils
|
||||
import deckers.thibault.aves.utils.BitmapUtils.getBytes
|
||||
import deckers.thibault.aves.utils.LogUtils
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
|
@ -39,6 +47,8 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
|
|||
"openMap" -> safe(call, result, ::openMap)
|
||||
"setAs" -> safe(call, result, ::setAs)
|
||||
"share" -> safe(call, result, ::share)
|
||||
"canPin" -> safe(call, result, ::canPin)
|
||||
"pin" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::pin) }
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
@ -307,6 +317,64 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
|
|||
}
|
||||
}
|
||||
|
||||
// shortcuts
|
||||
|
||||
private fun isPinSupported() = ShortcutManagerCompat.isRequestPinShortcutSupported(context)
|
||||
|
||||
private fun canPin(@Suppress("UNUSED_PARAMETER") call: MethodCall, result: MethodChannel.Result) {
|
||||
result.success(isPinSupported())
|
||||
}
|
||||
|
||||
private fun pin(call: MethodCall, result: MethodChannel.Result) {
|
||||
val label = call.argument<String>("label")
|
||||
val iconBytes = call.argument<ByteArray>("iconBytes")
|
||||
val filters = call.argument<List<String>>("filters")
|
||||
if (label == null || filters == null) {
|
||||
result.error("pin-args", "failed because of missing arguments", null)
|
||||
return
|
||||
}
|
||||
|
||||
if (!isPinSupported()) {
|
||||
result.error("pin-unsupported", "failed because the launcher does not support pinning shortcuts", null)
|
||||
return
|
||||
}
|
||||
|
||||
var icon: IconCompat? = null
|
||||
if (iconBytes?.isNotEmpty() == true) {
|
||||
var bitmap = BitmapFactory.decodeByteArray(iconBytes, 0, iconBytes.size)
|
||||
bitmap = BitmapUtils.centerSquareCrop(context, bitmap, 256)
|
||||
if (bitmap != null) {
|
||||
// adaptive, so the bitmap is used as background and covers the whole icon
|
||||
icon = IconCompat.createWithAdaptiveBitmap(bitmap)
|
||||
}
|
||||
}
|
||||
if (icon == null) {
|
||||
// shortcut adaptive icons are placed in `mipmap`, not `drawable`,
|
||||
// so that foreground is rendered at the intended scale
|
||||
val supportAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||
|
||||
icon = IconCompat.createWithResource(context, if (supportAdaptiveIcon) R.mipmap.ic_shortcut_collection else R.drawable.ic_shortcut_collection)
|
||||
}
|
||||
|
||||
val intent = Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
|
||||
.putExtra("page", "/collection")
|
||||
.putExtra("filters", filters.toTypedArray())
|
||||
// on API 25, `String[]` or `ArrayList` extras are null when using the shortcut
|
||||
// so we use a joined `String` as fallback
|
||||
.putExtra("filtersString", filters.joinToString(MainActivity.EXTRA_STRING_ARRAY_SEPARATOR))
|
||||
|
||||
// multiple shortcuts sharing the same ID cannot be created with different labels or icons
|
||||
// so we provide a unique ID for each one, and let the user manage duplicates (i.e. same filter set), if any
|
||||
val shortcut = ShortcutInfoCompat.Builder(context, UUID.randomUUID().toString())
|
||||
.setShortLabel(label)
|
||||
.setIcon(icon)
|
||||
.setIntent(intent)
|
||||
.build()
|
||||
ShortcutManagerCompat.requestPinShortcut(context, shortcut, null)
|
||||
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG_TAG = LogUtils.createTag<AppAdapterHandler>()
|
||||
const val CHANNEL = "deckers.thibault/aves/app"
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
package deckers.thibault.aves.channel.calls
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Build
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import deckers.thibault.aves.MainActivity
|
||||
import deckers.thibault.aves.R
|
||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
||||
import deckers.thibault.aves.utils.BitmapUtils.centerSquareCrop
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
|
||||
class AppShortcutHandler(private val context: Context) : MethodCallHandler {
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
when (call.method) {
|
||||
"canPin" -> safe(call, result, ::canPin)
|
||||
"pin" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::pin) }
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSupported() = ShortcutManagerCompat.isRequestPinShortcutSupported(context)
|
||||
|
||||
private fun canPin(@Suppress("UNUSED_PARAMETER") call: MethodCall, result: MethodChannel.Result) {
|
||||
result.success(isSupported())
|
||||
}
|
||||
|
||||
private fun pin(call: MethodCall, result: MethodChannel.Result) {
|
||||
val label = call.argument<String>("label")
|
||||
val iconBytes = call.argument<ByteArray>("iconBytes")
|
||||
val filters = call.argument<List<String>>("filters")
|
||||
if (label == null || filters == null) {
|
||||
result.error("pin-args", "failed because of missing arguments", null)
|
||||
return
|
||||
}
|
||||
|
||||
if (!isSupported()) {
|
||||
result.error("pin-unsupported", "failed because the launcher does not support pinning shortcuts", null)
|
||||
return
|
||||
}
|
||||
|
||||
var icon: IconCompat? = null
|
||||
if (iconBytes?.isNotEmpty() == true) {
|
||||
var bitmap = BitmapFactory.decodeByteArray(iconBytes, 0, iconBytes.size)
|
||||
bitmap = centerSquareCrop(context, bitmap, 256)
|
||||
if (bitmap != null) {
|
||||
// adaptive, so the bitmap is used as background and covers the whole icon
|
||||
icon = IconCompat.createWithAdaptiveBitmap(bitmap)
|
||||
}
|
||||
}
|
||||
if (icon == null) {
|
||||
// shortcut adaptive icons are placed in `mipmap`, not `drawable`,
|
||||
// so that foreground is rendered at the intended scale
|
||||
val supportAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||
|
||||
icon = IconCompat.createWithResource(context, if (supportAdaptiveIcon) R.mipmap.ic_shortcut_collection else R.drawable.ic_shortcut_collection)
|
||||
}
|
||||
|
||||
val intent = Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
|
||||
.putExtra("page", "/collection")
|
||||
.putExtra("filters", filters.toTypedArray())
|
||||
// on API 25, `String[]` or `ArrayList` extras are null when using the shortcut
|
||||
// so we use a joined `String` as fallback
|
||||
.putExtra("filtersString", filters.joinToString(MainActivity.EXTRA_STRING_ARRAY_SEPARATOR))
|
||||
|
||||
// multiple shortcuts sharing the same ID cannot be created with different labels or icons
|
||||
// so we provide a unique ID for each one, and let the user manage duplicates (i.e. same filter set), if any
|
||||
val shortcut = ShortcutInfoCompat.Builder(context, UUID.randomUUID().toString())
|
||||
.setShortLabel(label)
|
||||
.setIcon(icon)
|
||||
.setIntent(intent)
|
||||
.build()
|
||||
ShortcutManagerCompat.requestPinShortcut(context, shortcut, null)
|
||||
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CHANNEL = "deckers.thibault/aves/shortcut"
|
||||
}
|
||||
}
|
|
@ -5,15 +5,21 @@ import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
|||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
import java.util.*
|
||||
|
||||
class DeviceHandler : MethodCallHandler {
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
when (call.method) {
|
||||
"getDefaultTimeZone" -> safe(call, result, ::getDefaultTimeZone)
|
||||
"getPerformanceClass" -> safe(call, result, ::getPerformanceClass)
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDefaultTimeZone(@Suppress("UNUSED_PARAMETER") call: MethodCall, result: MethodChannel.Result) {
|
||||
result.success(TimeZone.getDefault().id)
|
||||
}
|
||||
|
||||
private fun getPerformanceClass(@Suppress("UNUSED_PARAMETER") call: MethodCall, result: MethodChannel.Result) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
result.success(Build.VERSION.MEDIA_PERFORMANCE_CLASS)
|
||||
|
|
|
@ -23,7 +23,7 @@ import kotlinx.coroutines.GlobalScope
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ImageFileHandler(private val activity: Activity) : MethodCallHandler {
|
||||
class MediaFileHandler(private val activity: Activity) : MethodCallHandler {
|
||||
private val density = activity.resources.displayMetrics.density
|
||||
|
||||
private val regionFetcher = RegionFetcher(activity)
|
||||
|
@ -196,6 +196,6 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler {
|
|||
}
|
||||
|
||||
companion object {
|
||||
const val CHANNEL = "deckers.thibault/aves/image"
|
||||
const val CHANNEL = "deckers.thibault/aves/media_file"
|
||||
}
|
||||
}
|
|
@ -38,6 +38,6 @@ class MediaStoreHandler(private val activity: Activity) : MethodCallHandler {
|
|||
}
|
||||
|
||||
companion object {
|
||||
const val CHANNEL = "deckers.thibault/aves/mediastore"
|
||||
const val CHANNEL = "deckers.thibault/aves/media_store"
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package deckers.thibault.aves.channel.calls
|
||||
|
||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
import java.util.*
|
||||
|
||||
class TimeHandler : MethodCallHandler {
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
when (call.method) {
|
||||
"getDefaultTimeZone" -> safe(call, result, ::getDefaultTimeZone)
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDefaultTimeZone(@Suppress("UNUSED_PARAMETER") call: MethodCall, result: MethodChannel.Result) {
|
||||
result.success(TimeZone.getDefault().id)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CHANNEL = "deckers.thibault/aves/time"
|
||||
}
|
||||
}
|
|
@ -187,7 +187,7 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen
|
|||
|
||||
companion object {
|
||||
private val LOG_TAG = LogUtils.createTag<ImageByteStreamHandler>()
|
||||
const val CHANNEL = "deckers.thibault/aves/image_byte_stream"
|
||||
const val CHANNEL = "deckers.thibault/aves/media_byte_stream"
|
||||
|
||||
private const val BUFFER_SIZE = 2 shl 17 // 256kB
|
||||
|
||||
|
|
|
@ -177,6 +177,6 @@ class ImageOpStreamHandler(private val activity: Activity, private val arguments
|
|||
|
||||
companion object {
|
||||
private val LOG_TAG = LogUtils.createTag<ImageOpStreamHandler>()
|
||||
const val CHANNEL = "deckers.thibault/aves/image_op_stream"
|
||||
const val CHANNEL = "deckers.thibault/aves/media_op_stream"
|
||||
}
|
||||
}
|
|
@ -59,6 +59,6 @@ class MediaStoreChangeStreamHandler(private val context: Context) : EventChannel
|
|||
|
||||
companion object {
|
||||
private val LOG_TAG = LogUtils.createTag<MediaStoreChangeStreamHandler>()
|
||||
const val CHANNEL = "deckers.thibault/aves/mediastorechange"
|
||||
const val CHANNEL = "deckers.thibault/aves/media_store_change"
|
||||
}
|
||||
}
|
|
@ -62,6 +62,6 @@ class MediaStoreStreamHandler(private val context: Context, arguments: Any?) : E
|
|||
|
||||
companion object {
|
||||
private val LOG_TAG = LogUtils.createTag<MediaStoreStreamHandler>()
|
||||
const val CHANNEL = "deckers.thibault/aves/mediastorestream"
|
||||
const val CHANNEL = "deckers.thibault/aves/media_store_stream"
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ class RegionProvider extends ImageProvider<RegionProviderKey> {
|
|||
final mimeType = key.mimeType;
|
||||
final pageId = key.pageId;
|
||||
try {
|
||||
final bytes = await imageFileService.getRegion(
|
||||
final bytes = await mediaFileService.getRegion(
|
||||
uri,
|
||||
mimeType,
|
||||
key.rotationDegrees,
|
||||
|
@ -56,11 +56,11 @@ class RegionProvider extends ImageProvider<RegionProviderKey> {
|
|||
|
||||
@override
|
||||
void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, RegionProviderKey key, ImageErrorListener handleError) {
|
||||
imageFileService.resumeLoading(key);
|
||||
mediaFileService.resumeLoading(key);
|
||||
super.resolveStreamForKey(configuration, stream, key, handleError);
|
||||
}
|
||||
|
||||
void pause() => imageFileService.cancelRegion(key);
|
||||
void pause() => mediaFileService.cancelRegion(key);
|
||||
}
|
||||
|
||||
@immutable
|
||||
|
|
|
@ -35,7 +35,7 @@ class ThumbnailProvider extends ImageProvider<ThumbnailProviderKey> {
|
|||
final mimeType = key.mimeType;
|
||||
final pageId = key.pageId;
|
||||
try {
|
||||
final bytes = await imageFileService.getThumbnail(
|
||||
final bytes = await mediaFileService.getThumbnail(
|
||||
uri: uri,
|
||||
mimeType: mimeType,
|
||||
pageId: pageId,
|
||||
|
@ -57,11 +57,11 @@ class ThumbnailProvider extends ImageProvider<ThumbnailProviderKey> {
|
|||
|
||||
@override
|
||||
void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, ThumbnailProviderKey key, ImageErrorListener handleError) {
|
||||
imageFileService.resumeLoading(key);
|
||||
mediaFileService.resumeLoading(key);
|
||||
super.resolveStreamForKey(configuration, stream, key, handleError);
|
||||
}
|
||||
|
||||
void pause() => imageFileService.cancelThumbnail(key);
|
||||
void pause() => mediaFileService.cancelThumbnail(key);
|
||||
}
|
||||
|
||||
@immutable
|
||||
|
|
|
@ -49,7 +49,7 @@ class UriImage extends ImageProvider<UriImage> with EquatableMixin {
|
|||
assert(key == this);
|
||||
|
||||
try {
|
||||
final bytes = await imageFileService.getImage(
|
||||
final bytes = await mediaFileService.getImage(
|
||||
uri,
|
||||
mimeType,
|
||||
rotationDegrees,
|
||||
|
|
|
@ -615,7 +615,7 @@ class AvesEntry {
|
|||
|
||||
Future<bool> delete() {
|
||||
final completer = Completer<bool>();
|
||||
imageFileService.delete([this]).listen(
|
||||
mediaFileService.delete([this]).listen(
|
||||
(event) => completer.complete(event.success),
|
||||
onError: completer.completeError,
|
||||
onDone: () {
|
||||
|
|
|
@ -11,7 +11,6 @@ import 'package:aves/model/settings/enums.dart';
|
|||
import 'package:aves/model/settings/map_style.dart';
|
||||
import 'package:aves/model/settings/screen_on.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/services/device_service.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
|
@ -128,7 +127,7 @@ class Settings extends ChangeNotifier {
|
|||
|
||||
Future<void> setContextualDefaults() async {
|
||||
// performance
|
||||
final performanceClass = await DeviceService.getPerformanceClass();
|
||||
final performanceClass = await deviceService.getPerformanceClass();
|
||||
enableOverlayBlurEffect = performanceClass >= 30;
|
||||
|
||||
// availability
|
||||
|
|
|
@ -159,7 +159,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
|
|||
|
||||
Future<bool> renameEntry(AvesEntry entry, String newName, {required bool persist}) async {
|
||||
if (newName == entry.filenameWithoutExtension) return true;
|
||||
final newFields = await imageFileService.rename(entry, '$newName${entry.extension}');
|
||||
final newFields = await mediaFileService.rename(entry, '$newName${entry.extension}');
|
||||
if (newFields.isEmpty) return false;
|
||||
|
||||
await _moveEntry(entry, newFields, persist: persist);
|
||||
|
|
|
@ -25,7 +25,7 @@ class MediaStoreSource extends CollectionSource {
|
|||
await metadataDb.init();
|
||||
await favourites.init();
|
||||
await covers.init();
|
||||
final currentTimeZone = await timeService.getDefaultTimeZone();
|
||||
final currentTimeZone = await deviceService.getDefaultTimeZone();
|
||||
if (currentTimeZone != null) {
|
||||
final catalogTimeZone = settings.catalogTimeZone;
|
||||
if (currentTimeZone != catalogTimeZone) {
|
||||
|
@ -153,7 +153,7 @@ class MediaStoreSource extends CollectionSource {
|
|||
for (final kv in uriByContentId.entries) {
|
||||
final contentId = kv.key;
|
||||
final uri = kv.value;
|
||||
final sourceEntry = await imageFileService.getEntry(uri, null);
|
||||
final sourceEntry = await mediaFileService.getEntry(uri, null);
|
||||
if (sourceEntry != null) {
|
||||
final existingEntry = allEntries.firstWhereOrNull((entry) => entry.contentId == contentId);
|
||||
// compare paths because some apps move files without updating their `last modified date`
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/utils/math_utils.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
|
@ -136,4 +138,49 @@ class AndroidAppService {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// app shortcuts
|
||||
|
||||
// this ability will not change over the lifetime of the app
|
||||
static bool? _canPin;
|
||||
|
||||
static Future<bool> canPinToHomeScreen() async {
|
||||
if (_canPin != null) return SynchronousFuture(_canPin!);
|
||||
|
||||
try {
|
||||
final result = await platform.invokeMethod('canPin');
|
||||
if (result != null) {
|
||||
_canPin = result;
|
||||
return result;
|
||||
}
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static Future<void> pinToHomeScreen(String label, AvesEntry? entry, Set<CollectionFilter> filters) async {
|
||||
Uint8List? iconBytes;
|
||||
if (entry != null) {
|
||||
final size = entry.isVideo ? 0.0 : 256.0;
|
||||
iconBytes = await mediaFileService.getThumbnail(
|
||||
uri: entry.uri,
|
||||
mimeType: entry.mimeType,
|
||||
pageId: entry.pageId,
|
||||
rotationDegrees: entry.rotationDegrees,
|
||||
isFlipped: entry.isFlipped,
|
||||
dateModifiedSecs: entry.dateModifiedSecs,
|
||||
extent: size,
|
||||
);
|
||||
}
|
||||
try {
|
||||
await platform.invokeMethod('pin', <String, dynamic>{
|
||||
'label': label,
|
||||
'iconBytes': iconBytes,
|
||||
'filters': filters.map((filter) => filter.toJson()).toList(),
|
||||
});
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class AppShortcutService {
|
||||
static const platform = MethodChannel('deckers.thibault/aves/shortcut');
|
||||
|
||||
// this ability will not change over the lifetime of the app
|
||||
static bool? _canPin;
|
||||
|
||||
static Future<bool> canPin() async {
|
||||
if (_canPin != null) return SynchronousFuture(_canPin!);
|
||||
|
||||
try {
|
||||
final result = await platform.invokeMethod('canPin');
|
||||
if (result != null) {
|
||||
_canPin = result;
|
||||
return result;
|
||||
}
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static Future<void> pin(String label, AvesEntry? entry, Set<CollectionFilter> filters) async {
|
||||
Uint8List? iconBytes;
|
||||
if (entry != null) {
|
||||
final size = entry.isVideo ? 0.0 : 256.0;
|
||||
iconBytes = await imageFileService.getThumbnail(
|
||||
uri: entry.uri,
|
||||
mimeType: entry.mimeType,
|
||||
pageId: entry.pageId,
|
||||
rotationDegrees: entry.rotationDegrees,
|
||||
isFlipped: entry.isFlipped,
|
||||
dateModifiedSecs: entry.dateModifiedSecs,
|
||||
extent: size,
|
||||
);
|
||||
}
|
||||
try {
|
||||
await platform.invokeMethod('pin', <String, dynamic>{
|
||||
'label': label,
|
||||
'iconBytes': iconBytes,
|
||||
'filters': filters.map((filter) => filter.toJson()).toList(),
|
||||
});
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
import 'package:aves/model/availability.dart';
|
||||
import 'package:aves/model/metadata_db.dart';
|
||||
import 'package:aves/services/embedded_data_service.dart';
|
||||
import 'package:aves/services/image_file_service.dart';
|
||||
import 'package:aves/services/media_store_service.dart';
|
||||
import 'package:aves/services/device_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_store_service.dart';
|
||||
import 'package:aves/services/metadata/metadata_edit_service.dart';
|
||||
import 'package:aves/services/metadata/metadata_fetch_service.dart';
|
||||
import 'package:aves/services/report_service.dart';
|
||||
import 'package:aves/services/storage_service.dart';
|
||||
import 'package:aves/services/time_service.dart';
|
||||
import 'package:aves/services/window_service.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
@ -18,14 +18,14 @@ final p.Context pContext = getIt<p.Context>();
|
|||
final AvesAvailability availability = getIt<AvesAvailability>();
|
||||
final MetadataDb metadataDb = getIt<MetadataDb>();
|
||||
|
||||
final DeviceService deviceService = getIt<DeviceService>();
|
||||
final EmbeddedDataService embeddedDataService = getIt<EmbeddedDataService>();
|
||||
final ImageFileService imageFileService = getIt<ImageFileService>();
|
||||
final MediaFileService mediaFileService = getIt<MediaFileService>();
|
||||
final MediaStoreService mediaStoreService = getIt<MediaStoreService>();
|
||||
final MetadataEditService metadataEditService = getIt<MetadataEditService>();
|
||||
final MetadataFetchService metadataFetchService = getIt<MetadataFetchService>();
|
||||
final ReportService reportService = getIt<ReportService>();
|
||||
final StorageService storageService = getIt<StorageService>();
|
||||
final TimeService timeService = getIt<TimeService>();
|
||||
final WindowService windowService = getIt<WindowService>();
|
||||
|
||||
void initPlatformServices() {
|
||||
|
@ -33,13 +33,13 @@ void initPlatformServices() {
|
|||
getIt.registerLazySingleton<AvesAvailability>(() => LiveAvesAvailability());
|
||||
getIt.registerLazySingleton<MetadataDb>(() => SqfliteMetadataDb());
|
||||
|
||||
getIt.registerLazySingleton<DeviceService>(() => PlatformDeviceService());
|
||||
getIt.registerLazySingleton<EmbeddedDataService>(() => PlatformEmbeddedDataService());
|
||||
getIt.registerLazySingleton<ImageFileService>(() => PlatformImageFileService());
|
||||
getIt.registerLazySingleton<MediaFileService>(() => PlatformMediaFileService());
|
||||
getIt.registerLazySingleton<MediaStoreService>(() => PlatformMediaStoreService());
|
||||
getIt.registerLazySingleton<MetadataEditService>(() => PlatformMetadataEditService());
|
||||
getIt.registerLazySingleton<MetadataFetchService>(() => PlatformMetadataFetchService());
|
||||
getIt.registerLazySingleton<ReportService>(() => CrashlyticsReportService());
|
||||
getIt.registerLazySingleton<StorageService>(() => PlatformStorageService());
|
||||
getIt.registerLazySingleton<TimeService>(() => PlatformTimeService());
|
||||
getIt.registerLazySingleton<WindowService>(() => PlatformWindowService());
|
||||
}
|
||||
|
|
|
@ -1,10 +1,27 @@
|
|||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class DeviceService {
|
||||
abstract class DeviceService {
|
||||
Future<String?> getDefaultTimeZone();
|
||||
|
||||
Future<int> getPerformanceClass();
|
||||
}
|
||||
|
||||
class PlatformDeviceService implements DeviceService {
|
||||
static const platform = MethodChannel('deckers.thibault/aves/device');
|
||||
|
||||
static Future<int> getPerformanceClass() async {
|
||||
@override
|
||||
Future<String?> getDefaultTimeZone() async {
|
||||
try {
|
||||
return await platform.invokeMethod('getDefaultTimeZone');
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> getPerformanceClass() async {
|
||||
try {
|
||||
await platform.invokeMethod('getPerformanceClass');
|
||||
final result = await platform.invokeMethod('getPerformanceClass');
|
||||
|
|
|
@ -13,7 +13,7 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:streams_channel/streams_channel.dart';
|
||||
|
||||
abstract class ImageFileService {
|
||||
abstract class MediaFileService {
|
||||
Future<AvesEntry?> getEntry(String uri, String? mimeType);
|
||||
|
||||
Future<Uint8List> getSvg(
|
||||
|
@ -92,10 +92,10 @@ abstract class ImageFileService {
|
|||
Future<Map<String, dynamic>> rename(AvesEntry entry, String newName);
|
||||
}
|
||||
|
||||
class PlatformImageFileService implements ImageFileService {
|
||||
static const platform = MethodChannel('deckers.thibault/aves/image');
|
||||
static final StreamsChannel _byteStreamChannel = StreamsChannel('deckers.thibault/aves/image_byte_stream');
|
||||
static final StreamsChannel _opStreamChannel = StreamsChannel('deckers.thibault/aves/image_op_stream');
|
||||
class PlatformMediaFileService implements MediaFileService {
|
||||
static const platform = MethodChannel('deckers.thibault/aves/media_file');
|
||||
static final StreamsChannel _byteStreamChannel = StreamsChannel('deckers.thibault/aves/media_byte_stream');
|
||||
static final StreamsChannel _opStreamChannel = StreamsChannel('deckers.thibault/aves/media_op_stream');
|
||||
static const double thumbnailDefaultSize = 64.0;
|
||||
|
||||
static Map<String, dynamic> _toPlatformEntryMap(AvesEntry entry) {
|
|
@ -12,11 +12,14 @@ abstract class MediaStoreService {
|
|||
|
||||
// knownEntries: map of contentId -> dateModifiedSecs
|
||||
Stream<AvesEntry> getEntries(Map<int, int> knownEntries);
|
||||
|
||||
// returns media URI
|
||||
Future<Uri?> scanFile(String path, String mimeType);
|
||||
}
|
||||
|
||||
class PlatformMediaStoreService implements MediaStoreService {
|
||||
static const platform = MethodChannel('deckers.thibault/aves/mediastore');
|
||||
static final StreamsChannel _streamChannel = StreamsChannel('deckers.thibault/aves/mediastorestream');
|
||||
static const platform = MethodChannel('deckers.thibault/aves/media_store');
|
||||
static final StreamsChannel _streamChannel = StreamsChannel('deckers.thibault/aves/media_store_stream');
|
||||
|
||||
@override
|
||||
Future<List<int>> checkObsoleteContentIds(List<int> knownContentIds) async {
|
||||
|
@ -55,4 +58,19 @@ class PlatformMediaStoreService implements MediaStoreService {
|
|||
return Stream.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
// returns media URI
|
||||
@override
|
||||
Future<Uri?> scanFile(String path, String mimeType) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('scanFile', <String, dynamic>{
|
||||
'path': path,
|
||||
'mimeType': mimeType,
|
||||
});
|
||||
if (result != null) return Uri.tryParse(result);
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ class SvgMetadataService {
|
|||
|
||||
static Future<Size?> getSize(AvesEntry entry) async {
|
||||
try {
|
||||
final data = await imageFileService.getSvg(entry.uri, entry.mimeType);
|
||||
final data = await mediaFileService.getSvg(entry.uri, entry.mimeType);
|
||||
|
||||
final document = XmlDocument.parse(utf8.decode(data));
|
||||
final root = document.rootElement;
|
||||
|
@ -64,7 +64,7 @@ class SvgMetadataService {
|
|||
}
|
||||
|
||||
try {
|
||||
final data = await imageFileService.getSvg(entry.uri, entry.mimeType);
|
||||
final data = await mediaFileService.getSvg(entry.uri, entry.mimeType);
|
||||
|
||||
final document = XmlDocument.parse(utf8.decode(data));
|
||||
final root = document.rootElement;
|
||||
|
|
|
@ -4,7 +4,6 @@ import 'dart:typed_data';
|
|||
import 'package:aves/services/common/output_buffer.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:streams_channel/streams_channel.dart';
|
||||
|
||||
|
@ -27,9 +26,6 @@ abstract class StorageService {
|
|||
// returns number of deleted directories
|
||||
Future<int> deleteEmptyDirectories(Iterable<String> dirPaths);
|
||||
|
||||
// returns media URI
|
||||
Future<Uri?> scanFile(String path, String mimeType);
|
||||
|
||||
// return whether operation succeeded (`null` if user cancelled)
|
||||
Future<bool?> createFile(String name, String mimeType, Uint8List bytes);
|
||||
|
||||
|
@ -154,22 +150,6 @@ class PlatformStorageService implements StorageService {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// returns media URI
|
||||
@override
|
||||
Future<Uri?> scanFile(String path, String mimeType) async {
|
||||
debugPrint('scanFile with path=$path, mimeType=$mimeType');
|
||||
try {
|
||||
final result = await platform.invokeMethod('scanFile', <String, dynamic>{
|
||||
'path': path,
|
||||
'mimeType': mimeType,
|
||||
});
|
||||
if (result != null) return Uri.tryParse(result);
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool?> createFile(String name, String mimeType, Uint8List bytes) async {
|
||||
try {
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
abstract class TimeService {
|
||||
Future<String?> getDefaultTimeZone();
|
||||
}
|
||||
|
||||
class PlatformTimeService implements TimeService {
|
||||
static const platform = MethodChannel('deckers.thibault/aves/time');
|
||||
|
||||
@override
|
||||
Future<String?> getDefaultTimeZone() async {
|
||||
try {
|
||||
return await platform.invokeMethod('getDefaultTimeZone');
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -41,7 +41,7 @@ class _AvesAppState extends State<AvesApp> {
|
|||
// observers are not registered when using the same list object with different items
|
||||
// the list itself needs to be reassigned
|
||||
List<NavigatorObserver> _navigatorObservers = [];
|
||||
final EventChannel _mediaStoreChangeChannel = const EventChannel('deckers.thibault/aves/mediastorechange');
|
||||
final EventChannel _mediaStoreChangeChannel = const EventChannel('deckers.thibault/aves/media_store_change');
|
||||
final EventChannel _newIntentChannel = const EventChannel('deckers.thibault/aves/intent');
|
||||
final EventChannel _errorChannel = const EventChannel('deckers.thibault/aves/error');
|
||||
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey(debugLabel: 'app-navigator');
|
||||
|
|
|
@ -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_source.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/services/app_shortcut_service.dart';
|
||||
import 'package:aves/services/android_app_service.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/widgets/collection/entry_set_action_delegate.dart';
|
||||
import 'package:aves/widgets/collection/filter_bar.dart';
|
||||
|
@ -61,7 +61,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
vsync: this,
|
||||
);
|
||||
_isSelectingNotifier.addListener(_onActivityChange);
|
||||
_canAddShortcutsLoader = AppShortcutService.canPin();
|
||||
_canAddShortcutsLoader = AndroidAppService.canPinToHomeScreen();
|
||||
_registerWidget(widget);
|
||||
WidgetsBinding.instance!.addPostFrameCallback((_) => _onFilterChanged());
|
||||
}
|
||||
|
@ -370,7 +370,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
final name = result.item2;
|
||||
if (name.isEmpty) return;
|
||||
|
||||
unawaited(AppShortcutService.pin(name, coverEntry, filters));
|
||||
unawaited(AndroidAppService.pinToHomeScreen(name, coverEntry, filters));
|
||||
}
|
||||
|
||||
void _goToSearch() {
|
||||
|
|
|
@ -122,7 +122,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
source.pauseMonitoring();
|
||||
showOpReport<MoveOpEvent>(
|
||||
context: context,
|
||||
opStream: imageFileService.move(todoEntries, copy: copy, destinationAlbum: destinationAlbum),
|
||||
opStream: mediaFileService.move(todoEntries, copy: copy, destinationAlbum: destinationAlbum),
|
||||
itemCount: todoCount,
|
||||
onDone: (processed) async {
|
||||
final movedOps = processed.where((e) => e.success).toSet();
|
||||
|
@ -222,7 +222,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
source.pauseMonitoring();
|
||||
showOpReport<ImageOpEvent>(
|
||||
context: context,
|
||||
opStream: imageFileService.delete(selectedItems),
|
||||
opStream: mediaFileService.delete(selectedItems),
|
||||
itemCount: todoCount,
|
||||
onDone: (processed) async {
|
||||
final deletedUris = processed.where((event) => event.success).map((event) => event.uri).toSet();
|
||||
|
|
|
@ -155,7 +155,7 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
|
|||
if (widget.cancellableNotifier?.value ?? false) {
|
||||
final key = await _currentProviderStream?.provider.provider.obtainKey(ImageConfiguration.empty);
|
||||
if (key is ThumbnailProviderKey) {
|
||||
imageFileService.cancelThumbnail(key);
|
||||
mediaFileService.cancelThumbnail(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ class _DebugCacheSectionState extends State<DebugCacheSection> with AutomaticKee
|
|||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: imageFileService.clearSizedThumbnailDiskCache,
|
||||
onPressed: mediaFileService.clearSizedThumbnailDiskCache,
|
||||
child: const Text('Clear'),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -167,7 +167,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> {
|
|||
source.pauseMonitoring();
|
||||
showOpReport<ImageOpEvent>(
|
||||
context: context,
|
||||
opStream: imageFileService.delete(todoEntries),
|
||||
opStream: mediaFileService.delete(todoEntries),
|
||||
itemCount: todoCount,
|
||||
onDone: (processed) async {
|
||||
final deletedUris = processed.where((event) => event.success).map((event) => event.uri).toSet();
|
||||
|
@ -226,7 +226,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> {
|
|||
source.pauseMonitoring();
|
||||
showOpReport<MoveOpEvent>(
|
||||
context: context,
|
||||
opStream: imageFileService.move(todoEntries, copy: false, destinationAlbum: destinationAlbum),
|
||||
opStream: mediaFileService.move(todoEntries, copy: false, destinationAlbum: destinationAlbum),
|
||||
itemCount: todoCount,
|
||||
onDone: (processed) async {
|
||||
final movedOps = processed.where((e) => e.success).toSet();
|
||||
|
|
|
@ -125,7 +125,7 @@ class _HomePageState extends State<HomePage> {
|
|||
}
|
||||
|
||||
Future<AvesEntry?> _initViewerEntry({required String uri, required String? mimeType}) async {
|
||||
final entry = await imageFileService.getEntry(uri, mimeType);
|
||||
final entry = await mediaFileService.getEntry(uri, mimeType);
|
||||
if (entry != null) {
|
||||
// cataloguing is essential for coordinates and video rotation
|
||||
await entry.catalog(background: false, persist: false);
|
||||
|
|
|
@ -204,7 +204,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
showOpReport<ExportOpEvent>(
|
||||
context: context,
|
||||
// TODO TLAD [SVG] export separately from raster images (sending bytes, like frame captures)
|
||||
opStream: imageFileService.export(
|
||||
opStream: mediaFileService.export(
|
||||
selection,
|
||||
mimeType: MimeTypes.jpeg,
|
||||
destinationAlbum: destinationAlbum,
|
||||
|
@ -286,7 +286,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
MaterialPageRoute(
|
||||
settings: const RouteSettings(name: SourceViewerPage.routeName),
|
||||
builder: (context) => SourceViewerPage(
|
||||
loader: () => imageFileService.getSvg(entry.uri, entry.mimeType).then(utf8.decode),
|
||||
loader: () => mediaFileService.getSvg(entry.uri, entry.mimeType).then(utf8.decode),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -74,7 +74,7 @@ class EntryPrinter with FeedbackMixin {
|
|||
|
||||
Future<pdf.Widget?> _buildPageImage(AvesEntry entry) async {
|
||||
if (entry.isSvg) {
|
||||
final bytes = await imageFileService.getSvg(entry.uri, entry.mimeType);
|
||||
final bytes = await mediaFileService.getSvg(entry.uri, entry.mimeType);
|
||||
if (bytes.isNotEmpty) {
|
||||
return pdf.SvgImage(svg: utf8.decode(bytes));
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
}
|
||||
};
|
||||
|
||||
final newFields = await imageFileService.captureFrame(
|
||||
final newFields = await mediaFileService.captureFrame(
|
||||
entry,
|
||||
desiredName: '${entry.bestTitle}_${'$positionMillis'.padLeft(8, '0')}',
|
||||
exif: exif,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:aves/services/time_service.dart';
|
||||
import 'package:aves/services/device_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class FakeTimeService extends Fake implements TimeService {
|
||||
class FakeDeviceService extends Fake implements DeviceService {
|
||||
@override
|
||||
Future<String> getDefaultTimeZone() => SynchronousFuture('');
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/services/image_file_service.dart';
|
||||
import 'package:aves/services/media/media_file_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'media_store_service.dart';
|
||||
|
||||
class FakeImageFileService extends Fake implements ImageFileService {
|
||||
class FakeMediaFileService extends Fake implements MediaFileService {
|
||||
@override
|
||||
Future<Map<String, dynamic>> rename(AvesEntry entry, String newName) {
|
||||
final contentId = FakeMediaStoreService.nextContentId;
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/services/common/image_op_events.dart';
|
||||
import 'package:aves/services/media_store_service.dart';
|
||||
import 'package:aves/services/media/media_store_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
|
|
|
@ -8,12 +8,12 @@ import 'package:aves/model/metadata_db.dart';
|
|||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/model/source/media_store_source.dart';
|
||||
import 'package:aves/services/image_file_service.dart';
|
||||
import 'package:aves/services/media_store_service.dart';
|
||||
import 'package:aves/services/metadata/metadata_fetch_service.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/services/device_service.dart';
|
||||
import 'package:aves/services/media/media_file_service.dart';
|
||||
import 'package:aves/services/media/media_store_service.dart';
|
||||
import 'package:aves/services/metadata/metadata_fetch_service.dart';
|
||||
import 'package:aves/services/storage_service.dart';
|
||||
import 'package:aves/services/time_service.dart';
|
||||
import 'package:aves/services/window_service.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
@ -21,12 +21,12 @@ import 'package:flutter_test/flutter_test.dart';
|
|||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../fake/availability.dart';
|
||||
import '../fake/image_file_service.dart';
|
||||
import '../fake/device_service.dart';
|
||||
import '../fake/media_file_service.dart';
|
||||
import '../fake/media_store_service.dart';
|
||||
import '../fake/metadata_db.dart';
|
||||
import '../fake/metadata_fetch_service.dart';
|
||||
import '../fake/storage_service.dart';
|
||||
import '../fake/time_service.dart';
|
||||
import '../fake/window_service.dart';
|
||||
|
||||
void main() {
|
||||
|
@ -40,11 +40,11 @@ void main() {
|
|||
getIt.registerLazySingleton<AvesAvailability>(() => FakeAvesAvailability());
|
||||
getIt.registerLazySingleton<MetadataDb>(() => FakeMetadataDb());
|
||||
|
||||
getIt.registerLazySingleton<ImageFileService>(() => FakeImageFileService());
|
||||
getIt.registerLazySingleton<DeviceService>(() => FakeDeviceService());
|
||||
getIt.registerLazySingleton<MediaFileService>(() => FakeMediaFileService());
|
||||
getIt.registerLazySingleton<MediaStoreService>(() => FakeMediaStoreService());
|
||||
getIt.registerLazySingleton<MetadataFetchService>(() => FakeMetadataFetchService());
|
||||
getIt.registerLazySingleton<StorageService>(() => FakeStorageService());
|
||||
getIt.registerLazySingleton<TimeService>(() => FakeTimeService());
|
||||
getIt.registerLazySingleton<WindowService>(() => FakeWindowService());
|
||||
|
||||
await settings.init();
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'package:aves/main.dart' as app;
|
|||
import 'package:aves/model/settings/enums.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/services/storage_service.dart';
|
||||
import 'package:aves/services/media/media_store_service.dart';
|
||||
import 'package:aves/services/window_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/src/widgets/media_query.dart';
|
||||
|
@ -19,9 +19,9 @@ void main() {
|
|||
// scan files copied from test assets
|
||||
// we do it via the app instead of broadcasting via ADB
|
||||
// because `MEDIA_SCANNER_SCAN_FILE` intent got deprecated in API 29
|
||||
final storageService = PlatformStorageService();
|
||||
storageService.scanFile(p.join(targetPicturesDir, 'aves_logo.svg'), 'image/svg+xml');
|
||||
storageService.scanFile(p.join(targetPicturesDir, 'ipse.jpg'), 'image/jpeg');
|
||||
final mediaStoreService = PlatformMediaStoreService();
|
||||
mediaStoreService.scanFile(p.join(targetPicturesDir, 'aves_logo.svg'), 'image/svg+xml');
|
||||
mediaStoreService.scanFile(p.join(targetPicturesDir, 'ipse.jpg'), 'image/jpeg');
|
||||
|
||||
configureAndLaunch();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue