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(
|
body: SafeArea(
|
||||||
child: NotificationListener(
|
child: NotificationListener(
|
||||||
onNotification: _handleTopScroll,
|
onNotification: _handleTopScroll,
|
||||||
child: Selector<MediaQueryData, Tuple2<Orientation, double>>(
|
child: Selector<MediaQueryData, Tuple2<double, double>>(
|
||||||
selector: (c, mq) => Tuple2(mq.orientation, mq.viewInsets.bottom),
|
selector: (c, mq) => Tuple2(mq.size.width, mq.viewInsets.bottom),
|
||||||
builder: (c, mq, child) {
|
builder: (c, mq, child) {
|
||||||
final mqOrientation = mq.item1;
|
final mqWidth = mq.item1;
|
||||||
final mqViewInsetsBottom = mq.item2;
|
final mqViewInsetsBottom = mq.item2;
|
||||||
|
final split = mqWidth > 400;
|
||||||
|
|
||||||
return ListView(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0) + EdgeInsets.only(bottom: mqViewInsetsBottom),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
children: [
|
child: CustomScrollView(
|
||||||
if (mqOrientation == Orientation.landscape && entry.hasGps)
|
slivers: [
|
||||||
Row(
|
const SliverPadding(
|
||||||
|
padding: EdgeInsets.only(top: 8),
|
||||||
|
),
|
||||||
|
if (split && entry.hasGps)
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: BasicSection(entry: entry)),
|
Expanded(child: BasicSection(entry: entry)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: LocationSection(entry: entry, showTitle: false)),
|
Expanded(child: LocationSection(entry: entry, showTitle: false)),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
else ...[
|
else
|
||||||
|
SliverList(
|
||||||
|
delegate: SliverChildListDelegate(
|
||||||
|
[
|
||||||
BasicSection(entry: entry),
|
BasicSection(entry: entry),
|
||||||
LocationSection(entry: entry, showTitle: true),
|
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/image_entry.dart';
|
||||||
import 'package:aves/model/metadata_service.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:aves/widgets/fullscreen/info/info_page.dart';
|
||||||
import 'package:flutter/material.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 ImageEntry entry;
|
||||||
|
final int columnCount;
|
||||||
|
|
||||||
const MetadataSection({this.entry});
|
const MetadataSectionSliver({
|
||||||
|
@required this.entry,
|
||||||
|
@required this.columnCount,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => MetadataSectionState();
|
State<StatefulWidget> createState() => _MetadataSectionSliverState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class MetadataSectionState extends State<MetadataSection> {
|
class _MetadataSectionSliverState extends State<MetadataSectionSliver> {
|
||||||
Future<Map> _metadataLoader;
|
Map _metadata;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_initMetadataLoader();
|
_getMetadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(MetadataSection oldWidget) {
|
void didUpdateWidget(MetadataSectionSliver oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
_initMetadataLoader();
|
_getMetadata();
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _initMetadataLoader() async {
|
|
||||||
_metadataLoader = MetadataService.getAllMetadata(widget.entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Selector<MediaQueryData, double>(
|
debugPrint('$runtimeType build');
|
||||||
selector: (c, mq) => mq.size.width,
|
final directoryNames = (_metadata?.keys?.toList() ?? [])..sort();
|
||||||
builder: (c, mqWidth, child) => FutureBuilder(
|
return SliverStaggeredGrid.countBuilder(
|
||||||
future: _metadataLoader,
|
crossAxisCount: widget.columnCount,
|
||||||
builder: (futureContext, AsyncSnapshot<Map> snapshot) {
|
staggeredTileBuilder: (index) => StaggeredTile.fit(index == 0 ? widget.columnCount : 1),
|
||||||
if (snapshot.hasError) return Text(snapshot.error.toString());
|
itemBuilder: (context, index) {
|
||||||
if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink();
|
return index == 0
|
||||||
final metadataMap = snapshot.data.cast<String, Map>();
|
? const SectionRow('Metadata')
|
||||||
|
: _Directory(
|
||||||
return Column(
|
metadataMap: _metadata,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
directoryName: directoryNames[index - 1],
|
||||||
children: [
|
|
||||||
const SectionRow('Metadata'),
|
|
||||||
_MetadataSectionContent(
|
|
||||||
metadataMap: metadataMap,
|
|
||||||
split: mqWidth > 400,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
itemCount: directoryNames.isEmpty ? 0 : directoryNames.length + 1,
|
||||||
|
mainAxisSpacing: 0,
|
||||||
|
crossAxisSpacing: 8,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class _MetadataSectionContent extends StatelessWidget {
|
Future<void> _getMetadata() async {
|
||||||
final Map<String, Map> metadataMap;
|
debugPrint('$runtimeType _getMetadata');
|
||||||
final List<String> directoryNames;
|
_metadata = await MetadataService.getAllMetadata(widget.entry);
|
||||||
final bool split;
|
if (mounted) setState(() {});
|
||||||
|
|
||||||
_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 {
|
class _Directory extends StatelessWidget {
|
||||||
final Map<String, Map> metadataMap;
|
final Map metadataMap;
|
||||||
final List<String> directoryNames;
|
final String directoryName;
|
||||||
|
|
||||||
const _MetadataColumn({@required this.metadataMap, @required this.directoryNames});
|
|
||||||
|
|
||||||
static const int maxValueLength = 140;
|
static const int maxValueLength = 140;
|
||||||
|
|
||||||
|
const _Directory({@required this.metadataMap, @required this.directoryName});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final directory = metadataMap[directoryName];
|
||||||
|
final tagKeys = directory.keys.toList()..sort();
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
...directoryNames.expand((directoryName) {
|
|
||||||
final directory = metadataMap[directoryName];
|
|
||||||
final tagKeys = directory.keys.toList()..sort();
|
|
||||||
return [
|
|
||||||
if (directoryName.isNotEmpty)
|
if (directoryName.isNotEmpty)
|
||||||
Padding(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
decoration: _DirectoryTitleDecoration(
|
||||||
child: Text(directoryName,
|
color: stringToColor(directoryName),
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 4.0),
|
||||||
|
child: Text(
|
||||||
|
directoryName,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
color: Colors.black,
|
||||||
|
offset: Offset(1, 1),
|
||||||
|
blurRadius: 2,
|
||||||
|
)
|
||||||
|
],
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontFamily: 'Concourse Caps',
|
fontFamily: 'Concourse Caps',
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
...tagKeys.map((tagKey) {
|
...tagKeys.map((tagKey) {
|
||||||
final value = directory[tagKey] as String;
|
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);
|
return InfoRow(tagKey, value.length > maxValueLength ? '${value.substring(0, maxValueLength)}…' : value);
|
||||||
}),
|
}),
|
||||||
const SizedBox(height: 16),
|
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:aves/widgets/fullscreen/info/info_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class XmpTagSection extends AnimatedWidget {
|
class XmpTagSectionSliver extends AnimatedWidget {
|
||||||
final ImageCollection collection;
|
final ImageCollection collection;
|
||||||
final ImageEntry entry;
|
final ImageEntry entry;
|
||||||
|
|
||||||
XmpTagSection({
|
XmpTagSectionSliver({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.collection,
|
@required this.collection,
|
||||||
@required this.entry,
|
@required this.entry,
|
||||||
|
@ -18,11 +18,11 @@ class XmpTagSection extends AnimatedWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final tags = entry.xmpSubjects;
|
final tags = entry.xmpSubjects;
|
||||||
return tags.isEmpty
|
return SliverList(
|
||||||
? const SizedBox.shrink()
|
delegate: SliverChildListDelegate(
|
||||||
: Column(
|
tags.isEmpty
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
? []
|
||||||
children: [
|
: [
|
||||||
const SectionRow('XMP Tags'),
|
const SectionRow('XMP Tags'),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
|
@ -40,6 +40,7 @@ class XmpTagSection extends AnimatedWidget {
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
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:
|
flutter_sticky_header:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -23,6 +23,7 @@ dependencies:
|
||||||
url: git://github.com/deckerst/flutter-draggable-scrollbar.git
|
url: git://github.com/deckerst/flutter-draggable-scrollbar.git
|
||||||
flushbar:
|
flushbar:
|
||||||
flutter_native_timezone:
|
flutter_native_timezone:
|
||||||
|
flutter_staggered_grid_view:
|
||||||
flutter_sticky_header:
|
flutter_sticky_header:
|
||||||
git:
|
git:
|
||||||
url: git://github.com/deckerst/flutter_sticky_header.git
|
url: git://github.com/deckerst/flutter_sticky_header.git
|
||||||
|
|
Loading…
Reference in a new issue