info: landscape layout
This commit is contained in:
parent
d919cd6022
commit
42820b7e48
4 changed files with 127 additions and 46 deletions
40
lib/widgets/fullscreen/info/basic_section.dart
Normal file
40
lib/widgets/fullscreen/info/basic_section.dart
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import 'package:aves/model/image_entry.dart';
|
||||||
|
import 'package:aves/utils/file_utils.dart';
|
||||||
|
import 'package:aves/widgets/fullscreen/info/info_page.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class BasicSection extends StatelessWidget {
|
||||||
|
final ImageEntry entry;
|
||||||
|
|
||||||
|
const BasicSection({
|
||||||
|
Key key,
|
||||||
|
@required this.entry,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final date = entry.bestDate;
|
||||||
|
final dateText = '${DateFormat.yMMMd().format(date)} – ${DateFormat.Hm().format(date)}';
|
||||||
|
final resolutionText = '${entry.width} × ${entry.height}${entry.isVideo ? '' : ' (${entry.megaPixels} MP)'}';
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
InfoRow('Title', entry.title),
|
||||||
|
InfoRow('Date', dateText),
|
||||||
|
if (entry.isVideo) ..._buildVideoRows(),
|
||||||
|
InfoRow('Resolution', resolutionText),
|
||||||
|
InfoRow('Size', formatFilesize(entry.sizeBytes)),
|
||||||
|
InfoRow('Uri', entry.uri),
|
||||||
|
InfoRow('Path', entry.path),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildVideoRows() {
|
||||||
|
final rotation = entry.catalogMetadata?.videoRotation;
|
||||||
|
if (rotation != null) InfoRow('Rotation', '$rotation°');
|
||||||
|
return [InfoRow('Duration', entry.durationText), if (rotation != null) InfoRow('Rotation', '$rotation°')];
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,10 @@
|
||||||
import 'package:aves/model/image_collection.dart';
|
import 'package:aves/model/image_collection.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/utils/file_utils.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_section.dart';
|
||||||
import 'package:aves/widgets/fullscreen/info/xmp_section.dart';
|
import 'package:aves/widgets/fullscreen/info/xmp_section.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
|
|
||||||
class InfoPage extends StatefulWidget {
|
class InfoPage extends StatefulWidget {
|
||||||
final ImageCollection collection;
|
final ImageCollection collection;
|
||||||
|
@ -34,9 +33,8 @@ class InfoPageState extends State<InfoPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final date = entry.bestDate;
|
// use MediaQuery instead of unreliable OrientationBuilder
|
||||||
final dateText = '${DateFormat.yMMMd().format(date)} – ${DateFormat.Hm().format(date)}';
|
final orientation = MediaQuery.of(context).orientation;
|
||||||
final resolutionText = '${entry.width} × ${entry.height}${entry.isVideo ? '' : ' (${entry.megaPixels} MP)'}';
|
|
||||||
final bottomInsets = MediaQuery.of(context).viewInsets.bottom;
|
final bottomInsets = MediaQuery.of(context).viewInsets.bottom;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
@ -53,14 +51,19 @@ class InfoPageState extends State<InfoPage> {
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: EdgeInsets.all(8.0) + EdgeInsets.only(bottom: bottomInsets),
|
padding: EdgeInsets.all(8.0) + EdgeInsets.only(bottom: bottomInsets),
|
||||||
children: [
|
children: [
|
||||||
InfoRow('Title', entry.title),
|
if (orientation == Orientation.landscape && entry.hasGps)
|
||||||
InfoRow('Date', dateText),
|
Row(
|
||||||
if (entry.isVideo) ..._buildVideoRows(),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
InfoRow('Resolution', resolutionText),
|
children: [
|
||||||
InfoRow('Size', formatFilesize(entry.sizeBytes)),
|
Expanded(child: BasicSection(entry: entry)),
|
||||||
InfoRow('Path', entry.path),
|
SizedBox(width: 8),
|
||||||
InfoRow('Uri', entry.uri),
|
Expanded(child: LocationSection(entry: entry, showTitle: false)),
|
||||||
LocationSection(entry: entry),
|
],
|
||||||
|
)
|
||||||
|
else ...[
|
||||||
|
BasicSection(entry: entry),
|
||||||
|
LocationSection(entry: entry, showTitle: true),
|
||||||
|
],
|
||||||
XmpTagSection(collection: widget.collection, entry: entry),
|
XmpTagSection(collection: widget.collection, entry: entry),
|
||||||
MetadataSection(entry: entry),
|
MetadataSection(entry: entry),
|
||||||
],
|
],
|
||||||
|
@ -71,12 +74,6 @@ class InfoPageState extends State<InfoPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildVideoRows() {
|
|
||||||
final rotation = entry.catalogMetadata?.videoRotation;
|
|
||||||
if (rotation != null) InfoRow('Rotation', '$rotation°');
|
|
||||||
return [InfoRow('Duration', entry.durationText), if (rotation != null) InfoRow('Rotation', '$rotation°')];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _handleTopScroll(Notification notification) {
|
bool _handleTopScroll(Notification notification) {
|
||||||
if (notification is ScrollNotification) {
|
if (notification is ScrollNotification) {
|
||||||
if (notification is ScrollStartNotification) {
|
if (notification is ScrollStartNotification) {
|
||||||
|
|
|
@ -7,8 +7,13 @@ import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
|
|
||||||
class LocationSection extends AnimatedWidget {
|
class LocationSection extends AnimatedWidget {
|
||||||
final ImageEntry entry;
|
final ImageEntry entry;
|
||||||
|
final showTitle;
|
||||||
|
|
||||||
const LocationSection({Key key, this.entry}) : super(key: key, listenable: entry);
|
const LocationSection({
|
||||||
|
Key key,
|
||||||
|
@required this.entry,
|
||||||
|
@required this.showTitle,
|
||||||
|
}) : super(key: key, listenable: entry);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -17,8 +22,11 @@ class LocationSection extends AnimatedWidget {
|
||||||
: Column(
|
: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SectionRow('Location'),
|
if (showTitle)
|
||||||
SizedBox(height: 8),
|
Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 8),
|
||||||
|
child: SectionRow('Location'),
|
||||||
|
),
|
||||||
ImageMap(
|
ImageMap(
|
||||||
markerId: entry.path,
|
markerId: entry.path,
|
||||||
latLng: LatLng(
|
latLng: LatLng(
|
||||||
|
|
|
@ -44,36 +44,72 @@ class MetadataSectionState extends State<MetadataSection> {
|
||||||
if (snapshot.connectionState != ConnectionState.done) return SizedBox.shrink();
|
if (snapshot.connectionState != ConnectionState.done) return SizedBox.shrink();
|
||||||
final metadataMap = snapshot.data.cast<String, Map>();
|
final metadataMap = snapshot.data.cast<String, Map>();
|
||||||
final directoryNames = metadataMap.keys.toList()..sort();
|
final directoryNames = metadataMap.keys.toList()..sort();
|
||||||
|
|
||||||
|
Widget content;
|
||||||
|
// use MediaQuery instead of unreliable OrientationBuilder
|
||||||
|
final orientation = MediaQuery.of(context).orientation;
|
||||||
|
if (orientation == Orientation.landscape) {
|
||||||
|
final threshold = directoryNames.map((k) => metadataMap[k].length).reduce((v, e) => v + e) / 2;
|
||||||
|
final first = <String>[], second = <String>[];
|
||||||
|
var processed = 0;
|
||||||
|
for (int i = 0; i < directoryNames.length; i++) {
|
||||||
|
final directoryName = directoryNames[i];
|
||||||
|
if (processed <= threshold)
|
||||||
|
first.add(directoryName);
|
||||||
|
else
|
||||||
|
second.add(directoryName);
|
||||||
|
processed += 1 + metadataMap[directoryName].length;
|
||||||
|
}
|
||||||
|
debugPrint('first=$first second=$second');
|
||||||
|
content = Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(child: getMetadataColumn(metadataMap, first)),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Expanded(child: getMetadataColumn(metadataMap, second)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
content = getMetadataColumn(metadataMap, directoryNames);
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
SectionRow('Metadata'),
|
SectionRow('Metadata'),
|
||||||
...directoryNames.expand(
|
content,
|
||||||
(directoryName) {
|
|
||||||
final directory = metadataMap[directoryName];
|
|
||||||
final tagKeys = directory.keys.toList()..sort();
|
|
||||||
return [
|
|
||||||
if (directoryName.isNotEmpty)
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 4.0),
|
|
||||||
child: Text(directoryName,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontFamily: 'Concourse Caps',
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
...tagKeys.map((tagKey) {
|
|
||||||
final value = directory[tagKey] as String;
|
|
||||||
if (value == null || value.isEmpty) return SizedBox.shrink();
|
|
||||||
return InfoRow(tagKey, value.length > maxValueLength ? '${value.substring(0, maxValueLength)}…' : value);
|
|
||||||
}),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget getMetadataColumn(Map<String, Map> metadataMap, List<String> directoryNames) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
...directoryNames.expand((directoryName) {
|
||||||
|
final directory = metadataMap[directoryName];
|
||||||
|
final tagKeys = directory.keys.toList()..sort();
|
||||||
|
return [
|
||||||
|
if (directoryName.isNotEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 4.0),
|
||||||
|
child: Text(directoryName,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontFamily: 'Concourse Caps',
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
...tagKeys.map((tagKey) {
|
||||||
|
final value = directory[tagKey] as String;
|
||||||
|
if (value == null || value.isEmpty) return SizedBox.shrink();
|
||||||
|
return InfoRow(tagKey, value.length > maxValueLength ? '${value.substring(0, maxValueLength)}…' : value);
|
||||||
|
}),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
];
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue