Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable multiple-day allDay events on Android #324

Merged
merged 7 commits into from
Dec 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -468,25 +468,10 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {
val values = ContentValues()
val duration: String? = null
values.put(Events.ALL_DAY, event.eventAllDay)

if (event.eventAllDay) {
val calendar = java.util.Calendar.getInstance()
calendar.timeInMillis = event.eventStartDate!!
calendar.set(java.util.Calendar.HOUR, 0)
calendar.set(java.util.Calendar.MINUTE, 0)
calendar.set(java.util.Calendar.SECOND, 0)
calendar.set(java.util.Calendar.MILLISECOND, 0)

values.put(Events.DTSTART, calendar.timeInMillis)
values.put(Events.DTEND, calendar.timeInMillis)
values.put(Events.EVENT_TIMEZONE, getTimeZone(event.eventStartTimeZone).id)
} else {
values.put(Events.DTSTART, event.eventStartDate!!)
values.put(Events.EVENT_TIMEZONE, getTimeZone(event.eventStartTimeZone).id)

values.put(Events.DTEND, event.eventEndDate!!)
values.put(Events.EVENT_END_TIMEZONE, getTimeZone(event.eventEndTimeZone).id)
}
values.put(Events.DTSTART, event.eventStartDate!!)
values.put(Events.EVENT_TIMEZONE, getTimeZone(event.eventStartTimeZone).id)
values.put(Events.DTEND, event.eventEndDate!!)
values.put(Events.EVENT_END_TIMEZONE, getTimeZone(event.eventEndTimeZone).id)
values.put(Events.TITLE, event.eventTitle)
values.put(Events.DESCRIPTION, event.eventDescription)
values.put(Events.EVENT_LOCATION, event.eventLocation)
Expand Down
32 changes: 25 additions & 7 deletions example/lib/presentation/event_item.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:io';
import 'package:device_calendar/device_calendar.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
Expand Down Expand Up @@ -76,10 +77,9 @@ class _EventItemState extends State<EventItem> {
Text(
widget._calendarEvent == null
? ''
: DateFormat('yyyy-MM-dd HH:mm:ss').format(
TZDateTime.from(
widget._calendarEvent!.start!,
_currentLocation!)),
: _formatDateTime(
dateTime: widget._calendarEvent!.start!,
),
)
],
),
Expand All @@ -99,9 +99,9 @@ class _EventItemState extends State<EventItem> {
Text(
widget._calendarEvent?.end == null
? ''
: DateFormat('yyyy-MM-dd HH:mm:ss').format(
TZDateTime.from(widget._calendarEvent!.end!,
_currentLocation!)),
: _formatDateTime(
dateTime: widget._calendarEvent!.end!,
),
),
],
),
Expand Down Expand Up @@ -301,4 +301,22 @@ class _EventItemState extends State<EventItem> {
_currentLocation = timeZoneDatabase.locations[timezone];
setState(() {});
}

