Calendar widget: separate running tasks from running events
This commit is contained in:
parent
9e17686c5f
commit
f97fb0a88d
@ -366,7 +366,7 @@ fun CalendarItemGridPopup(
|
|||||||
|
|
||||||
private fun CalendarEvent.formatTime(context: Context): String {
|
private fun CalendarEvent.formatTime(context: Context): String {
|
||||||
val startTime = startTime
|
val startTime = startTime
|
||||||
if (startTime == null) {
|
if (startTime == null || isTask) {
|
||||||
if (allDay) {
|
if (allDay) {
|
||||||
return DateUtils.formatDateRange(
|
return DateUtils.formatDateRange(
|
||||||
context,
|
context,
|
||||||
@ -400,7 +400,7 @@ private fun CalendarEvent.formatTime(context: Context): String {
|
|||||||
|
|
||||||
private fun CalendarEvent.getSummary(context: Context): String {
|
private fun CalendarEvent.getSummary(context: Context): String {
|
||||||
val startTime = startTime
|
val startTime = startTime
|
||||||
if (startTime == null) {
|
if (isTask || startTime == null) {
|
||||||
val isToday = DateUtils.isToday(endTime)
|
val isToday = DateUtils.isToday(endTime)
|
||||||
if (isToday) {
|
if (isToday) {
|
||||||
if (allDay) {
|
if (allDay) {
|
||||||
|
|||||||
@ -52,6 +52,7 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
|
|||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import de.mm20.launcher2.Quadruple
|
import de.mm20.launcher2.Quadruple
|
||||||
|
import de.mm20.launcher2.Quintuple
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.component.InnerCard
|
import de.mm20.launcher2.ui.component.InnerCard
|
||||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||||
@ -132,6 +133,7 @@ fun CalendarWidget(
|
|||||||
val events by viewModel.calendarEvents
|
val events by viewModel.calendarEvents
|
||||||
val nextEvents by viewModel.nextEvents
|
val nextEvents by viewModel.nextEvents
|
||||||
val runningEvents by viewModel.hiddenPastEvents
|
val runningEvents by viewModel.hiddenPastEvents
|
||||||
|
val runningTasks by viewModel.hiddenRunningTasks
|
||||||
val hasPermission by viewModel.hasPermission.collectAsState()
|
val hasPermission by viewModel.hasPermission.collectAsState()
|
||||||
Column {
|
Column {
|
||||||
if (hasPermission == false) {
|
if (hasPermission == false) {
|
||||||
@ -145,10 +147,11 @@ fun CalendarWidget(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
Quadruple(
|
Quintuple(
|
||||||
selectedDate,
|
selectedDate,
|
||||||
events,
|
events,
|
||||||
runningEvents,
|
runningEvents,
|
||||||
|
runningTasks,
|
||||||
nextEvents
|
nextEvents
|
||||||
),
|
),
|
||||||
transitionSpec = {
|
transitionSpec = {
|
||||||
@ -185,7 +188,7 @@ fun CalendarWidget(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { (_, events, runningEvents, nextEvents) ->
|
) { (_, events, runningEvents, runningTasks, nextEvents) ->
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 12.dp)
|
.padding(horizontal = 12.dp)
|
||||||
@ -212,6 +215,20 @@ fun CalendarWidget(
|
|||||||
modifier = Modifier.padding(top = 8.dp)
|
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()) {
|
if (nextEvents.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.calendar_widget_next_events),
|
stringResource(R.string.calendar_widget_next_events),
|
||||||
@ -272,16 +289,11 @@ private fun Info(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clip(MaterialTheme.shapes.small)
|
.clip(MaterialTheme.shapes.small)
|
||||||
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, MaterialTheme.shapes.small)
|
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, MaterialTheme.shapes.small)
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
contentAlignment = Alignment.CenterStart,
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(enabled = onClick != null, onClick = { onClick?.invoke() })
|
.clickable(enabled = onClick != null, onClick = { onClick?.invoke() })
|
||||||
.padding(12.dp)
|
.padding(12.dp)
|
||||||
) {
|
) {
|
||||||
Text(text, style = MaterialTheme.typography.bodySmall)
|
Text(text, style = MaterialTheme.typography.bodySmall)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -54,7 +54,9 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
|
|||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||||
|
|
||||||
private var showRunningPastDayEvents = false
|
private var showRunningPastDayEvents = false
|
||||||
|
private var showRunningTasks = false
|
||||||
val hiddenPastEvents = mutableStateOf(0)
|
val hiddenPastEvents = mutableStateOf(0)
|
||||||
|
val hiddenRunningTasks = mutableStateOf(0)
|
||||||
|
|
||||||
val selectedDate = mutableStateOf(LocalDate.now())
|
val selectedDate = mutableStateOf(LocalDate.now())
|
||||||
|
|
||||||
@ -102,6 +104,7 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
|
|||||||
fun selectDate(date: LocalDate) {
|
fun selectDate(date: LocalDate) {
|
||||||
val dates = availableDates
|
val dates = availableDates
|
||||||
showRunningPastDayEvents = false
|
showRunningPastDayEvents = false
|
||||||
|
showRunningTasks = false
|
||||||
if (dates.contains(date)) {
|
if (dates.contains(date)) {
|
||||||
selectedDate.value = date
|
selectedDate.value = date
|
||||||
updateEvents()
|
updateEvents()
|
||||||
@ -113,6 +116,11 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
|
|||||||
updateEvents()
|
updateEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showAllTasks() {
|
||||||
|
showRunningTasks = true
|
||||||
|
updateEvents()
|
||||||
|
}
|
||||||
|
|
||||||
fun createEvent(context: Context) {
|
fun createEvent(context: Context) {
|
||||||
val intent = Intent(Intent.ACTION_EDIT)
|
val intent = Intent(Intent.ACTION_EDIT)
|
||||||
intent.data = CalendarContract.Events.CONTENT_URI
|
intent.data = CalendarContract.Events.CONTENT_URI
|
||||||
@ -136,15 +144,23 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
|
|||||||
val dayStart = max(now, date.atStartOfDay().toEpochSecond(offset) * 1000)
|
val dayStart = max(now, date.atStartOfDay().toEpochSecond(offset) * 1000)
|
||||||
val dayEnd = date.plusDays(1).atStartOfDay().toEpochSecond(offset) * 1000
|
val dayEnd = date.plusDays(1).atStartOfDay().toEpochSecond(offset) * 1000
|
||||||
var events = upcomingEvents.filter {
|
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) {
|
if (!showRunningPastDayEvents) {
|
||||||
val totalCount = events.size
|
val totalCount = events.size
|
||||||
|
|
||||||
|
|
||||||
events = events.filter {
|
events = events.filter {
|
||||||
(it.startTime != null && it.startTime!! >= date.atStartOfDay().toEpochSecond(offset) * 1000) ||
|
((it.startTime != null && it.startTime!! >= startOfDay) ||
|
||||||
it.endTime < date.atStartOfDay().plusDays(1).toEpochSecond(offset) * 1000
|
it.endTime < startOfNextDay) || it.isTask
|
||||||
}
|
}
|
||||||
|
|
||||||
val hiddenCount = totalCount - events.size
|
val hiddenCount = totalCount - events.size
|
||||||
@ -152,11 +168,27 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
|
|||||||
} else {
|
} else {
|
||||||
hiddenPastEvents.value = 0
|
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
|
calendarEvents.value = events
|
||||||
val e = this.upcomingEvents
|
val e = this.upcomingEvents
|
||||||
if (events.isEmpty() && e.isNotEmpty()) {
|
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 {
|
} else {
|
||||||
nextEvents.value = emptyList()
|
nextEvents.value = emptyList()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,3 +6,11 @@ data class Quadruple<out A, out B, out C, out D>(
|
|||||||
val third: C,
|
val third: C,
|
||||||
val fourth: D
|
val fourth: D
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class Quintuple<out A, out B, out C, out D, out E>(
|
||||||
|
val first: A,
|
||||||
|
val second: B,
|
||||||
|
val third: C,
|
||||||
|
val fourth: D,
|
||||||
|
val fifth: E,
|
||||||
|
)
|
||||||
@ -22,6 +22,9 @@ interface CalendarEvent : SavableSearchable {
|
|||||||
val isCompleted: Boolean?
|
val isCompleted: Boolean?
|
||||||
get() = null
|
get() = null
|
||||||
|
|
||||||
|
val isTask: Boolean
|
||||||
|
get() = isCompleted != null
|
||||||
|
|
||||||
|
|
||||||
override val preferDetailsOverLaunch: Boolean
|
override val preferDetailsOverLaunch: Boolean
|
||||||
get() = true
|
get() = true
|
||||||
|
|||||||
@ -318,6 +318,10 @@
|
|||||||
<item quantity="one">+%1$d running event from past days</item>
|
<item quantity="one">+%1$d running event from past days</item>
|
||||||
<item quantity="other">+%1$d running events from past days</item>
|
<item quantity="other">+%1$d running events from past days</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<plurals name="calendar_widget_running_tasks">
|
||||||
|
<item quantity="one">+%1$d unfinished task</item>
|
||||||
|
<item quantity="other">+%1$d unfinished tasks</item>
|
||||||
|
</plurals>
|
||||||
<!-- Calendar events that last the entire day -->
|
<!-- Calendar events that last the entire day -->
|
||||||
<string name="calendar_event_allday">all-day</string>
|
<string name="calendar_event_allday">all-day</string>
|
||||||
<string name="task_due_time">Due at %1$s</string>
|
<string name="task_due_time">Due at %1$s</string>
|
||||||
|
|||||||
@ -24,8 +24,8 @@ internal class TasksCalendarProvider(
|
|||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
queryTasks(
|
queryTasks(
|
||||||
selection = buildList {
|
selection = buildList {
|
||||||
add("dueDate >= $from")
|
add("($to >= hideUntil OR hideUntil IS NULL)")
|
||||||
add("dueDate <= $to")
|
add("$from <= dueDate")
|
||||||
if (excludedCalendars.isNotEmpty()) {
|
if (excludedCalendars.isNotEmpty()) {
|
||||||
add("cdl_id NOT IN (${excludedCalendars.joinToString()})")
|
add("cdl_id NOT IN (${excludedCalendars.joinToString()})")
|
||||||
}
|
}
|
||||||
@ -82,6 +82,7 @@ internal class TasksCalendarProvider(
|
|||||||
cursor?.use {
|
cursor?.use {
|
||||||
val idIndex = cursor.getColumnIndex("_id")
|
val idIndex = cursor.getColumnIndex("_id")
|
||||||
val titleIndex = cursor.getColumnIndex("title")
|
val titleIndex = cursor.getColumnIndex("title")
|
||||||
|
val startIndex = cursor.getColumnIndex("hideUntil")
|
||||||
val dueIndex = cursor.getColumnIndex("dueDate")
|
val dueIndex = cursor.getColumnIndex("dueDate")
|
||||||
val completedIndex = cursor.getColumnIndex("completed")
|
val completedIndex = cursor.getColumnIndex("completed")
|
||||||
val notesIndex = cursor.getColumnIndex("notes")
|
val notesIndex = cursor.getColumnIndex("notes")
|
||||||
@ -99,7 +100,7 @@ internal class TasksCalendarProvider(
|
|||||||
description = cursor.getStringOrNull(notesIndex),
|
description = cursor.getStringOrNull(notesIndex),
|
||||||
color = cursor.getIntOrNull(colorIndex),
|
color = cursor.getIntOrNull(colorIndex),
|
||||||
calendarName = cursor.getStringOrNull(calendarNameIndex),
|
calendarName = cursor.getStringOrNull(calendarNameIndex),
|
||||||
startTime = null,
|
startTime = cursor.getLongOrNull(startIndex)?.takeIf { it > 0L },
|
||||||
endTime = dueDate,
|
endTime = dueDate,
|
||||||
allDay = dueDate % 60000 <= 0, // https://github.com/tasks/tasks/blob/13d4c029e855fd32ec91e4d4ec5f740ec506136e/data/src/commonMain/kotlin/org/tasks/data/entity/Task.kt#L345
|
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,
|
isCompleted = (cursor.getLongOrNull(completedIndex) ?: 0L) != 0L,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user