diff --git a/.github/workflows/passkit_ui.yaml b/.github/workflows/passkit_ui.yaml new file mode 100644 index 0000000..9cddc98 --- /dev/null +++ b/.github/workflows/passkit_ui.yaml @@ -0,0 +1,22 @@ +name: passkit_ui + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + pull_request: + paths: + - "passkit_ui/**" + - ".github/workflows/passkit_ui.yaml" + branches: + - master + +jobs: + build: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 + with: + flutter_version: 3.22.2 + working_directory: passkit_ui + # TODO: Increase minimum coverage to 100% gradually. + min_coverage: 50 diff --git a/.github/workflows/semantic_pull_request.yaml b/.github/workflows/semantic_pull_request.yaml new file mode 100644 index 0000000..005bccc --- /dev/null +++ b/.github/workflows/semantic_pull_request.yaml @@ -0,0 +1,13 @@ +name: semantic_pull_request + +on: + pull_request: + branches: + - master + push: + branches: + - master + +jobs: + build: + uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/semantic_pull_request.yml@v1 diff --git a/.gitignore b/.gitignore index e43b0f9..a315c9f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,26 @@ +# Miscellaneous +.atom/ +.idea/* +.vscode/* + +# Files and directories created by pub +.dart_tool/ +.packages +pubspec.lock + +# Files and directories created by MacOS .DS_Store + +# Conventional directory for build outputs +build/ + +# Coverage +coverage/ + +# Submodules +!pubspec.lock +packages/**/pubspec.lock + +# Exceptions +!.vscode/launch.json +!.vscode/tasks.json diff --git a/app/pubspec.lock b/app/pubspec.lock index 8a0b9a0..083721a 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -196,11 +196,10 @@ packages: content_resolver: dependency: "direct main" description: - path: "." - ref: main - resolved-ref: b6843d4f4638b811711964400dc3d4ccd55b013a - url: "git@github.com:espresso3389/flutter_content_resolver.git" - source: git + name: content_resolver + sha256: "190cfc226655ac102384f8b28a41618d01a879bb578324510e8384efed9c1b80" + url: "https://pub.dev" + source: hosted version: "0.3.1" convert: dependency: transitive diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 9040287..a2a9e03 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -38,10 +38,6 @@ dependencies: wakelock_plus: ^1.2.5 dependency_overrides: - content_resolver: - git: - url: git@github.com:espresso3389/flutter_content_resolver.git - ref: main passkit: path: ../passkit passkit_ui: diff --git a/apple_passkit/lib/src/apple_passkit.dart b/apple_passkit/lib/src/apple_passkit.dart index a9bfde2..81fe670 100644 --- a/apple_passkit/lib/src/apple_passkit.dart +++ b/apple_passkit/lib/src/apple_passkit.dart @@ -25,7 +25,8 @@ class ApplePassKit { /// passes. Use this class’s [canAddPasses] method instead. Future isPassLibraryAvailable() async => await wrapWithException( - methodChannel.invokeMethod('isPassLibraryAvailable')) ?? + methodChannel.invokeMethod('isPassLibraryAvailable'), + ) ?? false; /// Presents a user interface for adding multiple passes at once. @@ -43,11 +44,12 @@ class ApplePassKit { return PKPassLibraryAddPassesStatus.unknown; } - final resultCode = - await wrapWithException(methodChannel.invokeMethod( - 'addPasses', - nonEmptyPasses, - )); + final resultCode = await wrapWithException( + methodChannel.invokeMethod( + 'addPasses', + nonEmptyPasses, + ), + ); if (resultCode == null) { return PKPassLibraryAddPassesStatus.unknown; @@ -69,10 +71,11 @@ class ApplePassKit { /// Passes don’t have a fixed order. Calling this method multiple times may /// return the same passes, but in a different order. Future> passes() async { - final list = - await wrapWithException(methodChannel.invokeMethod>( - 'getPasses', - )); + final list = await wrapWithException( + methodChannel.invokeMethod>( + 'getPasses', + ), + ); return list ?.cast>() @@ -96,7 +99,8 @@ class ApplePassKit { throw PkPassEmptyException(); } return await wrapWithException( - methodChannel.invokeMethod('containsPass', [pass])) ?? + methodChannel.invokeMethod('containsPass', [pass]), + ) ?? false; } @@ -105,7 +109,8 @@ class ApplePassKit { /// Returns a Boolean value that indicates whether the device supports adding passes. Future canAddPasses() async => await wrapWithException( - methodChannel.invokeMethod('canAddPasses')) ?? + methodChannel.invokeMethod('canAddPasses'), + ) ?? false; /// Adding payment passes requires a special entitlement issued by Apple. @@ -116,10 +121,12 @@ class ApplePassKit { if (pass.isEmpty) { throw PkPassEmptyException(); } - await wrapWithException(methodChannel.invokeMethod( - 'addPassViewController', - [pass], - )); + await wrapWithException( + methodChannel.invokeMethod( + 'addPassViewController', + [pass], + ), + ); } /// Adding payment passes requires a special entitlement issued by Apple. @@ -131,9 +138,11 @@ class ApplePassKit { if (nonEmptyPasses.isEmpty) { return; } - await wrapWithException(methodChannel.invokeMethod( - 'addPassesViewController', - nonEmptyPasses, - )); + await wrapWithException( + methodChannel.invokeMethod( + 'addPassesViewController', + nonEmptyPasses, + ), + ); } } diff --git a/passkit/lib/passkit.dart b/passkit/lib/passkit.dart index 9e8eb55..b64288a 100644 --- a/passkit/lib/passkit.dart +++ b/passkit/lib/passkit.dart @@ -10,6 +10,7 @@ export 'src/passkit/location.dart'; export 'src/passkit/nfc.dart'; export 'src/passkit/parse_utils.dart'; export 'src/passkit/pass_structure.dart'; +export 'src/passkit/pass_data.dart'; export 'src/passkit/pass_type.dart'; export 'src/passkit/pk_pass_image.dart'; export 'src/passkit/pkpass.dart'; diff --git a/passkit/lib/src/passkit/pass_type.dart b/passkit/lib/src/passkit/pass_type.dart index d2ca140..5ad1db8 100644 --- a/passkit/lib/src/passkit/pass_type.dart +++ b/passkit/lib/src/passkit/pass_type.dart @@ -2,7 +2,7 @@ enum PassType { boardingPass, coupon, eventTicket, - generic, storeCard, + generic, unknown, } diff --git a/passkit/lib/src/passkit/pk_pass_image.dart b/passkit/lib/src/passkit/pk_pass_image.dart index 8bb023c..34ae971 100644 --- a/passkit/lib/src/passkit/pk_pass_image.dart +++ b/passkit/lib/src/passkit/pk_pass_image.dart @@ -2,6 +2,11 @@ import 'dart:typed_data'; class PkPassImage { PkPassImage({this.image1, this.image2, this.image3}); + + final Uint8List? image1; + final Uint8List? image2; + final Uint8List? image3; + static PkPassImage? fromImages({ Uint8List? image1, Uint8List? image2, @@ -13,18 +18,12 @@ class PkPassImage { return PkPassImage(image1: image1, image2: image2, image3: image3); } - final Uint8List? image1; - final Uint8List? image2; - final Uint8List? image3; - Uint8List fromMultiplier(int multiplier) { assert(multiplier == 1 || multiplier == 2 || multiplier == 3); - switch (multiplier) { - case 1: - return (image1 ?? image2 ?? image3)!; - case 2: - return (image2 ?? image3 ?? image1)!; - } - return (image3 ?? image2 ?? image1)!; + return switch (multiplier) { + 1 => (image1 ?? image2 ?? image3)!, + 2 => (image2 ?? image3 ?? image1)!, + _ => (image3 ?? image2 ?? image1)!, + }; } } diff --git a/passkit_ui/example/pubspec.lock b/passkit_ui/example/pubspec.lock index 8935213..c1973ca 100644 --- a/passkit_ui/example/pubspec.lock +++ b/passkit_ui/example/pubspec.lock @@ -107,6 +107,22 @@ packages: description: flutter source: sdk version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" json_annotation: dependency: transitive description: @@ -278,6 +294,14 @@ packages: url: "https://pub.dev" source: hosted version: "14.2.1" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" sdks: dart: ">=3.3.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/passkit_ui/lib/passkit_ui.dart b/passkit_ui/lib/passkit_ui.dart index 4fe2108..e5a5d8c 100644 --- a/passkit_ui/lib/passkit_ui.dart +++ b/passkit_ui/lib/passkit_ui.dart @@ -1 +1,3 @@ +export 'src/widgets/widgets.dart'; +export 'src/extensions/extensions.dart'; export 'src/pk_pass_widget.dart'; diff --git a/passkit_ui/lib/src/boarding_pass.dart b/passkit_ui/lib/src/boarding_pass.dart index 61497c3..cf95167 100644 --- a/passkit_ui/lib/src/boarding_pass.dart +++ b/passkit_ui/lib/src/boarding_pass.dart @@ -1,12 +1,8 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:passkit/passkit.dart'; -import 'package:passkit_ui/src/extension/formatting_extensions.dart'; -import 'package:passkit_ui/src/extension/pk_pass_image_extensions.dart'; -import 'package:passkit_ui/src/pass_theme.dart'; -import 'package:passkit_ui/src/widgets/footer.dart'; -import 'package:passkit_ui/src/widgets/passkit_barcode.dart'; -import 'package:passkit_ui/src/widgets/transit_types/transit_type_widget.dart'; +import 'package:passkit_ui/passkit_ui.dart'; +import 'package:passkit_ui/src/theme/theme.dart'; /// A boarding pass looks like the following: /// @@ -28,7 +24,8 @@ class BoardingPass extends StatelessWidget { @override Widget build(BuildContext context) { - final passTheme = pass.toTheme(); + final devicePixelRatio = MediaQuery.devicePixelRatioOf(context); + final passTheme = pass.theme; final boardingPass = pass.pass.boardingPass!; return Card( @@ -53,7 +50,7 @@ class BoardingPass extends StatelessWidget { maxHeight: 50, ), child: Image.memory( - pass.logo!.forCorrectPixelRatio(context), + pass.logo!.forCorrectPixelRatio(devicePixelRatio), fit: BoxFit.contain, ), ), @@ -147,7 +144,7 @@ class BoardingPass extends StatelessWidget { if (pass.icon != null) // TODO(ueman): check whether this matches Apples design guidelines Image.memory( - pass.icon!.forCorrectPixelRatio(context), + pass.icon!.forCorrectPixelRatio(devicePixelRatio), fit: BoxFit.contain, height: 15, ), @@ -207,34 +204,41 @@ class _AuxiliaryRow extends StatelessWidget { @override Widget build(BuildContext context) { + final directionality = Directionality.of(context); return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, - children: auxiliaryRow.map((item) { - return Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - item.label ?? '', - style: passTheme.labelTextStyle.copyWith( - fontSize: 10, - fontWeight: FontWeight.w600, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ...auxiliaryRow.map( + (item) => Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + item.label ?? '', + style: passTheme.labelTextStyle.copyWith( + fontSize: 10, + fontWeight: FontWeight.w600, + ), + textAlign: item.textAlignment?.toFlutterTextAlign( + textDirection: directionality, + ), ), - textAlign: item.textAlignment?.flutterTextAlign(context), - ), - Text( - item.value.toString(), - style: passTheme.foregroundTextStyle.copyWith( - fontSize: 16, - height: 0.9, + Text( + item.value.toString(), + style: passTheme.foregroundTextStyle.copyWith( + fontSize: 16, + height: 0.9, + ), + textAlign: item.textAlignment?.toFlutterTextAlign( + textDirection: directionality, + ), ), - textAlign: item.textAlignment?.flutterTextAlign(context), - ), - ], + ], + ), ), - ); - }).toList(), + ), + ], ); } } diff --git a/passkit_ui/lib/src/coupon.dart b/passkit_ui/lib/src/coupon.dart index 1a85cfa..70bd4b0 100644 --- a/passkit_ui/lib/src/coupon.dart +++ b/passkit_ui/lib/src/coupon.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; import 'package:passkit/passkit.dart'; -import 'package:passkit_ui/src/extension/formatting_extensions.dart'; -import 'package:passkit_ui/src/extension/pk_pass_image_extensions.dart'; -import 'package:passkit_ui/src/pass_theme.dart'; -import 'package:passkit_ui/src/widgets/passkit_barcode.dart'; +import 'package:passkit_ui/passkit_ui.dart'; +import 'package:passkit_ui/src/theme/theme.dart'; /// A coupon looks like the following: /// @@ -21,7 +19,9 @@ class Coupon extends StatelessWidget { @override Widget build(BuildContext context) { - final passTheme = pass.toTheme(); + final devicePixelRatio = MediaQuery.devicePixelRatioOf(context); + + final passTheme = pass.theme; final coupon = pass.pass.coupon!; return Card( @@ -47,7 +47,7 @@ class Coupon extends StatelessWidget { maxHeight: 50, ), child: Image.memory( - pass.logo!.forCorrectPixelRatio(context), + pass.logo!.forCorrectPixelRatio(devicePixelRatio), fit: BoxFit.contain, ), ), @@ -73,7 +73,9 @@ class Coupon extends StatelessWidget { Stack( children: [ if (pass.strip != null) - Image.memory(pass.strip!.forCorrectPixelRatio(context)), + Image.memory( + pass.strip!.forCorrectPixelRatio(devicePixelRatio), + ), _AuxiliaryRow( passTheme: passTheme, auxiliaryRow: coupon.primaryFields!, @@ -91,7 +93,7 @@ class Coupon extends StatelessWidget { const SizedBox(height: 16), if (pass.footer != null) Image.memory( - pass.footer!.forCorrectPixelRatio(context), + pass.footer!.forCorrectPixelRatio(devicePixelRatio), fit: BoxFit.contain, width: 286, height: 15, @@ -120,6 +122,7 @@ class _AuxiliaryRow extends StatelessWidget { @override Widget build(BuildContext context) { + final directionality = Directionality.of(context); return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: auxiliaryRow.map((item) { @@ -128,12 +131,16 @@ class _AuxiliaryRow extends StatelessWidget { Text( item.label ?? '', style: passTheme.labelTextStyle, - textAlign: item.textAlignment?.flutterTextAlign(context), + textAlign: item.textAlignment?.toFlutterTextAlign( + textDirection: directionality, + ), ), Text( item.value.toString(), style: passTheme.foregroundTextStyle, - textAlign: item.textAlignment?.flutterTextAlign(context), + textAlign: item.textAlignment?.toFlutterTextAlign( + textDirection: directionality, + ), ), ], ); diff --git a/passkit_ui/lib/src/event_ticket.dart b/passkit_ui/lib/src/event_ticket.dart index 145aece..a70d273 100644 --- a/passkit_ui/lib/src/event_ticket.dart +++ b/passkit_ui/lib/src/event_ticket.dart @@ -2,10 +2,8 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:passkit/passkit.dart'; -import 'package:passkit_ui/src/extension/formatting_extensions.dart'; -import 'package:passkit_ui/src/extension/pk_pass_image_extensions.dart'; -import 'package:passkit_ui/src/pass_theme.dart'; -import 'package:passkit_ui/src/widgets/passkit_barcode.dart'; +import 'package:passkit_ui/passkit_ui.dart'; +import 'package:passkit_ui/src/theme/theme.dart'; /// Event tickets /// @@ -28,7 +26,9 @@ class EventTicket extends StatelessWidget { @override Widget build(BuildContext context) { - final passTheme = pass.toTheme(); + final devicePixelRatio = MediaQuery.devicePixelRatioOf(context); + + final passTheme = pass.theme; final eventTicket = pass.pass.eventTicket!; assert( @@ -55,7 +55,7 @@ class EventTicket extends StatelessWidget { imageFilter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: Image.memory( fit: BoxFit.cover, - pass.background!.forCorrectPixelRatio(context), + pass.background!.forCorrectPixelRatio(devicePixelRatio), ), ), ), @@ -77,7 +77,7 @@ class EventTicket extends StatelessWidget { maxHeight: 50, ), child: Image.memory( - pass.logo!.forCorrectPixelRatio(context), + pass.logo!.forCorrectPixelRatio(devicePixelRatio), fit: BoxFit.contain, ), ), @@ -119,7 +119,7 @@ class EventTicket extends StatelessWidget { // 2:3 to 3:2, otherwise the image is cropped. if (pass.thumbnail != null) Image.memory( - pass.thumbnail!.forCorrectPixelRatio(context), + pass.thumbnail!.forCorrectPixelRatio(devicePixelRatio), ), ], ), @@ -138,7 +138,7 @@ class EventTicket extends StatelessWidget { const SizedBox(height: 16), if (pass.footer != null) Image.memory( - pass.footer!.forCorrectPixelRatio(context), + pass.footer!.forCorrectPixelRatio(devicePixelRatio), fit: BoxFit.contain, width: 286, height: 15, @@ -170,6 +170,7 @@ class _AuxiliaryRow extends StatelessWidget { @override Widget build(BuildContext context) { + final directionality = Directionality.of(context); return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: auxiliaryRow.map((item) { @@ -178,12 +179,16 @@ class _AuxiliaryRow extends StatelessWidget { Text( item.label ?? '', style: passTheme.labelTextStyle, - textAlign: item.textAlignment?.flutterTextAlign(context), + textAlign: item.textAlignment?.toFlutterTextAlign( + textDirection: directionality, + ), ), Text( item.value.toString(), style: passTheme.foregroundTextStyle, - textAlign: item.textAlignment?.flutterTextAlign(context), + textAlign: item.textAlignment?.toFlutterTextAlign( + textDirection: directionality, + ), ), ], ); diff --git a/passkit_ui/lib/src/extension/formatting_extensions.dart b/passkit_ui/lib/src/extension/formatting_extensions.dart deleted file mode 100644 index acda7f1..0000000 --- a/passkit_ui/lib/src/extension/formatting_extensions.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:passkit/passkit.dart'; - -extension PkTextAlignmentExtension on PkTextAlignment { - TextAlign flutterTextAlign(BuildContext context) { - return switch (this) { - PkTextAlignment.left => TextAlign.left, - PkTextAlignment.center => TextAlign.center, - PkTextAlignment.right => TextAlign.right, - PkTextAlignment.natural => Directionality.of(context) == TextDirection.ltr - ? TextAlign.left - : TextAlign.right - }; - } -} diff --git a/passkit_ui/lib/src/extension/pk_pass_image_extensions.dart b/passkit_ui/lib/src/extension/pk_pass_image_extensions.dart deleted file mode 100644 index c44b3a8..0000000 --- a/passkit_ui/lib/src/extension/pk_pass_image_extensions.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'dart:typed_data'; - -import 'package:flutter/material.dart'; -import 'package:passkit/passkit.dart'; - -extension PkPassImageExtension on PkPassImage { - Uint8List forCorrectPixelRatio(BuildContext context) { - final pixelRatio = MediaQuery.devicePixelRatioOf(context).toInt(); - // It just looks better when using images a step higher - return fromMultiplier(pixelRatio + 1); - } -} diff --git a/passkit_ui/lib/src/extension/css_color_extension.dart b/passkit_ui/lib/src/extensions/css_color_extension.dart similarity index 100% rename from passkit_ui/lib/src/extension/css_color_extension.dart rename to passkit_ui/lib/src/extensions/css_color_extension.dart diff --git a/passkit_ui/lib/src/extensions/extensions.dart b/passkit_ui/lib/src/extensions/extensions.dart new file mode 100644 index 0000000..39eb12f --- /dev/null +++ b/passkit_ui/lib/src/extensions/extensions.dart @@ -0,0 +1,3 @@ +export 'css_color_extension.dart'; +export 'pk_text_alignment_extension.dart'; +export 'pk_pass_image_extension.dart'; diff --git a/passkit_ui/lib/src/extensions/pk_pass_image_extension.dart b/passkit_ui/lib/src/extensions/pk_pass_image_extension.dart new file mode 100644 index 0000000..e446e3e --- /dev/null +++ b/passkit_ui/lib/src/extensions/pk_pass_image_extension.dart @@ -0,0 +1,10 @@ +import 'dart:typed_data'; + +import 'package:passkit/passkit.dart'; + +extension PkPassImageX on PkPassImage { + Uint8List forCorrectPixelRatio(double devicePixelRatio) { + // It just looks better when using images a step higher. + return fromMultiplier(devicePixelRatio.toInt() + 1); + } +} diff --git a/passkit_ui/lib/src/extensions/pk_text_alignment_extension.dart b/passkit_ui/lib/src/extensions/pk_text_alignment_extension.dart new file mode 100644 index 0000000..2ed36ba --- /dev/null +++ b/passkit_ui/lib/src/extensions/pk_text_alignment_extension.dart @@ -0,0 +1,17 @@ +import 'package:flutter/widgets.dart'; +import 'package:passkit/passkit.dart'; + +extension PkTextAlignmentX on PkTextAlignment { + TextAlign toFlutterTextAlign({TextDirection? textDirection}) { + return switch (this) { + PkTextAlignment.left => TextAlign.left, + PkTextAlignment.center => TextAlign.center, + PkTextAlignment.right => TextAlign.right, + PkTextAlignment.natural when textDirection == TextDirection.ltr => + TextAlign.left, + PkTextAlignment.natural when textDirection == TextDirection.rtl => + TextAlign.right, + _ => TextAlign.left, + }; + } +} diff --git a/passkit_ui/lib/src/generic.dart b/passkit_ui/lib/src/generic.dart index 13d8707..031be2f 100644 --- a/passkit_ui/lib/src/generic.dart +++ b/passkit_ui/lib/src/generic.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; import 'package:passkit/passkit.dart'; -import 'package:passkit_ui/src/extension/pk_pass_image_extensions.dart'; -import 'package:passkit_ui/src/extension/formatting_extensions.dart'; -import 'package:passkit_ui/src/pass_theme.dart'; -import 'package:passkit_ui/src/widgets/passkit_barcode.dart'; +import 'package:passkit_ui/passkit_ui.dart'; +import 'package:passkit_ui/src/theme/theme.dart'; /// https://developer.apple.com/design/human-interface-guidelines/wallet#Generic-passes class Generic extends StatelessWidget { @@ -13,7 +11,8 @@ class Generic extends StatelessWidget { @override Widget build(BuildContext context) { - final passTheme = pass.toTheme(); + final devicePixelRatio = MediaQuery.devicePixelRatioOf(context); + final passTheme = pass.theme; final generic = pass.pass.generic!; return Card( @@ -38,7 +37,7 @@ class Generic extends StatelessWidget { maxHeight: 50, ), child: Image.memory( - pass.logo!.forCorrectPixelRatio(context), + pass.logo!.forCorrectPixelRatio(devicePixelRatio), fit: BoxFit.contain, ), ), @@ -79,7 +78,7 @@ class Generic extends StatelessWidget { // 2:3 to 3:2, otherwise the image is cropped. if (pass.thumbnail != null) Image.memory( - pass.thumbnail!.forCorrectPixelRatio(context), + pass.thumbnail!.forCorrectPixelRatio(devicePixelRatio), ), ], ), @@ -98,7 +97,7 @@ class Generic extends StatelessWidget { const SizedBox(height: 16), if (pass.footer != null) Image.memory( - pass.footer!.forCorrectPixelRatio(context), + pass.footer!.forCorrectPixelRatio(devicePixelRatio), fit: BoxFit.contain, width: 286, height: 15, @@ -127,6 +126,7 @@ class _AuxiliaryRow extends StatelessWidget { @override Widget build(BuildContext context) { + final directionality = Directionality.of(context); return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: auxiliaryRow.map((item) { @@ -135,12 +135,16 @@ class _AuxiliaryRow extends StatelessWidget { Text( item.label ?? '', style: passTheme.labelTextStyle, - textAlign: item.textAlignment?.flutterTextAlign(context), + textAlign: item.textAlignment?.toFlutterTextAlign( + textDirection: directionality, + ), ), Text( item.value.toString(), style: passTheme.foregroundTextStyle, - textAlign: item.textAlignment?.flutterTextAlign(context), + textAlign: item.textAlignment?.toFlutterTextAlign( + textDirection: directionality, + ), ), ], ); diff --git a/passkit_ui/lib/src/pk_pass_widget.dart b/passkit_ui/lib/src/pk_pass_widget.dart index 14dda56..bd92573 100644 --- a/passkit_ui/lib/src/pk_pass_widget.dart +++ b/passkit_ui/lib/src/pk_pass_widget.dart @@ -17,18 +17,13 @@ class PkPassWidget extends StatelessWidget { @override Widget build(BuildContext context) { - switch (pass.type) { - case PassType.boardingPass: - return BoardingPass(pass: pass); - case PassType.coupon: - return Coupon(pass: pass); - case PassType.eventTicket: - return EventTicket(pass: pass); - case PassType.storeCard: - return StoreCard(pass: pass); - case PassType.unknown: - case PassType.generic: - return Generic(pass: pass); - } + return switch (pass.type) { + PassType.boardingPass => BoardingPass(pass: pass), + PassType.coupon => Coupon(pass: pass), + PassType.eventTicket => EventTicket(pass: pass), + PassType.storeCard => StoreCard(pass: pass), + PassType.generic => Generic(pass: pass), + PassType.unknown => Generic(pass: pass), + }; } } diff --git a/passkit_ui/lib/src/store_card.dart b/passkit_ui/lib/src/store_card.dart index 1ee36b4..01ac91b 100644 --- a/passkit_ui/lib/src/store_card.dart +++ b/passkit_ui/lib/src/store_card.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; import 'package:passkit/passkit.dart'; -import 'package:passkit_ui/src/extension/formatting_extensions.dart'; -import 'package:passkit_ui/src/extension/pk_pass_image_extensions.dart'; -import 'package:passkit_ui/src/pass_theme.dart'; -import 'package:passkit_ui/src/widgets/passkit_barcode.dart'; +import 'package:passkit_ui/passkit_ui.dart'; +import 'package:passkit_ui/src/theme/theme.dart'; /// A store card looks like the following: /// @@ -22,7 +20,9 @@ class StoreCard extends StatelessWidget { @override Widget build(BuildContext context) { - final passTheme = pass.toTheme(); + final devicePixelRatio = MediaQuery.devicePixelRatioOf(context); + + final passTheme = pass.theme; final storeCard = pass.pass.storeCard!; return Card( @@ -48,7 +48,7 @@ class StoreCard extends StatelessWidget { maxHeight: 50, ), child: Image.memory( - pass.logo!.forCorrectPixelRatio(context), + pass.logo!.forCorrectPixelRatio(devicePixelRatio), fit: BoxFit.contain, ), ), @@ -86,7 +86,7 @@ class StoreCard extends StatelessWidget { const SizedBox(height: 16), if (pass.footer != null) Image.memory( - pass.footer!.forCorrectPixelRatio(context), + pass.footer!.forCorrectPixelRatio(devicePixelRatio), fit: BoxFit.contain, width: 286, height: 15, @@ -115,6 +115,7 @@ class _AuxiliaryRow extends StatelessWidget { @override Widget build(BuildContext context) { + final directionality = Directionality.of(context); return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: auxiliaryRow.map((item) { @@ -123,12 +124,16 @@ class _AuxiliaryRow extends StatelessWidget { Text( item.label ?? '', style: passTheme.labelTextStyle, - textAlign: item.textAlignment?.flutterTextAlign(context), + textAlign: item.textAlignment?.toFlutterTextAlign( + textDirection: directionality, + ), ), Text( item.value.toString(), style: passTheme.foregroundTextStyle, - textAlign: item.textAlignment?.flutterTextAlign(context), + textAlign: item.textAlignment?.toFlutterTextAlign( + textDirection: directionality, + ), ), ], ); diff --git a/passkit_ui/lib/src/pass_theme.dart b/passkit_ui/lib/src/theme/pass_theme.dart similarity index 83% rename from passkit_ui/lib/src/pass_theme.dart rename to passkit_ui/lib/src/theme/pass_theme.dart index 7fd70d7..c6ef481 100644 --- a/passkit_ui/lib/src/pass_theme.dart +++ b/passkit_ui/lib/src/theme/pass_theme.dart @@ -1,7 +1,8 @@ +// TODO(any): Make [PassTheme] a Theme extension. + import 'package:flutter/material.dart'; import 'package:passkit/passkit.dart'; - -import 'extension/css_color_extension.dart'; +import 'package:passkit_ui/passkit_ui.dart'; class PassTheme { PassTheme({ @@ -19,8 +20,8 @@ class PassTheme { TextStyle get labelTextStyle => TextStyle(color: labelColor); } -extension PkPassThemeExtension on PkPass { - PassTheme toTheme() { +extension PkPassThemeX on PkPass { + PassTheme get theme { return PassTheme( backgroundColor: pass.backgroundColor?.toDartUiColor() ?? Colors.white, foregroundColor: pass.foregroundColor?.toDartUiColor() ?? Colors.black, diff --git a/passkit_ui/lib/src/theme/theme.dart b/passkit_ui/lib/src/theme/theme.dart new file mode 100644 index 0000000..db59b26 --- /dev/null +++ b/passkit_ui/lib/src/theme/theme.dart @@ -0,0 +1 @@ +export 'pass_theme.dart'; diff --git a/passkit_ui/lib/src/widgets/footer.dart b/passkit_ui/lib/src/widgets/footer.dart index 189505f..3bf16de 100644 --- a/passkit_ui/lib/src/widgets/footer.dart +++ b/passkit_ui/lib/src/widgets/footer.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:passkit/passkit.dart'; -import 'package:passkit_ui/src/extension/pk_pass_image_extensions.dart'; +import 'package:passkit_ui/src/extensions/pk_pass_image_extension.dart'; /// The footer image (`footer.png`) is displayed near the barcode. /// The allotted space is 286 x 15 points. @@ -14,15 +14,15 @@ class Footer extends StatelessWidget { @override Widget build(BuildContext context) { - final footerImage = footer; - if (footerImage != null) { - return Image.memory( - footerImage.forCorrectPixelRatio(context), - fit: BoxFit.contain, - width: 286, - height: 15, - ); - } - return const SizedBox.shrink(); + if (footer == null) return const SizedBox.shrink(); + + final devicePixelRatio = MediaQuery.devicePixelRatioOf(context); + + return Image.memory( + footer!.forCorrectPixelRatio(devicePixelRatio), + fit: BoxFit.contain, + width: 286, + height: 15, + ); } } diff --git a/passkit_ui/lib/src/widgets/logo.dart b/passkit_ui/lib/src/widgets/logo.dart index fb0522e..9100e36 100644 --- a/passkit_ui/lib/src/widgets/logo.dart +++ b/passkit_ui/lib/src/widgets/logo.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:passkit/passkit.dart'; -import 'package:passkit_ui/src/extension/pk_pass_image_extensions.dart'; +import 'package:passkit_ui/src/extensions/pk_pass_image_extension.dart'; /// The logo image (`logo.png`) is displayed in the top left corner of the pass, /// next to the logo text. The allotted space is 160 x 50 points; @@ -16,18 +16,18 @@ class Logo extends StatelessWidget { @override Widget build(BuildContext context) { - final logoImage = logo; - if (logoImage != null) { - return FittedBox( - clipBehavior: Clip.hardEdge, - child: Image.memory( - logoImage.forCorrectPixelRatio(context), - fit: BoxFit.cover, - width: 160, - height: 50, - ), - ); - } - return const SizedBox.shrink(); + if (logo == null) return const SizedBox.shrink(); + + final devicePixelRatio = MediaQuery.devicePixelRatioOf(context); + + return FittedBox( + clipBehavior: Clip.hardEdge, + child: Image.memory( + logo!.forCorrectPixelRatio(devicePixelRatio), + fit: BoxFit.cover, + width: 160, + height: 50, + ), + ); } } diff --git a/passkit_ui/lib/src/widgets/passkit_barcode.dart b/passkit_ui/lib/src/widgets/passkit_barcode.dart index c777ad0..5254550 100644 --- a/passkit_ui/lib/src/widgets/passkit_barcode.dart +++ b/passkit_ui/lib/src/widgets/passkit_barcode.dart @@ -1,7 +1,7 @@ import 'package:barcode_widget/barcode_widget.dart'; import 'package:flutter/material.dart'; import 'package:passkit/passkit.dart' as passkit; -import 'package:passkit_ui/src/pass_theme.dart'; +import 'package:passkit_ui/src/theme/theme.dart'; /// PassKit displays the first supported barcode in this array. /// Note that the `PKBarcodeFormatQR`, `PKBarcodeFormatPDF417`, diff --git a/passkit_ui/lib/src/widgets/transit_types/transit_types.dart b/passkit_ui/lib/src/widgets/transit_types/transit_types.dart new file mode 100644 index 0000000..5ca5329 --- /dev/null +++ b/passkit_ui/lib/src/widgets/transit_types/transit_types.dart @@ -0,0 +1,4 @@ +export 'bus_icon.dart'; +export 'generic_transit_type.dart'; +export 'plane_icon.dart'; +export 'transit_type_widget.dart'; diff --git a/passkit_ui/lib/src/widgets/widgets.dart b/passkit_ui/lib/src/widgets/widgets.dart new file mode 100644 index 0000000..75d7bc2 --- /dev/null +++ b/passkit_ui/lib/src/widgets/widgets.dart @@ -0,0 +1,4 @@ +export 'transit_types/transit_types.dart'; +export 'footer.dart'; +export 'logo.dart'; +export 'passkit_barcode.dart'; diff --git a/passkit_ui/pubspec.yaml b/passkit_ui/pubspec.yaml index 59214ff..a839cf9 100644 --- a/passkit_ui/pubspec.yaml +++ b/passkit_ui/pubspec.yaml @@ -1,5 +1,5 @@ name: passkit_ui -description: A UI kit for PassKit files. Uses the `passkit` package under the hood +description: A UI kit for PassKit files. Uses the `passkit` package under the hood. version: 0.0.1 repository: https://github.com/ueman/passkit issue_tracker: https://github.com/ueman/passkit/issues diff --git a/passkit_ui/test/helpers/helpers.dart b/passkit_ui/test/helpers/helpers.dart new file mode 100644 index 0000000..c22ca9c --- /dev/null +++ b/passkit_ui/test/helpers/helpers.dart @@ -0,0 +1,7 @@ +import 'dart:convert'; + +export 'widget_tester_extension.dart'; + +final transparentPixelPng = base64Decode( + '''iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==''', +); diff --git a/passkit_ui/test/helpers/widget_tester_extension.dart b/passkit_ui/test/helpers/widget_tester_extension.dart new file mode 100644 index 0000000..78595cd --- /dev/null +++ b/passkit_ui/test/helpers/widget_tester_extension.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +extension WidgetTesterX on WidgetTester { + Future pumpApp(Widget widget) async { + await pumpWidget(MaterialApp(home: widget)); + await pump(); + } +} diff --git a/passkit_ui/test/src/extensions/css_color_extension_test.dart b/passkit_ui/test/src/extensions/css_color_extension_test.dart new file mode 100644 index 0000000..3d7d7ac --- /dev/null +++ b/passkit_ui/test/src/extensions/css_color_extension_test.dart @@ -0,0 +1,29 @@ +import 'dart:ui'; + +import 'package:flutter_test/flutter_test.dart'; + +import 'package:csslib/parser.dart' as css; +import 'package:passkit_ui/passkit_ui.dart'; + +void main() { + group('FlutterColor', () { + group('toDartUiColor', () { + test('should return a Color object', () { + final cssColor = css.Color(0, 0); + final result = cssColor.toDartUiColor(); + + expect(result, isA()); + }); + + test('should return a Color object with the correct ARGB values', () { + final cssColor = css.Color(255, 0); + final result = cssColor.toDartUiColor(); + + expect( + result, + isSameColorAs(const Color.fromARGB(255, 0, 0, 255)), + ); + }); + }); + }); +} diff --git a/passkit_ui/test/src/extensions/pk_pass_image_extension_test.dart b/passkit_ui/test/src/extensions/pk_pass_image_extension_test.dart new file mode 100644 index 0000000..94cafe5 --- /dev/null +++ b/passkit_ui/test/src/extensions/pk_pass_image_extension_test.dart @@ -0,0 +1,23 @@ +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:passkit/passkit.dart'; +import 'package:passkit_ui/passkit_ui.dart'; + +void main() { + group('PkPassImageX', () { + group('forCorrectPixelRatio', () { + test('should return a Uint8List object', () { + final uint8List = Uint8List.fromList([0, 2, 5, 7, 42, 255]); + final passImage = PkPassImage.fromImages( + image1: uint8List, + image2: uint8List, + image3: uint8List, + ); + final result = passImage?.forCorrectPixelRatio(1.0); + + expect(result, isA()); + }); + }); + }); +} diff --git a/passkit_ui/test/src/extensions/pk_text_alignment_extension_test.dart b/passkit_ui/test/src/extensions/pk_text_alignment_extension_test.dart new file mode 100644 index 0000000..7fc0151 --- /dev/null +++ b/passkit_ui/test/src/extensions/pk_text_alignment_extension_test.dart @@ -0,0 +1,59 @@ +import 'dart:ui'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:passkit/passkit.dart'; +import 'package:passkit_ui/passkit_ui.dart'; + +void main() { + group('PkTextAlignmentX', () { + group('toFlutterTextAlign', () { + final textAlignsLtr = { + PkTextAlignment.left: TextAlign.left, + PkTextAlignment.center: TextAlign.center, + PkTextAlignment.right: TextAlign.right, + PkTextAlignment.natural: TextAlign.left, + }; + + final textAlignsRtl = { + PkTextAlignment.left: TextAlign.left, + PkTextAlignment.center: TextAlign.center, + PkTextAlignment.right: TextAlign.right, + PkTextAlignment.natural: TextAlign.right, + }; + + test( + 'returns TextAlign.left when is default case', + () => expect( + PkTextAlignment.natural.toFlutterTextAlign(), + equals(TextAlign.left), + ), + ); + + test( + 'returns correct TextAlign for each PkTextAlignment ' + 'when TextDirection is ltr', + () { + for (final textAlign in textAlignsLtr.entries) { + final result = textAlign.key.toFlutterTextAlign( + textDirection: TextDirection.ltr, + ); + expect(result, equals(textAlign.value)); + } + }, + ); + + test( + 'returns correct TextAlign for each PkTextAlignment ' + 'when TextDirection is rtl', + () { + for (final textAlign in textAlignsRtl.entries) { + final result = textAlign.key.toFlutterTextAlign( + textDirection: TextDirection.rtl, + ); + expect(result, equals(textAlign.value)); + } + }, + ); + }); + }); +} diff --git a/passkit_ui/test/src/pk_pass_widget_test.dart b/passkit_ui/test/src/pk_pass_widget_test.dart new file mode 100644 index 0000000..23cbd52 --- /dev/null +++ b/passkit_ui/test/src/pk_pass_widget_test.dart @@ -0,0 +1,132 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:passkit/passkit.dart'; +import 'package:passkit_ui/passkit_ui.dart'; +import 'package:passkit_ui/src/boarding_pass.dart'; +import 'package:passkit_ui/src/coupon.dart'; +import 'package:passkit_ui/src/event_ticket.dart'; +import 'package:passkit_ui/src/generic.dart'; +import 'package:passkit_ui/src/store_card.dart'; + +import '../helpers/helpers.dart'; + +class _FakePkPass extends Fake implements PkPass { + _FakePkPass({required this.type}); + + @override + final PassType type; + + @override + PkPassImage? get icon => _passImage; + + @override + PkPassImage? get logo => _passImage; + + @override + PkPassImage? get footer => _passImage; + + @override + PkPassImage? get strip => _passImage; + + @override + PkPassImage? get background => null; + + @override + PkPassImage? get thumbnail => null; + + @override + PassData get pass { + return PassData( + description: 'description', + formatVersion: 0, + organizationName: 'organizationName', + passTypeIdentifier: 'passTypeIdentifier', + serialNumber: 'serialNumber', + teamIdentifier: 'teamIdentifier', + boardingPass: _passStructure, + coupon: _passStructure, + eventTicket: _passStructure, + generic: _passStructure, + storeCard: _passStructure, + logoText: 'logoText', + ); + } + + static final PkPassImage? _passImage = PkPassImage.fromImages( + image1: transparentPixelPng, + image2: transparentPixelPng, + image3: transparentPixelPng, + ); + + static final PassStructure _passStructure = PassStructure( + headerFields: [ + FieldDict(key: 'key', label: 'label', value: 'value'), + FieldDict(key: 'key', label: 'label', value: 'value'), + ], + primaryFields: [ + FieldDict(key: 'key', label: 'label', value: 'value'), + FieldDict(key: 'key', label: 'label', value: 'value'), + ], + secondaryFields: [ + FieldDict(key: 'key', label: 'label', value: 'value'), + FieldDict(key: 'key', label: 'label', value: 'value'), + ], + auxiliaryFields: [ + FieldDict(key: 'key', label: 'label', value: 'value'), + FieldDict(key: 'key', label: 'label', value: 'value'), + ], + backFields: [ + FieldDict(key: 'key', label: 'label', value: 'value'), + FieldDict(key: 'key', label: 'label', value: 'value'), + ], + ); +} + +void main() { + group('PkPassWidget', () { + final passes = PassType.values.map((type) => _FakePkPass(type: type)); + + final widgets = { + PassType.boardingPass: BoardingPass( + pass: passes.firstWhere((pass) => pass.type == PassType.boardingPass), + ), + PassType.coupon: Coupon( + pass: passes.firstWhere((pass) => pass.type == PassType.coupon), + ), + PassType.eventTicket: EventTicket( + pass: passes.firstWhere((pass) => pass.type == PassType.eventTicket), + ), + PassType.storeCard: StoreCard( + pass: passes.firstWhere((pass) => pass.type == PassType.storeCard), + ), + PassType.generic: Generic( + pass: passes.firstWhere((pass) => pass.type == PassType.generic), + ), + PassType.unknown: Generic( + pass: passes.firstWhere((pass) => pass.type == PassType.unknown), + ), + }; + + final zip = IterableZip([passes, widgets.entries]); + + for (final pair in zip) { + final pass = pair.first as PkPass; + final pkPassWidget = pair.last as MapEntry; + final passType = pass.type; + final widgetType = pkPassWidget.value.runtimeType; + + testWidgets( + 'renders $widgetType when type is $passType', + (tester) async { + tester.view.devicePixelRatio = 1.0; + + await tester.pumpApp(PkPassWidget(pass: pass)); + expect(find.byType(widgetType), findsOneWidget); + + tester.view.resetDevicePixelRatio(); + }, + ); + } + }); +} diff --git a/passkit_ui/test/src/theme/pass_theme_test.dart b/passkit_ui/test/src/theme/pass_theme_test.dart new file mode 100644 index 0000000..166dca1 --- /dev/null +++ b/passkit_ui/test/src/theme/pass_theme_test.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:passkit/passkit.dart'; +import 'package:csslib/parser.dart' as css; +import 'package:passkit_ui/src/theme/theme.dart'; + +void main() { + group('PassTheme', () { + test('should return a PassTheme with the correct colors', () { + const backgroundColor = Colors.red; + const foregroundColor = Colors.green; + const labelColor = Colors.blue; + + final theme = PassTheme( + backgroundColor: backgroundColor, + foregroundColor: foregroundColor, + labelColor: labelColor, + ); + + expect(theme.backgroundColor, isSameColorAs(backgroundColor)); + expect(theme.foregroundColor, isSameColorAs(foregroundColor)); + expect(theme.labelColor, isSameColorAs(labelColor)); + }); + + test('should return a PassTheme with the correct text styles', () { + const backgroundColor = Colors.red; + const foregroundColor = Colors.green; + const labelColor = Colors.blue; + + final theme = PassTheme( + backgroundColor: backgroundColor, + foregroundColor: foregroundColor, + labelColor: labelColor, + ); + + expect(theme.foregroundTextStyle.color, isSameColorAs(foregroundColor)); + expect(theme.backgroundTextStyle.color, isSameColorAs(backgroundColor)); + expect(theme.labelTextStyle.color, isSameColorAs(labelColor)); + }); + + group('PkPassThemeX', () { + test('returns correct colors for the given pass', () { + final pass = PkPass( + pass: PassData( + description: 'description', + formatVersion: 0, + organizationName: 'organizationName', + passTypeIdentifier: 'passTypeIdentifier', + serialNumber: 'serialNumber', + teamIdentifier: 'teamIdentifier', + backgroundColor: css.Color.createRgba(255, 0, 0), + foregroundColor: css.Color.createRgba(0, 255, 0), + labelColor: css.Color.createRgba(0, 0, 255), + ), + manifest: {}, + sourceData: [], + ); + + final theme = pass.theme; + + expect( + theme.backgroundColor, + isSameColorAs(const Color.fromARGB(255, 255, 0, 0)), + ); + expect( + theme.foregroundColor, + isSameColorAs(const Color.fromARGB(255, 0, 255, 0)), + ); + expect( + theme.labelColor, + isSameColorAs(const Color.fromARGB(255, 0, 0, 255)), + ); + }); + }); + }); +} diff --git a/passkit_ui/test/src/widgets/footer_test.dart b/passkit_ui/test/src/widgets/footer_test.dart new file mode 100644 index 0000000..a1fa136 --- /dev/null +++ b/passkit_ui/test/src/widgets/footer_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:passkit/passkit.dart'; +import 'package:passkit_ui/passkit_ui.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + group('Footer', () { + testWidgets('renders nothing when PkPassImage is null', (tester) async { + await tester.pumpApp(const Footer(footer: null)); + expect(find.byType(SizedBox), findsOneWidget); + }); + + testWidgets( + 'renders an Image when PkPassImage is not null', + (tester) async { + tester.view.display.devicePixelRatio = 1.0; + + final footer = PkPassImage.fromImages( + image1: transparentPixelPng, + image2: transparentPixelPng, + image3: transparentPixelPng, + ); + await tester.pumpApp(Footer(footer: footer)); + expect(find.byType(Image), findsOneWidget); + + tester.view.display.resetDevicePixelRatio(); + }, + ); + }); +} diff --git a/passkit_ui/test/src/widgets/logo_test.dart b/passkit_ui/test/src/widgets/logo_test.dart new file mode 100644 index 0000000..af4c641 --- /dev/null +++ b/passkit_ui/test/src/widgets/logo_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:passkit/passkit.dart'; +import 'package:passkit_ui/passkit_ui.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + group('Logo', () { + testWidgets('renders nothing when PkPassImage is null', (tester) async { + await tester.pumpApp(const Logo(logo: null)); + expect(find.byType(SizedBox), findsOneWidget); + }); + + testWidgets( + 'renders an Image when PkPassImage is not null', + (tester) async { + tester.view.display.devicePixelRatio = 1.0; + + final footer = PkPassImage.fromImages( + image1: transparentPixelPng, + image2: transparentPixelPng, + image3: transparentPixelPng, + ); + await tester.pumpApp(Logo(logo: footer)); + expect(find.byType(Image), findsOneWidget); + + tester.view.display.resetDevicePixelRatio(); + }, + ); + }); +} diff --git a/passkit_ui/test/src/widgets/passkit_barcode_test.dart b/passkit_ui/test/src/widgets/passkit_barcode_test.dart new file mode 100644 index 0000000..21bcf4b --- /dev/null +++ b/passkit_ui/test/src/widgets/passkit_barcode_test.dart @@ -0,0 +1,35 @@ +import 'package:barcode_widget/barcode_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:passkit/passkit.dart' as pk; +import 'package:passkit_ui/passkit_ui.dart'; +import 'package:passkit_ui/src/theme/theme.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + group('PasskitBarcode', () { + testWidgets('renders correctly', (tester) async { + const altText = '1234567890'; + + await tester.pumpApp( + PasskitBarcode( + barcode: pk.Barcode( + message: '1234567890', + format: 'PKBarcodeFormatQR', + messageEncoding: 'utf-8', + altText: altText, + ), + passTheme: PassTheme( + labelColor: Colors.green, + foregroundColor: Colors.red, + backgroundColor: Colors.blue, + ), + ), + ); + + expect(find.byType(BarcodeWidget), findsOneWidget); + expect(find.text(altText), findsOneWidget); + }); + }); +}