diff --git a/lib/pkpass/models/pass.dart b/lib/pkpass/models/pass.dart index 7c2fb38..686517e 100644 --- a/lib/pkpass/models/pass.dart +++ b/lib/pkpass/models/pass.dart @@ -1,7 +1,7 @@ import 'package:intl/locale.dart'; import 'package:pkpass/pkpass.dart'; -import 'package:pkpass/pkpass/utils/mabe_decode.dart'; +import 'package:pkpass/pkpass/utils/maybe_decode.dart'; /// Information that is required for all passes. class PassMetadata { diff --git a/lib/pkpass/models/pass_structure_dictionary.dart b/lib/pkpass/models/pass_structure_dictionary.dart index 2ffe1e6..7b64f42 100644 --- a/lib/pkpass/models/pass_structure_dictionary.dart +++ b/lib/pkpass/models/pass_structure_dictionary.dart @@ -1,7 +1,8 @@ +import 'package:intl/intl.dart'; import 'package:intl/locale.dart'; import 'package:pkpass/pkpass.dart'; -import 'package:pkpass/pkpass/utils/mabe_decode.dart'; +import 'package:pkpass/pkpass/utils/maybe_decode.dart'; /// Keys that define the structure of the pass. /// @@ -70,6 +71,15 @@ class DictionaryField { /// Value of the field, for example, 42. final DictionaryValue value; + /// Attributed value of the field. + /// + /// The value may contain HTML markup for links. Only the tag and its href attribute are supported. For example, the following is key-value pair specifies a link with the text “Edit my profile”: + /// + /// "attributedValue": "Edit my profile" + /// + /// This key’s value overrides the text specified by the value key. + final DictionaryValue? attributedValue; + /// Label text for the field. final String? label; @@ -81,35 +91,50 @@ class DictionaryField { /// Alignment for the field’s contents. final PassTextAlign? textAlignment; - /// Attributed value of the field. - /// - /// The value may contain HTML markup for links. Only the tag and its href attribute are supported. For example, the following is key-value pair specifies a link with the text “Edit my profile”: - /// - /// "attributedValue": "Edit my profile" - /// - /// This key’s value overrides the text specified by the value key. - final DictionaryValue? attributedValue; + /// Number Style for a numeric value. + final PassTextNumberStyle? numberStyle; + + /// ISO 4217 Currency Code for a numeric value. + final String? currencyCode; + + /// Date and Time Style for a Date/Time value. + final PassTextDateStyle? dateStyle; + final PassTextDateStyle? timeStyle; + + /// Wether to display the Date/Time value in the device timezone or in the timezone of the value. + final bool ignoresTimeZone; const DictionaryField({ required this.key, required this.value, + this.attributedValue, this.label, this.changeMessage, this.textAlignment, - this.attributedValue, + this.numberStyle, + this.currencyCode, + this.dateStyle, + this.timeStyle, + this.ignoresTimeZone = false, }); factory DictionaryField.fromJson(Map json) => DictionaryField( key: json['key'] as String, value: DictionaryValue.parse(json['value'] as String), + attributedValue: json['attributedValue'] == null + ? null + : DictionaryValue.parse(json['attributedValue'] as String), label: json['label'] as String?, changeMessage: json['changeMessage'] as String?, textAlignment: MaybeDecode.maybeTextAlign(json['textAlignment'] as String?), - attributedValue: json['attributedValue'] == null - ? null - : DictionaryValue.parse(json['attributedValue'] as String), + numberStyle: + MaybeDecode.maybeNumberStyle(json['numberStyle'] as String?), + currencyCode: json['currencyCode'] as String?, + dateStyle: MaybeDecode.maybeDateStyle(json['dateStyle'] as String?), + timeStyle: MaybeDecode.maybeDateStyle(json['timeStyle'] as String?), + ignoresTimeZone: json['ignoresTimeZone'] == true ? true : false, ); /// Localized version of [label] based on given [locale] and [pass]. @@ -118,6 +143,75 @@ class DictionaryField { return localizations?[label] ?? label; } + /// Localized version of [value] based on given [locale] and [pass]. + String? getLocalizedValue(PassFile pass, Locale? locale) { + final language = locale?.toLanguageTag(); + + if (value is StringDictionaryValue) { + String string = (value as StringDictionaryValue).string; + final localizations = pass.getLocalizations(locale); + return localizations?[string] ?? string; + } else if (value is DateTimeDictionaryValue) { + DateTime dateTime = (value as DateTimeDictionaryValue).dateTime; + DateFormat format = DateFormat(null, language); + + switch (dateStyle) { + case PassTextDateStyle.None: + break; + case PassTextDateStyle.Short: + format = DateFormat.yMd(language); + break; + case PassTextDateStyle.Medium: + format = DateFormat.yMMMd(language); + break; + case PassTextDateStyle.Long: + format = DateFormat.yMMMMd(language); + break; + case PassTextDateStyle.Full: + default: + format = DateFormat.yMMMMEEEEd(language); + break; + } + + switch (timeStyle) { + case PassTextDateStyle.None: + break; + case PassTextDateStyle.Short: + format.add_jm(); + break; + case PassTextDateStyle.Medium: + case PassTextDateStyle.Long: + case PassTextDateStyle.Full: + default: + format.add_jms(); + break; + } + + if (ignoresTimeZone) { + /// violating standard: return in UTC instead of original timezone + return format.format(dateTime); + } else { + return format.format(dateTime.toLocal()); + } + } else if (value is NumberDictionaryValue) { + num number = (value as NumberDictionaryValue).number; + if (currencyCode != null) { + return NumberFormat.simpleCurrency(locale: language, name: currencyCode) + .format(number); + } else if (numberStyle == PassTextNumberStyle.Percent) { + return NumberFormat.percentPattern(language).format(number); + } else if (numberStyle == PassTextNumberStyle.Scientific) { + return NumberFormat.scientificPattern(language).format(number); + } else { + /// PassTextNumberStyle.SpellOut not implemented + /// default to decimal format + return NumberFormat.decimalPattern(language).format(number); + } + } + + return null; + } + /// Localized version of [changeMessage] based on given [locale] and [pass]. String? getLocalizedChangeMessage(PassFile pass, Locale? locale) { final localizations = pass.getLocalizations(locale); @@ -131,7 +225,7 @@ abstract class DictionaryValue { /// parses the correct [DictionaryValue] implementor based on a given [value]. factory DictionaryValue.parse(String value) { - final number = int.tryParse(value); + final number = num.tryParse(value); if (number != null) return NumberDictionaryValue(number); final dateTime = DateTime.tryParse(value); @@ -139,9 +233,6 @@ abstract class DictionaryValue { return StringDictionaryValue(value); } - - /// Localized value based on given [locale] and [pass]. - DictionaryValue getLocalizedValue(PassFile pass, Locale? locale); } /// [String] content of a [DictionaryField]. @@ -149,14 +240,6 @@ class StringDictionaryValue extends DictionaryValue { final String string; const StringDictionaryValue(this.string); - - @override - - /// Localized value based on given [locale] and [pass]. - DictionaryValue getLocalizedValue(PassFile pass, Locale? locale) { - final localizations = pass.getLocalizations(locale); - return StringDictionaryValue(localizations?[string] ?? string); - } } /// [DateTime] content of a [DictionaryField]. @@ -164,23 +247,13 @@ class DateTimeDictionaryValue extends DictionaryValue { final DateTime dateTime; const DateTimeDictionaryValue(this.dateTime); - - @override - - /// Localized value based on given [locale] and [pass]. Same as [dateTime]. - DictionaryValue getLocalizedValue(PassFile pass, Locale? locale) => this; } -/// [int] content of a [DictionaryField]. +/// [num] content of a [DictionaryField]. class NumberDictionaryValue extends DictionaryValue { - final int number; + final num number; const NumberDictionaryValue(this.number); - - @override - - /// Localized value based on given [locale] and [pass]. Same as [number]. - DictionaryValue getLocalizedValue(PassFile pass, Locale? locale) => this; } /// Possible types of [PassStructureDictionary.transitType]. @@ -216,6 +289,39 @@ enum PassTextAlign { natural, } +/// Possible types of [DictionaryField.dateStyle] and [DictionaryField.timeStyle]. +enum PassTextDateStyle { + /// PKDateStyleNone + None, + + /// PKDateStyleShort + Short, + + /// PKDateStyleMedium + Medium, + + /// PKDateStyleLong + Long, + + /// PKDateStyleFull + Full, +} + +/// Possible types of [DictionaryField.numberStyle]. +enum PassTextNumberStyle { + /// PKNumberStyleDecimal + Decimal, + + /// PKNumberStylePercent + Percent, + + /// PKNumberStyleScientific + Scientific, + + /// PKNumberStyleSpellOut + SpellOut, +} + extension _TarnsitType on TransitType { static TransitType? parse(String? type) { if (type == null) return null; diff --git a/lib/pkpass/utils/mabe_decode.dart b/lib/pkpass/utils/mabe_decode.dart deleted file mode 100644 index 19a471b..0000000 --- a/lib/pkpass/utils/mabe_decode.dart +++ /dev/null @@ -1,29 +0,0 @@ -import '../models/pass_structure_dictionary.dart'; -import 'color_helper.dart'; - -abstract class MaybeDecode { - const MaybeDecode._(); - - static int? maybeColor(String? colorCode) { - if (colorCode == null) return null; - return fromCssColor(colorCode); - } - - static DateTime? maybeDateTime(String? timeStamp) { - if (timeStamp == null) return null; - return DateTime.tryParse(timeStamp); - } - - static PassTextAlign? maybeTextAlign(String? align) { - switch (align) { - case 'PKTextAlignmentLeft': - return PassTextAlign.left; - case 'PKTextAlignmentCenter': - return PassTextAlign.center; - case 'PKTextAlignmentRight': - return PassTextAlign.right; - default: - return PassTextAlign.natural; - } - } -} diff --git a/lib/pkpass/utils/maybe_decode.dart b/lib/pkpass/utils/maybe_decode.dart new file mode 100644 index 0000000..b468a3a --- /dev/null +++ b/lib/pkpass/utils/maybe_decode.dart @@ -0,0 +1,61 @@ +import '../models/pass_structure_dictionary.dart'; +import 'color_helper.dart'; + +abstract class MaybeDecode { + const MaybeDecode._(); + + static int? maybeColor(String? colorCode) { + if (colorCode == null) return null; + return fromCssColor(colorCode); + } + + static DateTime? maybeDateTime(String? timeStamp) { + if (timeStamp == null) return null; + return DateTime.tryParse(timeStamp); + } + + static PassTextAlign? maybeTextAlign(String? align) { + switch (align) { + case 'PKTextAlignmentLeft': + return PassTextAlign.left; + case 'PKTextAlignmentCenter': + return PassTextAlign.center; + case 'PKTextAlignmentRight': + return PassTextAlign.right; + default: + return PassTextAlign.natural; + } + } + + static PassTextDateStyle? maybeDateStyle(String? style) { + switch (style) { + case 'PKDateStyleNone': + return PassTextDateStyle.None; + case 'PKDateStyleShort': + return PassTextDateStyle.Short; + case 'PKDateStyleMedium': + return PassTextDateStyle.Medium; + case 'PKDateStyleLong': + return PassTextDateStyle.Long; + case 'PKDateStyleFull': + return PassTextDateStyle.Full; + default: + return null; + } + } + + static PassTextNumberStyle? maybeNumberStyle(String? style) { + switch (style) { + case 'PKNumberStyleDecimal': + return PassTextNumberStyle.Decimal; + case 'PKNumberStylePercent': + return PassTextNumberStyle.Percent; + case 'PKNumberStyleScientific': + return PassTextNumberStyle.Scientific; + case 'PKNumberStyleSpellOut': + return PassTextNumberStyle.SpellOut; + default: + return null; + } + } +}