diff --git a/example/lib/main.dart b/example/lib/main.dart index 9160966..3e6043d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -96,6 +96,9 @@ class _TimetableExampleState extends State { ), body: Timetable( controller: _controller, + onEventBackgroundTap: (start, isAllDay) { + _showSnackBar('Background tapped $start is all day event $isAllDay'); + }, eventBuilder: (event) { return BasicEventWidget( event, diff --git a/lib/src/content/date_events.dart b/lib/src/content/date_events.dart index 7179164..4a9342d 100644 --- a/lib/src/content/date_events.dart +++ b/lib/src/content/date_events.dart @@ -116,7 +116,6 @@ class _DayEventsLayoutDelegate for (final event in events) { final position = positions.eventPositions[event]; - final top = timeToY(event.start) .coerceAtMost(size.height - periodToY(minEventDuration)) .coerceAtMost(size.height - minEventHeight); diff --git a/lib/src/content/multi_date_content.dart b/lib/src/content/multi_date_content.dart index c334f94..4d4c70b 100644 --- a/lib/src/content/multi_date_content.dart +++ b/lib/src/content/multi_date_content.dart @@ -1,5 +1,6 @@ import 'package:black_hole_flutter/black_hole_flutter.dart'; import 'package:flutter/widgets.dart'; +import 'package:time_machine/time_machine.dart'; import '../controller.dart'; import '../date_page_view.dart'; @@ -16,12 +17,14 @@ class MultiDateContent extends StatefulWidget { Key key, @required this.controller, @required this.eventBuilder, + this.onEventBackgroundTap, }) : assert(controller != null), assert(eventBuilder != null), super(key: key); final TimetableController controller; final EventBuilder eventBuilder; + final OnEventBackgroundTapCallback onEventBackgroundTap; @override _MultiDateContentState createState() => _MultiDateContentState(); @@ -42,7 +45,6 @@ class _MultiDateContentState Widget build(BuildContext context) { final theme = context.theme; final timetableTheme = context.timetableTheme; - return CustomPaint( painter: MultiDateBackgroundPainter( controller: widget.controller, @@ -56,13 +58,36 @@ class _MultiDateContentState child: DatePageView( controller: widget.controller, builder: (_, date) { - return StreamedDateEvents( - date: date, - controller: widget.controller, - eventBuilder: widget.eventBuilder, + return LayoutBuilder( + builder: (context, constraints) { + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTapUp: widget.onEventBackgroundTap != null + ? (details) { + _callOnEventBackgroundTap(details, date, constraints); + } + : null, + child: StreamedDateEvents( + date: date, + controller: widget.controller, + eventBuilder: widget.eventBuilder, + ), + ); + }, ); }, ), ); } + + void _callOnEventBackgroundTap(TapUpDetails details, LocalDate date, BoxConstraints constraints) { + final millis = details.localPosition.dy / + constraints.maxHeight * + TimeConstants.millisecondsPerDay; + final startTime = LocalTime.sinceMidnight( + Time(milliseconds: millis.floor())) + .atDate(date); + widget.onEventBackgroundTap(startTime, false); + + } } diff --git a/lib/src/content/timetable_content.dart b/lib/src/content/timetable_content.dart index 93baa02..34c56ba 100644 --- a/lib/src/content/timetable_content.dart +++ b/lib/src/content/timetable_content.dart @@ -15,12 +15,14 @@ class TimetableContent extends StatelessWidget { Key key, @required this.controller, @required this.eventBuilder, + this.onEventBackgroundTap, }) : assert(controller != null), assert(eventBuilder != null), super(key: key); final TimetableController controller; final EventBuilder eventBuilder; + final OnEventBackgroundTapCallback onEventBackgroundTap; @override Widget build(BuildContext context) { @@ -53,6 +55,7 @@ class TimetableContent extends StatelessWidget { child: MultiDateContent( controller: controller, eventBuilder: eventBuilder, + onEventBackgroundTap: onEventBackgroundTap ), ), ], diff --git a/lib/src/header/all_day_events.dart b/lib/src/header/all_day_events.dart index 9177e58..eedcb9b 100644 --- a/lib/src/header/all_day_events.dart +++ b/lib/src/header/all_day_events.dart @@ -19,32 +19,48 @@ class AllDayEvents extends StatelessWidget { Key key, @required this.controller, @required this.allDayEventBuilder, + this.onEventBackgroundTap, }) : assert(controller != null), assert(allDayEventBuilder != null), super(key: key); final TimetableController controller; final AllDayEventBuilder allDayEventBuilder; + final OnEventBackgroundTapCallback onEventBackgroundTap; @override Widget build(BuildContext context) { return ClipRect( - child: ValueListenableBuilder( - valueListenable: controller.currentlyVisibleDatesListenable, - builder: (_, visibleDates, __) { - return StreamBuilder>( - stream: controller.eventProvider - .getAllDayEventsIntersecting(visibleDates), - builder: (_, snapshot) { - var events = snapshot.data ?? []; - // The StreamBuilder gets recycled and initially still has a list of - // old events. - events = events.where((e) => e.intersectsInterval(visibleDates)); - - return ValueListenableBuilder( - valueListenable: controller.scrollControllers.pageListenable, - builder: (context, page, __) => - _buildEventLayout(context, events, page), + child: LayoutBuilder( + builder: (context, constraints) { + return ValueListenableBuilder( + valueListenable: controller.currentlyVisibleDatesListenable, + builder: (_, visibleDates, __) { + return StreamBuilder>( + stream: controller.eventProvider + .getAllDayEventsIntersecting(visibleDates), + builder: (_, snapshot) { + var events = snapshot.data ?? []; + // The StreamBuilder gets recycled and initially still has a list of + // old events. + events = + events.where((e) => e.intersectsInterval(visibleDates)); + + return ValueListenableBuilder( + valueListenable: + controller.scrollControllers.pageListenable, + builder: (context, page, __) => GestureDetector( + behavior: HitTestBehavior.translucent, + onTapUp: onEventBackgroundTap != null + ? (details) { + _callOnAllDayEventBackgroundTap( + details, page, constraints); + } + : null, + child: _buildEventLayout(context, events, page), + ), + ); + }, ); }, ); @@ -53,6 +69,14 @@ class AllDayEvents extends StatelessWidget { ); } + void _callOnAllDayEventBackgroundTap(TapUpDetails details, double page, BoxConstraints constraints) { + final tappedCell = details.localPosition.dx / + (constraints.maxWidth / controller.visibleRange.visibleDays); + final date = LocalDate.fromEpochDay((page + tappedCell).floor()); + final startTime = date.atMidnight(); + onEventBackgroundTap(startTime, true); + } + Widget _buildEventLayout( BuildContext context, Iterable events, @@ -183,7 +207,9 @@ class _EventsLayout extends RenderBox _eventHeight = eventHeight; VisibleRange _visibleRange; + VisibleRange get visibleRange => _visibleRange; + set visibleRange(VisibleRange value) { assert(value != null); if (_visibleRange == value) { @@ -195,7 +221,9 @@ class _EventsLayout extends RenderBox } DateInterval _currentlyVisibleDates; + DateInterval get currentlyVisibleDates => _currentlyVisibleDates; + set currentlyVisibleDates(DateInterval value) { assert(value != null); if (_currentlyVisibleDates == value) { @@ -207,7 +235,9 @@ class _EventsLayout extends RenderBox } double _page; + double get page => _page; + set page(double value) { assert(value != null); if (_page == value) { @@ -219,7 +249,9 @@ class _EventsLayout extends RenderBox } double _eventHeight; + double get eventHeight => _eventHeight; + set eventHeight(double value) { assert(value != null); if (_eventHeight == value) { @@ -286,6 +318,7 @@ class _EventsLayout extends RenderBox @override double computeMinIntrinsicHeight(double width) => _parallelEventCount() * eventHeight; + @override double computeMaxIntrinsicHeight(double width) => _parallelEventCount() * eventHeight; @@ -343,6 +376,7 @@ class _EventsLayout extends RenderBox } bool _hasOverflow = false; + void _setSize() { final parallelEvents = _parallelEventCount(); size = Size(constraints.maxWidth, parallelEvents * eventHeight); diff --git a/lib/src/header/timetable_header.dart b/lib/src/header/timetable_header.dart index 64c7c7b..af7196f 100644 --- a/lib/src/header/timetable_header.dart +++ b/lib/src/header/timetable_header.dart @@ -14,12 +14,14 @@ class TimetableHeader extends StatelessWidget { Key key, @required this.controller, @required this.allDayEventBuilder, + this.onEventBackgroundTap, }) : assert(controller != null), assert(allDayEventBuilder != null), super(key: key); final TimetableController controller; final AllDayEventBuilder allDayEventBuilder; + final OnEventBackgroundTapCallback onEventBackgroundTap; @override Widget build(BuildContext context) { @@ -49,6 +51,7 @@ class TimetableHeader extends StatelessWidget { ), AllDayEvents( controller: controller, + onEventBackgroundTap: onEventBackgroundTap, allDayEventBuilder: allDayEventBuilder, ), ], diff --git a/lib/src/timetable.dart b/lib/src/timetable.dart index de8a032..570a07d 100644 --- a/lib/src/timetable.dart +++ b/lib/src/timetable.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:time_machine/time_machine.dart'; import 'all_day.dart'; import 'content/timetable_content.dart'; @@ -9,6 +10,7 @@ import 'header/timetable_header.dart'; import 'theme.dart'; typedef EventBuilder = Widget Function(E event); +typedef OnEventBackgroundTapCallback = void Function(LocalDateTime start, bool isAllDay); typedef AllDayEventBuilder = Widget Function( BuildContext context, E event, @@ -18,11 +20,13 @@ typedef AllDayEventBuilder = Widget Function( const double hourColumnWidth = 48; class Timetable extends StatelessWidget { + const Timetable({ Key key, @required this.controller, @required this.eventBuilder, this.allDayEventBuilder, + this.onEventBackgroundTap, this.theme, }) : assert(controller != null), assert(eventBuilder != null), @@ -35,8 +39,8 @@ class Timetable extends StatelessWidget { /// /// If not set, [eventBuilder] will be used instead. final AllDayEventBuilder allDayEventBuilder; - final TimetableThemeData theme; + final OnEventBackgroundTapCallback onEventBackgroundTap; @override Widget build(BuildContext context) { @@ -44,6 +48,7 @@ class Timetable extends StatelessWidget { children: [ TimetableHeader( controller: controller, + onEventBackgroundTap: onEventBackgroundTap, allDayEventBuilder: allDayEventBuilder ?? (_, event, __) => eventBuilder(event), ), @@ -51,6 +56,7 @@ class Timetable extends StatelessWidget { child: TimetableContent( controller: controller, eventBuilder: eventBuilder, + onEventBackgroundTap: onEventBackgroundTap ), ), ],