info: improved XMP titles & keys
This commit is contained in:
parent
37d575a1b3
commit
edc90f085c
8 changed files with 143 additions and 50 deletions
21
lib/utils/brand_colors.dart
Normal file
21
lib/utils/brand_colors.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,9 +3,6 @@ import 'package:flutter/painting.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
class Constants {
|
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
|
// 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
|
// so we give it a `strutStyle` with a slightly larger height
|
||||||
static const overflowStrutStyle = StrutStyle(height: 1.3);
|
static const overflowStrutStyle = StrutStyle(height: 1.3);
|
||||||
|
|
27
lib/utils/xmp.dart
Normal file
27
lib/utils/xmp.dart
Normal 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',
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:aves/utils/brand_colors.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/widgets/common/aves_expansion_tile.dart';
|
import 'package:aves/widgets/common/aves_expansion_tile.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
|
@ -51,13 +52,13 @@ class _LicensesState extends State<Licenses> {
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
AvesExpansionTile(
|
AvesExpansionTile(
|
||||||
title: 'Android Libraries',
|
title: 'Android Libraries',
|
||||||
color: Constants.androidGreen,
|
color: BrandColors.android,
|
||||||
expandedNotifier: _expandedNotifier,
|
expandedNotifier: _expandedNotifier,
|
||||||
children: _platform.map((package) => LicenseRow(package)).toList(),
|
children: _platform.map((package) => LicenseRow(package)).toList(),
|
||||||
),
|
),
|
||||||
AvesExpansionTile(
|
AvesExpansionTile(
|
||||||
title: 'Flutter Packages',
|
title: 'Flutter Packages',
|
||||||
color: Constants.flutterBlue,
|
color: BrandColors.flutter,
|
||||||
expandedNotifier: _expandedNotifier,
|
expandedNotifier: _expandedNotifier,
|
||||||
children: _flutter.map((package) => LicenseRow(package)).toList(),
|
children: _flutter.map((package) => LicenseRow(package)).toList(),
|
||||||
),
|
),
|
||||||
|
|
|
@ -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/common/icons.dart';
|
||||||
import 'package:aves/widgets/fullscreen/info/basic_section.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/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:aves/widgets/fullscreen/info/notifications.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
|
@ -2,14 +2,15 @@ import 'dart:collection';
|
||||||
|
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/services/metadata_service.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/color_utils.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/utils/durations.dart';
|
import 'package:aves/utils/durations.dart';
|
||||||
import 'package:aves/widgets/common/aves_expansion_tile.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/common/icons.dart';
|
||||||
import 'package:aves/widgets/fullscreen/info/common.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:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -121,12 +122,17 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> with Auto
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDirTile(String title, _MetadataDirectory dir) {
|
Widget _buildDirTile(String title, _MetadataDirectory dir) {
|
||||||
if (dir.name == xmpDirectory) {
|
final dirName = dir.name;
|
||||||
return _buildXmpDirTile(dir);
|
if (dirName == xmpDirectory) {
|
||||||
|
return XmpDirTile(
|
||||||
|
entry: entry,
|
||||||
|
tags: dir.tags,
|
||||||
|
expandedNotifier: _expandedDirectoryNotifier,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Widget thumbnail;
|
Widget thumbnail;
|
||||||
final prefixChildren = <Widget>[];
|
final prefixChildren = <Widget>[];
|
||||||
switch (dir.name) {
|
switch (dirName) {
|
||||||
case exifThumbnailDirectory:
|
case exifThumbnailDirectory:
|
||||||
thumbnail = MetadataThumbnails(source: MetadataThumbnailSource.exif, entry: entry);
|
thumbnail = MetadataThumbnails(source: MetadataThumbnailSource.exif, entry: entry);
|
||||||
break;
|
break;
|
||||||
|
@ -150,7 +156,7 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> with Auto
|
||||||
|
|
||||||
return AvesExpansionTile(
|
return AvesExpansionTile(
|
||||||
title: title,
|
title: title,
|
||||||
color: stringToColor(dir.name),
|
color: BrandColors.get(dirName) ?? stringToColor(dirName),
|
||||||
expandedNotifier: _expandedDirectoryNotifier,
|
expandedNotifier: _expandedDirectoryNotifier,
|
||||||
children: [
|
children: [
|
||||||
if (prefixChildren.isNotEmpty) Wrap(children: prefixChildren),
|
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() {
|
void _onMetadataChanged() {
|
||||||
_loadedMetadataUri.value = null;
|
_loadedMetadataUri.value = null;
|
||||||
_metadata = {};
|
_metadata = {};
|
||||||
|
@ -223,7 +192,7 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> with Auto
|
||||||
|
|
||||||
final rawTags = dirKV.value as Map ?? {};
|
final rawTags = dirKV.value as Map ?? {};
|
||||||
final tags = SplayTreeMap.of(Map.fromEntries(rawTags.entries.map((tagKV) {
|
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;
|
if (value.isEmpty) return null;
|
||||||
final tagName = tagKV.key as String ?? '';
|
final tagName = tagKV.key as String ?? '';
|
||||||
return MapEntry(tagName, value);
|
return MapEntry(tagName, value);
|
78
lib/widgets/fullscreen/info/metadata/xmp_tile.dart
Normal file
78
lib/widgets/fullscreen/info/metadata/xmp_tile.dart
Normal 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(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue