From 3ea5ddd7533ebe86b9c2d0117a65aff99425f8d6 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Mon, 7 Feb 2022 13:05:05 +0900 Subject: [PATCH] minor fixes --- .../aves/SearchSuggestionsProvider.kt | 43 +++++++++++-------- .../channel/calls/MetadataFetchHandler.kt | 7 ++- .../thibault/aves/metadata/SphericalVideo.kt | 4 +- .../deckers/thibault/aves/utils/MimeTypes.kt | 6 ++- lib/widgets/aves_app.dart | 31 ++++++++++--- lib/widgets/dialogs/aves_dialog.dart | 2 +- lib/widgets/dialogs/export_entry_dialog.dart | 22 ++++++---- 7 files changed, 74 insertions(+), 41 deletions(-) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/SearchSuggestionsProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/SearchSuggestionsProvider.kt index ca9ba2fa7..c785cebd5 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/SearchSuggestionsProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/SearchSuggestionsProvider.kt @@ -77,29 +77,34 @@ class SearchSuggestionsProvider : MethodChannel.MethodCallHandler, ContentProvid val backgroundChannel = MethodChannel(messenger, BACKGROUND_CHANNEL) backgroundChannel.setMethodCallHandler(this) - return suspendCoroutine { cont -> - GlobalScope.launch { - FlutterUtils.runOnUiThread { - backgroundChannel.invokeMethod("getSuggestions", hashMapOf( - "query" to query, - "locale" to Locale.getDefault().toString(), - "use24hour" to DateFormat.is24HourFormat(context), - ), object : MethodChannel.Result { - override fun success(result: Any?) { - @Suppress("unchecked_cast") - cont.resume(result as List) - } + try { + return suspendCoroutine { cont -> + GlobalScope.launch { + FlutterUtils.runOnUiThread { + backgroundChannel.invokeMethod("getSuggestions", hashMapOf( + "query" to query, + "locale" to Locale.getDefault().toString(), + "use24hour" to DateFormat.is24HourFormat(context), + ), object : MethodChannel.Result { + override fun success(result: Any?) { + @Suppress("unchecked_cast") + cont.resume(result as List) + } - override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) { - cont.resumeWithException(Exception("$errorCode: $errorMessage\n$errorDetails")) - } + override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) { + cont.resumeWithException(Exception("$errorCode: $errorMessage\n$errorDetails")) + } - override fun notImplemented() { - cont.resumeWithException(NotImplementedError("getSuggestions")) - } - }) + override fun notImplemented() { + cont.resumeWithException(NotImplementedError("getSuggestions")) + } + }) + } } } + } catch (e: Exception) { + Log.e(LOG_TAG, "failed to get suggestions", e) + return ArrayList() } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt index b461e2b9f..dcf6d31c5 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt @@ -80,7 +80,6 @@ import kotlinx.coroutines.launch import java.nio.charset.Charset import java.nio.charset.StandardCharsets import java.text.ParseException -import java.util.* import kotlin.math.roundToInt import kotlin.math.roundToLong @@ -412,19 +411,19 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { // File type for (dir in metadata.getDirectoriesOfType(FileTypeDirectory::class.java)) { - // * `metadata-extractor` sometimes detects the wrong MIME type (e.g. `pef` file as `tiff`, `mpeg` as `dvd`) + // * `metadata-extractor` sometimes detects the wrong MIME type (e.g. `pef` file as `tiff`, `mpeg` as `dvd`, `avif` as `mov`) // * the content resolver / media store sometimes reports the wrong MIME type (e.g. `png` file as `jpeg`, `tiff` as `srw`) // * `context.getContentResolver().getType()` sometimes returns an incorrect value // * `MediaMetadataRetriever.setDataSource()` sometimes fails with `status = 0x80000000` // * file extension is unreliable - // In the end, `metadata-extractor` is the most reliable, except for `tiff`/`dvd` (false positives, false negatives), + // In the end, `metadata-extractor` is the most reliable, except for `tiff`/`dvd`/`mov` (false positives, false negatives), // in which case we trust the file extension // cf https://github.com/drewnoakes/metadata-extractor/issues/296 if (path?.matches(TIFF_EXTENSION_PATTERN) == true) { metadataMap[KEY_MIME_TYPE] = MimeTypes.TIFF } else { dir.getSafeString(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE) { - if (it != MimeTypes.TIFF && it != MimeTypes.DVD) { + if (it != MimeTypes.TIFF && it != MimeTypes.DVD && it != MimeTypes.MOV) { metadataMap[KEY_MIME_TYPE] = it } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/SphericalVideo.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/SphericalVideo.kt index 23f5e9020..e2a1c45e4 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/SphericalVideo.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/SphericalVideo.kt @@ -62,7 +62,7 @@ class GSpherical(xmlBytes: ByteArray) { } } - fun describe(): Map = hashMapOf( + fun describe(): Map = hashMapOf( "Spherical" to spherical.toString(), "Stitched" to stitched.toString(), "Stitching Software" to stitchingSoftware, @@ -79,7 +79,7 @@ class GSpherical(xmlBytes: ByteArray) { "Cropped Area Image Height Pixels" to croppedAreaImageHeightPixels?.toString(), "Cropped Area Left Pixels" to croppedAreaLeftPixels?.toString(), "Cropped Area Top Pixels" to croppedAreaTopPixels?.toString(), - ).filterValues { it != null } + ).filterValues { it != null }.mapValues { it.value as String } companion object SphericalVideo { private val LOG_TAG = LogUtils.createTag() diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt index 51d83b4a3..15d64b552 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt @@ -6,6 +6,7 @@ object MimeTypes { const val ANY = "*/*" // generic raster + private const val AVIF = "image/avif" const val BMP = "image/bmp" private const val DJVU = "image/vnd.djvu" const val GIF = "image/gif" @@ -49,7 +50,7 @@ object MimeTypes { private const val AVI_VND = "video/vnd.avi" const val DVD = "video/dvd" private const val MKV = "video/x-matroska" - private const val MOV = "video/quicktime" + const val MOV = "video/quicktime" private const val MP2T = "video/mp2t" private const val MP2TS = "video/mp2ts" const val MP4 = "video/mp4" @@ -72,7 +73,7 @@ object MimeTypes { // returns whether the specified MIME type represents // a raster image format that allows an alpha channel fun canHaveAlpha(mimeType: String?) = when (mimeType) { - BMP, GIF, ICO, PNG, SVG, TIFF, WEBP -> true + AVIF, BMP, GIF, ICO, PNG, SVG, TIFF, WEBP -> true else -> false } @@ -150,6 +151,7 @@ object MimeTypes { fun extensionFor(mimeType: String): String? = when (mimeType) { ARW -> ".arw" AVI, AVI_VND -> ".avi" + AVIF -> ".avif" BMP -> ".bmp" CR2 -> ".cr2" CRW -> ".crw" diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart index 995ad2040..d8b8e5d4a 100644 --- a/lib/widgets/aves_app.dart +++ b/lib/widgets/aves_app.dart @@ -158,15 +158,35 @@ class _AvesAppState extends State { ); } + // setup before the first page is displayed. keep it short Future _setup() async { + final stopwatch = Stopwatch()..start(); + + // TODO TLAD [init] init settings/device w/o platform calls (first platform channel call takes ~800ms): + // 1) use cached values if any, + // 2a) call platform w/ delay if cached + // 2b) call platform w/o delay if not cached + // 3) cache platform call results across app restarts + + await device.init(); + final isRotationLocked = await windowService.isRotationLocked(); + final areAnimationsRemoved = await AccessibilityService.areAnimationsRemoved(); + + // TODO TLAD [init] migrate settings away from `shared_preferences` to a platform-free solution await settings.init( monitorPlatformSettings: true, - isRotationLocked: await windowService.isRotationLocked(), - areAnimationsRemoved: await AccessibilityService.areAnimationsRemoved(), + isRotationLocked: isRotationLocked, + areAnimationsRemoved: areAnimationsRemoved, ); - await device.init(); - FijkLog.setLevel(FijkLogLevel.Warn); + _monitorSettings(); + FijkLog.setLevel(FijkLogLevel.Warn); + unawaited(_setupErrorReporting()); + + debugPrint('App setup in ${stopwatch.elapsed.inMilliseconds}ms'); + } + + void _monitorSettings() { // keep screen on settings.updateStream.where((key) => key == Settings.keepScreenOnKey).listen( (_) => settings.keepScreenOn.apply(), @@ -183,8 +203,9 @@ class _AvesAppState extends State { } }, ); + } - // error reporting + Future _setupErrorReporting() async { await reportService.init(); settings.updateStream.where((key) => key == Settings.isErrorReportingAllowedKey).listen( (_) => reportService.setCollectionEnabled(settings.isErrorReportingAllowed), diff --git a/lib/widgets/dialogs/aves_dialog.dart b/lib/widgets/dialogs/aves_dialog.dart index 020a70956..cdc9d6d2a 100644 --- a/lib/widgets/dialogs/aves_dialog.dart +++ b/lib/widgets/dialogs/aves_dialog.dart @@ -44,7 +44,7 @@ class AvesDialog extends StatelessWidget { // and overflow feedback ignores the dialog shape, // so we restrict scrolling to the content instead content: _buildContent(context), - contentPadding: scrollableContent != null ? EdgeInsets.zero : EdgeInsets.fromLTRB(horizontalContentPadding, 20, horizontalContentPadding, 0), + contentPadding: scrollableContent != null ? EdgeInsets.zero : EdgeInsets.only(left: horizontalContentPadding, top: 20, right: horizontalContentPadding), actions: actions, actionsPadding: const EdgeInsets.symmetric(horizontal: 8), shape: shape(context), diff --git a/lib/widgets/dialogs/export_entry_dialog.dart b/lib/widgets/dialogs/export_entry_dialog.dart index 1fc67ee6e..d84b6c10d 100644 --- a/lib/widgets/dialogs/export_entry_dialog.dart +++ b/lib/widgets/dialogs/export_entry_dialog.dart @@ -51,12 +51,14 @@ class _ExportEntryDialogState extends State { @override Widget build(BuildContext context) { final l10n = context.l10n; + const contentHorizontalPadding = EdgeInsets.symmetric(horizontal: AvesDialog.defaultHorizontalContentPadding); + return AvesDialog( - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( + scrollableContent: [ + const SizedBox(height: 16), + Padding( + padding: contentHorizontalPadding, + child: Row( mainAxisSize: MainAxisSize.min, children: [ Text(l10n.exportEntryDialogFormat), @@ -77,7 +79,10 @@ class _ExportEntryDialogState extends State { ), ], ), - Row( + ), + Padding( + padding: contentHorizontalPadding, + child: Row( crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, children: [ @@ -108,8 +113,9 @@ class _ExportEntryDialogState extends State { ), ], ), - ], - ), + ), + const SizedBox(height: 16), + ], actions: [ TextButton( onPressed: () => Navigator.pop(context),