From 6b5a108b81040f9d1ec91ce988953d47adce2989 Mon Sep 17 00:00:00 2001 From: Parth Baraiya Date: Thu, 23 Nov 2023 21:11:17 +0530 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Adds=20new=20method=20updateEvent?= =?UTF-8?q?=20to=20update=20the=20existing=20event=20in=20the=20calendar?= =?UTF-8?q?=20view.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixes issue #125 --- lib/src/calendar_event_data.dart | 48 +++- lib/src/components/day_view_components.dart | 8 +- lib/src/event_controller.dart | 278 +++++++++++++++----- lib/src/typedefs.dart | 2 + 4 files changed, 266 insertions(+), 70 deletions(-) diff --git a/lib/src/calendar_event_data.dart b/lib/src/calendar_event_data.dart index da9ac3b2..121afd70 100644 --- a/lib/src/calendar_event_data.dart +++ b/lib/src/calendar_event_data.dart @@ -26,7 +26,7 @@ class CalendarEventData { final String title; /// Description of the event. - final String description; + final String? description; /// Defines color of event. /// This color will be used in default widgets provided by plugin. @@ -43,10 +43,14 @@ class CalendarEventData { /// Define style of description. final TextStyle? descriptionStyle; - /// Stores all the events on [date] + /// Stores all the events on [date]. + /// + /// If [startTime] and [endTime] both are 0 or either of them is null, then + /// event will be considered a full day event. + /// const CalendarEventData({ required this.title, - this.description = "", + this.description, this.event, this.color = Colors.blue, this.startTime, @@ -59,6 +63,18 @@ class CalendarEventData { DateTime get endDate => _endDate ?? date; + bool get isRangingEvent { + final diff = endDate.withoutTime.difference(date.withoutTime).inDays; + + return diff > 0 && !isFullDayEvent; + } + + bool get isFullDayEvent { + return (startTime == null || + endTime == null || + (startTime!.isDayStart && endTime!.isDayStart)); + } + Map toJson() => { "date": date, "startTime": startTime, @@ -69,6 +85,32 @@ class CalendarEventData { "endDate": endDate, }; + CalendarEventData copyWith({ + String? title, + String? description, + T? event, + Color? color, + DateTime? startTime, + DateTime? endTime, + TextStyle? titleStyle, + TextStyle? descriptionStyle, + DateTime? endDate, + DateTime? date, + }) { + return CalendarEventData( + title: title ?? this.title, + date: date ?? this.date, + startTime: startTime ?? this.startTime, + endTime: endTime ?? this.endTime, + color: color ?? this.color, + description: description ?? this.description, + descriptionStyle: descriptionStyle ?? this.descriptionStyle, + endDate: endDate ?? this.endDate, + event: event ?? this.event, + titleStyle: titleStyle ?? this.titleStyle, + ); + } + @override String toString() => '${toJson()}'; diff --git a/lib/src/components/day_view_components.dart b/lib/src/components/day_view_components.dart index 2cda9a06..98eb623e 100644 --- a/lib/src/components/day_view_components.dart +++ b/lib/src/components/day_view_components.dart @@ -18,7 +18,7 @@ class RoundedEventTile extends StatelessWidget { final String title; /// Description of the tile. - final String description; + final String? description; /// Background color of tile. /// Default color is [Colors.blue] @@ -49,7 +49,7 @@ class RoundedEventTile extends StatelessWidget { required this.title, this.padding = EdgeInsets.zero, this.margin = EdgeInsets.zero, - this.description = "", + this.description, this.borderRadius = BorderRadius.zero, this.totalEvents = 1, this.backgroundColor = Colors.blue, @@ -83,12 +83,12 @@ class RoundedEventTile extends StatelessWidget { overflow: TextOverflow.fade, ), ), - if (description.isNotEmpty) + if (description?.isNotEmpty ?? false) Expanded( child: Padding( padding: const EdgeInsets.only(bottom: 15.0), child: Text( - description, + description!, style: descriptionStyle ?? TextStyle( fontSize: 17, diff --git a/lib/src/event_controller.dart b/lib/src/event_controller.dart index 4a395a51..1fbd6938 100644 --- a/lib/src/event_controller.dart +++ b/lib/src/event_controller.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a MIT-style license // that can be found in the LICENSE file. +import 'dart:collection'; + import 'package:flutter/material.dart'; import 'calendar_event_data.dart'; @@ -27,7 +29,7 @@ class EventController extends ChangeNotifier { //#region Private Fields EventFilter? _eventFilter; - // Store all calendar event data + /// Store all calendar event data final CalendarData _calendarData = CalendarData(); //#endregion @@ -40,10 +42,15 @@ class EventController extends ChangeNotifier { // Note: Do not use this getter inside of EventController class. // use _eventList instead. /// Returns list of [CalendarEventData] stored in this controller. + @Deprecated('This is deprecated and will be removed in next major release. ' + 'Use allEvents instead.') List> get events => - _calendarData.eventList.toList(growable: false); + _calendarData.events.toList(growable: false); + + UnmodifiableListView> get allEvents => + _calendarData.events; - /// This method will provide list of events on particular date. + /// Defines which events should be displayed on given date. /// /// This method is use full when you have recurring events. /// As of now this library does not support recurring events. @@ -60,48 +67,41 @@ class EventController extends ChangeNotifier { /// Add all the events in the list /// If there is an event with same date then void addAll(List> events) { - for (final event in events) { - _addEvent(event); - } - + for (final event in events) _calendarData.addEvent(event); notifyListeners(); } /// Adds a single event in [_events] void add(CalendarEventData event) { - _addEvent(event); - + _calendarData.addEvent(event); notifyListeners(); } /// Removes [event] from this controller. void remove(CalendarEventData event) { - final date = event.date.withoutTime; + _calendarData.removeEvent(event); + notifyListeners(); + } - // Removes the event from single event map. - if (_calendarData.events[date] != null) { - if (_calendarData.events[date]!.remove(event)) { - _calendarData.eventList.remove(event); - notifyListeners(); - return; - } - } + /// Updates the [event] to have the data from [updated] event. + /// + /// If [event] is not found in the controller, it will add the [updated] + /// event in the controller. + /// + void updateEvent(CalendarEventData event, CalendarEventData updated) { + _calendarData.updateEvent(event, updated); + notifyListeners(); + } - // Removes the event from ranging or full day event. - _calendarData.eventList.remove(event); - _calendarData.rangingEventList.remove(event); - _calendarData.fullDayEventList.remove(event); + /// Removes all the [events] from this controller. + void removeAll(List> events) { + for (final event in events) _calendarData.removeEvent(event); notifyListeners(); } /// Removes multiple [event] from this controller. - void removeWhere(bool Function(CalendarEventData element) test) { - for (final e in _calendarData.events.values) { - e.removeWhere(test); - } - _calendarData.rangingEventList.removeWhere(test); - _calendarData.eventList.removeWhere(test); - _calendarData.fullDayEventList.removeWhere(test); + void removeWhere(TestPredicate> test) { + _calendarData.removeWhere(test); notifyListeners(); } @@ -109,16 +109,191 @@ class EventController extends ChangeNotifier { /// /// To overwrite default behaviour of this function, /// provide [_eventFilter] argument in [EventController] constructor. - List> getEventsOnDay(DateTime date) { + /// + /// if [includeFullDayEvents] is true, it will include full day events + /// as well else, it will exclude full day events. + /// + /// NOTE: If [eventFilter] is set i.e, not null, [includeFullDayEvents] will + /// have no effect. As what events to be included will be totally governed + /// by the [eventFilter]. + /// + /// To get full day events exclusively, check [getFullDayEvent] method. + /// + List> getEventsOnDay(DateTime date, + {bool includeFullDayEvents = true}) { + //ignore: deprecated_member_use_from_same_package if (_eventFilter != null) return _eventFilter!.call(date, this.events); + return _calendarData.getEventsOnDay(date, + includeFullDays: includeFullDayEvents); + } + + /// Returns full day events on given day. + List> getFullDayEvent(DateTime date) { + return _calendarData.getFullDayEvent(date); + } + + /// Updates the [eventFilter]. + /// + /// This will also refresh the UI to reflect the latest event filter. + void updateFilter({required EventFilter newFilter}) { + if (newFilter != _eventFilter) { + _eventFilter = newFilter; + notifyListeners(); + } + } + //#endregion +} + +/// Stores the list of the calendar events. +/// +/// Provides basic data structure to store the events. +/// +/// Exposes methods to manipulate stored data. +/// +/// +class CalendarData { + /// Stores all the events in a list(all the items in below 3 list will be + /// available in this list as global itemList of all events). + final _eventList = >[]; + + UnmodifiableListView> get events => + UnmodifiableListView(_eventList); + + /// Stores events that occurs only once in a map, Here the key will a day + /// and along to the day as key we will store all the events of that day as + /// list as value + final _singleDayEvents = >>{}; + + UnmodifiableMapView>> + get singleDayEvents => UnmodifiableMapView( + Map.fromIterable( + _singleDayEvents.keys.map((key) { + return MapEntry( + key, + UnmodifiableListView( + _singleDayEvents[key] ?? [], + )); + }), + ), + ); + + /// Stores all the ranging events in a list + /// + /// Events that occurs on multiple day from startDate to endDate. + /// + final _rangingEventList = >[]; + UnmodifiableListView> get rangingEventList => + UnmodifiableListView(_rangingEventList); + + /// Stores all full day events(24hr event) + final _fullDayEventList = >[]; + UnmodifiableListView> get fullDayEventList => + UnmodifiableListView(_fullDayEventList); + + //#region Data Manipulation Methods + void addFullDayEvent(CalendarEventData event) { + _fullDayEventList.addEventInSortedManner(event); + _eventList.add(event); + } + + void addRangingEvent(CalendarEventData event) { + _rangingEventList.addEventInSortedManner(event); + _eventList.add(event); + } + + void addSingleDayEvent(CalendarEventData event) { + final date = event.date.withoutTime; + + if (_singleDayEvents[date] == null) { + _singleDayEvents.addAll({ + date: [event], + }); + } else { + _singleDayEvents[date]!.addEventInSortedManner(event); + } + + _eventList.add(event); + } + + void addEvent(CalendarEventData event) { + assert(event.endDate.difference(event.date).inDays >= 0, + 'The end date must be greater or equal to the start date'); + + // TODO: improve this... + if (_eventList.contains(event)) return; + + if (event.isFullDayEvent) { + addFullDayEvent(event); + } else if (event.isRangingEvent) { + addRangingEvent(event); + } else { + addSingleDayEvent(event); + } + } + + void removeFullDayEvent(CalendarEventData event) { + if (_fullDayEventList.remove(event)) { + _eventList.remove(event); + } + } + + void removeRangingEvent(CalendarEventData event) { + if (_rangingEventList.remove(event)) { + _eventList.remove(event); + } + } + + void removeSingleDayEvent(CalendarEventData event) { + if (_singleDayEvents[event.date.withoutTime]?.remove(event) ?? false) { + _eventList.remove(event); + } + } + + void removeEvent(CalendarEventData event) { + if (event.isFullDayEvent) { + removeFullDayEvent(event); + } else if (event.isRangingEvent) { + removeRangingEvent(event); + } else { + removeSingleDayEvent(event); + } + } + + void removeWhere(TestPredicate> test) { + final _predicates = , bool>{}; + + bool wrappedPredicate(CalendarEventData event) { + return _predicates[event] = test(event); + } + + for (final e in _singleDayEvents.values) { + e.removeWhere(wrappedPredicate); + } + + _rangingEventList.removeWhere(wrappedPredicate); + _fullDayEventList.removeWhere(wrappedPredicate); + + _eventList.removeWhere((event) => _predicates[event] ?? false); + } + + void updateEvent( + CalendarEventData oldEvent, CalendarEventData newEvent) { + removeEvent(oldEvent); + addEvent(newEvent); + } + //#endregion + + //#region Data Fetch Methods + List> getEventsOnDay(DateTime date, + {bool includeFullDays = true}) { final events = >[]; - if (_calendarData.events[date] != null) { - events.addAll(_calendarData.events[date]!); + if (_singleDayEvents[date] != null) { + events.addAll(_singleDayEvents[date]!); } - for (final rangingEvent in _calendarData.rangingEventList) { + for (final rangingEvent in _rangingEventList) { if (date == rangingEvent.date || date == rangingEvent.endDate || (date.isBefore(rangingEvent.endDate) && @@ -127,30 +302,24 @@ class EventController extends ChangeNotifier { } } - events.addAll(getFullDayEvent(date)); + if (includeFullDays) { + events.addAll(getFullDayEvent(date)); + } return events; } /// Returns full day events on given day. - List> getFullDayEvent(DateTime dateTime) { + List> getFullDayEvent(DateTime date) { final events = >[]; - for (final event in _calendarData.fullDayEventList) { - if (dateTime.difference(event.date).inDays >= 0 && - event.endDate.difference(dateTime).inDays > 0) { + for (final event in fullDayEventList) { + if (date.difference(event.date).inDays >= 0 && + event.endDate.difference(date).inDays > 0) { events.add(event); } } return events; } - - void updateFilter({required EventFilter newFilter}) { - if (newFilter != _eventFilter) { - _eventFilter = newFilter; - notifyListeners(); - } - } - //#endregion //#region Private Methods @@ -185,20 +354,3 @@ class EventController extends ChangeNotifier { //#endregion } - -class CalendarData { - // Stores all the events in a list(all the items in below 3 list will be - // available in this list as global itemList of all events). - final eventList = >[]; - - // Stores events that occurs only once in a map, Here the key will a day - // and along to the day as key we will store all the events of that day as - // list as value - final events = >>{}; - - // Stores all the ranging events in a list - final rangingEventList = >[]; - - // Stores all full day events(24hr event) - final fullDayEventList = >[]; -} diff --git a/lib/src/typedefs.dart b/lib/src/typedefs.dart index 61e72021..f517c492 100644 --- a/lib/src/typedefs.dart +++ b/lib/src/typedefs.dart @@ -84,3 +84,5 @@ typedef CustomHourLinePainter = CustomPainter Function( double dashSpaceWidth, double emulateVerticalOffsetBy, ); + +typedef TestPredicate = bool Function(T element);