info: use slivers for dynamic building of metadata widgets

This commit is contained in:
Thibault Deckers 2019-12-31 18:51:24 +09:00
parent 73663b8e46
commit 5fdbe0887b
5 changed files with 150 additions and 140 deletions

View file

@ -51,31 +51,47 @@ class InfoPageState extends State<InfoPage> {
body: SafeArea(
child: NotificationListener(
onNotification: _handleTopScroll,
child: Selector<MediaQueryData, Tuple2<Orientation, double>>(
selector: (c, mq) => Tuple2(mq.orientation, mq.viewInsets.bottom),
child: Selector<MediaQueryData, Tuple2<double, double>>(
selector: (c, mq) => Tuple2(mq.size.width, mq.viewInsets.bottom),
builder: (c, mq, child) {
final mqOrientation = mq.item1;
final mqWidth = mq.item1;
final mqViewInsetsBottom = mq.item2;
final split = mqWidth > 400;
return ListView(
padding: const EdgeInsets.all(8.0) + EdgeInsets.only(bottom: mqViewInsetsBottom),
children: [
if (mqOrientation == Orientation.landscape && entry.hasGps)
Row(
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: CustomScrollView(
slivers: [
const SliverPadding(
padding: EdgeInsets.only(top: 8),
),
if (split && entry.hasGps)
SliverToBoxAdapter(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: BasicSection(entry: entry)),
const SizedBox(width: 8),
Expanded(child: LocationSection(entry: entry, showTitle: false)),
],
),
)
else ...[
else
SliverList(
delegate: SliverChildListDelegate(
[
BasicSection(entry: entry),
LocationSection(entry: entry, showTitle: true),
],
XmpTagSection(collection: widget.collection, entry: entry),
MetadataSection(entry: entry),
),
),
XmpTagSectionSliver(collection: widget.collection, entry: entry),
MetadataSectionSliver(entry: entry, columnCount: split ? 2 : 1),
SliverPadding(
padding: EdgeInsets.only(bottom: 8 + mqViewInsetsBottom),
),
],
),
);
},
),

View file

@ -2,128 +2,103 @@ import 'dart:async';
import 'package:aves/model/image_entry.dart';
import 'package:aves/model/metadata_service.dart';
import 'package:aves/utils/color_utils.dart';
import 'package:aves/widgets/fullscreen/info/info_page.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
class MetadataSection extends StatefulWidget {
class MetadataSectionSliver extends StatefulWidget {
final ImageEntry entry;
final int columnCount;
const MetadataSection({this.entry});
const MetadataSectionSliver({
@required this.entry,
@required this.columnCount,
});
@override
State<StatefulWidget> createState() => MetadataSectionState();
State<StatefulWidget> createState() => _MetadataSectionSliverState();
}
class MetadataSectionState extends State<MetadataSection> {
Future<Map> _metadataLoader;
class _MetadataSectionSliverState extends State<MetadataSectionSliver> {
Map _metadata;
@override
void initState() {
super.initState();
_initMetadataLoader();
_getMetadata();
}
@override
void didUpdateWidget(MetadataSection oldWidget) {
void didUpdateWidget(MetadataSectionSliver oldWidget) {
super.didUpdateWidget(oldWidget);
_initMetadataLoader();
}
Future<void> _initMetadataLoader() async {
_metadataLoader = MetadataService.getAllMetadata(widget.entry);
_getMetadata();
}
@override
Widget build(BuildContext context) {
return Selector<MediaQueryData, double>(
selector: (c, mq) => mq.size.width,
builder: (c, mqWidth, child) => FutureBuilder(
future: _metadataLoader,
builder: (futureContext, AsyncSnapshot<Map> snapshot) {
if (snapshot.hasError) return Text(snapshot.error.toString());
if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink();
final metadataMap = snapshot.data.cast<String, Map>();
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SectionRow('Metadata'),
_MetadataSectionContent(
metadataMap: metadataMap,
split: mqWidth > 400,
),
],
debugPrint('$runtimeType build');
final directoryNames = (_metadata?.keys?.toList() ?? [])..sort();
return SliverStaggeredGrid.countBuilder(
crossAxisCount: widget.columnCount,
staggeredTileBuilder: (index) => StaggeredTile.fit(index == 0 ? widget.columnCount : 1),
itemBuilder: (context, index) {
return index == 0
? const SectionRow('Metadata')
: _Directory(
metadataMap: _metadata,
directoryName: directoryNames[index - 1],
);
},
),
itemCount: directoryNames.isEmpty ? 0 : directoryNames.length + 1,
mainAxisSpacing: 0,
crossAxisSpacing: 8,
);
}
}
class _MetadataSectionContent extends StatelessWidget {
final Map<String, Map> metadataMap;
final List<String> directoryNames;
final bool split;
_MetadataSectionContent({@required this.metadataMap, @required this.split}) : directoryNames = metadataMap.keys.toList()..sort();
@override
Widget build(BuildContext context) {
if (split) {
final first = <String>[], second = <String>[];
var firstItemCount = 0, secondItemCount = 0;
var firstIndex = 0, secondIndex = directoryNames.length - 1;
while (firstIndex <= secondIndex) {
if (firstItemCount <= secondItemCount) {
final directoryName = directoryNames[firstIndex++];
first.add(directoryName);
firstItemCount += 2 + metadataMap[directoryName].length;
} else {
final directoryName = directoryNames[secondIndex--];
second.insert(0, directoryName);
secondItemCount += 2 + metadataMap[directoryName].length;
}
}
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: _MetadataColumn(metadataMap: metadataMap, directoryNames: first)),
const SizedBox(width: 8),
Expanded(child: _MetadataColumn(metadataMap: metadataMap, directoryNames: second)),
],
);
} else {
return _MetadataColumn(metadataMap: metadataMap, directoryNames: directoryNames);
}
Future<void> _getMetadata() async {
debugPrint('$runtimeType _getMetadata');
_metadata = await MetadataService.getAllMetadata(widget.entry);
if (mounted) setState(() {});
}
}
class _MetadataColumn extends StatelessWidget {
final Map<String, Map> metadataMap;
final List<String> directoryNames;
const _MetadataColumn({@required this.metadataMap, @required this.directoryNames});
class _Directory extends StatelessWidget {
final Map metadataMap;
final String directoryName;
static const int maxValueLength = 140;
const _Directory({@required this.metadataMap, @required this.directoryName});
@override
Widget build(BuildContext context) {
final directory = metadataMap[directoryName];
final tagKeys = directory.keys.toList()..sort();
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: const EdgeInsets.symmetric(vertical: 4.0),
child: Text(directoryName,
Container(
decoration: _DirectoryTitleDecoration(
color: stringToColor(directoryName),
),
margin: const EdgeInsets.symmetric(vertical: 4.0),
child: Text(
directoryName,
style: const TextStyle(
shadows: [
Shadow(
color: Colors.black,
offset: Offset(1, 1),
blurRadius: 2,
)
],
fontSize: 18,
fontFamily: 'Concourse Caps',
)),
),
),
),
...tagKeys.map((tagKey) {
final value = directory[tagKey] as String;
@ -131,9 +106,19 @@ class _MetadataColumn extends StatelessWidget {
return InfoRow(tagKey, value.length > maxValueLength ? '${value.substring(0, maxValueLength)}' : value);
}),
const SizedBox(height: 16),
];
}),
],
);
}
}
class _DirectoryTitleDecoration extends BoxDecoration {
_DirectoryTitleDecoration({@required Color color})
: super(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
stops: [0, .4, .4],
colors: [color, color, Colors.transparent],
),
);
}

View file

@ -5,11 +5,11 @@ import 'package:aves/widgets/album/filtered_collection_page.dart';
import 'package:aves/widgets/fullscreen/info/info_page.dart';
import 'package:flutter/material.dart';
class XmpTagSection extends AnimatedWidget {
class XmpTagSectionSliver extends AnimatedWidget {
final ImageCollection collection;
final ImageEntry entry;
XmpTagSection({
XmpTagSectionSliver({
Key key,
@required this.collection,
@required this.entry,
@ -18,11 +18,11 @@ class XmpTagSection extends AnimatedWidget {
@override
Widget build(BuildContext context) {
final tags = entry.xmpSubjects;
return tags.isEmpty
? const SizedBox.shrink()
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
return SliverList(
delegate: SliverChildListDelegate(
tags.isEmpty
? []
: [
const SectionRow('XMP Tags'),
Wrap(
spacing: 8,
@ -40,6 +40,7 @@ class XmpTagSection extends AnimatedWidget {
.toList(),
),
],
),
);
}

View file

@ -99,6 +99,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
flutter_staggered_grid_view:
dependency: "direct main"
description:
name: flutter_staggered_grid_view
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
flutter_sticky_header:
dependency: "direct main"
description:

View file

@ -23,6 +23,7 @@ dependencies:
url: git://github.com/deckerst/flutter-draggable-scrollbar.git
flushbar:
flutter_native_timezone:
flutter_staggered_grid_view:
flutter_sticky_header:
git:
url: git://github.com/deckerst/flutter_sticky_header.git