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';
|
||||
|
||||
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
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/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(),
|
||||
),
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
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