info: use slivers for dynamic building of metadata widgets
This commit is contained in:
parent
73663b8e46
commit
5fdbe0887b
5 changed files with 150 additions and 140 deletions
|
@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _getMetadata() async {
|
||||
debugPrint('$runtimeType _getMetadata');
|
||||
_metadata = await MetadataService.getAllMetadata(widget.entry);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue