diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt index 34e8abd4..26f667b8 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt @@ -366,7 +366,7 @@ fun CalendarItemGridPopup( private fun CalendarEvent.formatTime(context: Context): String { val startTime = startTime - if (startTime == null) { + if (startTime == null || isTask) { if (allDay) { return DateUtils.formatDateRange( context, @@ -400,7 +400,7 @@ private fun CalendarEvent.formatTime(context: Context): String { private fun CalendarEvent.getSummary(context: Context): String { val startTime = startTime - if (startTime == null) { + if (isTask || startTime == null) { val isToday = DateUtils.isToday(endTime) if (isToday) { if (allDay) { diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidget.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidget.kt index e7801197..e17ff216 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidget.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidget.kt @@ -52,6 +52,7 @@ import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.Quadruple +import de.mm20.launcher2.Quintuple import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.InnerCard import de.mm20.launcher2.ui.component.MissingPermissionBanner @@ -132,6 +133,7 @@ fun CalendarWidget( val events by viewModel.calendarEvents val nextEvents by viewModel.nextEvents val runningEvents by viewModel.hiddenPastEvents + val runningTasks by viewModel.hiddenRunningTasks val hasPermission by viewModel.hasPermission.collectAsState() Column { if (hasPermission == false) { @@ -145,10 +147,11 @@ fun CalendarWidget( ) } AnimatedContent( - Quadruple( + Quintuple( selectedDate, events, runningEvents, + runningTasks, nextEvents ), transitionSpec = { @@ -185,7 +188,7 @@ fun CalendarWidget( } } } - ) { (_, events, runningEvents, nextEvents) -> + ) { (_, events, runningEvents, runningTasks, nextEvents) -> Column( modifier = Modifier .padding(horizontal = 12.dp) @@ -212,6 +215,20 @@ fun CalendarWidget( modifier = Modifier.padding(top = 8.dp) ) } + + if (runningTasks > 0) { + Info( + text = pluralStringResource( + R.plurals.calendar_widget_running_tasks, + runningTasks, + runningTasks + ), + onClick = { + viewModel.showAllTasks() + }, + modifier = Modifier.padding(top = 8.dp) + ) + } if (nextEvents.isNotEmpty()) { Text( stringResource(R.string.calendar_widget_next_events), @@ -272,15 +289,10 @@ private fun Info( .fillMaxWidth() .clip(MaterialTheme.shapes.small) .border(1.dp, MaterialTheme.colorScheme.outlineVariant, MaterialTheme.shapes.small) + .clickable(enabled = onClick != null, onClick = { onClick?.invoke() }) + .padding(12.dp) ) { - Box( - contentAlignment = Alignment.CenterStart, - modifier = Modifier - .clickable(enabled = onClick != null, onClick = { onClick?.invoke() }) - .padding(12.dp) - ) { - Text(text, style = MaterialTheme.typography.bodySmall) - } + Text(text, style = MaterialTheme.typography.bodySmall) } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidgetVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidgetVM.kt index efb42202..a8293c9b 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidgetVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/calendar/CalendarWidgetVM.kt @@ -54,7 +54,9 @@ class CalendarWidgetVM : ViewModel(), KoinComponent { .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) private var showRunningPastDayEvents = false + private var showRunningTasks = false val hiddenPastEvents = mutableStateOf(0) + val hiddenRunningTasks = mutableStateOf(0) val selectedDate = mutableStateOf(LocalDate.now()) @@ -102,6 +104,7 @@ class CalendarWidgetVM : ViewModel(), KoinComponent { fun selectDate(date: LocalDate) { val dates = availableDates showRunningPastDayEvents = false + showRunningTasks = false if (dates.contains(date)) { selectedDate.value = date updateEvents() @@ -113,6 +116,11 @@ class CalendarWidgetVM : ViewModel(), KoinComponent { updateEvents() } + fun showAllTasks() { + showRunningTasks = true + updateEvents() + } + fun createEvent(context: Context) { val intent = Intent(Intent.ACTION_EDIT) intent.data = CalendarContract.Events.CONTENT_URI @@ -136,15 +144,23 @@ class CalendarWidgetVM : ViewModel(), KoinComponent { val dayStart = max(now, date.atStartOfDay().toEpochSecond(offset) * 1000) val dayEnd = date.plusDays(1).atStartOfDay().toEpochSecond(offset) * 1000 var events = upcomingEvents.filter { - it.endTime >= dayStart && (it.startTime ?: it.endTime) < dayEnd + if (it.isTask && it.isCompleted == true) { + it.endTime >= dayStart && it.endTime < dayEnd + } else { + it.endTime >= dayStart && (it.startTime ?: 0L) < dayEnd + } } + val startOfDay = date.atStartOfDay().toEpochSecond(offset) * 1000 + val startOfNextDay = date.atStartOfDay().plusDays(1).toEpochSecond(offset) * 1000 + if (!showRunningPastDayEvents) { val totalCount = events.size + events = events.filter { - (it.startTime != null && it.startTime!! >= date.atStartOfDay().toEpochSecond(offset) * 1000) || - it.endTime < date.atStartOfDay().plusDays(1).toEpochSecond(offset) * 1000 + ((it.startTime != null && it.startTime!! >= startOfDay) || + it.endTime < startOfNextDay) || it.isTask } val hiddenCount = totalCount - events.size @@ -152,11 +168,27 @@ class CalendarWidgetVM : ViewModel(), KoinComponent { } else { hiddenPastEvents.value = 0 } + if (!showRunningTasks) { + val totalCount = events.size + + events = events.filter { + ((it.startTime != null && it.startTime!! >= startOfDay) || + it.endTime < startOfNextDay) || !it.isTask + } + + val hiddenCount = totalCount - events.size + hiddenRunningTasks.value = hiddenCount + } else { + hiddenRunningTasks.value = 0 + } calendarEvents.value = events val e = this.upcomingEvents if (events.isEmpty() && e.isNotEmpty()) { - nextEvents.value = listOf(e[0]) + nextEvents.value = listOfNotNull( + e.sortedBy { if (it.isTask) it.endTime else (it.startTime ?: 0L) } + .find { now < if (it.isTask) it.endTime else (it.startTime ?: 0L) } + ) } else { nextEvents.value = emptyList() } diff --git a/core/base/src/main/java/de/mm20/launcher2/Tuples.kt b/core/base/src/main/java/de/mm20/launcher2/Tuples.kt index 1ed96c98..ba987d55 100644 --- a/core/base/src/main/java/de/mm20/launcher2/Tuples.kt +++ b/core/base/src/main/java/de/mm20/launcher2/Tuples.kt @@ -5,4 +5,12 @@ data class Quadruple( val second: B, val third: C, val fourth: D +) + +data class Quintuple( + val first: A, + val second: B, + val third: C, + val fourth: D, + val fifth: E, ) \ No newline at end of file diff --git a/core/base/src/main/java/de/mm20/launcher2/search/CalendarEvent.kt b/core/base/src/main/java/de/mm20/launcher2/search/CalendarEvent.kt index 1bf78e04..f85109d6 100644 --- a/core/base/src/main/java/de/mm20/launcher2/search/CalendarEvent.kt +++ b/core/base/src/main/java/de/mm20/launcher2/search/CalendarEvent.kt @@ -22,6 +22,9 @@ interface CalendarEvent : SavableSearchable { val isCompleted: Boolean? get() = null + val isTask: Boolean + get() = isCompleted != null + override val preferDetailsOverLaunch: Boolean get() = true diff --git a/core/i18n/src/main/res/values/strings.xml b/core/i18n/src/main/res/values/strings.xml index f7ab1e2e..9215f1e1 100644 --- a/core/i18n/src/main/res/values/strings.xml +++ b/core/i18n/src/main/res/values/strings.xml @@ -318,6 +318,10 @@ +%1$d running event from past days +%1$d running events from past days + + +%1$d unfinished task + +%1$d unfinished tasks + all-day Due at %1$s diff --git a/data/calendar/src/main/java/de/mm20/launcher2/calendar/providers/TasksCalendarProvider.kt b/data/calendar/src/main/java/de/mm20/launcher2/calendar/providers/TasksCalendarProvider.kt index 9fcfc394..7382077f 100644 --- a/data/calendar/src/main/java/de/mm20/launcher2/calendar/providers/TasksCalendarProvider.kt +++ b/data/calendar/src/main/java/de/mm20/launcher2/calendar/providers/TasksCalendarProvider.kt @@ -24,8 +24,8 @@ internal class TasksCalendarProvider( return withContext(Dispatchers.IO) { queryTasks( selection = buildList { - add("dueDate >= $from") - add("dueDate <= $to") + add("($to >= hideUntil OR hideUntil IS NULL)") + add("$from <= dueDate") if (excludedCalendars.isNotEmpty()) { add("cdl_id NOT IN (${excludedCalendars.joinToString()})") } @@ -82,6 +82,7 @@ internal class TasksCalendarProvider( cursor?.use { val idIndex = cursor.getColumnIndex("_id") val titleIndex = cursor.getColumnIndex("title") + val startIndex = cursor.getColumnIndex("hideUntil") val dueIndex = cursor.getColumnIndex("dueDate") val completedIndex = cursor.getColumnIndex("completed") val notesIndex = cursor.getColumnIndex("notes") @@ -99,7 +100,7 @@ internal class TasksCalendarProvider( description = cursor.getStringOrNull(notesIndex), color = cursor.getIntOrNull(colorIndex), calendarName = cursor.getStringOrNull(calendarNameIndex), - startTime = null, + startTime = cursor.getLongOrNull(startIndex)?.takeIf { it > 0L }, endTime = dueDate, allDay = dueDate % 60000 <= 0, // https://github.com/tasks/tasks/blob/13d4c029e855fd32ec91e4d4ec5f740ec506136e/data/src/commonMain/kotlin/org/tasks/data/entity/Task.kt#L345 isCompleted = (cursor.getLongOrNull(completedIndex) ?: 0L) != 0L,