Skip to content

Commit a8e2186

Browse files
P4u14Paula
andauthored
fix: Monthly widget doesn't show multiple-day events properly #15 (#1068)
* #15 adapt styling for multi-day events on monthlyWidget to look like one rectangle * refactor to avoid exceeding method length --------- Co-authored-by: Paula <paula.schreiber@altavo.eu>
1 parent 12b8d73 commit a8e2186

7 files changed

Lines changed: 269 additions & 28 deletions

app/src/main/kotlin/org/fossify/calendar/helpers/MyWidgetMonthlyProvider.kt

Lines changed: 97 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ class MyWidgetMonthlyProvider : AppWidgetProvider() {
9999
val dimCompletedTasks = context.config.dimCompletedTasks
100100
val smallerFontSize = context.getWidgetFontSize() - 3f
101101
val res = context.resources
102-
val len = days.size
103102
val packageName = context.packageName
104103
views.apply {
105104
setTextColor(R.id.week_num, textColor)
@@ -117,51 +116,121 @@ class MyWidgetMonthlyProvider : AppWidgetProvider() {
117116
}
118117
}
119118

120-
for (i in 0 until len) {
121-
val day = days[i]
119+
val eventDayIndices = mutableMapOf<Pair<Long, Long>, MutableList<Int>>()
120+
val eventByKey = mutableMapOf<Pair<Long, Long>, Event>()
121+
for (i in days.indices) {
122+
for (event in days[i].dayEvents) {
123+
val key = Pair(event.id ?: 0L, event.startTS)
124+
eventDayIndices.getOrPut(key) { mutableListOf() }.add(i)
125+
if (key !in eventByKey) eventByKey[key] = event
126+
}
127+
}
122128

129+
val sortedKeys = eventDayIndices.keys.sortedWith(
130+
compareBy(
131+
{ -(eventDayIndices[it]!!.size) },
132+
{ (eventByKey[it]!!.flags and FLAG_ALL_DAY) == 0 },
133+
{ eventByKey[it]!!.startTS },
134+
{ eventByKey[it]!!.endTS },
135+
{ eventDayIndices[it]!!.firstOrNull() ?: 0 },
136+
{ eventByKey[it]!!.title }
137+
)
138+
)
139+
140+
val daySlotLists = buildDaySlotLists(days.size, sortedKeys, eventDayIndices, eventByKey)
141+
142+
val titleShownForKey = mutableSetOf<Pair<Long, Long>>()
143+
for (i in days.indices) {
144+
val day = days[i]
123145
val dayTextColor = if (context.config.highlightWeekends && day.isWeekend) {
124146
context.config.highlightWeekendsColor
125147
} else {
126148
textColor
127149
}
128-
129150
val weakTextColor = dayTextColor.adjustAlpha(MEDIUM_ALPHA)
130151
val currTextColor = if (day.isThisMonth) dayTextColor else weakTextColor
131152
val id = res.getIdentifier("day_$i", "id", packageName)
132153
views.removeAllViews(id)
133154
addDayNumber(context, views, day, currTextColor, id)
134155
setupDayOpenIntent(context, views, id, day.code)
135156

136-
day.dayEvents = day.dayEvents.asSequence().sortedWith(compareBy({ it.flags and FLAG_ALL_DAY == 0 }, { it.startTS }, { it.title }))
137-
.toMutableList() as ArrayList<Event>
138-
139-
day.dayEvents.forEach {
140-
val backgroundColor = it.color
141-
var eventTextColor = backgroundColor.getContrastColor()
142-
val shouldDim = (it.isTask() && it.isTaskCompleted() && dimCompletedTasks)
143-
|| (dimPastEvents && it.isPastEvent && !it.isTask())
144-
if (shouldDim) {
145-
eventTextColor = eventTextColor.adjustAlpha(MEDIUM_ALPHA)
157+
for (slotEvent in daySlotLists[i]) {
158+
if (slotEvent == null) {
159+
views.addView(id, createSpacerView(packageName))
160+
} else {
161+
val key = Pair(slotEvent.id ?: 0L, slotEvent.startTS)
162+
val showTitle = titleShownForKey.add(key) || i % 7 == 0
163+
var eventTextColor = slotEvent.color.getContrastColor()
164+
val shouldDim = (slotEvent.isTask() && slotEvent.isTaskCompleted() && dimCompletedTasks)
165+
|| (dimPastEvents && slotEvent.isPastEvent && !slotEvent.isTask())
166+
if (shouldDim) {
167+
eventTextColor = eventTextColor.adjustAlpha(MEDIUM_ALPHA)
168+
}
169+
val dayIndices = eventDayIndices[key]!!
170+
val prevInRun = i > 0 && i % 7 != 0 && dayIndices.contains(i - 1)
171+
val nextInRun = i < days.size - 1 && (i + 1) % 7 != 0 && dayIndices.contains(i + 1)
172+
val eventLayout = when {
173+
prevInRun && nextInRun -> R.layout.day_monthly_event_view_widget_event_middle
174+
prevInRun -> R.layout.day_monthly_event_view_widget_event_end
175+
nextInRun -> R.layout.day_monthly_event_view_widget_event_start
176+
else -> R.layout.day_monthly_event_view_widget
177+
}
178+
val newRemoteView = RemoteViews(packageName, eventLayout).apply {
179+
setTextColor(R.id.day_monthly_event_id, eventTextColor)
180+
setTextSize(R.id.day_monthly_event_id, smallerFontSize - 3f)
181+
setInt(R.id.day_monthly_event_background, "setColorFilter", slotEvent.color)
182+
if (showTitle) {
183+
setText(R.id.day_monthly_event_id, slotEvent.title.replace(" ", "\u00A0"))
184+
setVisibleIf(R.id.day_monthly_task_image, slotEvent.isTask())
185+
applyColorFilter(R.id.day_monthly_task_image, eventTextColor)
186+
if (slotEvent.shouldStrikeThrough()) {
187+
setInt(R.id.day_monthly_event_id, "setPaintFlags", Paint.ANTI_ALIAS_FLAG or Paint.STRIKE_THRU_TEXT_FLAG)
188+
} else {
189+
setInt(R.id.day_monthly_event_id, "setPaintFlags", Paint.ANTI_ALIAS_FLAG)
190+
}
191+
} else {
192+
setText(R.id.day_monthly_event_id, "")
193+
setVisibleIf(R.id.day_monthly_task_image, false)
194+
setInt(R.id.day_monthly_event_id, "setPaintFlags", Paint.ANTI_ALIAS_FLAG)
195+
}
196+
}
197+
views.addView(id, newRemoteView)
146198
}
199+
}
200+
}
201+
}
147202

148-
val newRemoteView = RemoteViews(packageName, R.layout.day_monthly_event_view_widget).apply {
149-
setText(R.id.day_monthly_event_id, it.title.replace(" ", "\u00A0"))
150-
setTextColor(R.id.day_monthly_event_id, eventTextColor)
151-
setTextSize(R.id.day_monthly_event_id, smallerFontSize - 3f)
152-
setVisibleIf(R.id.day_monthly_task_image, it.isTask())
153-
applyColorFilter(R.id.day_monthly_task_image, eventTextColor)
154-
setInt(R.id.day_monthly_event_background, "setColorFilter", it.color)
155-
156-
if (it.shouldStrikeThrough()) {
157-
setInt(R.id.day_monthly_event_id, "setPaintFlags", Paint.ANTI_ALIAS_FLAG or Paint.STRIKE_THRU_TEXT_FLAG)
158-
} else {
159-
setInt(R.id.day_monthly_event_id, "setPaintFlags", Paint.ANTI_ALIAS_FLAG)
160-
}
203+
private fun buildDaySlotLists(
204+
daysSize: Int,
205+
sortedKeys: List<Pair<Long, Long>>,
206+
eventDayIndices: Map<Pair<Long, Long>, List<Int>>,
207+
eventByKey: Map<Pair<Long, Long>, Event>
208+
): Array<MutableList<Event?>> {
209+
val daySlotIndex = IntArray(daysSize) { 0 }
210+
val daySlotLists = Array(daysSize) { mutableListOf<Event?>() }
211+
for (key in sortedKeys) {
212+
val dayIndices = eventDayIndices[key]!!
213+
val event = eventByKey[key]!!
214+
val slot = dayIndices.maxOf { daySlotIndex[it] }
215+
for (dayIdx in dayIndices) {
216+
while (daySlotIndex[dayIdx] < slot) {
217+
daySlotLists[dayIdx].add(null) // invisible spacer
218+
daySlotIndex[dayIdx]++
161219
}
162-
views.addView(id, newRemoteView)
220+
daySlotLists[dayIdx].add(event)
221+
daySlotIndex[dayIdx]++
163222
}
164223
}
224+
return daySlotLists
225+
}
226+
227+
private fun createSpacerView(packageName: String): RemoteViews {
228+
return RemoteViews(packageName, R.layout.day_monthly_event_view_widget).apply {
229+
setText(R.id.day_monthly_event_id, " ")
230+
setViewVisibility(R.id.day_monthly_event_background, View.INVISIBLE)
231+
setViewVisibility(R.id.day_monthly_task_image, View.GONE)
232+
setInt(R.id.day_monthly_event_id, "setPaintFlags", Paint.ANTI_ALIAS_FLAG)
233+
}
165234
}
166235

167236
private fun addDayNumber(context: Context, views: RemoteViews, day: DayMonthly, textColor: Int, id: Int) {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:shape="rectangle">
4+
<corners
5+
android:topLeftRadius="0dp"
6+
android:bottomLeftRadius="0dp"
7+
android:topRightRadius="2dp"
8+
android:bottomRightRadius="2dp" />
9+
<solid android:color="@android:color/white" />
10+
</shape>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:shape="rectangle">
4+
<solid android:color="@android:color/white" />
5+
</shape>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:shape="rectangle">
4+
<corners
5+
android:topLeftRadius="2dp"
6+
android:bottomLeftRadius="2dp"
7+
android:topRightRadius="0dp"
8+
android:bottomRightRadius="0dp" />
9+
<solid android:color="@android:color/white" />
10+
</shape>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools"
4+
android:id="@+id/day_monthly_event_holder"
5+
android:layout_width="match_parent"
6+
android:layout_height="wrap_content">
7+
8+
<ImageView
9+
android:id="@+id/day_monthly_event_background"
10+
android:layout_width="match_parent"
11+
android:layout_height="match_parent"
12+
android:layout_alignBottom="@+id/day_monthly_event_id"
13+
android:layout_marginTop="@dimen/one_dp"
14+
android:layout_marginBottom="@dimen/one_dp"
15+
android:layout_marginStart="0dp"
16+
android:layout_marginEnd="@dimen/one_dp"
17+
android:contentDescription="@null"
18+
android:src="@drawable/day_monthly_event_background_widget_end" />
19+
20+
<ImageView
21+
android:id="@+id/day_monthly_task_image"
22+
android:layout_width="@dimen/activity_margin"
23+
android:layout_height="@dimen/activity_margin"
24+
android:layout_alignTop="@+id/day_monthly_event_id"
25+
android:layout_alignBottom="@+id/day_monthly_event_id"
26+
android:adjustViewBounds="true"
27+
android:contentDescription="@string/task"
28+
android:paddingStart="@dimen/one_dp"
29+
android:paddingTop="@dimen/one_dp"
30+
android:paddingBottom="@dimen/one_dp"
31+
android:scaleType="fitCenter"
32+
android:src="@drawable/ic_task_vector" />
33+
34+
<TextView
35+
android:id="@+id/day_monthly_event_id"
36+
android:layout_width="wrap_content"
37+
android:layout_height="wrap_content"
38+
android:layout_toEndOf="@+id/day_monthly_task_image"
39+
android:ellipsize="none"
40+
android:gravity="start"
41+
android:hyphenationFrequency="none"
42+
android:maxLines="1"
43+
android:paddingStart="@dimen/tiny_margin"
44+
android:paddingEnd="@dimen/tiny_margin"
45+
android:textSize="@dimen/day_monthly_text_size"
46+
tools:targetApi="m"
47+
tools:text="My event" />
48+
49+
</RelativeLayout>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools"
4+
android:id="@+id/day_monthly_event_holder"
5+
android:layout_width="match_parent"
6+
android:layout_height="wrap_content">
7+
8+
<ImageView
9+
android:id="@+id/day_monthly_event_background"
10+
android:layout_width="match_parent"
11+
android:layout_height="match_parent"
12+
android:layout_alignBottom="@+id/day_monthly_event_id"
13+
android:layout_marginTop="@dimen/one_dp"
14+
android:layout_marginBottom="@dimen/one_dp"
15+
android:layout_marginStart="0dp"
16+
android:layout_marginEnd="0dp"
17+
android:contentDescription="@null"
18+
android:src="@drawable/day_monthly_event_background_widget_middle" />
19+
20+
<ImageView
21+
android:id="@+id/day_monthly_task_image"
22+
android:layout_width="@dimen/activity_margin"
23+
android:layout_height="@dimen/activity_margin"
24+
android:layout_alignTop="@+id/day_monthly_event_id"
25+
android:layout_alignBottom="@+id/day_monthly_event_id"
26+
android:adjustViewBounds="true"
27+
android:contentDescription="@string/task"
28+
android:paddingStart="@dimen/one_dp"
29+
android:paddingTop="@dimen/one_dp"
30+
android:paddingBottom="@dimen/one_dp"
31+
android:scaleType="fitCenter"
32+
android:src="@drawable/ic_task_vector" />
33+
34+
<TextView
35+
android:id="@+id/day_monthly_event_id"
36+
android:layout_width="wrap_content"
37+
android:layout_height="wrap_content"
38+
android:layout_toEndOf="@+id/day_monthly_task_image"
39+
android:ellipsize="none"
40+
android:gravity="start"
41+
android:hyphenationFrequency="none"
42+
android:maxLines="1"
43+
android:paddingStart="@dimen/tiny_margin"
44+
android:paddingEnd="@dimen/tiny_margin"
45+
android:textSize="@dimen/day_monthly_text_size"
46+
tools:targetApi="m"
47+
tools:text="My event" />
48+
49+
</RelativeLayout>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools"
4+
android:id="@+id/day_monthly_event_holder"
5+
android:layout_width="match_parent"
6+
android:layout_height="wrap_content">
7+
8+
<ImageView
9+
android:id="@+id/day_monthly_event_background"
10+
android:layout_width="match_parent"
11+
android:layout_height="match_parent"
12+
android:layout_alignBottom="@+id/day_monthly_event_id"
13+
android:layout_marginTop="@dimen/one_dp"
14+
android:layout_marginBottom="@dimen/one_dp"
15+
android:layout_marginStart="@dimen/one_dp"
16+
android:layout_marginEnd="0dp"
17+
android:contentDescription="@null"
18+
android:src="@drawable/day_monthly_event_background_widget_start" />
19+
20+
<ImageView
21+
android:id="@+id/day_monthly_task_image"
22+
android:layout_width="@dimen/activity_margin"
23+
android:layout_height="@dimen/activity_margin"
24+
android:layout_alignTop="@+id/day_monthly_event_id"
25+
android:layout_alignBottom="@+id/day_monthly_event_id"
26+
android:adjustViewBounds="true"
27+
android:contentDescription="@string/task"
28+
android:paddingStart="@dimen/one_dp"
29+
android:paddingTop="@dimen/one_dp"
30+
android:paddingBottom="@dimen/one_dp"
31+
android:scaleType="fitCenter"
32+
android:src="@drawable/ic_task_vector" />
33+
34+
<TextView
35+
android:id="@+id/day_monthly_event_id"
36+
android:layout_width="wrap_content"
37+
android:layout_height="wrap_content"
38+
android:layout_toEndOf="@+id/day_monthly_task_image"
39+
android:ellipsize="none"
40+
android:gravity="start"
41+
android:hyphenationFrequency="none"
42+
android:maxLines="1"
43+
android:paddingStart="@dimen/tiny_margin"
44+
android:paddingEnd="@dimen/tiny_margin"
45+
android:textSize="@dimen/day_monthly_text_size"
46+
tools:targetApi="m"
47+
tools:text="My event" />
48+
49+
</RelativeLayout>

0 commit comments

Comments
 (0)