info: improved XMP titles & keys

This commit is contained in:
Thibault Deckers 2020-11-20 12:24:23 +09:00
parent 37d575a1b3
commit edc90f085c
8 changed files with 143 additions and 50 deletions

View file

@ -0,0 +1,21 @@
import 'package:flutter/painting.dart';
class BrandColors {
static const Color adobeIllustrator = Color(0xFFFF9B00);
static const Color adobePhotoshop = Color(0xFF2DAAFF);
static const Color android = Color(0xFF3DDC84);
static const Color flutter = Color(0xFF47D1FD);
static Color get(String text) {
if (text != null) {
switch (text.toLowerCase()) {
case 'illustrator':
return adobeIllustrator;
case 'photoshop':
case 'lightroom':
return adobePhotoshop;
}
}
return null;
}
}

View file

@ -3,9 +3,6 @@ import 'package:flutter/painting.dart';
import 'package:tuple/tuple.dart';
class Constants {
static const Color androidGreen = Color(0xFF3DDC84);
static const Color flutterBlue = Color(0xFF47D1FD);
// as of Flutter v1.22.3, overflowing `Text` miscalculates height and some text (e.g. 'Å') is clipped
// so we give it a `strutStyle` with a slightly larger height
static const overflowStrutStyle = StrutStyle(height: 1.3);

27
lib/utils/xmp.dart Normal file
View file

@ -0,0 +1,27 @@
class XMP {
static const namespaceSeparator = ':';
static const structFieldSeparator = '/';
static const Map<String, String> namespaces = {
'Camera': 'Camera',
'crs': 'Camera Raw Settings',
'dc': 'Dublin Core',
'exif': 'Exif',
'GIMP': 'GIMP',
'illustrator': 'Illustrator',
'Iptc4xmpCore': 'IPTC Core',
'lr': 'Lightroom',
'MicrosoftPhoto': 'Microsoft Photo',
'pdf': 'PDF',
'pdfx': 'PDF/X',
'photomechanic': 'Photo Mechanic',
'photoshop': 'Photoshop',
'tiff': 'TIFF',
'xmp': 'Basic',
'xmpBJ': 'Basic Job Ticket',
'xmpDM': 'Dynamic Media',
'xmpMM': 'Media Management',
'xmpRights': 'Rights Management',
'xmpTPg': 'Paged-Text',
};
}

View file

@ -1,3 +1,4 @@
import 'package:aves/utils/brand_colors.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/aves_expansion_tile.dart';
import 'package:aves/widgets/common/icons.dart';
@ -51,13 +52,13 @@ class _LicensesState extends State<Licenses> {
SizedBox(height: 16),
AvesExpansionTile(
title: 'Android Libraries',
color: Constants.androidGreen,
color: BrandColors.android,
expandedNotifier: _expandedNotifier,
children: _platform.map((package) => LicenseRow(package)).toList(),
),
AvesExpansionTile(
title: 'Flutter Packages',
color: Constants.flutterBlue,
color: BrandColors.flutter,
expandedNotifier: _expandedNotifier,
children: _flutter.map((package) => LicenseRow(package)).toList(),
),

View file

@ -6,7 +6,7 @@ import 'package:aves/widgets/common/data_providers/media_query_data_provider.dar
import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/fullscreen/info/basic_section.dart';
import 'package:aves/widgets/fullscreen/info/location_section.dart';
import 'package:aves/widgets/fullscreen/info/metadata_section.dart';
import 'package:aves/widgets/fullscreen/info/metadata/metadata_section.dart';
import 'package:aves/widgets/fullscreen/info/notifications.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

View file

@ -2,14 +2,15 @@ import 'dart:collection';
import 'package:aves/model/image_entry.dart';
import 'package:aves/services/metadata_service.dart';
import 'package:aves/utils/brand_colors.dart';
import 'package:aves/utils/color_utils.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/common/aves_expansion_tile.dart';
import 'package:aves/widgets/common/highlight_title.dart';
import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/fullscreen/info/common.dart';
import 'package:aves/widgets/fullscreen/info/metadata_thumbnail.dart';
import 'package:aves/widgets/fullscreen/info/metadata/metadata_thumbnail.dart';
import 'package:aves/widgets/fullscreen/info/metadata/xmp_tile.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -121,12 +122,17 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> with Auto
}
Widget _buildDirTile(String title, _MetadataDirectory dir) {
if (dir.name == xmpDirectory) {
return _buildXmpDirTile(dir);
final dirName = dir.name;
if (dirName == xmpDirectory) {
return XmpDirTile(
entry: entry,
tags: dir.tags,
expandedNotifier: _expandedDirectoryNotifier,
);
}
Widget thumbnail;
final prefixChildren = <Widget>[];
switch (dir.name) {
switch (dirName) {
case exifThumbnailDirectory:
thumbnail = MetadataThumbnails(source: MetadataThumbnailSource.exif, entry: entry);
break;
@ -150,7 +156,7 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> with Auto
return AvesExpansionTile(
title: title,
color: stringToColor(dir.name),
color: BrandColors.get(dirName) ?? stringToColor(dirName),
expandedNotifier: _expandedDirectoryNotifier,
children: [
if (prefixChildren.isNotEmpty) Wrap(children: prefixChildren),
@ -163,43 +169,6 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> with Auto
);
}
Widget _buildXmpDirTile(_MetadataDirectory dir) {
final thumbnail = MetadataThumbnails(source: MetadataThumbnailSource.xmp, entry: entry);
final byNamespace = SplayTreeMap.of(
groupBy<MapEntry<String, String>, String>(dir.tags.entries, (kv) {
final fullKey = kv.key;
final i = fullKey.indexOf(':');
if (i == -1) return '';
return fullKey.substring(0, i);
}),
compareAsciiUpperCase,
);
return AvesExpansionTile(
title: dir.name,
expandedNotifier: _expandedDirectoryNotifier,
children: [
if (thumbnail != null) thumbnail,
Padding(
padding: EdgeInsets.only(left: 8, right: 8, bottom: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: byNamespace.entries.expand((kv) {
final ns = kv.key;
final hasNamespace = ns.isNotEmpty;
final i = hasNamespace ? ns.length + 1 : 0;
final entries = kv.value.map((kv) => MapEntry(kv.key.substring(i), kv.value)).toList();
entries.sort((a, b) => compareAsciiUpperCaseNatural(a.key, b.key));
return [
if (hasNamespace) HighlightTitle(ns),
InfoRowGroup(Map.fromEntries(entries), maxValueLength: Constants.infoGroupMaxValueLength),
];
}).toList(),
),
),
],
);
}
void _onMetadataChanged() {
_loadedMetadataUri.value = null;
_metadata = {};
@ -223,7 +192,7 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> with Auto
final rawTags = dirKV.value as Map ?? {};
final tags = SplayTreeMap.of(Map.fromEntries(rawTags.entries.map((tagKV) {
final value = tagKV.value as String ?? '';
final value = (tagKV.value as String ?? '').trim();
if (value.isEmpty) return null;
final tagName = tagKV.key as String ?? '';
return MapEntry(tagName, value);

View file

@ -0,0 +1,78 @@
import 'dart:collection';
import 'package:aves/model/image_entry.dart';
import 'package:aves/utils/brand_colors.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/utils/xmp.dart';
import 'package:aves/widgets/common/aves_expansion_tile.dart';
import 'package:aves/widgets/common/highlight_title.dart';
import 'package:aves/widgets/fullscreen/info/common.dart';
import 'package:aves/widgets/fullscreen/info/metadata/metadata_thumbnail.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class XmpDirTile extends StatelessWidget {
final ImageEntry entry;
final SplayTreeMap<String, String> tags;
final ValueNotifier<String> expandedNotifier;
const XmpDirTile({
@required this.entry,
@required this.tags,
@required this.expandedNotifier,
});
@override
Widget build(BuildContext context) {
final thumbnail = MetadataThumbnails(source: MetadataThumbnailSource.xmp, entry: entry);
final sections = SplayTreeMap.of(
groupBy<MapEntry<String, String>, String>(tags.entries, (kv) {
final fullKey = kv.key;
final i = fullKey.indexOf(XMP.namespaceSeparator);
if (i == -1) return '';
final namespace = fullKey.substring(0, i);
return XMP.namespaces[namespace] ?? namespace;
}),
compareAsciiUpperCase,
);
return AvesExpansionTile(
title: 'XMP',
expandedNotifier: expandedNotifier,
children: [
if (thumbnail != null) thumbnail,
Padding(
padding: EdgeInsets.only(left: 8, right: 8, bottom: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: sections.entries.expand((sectionEntry) {
final title = sectionEntry.key;
final entries = sectionEntry.value.map((kv) {
final key = kv.key.splitMapJoin(XMP.structFieldSeparator, onNonMatch: (s) {
// strip namespace
final key = s.split(XMP.namespaceSeparator).last;
// uppercase first letter
return key.replaceFirstMapped(RegExp('.'), (m) => m.group(0).toUpperCase());
});
return MapEntry(key, kv.value);
}).toList()
..sort((a, b) => compareAsciiUpperCaseNatural(a.key, b.key));
return [
if (title.isNotEmpty)
Padding(
padding: EdgeInsets.only(top: 8),
child: HighlightTitle(
title,
color: BrandColors.get(title),
),
),
InfoRowGroup(Map.fromEntries(entries), maxValueLength: Constants.infoGroupMaxValueLength),
];
}).toList(),
),
),
],
);
}
}