diff --git a/packages/ios_platform_images/CHANGELOG.md b/packages/ios_platform_images/CHANGELOG.md index 69781d6576a7..00259503a204 100644 --- a/packages/ios_platform_images/CHANGELOG.md +++ b/packages/ios_platform_images/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.2+3 + +* Converts platform communication to Pigeon. + ## 0.2.2+2 * Adds pub topics to package metadata. diff --git a/packages/ios_platform_images/example/ios/Runner.xcodeproj/project.pbxproj b/packages/ios_platform_images/example/ios/Runner.xcodeproj/project.pbxproj index acea2f5253f1..ba035186b2fc 100644 --- a/packages/ios_platform_images/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/ios_platform_images/example/ios/Runner.xcodeproj/project.pbxproj @@ -222,7 +222,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/packages/ios_platform_images/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/ios_platform_images/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 7ae2cb4d4e54..51e700a5f8c5 100644 --- a/packages/ios_platform_images/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/ios_platform_images/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ +#import "messages.g.h" + /// A plugin for Flutter that allows Flutter to load images in a platform /// specific way on iOS. -@interface IosPlatformImagesPlugin : NSObject +@interface IosPlatformImagesPlugin : NSObject @end diff --git a/packages/ios_platform_images/ios/Classes/IosPlatformImagesPlugin.m b/packages/ios_platform_images/ios/Classes/IosPlatformImagesPlugin.m index 5f7debc3fe07..b41ff9900d8e 100644 --- a/packages/ios_platform_images/ios/Classes/IosPlatformImagesPlugin.m +++ b/packages/ios_platform_images/ios/Classes/IosPlatformImagesPlugin.m @@ -14,35 +14,27 @@ @interface IosPlatformImagesPlugin () @implementation IosPlatformImagesPlugin + (void)registerWithRegistrar:(NSObject *)registrar { - FlutterMethodChannel *channel = - [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/ios_platform_images" - binaryMessenger:[registrar messenger]]; - - [channel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) { - if ([@"loadImage" isEqualToString:call.method]) { - NSString *name = call.arguments; - UIImage *image = [UIImage imageNamed:name]; - NSData *data = UIImagePNGRepresentation(image); - if (data) { - result(@{ - @"scale" : @(image.scale), - @"data" : [FlutterStandardTypedData typedDataWithBytes:data], - }); - } else { - result(nil); - } - return; - } else if ([@"resolveURL" isEqualToString:call.method]) { - NSArray *args = call.arguments; - NSString *name = args[0]; - NSString *extension = (args[1] == (id)NSNull.null) ? nil : args[1]; - - NSURL *url = [[NSBundle mainBundle] URLForResource:name withExtension:extension]; - result(url.absoluteString); - return; - } - result(FlutterMethodNotImplemented); - }]; + FPIPlatformImagesApiSetup(registrar.messenger, [[IosPlatformImagesPlugin alloc] init]); +} + +- (nullable FPIPlatformImageData *) + loadImageWithName:(nonnull NSString *)name + error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { + UIImage *image = [UIImage imageNamed:name]; + NSData *data = UIImagePNGRepresentation(image); + if (!data) { + return nil; + } + return [FPIPlatformImageData makeWithData:[FlutterStandardTypedData typedDataWithBytes:data] + scale:@(image.scale)]; +} + +- (nullable NSString *)resolveURLForResource:(nonnull NSString *)name + withExtension:(nullable NSString *)extension + error:(FlutterError *_Nullable __autoreleasing *_Nonnull) + error { + NSURL *url = [[NSBundle mainBundle] URLForResource:name withExtension:extension]; + return url.absoluteString; } @end diff --git a/packages/ios_platform_images/ios/Classes/messages.g.h b/packages/ios_platform_images/ios/Classes/messages.g.h new file mode 100644 index 000000000000..f4235cf9063b --- /dev/null +++ b/packages/ios_platform_images/ios/Classes/messages.g.h @@ -0,0 +1,47 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v11.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#import + +@protocol FlutterBinaryMessenger; +@protocol FlutterMessageCodec; +@class FlutterError; +@class FlutterStandardTypedData; + +NS_ASSUME_NONNULL_BEGIN + +@class FPIPlatformImageData; + +/// A serialization of a platform image's data. +@interface FPIPlatformImageData : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithData:(FlutterStandardTypedData *)data scale:(NSNumber *)scale; +/// The image data. +@property(nonatomic, strong) FlutterStandardTypedData *data; +/// The image's scale factor. +@property(nonatomic, strong) NSNumber *scale; +@end + +/// The codec used by FPIPlatformImagesApi. +NSObject *FPIPlatformImagesApiGetCodec(void); + +@protocol FPIPlatformImagesApi +/// Returns the URL for the given resource, or null if no such resource is +/// found. +- (nullable NSString *)resolveURLForResource:(NSString *)resourceName + withExtension:(nullable NSString *)extension + error:(FlutterError *_Nullable *_Nonnull)error; +/// Returns the data for the image resource with the given name, or null if +/// no such resource is found. +- (nullable FPIPlatformImageData *)loadImageWithName:(NSString *)name + error:(FlutterError *_Nullable *_Nonnull)error; +@end + +extern void FPIPlatformImagesApiSetup(id binaryMessenger, + NSObject *_Nullable api); + +NS_ASSUME_NONNULL_END diff --git a/packages/ios_platform_images/ios/Classes/messages.g.m b/packages/ios_platform_images/ios/Classes/messages.g.m new file mode 100644 index 000000000000..b96e65ea263b --- /dev/null +++ b/packages/ios_platform_images/ios/Classes/messages.g.m @@ -0,0 +1,163 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v11.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#import "messages.g.h" + +#if TARGET_OS_OSX +#import +#else +#import +#endif + +#if !__has_feature(objc_arc) +#error File requires ARC to be enabled. +#endif + +static NSArray *wrapResult(id result, FlutterError *error) { + if (error) { + return @[ + error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null] + ]; + } + return @[ result ?: [NSNull null] ]; +} +static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { + id result = array[key]; + return (result == [NSNull null]) ? nil : result; +} + +@interface FPIPlatformImageData () ++ (FPIPlatformImageData *)fromList:(NSArray *)list; ++ (nullable FPIPlatformImageData *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + +@implementation FPIPlatformImageData ++ (instancetype)makeWithData:(FlutterStandardTypedData *)data scale:(NSNumber *)scale { + FPIPlatformImageData *pigeonResult = [[FPIPlatformImageData alloc] init]; + pigeonResult.data = data; + pigeonResult.scale = scale; + return pigeonResult; +} ++ (FPIPlatformImageData *)fromList:(NSArray *)list { + FPIPlatformImageData *pigeonResult = [[FPIPlatformImageData alloc] init]; + pigeonResult.data = GetNullableObjectAtIndex(list, 0); + NSAssert(pigeonResult.data != nil, @""); + pigeonResult.scale = GetNullableObjectAtIndex(list, 1); + NSAssert(pigeonResult.scale != nil, @""); + return pigeonResult; +} ++ (nullable FPIPlatformImageData *)nullableFromList:(NSArray *)list { + return (list) ? [FPIPlatformImageData fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + (self.data ?: [NSNull null]), + (self.scale ?: [NSNull null]), + ]; +} +@end + +@interface FPIPlatformImagesApiCodecReader : FlutterStandardReader +@end +@implementation FPIPlatformImagesApiCodecReader +- (nullable id)readValueOfType:(UInt8)type { + switch (type) { + case 128: + return [FPIPlatformImageData fromList:[self readValue]]; + default: + return [super readValueOfType:type]; + } +} +@end + +@interface FPIPlatformImagesApiCodecWriter : FlutterStandardWriter +@end +@implementation FPIPlatformImagesApiCodecWriter +- (void)writeValue:(id)value { + if ([value isKindOfClass:[FPIPlatformImageData class]]) { + [self writeByte:128]; + [self writeValue:[value toList]]; + } else { + [super writeValue:value]; + } +} +@end + +@interface FPIPlatformImagesApiCodecReaderWriter : FlutterStandardReaderWriter +@end +@implementation FPIPlatformImagesApiCodecReaderWriter +- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { + return [[FPIPlatformImagesApiCodecWriter alloc] initWithData:data]; +} +- (FlutterStandardReader *)readerWithData:(NSData *)data { + return [[FPIPlatformImagesApiCodecReader alloc] initWithData:data]; +} +@end + +NSObject *FPIPlatformImagesApiGetCodec(void) { + static FlutterStandardMessageCodec *sSharedObject = nil; + static dispatch_once_t sPred = 0; + dispatch_once(&sPred, ^{ + FPIPlatformImagesApiCodecReaderWriter *readerWriter = + [[FPIPlatformImagesApiCodecReaderWriter alloc] init]; + sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; + }); + return sSharedObject; +} + +void FPIPlatformImagesApiSetup(id binaryMessenger, + NSObject *api) { + /// Returns the URL for the given resource, or null if no such resource is + /// found. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.ios_platform_images.PlatformImagesApi.resolveUrl" + binaryMessenger:binaryMessenger + codec:FPIPlatformImagesApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(resolveURLForResource:withExtension:error:)], + @"FPIPlatformImagesApi api (%@) doesn't respond to " + @"@selector(resolveURLForResource:withExtension:error:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSString *arg_resourceName = GetNullableObjectAtIndex(args, 0); + NSString *arg_extension = GetNullableObjectAtIndex(args, 1); + FlutterError *error; + NSString *output = [api resolveURLForResource:arg_resourceName + withExtension:arg_extension + error:&error]; + callback(wrapResult(output, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Returns the data for the image resource with the given name, or null if + /// no such resource is found. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.ios_platform_images.PlatformImagesApi.loadImage" + binaryMessenger:binaryMessenger + codec:FPIPlatformImagesApiGetCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector(loadImageWithName:error:)], + @"FPIPlatformImagesApi api (%@) doesn't respond to @selector(loadImageWithName:error:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSString *arg_name = GetNullableObjectAtIndex(args, 0); + FlutterError *error; + FPIPlatformImageData *output = [api loadImageWithName:arg_name error:&error]; + callback(wrapResult(output, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } +} diff --git a/packages/ios_platform_images/lib/ios_platform_images.dart b/packages/ios_platform_images/lib/ios_platform_images.dart index b372d362f6f7..22e0b84db033 100644 --- a/packages/ios_platform_images/lib/ios_platform_images.dart +++ b/packages/ios_platform_images/lib/ios_platform_images.dart @@ -6,10 +6,17 @@ import 'dart:async'; import 'dart:ui' as ui; import 'package:flutter/foundation.dart' - show SynchronousFuture, describeIdentity, immutable, objectRuntimeType; + show + SynchronousFuture, + describeIdentity, + immutable, + objectRuntimeType, + visibleForTesting; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; +import 'src/messages.g.dart'; + class _FutureImageStreamCompleter extends ImageStreamCompleter { _FutureImageStreamCompleter({ required Future codec, @@ -101,14 +108,22 @@ class _FutureMemoryImage extends ImageProvider<_FutureMemoryImage> { '(${describeIdentity(_futureBytes)}, scale: $_futureScale)'; } +PlatformImagesApi _hostApi = PlatformImagesApi(); + +/// Sets the [PlatformImagesApi] instance used to implement the static methods +/// of [IosPlatformImages]. +/// +/// This exists only for unit tests. +@visibleForTesting +void setPlatformImageHostApi(PlatformImagesApi api) { + _hostApi = api; +} + // ignore: avoid_classes_with_only_static_members /// Class to help loading of iOS platform images into Flutter. /// /// For example, loading an image that is in `Assets.xcassts`. class IosPlatformImages { - static const MethodChannel _channel = - MethodChannel('plugins.flutter.io/ios_platform_images'); - /// Loads an image from asset catalogs. The equivalent would be: /// `[UIImage imageNamed:name]`. /// @@ -116,12 +131,11 @@ class IosPlatformImages { /// /// See [https://developer.apple.com/documentation/uikit/uiimage/1624146-imagenamed?language=objc] static ImageProvider load(String name) { - final Future?> loadInfo = - _channel.invokeMapMethod('loadImage', name); + final Future imageData = _hostApi.loadImage(name); final Completer bytesCompleter = Completer(); final Completer scaleCompleter = Completer(); - loadInfo.then((Map? map) { - if (map == null) { + imageData.then((PlatformImageData? image) { + if (image == null) { scaleCompleter.completeError( Exception("Image couldn't be found: $name"), ); @@ -130,8 +144,8 @@ class IosPlatformImages { ); return; } - scaleCompleter.complete(map['scale']! as double); - bytesCompleter.complete(map['data']! as Uint8List); + scaleCompleter.complete(image.scale); + bytesCompleter.complete(image.data); }); return _FutureMemoryImage(bytesCompleter.future, scaleCompleter.future); } @@ -143,7 +157,6 @@ class IosPlatformImages { /// /// See [https://developer.apple.com/documentation/foundation/nsbundle/1411540-urlforresource?language=objc] static Future resolveURL(String name, {String? extension}) { - return _channel - .invokeMethod('resolveURL', [name, extension]); + return _hostApi.resolveUrl(name, extension); } } diff --git a/packages/ios_platform_images/lib/src/messages.g.dart b/packages/ios_platform_images/lib/src/messages.g.dart new file mode 100644 index 000000000000..a3a5ab43350e --- /dev/null +++ b/packages/ios_platform_images/lib/src/messages.g.dart @@ -0,0 +1,126 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v11.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +/// A serialization of a platform image's data. +class PlatformImageData { + PlatformImageData({ + required this.data, + required this.scale, + }); + + /// The image data. + Uint8List data; + + /// The image's scale factor. + double scale; + + Object encode() { + return [ + data, + scale, + ]; + } + + static PlatformImageData decode(Object result) { + result as List; + return PlatformImageData( + data: result[0]! as Uint8List, + scale: result[1]! as double, + ); + } +} + +class _PlatformImagesApiCodec extends StandardMessageCodec { + const _PlatformImagesApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is PlatformImageData) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return PlatformImageData.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class PlatformImagesApi { + /// Constructor for [PlatformImagesApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + PlatformImagesApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _PlatformImagesApiCodec(); + + /// Returns the URL for the given resource, or null if no such resource is + /// found. + Future resolveUrl( + String arg_resourceName, String? arg_extension) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ios_platform_images.PlatformImagesApi.resolveUrl', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel + .send([arg_resourceName, arg_extension]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as String?); + } + } + + /// Returns the data for the image resource with the given name, or null if + /// no such resource is found. + Future loadImage(String arg_name) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ios_platform_images.PlatformImagesApi.loadImage', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_name]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as PlatformImageData?); + } + } +} diff --git a/packages/ios_platform_images/pigeons/copyright.txt b/packages/ios_platform_images/pigeons/copyright.txt new file mode 100644 index 000000000000..1236b63caf3a --- /dev/null +++ b/packages/ios_platform_images/pigeons/copyright.txt @@ -0,0 +1,3 @@ +Copyright 2013 The Flutter Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. diff --git a/packages/ios_platform_images/pigeons/messages.dart b/packages/ios_platform_images/pigeons/messages.dart new file mode 100644 index 000000000000..d0a8d3473171 --- /dev/null +++ b/packages/ios_platform_images/pigeons/messages.dart @@ -0,0 +1,37 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + objcOptions: ObjcOptions(prefix: 'FPI'), + objcHeaderOut: 'ios/Classes/messages.g.h', + objcSourceOut: 'ios/Classes/messages.g.m', + copyrightHeader: 'pigeons/copyright.txt', +)) + +/// A serialization of a platform image's data. +class PlatformImageData { + PlatformImageData(this.data, this.scale); + + /// The image data. + final Uint8List data; + + /// The image's scale factor. + final double scale; +} + +@HostApi() +abstract class PlatformImagesApi { + /// Returns the URL for the given resource, or null if no such resource is + /// found. + @ObjCSelector('resolveURLForResource:withExtension:') + String? resolveUrl(String resourceName, String? extension); + + /// Returns the data for the image resource with the given name, or null if + /// no such resource is found. + @ObjCSelector('loadImageWithName:') + PlatformImageData? loadImage(String name); +} diff --git a/packages/ios_platform_images/pubspec.yaml b/packages/ios_platform_images/pubspec.yaml index 7a14d4a71b57..c4a0eee2c690 100644 --- a/packages/ios_platform_images/pubspec.yaml +++ b/packages/ios_platform_images/pubspec.yaml @@ -2,7 +2,7 @@ name: ios_platform_images description: A plugin to share images between Flutter and iOS in add-to-app setups. repository: https://github.com/flutter/packages/tree/main/packages/ios_platform_images issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+ios_platform_images%22 -version: 0.2.2+2 +version: 0.2.2+3 environment: sdk: ">=2.19.0 <4.0.0" @@ -21,6 +21,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + pigeon: ^11.0.0 topics: - image diff --git a/packages/ios_platform_images/test/ios_platform_images_test.dart b/packages/ios_platform_images/test/ios_platform_images_test.dart index f42b78646038..04605c1e5cba 100644 --- a/packages/ios_platform_images/test/ios_platform_images_test.dart +++ b/packages/ios_platform_images/test/ios_platform_images_test.dart @@ -5,34 +5,63 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:ios_platform_images/ios_platform_images.dart'; +import 'package:ios_platform_images/src/messages.g.dart'; void main() { - const MethodChannel channel = - MethodChannel('plugins.flutter.io/ios_platform_images'); - - TestWidgetsFlutterBinding.ensureInitialized(); + late FakePlatformImagesApi fakeApi; setUp(() { - _ambiguate(TestDefaultBinaryMessengerBinding.instance)! - .defaultBinaryMessenger - .setMockMethodCallHandler(channel, (MethodCall methodCall) async { - return '42'; - }); + fakeApi = FakePlatformImagesApi(); + setPlatformImageHostApi(fakeApi); }); - tearDown(() { - _ambiguate(TestDefaultBinaryMessengerBinding.instance)! - .defaultBinaryMessenger - .setMockMethodCallHandler(channel, null); + test('resolveURL passes arguments', () async { + const String name = 'a name'; + const String extension = '.extension'; + + await IosPlatformImages.resolveURL(name, extension: extension); + + expect(fakeApi.passedName, name); + expect(fakeApi.passedExtension, extension); }); - test('resolveURL', () async { - expect(await IosPlatformImages.resolveURL('foobar'), '42'); + test('resolveURL returns null', () async { + expect(await IosPlatformImages.resolveURL('foobar'), null); + }); + + test('resolveURL returns result', () async { + const String result = 'a result'; + fakeApi.resolutionResult = result; + + expect(await IosPlatformImages.resolveURL('foobar'), result); + }); + + test('loadImage passes argument', () async { + fakeApi.loadResult = PlatformImageData(data: Uint8List(1), scale: 1.0); + const String name = 'a name'; + + IosPlatformImages.load(name); + + expect(fakeApi.passedName, name); }); } -/// This allows a value of type T or T? to be treated as a value of type T?. -/// -/// We use this so that APIs that have become non-nullable can still be used -/// with `!` and `?` on the stable branch. -T? _ambiguate(T? value) => value; +class FakePlatformImagesApi implements PlatformImagesApi { + String? passedName; + String? passedExtension; + String? resolutionResult; + PlatformImageData? loadResult; + + @override + Future loadImage(String name) async { + passedName = name; + return loadResult; + } + + @override + Future resolveUrl(String name, String? extension) async { + passedName = name; + passedExtension = extension; + return resolutionResult; + } +}