#226 collection: fast-scrolling shows breadcrumbs from groups
This commit is contained in:
parent
839f19a141
commit
987a7dfe70
7 changed files with 250 additions and 54 deletions
|
@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
- Bottom navigation bar
|
- Bottom navigation bar
|
||||||
- Collection: thumbnail overlay tag icon
|
- Collection: thumbnail overlay tag icon
|
||||||
|
- Collection: fast-scrolling shows breadcrumbs from groups
|
||||||
- Settings: search
|
- Settings: search
|
||||||
- `huawei` app flavor (Petal Maps, no Crashlytics)
|
- `huawei` app flavor (Petal Maps, no Crashlytics)
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import 'package:aves/model/filters/mime.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums.dart';
|
||||||
|
import 'package:aves/model/source/section_keys.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
|
@ -21,8 +22,10 @@ import 'package:aves/widgets/common/basic/insets.dart';
|
||||||
import 'package:aves/widgets/common/behaviour/sloppy_scroll_physics.dart';
|
import 'package:aves/widgets/common/behaviour/sloppy_scroll_physics.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/extensions/media_query.dart';
|
import 'package:aves/widgets/common/extensions/media_query.dart';
|
||||||
|
import 'package:aves/widgets/common/grid/draggable_thumb_label.dart';
|
||||||
import 'package:aves/widgets/common/grid/item_tracker.dart';
|
import 'package:aves/widgets/common/grid/item_tracker.dart';
|
||||||
import 'package:aves/widgets/common/grid/scaling.dart';
|
import 'package:aves/widgets/common/grid/scaling.dart';
|
||||||
|
import 'package:aves/widgets/common/grid/section_layout.dart';
|
||||||
import 'package:aves/widgets/common/grid/selector.dart';
|
import 'package:aves/widgets/common/grid/selector.dart';
|
||||||
import 'package:aves/widgets/common/grid/sliver.dart';
|
import 'package:aves/widgets/common/grid/sliver.dart';
|
||||||
import 'package:aves/widgets/common/grid/theme.dart';
|
import 'package:aves/widgets/common/grid/theme.dart';
|
||||||
|
@ -34,6 +37,7 @@ import 'package:aves/widgets/common/tile_extent_controller.dart';
|
||||||
import 'package:aves/widgets/navigation/nav_bar/nav_bar.dart';
|
import 'package:aves/widgets/navigation/nav_bar/nav_bar.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
@ -347,26 +351,34 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> {
|
||||||
valueListenable: widget.appBarHeightNotifier,
|
valueListenable: widget.appBarHeightNotifier,
|
||||||
builder: (context, appBarHeight, child) => Selector<MediaQueryData, double>(
|
builder: (context, appBarHeight, child) => Selector<MediaQueryData, double>(
|
||||||
selector: (context, mq) => mq.effectiveBottomPadding,
|
selector: (context, mq) => mq.effectiveBottomPadding,
|
||||||
builder: (context, mqPaddingBottom, child) => DraggableScrollbar(
|
builder: (context, mqPaddingBottom, child) {
|
||||||
backgroundColor: Colors.white,
|
return Selector<SectionedListLayout<AvesEntry>, List<SectionLayout>>(
|
||||||
scrollThumbHeight: avesScrollThumbHeight,
|
selector: (context, layout) => layout.sectionLayouts,
|
||||||
scrollThumbBuilder: avesScrollThumbBuilder(
|
builder: (context, sectionLayouts, child) {
|
||||||
height: avesScrollThumbHeight,
|
return DraggableScrollbar(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
),
|
scrollThumbSize: Size(avesScrollThumbWidth, avesScrollThumbHeight),
|
||||||
controller: widget.scrollController,
|
scrollThumbBuilder: avesScrollThumbBuilder(
|
||||||
padding: EdgeInsets.only(
|
height: avesScrollThumbHeight,
|
||||||
// padding to keep scroll thumb between app bar above and nav bar below
|
backgroundColor: Colors.white,
|
||||||
top: appBarHeight,
|
),
|
||||||
bottom: mqPaddingBottom,
|
controller: widget.scrollController,
|
||||||
),
|
crumbsBuilder: () => _getCrumbs(sectionLayouts),
|
||||||
labelTextBuilder: (offsetY) => CollectionDraggableThumbLabel(
|
padding: EdgeInsets.only(
|
||||||
collection: collection,
|
// padding to keep scroll thumb between app bar above and nav bar below
|
||||||
offsetY: offsetY,
|
top: appBarHeight,
|
||||||
),
|
bottom: mqPaddingBottom,
|
||||||
child: scrollView,
|
),
|
||||||
),
|
labelTextBuilder: (offsetY) => CollectionDraggableThumbLabel(
|
||||||
child: child,
|
collection: collection,
|
||||||
|
offsetY: offsetY,
|
||||||
|
),
|
||||||
|
crumbTextBuilder: (label) => DraggableCrumbLabel(label: label),
|
||||||
|
child: scrollView,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -433,4 +445,64 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> {
|
||||||
void _stopScrollMonitoringTimer() {
|
void _stopScrollMonitoringTimer() {
|
||||||
_scrollMonitoringTimer?.cancel();
|
_scrollMonitoringTimer?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<double, String> _getCrumbs(List<SectionLayout> sectionLayouts) {
|
||||||
|
final crumbs = <double, String>{};
|
||||||
|
if (sectionLayouts.length <= 1) return crumbs;
|
||||||
|
|
||||||
|
void addAlbums(CollectionLens collection, List<SectionLayout> sectionLayouts, Map<double, String> crumbs) {
|
||||||
|
final source = collection.source;
|
||||||
|
sectionLayouts.forEach((section) {
|
||||||
|
final directory = (section.sectionKey as EntryAlbumSectionKey).directory;
|
||||||
|
if (directory != null) {
|
||||||
|
final label = source.getAlbumDisplayName(context, directory);
|
||||||
|
crumbs[section.minOffset] = label;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final collection = widget.collection;
|
||||||
|
switch (collection.sortFactor) {
|
||||||
|
case EntrySortFactor.date:
|
||||||
|
switch (collection.sectionFactor) {
|
||||||
|
case EntryGroupFactor.album:
|
||||||
|
addAlbums(collection, sectionLayouts, crumbs);
|
||||||
|
break;
|
||||||
|
case EntryGroupFactor.month:
|
||||||
|
case EntryGroupFactor.day:
|
||||||
|
final firstKey = sectionLayouts.first.sectionKey;
|
||||||
|
final lastKey = sectionLayouts.last.sectionKey;
|
||||||
|
if (firstKey is EntryDateSectionKey && lastKey is EntryDateSectionKey) {
|
||||||
|
final newest = firstKey.date;
|
||||||
|
final oldest = lastKey.date;
|
||||||
|
if (newest != null && oldest != null) {
|
||||||
|
final localeName = context.l10n.localeName;
|
||||||
|
final dateFormat = newest.difference(oldest).inDays > 365 ? DateFormat.y(localeName) : DateFormat.MMM(localeName);
|
||||||
|
String? lastLabel;
|
||||||
|
sectionLayouts.forEach((section) {
|
||||||
|
final date = (section.sectionKey as EntryDateSectionKey).date;
|
||||||
|
if (date != null) {
|
||||||
|
final label = dateFormat.format(date);
|
||||||
|
if (label != lastLabel) {
|
||||||
|
crumbs[section.minOffset] = label;
|
||||||
|
lastLabel = label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EntryGroupFactor.none:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EntrySortFactor.name:
|
||||||
|
addAlbums(collection, sectionLayouts, crumbs);
|
||||||
|
break;
|
||||||
|
case EntrySortFactor.rating:
|
||||||
|
case EntrySortFactor.size:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return crumbs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,8 @@ typedef ScrollThumbBuilder = Widget Function(
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Build a Text widget using the current scroll offset
|
/// Build a Text widget using the current scroll offset
|
||||||
typedef LabelTextBuilder = Widget Function(double offsetY);
|
typedef OffsetLabelBuilder = Widget Function(double offsetY);
|
||||||
|
typedef TextLabelBuilder = Widget Function(String label);
|
||||||
|
|
||||||
/// A widget that will display a BoxScrollView with a ScrollThumb that can be dragged
|
/// A widget that will display a BoxScrollView with a ScrollThumb that can be dragged
|
||||||
/// for quick navigation of the BoxScrollView.
|
/// for quick navigation of the BoxScrollView.
|
||||||
|
@ -32,14 +33,15 @@ class DraggableScrollbar extends StatefulWidget {
|
||||||
/// The background color of the label and thumb
|
/// The background color of the label and thumb
|
||||||
final Color backgroundColor;
|
final Color backgroundColor;
|
||||||
|
|
||||||
/// The height of the scroll thumb
|
final Map<double, String> Function()? crumbsBuilder;
|
||||||
final double scrollThumbHeight;
|
|
||||||
|
final Size scrollThumbSize;
|
||||||
|
|
||||||
/// A function that builds a thumb using the current configuration
|
/// A function that builds a thumb using the current configuration
|
||||||
final ScrollThumbBuilder scrollThumbBuilder;
|
final ScrollThumbBuilder scrollThumbBuilder;
|
||||||
|
|
||||||
/// The amount of padding that should surround the thumb
|
/// The amount of padding that should surround the thumb
|
||||||
final EdgeInsets? padding;
|
final EdgeInsets padding;
|
||||||
|
|
||||||
/// Determines how quickly the scrollbar will animate in and out
|
/// Determines how quickly the scrollbar will animate in and out
|
||||||
final Duration scrollbarAnimationDuration;
|
final Duration scrollbarAnimationDuration;
|
||||||
|
@ -48,7 +50,9 @@ class DraggableScrollbar extends StatefulWidget {
|
||||||
final Duration scrollbarTimeToFade;
|
final Duration scrollbarTimeToFade;
|
||||||
|
|
||||||
/// Build a Text widget from the current offset in the BoxScrollView
|
/// Build a Text widget from the current offset in the BoxScrollView
|
||||||
final LabelTextBuilder? labelTextBuilder;
|
final OffsetLabelBuilder labelTextBuilder;
|
||||||
|
|
||||||
|
final TextLabelBuilder crumbTextBuilder;
|
||||||
|
|
||||||
/// The ScrollController for the BoxScrollView
|
/// The ScrollController for the BoxScrollView
|
||||||
final ScrollController controller;
|
final ScrollController controller;
|
||||||
|
@ -59,13 +63,15 @@ class DraggableScrollbar extends StatefulWidget {
|
||||||
DraggableScrollbar({
|
DraggableScrollbar({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.backgroundColor,
|
required this.backgroundColor,
|
||||||
required this.scrollThumbHeight,
|
required this.scrollThumbSize,
|
||||||
required this.scrollThumbBuilder,
|
required this.scrollThumbBuilder,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
this.padding,
|
this.crumbsBuilder,
|
||||||
|
this.padding = EdgeInsets.zero,
|
||||||
this.scrollbarAnimationDuration = const Duration(milliseconds: 300),
|
this.scrollbarAnimationDuration = const Duration(milliseconds: 300),
|
||||||
this.scrollbarTimeToFade = const Duration(milliseconds: 1000),
|
this.scrollbarTimeToFade = const Duration(milliseconds: 1000),
|
||||||
this.labelTextBuilder,
|
required this.labelTextBuilder,
|
||||||
|
required this.crumbTextBuilder,
|
||||||
required this.child,
|
required this.child,
|
||||||
}) : assert(child.scrollDirection == Axis.vertical),
|
}) : assert(child.scrollDirection == Axis.vertical),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
@ -73,6 +79,8 @@ class DraggableScrollbar extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
State<DraggableScrollbar> createState() => _DraggableScrollbarState();
|
State<DraggableScrollbar> createState() => _DraggableScrollbarState();
|
||||||
|
|
||||||
|
static const double labelThumbPadding = 16;
|
||||||
|
|
||||||
static Widget buildScrollThumbAndLabel({
|
static Widget buildScrollThumbAndLabel({
|
||||||
required Widget scrollThumb,
|
required Widget scrollThumb,
|
||||||
required Color backgroundColor,
|
required Color backgroundColor,
|
||||||
|
@ -91,7 +99,7 @@ class DraggableScrollbar extends StatefulWidget {
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
child: labelText,
|
child: labelText,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 24),
|
const SizedBox(width: labelThumbPadding),
|
||||||
scrollThumb,
|
scrollThumb,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -141,6 +149,11 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
||||||
late AnimationController _labelAnimationController;
|
late AnimationController _labelAnimationController;
|
||||||
late Animation<double> _labelAnimation;
|
late Animation<double> _labelAnimation;
|
||||||
Timer? _fadeoutTimer;
|
Timer? _fadeoutTimer;
|
||||||
|
Map<double, String>? _modelCrumbs;
|
||||||
|
final List<_Crumb> _viewportCrumbs = [];
|
||||||
|
|
||||||
|
static const crumbPadding = 30.0;
|
||||||
|
static const crumbOffsetRatioThreshold = 10;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -167,6 +180,15 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant DraggableScrollbar oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|
||||||
|
if (oldWidget.crumbsBuilder != widget.crumbsBuilder) {
|
||||||
|
_modelCrumbs = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_thumbAnimationController.dispose();
|
_thumbAnimationController.dispose();
|
||||||
|
@ -177,7 +199,9 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
||||||
|
|
||||||
ScrollController get controller => widget.controller;
|
ScrollController get controller => widget.controller;
|
||||||
|
|
||||||
double get thumbMaxScrollExtent => context.size!.height - widget.scrollThumbHeight - (widget.padding?.vertical ?? 0.0);
|
double get scrollBarHeight => context.size!.height - widget.padding.vertical;
|
||||||
|
|
||||||
|
double get thumbMaxScrollExtent => scrollBarHeight - widget.scrollThumbSize.height;
|
||||||
|
|
||||||
double get thumbMinScrollExtent => 0.0;
|
double get thumbMinScrollExtent => 0.0;
|
||||||
|
|
||||||
|
@ -193,6 +217,27 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
||||||
RepaintBoundary(
|
RepaintBoundary(
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
),
|
),
|
||||||
|
if (_isDragInProcess)
|
||||||
|
..._viewportCrumbs.map((crumb) {
|
||||||
|
return Positioned.directional(
|
||||||
|
textDirection: Directionality.of(context),
|
||||||
|
top: crumb.labelOffset,
|
||||||
|
end: DraggableScrollbar.labelThumbPadding + widget.scrollThumbSize.width,
|
||||||
|
child: Padding(
|
||||||
|
padding: widget.padding,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(minHeight: widget.scrollThumbSize.height),
|
||||||
|
child: Center(
|
||||||
|
child: ScrollLabel(
|
||||||
|
animation: kAlwaysCompleteAnimation,
|
||||||
|
backgroundColor: widget.backgroundColor,
|
||||||
|
child: widget.crumbTextBuilder(crumb.label),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
RepaintBoundary(
|
RepaintBoundary(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onLongPressStart: (details) {
|
onLongPressStart: (details) {
|
||||||
|
@ -212,16 +257,16 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
||||||
valueListenable: _thumbOffsetNotifier,
|
valueListenable: _thumbOffsetNotifier,
|
||||||
builder: (context, thumbOffset, child) => Container(
|
builder: (context, thumbOffset, child) => Container(
|
||||||
alignment: AlignmentDirectional.topEnd,
|
alignment: AlignmentDirectional.topEnd,
|
||||||
padding: EdgeInsets.only(top: thumbOffset) + (widget.padding ?? EdgeInsets.zero),
|
padding: EdgeInsets.only(top: thumbOffset) + widget.padding,
|
||||||
child: widget.scrollThumbBuilder(
|
child: widget.scrollThumbBuilder(
|
||||||
widget.backgroundColor,
|
widget.backgroundColor,
|
||||||
_thumbAnimation,
|
_thumbAnimation,
|
||||||
_labelAnimation,
|
_labelAnimation,
|
||||||
widget.scrollThumbHeight,
|
widget.scrollThumbSize.height,
|
||||||
labelText: (widget.labelTextBuilder != null && _isDragInProcess)
|
labelText: _isDragInProcess
|
||||||
? ValueListenableBuilder<double>(
|
? ValueListenableBuilder<double>(
|
||||||
valueListenable: _viewOffsetNotifier,
|
valueListenable: _viewOffsetNotifier,
|
||||||
builder: (context, viewOffset, child) => widget.labelTextBuilder!.call(viewOffset + thumbOffset),
|
builder: (context, viewOffset, child) => widget.labelTextBuilder(viewOffset + thumbOffset),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
|
@ -261,6 +306,7 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
||||||
_labelAnimationController.forward();
|
_labelAnimationController.forward();
|
||||||
_fadeoutTimer?.cancel();
|
_fadeoutTimer?.cancel();
|
||||||
_showThumb();
|
_showThumb();
|
||||||
|
_updateViewportCrumbs();
|
||||||
setState(() => _isDragInProcess = true);
|
setState(() => _isDragInProcess = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,6 +342,50 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
||||||
_fadeoutTimer = null;
|
_fadeoutTimer = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updateViewportCrumbs() {
|
||||||
|
_viewportCrumbs.clear();
|
||||||
|
final crumbsBuilder = widget.crumbsBuilder;
|
||||||
|
if (crumbsBuilder != null) {
|
||||||
|
final position = controller.position;
|
||||||
|
final contentHeight = position.maxScrollExtent + thumbMaxScrollExtent + position.viewportDimension;
|
||||||
|
final ratio = contentHeight / scrollBarHeight;
|
||||||
|
if (ratio > crumbOffsetRatioThreshold) {
|
||||||
|
final maxLabelOffset = scrollBarHeight - widget.scrollThumbSize.height;
|
||||||
|
double lastLabelOffset = -crumbPadding;
|
||||||
|
_modelCrumbs ??= crumbsBuilder();
|
||||||
|
_modelCrumbs!.entries.forEach((kv) {
|
||||||
|
final viewOffset = kv.key;
|
||||||
|
final label = kv.value;
|
||||||
|
final labelOffset = (viewOffset / ratio).roundToDouble();
|
||||||
|
if (labelOffset >= lastLabelOffset + crumbPadding && labelOffset < maxLabelOffset) {
|
||||||
|
lastLabelOffset = labelOffset;
|
||||||
|
_viewportCrumbs.add(_Crumb(
|
||||||
|
viewOffset: viewOffset,
|
||||||
|
labelOffset: labelOffset,
|
||||||
|
label: label,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// hide lonesome crumb, whether it is because of a single section,
|
||||||
|
// or because multiple sections collapsed to a single crumb
|
||||||
|
if (_viewportCrumbs.length == 1) {
|
||||||
|
_viewportCrumbs.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Crumb {
|
||||||
|
final double viewOffset, labelOffset;
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
const _Crumb({
|
||||||
|
required this.viewOffset,
|
||||||
|
required this.labelOffset,
|
||||||
|
required this.label,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
///This cut 2 lines in arrow shape
|
///This cut 2 lines in arrow shape
|
||||||
|
|
|
@ -5,6 +5,26 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class DraggableCrumbLabel extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
const DraggableCrumbLabel({
|
||||||
|
Key? key,
|
||||||
|
required this.label,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: _crumbLabelMaxWidth),
|
||||||
|
child: Padding(
|
||||||
|
padding: _padding,
|
||||||
|
child: _buildText(label, isCrumb: true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class DraggableThumbLabel<T> extends StatelessWidget {
|
class DraggableThumbLabel<T> extends StatelessWidget {
|
||||||
final double offsetY;
|
final double offsetY;
|
||||||
final List<String> Function(BuildContext context, T item) lineBuilder;
|
final List<String> Function(BuildContext context, T item) lineBuilder;
|
||||||
|
@ -30,30 +50,20 @@ class DraggableThumbLabel<T> extends StatelessWidget {
|
||||||
if (lines.isEmpty) return const SizedBox();
|
if (lines.isEmpty) return const SizedBox();
|
||||||
|
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 140),
|
constraints: const BoxConstraints(maxWidth: _thumbLabelMaxWidth),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
padding: _padding,
|
||||||
child: lines.length > 1
|
child: lines.length > 1
|
||||||
? Column(
|
? Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: lines.map(_buildText).toList(),
|
children: lines.map((v) => _buildText(v, isCrumb: false)).toList(),
|
||||||
)
|
)
|
||||||
: _buildText(lines.first),
|
: _buildText(lines.first, isCrumb: false),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildText(String text) => Text(
|
|
||||||
text,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
softWrap: false,
|
|
||||||
overflow: TextOverflow.fade,
|
|
||||||
maxLines: 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
static String formatMonthThumbLabel(BuildContext context, DateTime? date) {
|
static String formatMonthThumbLabel(BuildContext context, DateTime? date) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
if (date == null) return l10n.sectionUnknown;
|
if (date == null) return l10n.sectionUnknown;
|
||||||
|
@ -66,3 +76,18 @@ class DraggableThumbLabel<T> extends StatelessWidget {
|
||||||
return formatDay(date, l10n.localeName);
|
return formatDay(date, l10n.localeName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const double _crumbLabelMaxWidth = 96;
|
||||||
|
const double _thumbLabelMaxWidth = 144;
|
||||||
|
const EdgeInsets _padding = EdgeInsets.symmetric(vertical: 4, horizontal: 8);
|
||||||
|
|
||||||
|
Widget _buildText(String text, {required bool isCrumb}) => Text(
|
||||||
|
text,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: isCrumb ? 10 : 14,
|
||||||
|
),
|
||||||
|
softWrap: false,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
maxLines: 1,
|
||||||
|
);
|
||||||
|
|
|
@ -15,12 +15,12 @@ ScrollThumbBuilder avesScrollThumbBuilder({
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
),
|
),
|
||||||
height: height,
|
height: height,
|
||||||
margin: const EdgeInsetsDirectional.only(end: 1),
|
margin: _margin,
|
||||||
padding: const EdgeInsets.all(2),
|
padding: _padding,
|
||||||
child: ClipPath(
|
child: ClipPath(
|
||||||
clipper: ArrowClipper(),
|
clipper: ArrowClipper(),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 20.0,
|
width: _width,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: backgroundColor,
|
color: backgroundColor,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
|
@ -38,3 +38,9 @@ ScrollThumbBuilder avesScrollThumbBuilder({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _margin = EdgeInsetsDirectional.only(end: 1);
|
||||||
|
const _padding = EdgeInsets.all(2);
|
||||||
|
const _width = 20.0;
|
||||||
|
|
||||||
|
double get avesScrollThumbWidth => _width + _padding.horizontal + _margin.horizontal;
|
||||||
|
|
|
@ -197,11 +197,12 @@ class _MagnifierCoreState extends State<MagnifierCore> with TickerProviderStateM
|
||||||
}
|
}
|
||||||
|
|
||||||
void onTap(TapUpDetails details) {
|
void onTap(TapUpDetails details) {
|
||||||
if (widget.onTap == null) return;
|
final onTap = widget.onTap;
|
||||||
|
if (onTap == null) return;
|
||||||
|
|
||||||
final viewportTapPosition = details.localPosition;
|
final viewportTapPosition = details.localPosition;
|
||||||
final childTapPosition = scaleBoundaries.viewportToChildPosition(controller, viewportTapPosition);
|
final childTapPosition = scaleBoundaries.viewportToChildPosition(controller, viewportTapPosition);
|
||||||
widget.onTap!.call(context, details, controller.currentState, childTapPosition);
|
onTap(context, details, controller.currentState, childTapPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDoubleTap(TapDownDetails details) {
|
void onDoubleTap(TapDownDetails details) {
|
||||||
|
|
|
@ -503,7 +503,7 @@ class _FilterScrollView<T extends CollectionFilter> extends StatelessWidget {
|
||||||
selector: (context, mq) => mq.effectiveBottomPadding,
|
selector: (context, mq) => mq.effectiveBottomPadding,
|
||||||
builder: (context, mqPaddingBottom, child) => DraggableScrollbar(
|
builder: (context, mqPaddingBottom, child) => DraggableScrollbar(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
scrollThumbHeight: avesScrollThumbHeight,
|
scrollThumbSize: Size(avesScrollThumbWidth, avesScrollThumbHeight),
|
||||||
scrollThumbBuilder: avesScrollThumbBuilder(
|
scrollThumbBuilder: avesScrollThumbBuilder(
|
||||||
height: avesScrollThumbHeight,
|
height: avesScrollThumbHeight,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
|
@ -518,6 +518,7 @@ class _FilterScrollView<T extends CollectionFilter> extends StatelessWidget {
|
||||||
sortFactor: sortFactor,
|
sortFactor: sortFactor,
|
||||||
offsetY: offsetY,
|
offsetY: offsetY,
|
||||||
),
|
),
|
||||||
|
crumbTextBuilder: (offsetY) => const SizedBox(),
|
||||||
child: scrollView,
|
child: scrollView,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue