mirror of
https://gitlab.com/TheOneWithTheBraid/dart_pkpass.git
synced 2025-07-05 12:58:47 +00:00
341 lines
10 KiB
Dart
341 lines
10 KiB
Dart
import 'package:intl/intl.dart';
|
||
import 'package:intl/locale.dart';
|
||
|
||
import 'package:pkpass/pkpass.dart';
|
||
import 'package:pkpass/pkpass/utils/maybe_decode.dart';
|
||
|
||
/// Keys that define the structure of the pass.
|
||
///
|
||
/// These keys are used for all pass styles and partition the fields into the various parts of the pass.
|
||
class PassStructureDictionary {
|
||
/// Fields to be displayed in the header on the front of the pass.
|
||
///
|
||
/// Use header fields sparingly; unlike all other fields, they remain visible when a stack of passes are displayed.
|
||
final List<DictionaryField> headerFields;
|
||
|
||
/// Fields to be displayed prominently on the front of the pass.
|
||
final List<DictionaryField> primaryFields;
|
||
|
||
/// Fields to be displayed on the front of the pass.
|
||
final List<DictionaryField> secondaryFields;
|
||
|
||
/// Fields to be on the back of the pass.
|
||
final List<DictionaryField> backFields;
|
||
|
||
/// Additional fields to be displayed on the front of the pass.
|
||
final List<DictionaryField> auxiliaryFields;
|
||
|
||
/// Required for boarding passes; otherwise not allowed. Type of transit.
|
||
final TransitType? transitType;
|
||
|
||
const PassStructureDictionary({
|
||
this.headerFields = const [],
|
||
this.primaryFields = const [],
|
||
this.secondaryFields = const [],
|
||
this.backFields = const [],
|
||
this.auxiliaryFields = const [],
|
||
this.transitType,
|
||
});
|
||
|
||
factory PassStructureDictionary.fromJson(Map<String, Object?> json) =>
|
||
PassStructureDictionary(
|
||
headerFields: (json['headerFields'] as List?)
|
||
?.map((i) => DictionaryField.fromJson(i))
|
||
.toList() ??
|
||
[],
|
||
primaryFields: (json['primaryFields'] as List?)
|
||
?.map((i) => DictionaryField.fromJson(i))
|
||
.toList() ??
|
||
[],
|
||
secondaryFields: (json['secondaryFields'] as List?)
|
||
?.map((i) => DictionaryField.fromJson(i))
|
||
.toList() ??
|
||
[],
|
||
backFields: (json['backFields'] as List?)
|
||
?.map((i) => DictionaryField.fromJson(i))
|
||
.toList() ??
|
||
[],
|
||
auxiliaryFields: (json['auxiliaryFields'] as List?)
|
||
?.map((i) => DictionaryField.fromJson(i))
|
||
.toList() ??
|
||
[],
|
||
transitType: _TarnsitType.parse(json['transitType'] as String?),
|
||
);
|
||
}
|
||
|
||
/// Information about a field.
|
||
class DictionaryField {
|
||
/// The key must be unique within the scope of the entire pass. For example, “departure-gate.”
|
||
final String key;
|
||
|
||
/// 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 <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.
|
||
final String? label;
|
||
|
||
/// Format string for the alert text that is displayed when the pass is updated. The format string must contain the escape %@, which is replaced with the field’s new value. For example, “Gate changed to %@.”
|
||
///
|
||
/// If you don’t specify a change message, the user isn’t notified when the field changes.
|
||
final String? changeMessage;
|
||
|
||
/// Alignment for the field’s contents.
|
||
final PassTextAlign? textAlignment;
|
||
|
||
/// 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.numberStyle,
|
||
this.currencyCode,
|
||
this.dateStyle,
|
||
this.timeStyle,
|
||
this.ignoresTimeZone = false,
|
||
});
|
||
|
||
factory DictionaryField.fromJson(Map<String, Object?> 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?),
|
||
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].
|
||
String? getLocalizedLabel(PassFile pass, Locale? locale) {
|
||
final localizations = pass.getLocalizations(locale);
|
||
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);
|
||
return localizations?[changeMessage] ?? changeMessage;
|
||
}
|
||
}
|
||
|
||
/// represents the possible values of a [DictionaryField].
|
||
abstract class DictionaryValue {
|
||
const DictionaryValue();
|
||
|
||
/// parses the correct [DictionaryValue] implementor based on a given [value].
|
||
factory DictionaryValue.parse(String value) {
|
||
final number = num.tryParse(value);
|
||
if (number != null) return NumberDictionaryValue(number);
|
||
|
||
final dateTime = DateTime.tryParse(value);
|
||
if (dateTime != null) return DateTimeDictionaryValue(dateTime);
|
||
|
||
return StringDictionaryValue(value);
|
||
}
|
||
}
|
||
|
||
/// [String] content of a [DictionaryField].
|
||
class StringDictionaryValue extends DictionaryValue {
|
||
final String string;
|
||
|
||
const StringDictionaryValue(this.string);
|
||
}
|
||
|
||
/// [DateTime] content of a [DictionaryField].
|
||
class DateTimeDictionaryValue extends DictionaryValue {
|
||
final DateTime dateTime;
|
||
|
||
const DateTimeDictionaryValue(this.dateTime);
|
||
}
|
||
|
||
/// [num] content of a [DictionaryField].
|
||
class NumberDictionaryValue extends DictionaryValue {
|
||
final num number;
|
||
|
||
const NumberDictionaryValue(this.number);
|
||
}
|
||
|
||
/// Possible types of [PassStructureDictionary.transitType].
|
||
enum TransitType {
|
||
/// PKTransitTypeAir
|
||
air,
|
||
|
||
/// PKTransitTypeBoat
|
||
boat,
|
||
|
||
/// PKTransitTypeBus
|
||
bus,
|
||
|
||
/// PKTransitTypeGeneric
|
||
generic,
|
||
|
||
/// PKTransitTypeTrain
|
||
train,
|
||
}
|
||
|
||
/// Possible types of [DictionaryField.textAlignment].
|
||
enum PassTextAlign {
|
||
/// PKTextAlignmentLeft, corresponds `dart:ui` [TextAlign.left]
|
||
left,
|
||
|
||
/// PKTextAlignmentCenter, corresponds `dart:ui` [TextAlign.center]
|
||
center,
|
||
|
||
/// PKTextAlignmentRight, corresponds `dart:ui` [TextAlign.left]
|
||
right,
|
||
|
||
/// PKTextAlignmentNatural, corresponds `dart:ui` [TextAlign.start]
|
||
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;
|
||
switch (type) {
|
||
case 'PKTransitTypeAir':
|
||
return TransitType.air;
|
||
case 'PKTransitTypeBoat':
|
||
return TransitType.boat;
|
||
case 'PKTransitTypeBus':
|
||
return TransitType.bus;
|
||
case 'PKTransitTypeTrain':
|
||
return TransitType.train;
|
||
default:
|
||
return TransitType.generic;
|
||
}
|
||
}
|
||
}
|