diff --git a/lib/calendar_day_view.dart b/lib/calendar_day_view.dart index 2383901..3feb5ea 100644 --- a/lib/calendar_day_view.dart +++ b/lib/calendar_day_view.dart @@ -6,12 +6,15 @@ /// in the app that need better day view library calendar_day_view; +export 'package:linked_scroll_controller/linked_scroll_controller.dart' + show LinkedScrollControllerGroup; + +export 'src/day_views/calendar_day_view_base.dart'; +export 'src/day_views/category/category_overflow_calendar_day_view.dart'; export 'src/day_views/event_calendar_day_view.dart'; export 'src/day_views/in_row_calendar_day_view.dart'; export 'src/day_views/overflow/overflow_calendar_day_view.dart'; -export 'src/day_views/category/category_overflow_calendar_day_view.dart'; -export 'src/models/day_event.dart'; export 'src/models/categorized_day_event.dart'; +export 'src/models/day_event.dart'; export 'src/models/event_category.dart'; export 'src/models/typedef.dart'; -export 'src/day_views/calendar_day_view_base.dart'; diff --git a/lib/src/day_views/category/category_overflow_calendar_day_view.dart b/lib/src/day_views/category/category_overflow_calendar_day_view.dart index 51ff710..9c3b603 100644 --- a/lib/src/day_views/category/category_overflow_calendar_day_view.dart +++ b/lib/src/day_views/category/category_overflow_calendar_day_view.dart @@ -4,7 +4,6 @@ import 'package:calendar_day_view/src/extensions/list_extensions.dart'; import 'package:calendar_day_view/src/extensions/time_of_day_extension.dart'; import 'package:calendar_day_view/src/widgets/timed_rebuilder.dart'; import 'package:flutter/material.dart'; -import 'package:linked_scroll_controller/linked_scroll_controller.dart'; import 'package:timezone/timezone.dart'; import '../../../calendar_day_view.dart'; @@ -21,6 +20,41 @@ typedef TitleRowBuilder = Widget Function({ Widget? logo, }); +typedef CategoryDayViewLayoutMetricsCallback = void Function( + CategoryDayViewLayoutMetrics metrics, +); + +@immutable +class CategoryDayViewLayoutMetrics { + final Size bodyContentSize; + final Rect bodyViewport; + + bool get isHorizontallyOverflowing => + bodyContentSize.width > bodyViewport.width; + + bool get isVerticallyOverflowing => + bodyContentSize.height > bodyViewport.height; + + @override + int get hashCode => Object.hash(bodyContentSize, bodyViewport); + + const CategoryDayViewLayoutMetrics({ + required this.bodyContentSize, + required this.bodyViewport, + }); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is CategoryDayViewLayoutMetrics && + other.bodyContentSize == bodyContentSize && + other.bodyViewport == bodyViewport; + + @override + String toString() => 'CategoryDayViewLayoutMetrics(' + 'bodyContentSize: $bodyContentSize, bodyViewport: $bodyViewport)'; +} + abstract class GroupingStrategy { ({List> grouped, List> nonGrouped}) groupEvents(List> events); @@ -28,6 +62,7 @@ abstract class GroupingStrategy { class NoGroupingStrategy implements GroupingStrategy { const NoGroupingStrategy(); + @override ({List> grouped, List> nonGrouped}) groupEvents(List> events) { @@ -49,6 +84,7 @@ class EventGroup extends CategorizedDayEvent { abstract class GroupLayoutStrategy { const GroupLayoutStrategy(); + bool canLayout(EventGroup group) => true; Widget layout( @@ -93,6 +129,9 @@ class CategoryOverflowCalendarDayView extends StatefulWidget required this.minColumnWidth, required this.timeLabelsFormatter, required this.currentTimeFormatter, + this.horizontalScrollControllerGroup, + this.verticalScrollControllerGroup, + this.onLayoutMetrics, ValueGetter? clock, }) : clock = clock ?? DateTime.now, super(key: key); @@ -100,6 +139,9 @@ class CategoryOverflowCalendarDayView extends StatefulWidget final Border? tableBodyBorder; final Border? timeColumnBorder; final double minColumnWidth; + final LinkedScrollControllerGroup? horizontalScrollControllerGroup; + final LinkedScrollControllerGroup? verticalScrollControllerGroup; + final CategoryDayViewLayoutMetricsCallback? onLayoutMetrics; final ValueGetter clock; final GroupingStrategy? groupingStrategy; final GroupLayoutStrategy? groupLayoutStrategy; @@ -176,13 +218,34 @@ class CategoryOverflowCalendarDayView extends StatefulWidget class _CategoryOverflowCalendarDayViewState extends State> { - final _horizontalScrollLink = LinkedScrollControllerGroup(); + late final _horizontalScrollLink = + widget.horizontalScrollControllerGroup ?? LinkedScrollControllerGroup(); late final _headerScrollController = _horizontalScrollLink.addAndGet(); late final _horizScrollController = _horizontalScrollLink.addAndGet(); - final _verticalScrollLink = LinkedScrollControllerGroup(); + late final _verticalScrollLink = + widget.verticalScrollControllerGroup ?? LinkedScrollControllerGroup(); late final _timeScrollController = _verticalScrollLink.addAndGet(); late final _vertScrollController = _verticalScrollLink.addAndGet(); + /// Identifies the scrollable body so its viewport rect can be measured. + final _bodyKey = GlobalKey(); + + /// Measures the scrollable body's viewport relative to the day view's + /// top-left, so the result accounts for the header and any safe-area inset. + /// Returns null while the render tree is not ready to be measured. + Rect? _measureBodyViewport() { + final root = context.findRenderObject(); + final body = _bodyKey.currentContext?.findRenderObject(); + if (root is! RenderBox || + body is! RenderBox || + !root.hasSize || + !body.hasSize) { + return null; + } + + return body.localToGlobal(Offset.zero, ancestor: root) & body.size; + } + @override Widget build(BuildContext context) { final timeStart = widget.currentDate.copyTimeAndMinClean(widget.startOfDay); @@ -207,6 +270,28 @@ class _CategoryOverflowCalendarDayViewState final rowLength = totalWidth - widget.timeColumnWidth; final tileWidth = rowLength / categoriesCount; + final onLayoutMetrics = widget.onLayoutMetrics; + + if (onLayoutMetrics != null) { + final bodyContentSize = Size( + rowLength, + rowHeight * timeList.length, + ); + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + + final bodyViewport = _measureBodyViewport(); + + if (bodyViewport == null) return; + + onLayoutMetrics(CategoryDayViewLayoutMetrics( + bodyContentSize: bodyContentSize, + bodyViewport: bodyViewport, + )); + }); + } + return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -261,6 +346,7 @@ class _CategoryOverflowCalendarDayViewState ), Expanded( child: ClipPath( + key: _bodyKey, clipper: VerticalClipper(), child: DecoratedBox( decoration: BoxDecoration( @@ -314,6 +400,15 @@ class _CategoryOverflowCalendarDayViewState ), ); } + + @override + void dispose() { + _headerScrollController.dispose(); + _horizScrollController.dispose(); + _timeScrollController.dispose(); + _vertScrollController.dispose(); + super.dispose(); + } } class VerticalClipper extends CustomClipper {