1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
package org.terst.doot.widget.data
import java.time.Instant
import java.time.temporal.ChronoUnit
data class FloatingSlot(
val task: WidgetItem,
val slotStart: Instant,
val slotEnd: Instant
)
data class TaskFragment(
val slots: List<FloatingSlot>,
val startTime: Instant,
val endTime: Instant
)
object SlotPacker {
/**
* Assigns 1-hour slots to floating tasks, packing them forward from [now],
* bumping past any overlapping scheduled events.
* Returns groups of consecutive task slots as TaskFragments.
* A new fragment starts whenever a scheduled event falls between two task slots.
*/
fun pack(
floatingTasks: List<WidgetItem>,
scheduledEvents: List<WidgetItem>,
now: Instant
): List<TaskFragment> {
if (floatingTasks.isEmpty()) return emptyList()
val eventIntervals: List<Pair<Instant, Instant>> = scheduledEvents.mapNotNull { event ->
val s = event.start?.let { runCatching { Instant.parse(it) }.getOrNull() } ?: return@mapNotNull null
val e = event.end?.let { runCatching { Instant.parse(it) }.getOrNull() } ?: return@mapNotNull null
s to e
}
val slots = mutableListOf<FloatingSlot>()
var cursor = now
for (task in floatingTasks) {
var slotStart = cursor
var slotEnd = slotStart.plus(1, ChronoUnit.HOURS)
// Bump cursor past any overlapping event, re-checking after each bump
var bumped = true
while (bumped) {
bumped = false
for ((evStart, evEnd) in eventIntervals) {
if (slotStart < evEnd && slotEnd > evStart) {
slotStart = evEnd
slotEnd = slotStart.plus(1, ChronoUnit.HOURS)
bumped = true
break
}
}
}
slots.add(FloatingSlot(task, slotStart, slotEnd))
cursor = slotEnd
}
// Group consecutive slots into fragments, splitting when a scheduled event falls between them
val fragments = mutableListOf<TaskFragment>()
var current = mutableListOf(slots[0])
for (i in 1 until slots.size) {
val prevEnd = slots[i - 1].slotEnd
val nextStart = slots[i].slotStart
val eventBetween = prevEnd < nextStart && eventIntervals.any { (evStart, _) ->
evStart >= prevEnd && evStart < nextStart
}
if (eventBetween) {
fragments.add(current.toFragment())
current = mutableListOf()
}
current.add(slots[i])
}
fragments.add(current.toFragment())
return fragments
}
private fun List<FloatingSlot>.toFragment() = TaskFragment(
slots = this,
startTime = first().slotStart,
endTime = last().slotEnd
)
}
|