split widgets, listen to collection by ChangeNotifierProvider instead of AnimatedWidget
This commit is contained in:
parent
07f073bd77
commit
935227f2e3
7 changed files with 178 additions and 148 deletions
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: [
|
||||
|
|
Loading…
Reference in a new issue