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 e9f400ebe..71c68e64d 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 @@ -38,7 +38,7 @@ 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.getEncodedBytes +import deckers.thibault.aves.utils.BitmapUtils.getDecodedBytes import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.anyCauseIs import deckers.thibault.aves.utils.getApplicationInfoCompat @@ -154,7 +154,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { // convert DIP to physical pixels here, instead of using `devicePixelRatio` in Flutter val density = context.resources.displayMetrics.density val size = (sizeDip * density).roundToInt() - var data: ByteArray? = null + var bytes: ByteArray? = null try { val iconResourceId = context.packageManager.getApplicationInfoCompat(packageName, 0).icon if (iconResourceId != Resources.ID_NULL) { @@ -175,7 +175,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { try { val bitmap = withContext(Dispatchers.IO) { target.get() } - data = bitmap?.getEncodedBytes(canHaveAlpha = true, recycle = false) + bytes = bitmap?.getDecodedBytes(recycle = false) } catch (e: Exception) { Log.w(LOG_TAG, "failed to decode app icon for packageName=$packageName", e) } @@ -185,8 +185,8 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { Log.w(LOG_TAG, "failed to get app info for packageName=$packageName", e) return } - if (data != null) { - result.success(data) + if (bytes != null) { + result.success(bytes) } else { result.error("getAppIcon-null", "failed to get icon for packageName=$packageName", null) } diff --git a/lib/image_providers/app_icon_image_provider.dart b/lib/image_providers/app_icon_image_provider.dart index 545fd1c45..1a0046b6a 100644 --- a/lib/image_providers/app_icon_image_provider.dart +++ b/lib/image_providers/app_icon_image_provider.dart @@ -1,10 +1,10 @@ import 'dart:ui' as ui; import 'package:aves/services/common/services.dart'; +import 'package:aves_report/aves_report.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; -import 'package:transparent_image/transparent_image.dart'; class AppIconImage extends ImageProvider { const AppIconImage({ @@ -39,12 +39,14 @@ class AppIconImage extends ImageProvider { Future _loadAsync(AppIconImageKey key, ImageDecoderCallback decode) async { try { - final bytes = await appService.getAppIcon(key.packageName, key.size); - final buffer = await ui.ImmutableBuffer.fromUint8List(bytes.isEmpty ? kTransparentImage : bytes); - return await decode(buffer); + final descriptor = await appService.getAppIcon(key.packageName, key.size); + if (descriptor == null) { + throw UnreportedStateError('$packageName app icon decoding failed'); + } + return descriptor.instantiateCodec(); } catch (error) { debugPrint('$runtimeType _loadAsync failed with packageName=$packageName, error=$error'); - throw StateError('$packageName app icon decoding failed'); + throw UnreportedStateError('$packageName app icon decoding failed'); } } } diff --git a/lib/model/app/dependencies.dart b/lib/model/app/dependencies.dart index c91f44969..f21536dce 100644 --- a/lib/model/app/dependencies.dart +++ b/lib/model/app/dependencies.dart @@ -369,11 +369,6 @@ class Dependencies { license: bsd3, sourceUrl: 'https://github.com/dart-lang/stack_trace', ), - Dependency( - name: 'Transparent Image', - license: mit, - sourceUrl: 'https://github.com/brianegan/transparent_image', - ), Dependency( name: 'Vector Math', license: '$zlib, $bsd3', diff --git a/lib/services/app_service.dart b/lib/services/app_service.dart index dd0c87180..d22cf4828 100644 --- a/lib/services/app_service.dart +++ b/lib/services/app_service.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:ui' as ui; import 'dart:ui'; import 'package:aves/geo/uri.dart'; @@ -6,6 +7,7 @@ import 'package:aves/model/app_inventory.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/services/common/decoding.dart'; import 'package:aves/services/common/services.dart'; import 'package:collection/collection.dart'; import 'package:flutter/services.dart'; @@ -15,7 +17,7 @@ import 'package:streams_channel/streams_channel.dart'; abstract class AppService { Future> getPackages(); - Future getAppIcon(String packageName, double size); + Future getAppIcon(String packageName, double size); Future copyToClipboard(String uri, String? label); @@ -75,17 +77,20 @@ class PlatformAppService implements AppService { } @override - Future getAppIcon(String packageName, double size) async { + Future getAppIcon(String packageName, double size) async { try { final result = await _platform.invokeMethod('getAppIcon', { 'packageName': packageName, 'sizeDip': size, }); - if (result != null) return result as Uint8List; + if (result != null) { + final bytes = result as Uint8List; + return InteropDecoding.bytesToCodec(bytes); + } } on PlatformException catch (_) { // ignore, as some packages legitimately do not have icons } - return Uint8List(0); + return null; } @override diff --git a/lib/services/common/decoding.dart b/lib/services/common/decoding.dart new file mode 100644 index 000000000..a8b154dc3 --- /dev/null +++ b/lib/services/common/decoding.dart @@ -0,0 +1,25 @@ +import 'dart:async'; +import 'dart:ui' as ui; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +class InteropDecoding { + static Future bytesToCodec(Uint8List bytes) async { + const trailerLength = 4 * 2; + if (bytes.length < trailerLength) return null; + + final trailerOffset = bytes.length - trailerLength; + final trailer = ByteData.sublistView(bytes, trailerOffset); + final bitmapWidth = trailer.getUint32(0); + final bitmapHeight = trailer.getUint32(4); + + final buffer = await ui.ImmutableBuffer.fromUint8List(bytes); + return ui.ImageDescriptor.raw( + buffer, + width: bitmapWidth, + height: bitmapHeight, + pixelFormat: ui.PixelFormat.rgba8888, + ); + } +} diff --git a/lib/services/media/media_fetch_service.dart b/lib/services/media/media_fetch_service.dart index 8e6052c0e..cfc3ee66d 100644 --- a/lib/services/media/media_fetch_service.dart +++ b/lib/services/media/media_fetch_service.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'package:aves/model/app/support.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/ref/mime_types.dart'; +import 'package:aves/services/common/decoding.dart'; import 'package:aves/services/common/output_buffer.dart'; import 'package:aves/services/common/service_policy.dart'; import 'package:aves/services/common/services.dart'; @@ -194,7 +195,7 @@ class PlatformMediaFetchService implements MediaFetchService { }); if (result != null) { final bytes = result as Uint8List; - return _bytesToCodec(bytes); + return InteropDecoding.bytesToCodec(bytes); } } on PlatformException catch (e, stack) { if (_isUnknownVisual(mimeType)) { @@ -237,7 +238,7 @@ class PlatformMediaFetchService implements MediaFetchService { }); if (result != null) { final bytes = result as Uint8List; - return _bytesToCodec(bytes); + return InteropDecoding.bytesToCodec(bytes); } } on PlatformException catch (e, stack) { if (_isUnknownVisual(mimeType)) { @@ -271,24 +272,6 @@ class PlatformMediaFetchService implements MediaFetchService { // convenience methods - Future _bytesToCodec(Uint8List bytes) async { - const trailerLength = 4 * 2; - if (bytes.length < trailerLength) return null; - - final trailerOffset = bytes.length - trailerLength; - final trailer = ByteData.sublistView(bytes, trailerOffset); - final bitmapWidth = trailer.getUint32(0); - final bitmapHeight = trailer.getUint32(4); - - final buffer = await ui.ImmutableBuffer.fromUint8List(bytes); - return ui.ImageDescriptor.raw( - buffer, - width: bitmapWidth, - height: bitmapHeight, - pixelFormat: ui.PixelFormat.rgba8888, - ); - } - bool _isUnknownVisual(String mimeType) => !_knownMediaTypes.contains(mimeType) && MimeTypes.isVisual(mimeType); static const Set _knownOpaqueImages = { diff --git a/pubspec.lock b/pubspec.lock index f6790bd42..a68e322b8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1525,14 +1525,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.8" - transparent_image: - dependency: "direct main" - description: - name: transparent_image - sha256: e8991d955a2094e197ca24c645efec2faf4285772a4746126ca12875e54ca02f - url: "https://pub.dev" - source: hosted - version: "2.0.1" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6597b130d..417f61615 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -123,7 +123,6 @@ dependencies: streams_channel: git: url: https://github.com/deckerst/aves_streams_channel.git - transparent_image: url_launcher: vector_map_tiles: ^8.0.0 # vector_map_tiles v9.0.0-beta.6 has a buggy cross-platform definition for `cacheFolder` vector_math: