aves/lib/widgets/album/grid/header_generic.dart
2020-04-23 11:21:05 +09:00

216 lines
7.7 KiB
Dart

import 'dart:math';
import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/collection_source.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/album/grid/header_album.dart';
import 'package:aves/widgets/album/grid/header_date.dart';
import 'package:aves/widgets/common/fx/outlined_text.dart';
import 'package:aves/widgets/common/icons.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
class SectionHeader extends StatelessWidget {
final CollectionLens collection;
final dynamic sectionKey;
const SectionHeader({
Key key,
@required this.collection,
@required this.sectionKey,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Widget header;
switch (collection.sortFactor) {
case SortFactor.date:
if (collection.sortFactor == SortFactor.date) {
switch (collection.groupFactor) {
case GroupFactor.album:
header = _buildAlbumSectionHeader();
break;
case GroupFactor.month:
header = MonthSectionHeader(key: ValueKey(sectionKey), date: sectionKey as DateTime);
break;
case GroupFactor.day:
header = DaySectionHeader(key: ValueKey(sectionKey), date: sectionKey as DateTime);
break;
}
}
break;
case SortFactor.size:
break;
case SortFactor.name:
header = _buildAlbumSectionHeader();
break;
}
return header ?? const SizedBox.shrink();
}
Widget _buildAlbumSectionHeader() {
final folderPath = sectionKey as String;
return AlbumSectionHeader(
key: ValueKey(folderPath),
folderPath: folderPath,
albumName: collection.source.getUniqueAlbumName(folderPath),
);
}
// TODO TLAD cache header extent computation?
static double computeHeaderHeight(CollectionSource source, dynamic sectionKey, double scrollableWidth) {
var headerExtent = 0.0;
if (sectionKey is String) {
// only compute height for album headers, as they're the only likely ones to split on multiple lines
final hasLeading = androidFileUtils.getAlbumType(sectionKey) != AlbumType.Default;
final hasTrailing = androidFileUtils.isOnSD(sectionKey);
final text = source.getUniqueAlbumName(sectionKey);
final maxWidth = scrollableWidth - TitleSectionHeader.padding.horizontal;
final para = RenderParagraph(
TextSpan(
children: [
if (hasLeading)
// `RenderParagraph` fails to lay out `WidgetSpan` offscreen as of Flutter v1.17.0
// so we use a hair space times a magic number to match leading width
TextSpan(text: '\u200A' * 23), // 23 hair spaces match a width of 40.0
if (hasTrailing)
TextSpan(text: '\u200A' * 17),
TextSpan(
text: text,
style: Constants.titleTextStyle,
),
],
),
textDirection: TextDirection.ltr,
)..layout(BoxConstraints(maxWidth: maxWidth), parentUsesSize: true);
headerExtent = para.getMaxIntrinsicHeight(maxWidth);
}
headerExtent = max(headerExtent, TitleSectionHeader.leadingDimension) + TitleSectionHeader.padding.vertical;
return headerExtent;
}
}
class TitleSectionHeader extends StatelessWidget {
final dynamic sectionKey;
final Widget leading, trailing;
final String title;
const TitleSectionHeader({
Key key,
@required this.sectionKey,
this.leading,
@required this.title,
this.trailing,
}) : super(key: key);
static const leadingDimension = 32.0;
static const leadingPadding = EdgeInsets.only(right: 8, bottom: 4);
static const trailingPadding = EdgeInsets.only(left: 8, bottom: 4);
static const padding = EdgeInsets.all(16);
@override
Widget build(BuildContext context) {
return Container(
alignment: AlignmentDirectional.centerStart,
padding: padding,
constraints: const BoxConstraints(minHeight: leadingDimension),
child: OutlinedText(
leadingBuilder: (context, isShadow) => SectionSelectableLeading(
sectionKey: sectionKey,
browsingBuilder: leading != null
? (context) => Container(
padding: leadingPadding,
width: leadingDimension,
height: leadingDimension,
child: isShadow ? null : leading,
)
: null,
),
text: title,
trailingBuilder: trailing != null
? (context, isShadow) => Container(
padding: trailingPadding,
child: isShadow ? null : trailing,
)
: null,
style: Constants.titleTextStyle,
outlineWidth: 2,
),
);
}
}
class SectionSelectableLeading extends StatelessWidget {
final dynamic sectionKey;
final WidgetBuilder browsingBuilder;
static final WidgetBuilder _defaultBrowsingBuilder = (context) => const SizedBox.shrink();
SectionSelectableLeading({
Key key,
@required this.sectionKey,
WidgetBuilder browsingBuilder,
}) : browsingBuilder = browsingBuilder ?? _defaultBrowsingBuilder,
super(key: key);
@override
Widget build(BuildContext context) {
final collection = Provider.of<CollectionLens>(context);
return ValueListenableBuilder<Activity>(
valueListenable: collection.activityNotifier,
builder: (context, activity, child) {
final child = collection.isSelecting
? AnimatedBuilder(
animation: collection.selectionChangeNotifier,
builder: (context, child) {
final sectionEntries = collection.sections[sectionKey];
final selected = collection.isSelected(sectionEntries);
final child = IconButton(
key: ValueKey(selected),
iconSize: 26,
padding: const EdgeInsets.only(top: 1),
alignment: Alignment.topLeft,
icon: Icon(selected ? AIcons.selected : AIcons.unselected),
onPressed: () {
if (selected) {
collection.removeFromSelection(sectionEntries);
} else {
collection.addToSelection(sectionEntries);
}
},
tooltip: selected ? 'Deselect section' : 'Select section',
constraints: const BoxConstraints(
minHeight: TitleSectionHeader.leadingDimension,
minWidth: TitleSectionHeader.leadingDimension,
),
);
return AnimatedSwitcher(
duration: Duration(milliseconds: (300 * timeDilation).toInt()),
switchInCurve: Curves.easeOutBack,
switchOutCurve: Curves.easeOutBack,
transitionBuilder: (child, animation) => ScaleTransition(
child: child,
scale: animation,
),
child: child,
);
},
)
: browsingBuilder(context);
return AnimatedSwitcher(
duration: Duration(milliseconds: (300 * timeDilation).toInt()),
switchInCurve: Curves.easeOutBack,
switchOutCurve: Curves.easeOutBack,
transitionBuilder: (child, animation) => ScaleTransition(
child: child,
scale: animation,
),
child: child,
);
},
);
}
}