API 16 support prep

This commit is contained in:
Thibault Deckers 2021-11-28 19:32:04 +09:00
parent 089304da2d
commit 35958d87fd
20 changed files with 143 additions and 91 deletions

View file

@ -56,8 +56,7 @@ android {
// minSdkVersion constraints: // minSdkVersion constraints:
// - Flutter & other plugins: 16 // - Flutter & other plugins: 16
// - google_maps_flutter v2.1.1: 20 // - google_maps_flutter v2.1.1: 20
// - Aves native: 19 minSdkVersion 16
minSdkVersion 19
targetSdkVersion 31 targetSdkVersion 31
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
@ -149,7 +148,7 @@ dependencies {
// forked, built by JitPack, cf https://jitpack.io/p/deckerst/Android-TiffBitmapFactory // forked, built by JitPack, cf https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
implementation 'com.github.deckerst:Android-TiffBitmapFactory:876e53870a' implementation 'com.github.deckerst:Android-TiffBitmapFactory:876e53870a'
// forked, built by JitPack, cf https://jitpack.io/p/deckerst/pixymeta-android // 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' implementation 'com.github.bumptech.glide:glide:4.12.0'
kapt 'androidx.annotation:annotation:1.3.0' kapt 'androidx.annotation:annotation:1.3.0'

View file

@ -23,7 +23,6 @@ import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.util.*
class AnalysisService : MethodChannel.MethodCallHandler, Service() { class AnalysisService : MethodChannel.MethodCallHandler, Service() {
private var backgroundFlutterEngine: FlutterEngine? = null private var backgroundFlutterEngine: FlutterEngine? = null
@ -141,11 +140,12 @@ class AnalysisService : MethodChannel.MethodCallHandler, Service() {
getString(R.string.analysis_notification_action_stop), getString(R.string.analysis_notification_action_stop),
stopServiceIntent stopServiceIntent
).build() ).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) return NotificationCompat.Builder(this, CHANNEL_ANALYSIS)
.setContentTitle(title ?: getText(R.string.analysis_notification_default_title)) .setContentTitle(title ?: getText(R.string.analysis_notification_default_title))
.setContentText(message) .setContentText(message)
.setBadgeIconType(NotificationCompat.BADGE_ICON_NONE) .setBadgeIconType(NotificationCompat.BADGE_ICON_NONE)
.setSmallIcon(R.drawable.ic_notification) .setSmallIcon(icon)
.setContentIntent(openAppIntent) .setContentIntent(openAppIntent)
.setPriority(NotificationCompat.PRIORITY_LOW) .setPriority(NotificationCompat.PRIORITY_LOW)
.addAction(stopAction) .addAction(stopAction)

View file

@ -24,10 +24,12 @@ class AccessibilityHandler(private val activity: Activity) : MethodCallHandler {
private fun areAnimationsRemoved(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) { private fun areAnimationsRemoved(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
var removed = false var removed = false
try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
removed = Settings.Global.getFloat(activity.contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE) == 0f try {
} catch (e: Exception) { removed = Settings.Global.getFloat(activity.contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE) == 0f
Log.w(LOG_TAG, "failed to get settings with error=${e.message}", null) } catch (e: Exception) {
Log.w(LOG_TAG, "failed to get settings with error=${e.message}", null)
}
} }
result.success(removed) result.success(removed)
} }

View file

@ -62,7 +62,14 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
fun addPackageDetails(intent: Intent) { fun addPackageDetails(intent: Intent) {
// apps tend to use their name in English when creating directories // apps tend to use their name in English when creating directories
// so we get their names in English as well as the current locale // 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 val pm = context.packageManager
for (resolveInfo in pm.queryIntentActivities(intent, 0)) { for (resolveInfo in pm.queryIntentActivities(intent, 0)) {

View file

@ -20,15 +20,21 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
} }
private fun getCapabilities(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) { private fun getCapabilities(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
result.success(hashMapOf( val sdkInt = Build.VERSION.SDK_INT
"canGrantDirectoryAccess" to (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP), result.success(
"canPinShortcut" to ShortcutManagerCompat.isRequestPinShortcutSupported(context), hashMapOf(
"canPrint" to (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT), "canGrantDirectoryAccess" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP),
// as of google_maps_flutter v2.1.1, minSDK is 20 because of default PlatformView usage, "canPinShortcut" to ShortcutManagerCompat.isRequestPinShortcutSupported(context),
// but using hybrid composition would make it usable on API 19 too, "canPrint" to (sdkInt >= Build.VERSION_CODES.KITKAT),
// cf https://github.com/flutter/flutter/issues/23728 "canRenderEmojis" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP),
"canRenderGoogleMaps" to (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH), // 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) { private fun getDefaultTimeZone(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {

View file

@ -584,7 +584,9 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
var flags = (metadataMap[KEY_FLAGS] ?: 0) as Int var flags = (metadataMap[KEY_FLAGS] ?: 0) as Int
try { 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)) { if (!metadataMap.containsKey(KEY_DATE_MILLIS)) {
retriever.getSafeDateMillis(MediaMetadataRetriever.METADATA_KEY_DATE) { metadataMap[KEY_DATE_MILLIS] = it } retriever.getSafeDateMillis(MediaMetadataRetriever.METADATA_KEY_DATE) { metadataMap[KEY_DATE_MILLIS] = it }
} }

View file

@ -3,6 +3,7 @@ package deckers.thibault.aves.channel.streams
import android.content.Context import android.content.Context
import android.database.ContentObserver import android.database.ContentObserver
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.provider.Settings import android.provider.Settings
@ -32,12 +33,13 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
override fun onChange(selfChange: Boolean, uri: Uri?) { override fun onChange(selfChange: Boolean, uri: Uri?) {
if (update()) { if (update()) {
success( val settings: FieldMap = hashMapOf(
hashMapOf( Settings.System.ACCELEROMETER_ROTATION to accelerometerRotation,
Settings.System.ACCELEROMETER_ROTATION to accelerometerRotation,
Settings.Global.TRANSITION_ANIMATION_SCALE to transitionAnimationScale,
)
) )
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 accelerometerRotation = newAccelerometerRotation
changed = true changed = true
} }
val newTransitionAnimationScale = Settings.Global.getFloat(context.contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (transitionAnimationScale != newTransitionAnimationScale) { val newTransitionAnimationScale = Settings.Global.getFloat(context.contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE)
transitionAnimationScale = newTransitionAnimationScale if (transitionAnimationScale != newTransitionAnimationScale) {
changed = true transitionAnimationScale = newTransitionAnimationScale
changed = true
}
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.w(LOG_TAG, "failed to get settings with error=${e.message}", null) Log.w(LOG_TAG, "failed to get settings with error=${e.message}", null)
} }

View file

@ -27,11 +27,13 @@ object MediaMetadataRetrieverHelper {
MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS to "Number of Tracks", MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS to "Number of Tracks",
MediaMetadataRetriever.METADATA_KEY_TITLE to "Title", MediaMetadataRetriever.METADATA_KEY_TITLE to "Title",
MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT to "Video Height", 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_VIDEO_WIDTH to "Video Width",
MediaMetadataRetriever.METADATA_KEY_WRITER to "Writer", MediaMetadataRetriever.METADATA_KEY_WRITER to "Writer",
MediaMetadataRetriever.METADATA_KEY_YEAR to "Year", MediaMetadataRetriever.METADATA_KEY_YEAR to "Year",
).apply { ).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) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
put(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE, "Capture Framerate") put(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE, "Capture Framerate")
} }

View file

@ -5,6 +5,7 @@ import android.content.Context
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever import android.media.MediaMetadataRetriever
import android.net.Uri import android.net.Uri
import android.os.Build
import androidx.exifinterface.media.ExifInterface import androidx.exifinterface.media.ExifInterface
import com.drew.imaging.ImageMetadataReader import com.drew.imaging.ImageMetadataReader
import com.drew.metadata.avi.AviDirectory import com.drew.metadata.avi.AviDirectory
@ -135,10 +136,12 @@ class SourceEntry {
try { try {
retriever.getSafeInt(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) { width = it } retriever.getSafeInt(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) { width = it }
retriever.getSafeInt(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT) { height = 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.getSafeLong(MediaMetadataRetriever.METADATA_KEY_DURATION) { durationMillis = it }
retriever.getSafeDateMillis(MediaMetadataRetriever.METADATA_KEY_DATE) { sourceDateTakenMillis = it } retriever.getSafeDateMillis(MediaMetadataRetriever.METADATA_KEY_DATE) { sourceDateTakenMillis = it }
retriever.getSafeString(MediaMetadataRetriever.METADATA_KEY_TITLE) { title = 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) { } catch (e: Exception) {
// ignore // ignore
} finally { } finally {

View file

@ -142,39 +142,6 @@ object PermissionManager {
} }
} }
fun getRestrictedDirectories(context: Context): List<Map<String, String>> {
val dirs = ArrayList<Map<String, String>>()
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<FieldMap>): Boolean { fun canInsertByMediaStore(directories: List<FieldMap>): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
directories.all { directories.all {
@ -217,14 +184,46 @@ object PermissionManager {
// from API 30 / Android 11 / R, any storage requires access permission // from API 30 / Android 11 / R, any storage requires access permission
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
accessibleDirs.addAll(StorageUtils.getVolumePaths(context)) accessibleDirs.addAll(StorageUtils.getVolumePaths(context))
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP } else if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
&& Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q
) {
accessibleDirs.add(StorageUtils.getPrimaryVolumePath(context)) accessibleDirs.add(StorageUtils.getPrimaryVolumePath(context))
} }
return accessibleDirs return accessibleDirs
} }
fun getRestrictedDirectories(context: Context): List<Map<String, String>> {
val dirs = ArrayList<Map<String, String>>()
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 // As of Android R, `MediaStore.getDocumentUri` fails if any of the persisted
// URI permissions we hold points to a folder that no longer exists, // URI permissions we hold points to a folder that no longer exists,
// so we should remove these obsolete URIs before proceeding. // so we should remove these obsolete URIs before proceeding.

View file

@ -5,7 +5,8 @@ final Device device = Device._private();
class Device { class Device {
late final String _userAgent; 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; String get userAgent => _userAgent;
@ -15,8 +16,15 @@ class Device {
bool get canPrint => _canPrint; bool get canPrint => _canPrint;
bool get canRenderEmojis => _canRenderEmojis;
bool get canRenderGoogleMaps => _canRenderGoogleMaps; bool get canRenderGoogleMaps => _canRenderGoogleMaps;
// TODO TLAD toggle settings > import/export, about > bug report > save
bool get hasFilePicker => _hasFilePicker;
bool get showPinShortcutFeedback => _showPinShortcutFeedback;
Device._private(); Device._private();
Future<void> init() async { Future<void> init() async {
@ -27,6 +35,9 @@ class Device {
_canGrantDirectoryAccess = capabilities['canGrantDirectoryAccess'] ?? false; _canGrantDirectoryAccess = capabilities['canGrantDirectoryAccess'] ?? false;
_canPinShortcut = capabilities['canPinShortcut'] ?? false; _canPinShortcut = capabilities['canPinShortcut'] ?? false;
_canPrint = capabilities['canPrint'] ?? false; _canPrint = capabilities['canPrint'] ?? false;
_canRenderEmojis = capabilities['canRenderEmojis'] ?? false;
_canRenderGoogleMaps = capabilities['canRenderGoogleMaps'] ?? false; _canRenderGoogleMaps = capabilities['canRenderGoogleMaps'] ?? false;
_hasFilePicker = capabilities['hasFilePicker'] ?? false;
_showPinShortcutFeedback = capabilities['showPinShortcutFeedback'] ?? false;
} }
} }

View file

@ -31,6 +31,8 @@ abstract class CollectionFilter extends Equatable implements Comparable<Collecti
]; ];
static CollectionFilter? fromJson(String jsonString) { static CollectionFilter? fromJson(String jsonString) {
if (jsonString.isEmpty) return null;
try { try {
final jsonMap = jsonDecode(jsonString); final jsonMap = jsonDecode(jsonString);
if (jsonMap is Map<String, dynamic>) { if (jsonMap is Map<String, dynamic>) {

View file

@ -1,3 +1,4 @@
import 'package:aves/model/device.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
@ -58,15 +59,17 @@ class LocationFilter extends CollectionFilter {
@override @override
Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true, bool embossed = false}) { Widget iconBuilder(BuildContext context, double size, {bool showGenericIcon = true, bool embossed = false}) {
final flag = countryCodeToFlag(_countryCode); if (_countryCode != null && device.canRenderEmojis) {
// as of Flutter v1.22.3, emoji shadows are rendered as colorful duplicates, final flag = countryCodeToFlag(_countryCode);
// not filled with the shadow color as expected, so we remove them // as of Flutter v1.22.3, emoji shadows are rendered as colorful duplicates,
if (flag != null) { // not filled with the shadow color as expected, so we remove them
return Text( if (flag != null) {
flag, return Text(
style: TextStyle(fontSize: size, shadows: const []), flag,
textScaleFactor: 1.0, style: TextStyle(fontSize: size, shadows: const []),
); textScaleFactor: 1.0,
);
}
} }
return Icon(_location.isEmpty ? AIcons.locationOff : AIcons.location, size: size); return Icon(_location.isEmpty ? AIcons.locationOff : AIcons.location, size: size);
} }

View file

@ -8,8 +8,8 @@ import 'package:aves/model/source/media_store_source.dart';
import 'package:aves/model/source/source_state.dart'; import 'package:aves/model/source/source_state.dart';
import 'package:aves/services/common/services.dart'; import 'package:aves/services/common/services.dart';
import 'package:fijkplayer/fijkplayer.dart'; import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class AnalysisService { class AnalysisService {

View file

@ -159,7 +159,7 @@ class PlatformMediaFileService implements MediaFileService {
int? pageId, int? pageId,
int? expectedContentLength, int? expectedContentLength,
BytesReceivedCallback? onBytesReceived, BytesReceivedCallback? onBytesReceived,
}) { }) async {
try { try {
final completer = Completer<Uint8List>.sync(); final completer = Completer<Uint8List>.sync();
final sink = OutputBuffer(); final sink = OutputBuffer();
@ -191,11 +191,12 @@ class PlatformMediaFileService implements MediaFileService {
}, },
cancelOnError: true, cancelOnError: true,
); );
return completer.future; // `await` here, so that `completeError` will be caught below
return await completer.future;
} on PlatformException catch (e, stack) { } on PlatformException catch (e, stack) {
reportService.recordError(e, stack); await reportService.recordError(e, stack);
} }
return Future.sync(() => Uint8List(0)); return Uint8List(0);
} }
@override @override

View file

@ -172,7 +172,8 @@ class PlatformStorageService implements StorageService {
}, },
cancelOnError: true, cancelOnError: true,
); );
return completer.future; // `await` here, so that `completeError` will be caught below
return await completer.future;
} on PlatformException catch (e, stack) { } on PlatformException catch (e, stack) {
await reportService.recordError(e, stack); await reportService.recordError(e, stack);
} }
@ -196,7 +197,8 @@ class PlatformStorageService implements StorageService {
}, },
cancelOnError: true, cancelOnError: true,
); );
return completer.future; // `await` here, so that `completeError` will be caught below
return await completer.future;
} on PlatformException catch (e, stack) { } on PlatformException catch (e, stack) {
await reportService.recordError(e, stack); await reportService.recordError(e, stack);
} }
@ -220,7 +222,8 @@ class PlatformStorageService implements StorageService {
}, },
cancelOnError: true, cancelOnError: true,
); );
return completer.future; // `await` here, so that `completeError` will be caught below
return await completer.future;
} on PlatformException catch (e, stack) { } on PlatformException catch (e, stack) {
await reportService.recordError(e, stack); await reportService.recordError(e, stack);
} }
@ -247,7 +250,8 @@ class PlatformStorageService implements StorageService {
}, },
cancelOnError: true, cancelOnError: true,
); );
return completer.future; // `await` here, so that `completeError` will be caught below
return await completer.future;
} on PlatformException catch (e, stack) { } on PlatformException catch (e, stack) {
await reportService.recordError(e, stack); await reportService.recordError(e, stack);
} }

