From f367a1c9c21f09e0f1bf1f2252ba8d16a5bfbba3 Mon Sep 17 00:00:00 2001 From: Yuri Volkov Date: Sat, 28 Dec 2019 22:13:21 +0300 Subject: [PATCH] #356 Make widget entry date and position of the widget entry on a timeline independent from an event start and end/due dates. Introduce special timeline positions like "End of today" in order to implement Tasks positioning and sorting as per https://github.com/plusonelabs/calendar-widget/issues/356#issuecomment-559910887 --- .../andstatus/todoagenda/BirthdayTest.java | 8 +- .../todoagenda/MultidayAllDayEventTest.java | 10 +- .../todoagenda/MultidayEventTest.java | 16 ++- .../todoagenda/OngoingEventTest.java | 8 +- .../PastDueHeaderWithTasksTest.java | 8 +- .../todoagenda/WrongDatesLostEventsTest.java | 8 +- .../todoagenda/RemoteViewsFactory.java | 14 +- .../calendar/CalendarEventVisualizer.java | 2 +- .../todoagenda/prefs/InstanceSettings.java | 2 +- .../todoagenda/prefs/TextShadingPref.java | 6 +- .../andstatus/todoagenda/util/DateUtil.java | 49 ++++--- .../todoagenda/widget/CalendarEntry.java | 60 ++++---- .../todoagenda/widget/DayHeader.java | 9 +- .../widget/DayHeaderVisualizer.java | 6 +- .../todoagenda/widget/LastEntry.java | 12 +- .../todoagenda/widget/TaskEntry.java | 14 +- .../todoagenda/widget/WidgetEntry.java | 129 ++++++++++++++---- .../widget/WidgetEntryPosition.java | 29 ++++ 18 files changed, 255 insertions(+), 135 deletions(-) create mode 100644 app/src/main/java/org/andstatus/todoagenda/widget/WidgetEntryPosition.java diff --git a/app/src/androidTest/java/org/andstatus/todoagenda/BirthdayTest.java b/app/src/androidTest/java/org/andstatus/todoagenda/BirthdayTest.java index 0ee44cb4..6d1693b0 100644 --- a/app/src/androidTest/java/org/andstatus/todoagenda/BirthdayTest.java +++ b/app/src/androidTest/java/org/andstatus/todoagenda/BirthdayTest.java @@ -84,10 +84,10 @@ private void playAtOneTime(QueryResultsStorage inputs, DateTime now, int entries factory.getWidgetEntries().size()); if (entriesWithoutLastExpected > 0) { CalendarEntry birthday = (CalendarEntry) factory.getWidgetEntries().get(1); - assertEquals(9, birthday.getStartDate().dayOfMonth().get()); - assertEquals(0, birthday.getStartDate().hourOfDay().get()); - assertEquals(0, birthday.getStartDate().minuteOfHour().get()); - assertEquals(0, birthday.getStartDate().millisOfDay().get()); + assertEquals(9, birthday.entryDate.dayOfMonth().get()); + assertEquals(0, birthday.entryDate.hourOfDay().get()); + assertEquals(0, birthday.entryDate.minuteOfHour().get()); + assertEquals(0, birthday.entryDate.millisOfDay().get()); assertEquals(true, birthday.isAllDay()); } } diff --git a/app/src/androidTest/java/org/andstatus/todoagenda/MultidayAllDayEventTest.java b/app/src/androidTest/java/org/andstatus/todoagenda/MultidayAllDayEventTest.java index 2bd7d919..120c80ca 100644 --- a/app/src/androidTest/java/org/andstatus/todoagenda/MultidayAllDayEventTest.java +++ b/app/src/androidTest/java/org/andstatus/todoagenda/MultidayAllDayEventTest.java @@ -48,16 +48,16 @@ public void testInsidePeriod() throws IOException, JSONException { WidgetEntry entry = factory.getWidgetEntries().get(ind); String logMsg = method + "; " + String.format("%02d ", ind) + entry.toString(); Log.v(TAG, logMsg); - if (entry.getStartDay().isBefore(today)) { + if (entry.getEntryDay().isBefore(today)) { fail("Is present before today " + logMsg); } - if (entry.getStartDay().isAfter(endOfRangeTime)) { + if (entry.getEntryDay().isAfter(endOfRangeTime)) { fail("After end of range " + logMsg); } - int dayOfEntry = entry.getStartDay().getDayOfYear(); + int dayOfEntry = entry.getEntryDay().getDayOfYear(); if (entry instanceof DayHeader) { if (dayOfHeaderPrev == 0) { - if (entry.getStartDate().withTimeAtStartOfDay().isAfter(today)) { + if (entry.entryDate.withTimeAtStartOfDay().isAfter(today)) { fail("No today's header " + logMsg); } } else { @@ -68,7 +68,7 @@ public void testInsidePeriod() throws IOException, JSONException { assertEquals(LastEntry.LastEntryType.LAST, ((LastEntry) entry).type); } else { if (dayOfEventEntryPrev == 0) { - if (entry.getStartDate().withTimeAtStartOfDay().isAfter(today)) { + if (entry.entryDate.withTimeAtStartOfDay().isAfter(today)) { fail("Today not filled " + logMsg); } } else { diff --git a/app/src/androidTest/java/org/andstatus/todoagenda/MultidayEventTest.java b/app/src/androidTest/java/org/andstatus/todoagenda/MultidayEventTest.java index a0cfa8a0..ef6d1efc 100644 --- a/app/src/androidTest/java/org/andstatus/todoagenda/MultidayEventTest.java +++ b/app/src/androidTest/java/org/andstatus/todoagenda/MultidayEventTest.java @@ -7,6 +7,7 @@ import org.andstatus.todoagenda.widget.CalendarEntry; import org.andstatus.todoagenda.widget.WidgetEntry; import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -29,9 +30,10 @@ public class MultidayEventTest extends BaseWidgetTest { */ @Test public void testEventWhichCarryOverToTheNextDay() { - DateTime today = DateUtil.now(provider.getSettings().getTimeZone()).withTimeAtStartOfDay(); + DateTimeZone timeZone = provider.getSettings().getTimeZone(); + DateTime today = DateUtil.now(timeZone).withTimeAtStartOfDay(); CalendarEvent event = new CalendarEvent(provider.getContext(), provider.getWidgetId(), - provider.getSettings().getTimeZone(), false); + timeZone, false); event.setEventSource(provider.getFirstActiveEventSource()); event.setEventId(++eventId); event.setTitle("Event that carry over to the next day, show as ending midnight"); @@ -58,15 +60,15 @@ public void testEventWhichCarryOverToTheNextDay() { assertTrue("Is Part of Multi Day Event", entry1.isPartOfMultiDayEvent()); assertTrue("Is start of Multi Day Event", entry1.isStartOfMultiDayEvent()); assertFalse("Is not an end of Multi Day Event", entry1.isEndOfMultiDayEvent()); - assertEquals("Start Time didn't change for today's event", event.getStartDate(), entry1.getStartDate()); - assertEquals("Event entry end time is next midnight", today.plusDays(1), entry1.getEndDate()); + assertEquals("Start Time didn't change for today's event", event.getStartDate(), entry1.entryDate); + assertEquals("Entry end time should be the same as Event end time", event.getEndDate(), entry1.getEndDate()); assertNotNull(entry2); assertFalse("Is not active event", entry2.getEvent().isActive()); assertTrue("Is Part of Multi Day Event", entry2.isPartOfMultiDayEvent()); assertFalse("Is not start of Multi Day Event", entry2.isStartOfMultiDayEvent()); assertTrue("Is end of Multi Day Event", entry2.isEndOfMultiDayEvent()); - assertEquals("Start Time of tomorrow's entry is midnight", today.plusDays(1), entry2.getStartDate()); + assertEquals("Start Time of tomorrow's entry is midnight", today.plusDays(1), entry2.entryDate); assertEquals("Tomorrow event entry end time is the same as for the event", entry2.getEvent().getEndDate(), entry2.getEndDate()); } @@ -92,7 +94,7 @@ public void testThreeDaysEvent() { private void assertSundayEntryAt(CalendarEvent event, DateTime sunday, DateTime currentDateTime) { CalendarEntry entry1 = getSundayEntryAt(event, currentDateTime); - assertEquals(sunday, entry1.getStartDate()); + assertEquals(sunday, entry1.entryDate); assertEquals(event.getEndDate(), entry1.getEndDate()); assertEquals(event.getTitle(), entry1.getTitle()); String timeString = entry1.getEventTimeString(); @@ -111,7 +113,7 @@ private CalendarEntry getSundayEntryAt(CalendarEvent event, DateTime currentDate for (WidgetEntry item : factory.getWidgetEntries()) { if (item instanceof CalendarEntry) { CalendarEntry entry = (CalendarEntry) item; - if (entry.getStartDate().getDayOfMonth() == 20) { + if (entry.entryDate.getDayOfMonth() == 20) { assertNull(sundayEntry); sundayEntry = entry; } diff --git a/app/src/androidTest/java/org/andstatus/todoagenda/OngoingEventTest.java b/app/src/androidTest/java/org/andstatus/todoagenda/OngoingEventTest.java index 4a4a699b..1aff8d64 100644 --- a/app/src/androidTest/java/org/andstatus/todoagenda/OngoingEventTest.java +++ b/app/src/androidTest/java/org/andstatus/todoagenda/OngoingEventTest.java @@ -46,7 +46,7 @@ public void testTodaysOngoingEvent() { assertNotNull(entry); assertTrue("Is active event", entry.getEvent().isActive()); assertFalse("Is not part of Multi Day Event", entry.isPartOfMultiDayEvent()); - assertEquals("Start Time didn't change for today's event", event.getStartDate(), entry.getStartDate()); + assertEquals("Start Time didn't change for today's event", event.getStartDate(), entry.entryDate); assertEquals("End Time didn't change for today's event", event.getEndDate(), entry.getEndDate()); } @@ -79,7 +79,7 @@ public void testYesterdaysOngoingEvent() { assertTrue("Is Part of Multi Day Event", entry.isPartOfMultiDayEvent()); assertFalse("Is not start of Multi Day Event", entry.isStartOfMultiDayEvent()); assertTrue("Is end of Multi Day Event", entry.isEndOfMultiDayEvent()); - assertEquals("Yesterday's event entry start time is midnight", today, entry.getStartDate()); + assertEquals("Yesterday's event entry start time is midnight", today, entry.entryDate); assertEquals("End Time didn't change for yesterday's event", event.getEndDate(), entry.getEndDate()); } @@ -110,8 +110,8 @@ public void testEventWhichCarryOverToTheNextDay() { assertTrue("Is Part of Multi Day Event", entry.isPartOfMultiDayEvent()); assertTrue("Is start of Multi Day Event", entry.isStartOfMultiDayEvent()); assertFalse("Is not an end of Multi Day Event", entry.isEndOfMultiDayEvent()); - assertEquals("Start Time didn't change for today's event", event.getStartDate(), entry.getStartDate()); - assertEquals("Event entry end time is next midnight", today.plusDays(1), entry.getEndDate()); + assertEquals("Start Time didn't change for today's event", event.getStartDate(), entry.entryDate); + assertEquals("Entry end time is the same as Event end time", event.getEndDate(), entry.getEndDate()); } } diff --git a/app/src/androidTest/java/org/andstatus/todoagenda/PastDueHeaderWithTasksTest.java b/app/src/androidTest/java/org/andstatus/todoagenda/PastDueHeaderWithTasksTest.java index 64870612..5602960a 100644 --- a/app/src/androidTest/java/org/andstatus/todoagenda/PastDueHeaderWithTasksTest.java +++ b/app/src/androidTest/java/org/andstatus/todoagenda/PastDueHeaderWithTasksTest.java @@ -39,16 +39,16 @@ public void testPastDueHeaderWithTasks() throws IOException, JSONException { factory.onDataSetChanged(); factory.logWidgetEntries(method); - assertEquals("Past and Due header", DateUtil.DATETIME_MIN, factory.getWidgetEntries().get(0).getStartDate()); + assertEquals("Past and Due header", DateUtil.DATETIME_MIN, factory.getWidgetEntries().get(0).entryDate); assertEquals("Past Calendar Entry", CalendarEntry.class, factory.getWidgetEntries().get(1).getClass()); assertEquals("Due task Entry", TaskEntry.class, factory.getWidgetEntries().get(2).getClass()); assertEquals("Due task Entry", dateTime(2019, 8, 1, 9, 0), - (factory.getWidgetEntries().get(2)).getStartDate()); + (factory.getWidgetEntries().get(2)).entryDate); assertEquals("Today header", dateTime(2019, 8, 4), - (factory.getWidgetEntries().get(3)).getStartDate()); + (factory.getWidgetEntries().get(3)).entryDate); assertEquals("Future task Entry", TaskEntry.class, factory.getWidgetEntries().get(8).getClass()); assertEquals("Future task Entry", dateTime(2019, 8, 8, 21, 0), - (factory.getWidgetEntries().get(8)).getStartDate()); + (factory.getWidgetEntries().get(8)).entryDate); assertEquals("Last Entry", LastEntry.LastEntryType.LAST, ((LastEntry) factory.getWidgetEntries().get(9)).type); assertEquals("Number of entries", 10, factory.getWidgetEntries().size()); } diff --git a/app/src/androidTest/java/org/andstatus/todoagenda/WrongDatesLostEventsTest.java b/app/src/androidTest/java/org/andstatus/todoagenda/WrongDatesLostEventsTest.java index bb4f3e64..9ceeb226 100644 --- a/app/src/androidTest/java/org/andstatus/todoagenda/WrongDatesLostEventsTest.java +++ b/app/src/androidTest/java/org/andstatus/todoagenda/WrongDatesLostEventsTest.java @@ -2,6 +2,8 @@ import android.util.Log; +import androidx.test.platform.app.InstrumentationRegistry; + import org.andstatus.todoagenda.provider.QueryResultsStorage; import org.andstatus.todoagenda.widget.CalendarEntry; import org.json.JSONException; @@ -9,8 +11,6 @@ import java.io.IOException; -import androidx.test.platform.app.InstrumentationRegistry; - import static org.junit.Assert.assertEquals; /** @@ -33,8 +33,8 @@ public void testIssue205() throws IOException, JSONException { factory.logWidgetEntries(method); assertEquals("Number of entries", 11, factory.getWidgetEntries().size()); assertEquals("On Saturday", "Maker Fair", ((CalendarEntry) factory.getWidgetEntries().get(4)).getEvent().getTitle()); - assertEquals("On Saturday", 6, factory.getWidgetEntries().get(4).getStartDate().getDayOfWeek()); + assertEquals("On Saturday", 6, factory.getWidgetEntries().get(4).entryDate.getDayOfWeek()); assertEquals("On Sunday", "Ribakovs", ((CalendarEntry) factory.getWidgetEntries().get(7)).getEvent().getTitle()); - assertEquals("On Sunday", 7, factory.getWidgetEntries().get(7).getStartDate().getDayOfWeek()); + assertEquals("On Sunday", 7, factory.getWidgetEntries().get(7).entryDate.getDayOfWeek()); } } diff --git a/app/src/main/java/org/andstatus/todoagenda/RemoteViewsFactory.java b/app/src/main/java/org/andstatus/todoagenda/RemoteViewsFactory.java index 3c22bec2..44e0c7a4 100644 --- a/app/src/main/java/org/andstatus/todoagenda/RemoteViewsFactory.java +++ b/app/src/main/java/org/andstatus/todoagenda/RemoteViewsFactory.java @@ -175,14 +175,14 @@ private List> getVisualizers() { private int getTodaysPosition() { for (int ind = 0; ind < getWidgetEntries().size() - 1; ind++) { - if (getWidgetEntries().get(ind).getStartDaySection() != TimeSection.PAST) return ind; + if (getWidgetEntries().get(ind).getTimeSection() != TimeSection.PAST) return ind; } return getWidgetEntries().size() - 1; } private int getTomorrowsPosition() { for (int ind = 0; ind < getWidgetEntries().size() - 1; ind++) { - if (getWidgetEntries().get(ind).getStartDaySection() == TimeSection.FUTURE) return ind; + if (getWidgetEntries().get(ind).getTimeSection() == TimeSection.FUTURE) return ind; } return getWidgetEntries().size() > 0 ? 0 : -1; } @@ -225,17 +225,17 @@ private List addDayHeaders(List listIn) { DayHeader curDayBucket = new DayHeader(DateUtil.DATETIME_MIN); boolean pastEventsHeaderAdded = false; for (WidgetEntry entry : listIn) { - DateTime nextStartOfDay = entry.getStartDay(); - if (settings.getShowPastEventsUnderOneHeader() && nextStartOfDay.isBefore(today)) { + DateTime nextEntryDay = entry.getEntryDay(); + if (settings.getShowPastEventsUnderOneHeader() && nextEntryDay.isBefore(today)) { if(!pastEventsHeaderAdded) { listOut.add(curDayBucket); pastEventsHeaderAdded = true; } - } else if (!nextStartOfDay.isEqual(curDayBucket.getStartDay())) { + } else if (!nextEntryDay.isEqual(curDayBucket.getEntryDay())) { if (settings.getShowDaysWithoutEvents()) { - addEmptyDayHeadersBetweenTwoDays(listOut, curDayBucket.getStartDay(), nextStartOfDay); + addEmptyDayHeadersBetweenTwoDays(listOut, curDayBucket.getEntryDay(), nextEntryDay); } - curDayBucket = new DayHeader(nextStartOfDay); + curDayBucket = new DayHeader(nextEntryDay); listOut.add(curDayBucket); } listOut.add(entry); diff --git a/app/src/main/java/org/andstatus/todoagenda/calendar/CalendarEventVisualizer.java b/app/src/main/java/org/andstatus/todoagenda/calendar/CalendarEventVisualizer.java index eb240729..948b3cef 100644 --- a/app/src/main/java/org/andstatus/todoagenda/calendar/CalendarEventVisualizer.java +++ b/app/src/main/java/org/andstatus/todoagenda/calendar/CalendarEventVisualizer.java @@ -134,7 +134,7 @@ private void createFollowingEntries(List entryList, CalendarEntry if (endDate.isAfter(eventProvider.getEndOfTimeRange())) { endDate = eventProvider.getEndOfTimeRange(); } - DateTime thisDay = dayOneEntry.getStartDay().plusDays(1).withTimeAtStartOfDay(); + DateTime thisDay = dayOneEntry.getEntryDay().plusDays(1).withTimeAtStartOfDay(); while (thisDay.isBefore(endDate)) { CalendarEntry nextEntry = CalendarEntry.fromEvent(dayOneEntry.getEvent(), thisDay); entryList.add(nextEntry); diff --git a/app/src/main/java/org/andstatus/todoagenda/prefs/InstanceSettings.java b/app/src/main/java/org/andstatus/todoagenda/prefs/InstanceSettings.java index 163c51e7..3a6368f4 100644 --- a/app/src/main/java/org/andstatus/todoagenda/prefs/InstanceSettings.java +++ b/app/src/main/java/org/andstatus/todoagenda/prefs/InstanceSettings.java @@ -623,7 +623,7 @@ public TextShading getShading(TextShadingPref pref) { } public int getEntryBackgroundColor(WidgetEntry entry) { - return entry.getEndTimeSection() + return entry.getTimeSection() .select(getPastEventsBackgroundColor(), getTodaysEventsBackgroundColor(), getEventsBackgroundColor()); } diff --git a/app/src/main/java/org/andstatus/todoagenda/prefs/TextShadingPref.java b/app/src/main/java/org/andstatus/todoagenda/prefs/TextShadingPref.java index 230f62ef..9ef2b098 100644 --- a/app/src/main/java/org/andstatus/todoagenda/prefs/TextShadingPref.java +++ b/app/src/main/java/org/andstatus/todoagenda/prefs/TextShadingPref.java @@ -41,15 +41,15 @@ public enum TextShadingPref { } public static TextShadingPref forDayHeader(WidgetEntry entry) { - return entry.getStartDaySection().select(DAY_HEADER_PAST, DAY_HEADER_TODAY, DAY_HEADER_FUTURE); + return entry.getTimeSection().select(DAY_HEADER_PAST, DAY_HEADER_TODAY, DAY_HEADER_FUTURE); } public static TextShadingPref forDetails(WidgetEntry entry) { - return entry.getEndTimeSection().select(DAY_HEADER_PAST, DAY_HEADER_TODAY, DAY_HEADER_FUTURE); + return entry.getTimeSection().select(DAY_HEADER_PAST, DAY_HEADER_TODAY, DAY_HEADER_FUTURE); } public static TextShadingPref forTitle(WidgetEntry entry) { - return entry.getEndTimeSection().select(ENTRY_PAST, ENTRY_TODAY, ENTRY_FUTURE); + return entry.getTimeSection().select(ENTRY_PAST, ENTRY_TODAY, ENTRY_FUTURE); } } diff --git a/app/src/main/java/org/andstatus/todoagenda/util/DateUtil.java b/app/src/main/java/org/andstatus/todoagenda/util/DateUtil.java index 981c2394..58f2c3a9 100644 --- a/app/src/main/java/org/andstatus/todoagenda/util/DateUtil.java +++ b/app/src/main/java/org/andstatus/todoagenda/util/DateUtil.java @@ -5,6 +5,9 @@ import android.text.format.DateUtils; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.andstatus.todoagenda.R; import org.andstatus.todoagenda.prefs.InstanceSettings; import org.joda.time.DateTime; @@ -16,14 +19,13 @@ import java.util.Formatter; import java.util.Locale; -import androidx.annotation.NonNull; - public class DateUtil { private static final String COMMA_SPACE = ", "; private static volatile DateTime mNow = null; private static volatile DateTime mNowSetAt = DateTime.now(); public static final DateTime DATETIME_MIN = new DateTime(0, DateTimeZone.UTC); + public static final DateTime DATETIME_MAX = new DateTime(Long.MAX_VALUE, DateTimeZone.UTC); public static boolean isMidnight(DateTime date) { return date.isEqual(date.withTimeAtStartOfDay()); @@ -86,37 +88,30 @@ public static CharSequence getDaysFromTodayString(Context context, int daysFromT } } - public static boolean isToday(DateTime date) { - return !isBeforeToday(date) && date.isBefore(DateUtil.now(date.getZone()).plusDays(1).withTimeAtStartOfDay()); + public static boolean isToday(@Nullable DateTime date) { + return isDateDefined(date) && !isBeforeToday(date) && date.isBefore(DateUtil.now(date.getZone()).plusDays(1).withTimeAtStartOfDay()); } - public static boolean isBeforeToday(DateTime date) { - return date.isBefore(DateUtil.now(date.getZone()).withTimeAtStartOfDay()); + public static boolean isBeforeToday(@Nullable DateTime date) { + return isDateDefined(date) && date.isBefore(DateUtil.now(date.getZone()).withTimeAtStartOfDay()); } - public static boolean isAfterToday(DateTime date) { - return !date.isBefore(DateUtil.now(date.getZone()).withTimeAtStartOfDay().plusDays(1)); + public static boolean isAfterToday(@Nullable DateTime date) { + return isDateDefined(date) && !date.isBefore(DateUtil.now(date.getZone()).withTimeAtStartOfDay().plusDays(1)); } - public static boolean isBeforeNow(DateTime date) { - return date.isBefore(now(date.getZone())); + public static boolean isBeforeNow(@Nullable DateTime date) { + return isDateDefined(date) && date.isBefore(now(date.getZone())); } public static DateTime startOfTomorrow(DateTimeZone zone) { return startOfNextDay(DateUtil.now(zone)); } + public static DateTime startOfNextDay(DateTime date) { return date.plusDays(1).withTimeAtStartOfDay(); } - public static DateTime endOfToday(DateTimeZone zone) { - return endOfSameDay(DateUtil.now(zone)); - } - - public static DateTime endOfSameDay(DateTime date) { - return date.plusDays(1).withTimeAtStartOfDay().minusSeconds(1); - } - public static void setNow(DateTime now) { mNowSetAt = DateTime.now(); mNow = now; @@ -173,4 +168,22 @@ public static String formatLogDateTime(long time) { } return Long.toString(time); // Fallback if above doesn't work } + + public static boolean isSameDate(@Nullable DateTime date, @Nullable DateTime other) { + if (date == null && other == null) return true; + if (date == null || other == null) return false; + + return date.equals(other); + } + + public static boolean isSameDay(@Nullable DateTime date, @Nullable DateTime other) { + if (date == null && other == null) return true; + if (date == null || other == null) return false; + + return date.year().equals(other.year()) && date.dayOfYear().equals(other.dayOfYear()); + } + + public static boolean isDateDefined(@Nullable DateTime dateTime) { + return dateTime != null && dateTime.isAfter(DATETIME_MIN) && dateTime.isBefore(DATETIME_MAX); + } } diff --git a/app/src/main/java/org/andstatus/todoagenda/widget/CalendarEntry.java b/app/src/main/java/org/andstatus/todoagenda/widget/CalendarEntry.java index fb868f00..7dfda0af 100644 --- a/app/src/main/java/org/andstatus/todoagenda/widget/CalendarEntry.java +++ b/app/src/main/java/org/andstatus/todoagenda/widget/CalendarEntry.java @@ -5,6 +5,8 @@ import android.text.format.DateFormat; import android.text.format.DateUtils; +import androidx.annotation.Nullable; + import org.andstatus.todoagenda.R; import org.andstatus.todoagenda.calendar.CalendarEvent; import org.andstatus.todoagenda.prefs.InstanceSettings; @@ -12,12 +14,14 @@ import org.andstatus.todoagenda.util.DateUtil; import org.joda.time.DateTime; +import static org.andstatus.todoagenda.util.DateUtil.isDateDefined; + public class CalendarEntry extends WidgetEntry { private static final String TWELVE = "12"; private static final String AUTO = "auto"; - private static final String SPACE_ARROW = " →"; - private static final String ARROW_SPACE = "→ "; + private static final String ARROW = "→"; + private static final String SPACE = " "; private static final String EMPTY_STRING = ""; static final String SPACE_DASH_SPACE = " - "; @@ -25,16 +29,16 @@ public class CalendarEntry extends WidgetEntry { private CalendarEvent event; public static CalendarEntry fromEvent(CalendarEvent event, DateTime entryDate) { - CalendarEntry entry = new CalendarEntry(); - entry.setStartDate(entryDate); - if (event.getEndDate().isBefore(entry.getEndDate())) { - entry.setEndDate(event.getEndDate()); - } + CalendarEntry entry = new CalendarEntry(entryDate, event.getEndDate()); entry.allDay = event.isAllDay(); entry.event = event; return entry; } + private CalendarEntry(DateTime entryDate, DateTime eventEndDate) { + super(WidgetEntryPosition.ENTRY_DATE, entryDate, eventEndDate); + } + @Override public String getTitle() { String title = event.getTitle(); @@ -70,15 +74,15 @@ public boolean isPartOfMultiDayEvent() { } public boolean isStartOfMultiDayEvent() { - return isPartOfMultiDayEvent() && !getEvent().getStartDate().isBefore(getStartDate()); + return isPartOfMultiDayEvent() && !getEvent().getStartDate().isBefore(entryDate); } public boolean isEndOfMultiDayEvent() { - return isPartOfMultiDayEvent() && !getEvent().getEndDate().isAfter(getEndDate()); + return isPartOfMultiDayEvent() && isLastEntryOfEvent; } public boolean spansOneFullDay() { - return getStartDate().plusDays(1).isEqual(getEndDate()); + return entryDate.plusDays(1).isEqual(event.getEndDate()); } public CalendarEvent getEvent() { @@ -105,7 +109,7 @@ private boolean hideLocation() { private String createTimeSpanString(Context context) { if (isAllDay() && !getSettings().getFillAllDayEvents()) { DateTime dateTime = getEvent().getEndDate().minusDays(1); - return ARROW_SPACE + DateUtil.createDateString(getSettings(), dateTime); + return ARROW + SPACE + DateUtil.createDateString(getSettings(), dateTime); } else { return createTimeStringForCalendarEntry(context); } @@ -115,20 +119,19 @@ private String createTimeStringForCalendarEntry(Context context) { String startStr; String endStr; String separator = SPACE_DASH_SPACE; - if (isPartOfMultiDayEvent() && DateUtil.isMidnight(getStartDate()) - && !isStartOfMultiDayEvent()) { - startStr = ARROW_SPACE; - separator = EMPTY_STRING; + if (!isDateDefined(entryDate) || (isPartOfMultiDayEvent() && DateUtil.isMidnight(entryDate) + && !isStartOfMultiDayEvent())) { + startStr = ARROW; + separator = SPACE; } else { - startStr = createTimeString(context, getStartDate()); + startStr = createTimeString(context, entryDate); } if (getSettings().getShowEndTime()) { - if (isPartOfMultiDayEvent() && DateUtil.isMidnight(getEndDate()) - && !isEndOfMultiDayEvent()) { - endStr = SPACE_ARROW; - separator = EMPTY_STRING; + if (!isDateDefined(event.getEndDate()) || (isPartOfMultiDayEvent() && !isLastEntryOfEvent)) { + endStr = ARROW; + separator = SPACE; } else { - endStr = createTimeString(context, getEndDate()); + endStr = createTimeString(context, event.getEndDate()); } } else { separator = EMPTY_STRING; @@ -142,7 +145,9 @@ private String createTimeStringForCalendarEntry(Context context) { return startStr + separator + endStr; } - private String createTimeString(Context context, DateTime time) { + private String createTimeString(Context context, @Nullable DateTime time) { + if (!isDateDefined(time)) return EMPTY_STRING; + String dateFormat = getSettings().getDateFormat(); if (!DateFormat.is24HourFormat(context) && dateFormat.equals(AUTO) || dateFormat.equals(TWELVE)) { @@ -168,9 +173,8 @@ public OrderedEventSource getSource() { @Override public String toString() { - return "CalendarEntry [" - + "startDate=" + getStartDate() - + ", endDate=" + getEndDate() + return super.toString() + " CalendarEntry [" + + "endDate=" + getEndDate() + ", allDay=" + allDay + ", time=" + getEventTimeString() + ", location=" + getLocationString() @@ -187,16 +191,14 @@ public boolean equals(Object o) { return false; } CalendarEntry that = (CalendarEntry) o; - if (!event.equals(that.event) || !getStartDate().equals(that.getStartDate())) { - return false; - } - return true; + return event.equals(that.event) && entryDate.equals(that.entryDate); } @Override public int hashCode() { int result = super.hashCode(); result += 31 * event.hashCode(); + result += 31 * entryDate.hashCode(); return result; } } diff --git a/app/src/main/java/org/andstatus/todoagenda/widget/DayHeader.java b/app/src/main/java/org/andstatus/todoagenda/widget/DayHeader.java index d2ee8ca2..ad9763dd 100644 --- a/app/src/main/java/org/andstatus/todoagenda/widget/DayHeader.java +++ b/app/src/main/java/org/andstatus/todoagenda/widget/DayHeader.java @@ -2,14 +2,11 @@ import org.joda.time.DateTime; +import static org.andstatus.todoagenda.widget.WidgetEntryPosition.DAY_HEADER; + public class DayHeader extends WidgetEntry { public DayHeader(DateTime date) { - setStartDate(date.withTimeAtStartOfDay()); - } - - @Override - public String toString() { - return "DayHeader [startDate=" + getStartDate() + "]"; + super(DAY_HEADER, date, null); } } diff --git a/app/src/main/java/org/andstatus/todoagenda/widget/DayHeaderVisualizer.java b/app/src/main/java/org/andstatus/todoagenda/widget/DayHeaderVisualizer.java index 3fcc2264..b80049cf 100644 --- a/app/src/main/java/org/andstatus/todoagenda/widget/DayHeaderVisualizer.java +++ b/app/src/main/java/org/andstatus/todoagenda/widget/DayHeaderVisualizer.java @@ -48,15 +48,15 @@ public RemoteViews getRemoteViews(WidgetEntry eventEntry, int position) { setBackgroundColor(rv, R.id.day_header, getSettings().getEntryBackgroundColor(entry)); setDayHeaderTitle(position, entry, rv, shadingContext); setDayHeaderSeparator(position, rv, shadingContext); - Intent intent = createOpenCalendarAtDayIntent(entry.getStartDate()); + Intent intent = createOpenCalendarAtDayIntent(entry.entryDate); rv.setOnClickFillInIntent(R.id.day_header, intent); return rv; } private void setDayHeaderTitle(int position, DayHeader entry, RemoteViews rv, ContextThemeWrapper shadingContext) { - String dateString = (entry.getStartDate().equals(DateUtil.DATETIME_MIN) + String dateString = ((entry.entryPosition == WidgetEntryPosition.PAST_AND_DUE_HEADER) ? getContext().getString(R.string.past_header) - : DateUtil.createDayHeaderTitle(getSettings(), entry.getStartDate())) + : DateUtil.createDayHeaderTitle(getSettings(), entry.entryDate)) .toUpperCase(Locale.getDefault()); rv.setTextViewText(R.id.day_header_title, dateString); setTextSize(getSettings(), rv, R.id.day_header_title, R.dimen.day_header_title); diff --git a/app/src/main/java/org/andstatus/todoagenda/widget/LastEntry.java b/app/src/main/java/org/andstatus/todoagenda/widget/LastEntry.java index f7cfa622..f11326c0 100644 --- a/app/src/main/java/org/andstatus/todoagenda/widget/LastEntry.java +++ b/app/src/main/java/org/andstatus/todoagenda/widget/LastEntry.java @@ -10,6 +10,7 @@ import static org.andstatus.todoagenda.widget.LastEntry.LastEntryType.EMPTY; import static org.andstatus.todoagenda.widget.LastEntry.LastEntryType.NO_PERMISSIONS; +import static org.andstatus.todoagenda.widget.WidgetEntryPosition.LIST_FOOTER; /** @author yvolk@yurivolkov.com */ public class LastEntry extends WidgetEntry { @@ -23,7 +24,7 @@ public static LastEntry forEmptyList(InstanceSettings settings) { public static void addLast(List widgetEntries) { if (!widgetEntries.isEmpty()) { - LastEntry entry = new LastEntry(LastEntryType.LAST, widgetEntries.get(widgetEntries.size() - 1).getStartDate()); + LastEntry entry = new LastEntry(LastEntryType.LAST, widgetEntries.get(widgetEntries.size() - 1).entryDate); widgetEntries.add(entry); } } @@ -44,12 +45,7 @@ public enum LastEntryType { public final LastEntryType type; public LastEntry(LastEntryType type, DateTime date) { + super(LIST_FOOTER, date, null); this.type = type; - super.setStartDate(date); } - - @Override - public String toString() { - return "LastEntry [" + type.name() + "]"; - } -} +} \ No newline at end of file diff --git a/app/src/main/java/org/andstatus/todoagenda/widget/TaskEntry.java b/app/src/main/java/org/andstatus/todoagenda/widget/TaskEntry.java index 7829668e..6c43d467 100644 --- a/app/src/main/java/org/andstatus/todoagenda/widget/TaskEntry.java +++ b/app/src/main/java/org/andstatus/todoagenda/widget/TaskEntry.java @@ -2,18 +2,23 @@ import org.andstatus.todoagenda.prefs.OrderedEventSource; import org.andstatus.todoagenda.task.TaskEvent; +import org.joda.time.DateTime; + +import static org.andstatus.todoagenda.widget.WidgetEntryPosition.ENTRY_DATE; public class TaskEntry extends WidgetEntry { private TaskEvent event; public static TaskEntry fromEvent(TaskEvent event) { - TaskEntry entry = new TaskEntry(); + TaskEntry entry = new TaskEntry(ENTRY_DATE, event.getStartDate(), event.getDueDate()); entry.event = event; - entry.setStartDate(event.getStartDate()); - entry.setEndDate(event.getDueDate()); return entry; } + private TaskEntry(WidgetEntryPosition entryPosition, DateTime entryDate, DateTime endDate) { + super(entryPosition, entryDate, endDate); + } + @Override public OrderedEventSource getSource() { return event.getEventSource(); @@ -30,6 +35,7 @@ public TaskEvent getEvent() { @Override public String toString() { - return "TaskEntry [startDate=" + event.getStartDate() + ", dueDate=" + event.getDueDate() + "]"; + return super.toString() + " TaskEntry [startDate=" + event.getStartDate() + ", dueDate=" + event.getDueDate() + + "]"; } } diff --git a/app/src/main/java/org/andstatus/todoagenda/widget/WidgetEntry.java b/app/src/main/java/org/andstatus/todoagenda/widget/WidgetEntry.java index 5dc824a6..59d3d175 100644 --- a/app/src/main/java/org/andstatus/todoagenda/widget/WidgetEntry.java +++ b/app/src/main/java/org/andstatus/todoagenda/widget/WidgetEntry.java @@ -1,30 +1,71 @@ package org.andstatus.todoagenda.widget; +import androidx.annotation.Nullable; + import org.andstatus.todoagenda.prefs.OrderedEventSource; import org.andstatus.todoagenda.util.DateUtil; import org.joda.time.DateTime; import org.joda.time.Days; -public abstract class WidgetEntry> implements Comparable> { +import static org.andstatus.todoagenda.util.DateUtil.isSameDate; - private DateTime startDate; - private DateTime endDate; +public abstract class WidgetEntry> implements Comparable> { - public DateTime getStartDate() { - return startDate; + public final WidgetEntryPosition entryPosition; + public final DateTime entryDate; + @Nullable + public final DateTime endDate; + public final boolean isLastEntryOfEvent; + + protected WidgetEntry(WidgetEntryPosition entryPosition, DateTime entryDate, @Nullable DateTime eventEndDate) { + this.entryPosition = entryPosition; + this.entryDate = fixEntryDate(entryPosition, entryDate); + endDate = eventEndDate; + isLastEntryOfEvent = endDate == null || + !entryPosition.entryDateIsRequired || + endDate.isBefore(DateUtil.startOfNextDay(this.entryDate)); } - public void setStartDate(DateTime startDate) { - this.startDate = startDate; - endDate = DateUtil.startOfNextDay(startDate); + private static DateTime fixEntryDate(WidgetEntryPosition entryPosition, DateTime entryDate) { + switch (entryPosition) { + case ENTRY_DATE: + throwIfNull(entryPosition, entryDate); + return entryDate; + case PAST_AND_DUE_HEADER: + return entryDate == null + ? DateUtil.DATETIME_MIN + : entryDate; + case DAY_HEADER: + case START_OF_DAY: + throwIfNull(entryPosition, entryDate); + return entryDate.withTimeAtStartOfDay(); + case START_OF_TODAY: + return DateUtil.isToday(entryDate) + ? entryDate + : DateUtil.DATETIME_MIN; + case END_OF_TODAY: + return DateUtil.isToday(entryDate) + ? entryDate + : DateUtil.DATETIME_MAX; + case END_OF_LIST_HEADER: + case END_OF_LIST: + case LIST_FOOTER: + return entryDate == null + ? DateUtil.DATETIME_MAX + : entryDate; + default: + throw new IllegalArgumentException("Invalid position " + entryPosition + "; entryDate: " + entryDate); + } } - public DateTime getStartDay() { - return getStartDate().withTimeAtStartOfDay(); + private static void throwIfNull(WidgetEntryPosition entryPosition, DateTime entryDate) { + if (entryDate == null) { + throw new IllegalArgumentException("Invalid entry date: " + entryDate + " at position " + entryPosition); + } } - public void setEndDate(DateTime endDate) { - this.endDate = endDate; + public DateTime getEntryDay() { + return entryDate.withTimeAtStartOfDay(); } public DateTime getEndDate() { @@ -44,15 +85,21 @@ public String getLocation() { } public int getDaysFromToday() { - return Days.daysBetween(DateUtil.now(startDate.getZone()).withTimeAtStartOfDay(), - startDate.withTimeAtStartOfDay()).getDays(); + return Days.daysBetween(DateUtil.now(entryDate.getZone()).withTimeAtStartOfDay(), + entryDate.withTimeAtStartOfDay()).getDays(); } @Override public int compareTo(WidgetEntry other) { - if (getStartDate().isAfter(other.getStartDate())) { + int globalSignum = Integer.signum(entryPosition.globalOrder - other.entryPosition.globalOrder); + if (globalSignum != 0) return globalSignum; + + int sameDaySignum = Integer.signum(entryPosition.sameDayOrder - other.entryPosition.sameDayOrder); + if ((sameDaySignum != 0) && DateUtil.isSameDay(entryDate, other.entryDate)) return sameDaySignum; + + if (entryDate.isAfter(other.entryDate)) { return 1; - } else if (getStartDate().isBefore(other.getStartDate())) { + } else if (entryDate.isBefore(other.entryDate)) { return -1; } int sourceSignum = Integer.signum(getSource().order - other.getSource().order); @@ -61,22 +108,50 @@ public int compareTo(WidgetEntry other) { : sourceSignum; } - public TimeSection getStartDaySection() { - return DateUtil.isBeforeToday(getStartDate()) - ? TimeSection.PAST - : (DateUtil.isToday(getStartDate()) ? TimeSection.TODAY : TimeSection.FUTURE); - } - - public TimeSection getEndTimeSection() { - return DateUtil.isBeforeNow(getEndDate()) + public TimeSection getTimeSection() { + switch (entryPosition) { + case PAST_AND_DUE_HEADER: + return TimeSection.PAST; + case START_OF_TODAY: + return TimeSection.TODAY; + case END_OF_TODAY: + case END_OF_LIST_HEADER: + case END_OF_LIST: + case LIST_FOOTER: + return TimeSection.FUTURE; + default: + break; + } + if (DateUtil.isToday(entryDate)) { + switch (entryPosition) { + case DAY_HEADER: + return TimeSection.TODAY; + default: + if (DateUtil.isToday(getEndDate())) { + return DateUtil.isBeforeNow(getEndDate()) + ? TimeSection.PAST + : TimeSection.TODAY; + } + } + } + return DateUtil.isBeforeToday(entryDate) ? TimeSection.PAST - : (DateUtil.isToday(getStartDate()) ? TimeSection.TODAY : TimeSection.FUTURE); + : (DateUtil.isToday(endDate) ? TimeSection.TODAY : TimeSection.FUTURE); } public boolean duplicates(WidgetEntry other) { - return getStartDate().equals(other.getStartDate()) && - getEndDate().equals(other.getEndDate()) && + return entryPosition == other.entryPosition && + entryDate.equals(other.entryDate) && + isSameDate(getEndDate(), other.getEndDate()) && getTitle().equals(other.getTitle()) && getLocation().equals(other.getLocation()); } + + @Override + public String toString() { + return entryPosition.value + " [entryDate=" + + (entryDate == DateUtil.DATETIME_MIN ? "min" : + (entryDate == DateUtil.DATETIME_MAX) ? "max" : entryDate) + + "]"; + } } diff --git a/app/src/main/java/org/andstatus/todoagenda/widget/WidgetEntryPosition.java b/app/src/main/java/org/andstatus/todoagenda/widget/WidgetEntryPosition.java new file mode 100644 index 00000000..29c60424 --- /dev/null +++ b/app/src/main/java/org/andstatus/todoagenda/widget/WidgetEntryPosition.java @@ -0,0 +1,29 @@ +package org.andstatus.todoagenda.widget; + +/** + * On special positions see https://github.com/plusonelabs/calendar-widget/issues/356#issuecomment-559910887 + * @author yvolk@yurivolkov.com + */ +public enum WidgetEntryPosition { + PAST_AND_DUE_HEADER("PastAndDueHeader",false, 1, 1), + DAY_HEADER("DayHeader", true, 2, 1), + START_OF_TODAY("StartOfToday", false, 2, 2), + START_OF_DAY("StartOfDay", true, 2, 3), + ENTRY_DATE("EntryDate", true, 2, 4), + END_OF_TODAY("EndOfToday", false, 2, 5), + END_OF_LIST_HEADER("EndOfListHeader", false, 3, 1), + END_OF_LIST("EndOfList", false, 4, 1), + LIST_FOOTER("ListFooter", false, 5, 1); + + final String value; + final boolean entryDateIsRequired; + final int globalOrder; + final int sameDayOrder; + + WidgetEntryPosition(String value, boolean dateIsRequired, int globalOrder, int sameDayOrder) { + this.value = value; + this.entryDateIsRequired = dateIsRequired; + this.globalOrder = globalOrder; + this.sameDayOrder = sameDayOrder; + } +}