aves/lib/widgets/album/grid/list_section_layout.dart
2020-07-26 03:03:07 +09:00

188 lines
6.3 KiB
Dart

import 'dart:math';
import 'package:aves/model/image_entry.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/widgets/album/grid/header_generic.dart';
import 'package:aves/widgets/album/grid/tile_extent_manager.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class SectionedListLayoutProvider extends StatelessWidget {
final CollectionLens collection;
final int columnCount;
final double scrollableWidth;
final double tileExtent;
final Widget Function(ImageEntry entry) thumbnailBuilder;
final Widget child;
SectionedListLayoutProvider({
@required this.collection,
@required this.scrollableWidth,
@required this.tileExtent,
@required this.thumbnailBuilder,
@required this.child,
}) : assert(scrollableWidth != 0),
columnCount = max((scrollableWidth / tileExtent).round(), TileExtentManager.columnCountMin);
@override
Widget build(BuildContext context) {
return ProxyProvider0<SectionedListLayout>(
update: (context, __) => _updateLayouts(context),
child: child,
);
}
SectionedListLayout _updateLayouts(BuildContext context) {
// debugPrint('$runtimeType _updateLayouts entries=${collection.entryCount} columnCount=$columnCount tileExtent=$tileExtent');
final sectionLayouts = <SectionLayout>[];
final showHeaders = collection.showHeaders;
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 = showHeaders ? SectionHeader.computeHeaderHeight(context, 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) => _buildInSection(
listIndex - sectionFirstIndex,
collection,
sectionKey,
headerExtent,
),
),
);
});
return SectionedListLayout(
collection: collection,
columnCount: columnCount,
tileExtent: tileExtent,
sectionLayouts: sectionLayouts,
);
}
Widget _buildInSection(int sectionChildIndex, CollectionLens collection, dynamic sectionKey, double headerExtent) {
if (sectionChildIndex == 0) {
return headerBuilder(collection, sectionKey, headerExtent);
}
sectionChildIndex--;
final section = collection.sections[sectionKey];
final sectionEntryCount = section.length;
final minEntryIndex = sectionChildIndex * columnCount;
final maxEntryIndex = min(sectionEntryCount, minEntryIndex + columnCount);
final children = <Widget>[];
for (var i = minEntryIndex; i < maxEntryIndex; i++) {
final entry = section[i];
children.add(thumbnailBuilder(entry));
}
return Row(
mainAxisSize: MainAxisSize.min,
children: children,
);
}
Widget headerBuilder(CollectionLens collection, dynamic sectionKey, double headerExtent) {
return collection.showHeaders
? SectionHeader(
collection: collection,
sectionKey: sectionKey,
height: headerExtent,
)
: const SizedBox.shrink();
}
}
class SectionedListLayout {
final CollectionLens collection;
final int columnCount;
final double tileExtent;
final List<SectionLayout> sectionLayouts;
const SectionedListLayout({
@required this.collection,
@required this.columnCount,
@required this.tileExtent,
@required this.sectionLayouts,
});
Rect getTileRect(ImageEntry entry) {
final section = collection.sections.entries.firstWhere((kv) => kv.value.contains(entry), orElse: () => null);
if (section == null) return null;
final sectionKey = section.key;
final sectionLayout = sectionLayouts.firstWhere((sl) => sl.sectionKey == sectionKey, orElse: () => null);
if (sectionLayout == null) return null;
final showHeaders = collection.showHeaders;
final sectionEntryIndex = section.value.indexOf(entry);
final column = sectionEntryIndex % columnCount;
final row = (sectionEntryIndex / columnCount).floor();
final listIndex = sectionLayout.firstIndex + (showHeaders ? 1 : 0) + row;
final left = tileExtent * column;
final top = sectionLayout.indexToLayoutOffset(listIndex);
return Rect.fromLTWH(left, top, tileExtent, tileExtent);
}
}
class SectionLayout {
final dynamic sectionKey;
final int firstIndex, lastIndex;
final double minOffset, maxOffset;
final double headerExtent, tileExtent;
final IndexedWidgetBuilder builder;
const SectionLayout({
@required this.sectionKey,
@required this.firstIndex,
@required this.lastIndex,
@required this.minOffset,
@required this.maxOffset,
@required this.headerExtent,
@required this.tileExtent,
@required this.builder,
});
bool hasChild(int index) => firstIndex <= index && index <= lastIndex;
bool hasChildAtOffset(double scrollOffset) => minOffset <= scrollOffset && scrollOffset <= maxOffset;
double indexToLayoutOffset(int index) {
return minOffset + (index == firstIndex ? 0 : headerExtent + (index - firstIndex - 1) * tileExtent);
}
double indexToMaxScrollOffset(int index) {
return minOffset + headerExtent + (index - firstIndex) * tileExtent;
}
int getMinChildIndexForScrollOffset(double scrollOffset) {
scrollOffset -= minOffset + headerExtent;
return firstIndex + (scrollOffset < 0 ? 0 : (scrollOffset / tileExtent).floor());
}
int getMaxChildIndexForScrollOffset(double scrollOffset) {
scrollOffset -= minOffset + headerExtent;
return firstIndex + (scrollOffset < 0 ? 0 : (scrollOffset / tileExtent).ceil() - 1);
}
}