From 5fdbe0887bd8054caffe1c553aaea85b2d28b6d0 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 31 Dec 2019 18:51:24 +0900 Subject: [PATCH] info: use slivers for dynamic building of metadata widgets --- lib/widgets/fullscreen/info/info_page.dart | 58 +++--- .../fullscreen/info/metadata_section.dart | 173 ++++++++---------- lib/widgets/fullscreen/info/xmp_section.dart | 51 +++--- pubspec.lock | 7 + pubspec.yaml | 1 + 5 files changed, 150 insertions(+), 140 deletions(-) diff --git a/lib/widgets/fullscreen/info/info_page.dart b/lib/widgets/fullscreen/info/info_page.dart index db4e389a5..d59e1074c 100644 --- a/lib/widgets/fullscreen/info/info_page.dart +++ b/lib/widgets/fullscreen/info/info_page.dart @@ -51,31 +51,47 @@ class InfoPageState extends State { body: SafeArea( child: NotificationListener( onNotification: _handleTopScroll, - child: Selector>( - selector: (c, mq) => Tuple2(mq.orientation, mq.viewInsets.bottom), + child: Selector>( + 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( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded(child: BasicSection(entry: entry)), - const SizedBox(width: 8), - Expanded(child: LocationSection(entry: entry, showTitle: false)), - ], - ) - else ...[ - BasicSection(entry: entry), - LocationSection(entry: entry, showTitle: true), + 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 + SliverList( + delegate: SliverChildListDelegate( + [ + BasicSection(entry: entry), + LocationSection(entry: entry, showTitle: true), + ], + ), + ), + XmpTagSectionSliver(collection: widget.collection, entry: entry), + MetadataSectionSliver(entry: entry, columnCount: split ? 2 : 1), + SliverPadding( + padding: EdgeInsets.only(bottom: 8 + mqViewInsetsBottom), + ), ], - XmpTagSection(collection: widget.collection, entry: entry), - MetadataSection(entry: entry), - ], + ), ); }, ), diff --git a/lib/widgets/fullscreen/info/metadata_section.dart b/lib/widgets/fullscreen/info/metadata_section.dart index e63603758..5d3fdf2cd 100644 --- a/lib/widgets/fullscreen/info/metadata_section.dart +++ b/lib/widgets/fullscreen/info/metadata_section.dart @@ -2,138 +2,123 @@ 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 createState() => MetadataSectionState(); + State createState() => _MetadataSectionSliverState(); } -class MetadataSectionState extends State { - Future _metadataLoader; +class _MetadataSectionSliverState extends State { + Map _metadata; @override void initState() { super.initState(); - _initMetadataLoader(); + _getMetadata(); } @override - void didUpdateWidget(MetadataSection oldWidget) { + void didUpdateWidget(MetadataSectionSliver oldWidget) { super.didUpdateWidget(oldWidget); - _initMetadataLoader(); - } - - Future _initMetadataLoader() async { - _metadataLoader = MetadataService.getAllMetadata(widget.entry); + _getMetadata(); } @override Widget build(BuildContext context) { - return Selector( - selector: (c, mq) => mq.size.width, - builder: (c, mqWidth, child) => FutureBuilder( - future: _metadataLoader, - builder: (futureContext, AsyncSnapshot snapshot) { - if (snapshot.hasError) return Text(snapshot.error.toString()); - if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink(); - final metadataMap = snapshot.data.cast(); - - 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 metadataMap; - final List 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 = [], second = []; - 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 _getMetadata() async { + debugPrint('$runtimeType _getMetadata'); + _metadata = await MetadataService.getAllMetadata(widget.entry); + if (mounted) setState(() {}); } } -class _MetadataColumn extends StatelessWidget { - final Map metadataMap; - final List 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, - style: const TextStyle( - fontSize: 18, - fontFamily: 'Concourse Caps', - )), + if (directoryName.isNotEmpty) + 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; - if (value == null || value.isEmpty) return const SizedBox.shrink(); - return InfoRow(tagKey, value.length > maxValueLength ? '${value.substring(0, maxValueLength)}…' : value); - }), - const SizedBox(height: 16), - ]; + ), + ), + ...tagKeys.map((tagKey) { + final value = directory[tagKey] as String; + if (value == null || value.isEmpty) return const SizedBox.shrink(); + 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], + ), + ); +} diff --git a/lib/widgets/fullscreen/info/xmp_section.dart b/lib/widgets/fullscreen/info/xmp_section.dart index 5680bad88..1145daaf0 100644 --- a/lib/widgets/fullscreen/info/xmp_section.dart +++ b/lib/widgets/fullscreen/info/xmp_section.dart @@ -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,29 +18,30 @@ class XmpTagSection extends AnimatedWidget { @override Widget build(BuildContext context) { final tags = entry.xmpSubjects; - return tags.isEmpty - ? const SizedBox.shrink() - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SectionRow('XMP Tags'), - Wrap( - spacing: 8, - children: tags - .map((tag) => OutlineButton( - onPressed: () => _goToTag(context, tag), - borderSide: BorderSide( - color: stringToColor(tag), - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(42), - ), - child: Text(tag), - )) - .toList(), - ), - ], - ); + return SliverList( + delegate: SliverChildListDelegate( + tags.isEmpty + ? [] + : [ + const SectionRow('XMP Tags'), + Wrap( + spacing: 8, + children: tags + .map((tag) => OutlineButton( + onPressed: () => _goToTag(context, tag), + borderSide: BorderSide( + color: stringToColor(tag), + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(42), + ), + child: Text(tag), + )) + .toList(), + ), + ], + ), + ); } void _goToTag(BuildContext context, String tag) { diff --git a/pubspec.lock b/pubspec.lock index 942d27bd5..9c0f469c4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: diff --git a/pubspec.yaml b/pubspec.yaml index ea83b6523..520038613 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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