/// Formats [dateTime] into a human-readable string.
/// If [_calendarEvent] is an Android allDay event, then the output will
/// omit the time.
String _formatDateTime({DateTime? dateTime}) {
if (dateTime == null) {
return 'Error';
}
var output = '';
if (Platform.isAndroid && widget._calendarEvent?.allDay == true) {
// just the dates, no times
output = DateFormat.yMd().format(dateTime);
} else {
output = DateFormat('yyyy-MM-dd HH:mm:ss')
.format(TZDateTime.from(dateTime, _currentLocation!));
}
return output;
}
}
32 changes: 18 additions & 14 deletions example/lib/presentation/pages/calendar_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -299,26 +299,29 @@ class _CalendarEventPageState extends State<CalendarEventPage> {
},
),
),
if (_event?.allDay == false) ...[
if (Platform.isAndroid)
Padding(
padding: const EdgeInsets.all(10.0),
child: TextFormField(
initialValue: _event?.start?.location.name,
decoration: const InputDecoration(
labelText: 'Start date time zone',
hintText: 'Australia/Sydney'),
onSaved: (String? value) {
_event?.updateStartLocation(value);
},
),
if ((_event?.allDay == false) && Platform.isAndroid)
Padding(
padding: const EdgeInsets.all(10.0),
child: TextFormField(
initialValue: _event?.start?.location.name,
decoration: const InputDecoration(
labelText: 'Start date time zone',
hintText: 'Australia/Sydney'),
onSaved: (String? value) {
_event?.updateStartLocation(value);
},
),
),
// Only add the 'To' Date for non-allDay events on all
// platforms except Android (which allows multiple-day allDay events)
if (_event?.allDay == false || Platform.isAndroid)
Padding(
padding: const EdgeInsets.all(10.0),
child: DateTimePicker(
labelText: 'To',
selectedDate: _endDate,
selectedTime: _endTime,
enableTime: _event?.allDay == false,
selectDate: (DateTime date) {
setState(
() {
Expand All @@ -344,6 +347,7 @@ class _CalendarEventPageState extends State<CalendarEventPage> {
},
),
),
if (_event?.allDay == false && Platform.isAndroid)
Padding(
padding: const EdgeInsets.all(10.0),
child: TextFormField(
Expand All @@ -355,7 +359,6 @@ class _CalendarEventPageState extends State<CalendarEventPage> {
_event?.updateEndLocation(value),
),
),
],
GestureDetector(
onTap: () async {
var result = await Navigator.push(
Expand Down Expand Up @@ -1007,6 +1010,7 @@ class _CalendarEventPageState extends State<CalendarEventPage> {
currentLocation!);

if (time == null) return dateWithoutTime;
if (Platform.isAndroid && _event?.allDay == true) return dateWithoutTime;

return dateWithoutTime
.add(Duration(hours: time.hour, minutes: time.minute));
Expand Down
18 changes: 15 additions & 3 deletions lib/src/device_calendar.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:collection';
import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Expand Down Expand Up @@ -211,14 +212,25 @@ class DeviceCalendarPlugin {
if (event.start != null) {
var dateStart = DateTime(event.start!.year, event.start!.month,
event.start!.day, 0, 0, 0);
event.start = TZDateTime.from(dateStart,
// allDay events on Android need to be at midnight UTC
event.start = Platform.isAndroid
? TZDateTime.utc(event.start!.year, event.start!.month,
event.start!.day, 0, 0, 0)
: TZDateTime.from(dateStart,
timeZoneDatabase.locations[event.start!.location.name]!);
}
if (event.end != null) {
var dateEnd = DateTime(
event.end!.year, event.end!.month, event.end!.day, 0, 0, 0);
event.end = TZDateTime.from(
dateEnd, timeZoneDatabase.locations[event.end!.location.name]!);
// allDay events on Android need to be at midnight UTC on the
// day after the last day. For example, a 2-day allDay event on
// Jan 1 and 2, should be from Jan 1 00:00:00 to Jan 3 00:00:00
event.end = Platform.isAndroid
? TZDateTime.utc(event.end!.year, event.end!.month,
event.end!.day, 0, 0, 0)
.add(Duration(days: 1))
: TZDateTime.from(dateEnd,
timeZoneDatabase.locations[event.end!.location.name]!);
}
}

Expand Down
18 changes: 16 additions & 2 deletions lib/src/models/event.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:io';

import '../../device_calendar.dart';
import '../common/error_messages.dart';
import 'package:timezone/timezone.dart';
Expand Down Expand Up @@ -79,12 +81,24 @@ class Event {
final int? endTimestamp = json['eventEndDate'];
final String? endLocationName = json['eventEndTimeZone'];
var endLocation = timeZoneDatabase.locations[endLocationName];
endLocation ??= local;
endLocation ??= startTimeZone;
end = endTimestamp != null
? TZDateTime.fromMillisecondsSinceEpoch(endLocation, endTimestamp)
: TZDateTime.now(local);

allDay = json['eventAllDay'] ?? false;
if (Platform.isAndroid && (allDay ?? false)){
// On Android, the datetime in an allDay event is adjusted to local
// timezone, which can result in the wrong day, so we need to bring the
// date back to midnight UTC to get the correct date
var startOffset = start?.timeZoneOffset.inMilliseconds ?? 0;
var endOffset = end?.timeZoneOffset.inMilliseconds ?? 0;
// subtract the offset to get back to midnight on the correct date
start = start?.subtract(Duration(milliseconds: startOffset));
end = end?.subtract(Duration(milliseconds: endOffset));
// The Event End Date for allDay events is midnight of the next day, so
// subtract one day
end = end?.subtract(Duration(days: 1));
}
location = json['eventLocation'];
availability = parseStringToAvailability(json['availability']);

Expand Down