View file

@ -626,6 +626,9 @@ class EntrySetActionDelegate with EntryEditorMixin, FeedbackMixin, PermissionAwa
final name = result.item2; final name = result.item2;
if (name.isEmpty) return; 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);
}
} }
} }

View file

@ -4,6 +4,7 @@ import 'dart:convert';
import 'package:aves/app_mode.dart'; import 'package:aves/app_mode.dart';
import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/entry_actions.dart';
import 'package:aves/model/actions/move_type.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/entry.dart';
import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/album.dart';
import 'package:aves/model/highlight.dart'; import 'package:aves/model/highlight.dart';
@ -124,7 +125,10 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
final name = result.item2; final name = result.item2;
if (name.isEmpty) return; 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<void> _flip(BuildContext context, AvesEntry entry) async { Future<void> _flip(BuildContext context, AvesEntry entry) async {

View file

@ -88,6 +88,7 @@ class ViewerTopOverlay extends StatelessWidget {
case EntryAction.rotateScreen: case EntryAction.rotateScreen:
return settings.isRotationLocked; return settings.isRotationLocked;
case EntryAction.addShortcut: case EntryAction.addShortcut:
return device.canPinShortcut;
case EntryAction.copyToClipboard: case EntryAction.copyToClipboard:
case EntryAction.edit: case EntryAction.edit:
case EntryAction.info: case EntryAction.info:

View file

@ -1025,7 +1025,7 @@ packages:
description: description:
path: "." path: "."
ref: HEAD ref: HEAD
resolved-ref: d644fedd9cb79a45b1b92788880e81b846a69d9b resolved-ref: fba50f0e380d8cbd6a5bbda32f97a9c5e4d033e2
url: "git://github.com/deckerst/aves_streams_channel.git" url: "git://github.com/deckerst/aves_streams_channel.git"
source: git source: git
version: "0.3.0" version: "0.3.0"
@ -1203,7 +1203,7 @@ packages:
name: win32 name: win32
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.0" version: "2.3.1"
wkt_parser: wkt_parser:
dependency: transitive dependency: transitive
description: description: