mirror of
https://gitlab.com/TheOneWithTheBraid/dart_pkpass.git
synced 2025-07-05 12:58:47 +00:00
Feature: implement advanced number and date formatting
This commit is contained in:
parent
0b994f6f9b
commit
2666eccf3e
4 changed files with 205 additions and 67 deletions
|
@ -1,7 +1,7 @@
|
||||||
import 'package:intl/locale.dart';
|
import 'package:intl/locale.dart';
|
||||||
|
|
||||||
import 'package:pkpass/pkpass.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.
|
/// Information that is required for all passes.
|
||||||
class PassMetadata {
|
class PassMetadata {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:intl/locale.dart';
|
import 'package:intl/locale.dart';
|
||||||
|
|
||||||
import 'package:pkpass/pkpass.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.
|
/// Keys that define the structure of the pass.
|
||||||
///
|
///
|
||||||
|
@ -70,6 +71,15 @@ class DictionaryField {
|
||||||
/// Value of the field, for example, 42.
|
/// Value of the field, for example, 42.
|
||||||
final DictionaryValue value;
|
final DictionaryValue value;
|
||||||
|
|
||||||
|
/// Attributed value of the field.
|
||||||
|
///
|
||||||
|
/// The value may contain HTML markup for links. Only the <a> 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": "<a href='http://example.com/customers/123'>Edit my profile</a>"
|
||||||
|
///
|
||||||
|
/// This key’s value overrides the text specified by the value key.
|
||||||
|
final DictionaryValue? attributedValue;
|
||||||
|
|
||||||
/// Label text for the field.
|
/// Label text for the field.
|
||||||
final String? label;
|
final String? label;
|
||||||
|
|
||||||
|
@ -81,35 +91,50 @@ class DictionaryField {
|
||||||
/// Alignment for the field’s contents.
|
/// Alignment for the field’s contents.
|
||||||
final PassTextAlign? textAlignment;
|
final PassTextAlign? textAlignment;
|
||||||
|
|
||||||
/// Attributed value of the field.
|
/// Number Style for a numeric value.
|
||||||
///
|
final PassTextNumberStyle? numberStyle;
|
||||||
/// The value may contain HTML markup for links. Only the <a> tag and its href attribute are supported. For example, the following is key-value pair specifies a link with the text “Edit my profile”:
|
|
||||||
///
|
/// ISO 4217 Currency Code for a numeric value.
|
||||||
/// "attributedValue": "<a href='http://example.com/customers/123'>Edit my profile</a>"
|
final String? currencyCode;
|
||||||
///
|
|
||||||
/// This key’s value overrides the text specified by the value key.
|
/// Date and Time Style for a Date/Time value.
|
||||||
final DictionaryValue? attributedValue;
|
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({
|
const DictionaryField({
|
||||||
required this.key,
|
required this.key,
|
||||||
required this.value,
|
required this.value,
|
||||||
|
this.attributedValue,
|
||||||
this.label,
|
this.label,
|
||||||
this.changeMessage,
|
this.changeMessage,
|
||||||
this.textAlignment,
|
this.textAlignment,
|
||||||
this.attributedValue,
|
this.numberStyle,
|
||||||
|
this.currencyCode,
|
||||||
|
this.dateStyle,
|
||||||
|
this.timeStyle,
|
||||||
|
this.ignoresTimeZone = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory DictionaryField.fromJson(Map<String, Object?> json) =>
|
factory DictionaryField.fromJson(Map<String, Object?> json) =>
|
||||||
DictionaryField(
|
DictionaryField(
|
||||||
key: json['key'] as String,
|
key: json['key'] as String,
|
||||||
value: DictionaryValue.parse(json['value'] 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?,
|
label: json['label'] as String?,
|
||||||
changeMessage: json['changeMessage'] as String?,
|
changeMessage: json['changeMessage'] as String?,
|
||||||
textAlignment:
|
textAlignment:
|
||||||
MaybeDecode.maybeTextAlign(json['textAlignment'] as String?),
|
MaybeDecode.maybeTextAlign(json['textAlignment'] as String?),
|
||||||
attributedValue: json['attributedValue'] == null
|
numberStyle:
|
||||||
? null
|
MaybeDecode.maybeNumberStyle(json['numberStyle'] as String?),
|
||||||
: DictionaryValue.parse(json['attributedValue'] 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].
|
/// Localized version of [label] based on given [locale] and [pass].
|
||||||
|
@ -118,6 +143,75 @@ class DictionaryField {
|
||||||
return localizations?[label] ?? label;
|
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].
|
/// Localized version of [changeMessage] based on given [locale] and [pass].
|
||||||
String? getLocalizedChangeMessage(PassFile pass, Locale? locale) {
|
String? getLocalizedChangeMessage(PassFile pass, Locale? locale) {
|
||||||
final localizations = pass.getLocalizations(locale);
|
final localizations = pass.getLocalizations(locale);
|
||||||
|
@ -131,7 +225,7 @@ abstract class DictionaryValue {
|
||||||
|
|
||||||
/// parses the correct [DictionaryValue] implementor based on a given [value].
|
/// parses the correct [DictionaryValue] implementor based on a given [value].
|
||||||
factory DictionaryValue.parse(String value) {
|
factory DictionaryValue.parse(String value) {
|
||||||
final number = int.tryParse(value);
|
final number = num.tryParse(value);
|
||||||
if (number != null) return NumberDictionaryValue(number);
|
if (number != null) return NumberDictionaryValue(number);
|
||||||
|
|
||||||
final dateTime = DateTime.tryParse(value);
|
final dateTime = DateTime.tryParse(value);
|
||||||
|
@ -139,9 +233,6 @@ abstract class DictionaryValue {
|
||||||
|
|
||||||
return StringDictionaryValue(value);
|
return StringDictionaryValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Localized value based on given [locale] and [pass].
|
|
||||||
DictionaryValue getLocalizedValue(PassFile pass, Locale? locale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [String] content of a [DictionaryField].
|
/// [String] content of a [DictionaryField].
|
||||||
|
@ -149,14 +240,6 @@ class StringDictionaryValue extends DictionaryValue {
|
||||||
final String string;
|
final String string;
|
||||||
|
|
||||||
const StringDictionaryValue(this.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].
|
/// [DateTime] content of a [DictionaryField].
|
||||||
|
@ -164,23 +247,13 @@ class DateTimeDictionaryValue extends DictionaryValue {
|
||||||
final DateTime dateTime;
|
final DateTime dateTime;
|
||||||
|
|
||||||
const DateTimeDictionaryValue(this.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 {
|
class NumberDictionaryValue extends DictionaryValue {
|
||||||
final int number;
|
final num number;
|
||||||
|
|
||||||
const NumberDictionaryValue(this.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].
|
/// Possible types of [PassStructureDictionary.transitType].
|
||||||
|
@ -216,6 +289,39 @@ enum PassTextAlign {
|
||||||
natural,
|
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 {
|
extension _TarnsitType on TransitType {
|
||||||
static TransitType? parse(String? type) {
|
static TransitType? parse(String? type) {
|
||||||
if (type == null) return null;
|
if (type == null) return 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
61
lib/pkpass/utils/maybe_decode.dart
Normal file
61
lib/pkpass/utils/maybe_decode.dart
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue