aves/lib/widgets/viewer/info/metadata/xmp_card.dart
Thibault Deckers 4c6c56e3f8 ui fixes
2023-12-19 19:09:40 +01:00

159 lines
5.7 KiB
Dart

import 'dart:math';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.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';
typedef XmpExtractedCard = (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 ({}, 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 (isIndexed && _indexNotifier.value >= indexedStructCount) {
_indexNotifier.value = indexedStructCount - 1;
}
}
@override
void dispose() {
_indexNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final _isIndexed = isIndexed;
return Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).dividerColor,
),
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.$1.entries.map((kv) => XmpProp(kv.key, kv.value.value)).toList()..sort();
final cards = data.$2;
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,
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: ADurations.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)))),
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]!.$1) : null,
),
);
}),
],
);
},
),
);
}
void _setIndex(int index) => _indexNotifier.value = index.clamp(0, indexedStructCount - 1);
}