info: improved display for XMP
This commit is contained in:
parent
47a2364f5a
commit
f687622997
17 changed files with 426 additions and 642 deletions
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
157
lib/widgets/viewer/info/metadata/xmp_card.dart
Normal file
157
lib/widgets/viewer/info/metadata/xmp_card.dart
Normal file
|
@ -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<Map<String, XmpProp>, List<XmpCardData>?>;
|
||||
|
||||
class XmpCard extends StatefulWidget {
|
||||
final String title;
|
||||
late final XmpExtractedCard? directStruct;
|
||||
late final List<XmpExtractedCard>? indexedStructs;
|
||||
final String Function(XmpProp prop) formatValue;
|
||||
final Map<String, InfoValueSpanBuilder> Function(int? index)? spanBuilders;
|
||||
|
||||
XmpCard({
|
||||
super.key,
|
||||
required this.title,
|
||||
required Map<int?, XmpExtractedCard> 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<XmpCard> createState() => _XmpCardState();
|
||||
}
|
||||
|
||||
class _XmpCardState extends State<XmpCard> {
|
||||
final ValueNotifier<int> _indexNotifier = ValueNotifier(0);
|
||||
|
||||
List<XmpExtractedCard>? 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<int>(
|
||||
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);
|
||||
}
|
|
@ -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<String, String> get buildProps => rawProps;
|
||||
|
||||
List<Widget> 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<String, String> 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<int, Map<String, String>> 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, () => <String, String>{});
|
||||
fields[field] = formatValue(prop);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool extractData(XmpProp prop) => false;
|
||||
|
||||
List<Widget> buildFromExtractedData() => [];
|
||||
List<XmpCardData> get cards => [];
|
||||
|
||||
String formatValue(XmpProp prop) => prop.value;
|
||||
|
||||
Map<String, InfoValueSpanBuilder> linkifyValues(List<XmpProp> props) => {};
|
||||
}
|
||||
|
||||
class XmpProp {
|
||||
class XmpProp implements Comparable<XmpProp> {
|
||||
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<String, InfoValueSpanBuilder> Function(int?, Map<String, XmpProp> data)? spanBuilders;
|
||||
final List<XmpCardData>? cards;
|
||||
final Map<int?, XmpExtractedCard> 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(<String, XmpProp>{}, 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(<String, XmpProp>{}, 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = <int, Map<String, String>>{};
|
||||
final gbc = <int, Map<String, String>>{};
|
||||
final mgbc = <int, Map<String, String>>{};
|
||||
final pbc = <int, Map<String, String>>{};
|
||||
final retouchAreas = <int, Map<String, String>>{};
|
||||
final look = <String, String>{};
|
||||
final rmmi = <String, String>{};
|
||||
|
||||
XmpCrsNamespace(String nsPrefix, Map<String, String> 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<Widget> buildFromExtractedData() => [
|
||||
if (cgbc.isNotEmpty)
|
||||
XmpStructArrayCard(
|
||||
title: 'Circular Gradient Based Corrections',
|
||||
structByIndex: cgbc,
|
||||
late final List<XmpCardData> 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/(.*)')),
|
||||
],
|
||||
),
|
||||
if (gbc.isNotEmpty)
|
||||
XmpStructArrayCard(
|
||||
title: 'Gradient Based Corrections',
|
||||
structByIndex: gbc,
|
||||
XmpCardData(
|
||||
RegExp(nsPrefix + r'Look/(.*)'),
|
||||
cards: [
|
||||
XmpCardData(RegExp(nsPrefix + r'Parameters/(.*)')),
|
||||
],
|
||||
),
|
||||
if (look.isNotEmpty)
|
||||
XmpStructCard(
|
||||
title: 'Look',
|
||||
struct: look,
|
||||
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+)\]/(.*)')),
|
||||
],
|
||||
),
|
||||
if (mgbc.isNotEmpty)
|
||||
XmpStructArrayCard(
|
||||
title: 'Mask Group Based Corrections',
|
||||
structByIndex: mgbc,
|
||||
],
|
||||
),
|
||||
if (pbc.isNotEmpty)
|
||||
XmpStructArrayCard(
|
||||
title: 'Paint Based Corrections',
|
||||
structByIndex: pbc,
|
||||
XmpCardData(
|
||||
RegExp(nsPrefix + r'PaintBasedCorrections\[(\d+)\]/(.*)'),
|
||||
cards: [
|
||||
XmpCardData(RegExp(nsPrefix + r'CorrectionMasks\[(\d+)\]/(.*)')),
|
||||
XmpCardData(RegExp(nsPrefix + r'CorrectionRangeMask/(.*)')),
|
||||
],
|
||||
),
|
||||
if (rmmi.isNotEmpty)
|
||||
XmpStructCard(
|
||||
title: 'Range Mask Map Info',
|
||||
struct: rmmi,
|
||||
),
|
||||
if (retouchAreas.isNotEmpty)
|
||||
XmpStructArrayCard(
|
||||
title: 'Retouch Areas',
|
||||
structByIndex: retouchAreas,
|
||||
XmpCardData(
|
||||
RegExp(nsPrefix + r'RetouchAreas\[(\d+)\]/(.*)'),
|
||||
cards: [
|
||||
XmpCardData(RegExp(nsPrefix + r'Masks\[(\d+)\]/(.*)')),
|
||||
],
|
||||
),
|
||||
XmpCardData(RegExp(nsPrefix + r'RangeMaskMapInfo/' + nsPrefix + r'RangeMaskMapInfo/(.*)')),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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 = <int, Map<String, String>>{};
|
||||
|
||||
XmpDarktableNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.darktable, nsPrefix, rawProps);
|
||||
|
||||
@override
|
||||
bool extractData(XmpProp prop) => extractIndexedStruct(prop, historyPattern, history);
|
||||
|
||||
@override
|
||||
List<Widget> buildFromExtractedData() => [
|
||||
if (history.isNotEmpty)
|
||||
XmpStructArrayCard(
|
||||
title: 'History',
|
||||
structByIndex: history,
|
||||
),
|
||||
];
|
||||
}
|
|
@ -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 = <String, String>{};
|
||||
final event = <String, String>{};
|
||||
final identification = <String, String>{};
|
||||
final geologicalContext = <String, String>{};
|
||||
final measurementOrFact = <String, String>{};
|
||||
final occurrence = <String, String>{};
|
||||
final record = <String, String>{};
|
||||
final resourceRelationship = <String, String>{};
|
||||
final taxon = <String, String>{};
|
||||
|
||||
XmpDwcNamespace(String nsPrefix, Map<String, String> 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<Widget> 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,
|
||||
),
|
||||
];
|
||||
}
|
|
@ -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 = <int, Map<String, String>>{};
|
||||
|
||||
XmpContainer(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.container, nsPrefix, rawProps);
|
||||
|
||||
@override
|
||||
bool extractData(XmpProp prop) => extractIndexedStruct(prop, directoryPattern, directories);
|
||||
|
||||
@override
|
||||
List<Widget> buildFromExtractedData() => [
|
||||
if (directories.isNotEmpty)
|
||||
XmpStructArrayCard(
|
||||
title: 'Directory Item',
|
||||
structByIndex: directories,
|
||||
),
|
||||
late final List<XmpCardData> cards = [
|
||||
XmpCardData(RegExp('${nsPrefix}Directory\\[(\\d+)\\]/${nsPrefix}Item/(.*)'), title: 'Directory Item'),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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 = <String, String>{};
|
||||
|
||||
XmpIptcCoreNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.iptc4xmpCore, nsPrefix, rawProps);
|
||||
|
||||
@override
|
||||
bool extractData(XmpProp prop) => extractStruct(prop, creatorContactInfoPattern, creatorContactInfo);
|
||||
|
||||
@override
|
||||
List<Widget> buildFromExtractedData() => [
|
||||
if (creatorContactInfo.isNotEmpty)
|
||||
XmpStructCard(
|
||||
title: 'Creator Contact Info',
|
||||
struct: creatorContactInfo,
|
||||
),
|
||||
];
|
||||
}
|
|
@ -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 = <int, Map<String, String>>{};
|
||||
|
||||
XmpIptc4xmpExtNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.iptc4xmpExt, nsPrefix, rawProps);
|
||||
|
||||
@override
|
||||
bool extractData(XmpProp prop) => extractIndexedStruct(prop, aooPattern, aoo);
|
||||
|
||||
@override
|
||||
List<Widget> buildFromExtractedData() => [
|
||||
if (aoo.isNotEmpty)
|
||||
XmpStructArrayCard(
|
||||
title: 'Artwork or Object',
|
||||
structByIndex: aoo,
|
||||
),
|
||||
];
|
||||
}
|
|
@ -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 = <int, Map<String, String>>{};
|
||||
|
||||
XmpMPNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.mp, nsPrefix, rawProps);
|
||||
|
||||
@override
|
||||
bool extractData(XmpProp prop) => extractIndexedStruct(prop, regionListPattern, regionList);
|
||||
|
||||
@override
|
||||
List<Widget> buildFromExtractedData() => [
|
||||
if (regionList.isNotEmpty)
|
||||
XmpStructArrayCard(
|
||||
title: 'Regions',
|
||||
structByIndex: regionList,
|
||||
),
|
||||
];
|
||||
}
|
87
lib/widgets/viewer/info/metadata/xmp_ns/misc.dart
Normal file
87
lib/widgets/viewer/info/metadata/xmp_ns/misc.dart
Normal file
|
@ -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<String, String> rawProps) : super(Namespaces.darktable, nsPrefix, rawProps);
|
||||
|
||||
@override
|
||||
late final List<XmpCardData> cards = [
|
||||
XmpCardData(RegExp(nsPrefix + r'history\[(\d+)\]/(.*)')),
|
||||
];
|
||||
}
|
||||
|
||||
class XmpDwcNamespace extends XmpNamespace {
|
||||
XmpDwcNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.dwc, nsPrefix, rawProps);
|
||||
|
||||
@override
|
||||
late final List<XmpCardData> 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<String, String> rawProps) : super(Namespaces.iptc4xmpCore, nsPrefix, rawProps);
|
||||
|
||||
@override
|
||||
late final List<XmpCardData> cards = [
|
||||
XmpCardData(RegExp(nsPrefix + r'CreatorContactInfo/(.*)')),
|
||||
];
|
||||
}
|
||||
|
||||
class XmpIptc4xmpExtNamespace extends XmpNamespace {
|
||||
XmpIptc4xmpExtNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.iptc4xmpExt, nsPrefix, rawProps);
|
||||
|
||||
@override
|
||||
late final List<XmpCardData> cards = [
|
||||
XmpCardData(RegExp(nsPrefix + r'ArtworkOrObject\[(\d+)\]/(.*)')),
|
||||
];
|
||||
}
|
||||
|
||||
class XmpMPNamespace extends XmpNamespace {
|
||||
XmpMPNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.mp, nsPrefix, rawProps);
|
||||
|
||||
@override
|
||||
late final List<XmpCardData> 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<String, String> rawProps) : super(Namespaces.mwgrs, nsPrefix, rawProps);
|
||||
|
||||
@override
|
||||
late final List<XmpCardData> 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<String, String> rawProps) : super(Namespaces.plus, nsPrefix, rawProps);
|
||||
|
||||
@override
|
||||
late final List<XmpCardData> cards = [
|
||||
XmpCardData(RegExp(nsPrefix + r'Licensor\[(\d+)\]/(.*)')),
|
||||
];
|
||||
}
|
||||
|
||||
class XmpMMNamespace extends XmpNamespace {
|
||||
XmpMMNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.xmpMM, nsPrefix, rawProps);
|
||||
|
||||
@override
|
||||
late final List<XmpCardData> cards = [
|
||||
XmpCardData(RegExp(nsPrefix + r'DerivedFrom/(.*)')),
|
||||
XmpCardData(RegExp(nsPrefix + r'History\[(\d+)\]/(.*)')),
|
||||
XmpCardData(RegExp(nsPrefix + r'Ingredients\[(\d+)\]/(.*)')),
|
||||
XmpCardData(RegExp(nsPrefix + r'Pantry\[(\d+)\]/(.*)')),
|
||||
];
|
||||
}
|
|
@ -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 = <String, String>{};
|
||||
final regionList = <int, Map<String, String>>{};
|
||||
|
||||
XmpMgwRegionsNamespace(String nsPrefix, Map<String, String> 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<Widget> buildFromExtractedData() => [
|
||||
if (dimensions.isNotEmpty)
|
||||
XmpStructCard(
|
||||
title: 'Applied To Dimensions',
|
||||
struct: dimensions,
|
||||
),
|
||||
if (regionList.isNotEmpty)
|
||||
XmpStructArrayCard(
|
||||
title: 'Region',
|
||||
structByIndex: regionList,
|
||||
),
|
||||
];
|
||||
}
|
|
@ -1,37 +1,19 @@
|
|||
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 = <int, Map<String, String>>{};
|
||||
final textLayers = <int, Map<String, String>>{};
|
||||
|
||||
XmpPhotoshopNamespace(String nsPrefix, Map<String, String> 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<Widget> buildFromExtractedData() => [
|
||||
if (cameraProfiles.isNotEmpty)
|
||||
XmpStructArrayCard(
|
||||
title: 'Camera Profiles',
|
||||
structByIndex: cameraProfiles,
|
||||
),
|
||||
if (textLayers.isNotEmpty)
|
||||
XmpStructArrayCard(
|
||||
title: 'Text Layers',
|
||||
structByIndex: textLayers,
|
||||
late final List<XmpCardData> cards = [
|
||||
XmpCardData(
|
||||
RegExp(nsPrefix + r'CameraProfiles\[(\d+)\]/(.*)'),
|
||||
cards: [
|
||||
XmpCardData(RegExp(r'crlcp:PerspectiveModel/(.*)')),
|
||||
],
|
||||
),
|
||||
XmpCardData(RegExp(nsPrefix + r'TextLayers\[(\d+)\]/(.*)')),
|
||||
];
|
||||
|
||||
@override
|
||||
|
|
|
@ -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 = <int, Map<String, String>>{};
|
||||
|
||||
XmpPlusNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.plus, nsPrefix, rawProps);
|
||||
|
||||
@override
|
||||
bool extractData(XmpProp prop) => extractIndexedStruct(prop, licensorPattern, licensor);
|
||||
|
||||
@override
|
||||
List<Widget> buildFromExtractedData() => [
|
||||
if (licensor.isNotEmpty)
|
||||
XmpStructArrayCard(
|
||||
title: 'Licensor',
|
||||
structByIndex: licensor,
|
||||
),
|
||||
];
|
||||
}
|
|
@ -4,31 +4,18 @@ 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 = <int, Map<String, String>>{};
|
||||
|
||||
XmpBasicNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.xmp, nsPrefix, rawProps);
|
||||
|
||||
@override
|
||||
bool extractData(XmpProp prop) => extractIndexedStruct(prop, thumbnailsPattern, thumbnails);
|
||||
|
||||
@override
|
||||
List<Widget> buildFromExtractedData() => [
|
||||
if (thumbnails.isNotEmpty)
|
||||
XmpStructArrayCard(
|
||||
title: 'Thumbnail',
|
||||
structByIndex: thumbnails,
|
||||
linkifier: (index) {
|
||||
final struct = thumbnails[index]!;
|
||||
late final List<XmpCardData> cards = [
|
||||
XmpCardData(
|
||||
RegExp(nsPrefix + r'Thumbnails\[(\d+)\]/(.*)'),
|
||||
spanBuilders: (index, struct) {
|
||||
return {
|
||||
if (struct.containsKey(thumbnailDataDisplayKey))
|
||||
thumbnailDataDisplayKey: InfoRowGroup.linkSpanBuilder(
|
||||
if (struct.containsKey('xmpGImg:image'))
|
||||
'Image': InfoRowGroup.linkSpanBuilder(
|
||||
linkText: (context) => context.l10n.viewerInfoOpenLinkText,
|
||||
onTap: (context) => OpenEmbeddedDataNotification.xmp(
|
||||
props: [
|
||||
|
@ -41,65 +28,6 @@ class XmpBasicNamespace extends XmpNamespace {
|
|||
),
|
||||
};
|
||||
},
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
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 = <String, String>{};
|
||||
final history = <int, Map<String, String>>{};
|
||||
final ingredients = <int, Map<String, String>>{};
|
||||
final pantry = <int, Map<String, String>>{};
|
||||
|
||||
XmpMMNamespace(String nsPrefix, Map<String, String> 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<Widget> 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<String, String> rawProps) : super(Namespaces.xmpNote, nsPrefix, rawProps);
|
||||
|
||||
@override
|
||||
bool extractData(XmpProp prop) {
|
||||
return prop.path == hasExtendedXmp;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Map<String, String>> structs;
|
||||
final Map<String, InfoValueSpanBuilder> Function(int index)? linkifier;
|
||||
|
||||
XmpStructArrayCard({
|
||||
super.key,
|
||||
required this.title,
|
||||
required Map<int, Map<String, String>> structByIndex,
|
||||
this.linkifier,
|
||||
}) {
|
||||
final length = structByIndex.keys.fold(0, max);
|
||||
structs = [for (var i = 0; i < length; i++) structByIndex[i + 1] ?? {}];
|
||||
}
|
||||
|
||||
@override
|
||||
State<XmpStructArrayCard> createState() => _XmpStructArrayCardState();
|
||||
}
|
||||
|
||||
class _XmpStructArrayCardState extends State<XmpStructArrayCard> {
|
||||
late int _index;
|
||||
|
||||
List<Map<String, String>> 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<String, String> struct;
|
||||
final Map<String, InfoValueSpanBuilder> 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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue