split widgets, listen to collection by ChangeNotifierProvider instead of AnimatedWidget

This commit is contained in:
Thibault Deckers 2019-12-31 11:59:01 +09:00
parent 07f073bd77
commit 935227f2e3
7 changed files with 178 additions and 148 deletions

View file

@ -14,55 +14,61 @@ class AllCollectionPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
debugPrint('$runtimeType build');
final collection = Provider.of<ImageCollection>(context);
return ThumbnailCollection(
collection: collection,
appBar: SliverAppBar(
title: const Text('All'),
actions: [
IconButton(
icon: Icon(OMIcons.search),
onPressed: () => showSearch(
context: context,
delegate: ImageSearchDelegate(collection),
),
appBar: _AllCollectionAppBar(),
);
}
}
class _AllCollectionAppBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
final collection = Provider.of<ImageCollection>(context);
return SliverAppBar(
title: const Text('All'),
actions: [
IconButton(
icon: Icon(OMIcons.search),
onPressed: () => showSearch(
context: context,
delegate: ImageSearchDelegate(collection),
),
PopupMenuButton<AlbumAction>(
itemBuilder: (context) => [
),
PopupMenuButton<AlbumAction>(
itemBuilder: (context) => [
PopupMenuItem(
value: AlbumAction.sortByDate,
child: MenuRow(text: 'Sort by date', checked: collection.sortFactor == SortFactor.date),
),
PopupMenuItem(
value: AlbumAction.sortBySize,
child: MenuRow(text: 'Sort by size', checked: collection.sortFactor == SortFactor.size),
),
const PopupMenuDivider(),
if (collection.sortFactor == SortFactor.date) ...[
PopupMenuItem(
value: AlbumAction.sortByDate,
child: MenuRow(text: 'Sort by date', checked: collection.sortFactor == SortFactor.date),
value: AlbumAction.groupByAlbum,
child: MenuRow(text: 'Group by album', checked: collection.groupFactor == GroupFactor.album),
),
PopupMenuItem(
value: AlbumAction.sortBySize,
child: MenuRow(text: 'Sort by size', checked: collection.sortFactor == SortFactor.size),
value: AlbumAction.groupByMonth,
child: MenuRow(text: 'Group by month', checked: collection.groupFactor == GroupFactor.month),
),
PopupMenuItem(
value: AlbumAction.groupByDay,
child: MenuRow(text: 'Group by day', checked: collection.groupFactor == GroupFactor.day),
),
const PopupMenuDivider(),
if (collection.sortFactor == SortFactor.date) ...[
PopupMenuItem(
value: AlbumAction.groupByAlbum,
child: MenuRow(text: 'Group by album', checked: collection.groupFactor == GroupFactor.album),
),
PopupMenuItem(
value: AlbumAction.groupByMonth,
child: MenuRow(text: 'Group by month', checked: collection.groupFactor == GroupFactor.month),
),
PopupMenuItem(
value: AlbumAction.groupByDay,
child: MenuRow(text: 'Group by day', checked: collection.groupFactor == GroupFactor.day),
),
const PopupMenuDivider(),
],
PopupMenuItem(
value: AlbumAction.debug,
child: MenuRow(text: 'Debug', icon: OMIcons.whatshot),
),
],
onSelected: (action) => _onActionSelected(context, collection, action),
),
],
floating: true,
),
PopupMenuItem(
value: AlbumAction.debug,
child: MenuRow(text: 'Debug', icon: OMIcons.whatshot),
),
],
onSelected: (action) => _onActionSelected(context, collection, action),
),
],
floating: true,
);
}

View file

@ -3,6 +3,7 @@ import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/album/thumbnail_collection.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class FilteredCollectionPage extends StatelessWidget {
final ImageCollection collection;
@ -17,11 +18,13 @@ class FilteredCollectionPage extends StatelessWidget {
Widget build(BuildContext context) {
return MediaQueryDataProvider(
child: Scaffold(
body: ThumbnailCollection(
collection: collection,
appBar: SliverAppBar(
title: Text(title),
floating: true,
body: ChangeNotifierProvider<ImageCollection>.value(
value: collection,
child: ThumbnailCollection(
appBar: SliverAppBar(
title: Text(title),
floating: true,
),
),
),
resizeToAvoidBottomInset: false,

View file

@ -4,6 +4,7 @@ import 'package:aves/widgets/album/thumbnail_collection.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:flutter/material.dart';
import 'package:outline_material_icons/outline_material_icons.dart';
import 'package:provider/provider.dart';
class ImageSearchDelegate extends SearchDelegate<ImageEntry> {
final ImageCollection collection;
@ -59,12 +60,13 @@ class ImageSearchDelegate extends SearchDelegate<ImageEntry> {
return _EmptyContent();
}
return MediaQueryDataProvider(
child: ThumbnailCollection(
collection: ImageCollection(
child: ChangeNotifierProvider<ImageCollection>.value(
value: ImageCollection(
entries: matches,
groupFactor: collection.groupFactor,
sortFactor: collection.sortFactor,
),
child: ThumbnailCollection(),
),
);
}

View file

@ -1,46 +1,24 @@
import 'package:aves/model/image_collection.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/album/collection_section.dart';
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ThumbnailCollection extends AnimatedWidget {
final ImageCollection collection;
class ThumbnailCollection extends StatelessWidget {
final Widget appBar;
const ThumbnailCollection({
Key key,
this.collection,
this.appBar,
}) : super(key: key, listenable: collection);
@override
Widget build(BuildContext context) {
return ThumbnailCollectionContent(
collection: collection,
appBar: appBar,
);
}
}
class ThumbnailCollectionContent extends StatelessWidget {
final ImageCollection collection;
final Widget appBar;
final Map<dynamic, List<ImageEntry>> _sections;
final ScrollController _scrollController = ScrollController();
ThumbnailCollectionContent({
ThumbnailCollection({
Key key,
@required this.collection,
@required this.appBar,
}) : _sections = collection.sections,
super(key: key);
this.appBar,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final sectionKeys = _sections.keys.toList();
final collection = Provider.of<ImageCollection>(context);
final sections = collection.sections;
final sectionKeys = sections.keys.toList();
double topPadding = 0;
if (appBar != null) {
final topWidget = appBar;
@ -51,40 +29,43 @@ class ThumbnailCollectionContent extends StatelessWidget {
}
}
final scrollView = CustomScrollView(
controller: _scrollController,
slivers: [
if (appBar != null) appBar,
...sectionKeys.map((sectionKey) => SectionSliver(
// need key to prevent section header mismatch
// but it should not be unique key, otherwise sections are rebuilt when changing page
key: ValueKey(sectionKey),
collection: collection,
sections: sections,
sectionKey: sectionKey,
)),
SliverToBoxAdapter(
child: Selector<MediaQueryData, double>(
selector: (c, mq) => mq.viewInsets.bottom,
builder: (c, mqViewInsetsBottom, child) {
return SizedBox(height: mqViewInsetsBottom);
},
),
),
],
);
return SafeArea(
child: Selector<MediaQueryData, double>(
selector: (c, mq) => mq.viewInsets.bottom,
builder: (c, mqViewInsetsBottom, child) => DraggableScrollbar.arrows(
controller: _scrollController,
padding: EdgeInsets.only(
// top padding to adjust scroll thumb
top: topPadding,
bottom: mqViewInsetsBottom,
),
child: CustomScrollView(
builder: (c, mqViewInsetsBottom, child) {
return DraggableScrollbar.arrows(
controller: _scrollController,
slivers: [
if (appBar != null) appBar,
...sectionKeys.map((sectionKey) {
Widget sliver = SectionSliver(
// need key to prevent section header mismatch
// but it should not be unique key, otherwise sections are rebuilt when changing page
key: ValueKey(sectionKey),
collection: collection,
sections: _sections,
sectionKey: sectionKey,
);
if (sectionKey == sectionKeys.last) {
sliver = SliverPadding(
padding: EdgeInsets.only(bottom: mqViewInsetsBottom),
sliver: sliver,
);
}
return sliver;
}),
],
),
),
padding: EdgeInsets.only(
// top padding to adjust scroll thumb
top: topPadding,
bottom: mqViewInsetsBottom,
),
child: scrollView,
);
},
),
);
}

View file

@ -36,7 +36,7 @@ class ImagePreviewState extends State<ImagePreview> with AfterInitMixin {
@override
void initState() {
debugPrint('$runtimeType initState path=${entry.path}');
// debugPrint('$runtimeType initState path=${entry.path}');
super.initState();
_entryChangeNotifier = Listenable.merge([
entry.imageChangeNotifier,

View file

@ -10,12 +10,25 @@ import 'package:provider/provider.dart';
final _stopwatch = Stopwatch()..start();
class MediaStoreCollectionProvider extends StatelessWidget {
class MediaStoreCollectionProvider extends StatefulWidget {
final Widget child;
const MediaStoreCollectionProvider({@required this.child});
@override
_MediaStoreCollectionProviderState createState() => _MediaStoreCollectionProviderState();
}
class _MediaStoreCollectionProviderState extends State<MediaStoreCollectionProvider> {
Future<ImageCollection> collectionFuture;
static const EventChannel eventChannel = EventChannel('deckers.thibault/aves/mediastore');
const MediaStoreCollectionProvider({@required this.child});
@override
void initState() {
super.initState();
collectionFuture = _create();
}
Future<ImageCollection> _create() async {
debugPrint('$runtimeType _create, elapsed=${_stopwatch.elapsed}');
@ -57,10 +70,15 @@ class MediaStoreCollectionProvider extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FutureProvider<ImageCollection>(
create: (context) => _create(),
initialData: ImageCollection(entries: []),
child: child,
return FutureBuilder(
future: collectionFuture,
builder: (futureContext, AsyncSnapshot<ImageCollection> snapshot) {
final collection = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : ImageCollection(entries: []);
return ChangeNotifierProvider<ImageCollection>.value(
value: collection,
child: widget.child,
);
},
);
}
}

View file

@ -18,8 +18,6 @@ class MetadataSection extends StatefulWidget {
class MetadataSectionState extends State<MetadataSection> {
Future<Map> _metadataLoader;
static const int maxValueLength = 140;
@override
void initState() {
super.initState();
@ -46,49 +44,71 @@ class MetadataSectionState extends State<MetadataSection> {
if (snapshot.hasError) return Text(snapshot.error.toString());
if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink();
final metadataMap = snapshot.data.cast<String, Map>();
final directoryNames = metadataMap.keys.toList()..sort();
Widget content;
if (mqWidth > 400) {
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;
}
}
content = Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: getMetadataColumn(metadataMap, first)),
const SizedBox(width: 8),
Expanded(child: getMetadataColumn(metadataMap, second)),
],
);
} else {
content = getMetadataColumn(metadataMap, directoryNames);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SectionRow('Metadata'),
content,
_MetadataSectionContent(
metadataMap: metadataMap,
split: mqWidth > 400,
),
],
);
},
),
);
}
}
Widget getMetadataColumn(Map<String, Map> metadataMap, Iterable<String> directoryNames) {
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});
static const int maxValueLength = 140;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [