From f687622997d0f20fc911e652f444f2bc43a9984b Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sat, 1 Oct 2022 11:30:54 +0200 Subject: [PATCH] info: improved display for XMP --- CHANGELOG.md | 1 + lib/utils/xmp_utils.dart | 9 +- .../viewer/info/metadata/xmp_card.dart | 157 ++++++++++++++++++ .../viewer/info/metadata/xmp_namespaces.dart | 141 +++++++++++----- .../viewer/info/metadata/xmp_ns/crs.dart | 109 +++++------- .../info/metadata/xmp_ns/darktable.dart | 24 --- .../viewer/info/metadata/xmp_ns/dwc.dart | 91 ---------- .../viewer/info/metadata/xmp_ns/google.dart | 19 +-- .../viewer/info/metadata/xmp_ns/iptc.dart | 24 --- .../info/metadata/xmp_ns/iptc4xmpext.dart | 24 --- .../info/metadata/xmp_ns/microsoft.dart | 24 --- .../viewer/info/metadata/xmp_ns/misc.dart | 87 ++++++++++ .../viewer/info/metadata/xmp_ns/mwg.dart | 36 ---- .../info/metadata/xmp_ns/photoshop.dart | 36 +--- .../viewer/info/metadata/xmp_ns/plus.dart | 24 --- .../viewer/info/metadata/xmp_ns/xmp.dart | 114 +++---------- .../viewer/info/metadata/xmp_structs.dart | 148 ----------------- 17 files changed, 426 insertions(+), 642 deletions(-) create mode 100644 lib/widgets/viewer/info/metadata/xmp_card.dart delete mode 100644 lib/widgets/viewer/info/metadata/xmp_ns/darktable.dart delete mode 100644 lib/widgets/viewer/info/metadata/xmp_ns/dwc.dart delete mode 100644 lib/widgets/viewer/info/metadata/xmp_ns/iptc.dart delete mode 100644 lib/widgets/viewer/info/metadata/xmp_ns/iptc4xmpext.dart delete mode 100644 lib/widgets/viewer/info/metadata/xmp_ns/microsoft.dart create mode 100644 lib/widgets/viewer/info/metadata/xmp_ns/misc.dart delete mode 100644 lib/widgets/viewer/info/metadata/xmp_ns/mwg.dart delete mode 100644 lib/widgets/viewer/info/metadata/xmp_ns/plus.dart delete mode 100644 lib/widgets/viewer/info/metadata/xmp_structs.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f0ca1911..abcaf3736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. - reverse filters to filter out/in - Collection: selection edit actions available as quick actions - Albums: group by content type +- Info: improved display for XMP - Stats: top albums - Stats: open full top listings - Slideshow: option for no transition diff --git a/lib/utils/xmp_utils.dart b/lib/utils/xmp_utils.dart index 49860cda7..f594ea82b 100644 --- a/lib/utils/xmp_utils.dart +++ b/lib/utils/xmp_utils.dart @@ -10,6 +10,7 @@ class Namespaces { static const container = 'http://ns.google.com/photos/1.0/container/'; static const creatorAtom = 'http://ns.adobe.com/creatorAtom/1.0/'; static const crd = 'http://ns.adobe.com/camera-raw-defaults/1.0/'; + static const crlcp = 'http://ns.adobe.com/camera-raw-embedded-lens-profile/1.0/'; static const crs = 'http://ns.adobe.com/camera-raw-settings/1.0/'; static const crss = 'http://ns.adobe.com/camera-raw-saved-settings/1.0/'; static const darktable = 'http://darktable.sf.net/'; @@ -30,7 +31,8 @@ class Namespaces { static const gettyImagesGift = 'http://xmp.gettyimages.com/gift/1.0/'; static const gFocus = 'http://ns.google.com/photos/1.0/focus/'; static const gImage = 'http://ns.google.com/photos/1.0/image/'; - static const gimp = 'http://www.gimp.org/ns/2.10/'; + static const gimp210 = 'http://www.gimp.org/ns/2.10/'; + static const gimpXmp = 'http://www.gimp.org/xmp/'; static const gPano = 'http://ns.google.com/photos/1.0/panorama/'; static const gSpherical = 'http://ns.google.com/videos/1.0/spherical/'; static const illustrator = 'http://ns.adobe.com/illustrator/1.0/'; @@ -56,6 +58,7 @@ class Namespaces { static const plus = 'http://ns.useplus.org/ldf/xmp/1.0/'; static const pmtm = 'http://www.hdrsoft.com/photomatix_settings01'; static const rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + static const stCamera = 'http://ns.adobe.com/photoshop/1.0/camera-profile'; static const stEvt = 'http://ns.adobe.com/xap/1.0/sType/ResourceEvent#'; static const stRef = 'http://ns.adobe.com/xap/1.0/sType/ResourceRef#'; static const tiff = 'http://ns.adobe.com/tiff/1.0/'; @@ -96,7 +99,8 @@ class Namespaces { gDepth: 'Google Depth', gFocus: 'Google Focus', gImage: 'Google Image', - gimp: 'GIMP', + gimp210: 'GIMP 2.10', + gimpXmp: 'GIMP', gPano: 'Google Panorama', gSpherical: 'Google Spherical', illustrator: 'Illustrator', @@ -122,6 +126,7 @@ class Namespaces { xmpBJ: 'Basic Job Ticket', xmpDM: 'Dynamic Media', xmpMM: 'Media Management', + xmpNote: 'Note', xmpRights: 'Rights Management', xmpTPg: 'Paged-Text', }; diff --git a/lib/widgets/viewer/info/metadata/xmp_card.dart b/lib/widgets/viewer/info/metadata/xmp_card.dart new file mode 100644 index 000000000..5afd950b0 --- /dev/null +++ b/lib/widgets/viewer/info/metadata/xmp_card.dart @@ -0,0 +1,157 @@ +import 'dart:math'; + +import 'package:aves/theme/durations.dart'; +import 'package:aves/theme/icons.dart'; +import 'package:aves/utils/constants.dart'; +import 'package:aves/widgets/common/basic/multi_cross_fader.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/identity/highlight_title.dart'; +import 'package:aves/widgets/viewer/info/common.dart'; +import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:tuple/tuple.dart'; + +typedef XmpExtractedCard = Tuple2, List?>; + +class XmpCard extends StatefulWidget { + final String title; + late final XmpExtractedCard? directStruct; + late final List? indexedStructs; + final String Function(XmpProp prop) formatValue; + final Map Function(int? index)? spanBuilders; + + XmpCard({ + super.key, + required this.title, + required Map structByIndex, + required this.formatValue, + this.spanBuilders, + }) { + directStruct = structByIndex[null]; + + final length = structByIndex.keys.whereNotNull().fold(0, max); + indexedStructs = length > 0 ? [for (var i = 0; i < length; i++) structByIndex[i + 1] ?? const Tuple2({}, null)] : null; + } + + @override + State createState() => _XmpCardState(); +} + +class _XmpCardState extends State { + final ValueNotifier _indexNotifier = ValueNotifier(0); + + List? get indexedStructs => widget.indexedStructs; + + bool get isIndexed => indexedStructs != null; + + int get indexedStructCount => indexedStructs?.length ?? 0; + + @override + void initState() { + super.initState(); + if (isIndexed) { + _indexNotifier.value = indexedStructCount - 1; + } + } + + @override + void didUpdateWidget(covariant XmpCard oldWidget) { + super.didUpdateWidget(oldWidget); + if (_indexNotifier.value >= indexedStructCount) { + _indexNotifier.value = indexedStructCount - 1; + } + } + + @override + Widget build(BuildContext context) { + final _isIndexed = isIndexed; + + return Container( + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.onPrimary.withOpacity(.2), + ), + borderRadius: const BorderRadius.all(Radius.circular(4)), + ), + child: ValueListenableBuilder( + valueListenable: _indexNotifier, + builder: (context, index, child) { + final data = _isIndexed ? indexedStructs![index] : widget.directStruct!; + final props = data.item1.entries.map((kv) => XmpProp(kv.key, kv.value.value)).toList()..sort(); + final cards = data.item2; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 8, top: 8, right: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: HighlightTitle( + title: widget.title, + selectable: true, + showHighlight: false, + ), + ), + if (_isIndexed) ...[ + IconButton( + visualDensity: VisualDensity.compact, + icon: const Icon(AIcons.previous), + onPressed: index > 0 ? () => _setIndex(index - 1) : null, + tooltip: context.l10n.previousTooltip, + ), + HighlightTitle( + title: '${index + 1}', + showHighlight: false, + ), + IconButton( + visualDensity: VisualDensity.compact, + icon: const Icon(AIcons.next), + onPressed: index < indexedStructCount - 1 ? () => _setIndex(index + 1) : null, + tooltip: context.l10n.nextTooltip, + ), + ] + ], + ), + ), + MultiCrossFader( + duration: Durations.xmpStructArrayCardTransition, + sizeCurve: Curves.easeOutBack, + alignment: AlignmentDirectional.topStart, + child: Padding( + // add padding at this level (instead of the column level) + // so that the crossfader can animate the content size + // without clipping the text + padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8), + child: InfoRowGroup( + info: Map.fromEntries(props.map((prop) => MapEntry(prop.displayKey, widget.formatValue(prop)))), + maxValueLength: Constants.infoGroupMaxValueLength, + spanBuilders: widget.spanBuilders?.call(_isIndexed ? index + 1 : null), + ), + ), + ), + if (cards != null) + ...cards.where((v) => !v.isEmpty).map((card) { + final spanBuilders = card.spanBuilders; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + child: XmpCard( + title: card.title, + structByIndex: card.data, + formatValue: widget.formatValue, + spanBuilders: spanBuilders != null ? (index) => spanBuilders(index, card.data[index]!.item1) : null, + ), + ); + }), + ], + ); + }, + ), + ); + } + + void _setIndex(int index) => _indexNotifier.value = index.clamp(0, indexedStructCount - 1); +} diff --git a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart index a141424bc..2351bfa0a 100644 --- a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart +++ b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart @@ -5,17 +5,12 @@ import 'package:aves/utils/string_utils.dart'; import 'package:aves/utils/xmp_utils.dart'; import 'package:aves/widgets/common/identity/highlight_title.dart'; import 'package:aves/widgets/viewer/info/common.dart'; +import 'package:aves/widgets/viewer/info/metadata/xmp_card.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/crs.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_ns/darktable.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_ns/dwc.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/exif.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/google.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_ns/iptc.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_ns/iptc4xmpext.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_ns/microsoft.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_ns/mwg.dart'; +import 'package:aves/widgets/viewer/info/metadata/xmp_ns/misc.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/photoshop.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_ns/plus.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/tiff.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/xmp.dart'; import 'package:collection/collection.dart'; @@ -23,6 +18,7 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; @immutable class XmpNamespace extends Equatable { @@ -70,8 +66,6 @@ class XmpNamespace extends Equatable { return XmpBasicNamespace(nsPrefix, rawProps); case Namespaces.xmpMM: return XmpMMNamespace(nsPrefix, rawProps); - case Namespaces.xmpNote: - return XmpNoteNamespace(nsPrefix, rawProps); default: return XmpNamespace(nsUri, nsPrefix, rawProps); } @@ -79,26 +73,37 @@ class XmpNamespace extends Equatable { String get displayTitle => Namespaces.nsTitles[nsUri] ?? (nsPrefix.isEmpty ? nsUri : '${nsPrefix.substring(0, nsPrefix.length - 1)} ($nsUri)'); - Map get buildProps => rawProps; - List buildNamespaceSection(BuildContext context) { - final props = buildProps.entries + final props = rawProps.entries .map((kv) { final prop = XmpProp(kv.key, kv.value); - return extractData(prop) ? null : prop; + var extracted = false; + cards.forEach((card) => extracted |= card.extract(prop)); + return extracted ? null : prop; }) .whereNotNull() .toList() - ..sort((a, b) => compareAsciiUpperCaseNatural(a.displayKey, b.displayKey)); + ..sort(); final content = [ if (props.isNotEmpty) InfoRowGroup( - info: Map.fromEntries(props.map((prop) => MapEntry(prop.displayKey, formatValue(prop)))), + info: Map.fromEntries(props.map((v) => MapEntry(v.displayKey, formatValue(v)))), maxValueLength: Constants.infoGroupMaxValueLength, spanBuilders: linkifyValues(props), ), - ...buildFromExtractedData(), + ...cards.where((v) => !v.isEmpty).map((card) { + final spanBuilders = card.spanBuilders; + return Padding( + padding: const EdgeInsets.only(top: 8), + child: XmpCard( + title: card.title, + structByIndex: card.data, + formatValue: formatValue, + spanBuilders: spanBuilders != null ? (index) => spanBuilders(index, card.data[index]!.item1) : null, + ), + ); + }), ]; return content.isNotEmpty @@ -117,38 +122,14 @@ class XmpNamespace extends Equatable { : []; } - bool extractStruct(XmpProp prop, RegExp pattern, Map store) { - final matches = pattern.allMatches(prop.path); - if (matches.isEmpty) return false; - - final match = matches.first; - final field = match.group(1)!; - store[field] = formatValue(prop); - return true; - } - - bool extractIndexedStruct(XmpProp prop, RegExp pattern, Map> store) { - final matches = pattern.allMatches(prop.path); - if (matches.isEmpty) return false; - - final match = matches.first; - final index = int.parse(match.group(1)!); - final field = match.group(2)!; - final fields = store.putIfAbsent(index, () => {}); - fields[field] = formatValue(prop); - return true; - } - - bool extractData(XmpProp prop) => false; - - List buildFromExtractedData() => []; + List get cards => []; String formatValue(XmpProp prop) => prop.value; Map linkifyValues(List props) => {}; } -class XmpProp { +class XmpProp implements Comparable { final String path, value; final String displayKey; @@ -165,6 +146,82 @@ class XmpProp { }); } + @override + int compareTo(XmpProp other) => compareAsciiUpperCaseNatural(displayKey, other.displayKey); + @override String toString() => '$runtimeType#${shortHash(this)}{path=$path, value=$value}'; } + +class XmpCardData { + final String title; + final RegExp pattern; + final bool indexed; + final Map Function(int?, Map data)? spanBuilders; + final List? cards; + final Map data = {}; + + bool get isEmpty => data.isEmpty && (cards?.every((card) => card.isEmpty) ?? true); + + static final titlePattern = RegExp(r'(.*?)[\\/]'); + + XmpCardData( + this.pattern, { + String? title, + this.spanBuilders, + this.cards, + }) : indexed = pattern.pattern.contains(r'\[(\d+)\]'), + title = title ?? XmpProp.formatKey(titlePattern.firstMatch(pattern.pattern)!.group(1)!); + + XmpCardData cloneEmpty() { + return XmpCardData( + pattern, + title: title, + spanBuilders: spanBuilders, + cards: cards?.map((v) => v.cloneEmpty()).toList(), + ); + } + + bool extract(XmpProp prop) => indexed ? _extractIndexedStruct(prop) : _extractDirectStruct(prop); + + bool _extractDirectStruct(XmpProp prop) { + final matches = pattern.allMatches(prop.path); + if (matches.isEmpty) return false; + + final match = matches.first; + final field = match.group(1)!; + + final fields = data.putIfAbsent(null, () => Tuple2({}, cards?.map((v) => v.cloneEmpty()).toList())); + final _cards = fields.item2; + if (_cards != null) { + final fieldProp = XmpProp(field, prop.value); + if (_cards.any((v) => v.extract(fieldProp))) { + return true; + } + } + + fields.item1[field] = prop; + return true; + } + + bool _extractIndexedStruct(XmpProp prop) { + final matches = pattern.allMatches(prop.path); + if (matches.isEmpty) return false; + + final match = matches.first; + final index = int.parse(match.group(1)!); + final field = match.group(2)!; + + final fields = data.putIfAbsent(index, () => Tuple2({}, cards?.map((v) => v.cloneEmpty()).toList())); + final _cards = fields.item2; + if (_cards != null) { + final fieldProp = XmpProp(field, prop.value); + if (_cards.any((v) => v.extract(fieldProp))) { + return true; + } + } + + fields.item1[field] = prop; + return true; + } +} diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/crs.dart b/lib/widgets/viewer/info/metadata/xmp_ns/crs.dart index 40933671f..adc24490e 100644 --- a/lib/widgets/viewer/info/metadata/xmp_ns/crs.dart +++ b/lib/widgets/viewer/info/metadata/xmp_ns/crs.dart @@ -1,75 +1,50 @@ import 'package:aves/utils/xmp_utils.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_structs.dart'; -import 'package:flutter/widgets.dart'; class XmpCrsNamespace extends XmpNamespace { - late final cgbcPattern = RegExp(nsPrefix + r'CircularGradientBasedCorrections\[(\d+)\]/(.*)'); - late final gbcPattern = RegExp(nsPrefix + r'GradientBasedCorrections\[(\d+)\]/(.*)'); - late final mgbcPattern = RegExp(nsPrefix + r'MaskGroupBasedCorrections\[(\d+)\]/(.*)'); - late final pbcPattern = RegExp(nsPrefix + r'PaintBasedCorrections\[(\d+)\]/(.*)'); - late final retouchAreasPattern = RegExp(nsPrefix + r'RetouchAreas\[(\d+)\]/(.*)'); - late final lookPattern = RegExp(nsPrefix + r'Look/(.*)'); - late final rmmiPattern = RegExp(nsPrefix + r'RangeMaskMapInfo/' + nsPrefix + r'RangeMaskMapInfo/(.*)'); - - final cgbc = >{}; - final gbc = >{}; - final mgbc = >{}; - final pbc = >{}; - final retouchAreas = >{}; - final look = {}; - final rmmi = {}; - XmpCrsNamespace(String nsPrefix, Map rawProps) : super(Namespaces.crs, nsPrefix, rawProps); @override - bool extractData(XmpProp prop) { - var hasStructs = extractStruct(prop, lookPattern, look); - hasStructs |= extractStruct(prop, rmmiPattern, rmmi); - var hasIndexedStructs = extractIndexedStruct(prop, cgbcPattern, cgbc); - hasIndexedStructs |= extractIndexedStruct(prop, gbcPattern, gbc); - hasIndexedStructs |= extractIndexedStruct(prop, mgbcPattern, mgbc); - hasIndexedStructs |= extractIndexedStruct(prop, pbcPattern, pbc); - hasIndexedStructs |= extractIndexedStruct(prop, retouchAreasPattern, retouchAreas); - return hasStructs || hasIndexedStructs; - } - - @override - List buildFromExtractedData() => [ - if (cgbc.isNotEmpty) - XmpStructArrayCard( - title: 'Circular Gradient Based Corrections', - structByIndex: cgbc, - ), - if (gbc.isNotEmpty) - XmpStructArrayCard( - title: 'Gradient Based Corrections', - structByIndex: gbc, - ), - if (look.isNotEmpty) - XmpStructCard( - title: 'Look', - struct: look, - ), - if (mgbc.isNotEmpty) - XmpStructArrayCard( - title: 'Mask Group Based Corrections', - structByIndex: mgbc, - ), - if (pbc.isNotEmpty) - XmpStructArrayCard( - title: 'Paint Based Corrections', - structByIndex: pbc, - ), - if (rmmi.isNotEmpty) - XmpStructCard( - title: 'Range Mask Map Info', - struct: rmmi, - ), - if (retouchAreas.isNotEmpty) - XmpStructArrayCard( - title: 'Retouch Areas', - structByIndex: retouchAreas, - ), - ]; + late final List cards = [ + XmpCardData(RegExp(nsPrefix + r'CircularGradientBasedCorrections\[(\d+)\]/(.*)')), + XmpCardData( + RegExp(nsPrefix + r'GradientBasedCorrections\[(\d+)\]/(.*)'), + cards: [ + XmpCardData(RegExp(nsPrefix + r'CorrectionMasks\[(\d+)\]/(.*)')), + XmpCardData(RegExp(nsPrefix + r'CorrectionRangeMask/(.*)')), + ], + ), + XmpCardData( + RegExp(nsPrefix + r'Look/(.*)'), + cards: [ + XmpCardData(RegExp(nsPrefix + r'Parameters/(.*)')), + ], + ), + XmpCardData( + RegExp(nsPrefix + r'MaskGroupBasedCorrections\[(\d+)\]/(.*)'), + cards: [ + XmpCardData( + RegExp(nsPrefix + r'CorrectionMasks\[(\d+)\]/(.*)'), + cards: [ + XmpCardData(RegExp(nsPrefix + r'CorrectionRangeMask/(.*)')), + XmpCardData(RegExp(nsPrefix + r'Masks\[(\d+)\]/(.*)')), + ], + ), + ], + ), + XmpCardData( + RegExp(nsPrefix + r'PaintBasedCorrections\[(\d+)\]/(.*)'), + cards: [ + XmpCardData(RegExp(nsPrefix + r'CorrectionMasks\[(\d+)\]/(.*)')), + XmpCardData(RegExp(nsPrefix + r'CorrectionRangeMask/(.*)')), + ], + ), + XmpCardData( + RegExp(nsPrefix + r'RetouchAreas\[(\d+)\]/(.*)'), + cards: [ + XmpCardData(RegExp(nsPrefix + r'Masks\[(\d+)\]/(.*)')), + ], + ), + XmpCardData(RegExp(nsPrefix + r'RangeMaskMapInfo/' + nsPrefix + r'RangeMaskMapInfo/(.*)')), + ]; } diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/darktable.dart b/lib/widgets/viewer/info/metadata/xmp_ns/darktable.dart deleted file mode 100644 index e47f67b4e..000000000 --- a/lib/widgets/viewer/info/metadata/xmp_ns/darktable.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:aves/utils/xmp_utils.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_structs.dart'; -import 'package:flutter/material.dart'; - -class XmpDarktableNamespace extends XmpNamespace { - late final historyPattern = RegExp(nsPrefix + r'history\[(\d+)\]/(.*)'); - - final history = >{}; - - XmpDarktableNamespace(String nsPrefix, Map rawProps) : super(Namespaces.darktable, nsPrefix, rawProps); - - @override - bool extractData(XmpProp prop) => extractIndexedStruct(prop, historyPattern, history); - - @override - List buildFromExtractedData() => [ - if (history.isNotEmpty) - XmpStructArrayCard( - title: 'History', - structByIndex: history, - ), - ]; -} diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/dwc.dart b/lib/widgets/viewer/info/metadata/xmp_ns/dwc.dart deleted file mode 100644 index ad5fe4212..000000000 --- a/lib/widgets/viewer/info/metadata/xmp_ns/dwc.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'package:aves/utils/xmp_utils.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_structs.dart'; -import 'package:flutter/widgets.dart'; - -class XmpDwcNamespace extends XmpNamespace { - late final dcTermsLocationPattern = RegExp(nsPrefix + r'dctermsLocation/(.*)'); - late final eventPattern = RegExp(nsPrefix + r'Event/(.*)'); - late final geologicalContextPattern = RegExp(nsPrefix + r'GeologicalContext/(.*)'); - late final identificationPattern = RegExp(nsPrefix + r'Identification/(.*)'); - late final measurementOrFactPattern = RegExp(nsPrefix + r'MeasurementOrFact/(.*)'); - late final occurrencePattern = RegExp(nsPrefix + r'Occurrence/(.*)'); - late final recordPattern = RegExp(nsPrefix + r'Record/(.*)'); - late final resourceRelationshipPattern = RegExp(nsPrefix + r'ResourceRelationship/(.*)'); - late final taxonPattern = RegExp(nsPrefix + r'Taxon/(.*)'); - - final dcTermsLocation = {}; - final event = {}; - final identification = {}; - final geologicalContext = {}; - final measurementOrFact = {}; - final occurrence = {}; - final record = {}; - final resourceRelationship = {}; - final taxon = {}; - - XmpDwcNamespace(String nsPrefix, Map rawProps) : super(Namespaces.dwc, nsPrefix, rawProps); - - @override - bool extractData(XmpProp prop) { - var hasStructs = extractStruct(prop, dcTermsLocationPattern, dcTermsLocation); - hasStructs |= extractStruct(prop, eventPattern, event); - hasStructs |= extractStruct(prop, geologicalContextPattern, geologicalContext); - hasStructs |= extractStruct(prop, measurementOrFactPattern, measurementOrFact); - hasStructs |= extractStruct(prop, identificationPattern, identification); - hasStructs |= extractStruct(prop, occurrencePattern, occurrence); - hasStructs |= extractStruct(prop, recordPattern, record); - hasStructs |= extractStruct(prop, resourceRelationshipPattern, resourceRelationship); - hasStructs |= extractStruct(prop, taxonPattern, taxon); - return hasStructs; - } - - @override - List buildFromExtractedData() => [ - if (dcTermsLocation.isNotEmpty) - XmpStructCard( - title: 'DC Terms Location', - struct: dcTermsLocation, - ), - if (event.isNotEmpty) - XmpStructCard( - title: 'Event', - struct: event, - ), - if (geologicalContext.isNotEmpty) - XmpStructCard( - title: 'Geological Context', - struct: geologicalContext, - ), - if (identification.isNotEmpty) - XmpStructCard( - title: 'Identification', - struct: identification, - ), - if (measurementOrFact.isNotEmpty) - XmpStructCard( - title: 'Measurement Or Fact', - struct: measurementOrFact, - ), - if (occurrence.isNotEmpty) - XmpStructCard( - title: 'Occurrence', - struct: occurrence, - ), - if (record.isNotEmpty) - XmpStructCard( - title: 'Record', - struct: record, - ), - if (resourceRelationship.isNotEmpty) - XmpStructCard( - title: 'Resource Relationship', - struct: resourceRelationship, - ), - if (taxon.isNotEmpty) - XmpStructCard( - title: 'Taxon', - struct: taxon, - ), - ]; -} diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/google.dart b/lib/widgets/viewer/info/metadata/xmp_ns/google.dart index 9a3270386..419d43fd4 100644 --- a/lib/widgets/viewer/info/metadata/xmp_ns/google.dart +++ b/lib/widgets/viewer/info/metadata/xmp_ns/google.dart @@ -3,9 +3,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/viewer/embedded/notifications.dart'; import 'package:aves/widgets/viewer/info/common.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_structs.dart'; import 'package:collection/collection.dart'; -import 'package:flutter/widgets.dart'; import 'package:tuple/tuple.dart'; abstract class XmpGoogleNamespace extends XmpNamespace { @@ -79,21 +77,10 @@ class XmpGImageNamespace extends XmpGoogleNamespace { } class XmpContainer extends XmpNamespace { - late final directoryPattern = RegExp('${nsPrefix}Directory\\[(\\d+)\\]/${nsPrefix}Item/(.*)'); - - final directories = >{}; - XmpContainer(String nsPrefix, Map rawProps) : super(Namespaces.container, nsPrefix, rawProps); @override - bool extractData(XmpProp prop) => extractIndexedStruct(prop, directoryPattern, directories); - - @override - List buildFromExtractedData() => [ - if (directories.isNotEmpty) - XmpStructArrayCard( - title: 'Directory Item', - structByIndex: directories, - ), - ]; + late final List cards = [ + XmpCardData(RegExp('${nsPrefix}Directory\\[(\\d+)\\]/${nsPrefix}Item/(.*)'), title: 'Directory Item'), + ]; } diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/iptc.dart b/lib/widgets/viewer/info/metadata/xmp_ns/iptc.dart deleted file mode 100644 index 167069b2a..000000000 --- a/lib/widgets/viewer/info/metadata/xmp_ns/iptc.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:aves/utils/xmp_utils.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_structs.dart'; -import 'package:flutter/material.dart'; - -class XmpIptcCoreNamespace extends XmpNamespace { - late final creatorContactInfoPattern = RegExp(nsPrefix + r'CreatorContactInfo/(.*)'); - - final creatorContactInfo = {}; - - XmpIptcCoreNamespace(String nsPrefix, Map rawProps) : super(Namespaces.iptc4xmpCore, nsPrefix, rawProps); - - @override - bool extractData(XmpProp prop) => extractStruct(prop, creatorContactInfoPattern, creatorContactInfo); - - @override - List buildFromExtractedData() => [ - if (creatorContactInfo.isNotEmpty) - XmpStructCard( - title: 'Creator Contact Info', - struct: creatorContactInfo, - ), - ]; -} diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/iptc4xmpext.dart b/lib/widgets/viewer/info/metadata/xmp_ns/iptc4xmpext.dart deleted file mode 100644 index 9d72e8952..000000000 --- a/lib/widgets/viewer/info/metadata/xmp_ns/iptc4xmpext.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:aves/utils/xmp_utils.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_structs.dart'; -import 'package:flutter/material.dart'; - -class XmpIptc4xmpExtNamespace extends XmpNamespace { - late final aooPattern = RegExp(nsPrefix + r'ArtworkOrObject\[(\d+)\]/(.*)'); - - final aoo = >{}; - - XmpIptc4xmpExtNamespace(String nsPrefix, Map rawProps) : super(Namespaces.iptc4xmpExt, nsPrefix, rawProps); - - @override - bool extractData(XmpProp prop) => extractIndexedStruct(prop, aooPattern, aoo); - - @override - List buildFromExtractedData() => [ - if (aoo.isNotEmpty) - XmpStructArrayCard( - title: 'Artwork or Object', - structByIndex: aoo, - ), - ]; -} diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/microsoft.dart b/lib/widgets/viewer/info/metadata/xmp_ns/microsoft.dart deleted file mode 100644 index af5d1b9cc..000000000 --- a/lib/widgets/viewer/info/metadata/xmp_ns/microsoft.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:aves/utils/xmp_utils.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_structs.dart'; -import 'package:flutter/widgets.dart'; - -class XmpMPNamespace extends XmpNamespace { - late final regionListPattern = RegExp(nsPrefix + r'RegionInfo/MPRI:Regions\[(\d+)\]/(.*)'); - - final regionList = >{}; - - XmpMPNamespace(String nsPrefix, Map rawProps) : super(Namespaces.mp, nsPrefix, rawProps); - - @override - bool extractData(XmpProp prop) => extractIndexedStruct(prop, regionListPattern, regionList); - - @override - List buildFromExtractedData() => [ - if (regionList.isNotEmpty) - XmpStructArrayCard( - title: 'Regions', - structByIndex: regionList, - ), - ]; -} diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/misc.dart b/lib/widgets/viewer/info/metadata/xmp_ns/misc.dart new file mode 100644 index 000000000..e1e0aa292 --- /dev/null +++ b/lib/widgets/viewer/info/metadata/xmp_ns/misc.dart @@ -0,0 +1,87 @@ +import 'package:aves/utils/xmp_utils.dart'; +import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; + +class XmpDarktableNamespace extends XmpNamespace { + XmpDarktableNamespace(String nsPrefix, Map rawProps) : super(Namespaces.darktable, nsPrefix, rawProps); + + @override + late final List cards = [ + XmpCardData(RegExp(nsPrefix + r'history\[(\d+)\]/(.*)')), + ]; +} + +class XmpDwcNamespace extends XmpNamespace { + XmpDwcNamespace(String nsPrefix, Map rawProps) : super(Namespaces.dwc, nsPrefix, rawProps); + + @override + late final List cards = [ + XmpCardData(RegExp(nsPrefix + r'dctermsLocation/(.*)'), title: 'DC Terms Location'), + XmpCardData(RegExp(nsPrefix + r'Event/(.*)')), + XmpCardData(RegExp(nsPrefix + r'GeologicalContext/(.*)')), + XmpCardData(RegExp(nsPrefix + r'Identification/(.*)')), + XmpCardData(RegExp(nsPrefix + r'MeasurementOrFact/(.*)')), + XmpCardData(RegExp(nsPrefix + r'Occurrence/(.*)')), + XmpCardData(RegExp(nsPrefix + r'Record/(.*)')), + XmpCardData(RegExp(nsPrefix + r'ResourceRelationship/(.*)')), + XmpCardData(RegExp(nsPrefix + r'Taxon/(.*)')), + ]; +} + +class XmpIptcCoreNamespace extends XmpNamespace { + XmpIptcCoreNamespace(String nsPrefix, Map rawProps) : super(Namespaces.iptc4xmpCore, nsPrefix, rawProps); + + @override + late final List cards = [ + XmpCardData(RegExp(nsPrefix + r'CreatorContactInfo/(.*)')), + ]; +} + +class XmpIptc4xmpExtNamespace extends XmpNamespace { + XmpIptc4xmpExtNamespace(String nsPrefix, Map rawProps) : super(Namespaces.iptc4xmpExt, nsPrefix, rawProps); + + @override + late final List cards = [ + XmpCardData(RegExp(nsPrefix + r'ArtworkOrObject\[(\d+)\]/(.*)')), + ]; +} + +class XmpMPNamespace extends XmpNamespace { + XmpMPNamespace(String nsPrefix, Map rawProps) : super(Namespaces.mp, nsPrefix, rawProps); + + @override + late final List cards = [ + XmpCardData(RegExp(nsPrefix + r'RegionInfo/MPRI:Regions\[(\d+)\]/(.*)'), title: 'Regions'), + ]; +} + +// cf www.metadataworkinggroup.org/pdf/mwg_guidance.pdf (down, as of 2021/02/15) +class XmpMgwRegionsNamespace extends XmpNamespace { + XmpMgwRegionsNamespace(String nsPrefix, Map rawProps) : super(Namespaces.mwgrs, nsPrefix, rawProps); + + @override + late final List cards = [ + XmpCardData(RegExp(nsPrefix + r'Regions/mwg-rs:AppliedToDimensions/(.*)'), title: 'Applied to Dimensions'), + XmpCardData(RegExp(nsPrefix + r'Regions/mwg-rs:RegionList\[(\d+)\]/(.*)'), title: 'Region List'), + ]; +} + +class XmpPlusNamespace extends XmpNamespace { + XmpPlusNamespace(String nsPrefix, Map rawProps) : super(Namespaces.plus, nsPrefix, rawProps); + + @override + late final List cards = [ + XmpCardData(RegExp(nsPrefix + r'Licensor\[(\d+)\]/(.*)')), + ]; +} + +class XmpMMNamespace extends XmpNamespace { + XmpMMNamespace(String nsPrefix, Map rawProps) : super(Namespaces.xmpMM, nsPrefix, rawProps); + + @override + late final List cards = [ + XmpCardData(RegExp(nsPrefix + r'DerivedFrom/(.*)')), + XmpCardData(RegExp(nsPrefix + r'History\[(\d+)\]/(.*)')), + XmpCardData(RegExp(nsPrefix + r'Ingredients\[(\d+)\]/(.*)')), + XmpCardData(RegExp(nsPrefix + r'Pantry\[(\d+)\]/(.*)')), + ]; +} diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/mwg.dart b/lib/widgets/viewer/info/metadata/xmp_ns/mwg.dart deleted file mode 100644 index 3c74449f6..000000000 --- a/lib/widgets/viewer/info/metadata/xmp_ns/mwg.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:aves/utils/xmp_utils.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_structs.dart'; -import 'package:flutter/widgets.dart'; - -// cf www.metadataworkinggroup.org/pdf/mwg_guidance.pdf (down, as of 2021/02/15) -class XmpMgwRegionsNamespace extends XmpNamespace { - late final dimensionsPattern = RegExp(nsPrefix + r'Regions/mwg-rs:AppliedToDimensions/(.*)'); - late final regionListPattern = RegExp(nsPrefix + r'Regions/mwg-rs:RegionList\[(\d+)\]/(.*)'); - - final dimensions = {}; - final regionList = >{}; - - XmpMgwRegionsNamespace(String nsPrefix, Map rawProps) : super(Namespaces.mwgrs, nsPrefix, rawProps); - - @override - bool extractData(XmpProp prop) { - final hasStructs = extractStruct(prop, dimensionsPattern, dimensions); - final hasIndexedStructs = extractIndexedStruct(prop, regionListPattern, regionList); - return hasStructs || hasIndexedStructs; - } - - @override - List buildFromExtractedData() => [ - if (dimensions.isNotEmpty) - XmpStructCard( - title: 'Applied To Dimensions', - struct: dimensions, - ), - if (regionList.isNotEmpty) - XmpStructArrayCard( - title: 'Region', - structByIndex: regionList, - ), - ]; -} diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/photoshop.dart b/lib/widgets/viewer/info/metadata/xmp_ns/photoshop.dart index a91008fa4..e6a749ec7 100644 --- a/lib/widgets/viewer/info/metadata/xmp_ns/photoshop.dart +++ b/lib/widgets/viewer/info/metadata/xmp_ns/photoshop.dart @@ -1,38 +1,20 @@ import 'package:aves/utils/xmp_utils.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_structs.dart'; -import 'package:flutter/widgets.dart'; // cf https://github.com/adobe/xmp-docs/blob/master/XMPNamespaces/photoshop.md class XmpPhotoshopNamespace extends XmpNamespace { - late final cameraProfilesPattern = RegExp(nsPrefix + r'CameraProfiles\[(\d+)\]/(.*)'); - late final textLayersPattern = RegExp(nsPrefix + r'TextLayers\[(\d+)\]/(.*)'); - - final cameraProfiles = >{}; - final textLayers = >{}; - XmpPhotoshopNamespace(String nsPrefix, Map rawProps) : super(Namespaces.photoshop, nsPrefix, rawProps); @override - bool extractData(XmpProp prop) { - var hasIndexedStructs = extractIndexedStruct(prop, cameraProfilesPattern, cameraProfiles); - hasIndexedStructs |= extractIndexedStruct(prop, textLayersPattern, textLayers); - return hasIndexedStructs; - } - - @override - List buildFromExtractedData() => [ - if (cameraProfiles.isNotEmpty) - XmpStructArrayCard( - title: 'Camera Profiles', - structByIndex: cameraProfiles, - ), - if (textLayers.isNotEmpty) - XmpStructArrayCard( - title: 'Text Layers', - structByIndex: textLayers, - ), - ]; + late final List cards = [ + XmpCardData( + RegExp(nsPrefix + r'CameraProfiles\[(\d+)\]/(.*)'), + cards: [ + XmpCardData(RegExp(r'crlcp:PerspectiveModel/(.*)')), + ], + ), + XmpCardData(RegExp(nsPrefix + r'TextLayers\[(\d+)\]/(.*)')), + ]; @override String formatValue(XmpProp prop) { diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/plus.dart b/lib/widgets/viewer/info/metadata/xmp_ns/plus.dart deleted file mode 100644 index 34ab0862e..000000000 --- a/lib/widgets/viewer/info/metadata/xmp_ns/plus.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:aves/utils/xmp_utils.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_structs.dart'; -import 'package:flutter/material.dart'; - -class XmpPlusNamespace extends XmpNamespace { - late final licensorPattern = RegExp(nsPrefix + r'Licensor\[(\d+)\]/(.*)'); - - final licensor = >{}; - - XmpPlusNamespace(String nsPrefix, Map rawProps) : super(Namespaces.plus, nsPrefix, rawProps); - - @override - bool extractData(XmpProp prop) => extractIndexedStruct(prop, licensorPattern, licensor); - - @override - List buildFromExtractedData() => [ - if (licensor.isNotEmpty) - XmpStructArrayCard( - title: 'Licensor', - structByIndex: licensor, - ), - ]; -} diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/xmp.dart b/lib/widgets/viewer/info/metadata/xmp_ns/xmp.dart index 042037017..fceb37783 100644 --- a/lib/widgets/viewer/info/metadata/xmp_ns/xmp.dart +++ b/lib/widgets/viewer/info/metadata/xmp_ns/xmp.dart @@ -4,102 +4,30 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/viewer/embedded/notifications.dart'; import 'package:aves/widgets/viewer/info/common.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_structs.dart'; -import 'package:flutter/material.dart'; class XmpBasicNamespace extends XmpNamespace { - late final thumbnailsPattern = RegExp(nsPrefix + r'Thumbnails\[(\d+)\]/(.*)'); - static const thumbnailDataDisplayKey = 'Image'; - - final thumbnails = >{}; - XmpBasicNamespace(String nsPrefix, Map rawProps) : super(Namespaces.xmp, nsPrefix, rawProps); @override - bool extractData(XmpProp prop) => extractIndexedStruct(prop, thumbnailsPattern, thumbnails); - - @override - List buildFromExtractedData() => [ - if (thumbnails.isNotEmpty) - XmpStructArrayCard( - title: 'Thumbnail', - structByIndex: thumbnails, - linkifier: (index) { - final struct = thumbnails[index]!; - return { - if (struct.containsKey(thumbnailDataDisplayKey)) - thumbnailDataDisplayKey: InfoRowGroup.linkSpanBuilder( - linkText: (context) => context.l10n.viewerInfoOpenLinkText, - onTap: (context) => OpenEmbeddedDataNotification.xmp( - props: [ - const [Namespaces.xmp, 'Thumbnails'], - index, - const [Namespaces.xmpGImg, 'image'], - ], - mimeType: MimeTypes.jpeg, - ).dispatch(context), - ), - }; - }, - ) - ]; -} - -class XmpMMNamespace extends XmpNamespace { - late final derivedFromPattern = RegExp(nsPrefix + r'DerivedFrom/(.*)'); - late final historyPattern = RegExp(nsPrefix + r'History\[(\d+)\]/(.*)'); - late final ingredientsPattern = RegExp(nsPrefix + r'Ingredients\[(\d+)\]/(.*)'); - late final pantryPattern = RegExp(nsPrefix + r'Pantry\[(\d+)\]/(.*)'); - - final derivedFrom = {}; - final history = >{}; - final ingredients = >{}; - final pantry = >{}; - - XmpMMNamespace(String nsPrefix, Map rawProps) : super(Namespaces.xmpMM, nsPrefix, rawProps); - - @override - bool extractData(XmpProp prop) { - final hasStructs = extractStruct(prop, derivedFromPattern, derivedFrom); - var hasIndexedStructs = extractIndexedStruct(prop, historyPattern, history); - hasIndexedStructs |= extractIndexedStruct(prop, ingredientsPattern, ingredients); - hasIndexedStructs |= extractIndexedStruct(prop, pantryPattern, pantry); - return hasStructs || hasIndexedStructs; - } - - @override - List buildFromExtractedData() => [ - if (derivedFrom.isNotEmpty) - XmpStructCard( - title: 'Derived From', - struct: derivedFrom, - ), - if (history.isNotEmpty) - XmpStructArrayCard( - title: 'History', - structByIndex: history, - ), - if (ingredients.isNotEmpty) - XmpStructArrayCard( - title: 'Ingredients', - structByIndex: ingredients, - ), - if (pantry.isNotEmpty) - XmpStructArrayCard( - title: 'Pantry', - structByIndex: pantry, - ), - ]; -} - -class XmpNoteNamespace extends XmpNamespace { - // `xmpNote:HasExtendedXMP` is structural and should not be displayed to users - late final hasExtendedXmp = '${nsPrefix}HasExtendedXMP'; - - XmpNoteNamespace(String nsPrefix, Map rawProps) : super(Namespaces.xmpNote, nsPrefix, rawProps); - - @override - bool extractData(XmpProp prop) { - return prop.path == hasExtendedXmp; - } + late final List cards = [ + XmpCardData( + RegExp(nsPrefix + r'Thumbnails\[(\d+)\]/(.*)'), + spanBuilders: (index, struct) { + return { + if (struct.containsKey('xmpGImg:image')) + 'Image': InfoRowGroup.linkSpanBuilder( + linkText: (context) => context.l10n.viewerInfoOpenLinkText, + onTap: (context) => OpenEmbeddedDataNotification.xmp( + props: [ + const [Namespaces.xmp, 'Thumbnails'], + index, + const [Namespaces.xmpGImg, 'image'], + ], + mimeType: MimeTypes.jpeg, + ).dispatch(context), + ), + }; + }, + ), + ]; } diff --git a/lib/widgets/viewer/info/metadata/xmp_structs.dart b/lib/widgets/viewer/info/metadata/xmp_structs.dart deleted file mode 100644 index 73018c2ca..000000000 --- a/lib/widgets/viewer/info/metadata/xmp_structs.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'dart:math'; - -import 'package:aves/theme/durations.dart'; -import 'package:aves/theme/icons.dart'; -import 'package:aves/theme/themes.dart'; -import 'package:aves/utils/constants.dart'; -import 'package:aves/widgets/common/basic/multi_cross_fader.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:aves/widgets/common/identity/highlight_title.dart'; -import 'package:aves/widgets/viewer/info/common.dart'; -import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; -import 'package:flutter/material.dart'; - -class XmpStructArrayCard extends StatefulWidget { - final String title; - late final List> structs; - final Map Function(int index)? linkifier; - - XmpStructArrayCard({ - super.key, - required this.title, - required Map> structByIndex, - this.linkifier, - }) { - final length = structByIndex.keys.fold(0, max); - structs = [for (var i = 0; i < length; i++) structByIndex[i + 1] ?? {}]; - } - - @override - State createState() => _XmpStructArrayCardState(); -} - -class _XmpStructArrayCardState extends State { - late int _index; - - List> get structs => widget.structs; - - @override - void initState() { - super.initState(); - _index = structs.length - 1; - } - - @override - Widget build(BuildContext context) { - void setIndex(int index) { - index = index.clamp(0, structs.length - 1); - if (_index != index) { - _index = index; - setState(() {}); - } - } - - return Card( - color: Themes.thirdLayerColor(context), - margin: XmpStructCard.cardMargin, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 8, top: 8, right: 8), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: HighlightTitle( - title: '${widget.title} ${_index + 1}', - selectable: true, - showHighlight: false, - ), - ), - IconButton( - visualDensity: VisualDensity.compact, - icon: const Icon(AIcons.previous), - onPressed: _index > 0 ? () => setIndex(_index - 1) : null, - tooltip: context.l10n.previousTooltip, - ), - IconButton( - visualDensity: VisualDensity.compact, - icon: const Icon(AIcons.next), - onPressed: _index < structs.length - 1 ? () => setIndex(_index + 1) : null, - tooltip: context.l10n.nextTooltip, - ), - ], - ), - ), - MultiCrossFader( - duration: Durations.xmpStructArrayCardTransition, - sizeCurve: Curves.easeOutBack, - alignment: AlignmentDirectional.topStart, - child: Padding( - // add padding at this level (instead of the column level) - // so that the crossfader can animate the content size - // without clipping the text - padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8), - child: InfoRowGroup( - info: structs[_index].map((key, value) => MapEntry(XmpProp.formatKey(key), value)), - maxValueLength: Constants.infoGroupMaxValueLength, - spanBuilders: widget.linkifier?.call(_index + 1), - ), - ), - ), - ], - ), - ); - } -} - -class XmpStructCard extends StatelessWidget { - final String title; - final Map struct; - final Map Function()? linkifier; - - static const cardMargin = EdgeInsets.symmetric(vertical: 8, horizontal: 0); - - const XmpStructCard({ - super.key, - required this.title, - required this.struct, - this.linkifier, - }); - - @override - Widget build(BuildContext context) { - return Card( - color: Themes.thirdLayerColor(context), - margin: cardMargin, - child: Padding( - padding: const EdgeInsets.all(8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - HighlightTitle( - title: title, - selectable: true, - showHighlight: false, - ), - InfoRowGroup( - info: struct.map((key, value) => MapEntry(XmpProp.formatKey(key), value)), - maxValueLength: Constants.infoGroupMaxValueLength, - spanBuilders: linkifier?.call(), - ), - ], - ), - ), - ); - } -}