reworked home screen widgets to mirror the in-app timetable, hidden below android 12

This commit is contained in:
2026-09-27 14:07:52 +02:00
parent 2aed3d104c
commit 2ca9feb49a
44 changed files with 2401 additions and 1245 deletions
@@ -11,7 +11,7 @@ import java.util.TimeZone
// Mirror of lib/widget_data/widget_data.dart — JSON keys + enum names
// must stay in sync.
enum class WidgetLessonStatus {
REGULAR, ONGOING, PAST, CANCELLED, IRREGULAR, TEACHER_CHANGED, EVENT;
REGULAR, ONGOING, PAST, CANCELLED, IRREGULAR, TEACHER_CHANGED, EVENT, DUTY;
companion object {
fun fromWire(raw: String?): WidgetLessonStatus = when (raw) {
@@ -22,6 +22,7 @@ enum class WidgetLessonStatus {
"irregular" -> IRREGULAR
"teacherChanged" -> TEACHER_CHANGED
"event" -> EVENT
"duty" -> DUTY
else -> REGULAR
}
}
@@ -39,6 +40,9 @@ data class WidgetLesson(
val originalTeacher: String?,
val status: WidgetLessonStatus,
val customColor: String?,
// Tile colour resolved in Dart (subject colours, palettes); null only for
// payloads written before the field existed.
val color: Int?,
val siblingCount: Int,
)
@@ -50,6 +54,12 @@ data class WidgetPeriod(
val virtualEndMinutes: Int,
)
data class WidgetDayInfo(
val date: Date,
val isHoliday: Boolean,
val holidayName: String?,
)
data class WidgetTimetableData(
val fetchedAt: Date,
val anchorDate: Date,
@@ -57,6 +67,8 @@ data class WidgetTimetableData(
val periods: List<WidgetPeriod>,
val isHoliday: Boolean,
val holidayName: String?,
// Week payload only; empty for the day payload.
val days: List<WidgetDayInfo>,
)
object WidgetDataParser {
@@ -81,6 +93,11 @@ object WidgetDataParser {
}
}
private fun parseColor(raw: String?): Int? {
if (raw == null || !Regex("#[0-9A-Fa-f]{6}").matches(raw)) return null
return 0xFF000000.toInt() or raw.substring(1).toInt(16)
}
fun parse(json: String?): WidgetTimetableData? {
if (json.isNullOrEmpty()) return null
return try {
@@ -102,6 +119,7 @@ object WidgetDataParser {
originalTeacher = obj.stringOrNull("originalTeacher"),
status = WidgetLessonStatus.fromWire(obj.stringOrNull("status")),
customColor = obj.stringOrNull("customColor"),
color = parseColor(obj.stringOrNull("color")),
siblingCount = obj.optInt("siblingCount", 0),
)
}
@@ -120,6 +138,18 @@ object WidgetDataParser {
)
}
}
val daysArray = root.optJSONArray("days")
val days = mutableListOf<WidgetDayInfo>()
if (daysArray != null) {
for (i in 0 until daysArray.length()) {
val obj = daysArray.optJSONObject(i) ?: continue
days += WidgetDayInfo(
date = parseDate(obj.stringOrNull("date")) ?: continue,
isHoliday = obj.optBoolean("isHoliday", false),
holidayName = obj.stringOrNull("holidayName"),
)
}
}
WidgetTimetableData(
fetchedAt = parseDate(root.stringOrNull("fetchedAt")) ?: Date(),
anchorDate = parseDate(root.stringOrNull("anchorDate")) ?: Date(),
@@ -127,6 +157,7 @@ object WidgetDataParser {
periods = periods,
isHoliday = root.optBoolean("isHoliday", false),
holidayName = root.stringOrNull("holidayName"),
days = days,
)
} catch (_: JSONException) {
null
File diff suppressed because it is too large Load Diff