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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
debugPrint('$runtimeType build');
|
debugPrint('$runtimeType build');
|
||||||
final collection = Provider.of<ImageCollection>(context);
|
|
||||||
return ThumbnailCollection(
|
return ThumbnailCollection(
|
||||||
collection: collection,
|
appBar: _AllCollectionAppBar(),
|
||||||
appBar: SliverAppBar(
|
);
|
||||||
title: const Text('All'),
|
}
|
||||||
actions: [
|
}
|
||||||
IconButton(
|
|
||||||
icon: Icon(OMIcons.search),
|
class _AllCollectionAppBar extends StatelessWidget {
|
||||||
onPressed: () => showSearch(
|
@override
|
||||||
context: context,
|
Widget build(BuildContext context) {
|
||||||
delegate: ImageSearchDelegate(collection),
|
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(
|
PopupMenuItem(
|
||||||
value: AlbumAction.sortByDate,
|
value: AlbumAction.groupByAlbum,
|
||||||
child: MenuRow(text: 'Sort by date', checked: collection.sortFactor == SortFactor.date),
|
child: MenuRow(text: 'Group by album', checked: collection.groupFactor == GroupFactor.album),
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: AlbumAction.sortBySize,
|
value: AlbumAction.groupByMonth,
|
||||||
child: MenuRow(text: 'Sort by size', checked: collection.sortFactor == SortFactor.size),
|
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(),
|
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),
|
PopupMenuItem(
|
||||||
),
|
value: AlbumAction.debug,
|
||||||
],
|
child: MenuRow(text: 'Debug', icon: OMIcons.whatshot),
|
||||||
floating: true,
|
),
|
||||||
),
|
],
|
||||||
|
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/album/thumbnail_collection.dart';
|
||||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class FilteredCollectionPage extends StatelessWidget {
|
class FilteredCollectionPage extends StatelessWidget {
|
||||||
final ImageCollection collection;
|
final ImageCollection collection;
|
||||||
|
@ -17,11 +18,13 @@ class FilteredCollectionPage extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MediaQueryDataProvider(
|
return MediaQueryDataProvider(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: ThumbnailCollection(
|
body: ChangeNotifierProvider<ImageCollection>.value(
|
||||||
collection: collection,
|
value: collection,
|
||||||
appBar: SliverAppBar(
|
child: ThumbnailCollection(
|
||||||
title: Text(title),
|
appBar: SliverAppBar(
|
||||||
floating: true,
|
title: Text(title),
|
||||||
|
floating: true,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
resizeToAvoidBottomInset: false,
|
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:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:outline_material_icons/outline_material_icons.dart';
|
import 'package:outline_material_icons/outline_material_icons.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ImageSearchDelegate extends SearchDelegate<ImageEntry> {
|
class ImageSearchDelegate extends SearchDelegate<ImageEntry> {
|
||||||
final ImageCollection collection;
|
final ImageCollection collection;
|
||||||
|
@ -59,12 +60,13 @@ class ImageSearchDelegate extends SearchDelegate<ImageEntry> {
|
||||||
return _EmptyContent();
|
return _EmptyContent();
|
||||||
}
|
}
|
||||||
return MediaQueryDataProvider(
|
return MediaQueryDataProvider(
|
||||||
child: ThumbnailCollection(
|
child: ChangeNotifierProvider<ImageCollection>.value(
|
||||||
collection: ImageCollection(
|
value: ImageCollection(
|
||||||
entries: matches,
|
entries: matches,
|
||||||
groupFactor: collection.groupFactor,
|
groupFactor: collection.groupFactor,
|
||||||
sortFactor: collection.sortFactor,
|
sortFactor: collection.sortFactor,
|
||||||
),
|
),
|
||||||
|
child: ThumbnailCollection(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,24 @@
|
||||||
import 'package:aves/model/image_collection.dart';
|
import 'package:aves/model/image_collection.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
|
||||||
import 'package:aves/widgets/album/collection_section.dart';
|
import 'package:aves/widgets/album/collection_section.dart';
|
||||||
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
|
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ThumbnailCollection extends AnimatedWidget {
|
class ThumbnailCollection extends StatelessWidget {
|
||||||
final ImageCollection collection;
|
|
||||||
final Widget appBar;
|
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();
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
ThumbnailCollectionContent({
|
ThumbnailCollection({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.collection,
|
this.appBar,
|
||||||
@required this.appBar,
|
}) : super(key: key);
|
||||||
}) : _sections = collection.sections,
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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;
|
double topPadding = 0;
|
||||||
if (appBar != null) {
|
if (appBar != null) {
|
||||||
final topWidget = appBar;
|
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(
|
return SafeArea(
|
||||||
child: Selector<MediaQueryData, double>(
|
child: Selector<MediaQueryData, double>(
|
||||||
selector: (c, mq) => mq.viewInsets.bottom,
|
selector: (c, mq) => mq.viewInsets.bottom,
|
||||||
builder: (c, mqViewInsetsBottom, child) => DraggableScrollbar.arrows(
|
builder: (c, mqViewInsetsBottom, child) {
|
||||||
controller: _scrollController,
|
return DraggableScrollbar.arrows(
|
||||||
padding: EdgeInsets.only(
|
|
||||||
// top padding to adjust scroll thumb
|
|
||||||
top: topPadding,
|
|
||||||
bottom: mqViewInsetsBottom,
|
|
||||||
),
|
|
||||||
child: CustomScrollView(
|
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
slivers: [
|
padding: EdgeInsets.only(
|
||||||
if (appBar != null) appBar,
|
// top padding to adjust scroll thumb
|
||||||
...sectionKeys.map((sectionKey) {
|
top: topPadding,
|
||||||
Widget sliver = SectionSliver(
|
bottom: mqViewInsetsBottom,
|
||||||
// need key to prevent section header mismatch
|
),
|
||||||
// but it should not be unique key, otherwise sections are rebuilt when changing page
|
child: scrollView,
|
||||||
key: ValueKey(sectionKey),
|
);
|
||||||
collection: collection,
|
},
|
||||||
sections: _sections,
|
|
||||||
sectionKey: sectionKey,
|
|
||||||
);
|
|
||||||
if (sectionKey == sectionKeys.last) {
|
|
||||||
sliver = SliverPadding(
|
|
||||||
padding: EdgeInsets.only(bottom: mqViewInsetsBottom),
|
|
||||||
sliver: sliver,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return sliver;
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ class ImagePreviewState extends State<ImagePreview> with AfterInitMixin {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
debugPrint('$runtimeType initState path=${entry.path}');
|
// debugPrint('$runtimeType initState path=${entry.path}');
|
||||||
super.initState();
|
super.initState();
|
||||||
_entryChangeNotifier = Listenable.merge([
|
_entryChangeNotifier = Listenable.merge([
|
||||||
entry.imageChangeNotifier,
|
entry.imageChangeNotifier,
|
||||||
|
|
|
@ -10,12 +10,25 @@ import 'package:provider/provider.dart';
|
||||||
|
|
||||||
final _stopwatch = Stopwatch()..start();
|
final _stopwatch = Stopwatch()..start();
|
||||||
|
|
||||||
class MediaStoreCollectionProvider extends StatelessWidget {
|
class MediaStoreCollectionProvider extends StatefulWidget {
|
||||||
final Widget child;
|
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');
|
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 {
|
Future<ImageCollection> _create() async {
|
||||||
debugPrint('$runtimeType _create, elapsed=${_stopwatch.elapsed}');
|
debugPrint('$runtimeType _create, elapsed=${_stopwatch.elapsed}');
|
||||||
|
@ -57,10 +70,15 @@ class MediaStoreCollectionProvider extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FutureProvider<ImageCollection>(
|
return FutureBuilder(
|
||||||
create: (context) => _create(),
|
future: collectionFuture,
|
||||||
initialData: ImageCollection(entries: []),
|
builder: (futureContext, AsyncSnapshot<ImageCollection> snapshot) {
|
||||||
child: child,
|
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> {
|
class MetadataSectionState extends State<MetadataSection> {
|
||||||
Future<Map> _metadataLoader;
|
Future<Map> _metadataLoader;
|
||||||
|
|
||||||
static const int maxValueLength = 140;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -46,49 +44,71 @@ class MetadataSectionState extends State<MetadataSection> {
|
||||||
if (snapshot.hasError) return Text(snapshot.error.toString());
|
if (snapshot.hasError) return Text(snapshot.error.toString());
|
||||||
if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink();
|
if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink();
|
||||||
final metadataMap = snapshot.data.cast<String, Map>();
|
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(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
const SectionRow('Metadata'),
|
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(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
Loading…
Reference in a new issue