164 lines
5.3 KiB
Dart
164 lines
5.3 KiB
Dart
import 'dart:math';
|
|
|
|
import 'package:aves/model/collection_lens.dart';
|
|
import 'package:aves/model/image_entry.dart';
|
|
import 'package:aves/widgets/album/grid/header_generic.dart';
|
|
import 'package:aves/widgets/album/grid/list_known_extent.dart';
|
|
import 'package:aves/widgets/album/grid/list_section_layout.dart';
|
|
import 'package:aves/widgets/album/thumbnail.dart';
|
|
import 'package:aves/widgets/album/transparent_material_page_route.dart';
|
|
import 'package:aves/widgets/fullscreen/fullscreen_page.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
|
|
// use a `SliverList` instead of multiple `SliverGrid` because having one `SliverGrid` per section does not scale up
|
|
// with the multiple `SliverGrid` solution, thumbnails at the beginning of each sections are built even though they are offscreen
|
|
// because of `RenderSliverMultiBoxAdaptor.addInitialChild` called by `RenderSliverGrid.performLayout` (line 547), as of Flutter v1.17.0
|
|
class CollectionListSliver extends StatelessWidget {
|
|
final CollectionLens collection;
|
|
final bool showHeader;
|
|
final double scrollableWidth;
|
|
final int columnCount;
|
|
final double tileExtent;
|
|
|
|
CollectionListSliver({
|
|
Key key,
|
|
@required this.collection,
|
|
@required this.showHeader,
|
|
@required this.scrollableWidth,
|
|
@required this.tileExtent,
|
|
}) : columnCount = (scrollableWidth / tileExtent).round(),
|
|
super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final sectionLayouts = <SectionLayout>[];
|
|
final source = collection.source;
|
|
final sections = collection.sections;
|
|
final sectionKeys = sections.keys.toList();
|
|
var currentIndex = 0, currentOffset = 0.0;
|
|
sectionKeys.forEach((sectionKey) {
|
|
final sectionEntryCount = sections[sectionKey].length;
|
|
final sectionChildCount = 1 + (sectionEntryCount / columnCount).ceil();
|
|
|
|
final headerExtent = showHeader ? SectionHeader.computeHeaderHeight(source, sectionKey, scrollableWidth) : 0.0;
|
|
|
|
final sectionFirstIndex = currentIndex;
|
|
currentIndex += sectionChildCount;
|
|
final sectionLastIndex = currentIndex - 1;
|
|
|
|
final sectionMinOffset = currentOffset;
|
|
currentOffset += headerExtent + tileExtent * (sectionChildCount - 1);
|
|
final sectionMaxOffset = currentOffset;
|
|
|
|
sectionLayouts.add(
|
|
SectionLayout(
|
|
sectionKey: sectionKey,
|
|
firstIndex: sectionFirstIndex,
|
|
lastIndex: sectionLastIndex,
|
|
minOffset: sectionMinOffset,
|
|
maxOffset: sectionMaxOffset,
|
|
headerExtent: headerExtent,
|
|
tileExtent: tileExtent,
|
|
builder: (context, listIndex) {
|
|
listIndex -= sectionFirstIndex;
|
|
if (listIndex == 0) {
|
|
return showHeader
|
|
? SectionHeader(
|
|
collection: collection,
|
|
sections: sections,
|
|
sectionKey: sectionKey,
|
|
)
|
|
: const SizedBox.shrink();
|
|
}
|
|
listIndex--;
|
|
|
|
final section = sections[sectionKey];
|
|
final minEntryIndex = listIndex * columnCount;
|
|
final maxEntryIndex = min(sectionEntryCount, minEntryIndex + columnCount);
|
|
final children = <Widget>[];
|
|
for (var i = minEntryIndex; i < maxEntryIndex; i++) {
|
|
children.add(GridThumbnail(
|
|
collection: collection,
|
|
index: i,
|
|
entry: section[i],
|
|
tileExtent: tileExtent,
|
|
));
|
|
}
|
|
return Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: children,
|
|
);
|
|
},
|
|
),
|
|
);
|
|
});
|
|
final childCount = currentIndex;
|
|
|
|
return SliverKnownExtentList(
|
|
sectionLayouts: sectionLayouts,
|
|
delegate: SliverChildBuilderDelegate(
|
|
(context, index) {
|
|
if (index >= childCount) return null;
|
|
final sectionLayout = sectionLayouts.firstWhere((section) => section.hasChild(index), orElse: () => null);
|
|
return sectionLayout?.builder(context, index) ?? const SizedBox.shrink();
|
|
},
|
|
childCount: childCount,
|
|
addAutomaticKeepAlives: false,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class GridThumbnail extends StatelessWidget {
|
|
final CollectionLens collection;
|
|
final int index;
|
|
final ImageEntry entry;
|
|
final double tileExtent;
|
|
final GestureTapCallback onTap;
|
|
|
|
const GridThumbnail({
|
|
Key key,
|
|
this.collection,
|
|
this.index,
|
|
this.entry,
|
|
this.tileExtent,
|
|
this.onTap,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
key: ValueKey(entry.uri),
|
|
onTap: () => _goToFullscreen(context),
|
|
child: MetaData(
|
|
metaData: ThumbnailMetadata(index, entry),
|
|
child: Thumbnail(
|
|
entry: entry,
|
|
extent: tileExtent,
|
|
heroTag: collection.heroTag(entry),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _goToFullscreen(BuildContext context) {
|
|
Navigator.push(
|
|
context,
|
|
TransparentMaterialPageRoute(
|
|
pageBuilder: (c, a, sa) => MultiFullscreenPage(
|
|
collection: collection,
|
|
initialEntry: entry,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// metadata to identify entry from RenderObject hit test during collection scaling
|
|
class ThumbnailMetadata {
|
|
final int index;
|
|
final ImageEntry entry;
|
|
|
|
const ThumbnailMetadata(this.index, this.entry);
|
|
}
|