import 'dart:collection'; import 'dart:typed_data'; import 'package:aves/model/image_entry.dart'; import 'package:aves/services/metadata_service.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; import 'package:aves/widgets/fullscreen/info/common.dart'; import 'package:flutter/material.dart'; class MetadataTab extends StatefulWidget { final ImageEntry entry; const MetadataTab({@required this.entry}); @override _MetadataTabState createState() => _MetadataTabState(); } class _MetadataTabState extends State { Future _bitmapFactoryLoader, _contentResolverMetadataLoader, _exifInterfaceMetadataLoader, _mediaMetadataLoader, _metadataExtractorLoader; // MediaStore timestamp keys static const secondTimestampKeys = ['date_added', 'date_modified', 'date_expires', 'isPlayed']; static const millisecondTimestampKeys = ['datetaken', 'datetime']; ImageEntry get entry => widget.entry; @override void initState() { super.initState(); _loadMetadata(); } void _loadMetadata() { _bitmapFactoryLoader = MetadataService.getBitmapFactoryInfo(entry); _contentResolverMetadataLoader = MetadataService.getContentResolverMetadata(entry); _exifInterfaceMetadataLoader = MetadataService.getExifInterfaceMetadata(entry); _mediaMetadataLoader = MetadataService.getMediaMetadataRetrieverMetadata(entry); _metadataExtractorLoader = MetadataService.getMetadataExtractorSummary(entry); setState(() {}); } @override Widget build(BuildContext context) { Widget builder(BuildContext context, AsyncSnapshot snapshot, String title) { if (snapshot.hasError) return Text(snapshot.error.toString()); if (snapshot.connectionState != ConnectionState.done) return SizedBox.shrink(); final data = SplayTreeMap.of(snapshot.data.map((k, v) { final key = k.toString(); var value = v?.toString() ?? 'null'; if ([...secondTimestampKeys, ...millisecondTimestampKeys].contains(key) && v is num && v != 0) { if (secondTimestampKeys.contains(key)) { v *= 1000; } value += ' (${DateTime.fromMillisecondsSinceEpoch(v)})'; } if (key == 'xmp' && v != null && v is Uint8List) { value = String.fromCharCodes(v); } return MapEntry(key, value); })); return AvesExpansionTile( title: title, children: data.isNotEmpty ? [ Padding( padding: EdgeInsets.only(left: 8, right: 8, bottom: 8), child: InfoRowGroup( data, maxValueLength: Constants.infoGroupMaxValueLength, ), ) ] : null, ); } return ListView( padding: EdgeInsets.all(8), children: [ FutureBuilder( future: _bitmapFactoryLoader, builder: (context, snapshot) => builder(context, snapshot, 'Bitmap Factory'), ), FutureBuilder( future: _contentResolverMetadataLoader, builder: (context, snapshot) => builder(context, snapshot, 'Content Resolver'), ), FutureBuilder( future: _exifInterfaceMetadataLoader, builder: (context, snapshot) => builder(context, snapshot, 'Exif Interface'), ), FutureBuilder( future: _mediaMetadataLoader, builder: (context, snapshot) => builder(context, snapshot, 'Media Metadata Retriever'), ), FutureBuilder( future: _metadataExtractorLoader, builder: (context, snapshot) => builder(context, snapshot, 'Metadata Extractor'), ), ], ); } }