From 35958d87fdf8f28f415b1ea5e873a9724136ba64 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sun, 28 Nov 2021 19:32:04 +0900 Subject: [PATCH] API 16 support prep --- android/app/build.gradle | 5 +- .../deckers/thibault/aves/AnalysisService.kt | 4 +- .../channel/calls/AccessibilityHandler.kt | 10 +-- .../aves/channel/calls/AppAdapterHandler.kt | 9 ++- .../aves/channel/calls/DeviceHandler.kt | 24 ++++--- .../channel/calls/MetadataFetchHandler.kt | 4 +- .../streams/SettingsChangeStreamHandler.kt | 23 +++--- .../metadata/MediaMetadataRetrieverHelper.kt | 4 +- .../thibault/aves/model/SourceEntry.kt | 5 +- .../thibault/aves/utils/PermissionManager.kt | 71 +++++++++---------- lib/model/device.dart | 13 +++- lib/model/filters/filters.dart | 2 + lib/model/filters/location.dart | 21 +++--- lib/services/analysis_service.dart | 2 +- lib/services/media/media_file_service.dart | 9 +-- lib/services/storage_service.dart | 12 ++-- .../collection/entry_set_action_delegate.dart | 5 +- lib/widgets/viewer/entry_action_delegate.dart | 6 +- lib/widgets/viewer/overlay/top.dart | 1 + pubspec.lock | 4 +- 20 files changed, 143 insertions(+), 91 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 5f39c6960..fa6db34f1 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -56,8 +56,7 @@ android { // minSdkVersion constraints: // - Flutter & other plugins: 16 // - google_maps_flutter v2.1.1: 20 - // - Aves native: 19 - minSdkVersion 19 + minSdkVersion 16 targetSdkVersion 31 versionCode flutterVersionCode.toInteger() versionName flutterVersionName @@ -149,7 +148,7 @@ dependencies { // forked, built by JitPack, cf https://jitpack.io/p/deckerst/Android-TiffBitmapFactory implementation 'com.github.deckerst:Android-TiffBitmapFactory:876e53870a' // forked, built by JitPack, cf https://jitpack.io/p/deckerst/pixymeta-android - implementation 'com.github.deckerst:pixymeta-android:a86b1b8e4c' + implementation 'com.github.deckerst:pixymeta-android:706bd73d6e' implementation 'com.github.bumptech.glide:glide:4.12.0' kapt 'androidx.annotation:annotation:1.3.0' diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/AnalysisService.kt b/android/app/src/main/kotlin/deckers/thibault/aves/AnalysisService.kt index b05c850e3..409f361bd 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/AnalysisService.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/AnalysisService.kt @@ -23,7 +23,6 @@ import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import kotlinx.coroutines.runBlocking -import java.util.* class AnalysisService : MethodChannel.MethodCallHandler, Service() { private var backgroundFlutterEngine: FlutterEngine? = null @@ -141,11 +140,12 @@ class AnalysisService : MethodChannel.MethodCallHandler, Service() { getString(R.string.analysis_notification_action_stop), stopServiceIntent ).build() + val icon = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) R.drawable.ic_notification else R.mipmap.ic_launcher_round return NotificationCompat.Builder(this, CHANNEL_ANALYSIS) .setContentTitle(title ?: getText(R.string.analysis_notification_default_title)) .setContentText(message) .setBadgeIconType(NotificationCompat.BADGE_ICON_NONE) - .setSmallIcon(R.drawable.ic_notification) + .setSmallIcon(icon) .setContentIntent(openAppIntent) .setPriority(NotificationCompat.PRIORITY_LOW) .addAction(stopAction) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AccessibilityHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AccessibilityHandler.kt index ebd55045c..b18950cf0 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AccessibilityHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AccessibilityHandler.kt @@ -24,10 +24,12 @@ class AccessibilityHandler(private val activity: Activity) : MethodCallHandler { private fun areAnimationsRemoved(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) { var removed = false - try { - removed = Settings.Global.getFloat(activity.contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE) == 0f - } catch (e: Exception) { - Log.w(LOG_TAG, "failed to get settings with error=${e.message}", null) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + try { + removed = Settings.Global.getFloat(activity.contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE) == 0f + } catch (e: Exception) { + Log.w(LOG_TAG, "failed to get settings with error=${e.message}", null) + } } result.success(removed) } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt index 6472a3149..df244191f 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt @@ -62,7 +62,14 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { fun addPackageDetails(intent: Intent) { // apps tend to use their name in English when creating directories // so we get their names in English as well as the current locale - val englishConfig = Configuration().apply { setLocale(Locale.ENGLISH) } + val englishConfig = Configuration().apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + setLocale(Locale.ENGLISH) + } else { + @Suppress("deprecation") + locale = Locale.ENGLISH + } + } val pm = context.packageManager for (resolveInfo in pm.queryIntentActivities(intent, 0)) { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DeviceHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DeviceHandler.kt index a6160eaaf..88f9fe146 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DeviceHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DeviceHandler.kt @@ -20,15 +20,21 @@ class DeviceHandler(private val context: Context) : MethodCallHandler { } private fun getCapabilities(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) { - result.success(hashMapOf( - "canGrantDirectoryAccess" to (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP), - "canPinShortcut" to ShortcutManagerCompat.isRequestPinShortcutSupported(context), - "canPrint" to (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT), - // as of google_maps_flutter v2.1.1, minSDK is 20 because of default PlatformView usage, - // but using hybrid composition would make it usable on API 19 too, - // cf https://github.com/flutter/flutter/issues/23728 - "canRenderGoogleMaps" to (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH), - )) + val sdkInt = Build.VERSION.SDK_INT + result.success( + hashMapOf( + "canGrantDirectoryAccess" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP), + "canPinShortcut" to ShortcutManagerCompat.isRequestPinShortcutSupported(context), + "canPrint" to (sdkInt >= Build.VERSION_CODES.KITKAT), + "canRenderEmojis" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP), + // as of google_maps_flutter v2.1.1, minSDK is 20 because of default PlatformView usage, + // but using hybrid composition would make it usable on API 19 too, + // cf https://github.com/flutter/flutter/issues/23728 + "canRenderGoogleMaps" to (sdkInt >= Build.VERSION_CODES.KITKAT_WATCH), + "hasFilePicker" to (sdkInt >= Build.VERSION_CODES.KITKAT), + "showPinShortcutFeedback" to (sdkInt >= Build.VERSION_CODES.O), + ) + ) } private fun getDefaultTimeZone(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) { 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 2395c6170..bfb96d900 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 @@ -584,7 +584,9 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { var flags = (metadataMap[KEY_FLAGS] ?: 0) as Int try { - retriever.getSafeInt(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) { metadataMap[KEY_ROTATION_DEGREES] = it } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + retriever.getSafeInt(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) { metadataMap[KEY_ROTATION_DEGREES] = it } + } if (!metadataMap.containsKey(KEY_DATE_MILLIS)) { retriever.getSafeDateMillis(MediaMetadataRetriever.METADATA_KEY_DATE) { metadataMap[KEY_DATE_MILLIS] = it } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/SettingsChangeStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/SettingsChangeStreamHandler.kt index 60cb1f800..4b53b693c 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/SettingsChangeStreamHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/SettingsChangeStreamHandler.kt @@ -3,6 +3,7 @@ package deckers.thibault.aves.channel.streams import android.content.Context import android.database.ContentObserver import android.net.Uri +import android.os.Build import android.os.Handler import android.os.Looper import android.provider.Settings @@ -32,12 +33,13 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S override fun onChange(selfChange: Boolean, uri: Uri?) { if (update()) { - success( - hashMapOf( - Settings.System.ACCELEROMETER_ROTATION to accelerometerRotation, - Settings.Global.TRANSITION_ANIMATION_SCALE to transitionAnimationScale, - ) + val settings: FieldMap = hashMapOf( + Settings.System.ACCELEROMETER_ROTATION to accelerometerRotation, ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + settings[Settings.Global.TRANSITION_ANIMATION_SCALE] = transitionAnimationScale + } + success(settings) } } @@ -49,12 +51,13 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S accelerometerRotation = newAccelerometerRotation changed = true } - val newTransitionAnimationScale = Settings.Global.getFloat(context.contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE) - if (transitionAnimationScale != newTransitionAnimationScale) { - transitionAnimationScale = newTransitionAnimationScale - changed = true + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + val newTransitionAnimationScale = Settings.Global.getFloat(context.contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE) + if (transitionAnimationScale != newTransitionAnimationScale) { + transitionAnimationScale = newTransitionAnimationScale + changed = true + } } - } catch (e: Exception) { Log.w(LOG_TAG, "failed to get settings with error=${e.message}", null) } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MediaMetadataRetrieverHelper.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MediaMetadataRetrieverHelper.kt index 1d04c0de9..4b2f04d16 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MediaMetadataRetrieverHelper.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MediaMetadataRetrieverHelper.kt @@ -27,11 +27,13 @@ object MediaMetadataRetrieverHelper { MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS to "Number of Tracks", MediaMetadataRetriever.METADATA_KEY_TITLE to "Title", MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT to "Video Height", - MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION to "Video Rotation", MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH to "Video Width", MediaMetadataRetriever.METADATA_KEY_WRITER to "Writer", MediaMetadataRetriever.METADATA_KEY_YEAR to "Year", ).apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + put(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION, "Video Rotation") + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { put(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE, "Capture Framerate") } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt index 924c76a1d..cbba47781 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt @@ -5,6 +5,7 @@ import android.content.Context import android.graphics.BitmapFactory import android.media.MediaMetadataRetriever import android.net.Uri +import android.os.Build import androidx.exifinterface.media.ExifInterface import com.drew.imaging.ImageMetadataReader import com.drew.metadata.avi.AviDirectory @@ -135,10 +136,12 @@ class SourceEntry { try { retriever.getSafeInt(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) { width = it } retriever.getSafeInt(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT) { height = it } - retriever.getSafeInt(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) { sourceRotationDegrees = it } retriever.getSafeLong(MediaMetadataRetriever.METADATA_KEY_DURATION) { durationMillis = it } retriever.getSafeDateMillis(MediaMetadataRetriever.METADATA_KEY_DATE) { sourceDateTakenMillis = it } retriever.getSafeString(MediaMetadataRetriever.METADATA_KEY_TITLE) { title = it } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + retriever.getSafeInt(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) { sourceRotationDegrees = it } + } } catch (e: Exception) { // ignore } finally { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt index 7a06458ca..a078ea9a1 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt @@ -142,39 +142,6 @@ object PermissionManager { } } - fun getRestrictedDirectories(context: Context): List> { - val dirs = ArrayList>() - val sdkInt = Build.VERSION.SDK_INT - - if (sdkInt >= Build.VERSION_CODES.R) { - // cf https://developer.android.com/about/versions/11/privacy/storage#directory-access - val volumePaths = StorageUtils.getVolumePaths(context) - dirs.addAll(volumePaths.map { - hashMapOf( - "volumePath" to it, - "relativeDir" to "", - ) - }) - dirs.addAll(volumePaths.map { - hashMapOf( - "volumePath" to it, - "relativeDir" to Environment.DIRECTORY_DOWNLOADS, - ) - }) - } else if (sdkInt == Build.VERSION_CODES.KITKAT || sdkInt == Build.VERSION_CODES.KITKAT_WATCH) { - // no SD card volume access on KitKat - val primaryVolume = StorageUtils.getPrimaryVolumePath(context) - val nonPrimaryVolumes = StorageUtils.getVolumePaths(context).filter { it != primaryVolume } - dirs.addAll(nonPrimaryVolumes.map { - hashMapOf( - "volumePath" to it, - "relativeDir" to "", - ) - }) - } - return dirs - } - fun canInsertByMediaStore(directories: List): Boolean { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { directories.all { @@ -217,14 +184,46 @@ object PermissionManager { // from API 30 / Android 11 / R, any storage requires access permission if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) { accessibleDirs.addAll(StorageUtils.getVolumePaths(context)) - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP - && Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q - ) { + } else if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { accessibleDirs.add(StorageUtils.getPrimaryVolumePath(context)) } return accessibleDirs } + fun getRestrictedDirectories(context: Context): List> { + val dirs = ArrayList>() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // cf https://developer.android.com/about/versions/11/privacy/storage#directory-access + val volumePaths = StorageUtils.getVolumePaths(context) + dirs.addAll(volumePaths.map { + hashMapOf( + "volumePath" to it, + "relativeDir" to "", + ) + }) + dirs.addAll(volumePaths.map { + hashMapOf( + "volumePath" to it, + "relativeDir" to Environment.DIRECTORY_DOWNLOADS, + ) + }) + } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT + || Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT_WATCH) { + // removable storage requires access permission, at the file level + // without directory access, we consider the whole volume restricted + val primaryVolume = StorageUtils.getPrimaryVolumePath(context) + val nonPrimaryVolumes = StorageUtils.getVolumePaths(context).filter { it != primaryVolume } + dirs.addAll(nonPrimaryVolumes.map { + hashMapOf( + "volumePath" to it, + "relativeDir" to "", + ) + }) + } + return dirs + } + // As of Android R, `MediaStore.getDocumentUri` fails if any of the persisted // URI permissions we hold points to a folder that no longer exists, // so we should remove these obsolete URIs before proceeding. diff --git a/lib/model/device.dart b/lib/model/device.dart index 0a3cf3ea4..821a22699 100644 --- a/lib/model/device.dart +++ b/lib/model/device.dart @@ -5,7 +5,8 @@ final Device device = Device._private(); class Device { late final String _userAgent; - late final bool _canGrantDirectoryAccess, _canPinShortcut, _canPrint, _canRenderGoogleMaps; + late final bool _canGrantDirectoryAccess, _canPinShortcut, _canPrint, _canRenderEmojis, _canRenderGoogleMaps; + late final bool _hasFilePicker, _showPinShortcutFeedback; String get userAgent => _userAgent; @@ -15,8 +16,15 @@ class Device { bool get canPrint => _canPrint; + bool get canRenderEmojis => _canRenderEmojis; + bool get canRenderGoogleMaps => _canRenderGoogleMaps; + // TODO TLAD toggle settings > import/export, about > bug report > save + bool get hasFilePicker => _hasFilePicker; + + bool get showPinShortcutFeedback => _showPinShortcutFeedback; + Device._private(); Future init() async { @@ -27,6 +35,9 @@ class Device { _canGrantDirectoryAccess = capabilities['canGrantDirectoryAccess'] ?? false; _canPinShortcut = capabilities['canPinShortcut'] ?? false; _canPrint = capabilities['canPrint'] ?? false; + _canRenderEmojis = capabilities['canRenderEmojis'] ?? false; _canRenderGoogleMaps = capabilities['canRenderGoogleMaps'] ?? false; + _hasFilePicker = capabilities['hasFilePicker'] ?? false; + _showPinShortcutFeedback = capabilities['showPinShortcutFeedback'] ?? false; } } diff --git a/lib/model/filters/filters.dart b/lib/model/filters/filters.dart index 0ffb1b86a..5942153a0 100644 --- a/lib/model/filters/filters.dart +++ b/lib/model/filters/filters.dart @@ -31,6 +31,8 @@ abstract class CollectionFilter extends Equatable implements Comparable) { diff --git a/lib/model/filters/location.dart b/lib/model/filters/location.dart index 01b9b1b71..5927501eb 100644 --- a/lib/model/filters/location.dart +++ b/lib/model/filters/location.dart @@ -1,3 +1,4 @@ +import 'package:aves/model/device.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; @@ -58,15 +59,17 @@ class LocationFilter extends CollectionFilter { @override Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true, bool embossed = false}) { - final flag = countryCodeToFlag(_countryCode); - // as of Flutter v1.22.3, emoji shadows are rendered as colorful duplicates, - // not filled with the shadow color as expected, so we remove them - if (flag != null) { - return Text( - flag, - style: TextStyle(fontSize: size, shadows: const []), - textScaleFactor: 1.0, - ); + if (_countryCode != null && device.canRenderEmojis) { + final flag = countryCodeToFlag(_countryCode); + // as of Flutter v1.22.3, emoji shadows are rendered as colorful duplicates, + // not filled with the shadow color as expected, so we remove them + if (flag != null) { + return Text( + flag, + style: TextStyle(fontSize: size, shadows: const []), + textScaleFactor: 1.0, + ); + } } return Icon(_location.isEmpty ? AIcons.locationOff : AIcons.location, size: size); } diff --git a/lib/services/analysis_service.dart b/lib/services/analysis_service.dart index 354068e6f..10a338886 100644 --- a/lib/services/analysis_service.dart +++ b/lib/services/analysis_service.dart @@ -8,8 +8,8 @@ import 'package:aves/model/source/media_store_source.dart'; import 'package:aves/model/source/source_state.dart'; import 'package:aves/services/common/services.dart'; import 'package:fijkplayer/fijkplayer.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class AnalysisService { diff --git a/lib/services/media/media_file_service.dart b/lib/services/media/media_file_service.dart index c2dc44a4d..ec0a941da 100644 --- a/lib/services/media/media_file_service.dart +++ b/lib/services/media/media_file_service.dart @@ -159,7 +159,7 @@ class PlatformMediaFileService implements MediaFileService { int? pageId, int? expectedContentLength, BytesReceivedCallback? onBytesReceived, - }) { + }) async { try { final completer = Completer.sync(); final sink = OutputBuffer(); @@ -191,11 +191,12 @@ class PlatformMediaFileService implements MediaFileService { }, cancelOnError: true, ); - return completer.future; + // `await` here, so that `completeError` will be caught below + return await completer.future; } on PlatformException catch (e, stack) { - reportService.recordError(e, stack); + await reportService.recordError(e, stack); } - return Future.sync(() => Uint8List(0)); + return Uint8List(0); } @override diff --git a/lib/services/storage_service.dart b/lib/services/storage_service.dart index e611679b6..4b3666e5f 100644 --- a/lib/services/storage_service.dart +++ b/lib/services/storage_service.dart @@ -172,7 +172,8 @@ class PlatformStorageService implements StorageService { }, cancelOnError: true, ); - return completer.future; + // `await` here, so that `completeError` will be caught below + return await completer.future; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } @@ -196,7 +197,8 @@ class PlatformStorageService implements StorageService { }, cancelOnError: true, ); - return completer.future; + // `await` here, so that `completeError` will be caught below + return await completer.future; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } @@ -220,7 +222,8 @@ class PlatformStorageService implements StorageService { }, cancelOnError: true, ); - return completer.future; + // `await` here, so that `completeError` will be caught below + return await completer.future; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } @@ -247,7 +250,8 @@ class PlatformStorageService implements StorageService { }, cancelOnError: true, ); - return completer.future; + // `await` here, so that `completeError` will be caught below + return await completer.future; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index 63eaca49d..3537fbb6b 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -626,6 +626,9 @@ class EntrySetActionDelegate with EntryEditorMixin, FeedbackMixin, PermissionAwa final name = result.item2; if (name.isEmpty) return; - unawaited(androidAppService.pinToHomeScreen(name, coverEntry, filters: filters)); + await androidAppService.pinToHomeScreen(name, coverEntry, filters: filters); + if (!device.showPinShortcutFeedback) { + showFeedback(context, context.l10n.genericSuccessFeedback); + } } } diff --git a/lib/widgets/viewer/entry_action_delegate.dart b/lib/widgets/viewer/entry_action_delegate.dart index 3602d466e..7e79b672e 100644 --- a/lib/widgets/viewer/entry_action_delegate.dart +++ b/lib/widgets/viewer/entry_action_delegate.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'package:aves/app_mode.dart'; import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/move_type.dart'; +import 'package:aves/model/device.dart'; import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/highlight.dart'; @@ -124,7 +125,10 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix final name = result.item2; if (name.isEmpty) return; - unawaited(androidAppService.pinToHomeScreen(name, entry, uri: entry.uri)); + await androidAppService.pinToHomeScreen(name, entry, uri: entry.uri); + if (!device.showPinShortcutFeedback) { + showFeedback(context, context.l10n.genericSuccessFeedback); + } } Future _flip(BuildContext context, AvesEntry entry) async { diff --git a/lib/widgets/viewer/overlay/top.dart b/lib/widgets/viewer/overlay/top.dart index 601ae8986..0d255c6a2 100644 --- a/lib/widgets/viewer/overlay/top.dart +++ b/lib/widgets/viewer/overlay/top.dart @@ -88,6 +88,7 @@ class ViewerTopOverlay extends StatelessWidget { case EntryAction.rotateScreen: return settings.isRotationLocked; case EntryAction.addShortcut: + return device.canPinShortcut; case EntryAction.copyToClipboard: case EntryAction.edit: case EntryAction.info: diff --git a/pubspec.lock b/pubspec.lock index 8129fddde..7302836cd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1025,7 +1025,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: d644fedd9cb79a45b1b92788880e81b846a69d9b + resolved-ref: fba50f0e380d8cbd6a5bbda32f97a9c5e4d033e2 url: "git://github.com/deckerst/aves_streams_channel.git" source: git version: "0.3.0" @@ -1203,7 +1203,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.3.0" + version: "2.3.1" wkt_parser: dependency: transitive description: