reworked home screen widgets to mirror the in-app timetable, hidden below android 12
This commit is contained in:
@@ -81,6 +81,7 @@
|
|||||||
the home_widget Flutter plugin resolves them as <app-package>.<name>. -->
|
the home_widget Flutter plugin resolves them as <app-package>.<name>. -->
|
||||||
<receiver
|
<receiver
|
||||||
android:name="eu.mhsl.marianum.mobile.client.TimetableDayWidget"
|
android:name="eu.mhsl.marianum.mobile.client.TimetableDayWidget"
|
||||||
|
android:enabled="@bool/widget_supported"
|
||||||
android:label="@string/widget_day_label"
|
android:label="@string/widget_day_label"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -93,6 +94,7 @@
|
|||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name="eu.mhsl.marianum.mobile.client.TimetableWeekWidget"
|
android:name="eu.mhsl.marianum.mobile.client.TimetableWeekWidget"
|
||||||
|
android:enabled="@bool/widget_supported"
|
||||||
android:label="@string/widget_week_label"
|
android:label="@string/widget_week_label"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import java.util.TimeZone
|
|||||||
// Mirror of lib/widget_data/widget_data.dart — JSON keys + enum names
|
// Mirror of lib/widget_data/widget_data.dart — JSON keys + enum names
|
||||||
// must stay in sync.
|
// must stay in sync.
|
||||||
enum class WidgetLessonStatus {
|
enum class WidgetLessonStatus {
|
||||||
REGULAR, ONGOING, PAST, CANCELLED, IRREGULAR, TEACHER_CHANGED, EVENT;
|
REGULAR, ONGOING, PAST, CANCELLED, IRREGULAR, TEACHER_CHANGED, EVENT, DUTY;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromWire(raw: String?): WidgetLessonStatus = when (raw) {
|
fun fromWire(raw: String?): WidgetLessonStatus = when (raw) {
|
||||||
@@ -22,6 +22,7 @@ enum class WidgetLessonStatus {
|
|||||||
"irregular" -> IRREGULAR
|
"irregular" -> IRREGULAR
|
||||||
"teacherChanged" -> TEACHER_CHANGED
|
"teacherChanged" -> TEACHER_CHANGED
|
||||||
"event" -> EVENT
|
"event" -> EVENT
|
||||||
|
"duty" -> DUTY
|
||||||
else -> REGULAR
|
else -> REGULAR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,6 +40,9 @@ data class WidgetLesson(
|
|||||||
val originalTeacher: String?,
|
val originalTeacher: String?,
|
||||||
val status: WidgetLessonStatus,
|
val status: WidgetLessonStatus,
|
||||||
val customColor: String?,
|
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,
|
val siblingCount: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,6 +54,12 @@ data class WidgetPeriod(
|
|||||||
val virtualEndMinutes: Int,
|
val virtualEndMinutes: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class WidgetDayInfo(
|
||||||
|
val date: Date,
|
||||||
|
val isHoliday: Boolean,
|
||||||
|
val holidayName: String?,
|
||||||
|
)
|
||||||
|
|
||||||
data class WidgetTimetableData(
|
data class WidgetTimetableData(
|
||||||
val fetchedAt: Date,
|
val fetchedAt: Date,
|
||||||
val anchorDate: Date,
|
val anchorDate: Date,
|
||||||
@@ -57,6 +67,8 @@ data class WidgetTimetableData(
|
|||||||
val periods: List<WidgetPeriod>,
|
val periods: List<WidgetPeriod>,
|
||||||
val isHoliday: Boolean,
|
val isHoliday: Boolean,
|
||||||
val holidayName: String?,
|
val holidayName: String?,
|
||||||
|
// Week payload only; empty for the day payload.
|
||||||
|
val days: List<WidgetDayInfo>,
|
||||||
)
|
)
|
||||||
|
|
||||||
object WidgetDataParser {
|
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? {
|
fun parse(json: String?): WidgetTimetableData? {
|
||||||
if (json.isNullOrEmpty()) return null
|
if (json.isNullOrEmpty()) return null
|
||||||
return try {
|
return try {
|
||||||
@@ -102,6 +119,7 @@ object WidgetDataParser {
|
|||||||
originalTeacher = obj.stringOrNull("originalTeacher"),
|
originalTeacher = obj.stringOrNull("originalTeacher"),
|
||||||
status = WidgetLessonStatus.fromWire(obj.stringOrNull("status")),
|
status = WidgetLessonStatus.fromWire(obj.stringOrNull("status")),
|
||||||
customColor = obj.stringOrNull("customColor"),
|
customColor = obj.stringOrNull("customColor"),
|
||||||
|
color = parseColor(obj.stringOrNull("color")),
|
||||||
siblingCount = obj.optInt("siblingCount", 0),
|
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(
|
WidgetTimetableData(
|
||||||
fetchedAt = parseDate(root.stringOrNull("fetchedAt")) ?: Date(),
|
fetchedAt = parseDate(root.stringOrNull("fetchedAt")) ?: Date(),
|
||||||
anchorDate = parseDate(root.stringOrNull("anchorDate")) ?: Date(),
|
anchorDate = parseDate(root.stringOrNull("anchorDate")) ?: Date(),
|
||||||
@@ -127,6 +157,7 @@ object WidgetDataParser {
|
|||||||
periods = periods,
|
periods = periods,
|
||||||
isHoliday = root.optBoolean("isHoliday", false),
|
isHoliday = root.optBoolean("isHoliday", false),
|
||||||
holidayName = root.stringOrNull("holidayName"),
|
holidayName = root.stringOrNull("holidayName"),
|
||||||
|
days = days,
|
||||||
)
|
)
|
||||||
} catch (_: JSONException) {
|
} catch (_: JSONException) {
|
||||||
null
|
null
|
||||||
|
|||||||
+589
-330
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Material Icons "coffee_outlined", matches the in-app period ruler. -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M18.52 3H6C4.92 3 3.98 3.89 3.98 5.02V10.69C3.98 14.53 6.94 17.91 10.78 18C14.72 18.14 18 14.95 18 11.02V9.98H18.52C20.44 9.98 21.98 8.44 21.98 6.52C21.98 4.55 20.44 3 18.52 3ZM15.98 5.02V8.02H6V5.02H15.98ZM15.98 9.98V11.02C15.98 13.78 13.78 15.98 11.02 15.98C8.25 15.98 6 13.78 6 11.02V9.98ZM18.52 8.02H18V5.02H18.52C19.31 5.02 20.02 5.67 20.02 6.52C20.02 7.31 19.31 8.02 18.52 8.02ZM3.98 18.98H20.02V21H3.98V18.98Z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Material Icons "event_outlined", matches the in-app holiday region. -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M18.98 3.98H18V2.02H15.98V3.98H8.02V2.02H6V3.98H5.02C3.89 3.98 3 4.92 3 6V20.02C3 21.09 3.89 21.98 5.02 21.98H18.98C20.11 21.98 21 21.09 21 20.02V6C21 4.92 20.11 3.98 18.98 3.98ZM18.98 20.02H5.02V9.98H18.98V20.02ZM18.98 8.02H5.02V6H18.98V8.02ZM12 12.98H17.02V18H12V12.98Z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Material Icons "restaurant", matches the in-app break region. -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M11.02 9H9V2.02H6.98V9H5.02V2.02H3V9C3 11.11 4.64 12.84 6.75 12.98V21.98H9.23V12.98C11.34 12.84 12.98 11.11 12.98 9V2.02H11.02V9ZM15.98 6V14.02H18.52V21.98H21V2.02C18.23 2.02 15.98 4.22 15.98 6Z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Tinted at runtime with the tile colour resolved in Dart. -->
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:radius="7dp" />
|
||||||
|
<solid android:color="@android:color/white" />
|
||||||
|
</shape>
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item>
|
|
||||||
<shape android:shape="rectangle">
|
|
||||||
<corners android:radius="6dp" />
|
|
||||||
<solid android:color="@color/widget_lesson_cancelled" />
|
|
||||||
<stroke android:width="1.5dp" android:color="#C8FF0000" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
<item
|
|
||||||
android:left="3dp"
|
|
||||||
android:top="3dp"
|
|
||||||
android:right="3dp"
|
|
||||||
android:bottom="3dp"
|
|
||||||
android:drawable="@drawable/widget_lesson_cancelled_x" />
|
|
||||||
</layer-list>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
<corners android:radius="6dp" />
|
|
||||||
<solid android:color="#FF2196F3" />
|
|
||||||
</shape>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
<corners android:radius="6dp" />
|
|
||||||
<solid android:color="#FF4CAF50" />
|
|
||||||
</shape>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
<corners android:radius="6dp" />
|
|
||||||
<solid android:color="#FFEF6C00" />
|
|
||||||
</shape>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
<corners android:radius="6dp" />
|
|
||||||
<solid android:color="@color/widget_lesson_irregular" />
|
|
||||||
</shape>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
<corners android:radius="6dp" />
|
|
||||||
<solid android:color="@color/widget_lesson_ongoing" />
|
|
||||||
</shape>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
<corners android:radius="6dp" />
|
|
||||||
<solid android:color="@color/widget_lesson_past" />
|
|
||||||
</shape>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
<corners android:radius="6dp" />
|
|
||||||
<solid android:color="@color/widget_lesson_regular" />
|
|
||||||
</shape>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
<corners android:radius="6dp" />
|
|
||||||
<solid android:color="@color/widget_lesson_teacher_changed" />
|
|
||||||
</shape>
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<corners android:radius="7dp" />
|
||||||
|
<solid android:color="@android:color/transparent" />
|
||||||
|
<stroke android:width="2dp" android:color="#C8F44336" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item
|
||||||
|
android:left="2dp"
|
||||||
|
android:top="2dp"
|
||||||
|
android:right="2dp"
|
||||||
|
android:bottom="2dp"
|
||||||
|
android:drawable="@drawable/widget_lesson_cancelled_x" />
|
||||||
|
</layer-list>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
android:viewportWidth="100"
|
android:viewportWidth="100"
|
||||||
android:viewportHeight="100">
|
android:viewportHeight="100">
|
||||||
<path
|
<path
|
||||||
android:strokeColor="#C8FF0000"
|
android:strokeColor="#C8F44336"
|
||||||
android:strokeWidth="5"
|
android:strokeWidth="2.5"
|
||||||
android:pathData="M0,0 L100,100 M100,0 L0,100" />
|
android:pathData="M0,0 L100,100 M100,0 L0,100" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
+2
-3
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="oval">
|
||||||
<corners android:radius="6dp" />
|
<solid android:color="@android:color/white" />
|
||||||
<solid android:color="#FF993333" />
|
|
||||||
</shape>
|
</shape>
|
||||||
@@ -3,6 +3,14 @@
|
|||||||
android:id="@+id/widget_break_block_root"
|
android:id="@+id/widget_break_block_root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="14dp"
|
android:layout_height="14dp"
|
||||||
android:layout_marginTop="0dp"
|
android:layout_marginTop="0dp">
|
||||||
android:layout_marginStart="2dp"
|
|
||||||
android:layout_marginEnd="2dp" />
|
<ImageView
|
||||||
|
android:id="@+id/widget_break_block_icon"
|
||||||
|
android:layout_width="12dp"
|
||||||
|
android:layout_height="12dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/widget_icon_restaurant" />
|
||||||
|
</FrameLayout>
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/widget_break_label_root"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="14dp"
|
||||||
|
android:layout_marginTop="0dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/widget_break_label_icon"
|
||||||
|
android:layout_width="10dp"
|
||||||
|
android:layout_height="10dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/widget_icon_coffee" />
|
||||||
|
</FrameLayout>
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- FrameLayout root so the Marianum watermark can sit at bottom|end behind
|
<!-- FrameLayout root so the Marianum watermark can sit at bottom|end behind
|
||||||
the actual widget content without disturbing the LinearLayout flow. -->
|
the actual widget content without disturbing the LinearLayout flow.
|
||||||
|
Text sizes and chrome heights are dp: WidgetRenderer derives the grid
|
||||||
|
height from them, so they must not follow the system font scale. -->
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/widget_root"
|
android:id="@+id/widget_root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@drawable/app_widget_background">
|
android:background="@drawable/app_widget_background"
|
||||||
|
tools:ignore="SpUsage,SmallSp">
|
||||||
|
|
||||||
<!-- Bottom-leading so the mark looks like it's peeking out of the lower
|
<!-- Width/height/end+bottom margins are overridden in the renderer so
|
||||||
left corner. Width/height/start+bottom margins are overridden in the
|
the mark scales with the widget size. -->
|
||||||
renderer so the mark scales with the widget size. -->
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/widget_watermark"
|
android:id="@+id/widget_watermark"
|
||||||
android:layout_width="160dp"
|
android:layout_width="160dp"
|
||||||
@@ -26,43 +28,68 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="10dp">
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="20dp"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical">
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/widget_day_title"
|
android:id="@+id/widget_day_title"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:textColor="@color/widget_text_primary"
|
android:textColor="@color/widget_text_primary"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
|
android:gravity="start|center_vertical"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:autoSizeTextType="uniform"
|
||||||
|
android:autoSizeMinTextSize="9dp"
|
||||||
|
android:autoSizeMaxTextSize="14dp"
|
||||||
|
android:autoSizeStepGranularity="0.5dp"
|
||||||
tools:text="Heute · 08.05." />
|
tools:text="Heute · 08.05." />
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/widget_day_subtitle"
|
android:id="@+id/widget_day_subtitle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="10sp"
|
android:layout_marginStart="6dp"
|
||||||
|
android:textSize="10dp"
|
||||||
android:textColor="@color/widget_text_secondary"
|
android:textColor="@color/widget_text_secondary"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:includeFontPadding="false"
|
||||||
tools:text="Stand: 14:32" />
|
tools:text="Stand: 14:32" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Narrow day widgets can't fit title and freshness side by side —
|
||||||
|
the renderer moves the freshness label down here instead. -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_day_subtitle_below"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="12dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:gravity="start|center_vertical"
|
||||||
|
android:textSize="9dp"
|
||||||
|
android:textColor="@color/widget_text_secondary"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
tools:text="Stand: 14:32" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_marginTop="6dp"
|
android:layout_marginTop="4dp"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/widget_day_time_labels"
|
android:id="@+id/widget_day_time_labels"
|
||||||
android:layout_width="32dp"
|
android:layout_width="30dp"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Static picker preview of the day widget, laid out like WidgetRenderer's output. -->
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/widget_background">
|
android:background="@color/widget_background"
|
||||||
|
tools:ignore="SpUsage,SmallSp,HardcodedText,UseAppTint">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="160dp"
|
android:layout_width="160dp"
|
||||||
@@ -20,164 +23,126 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="10dp">
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical">
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="20dp"
|
||||||
android:layout_weight="1"
|
android:gravity="start|center_vertical"
|
||||||
android:textSize="14sp"
|
android:includeFontPadding="false"
|
||||||
|
android:textSize="14dp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:textColor="@color/widget_text_primary"
|
android:textColor="@color/widget_text_primary"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
android:text="Heute · 06.05." />
|
android:text="Heute · 06.05." />
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="10sp"
|
|
||||||
android:textColor="@color/widget_text_secondary"
|
|
||||||
android:text="Stand: 07:50" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:orientation="horizontal"
|
android:layout_marginTop="4dp"
|
||||||
android:layout_marginTop="6dp">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout android:layout_width="30dp" android:layout_height="match_parent">
|
||||||
android:layout_width="32dp"
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="0dp" android:background="@color/widget_divider" />
|
||||||
android:layout_height="match_parent">
|
<FrameLayout android:layout_width="match_parent" android:layout_height="36dp" android:layout_marginTop="0dp">
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginTop="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="07:55" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:gravity="center_horizontal" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="1" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginBottom="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="08:40" />
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="36dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="36dp" android:layout_marginTop="36dp">
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginTop="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="08:40" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:gravity="center_horizontal" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="2" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginBottom="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="09:25" />
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="72dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="36dp" android:layout_marginTop="72dp">
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginTop="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="09:30" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:gravity="center_horizontal" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="3" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginBottom="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="10:15" />
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="108dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="108dp">
|
||||||
|
<ImageView android:layout_width="10dp" android:layout_height="10dp" android:layout_gravity="center" android:importantForAccessibility="no" android:contentDescription="@null" android:tint="@color/widget_text_secondary" android:src="@drawable/widget_icon_coffee" />
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="122dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="36dp" android:layout_marginTop="122dp">
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginTop="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="10:35" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:gravity="center_horizontal" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="4" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginBottom="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="11:20" />
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="158dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="36dp" android:layout_marginTop="158dp">
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginTop="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="11:25" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:gravity="center_horizontal" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="5" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginBottom="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="12:10" />
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="194dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="36dp" android:layout_marginTop="194dp">
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginTop="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="12:15" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:gravity="center_horizontal" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="6" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginBottom="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="13:00" />
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="230dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="36dp" android:layout_marginTop="230dp">
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginTop="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="13:05" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:gravity="center_horizontal" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="7" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginBottom="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="13:50" />
|
||||||
|
</FrameLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||||
|
|
||||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
|
<FrameLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1">
|
||||||
android:orientation="vertical" android:gravity="end" android:paddingEnd="3dp"
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="0dp" android:background="@color/widget_divider" />
|
||||||
android:layout_marginTop="22dp">
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="36dp" android:background="@color/widget_divider" />
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="72dp" android:background="@color/widget_divider" />
|
||||||
android:textSize="11sp" android:textStyle="bold"
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="108dp" android:background="@color/widget_divider" />
|
||||||
android:textColor="@color/widget_text_primary" android:text="1." />
|
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="108dp" android:background="@color/widget_preview_break">
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
<ImageView android:layout_width="12dp" android:layout_height="12dp" android:layout_gravity="center" android:importantForAccessibility="no" android:contentDescription="@null" android:tint="@color/widget_preview_accent" android:src="@drawable/widget_icon_restaurant" />
|
||||||
android:textSize="7sp" android:textColor="@color/widget_text_secondary" android:text="07:55" />
|
</FrameLayout>
|
||||||
</LinearLayout>
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="122dp" android:background="@color/widget_divider" />
|
||||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="158dp" android:background="@color/widget_divider" />
|
||||||
android:orientation="vertical" android:gravity="end" android:paddingEnd="3dp"
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="194dp" android:background="@color/widget_divider" />
|
||||||
android:layout_marginTop="50dp">
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="230dp" android:background="@color/widget_divider" />
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
<FrameLayout android:layout_width="match_parent" android:layout_height="70.5dp" android:layout_marginTop="0.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#5C6BC0">
|
||||||
android:textSize="11sp" android:textStyle="bold"
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
android:textColor="@color/widget_text_primary" android:text="2." />
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="MA-LK" />
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="B11" />
|
||||||
android:textSize="7sp" android:textColor="@color/widget_text_secondary" android:text="08:40" />
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="MUE" />
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical" android:gravity="end" android:paddingEnd="3dp"
|
|
||||||
android:layout_marginTop="79dp">
|
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
|
||||||
android:textSize="11sp" android:textStyle="bold"
|
|
||||||
android:textColor="@color/widget_text_primary" android:text="3." />
|
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
|
||||||
android:textSize="7sp" android:textColor="@color/widget_text_secondary" android:text="09:30" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical" android:gravity="end" android:paddingEnd="3dp"
|
|
||||||
android:layout_marginTop="115dp">
|
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
|
||||||
android:textSize="11sp" android:textStyle="bold"
|
|
||||||
android:textColor="@color/widget_text_primary" android:text="4." />
|
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
|
||||||
android:textSize="7sp" android:textColor="@color/widget_text_secondary" android:text="10:35" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical" android:gravity="end" android:paddingEnd="3dp"
|
|
||||||
android:layout_marginTop="142dp">
|
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
|
||||||
android:textSize="11sp" android:textStyle="bold"
|
|
||||||
android:textColor="@color/widget_text_primary" android:text="5." />
|
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
|
||||||
android:textSize="7sp" android:textColor="@color/widget_text_secondary" android:text="11:25" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical" android:gravity="end" android:paddingEnd="3dp"
|
|
||||||
android:layout_marginTop="169dp">
|
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
|
||||||
android:textSize="11sp" android:textStyle="bold"
|
|
||||||
android:textColor="@color/widget_text_primary" android:text="6." />
|
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
|
||||||
android:textSize="7sp" android:textColor="@color/widget_text_secondary" android:text="12:15" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="34.5dp" android:layout_marginTop="72.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#993333">
|
||||||
<FrameLayout
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
android:layout_width="0dp"
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="DE" />
|
||||||
android:layout_height="match_parent"
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="A204" />
|
||||||
android:layout_weight="1">
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="SCHN" />
|
||||||
|
|
||||||
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="29dp" android:background="@color/widget_divider" />
|
|
||||||
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="56dp" android:background="@color/widget_divider" />
|
|
||||||
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="83dp" android:background="@color/widget_divider" />
|
|
||||||
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="115dp" android:background="@color/widget_divider" />
|
|
||||||
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="143dp" android:background="@color/widget_divider" />
|
|
||||||
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="170dp" android:background="@color/widget_divider" />
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="22dp"
|
|
||||||
android:layout_marginTop="29dp"
|
|
||||||
android:layout_marginStart="2dp"
|
|
||||||
android:layout_marginEnd="2dp"
|
|
||||||
android:padding="3dp"
|
|
||||||
android:background="@drawable/widget_lesson_block_regular">
|
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
|
||||||
android:textSize="11sp" android:textStyle="bold"
|
|
||||||
android:textColor="@android:color/white" android:text="MA-LK" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="55dp"
|
|
||||||
android:layout_marginTop="83dp"
|
|
||||||
android:layout_marginStart="2dp"
|
|
||||||
android:layout_marginEnd="2dp"
|
|
||||||
android:padding="3dp"
|
|
||||||
android:background="@drawable/widget_lesson_block_regular">
|
|
||||||
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
|
||||||
android:textSize="11sp" android:textStyle="bold"
|
|
||||||
android:textColor="@android:color/white" android:text="DE-GK" />
|
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
|
||||||
android:textSize="9sp" android:textColor="#CCFFFFFF" android:text="B11" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="34.5dp" android:layout_marginTop="122.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#0097A7">
|
||||||
<FrameLayout
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
android:layout_width="match_parent"
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="EN" />
|
||||||
android:layout_height="22dp"
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="F2.04" />
|
||||||
android:layout_marginTop="143dp"
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="KRA" />
|
||||||
android:layout_marginStart="2dp"
|
</LinearLayout>
|
||||||
android:layout_marginEnd="2dp"
|
|
||||||
android:padding="3dp"
|
|
||||||
android:background="@drawable/widget_lesson_block_cancelled">
|
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
|
||||||
android:textSize="11sp" android:textStyle="bold"
|
|
||||||
android:textColor="@android:color/white" android:text="BIO" />
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="70.5dp" android:layout_marginTop="158.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#000000">
|
||||||
<FrameLayout
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
android:layout_width="match_parent"
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="BIO" />
|
||||||
android:layout_height="22dp"
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="N103" />
|
||||||
android:layout_marginTop="170dp"
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="WEB" />
|
||||||
android:layout_marginStart="2dp"
|
</LinearLayout>
|
||||||
android:layout_marginEnd="2dp"
|
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/widget_lesson_cancelled_overlay" />
|
||||||
android:padding="3dp"
|
</FrameLayout>
|
||||||
android:background="@drawable/widget_lesson_block_teacher_changed">
|
<FrameLayout android:layout_width="match_parent" android:layout_height="34.5dp" android:layout_marginTop="230.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#29639B">
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
android:textSize="11sp" android:textStyle="bold"
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="GE" />
|
||||||
android:textColor="@android:color/white" android:text="GE" />
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="A110" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="BER" />
|
||||||
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Mirrors the in-app holiday region: icon and "Schulfrei" on top, the
|
||||||
|
holiday name rotated along the column. The name's width is set by the
|
||||||
|
renderer — after the rotation it is the length along the column. -->
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/widget_holiday_root"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:ignore="SpUsage,SmallSp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center_horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/widget_holiday_icon"
|
||||||
|
android:layout_width="14dp"
|
||||||
|
android:layout_height="14dp"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/widget_icon_event" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_holiday_label"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="12dp"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="2dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/widget_school_free"
|
||||||
|
android:textColor="@color/widget_text_primary"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:autoSizeTextType="uniform"
|
||||||
|
android:autoSizeMinTextSize="6dp"
|
||||||
|
android:autoSizeMaxTextSize="9dp"
|
||||||
|
android:autoSizeStepGranularity="0.5dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_holiday_name"
|
||||||
|
android:layout_width="120dp"
|
||||||
|
android:layout_height="14dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:rotation="90"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:letterSpacing="0.1"
|
||||||
|
android:textColor="@color/widget_text_primary"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:autoSizeTextType="uniform"
|
||||||
|
android:autoSizeMinTextSize="7dp"
|
||||||
|
android:autoSizeMaxTextSize="11dp"
|
||||||
|
android:autoSizeStepGranularity="0.5dp"
|
||||||
|
tools:text="Herbstferien" />
|
||||||
|
</FrameLayout>
|
||||||
@@ -1,90 +1,113 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Mirrors AppointmentTile: title on top, room and teacher stacked below,
|
||||||
|
each line shrinking on its own to fit the tile width. Text sizes are dp
|
||||||
|
(not sp) because the line boxes are sized in dp by the renderer. -->
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/widget_lesson_block_root"
|
android:id="@+id/widget_lesson_block_root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
android:layout_marginStart="2dp"
|
android:layout_marginStart="1dp"
|
||||||
android:layout_marginEnd="2dp"
|
android:layout_marginEnd="1dp"
|
||||||
android:layout_marginTop="0dp"
|
android:layout_marginTop="0dp"
|
||||||
android:paddingStart="7dp"
|
android:background="@drawable/widget_lesson_block"
|
||||||
android:paddingEnd="7dp"
|
tools:ignore="SpUsage,SmallSp">
|
||||||
android:paddingTop="3dp"
|
|
||||||
android:paddingBottom="3dp"
|
|
||||||
android:background="@drawable/widget_lesson_block_regular">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="4dp"
|
||||||
|
android:paddingEnd="4dp"
|
||||||
|
android:paddingTop="2dp"
|
||||||
|
android:paddingBottom="2dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/widget_lesson_title_row"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="top"
|
|
||||||
android:baselineAligned="false">
|
android:baselineAligned="false">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/widget_lesson_subject"
|
android:id="@+id/widget_lesson_subject"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="14dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:textStyle="bold"
|
android:fontFamily="sans-serif-medium"
|
||||||
android:textColor="@android:color/white"
|
android:textColor="@android:color/white"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:gravity="top|start"
|
android:gravity="start|center_vertical"
|
||||||
android:includeFontPadding="false"
|
android:includeFontPadding="false"
|
||||||
android:autoSizeTextType="uniform"
|
android:autoSizeTextType="uniform"
|
||||||
android:autoSizeMinTextSize="5sp"
|
android:autoSizeMinTextSize="6.5dp"
|
||||||
android:autoSizeMaxTextSize="11sp"
|
android:autoSizeMaxTextSize="15dp"
|
||||||
android:autoSizeStepGranularity="1sp"
|
android:autoSizeStepGranularity="0.5dp"
|
||||||
tools:text="MA-LK" />
|
tools:text="MA-LK" />
|
||||||
|
|
||||||
<LinearLayout
|
<!-- Only for tiles too short for a body line below the title. -->
|
||||||
android:id="@+id/widget_lesson_secondary_stack"
|
<TextView
|
||||||
android:layout_width="0dp"
|
android:id="@+id/widget_lesson_inline"
|
||||||
android:layout_height="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:gravity="top|end"
|
android:layout_marginStart="3dp"
|
||||||
android:layout_marginStart="4dp">
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="7dp"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="B11" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/widget_lesson_room"
|
android:id="@+id/widget_lesson_room"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="12dp"
|
android:layout_height="10dp"
|
||||||
android:textColor="#CCFFFFFF"
|
android:textColor="@android:color/white"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:gravity="end|top"
|
android:gravity="start|center_vertical"
|
||||||
android:includeFontPadding="false"
|
android:includeFontPadding="false"
|
||||||
android:autoSizeTextType="uniform"
|
android:autoSizeTextType="uniform"
|
||||||
android:autoSizeMinTextSize="5sp"
|
android:autoSizeMinTextSize="5.5dp"
|
||||||
android:autoSizeMaxTextSize="11sp"
|
android:autoSizeMaxTextSize="10dp"
|
||||||
android:autoSizeStepGranularity="1sp"
|
android:autoSizeStepGranularity="0.5dp"
|
||||||
tools:text="A12" />
|
tools:text="A12" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/widget_lesson_teacher"
|
android:id="@+id/widget_lesson_teacher"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="12dp"
|
android:layout_height="10dp"
|
||||||
android:textColor="#B3FFFFFF"
|
android:textColor="@android:color/white"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:gravity="end|top"
|
android:gravity="start|center_vertical"
|
||||||
android:includeFontPadding="false"
|
android:includeFontPadding="false"
|
||||||
android:autoSizeTextType="uniform"
|
android:autoSizeTextType="uniform"
|
||||||
android:autoSizeMinTextSize="5sp"
|
android:autoSizeMinTextSize="5.5dp"
|
||||||
android:autoSizeMaxTextSize="11sp"
|
android:autoSizeMaxTextSize="10dp"
|
||||||
android:autoSizeStepGranularity="1sp"
|
android:autoSizeStepGranularity="0.5dp"
|
||||||
tools:text="Müller" />
|
tools:text="Müller" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/widget_lesson_cancelled"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/widget_lesson_cancelled_overlay"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/widget_lesson_sibling_badge"
|
android:id="@+id/widget_lesson_sibling_badge"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|start"
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:layout_marginBottom="2dp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:textSize="12sp"
|
android:textSize="9dp"
|
||||||
android:textColor="@android:color/white"
|
android:textColor="@android:color/white"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:includeFontPadding="false"
|
android:includeFontPadding="false"
|
||||||
|
|||||||
@@ -1,31 +1,50 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<!-- One period cell of the ruler, mirroring the in-app _PeriodLabel: number
|
||||||
|
centred, start time above, end time below. -->
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/widget_time_label_root"
|
android:id="@+id/widget_time_label_root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="36dp"
|
||||||
android:layout_marginTop="0dp"
|
android:layout_marginTop="0dp"
|
||||||
android:orientation="vertical"
|
tools:ignore="SpUsage,SmallSp">
|
||||||
android:gravity="end"
|
|
||||||
android:paddingEnd="3dp">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/widget_time_label_time"
|
android:id="@+id/widget_time_label_start"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="9sp"
|
android:layout_gravity="top"
|
||||||
android:textColor="@color/widget_text_primary"
|
android:layout_marginTop="2dp"
|
||||||
android:singleLine="true"
|
android:gravity="center_horizontal"
|
||||||
android:lineSpacingExtra="-2dp"
|
android:textSize="7dp"
|
||||||
|
android:textColor="@color/widget_text_secondary"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:includeFontPadding="false"
|
||||||
tools:text="07:55" />
|
tools:text="07:55" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/widget_time_label_number"
|
android:id="@+id/widget_time_label_number"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="7sp"
|
android:layout_gravity="center_vertical"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:textSize="11dp"
|
||||||
|
android:textColor="@color/widget_text_primary"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
tools:text="1" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_time_label_end"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_marginBottom="2dp"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:textSize="7dp"
|
||||||
android:textColor="@color/widget_text_secondary"
|
android:textColor="@color/widget_text_secondary"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
android:lineSpacingExtra="-2dp"
|
android:includeFontPadding="false"
|
||||||
tools:text="1." />
|
tools:text="08:40" />
|
||||||
</LinearLayout>
|
</FrameLayout>
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- FrameLayout root so the Marianum watermark can sit at bottom|end behind
|
||||||
|
the actual widget content without disturbing the LinearLayout flow.
|
||||||
|
Text sizes and chrome heights are dp: WidgetRenderer derives the grid
|
||||||
|
height from them, so they must not follow the system font scale. -->
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/widget_root"
|
android:id="@+id/widget_root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@drawable/app_widget_background">
|
android:background="@drawable/app_widget_background"
|
||||||
|
tools:ignore="SpUsage,SmallSp">
|
||||||
|
|
||||||
|
<!-- Width/height/end+bottom margins are overridden in the renderer so
|
||||||
|
the mark scales with the widget size. -->
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/widget_watermark"
|
android:id="@+id/widget_watermark"
|
||||||
android:layout_width="160dp"
|
android:layout_width="160dp"
|
||||||
@@ -21,42 +28,53 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="10dp">
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="20dp"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical">
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/widget_week_title"
|
android:id="@+id/widget_week_title"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:textColor="@color/widget_text_primary"
|
android:textColor="@color/widget_text_primary"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
|
android:gravity="start|center_vertical"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:autoSizeTextType="uniform"
|
||||||
|
android:autoSizeMinTextSize="9dp"
|
||||||
|
android:autoSizeMaxTextSize="14dp"
|
||||||
|
android:autoSizeStepGranularity="0.5dp"
|
||||||
tools:text="KW 19 · 06.05.–10.05." />
|
tools:text="KW 19 · 06.05.–10.05." />
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/widget_week_subtitle"
|
android:id="@+id/widget_week_subtitle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="10sp"
|
android:layout_marginStart="6dp"
|
||||||
|
android:textSize="10dp"
|
||||||
android:textColor="@color/widget_text_secondary"
|
android:textColor="@color/widget_text_secondary"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:includeFontPadding="false"
|
||||||
tools:text="Stand: 14:32" />
|
tools:text="Stand: 14:32" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="32dp"
|
||||||
android:orientation="horizontal"
|
android:layout_marginTop="4dp"
|
||||||
android:layout_marginTop="6dp">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="28dp"
|
android:layout_width="30dp"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="1dp"
|
android:layout_width="1dp"
|
||||||
@@ -66,7 +84,7 @@
|
|||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/widget_week_header_mon"
|
android:id="@+id/widget_week_header_mon"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
@@ -77,7 +95,7 @@
|
|||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/widget_week_header_tue"
|
android:id="@+id/widget_week_header_tue"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
@@ -88,7 +106,7 @@
|
|||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/widget_week_header_wed"
|
android:id="@+id/widget_week_header_wed"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
@@ -99,7 +117,7 @@
|
|||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/widget_week_header_thu"
|
android:id="@+id/widget_week_header_thu"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
@@ -110,7 +128,7 @@
|
|||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/widget_week_header_fri"
|
android:id="@+id/widget_week_header_fri"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -123,7 +141,7 @@
|
|||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/widget_week_time_labels"
|
android:id="@+id/widget_week_time_labels"
|
||||||
android:layout_width="28dp"
|
android:layout_width="30dp"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
|||||||
@@ -1,28 +1,35 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Mirrors the in-app _DayHeaderCell: weekday in caps, day number below,
|
||||||
|
today inside an accent circle. -->
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/widget_week_day_header_root"
|
android:id="@+id/widget_week_day_header_root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:paddingTop="2dp"
|
tools:ignore="SpUsage,SmallSp">
|
||||||
android:paddingBottom="3dp">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/widget_week_day_header_weekday"
|
android:id="@+id/widget_week_day_header_weekday"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="11sp"
|
android:fontFamily="sans-serif-medium"
|
||||||
android:textStyle="bold"
|
android:textSize="9dp"
|
||||||
android:textColor="@color/widget_text_primary"
|
android:textColor="@color/widget_text_secondary"
|
||||||
tools:text="Mo" />
|
android:maxLines="1"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
tools:text="MO." />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/widget_week_day_header_date"
|
android:id="@+id/widget_week_day_header_date"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="18dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="18dp"
|
||||||
android:textSize="9sp"
|
android:layout_marginTop="1dp"
|
||||||
android:textColor="@color/widget_text_secondary"
|
android:gravity="center"
|
||||||
tools:text="06.05." />
|
android:textSize="11dp"
|
||||||
|
android:textColor="@color/widget_text_primary"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
tools:text="6" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Static preview for the week widget. Five day columns with two short
|
<!-- Static picker preview of the week widget, laid out like WidgetRenderer's output. -->
|
||||||
demo blocks each so the structure is recognisable in the picker. -->
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/widget_background">
|
android:background="@color/widget_background"
|
||||||
|
tools:ignore="SpUsage,SmallSp,HardcodedText,UseAppTint">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="200dp"
|
android:layout_width="200dp"
|
||||||
@@ -22,124 +23,312 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="10dp">
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical">
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="20dp"
|
||||||
android:layout_weight="1"
|
android:gravity="start|center_vertical"
|
||||||
android:textSize="14sp"
|
android:includeFontPadding="false"
|
||||||
|
android:textSize="14dp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:textColor="@color/widget_text_primary"
|
android:textColor="@color/widget_text_primary"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
android:text="KW 19 · 06.05.–10.05." />
|
android:text="KW 19 · 04.05.–08.05." />
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="10sp"
|
|
||||||
android:textColor="@color/widget_text_secondary"
|
|
||||||
android:text="Stand: 07:50" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Day-name + date row -->
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="32dp"
|
||||||
android:orientation="horizontal"
|
android:layout_marginTop="4dp"
|
||||||
android:layout_marginTop="6dp">
|
android:orientation="horizontal">
|
||||||
<FrameLayout android:layout_width="20dp" android:layout_height="wrap_content" />
|
<FrameLayout android:layout_width="30dp" android:layout_height="match_parent" />
|
||||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:gravity="center">
|
<LinearLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:orientation="vertical" android:gravity="center">
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="11sp" android:textStyle="bold" android:textColor="@color/widget_text_primary" android:text="Mo" />
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="9dp" android:textColor="@color/widget_text_secondary" android:text="MO." />
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textColor="@color/widget_text_secondary" android:text="06.05." />
|
<TextView android:layout_width="18dp" android:layout_height="18dp" android:layout_marginTop="1dp" android:gravity="center" android:includeFontPadding="false" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="4" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:gravity="center">
|
<LinearLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:orientation="vertical" android:gravity="center">
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="11sp" android:textStyle="bold" android:textColor="@color/widget_text_primary" android:text="Di" />
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="9dp" android:textColor="@color/widget_text_secondary" android:text="DI." />
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textColor="@color/widget_text_secondary" android:text="07.05." />
|
<TextView android:layout_width="18dp" android:layout_height="18dp" android:layout_marginTop="1dp" android:gravity="center" android:includeFontPadding="false" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="5" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:gravity="center">
|
<LinearLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:orientation="vertical" android:gravity="center">
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="11sp" android:textStyle="bold" android:textColor="@color/widget_text_primary" android:text="Mi" />
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="9dp" android:textColor="@color/widget_text_secondary" android:text="MI." />
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textColor="@color/widget_text_secondary" android:text="08.05." />
|
<TextView android:layout_width="18dp" android:layout_height="18dp" android:layout_marginTop="1dp" android:gravity="center" android:includeFontPadding="false" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="6" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:gravity="center">
|
<LinearLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:orientation="vertical" android:gravity="center">
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="11sp" android:textStyle="bold" android:textColor="@color/widget_text_primary" android:text="Do" />
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="9dp" android:textColor="@color/widget_text_secondary" android:text="DO." />
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textColor="@color/widget_text_secondary" android:text="09.05." />
|
<TextView android:layout_width="18dp" android:layout_height="18dp" android:layout_marginTop="1dp" android:gravity="center" android:includeFontPadding="false" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="7" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:gravity="center">
|
<LinearLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:orientation="vertical" android:gravity="center">
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="11sp" android:textStyle="bold" android:textColor="@color/widget_text_primary" android:text="Fr" />
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="9dp" android:textColor="@color/widget_text_secondary" android:text="FR." />
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textColor="@color/widget_text_secondary" android:text="10.05." />
|
<TextView android:layout_width="18dp" android:layout_height="18dp" android:layout_marginTop="1dp" android:gravity="center" android:includeFontPadding="false" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="8" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Time grid: time-label column + 5 day columns -->
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:orientation="horizontal"
|
android:layout_marginTop="2dp"
|
||||||
android:layout_marginTop="2dp">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<FrameLayout android:layout_width="20dp" android:layout_height="match_parent">
|
<FrameLayout android:layout_width="30dp" android:layout_height="match_parent">
|
||||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="0dp" android:gravity="end" android:paddingEnd="3dp" android:textSize="8sp" android:textColor="@color/widget_text_secondary" android:text="08" />
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="0dp" android:background="@color/widget_divider" />
|
||||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="36dp" android:gravity="end" android:paddingEnd="3dp" android:textSize="8sp" android:textColor="@color/widget_text_secondary" android:text="10" />
|
<FrameLayout android:layout_width="match_parent" android:layout_height="36dp" android:layout_marginTop="0dp">
|
||||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="72dp" android:gravity="end" android:paddingEnd="3dp" android:textSize="8sp" android:textColor="@color/widget_text_secondary" android:text="12" />
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginTop="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="07:55" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:gravity="center_horizontal" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="1" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginBottom="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="08:40" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="36dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="36dp" android:layout_marginTop="36dp">
|
||||||
<!-- Mon -->
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginTop="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="08:40" />
|
||||||
<FrameLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1">
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:gravity="center_horizontal" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="2" />
|
||||||
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="2dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:padding="2dp" android:background="@drawable/widget_lesson_block_regular">
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginBottom="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="09:25" />
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textStyle="bold" android:textColor="@android:color/white" android:text="MA" />
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="40dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:padding="2dp" android:background="@drawable/widget_lesson_block_regular">
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="72dp" android:background="@color/widget_divider" />
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textStyle="bold" android:textColor="@android:color/white" android:text="EN" />
|
<FrameLayout android:layout_width="match_parent" android:layout_height="36dp" android:layout_marginTop="72dp">
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginTop="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="09:30" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:gravity="center_horizontal" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="3" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginBottom="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="10:15" />
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="108dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="108dp">
|
||||||
|
<ImageView android:layout_width="10dp" android:layout_height="10dp" android:layout_gravity="center" android:importantForAccessibility="no" android:contentDescription="@null" android:tint="@color/widget_text_secondary" android:src="@drawable/widget_icon_coffee" />
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="122dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="36dp" android:layout_marginTop="122dp">
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginTop="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="10:35" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:gravity="center_horizontal" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="4" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginBottom="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="11:20" />
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="158dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="36dp" android:layout_marginTop="158dp">
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginTop="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="11:25" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:gravity="center_horizontal" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="5" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginBottom="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="12:10" />
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="194dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="36dp" android:layout_marginTop="194dp">
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginTop="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="12:15" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:gravity="center_horizontal" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="6" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginBottom="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="13:00" />
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="230dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="36dp" android:layout_marginTop="230dp">
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginTop="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="13:05" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:gravity="center_horizontal" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:textSize="11dp" android:textColor="@color/widget_text_primary" android:text="7" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_marginBottom="2dp" android:gravity="center_horizontal" android:includeFontPadding="false" android:textSize="7dp" android:textColor="@color/widget_text_secondary" android:text="13:50" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||||
|
|
||||||
<!-- Tue -->
|
|
||||||
<FrameLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1">
|
<FrameLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1">
|
||||||
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="20dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:padding="2dp" android:background="@drawable/widget_lesson_block_cancelled">
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="0dp" android:background="@color/widget_divider" />
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textStyle="bold" android:textColor="@android:color/white" android:text="BIO" />
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="36dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="72dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="108dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="108dp" android:background="@color/widget_preview_break">
|
||||||
|
<ImageView android:layout_width="12dp" android:layout_height="12dp" android:layout_gravity="center" android:importantForAccessibility="no" android:contentDescription="@null" android:tint="@color/widget_preview_accent" android:src="@drawable/widget_icon_restaurant" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="60dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:padding="2dp" android:background="@drawable/widget_lesson_block_regular">
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="122dp" android:background="@color/widget_divider" />
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textStyle="bold" android:textColor="@android:color/white" android:text="DE" />
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="158dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="194dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="230dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="70.5dp" android:layout_marginTop="0.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#5C6BC0">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="MA-LK" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="B11" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="MUE" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="34.5dp" android:layout_marginTop="72.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#993333">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="DE" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="A204" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="SCHN" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="34.5dp" android:layout_marginTop="122.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#0097A7">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="EN" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="F2.04" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="KRA" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="70.5dp" android:layout_marginTop="158.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#000000">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="BIO" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="N103" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="WEB" />
|
||||||
|
</LinearLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/widget_lesson_cancelled_overlay" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||||
|
|
||||||
<!-- Wed -->
|
|
||||||
<FrameLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1">
|
<FrameLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1">
|
||||||
<FrameLayout android:layout_width="match_parent" android:layout_height="32dp" android:layout_marginTop="2dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:padding="2dp" android:background="@drawable/widget_lesson_block_regular">
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="0dp" android:background="@color/widget_divider" />
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textStyle="bold" android:textColor="@android:color/white" android:text="MA" />
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="36dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="72dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="108dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="108dp" android:background="@color/widget_preview_break">
|
||||||
|
<ImageView android:layout_width="12dp" android:layout_height="12dp" android:layout_gravity="center" android:importantForAccessibility="no" android:contentDescription="@null" android:tint="@color/widget_preview_accent" android:src="@drawable/widget_icon_restaurant" />
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="122dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="158dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="194dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="230dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="34.5dp" android:layout_marginTop="0.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#26A69A">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="SPO" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="TH1" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="BAU" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="34.5dp" android:layout_marginTop="36.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#8F19B3">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="PoWi" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="A105" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="HOF" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="84.5dp" android:layout_marginTop="72.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#29639B">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="PH" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="N201" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="LEH" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="34.5dp" android:layout_marginTop="158.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#9E9D24">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="GE" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="A110" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="BER" />
|
||||||
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||||
|
|
||||||
<!-- Thu -->
|
|
||||||
<FrameLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1">
|
<FrameLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1">
|
||||||
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="2dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:padding="2dp" android:background="@drawable/widget_lesson_block_teacher_changed">
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="0dp" android:background="@color/widget_divider" />
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textStyle="bold" android:textColor="@android:color/white" android:text="GE" />
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="36dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="72dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="108dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="108dp" android:background="@color/widget_preview_break">
|
||||||
|
<ImageView android:layout_width="12dp" android:layout_height="12dp" android:layout_gravity="center" android:importantForAccessibility="no" android:contentDescription="@null" android:tint="@color/widget_preview_accent" android:src="@drawable/widget_icon_restaurant" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="40dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:padding="2dp" android:background="@drawable/widget_lesson_block_regular">
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="122dp" android:background="@color/widget_divider" />
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textStyle="bold" android:textColor="@android:color/white" android:text="PH" />
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="158dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="194dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="230dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="70.5dp" android:layout_marginTop="0.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#E91E63">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="CH" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="N305" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="MEI" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="34.5dp" android:layout_marginTop="72.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#5C6BC0">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="MA-LK" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="B11" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="MUE" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="70.5dp" android:layout_marginTop="122.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#993333">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="DE" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="A204" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="SCHN" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="34.5dp" android:layout_marginTop="194.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#0097A7">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="EN" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="F2.04" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="KRA" />
|
||||||
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||||
|
|
||||||
<!-- Fri -->
|
|
||||||
<FrameLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1">
|
<FrameLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1">
|
||||||
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="20dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:padding="2dp" android:background="@drawable/widget_lesson_block_regular">
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="0dp" android:background="@color/widget_divider" />
|
||||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textStyle="bold" android:textColor="@android:color/white" android:text="EN" />
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="36dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="72dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="108dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="108dp" android:background="@color/widget_preview_break">
|
||||||
|
<ImageView android:layout_width="12dp" android:layout_height="12dp" android:layout_gravity="center" android:importantForAccessibility="no" android:contentDescription="@null" android:tint="@color/widget_preview_accent" android:src="@drawable/widget_icon_restaurant" />
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="122dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="158dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="194dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="230dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="34.5dp" android:layout_marginTop="0.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#78909C">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="EK" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="A301" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="LAN" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="70.5dp" android:layout_marginTop="36.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#993333">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="BIO" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="N103" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="WEB" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="34.5dp" android:layout_marginTop="122.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#7E57C2">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="MU" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="M1" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="SIN" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="70.5dp" android:layout_marginTop="158.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#F4511E">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="KU" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="K2" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="MAL" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1">
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="0dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="36dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="72dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="108dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="108dp" android:background="@color/widget_preview_break">
|
||||||
|
<ImageView android:layout_width="12dp" android:layout_height="12dp" android:layout_gravity="center" android:importantForAccessibility="no" android:contentDescription="@null" android:tint="@color/widget_preview_accent" android:src="@drawable/widget_icon_restaurant" />
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="122dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="158dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="194dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="230dp" android:background="@color/widget_divider" />
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="70.5dp" android:layout_marginTop="0.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#993333">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="PH" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="N201" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="LEH" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="34.5dp" android:layout_marginTop="72.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#FFA000">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="LA" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="A108" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="CIC" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
<FrameLayout android:layout_width="match_parent" android:layout_height="34.5dp" android:layout_marginTop="122.8dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:background="@drawable/widget_lesson_block" android:backgroundTint="#9E9D24">
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingStart="4dp" android:paddingEnd="4dp" android:paddingTop="2dp" android:paddingBottom="2dp">
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:fontFamily="sans-serif-medium" android:maxLines="1" android:textSize="11dp" android:textColor="@android:color/white" android:text="GE" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="A110" />
|
||||||
|
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" android:textSize="8dp" android:textColor="@android:color/white" android:text="BER" />
|
||||||
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -4,4 +4,6 @@
|
|||||||
<color name="widget_text_primary">#FFF1F1F1</color>
|
<color name="widget_text_primary">#FFF1F1F1</color>
|
||||||
<color name="widget_text_secondary">#FFB0B0B0</color>
|
<color name="widget_text_secondary">#FFB0B0B0</color>
|
||||||
<color name="widget_divider">#33FFFFFF</color>
|
<color name="widget_divider">#33FFFFFF</color>
|
||||||
|
<color name="widget_preview_accent">#FFFFB3AF</color>
|
||||||
|
<color name="widget_preview_break">#32FFB3AF</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<bool name="widget_supported">true</bool>
|
||||||
|
</resources>
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
<string name="widget_week_description">Stundenplan und Vertretungen für die ganze Schulwoche.</string>
|
<string name="widget_week_description">Stundenplan und Vertretungen für die ganze Schulwoche.</string>
|
||||||
<string name="widget_no_lessons">Keine Stunden</string>
|
<string name="widget_no_lessons">Keine Stunden</string>
|
||||||
<string name="widget_holiday">Ferien</string>
|
<string name="widget_holiday">Ferien</string>
|
||||||
|
<string name="widget_school_free">Schulfrei</string>
|
||||||
<string name="widget_login_required">Bitte einloggen, um den Stundenplan zu laden</string>
|
<string name="widget_login_required">Bitte einloggen, um den Stundenplan zu laden</string>
|
||||||
<string name="widget_loading">Lade…</string>
|
<string name="widget_loading">Lade…</string>
|
||||||
<string name="widget_status_label">Stand: %1$s</string>
|
<string name="widget_status_label">Stand: %1$s</string>
|
||||||
|
|||||||
@@ -1,17 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Status colors mirror lib/view/pages/timetable/data/lesson_color.dart
|
|
||||||
exactly so widget tiles match the in-app calendar. -->
|
|
||||||
<color name="widget_lesson_regular">#FF993333</color>
|
|
||||||
<color name="widget_lesson_ongoing">#FFC83333</color>
|
|
||||||
<color name="widget_lesson_past">#FF993333</color>
|
|
||||||
<color name="widget_lesson_cancelled">#FF000000</color>
|
|
||||||
<color name="widget_lesson_irregular">#FF8F19B3</color>
|
|
||||||
<color name="widget_lesson_teacher_changed">#FF29639B</color>
|
|
||||||
<color name="widget_lesson_event">#FF2E7D32</color>
|
|
||||||
|
|
||||||
<color name="widget_background">#FFFCF7F5</color>
|
<color name="widget_background">#FFFCF7F5</color>
|
||||||
<color name="widget_text_primary">#FF111111</color>
|
<color name="widget_text_primary">#FF111111</color>
|
||||||
<color name="widget_text_secondary">#FF555555</color>
|
<color name="widget_text_secondary">#FF555555</color>
|
||||||
<color name="widget_divider">#22000000</color>
|
<color name="widget_divider">#22000000</color>
|
||||||
|
<color name="widget_preview_accent">#FF904A47</color>
|
||||||
|
<color name="widget_preview_break">#32904A47</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- WidgetRenderer positions every tile with RemoteViews calls that only
|
||||||
|
exist from Android 12 (API 31). Below that the widgets are not
|
||||||
|
offered at all rather than showing up empty. -->
|
||||||
|
<bool name="widget_supported">false</bool>
|
||||||
|
</resources>
|
||||||
@@ -1,59 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import WidgetKit
|
import WidgetKit
|
||||||
|
|
||||||
// Layout constants — must mirror WidgetRenderer.kt on Android, otherwise
|
|
||||||
// the platforms drift apart on the same widget size.
|
|
||||||
let FALLBACK_VIRTUAL_MINUTES = 11 * 60
|
|
||||||
let MIN_HOUR_HEIGHT: CGFloat = 18
|
|
||||||
let MAX_HOUR_HEIGHT: CGFloat = 72
|
|
||||||
let MIN_BLOCK_HEIGHT: CGFloat = 16
|
|
||||||
let LESSON_GAP: CGFloat = 1.5
|
|
||||||
|
|
||||||
func realMinutesToVirtual(_ realMin: Int, periods: [WidgetPeriod]) -> CGFloat {
|
|
||||||
guard !periods.isEmpty else { return CGFloat(realMin) }
|
|
||||||
for p in periods where realMin >= p.startMinutes && realMin <= p.endMinutes {
|
|
||||||
return CGFloat(p.virtualStartMinutes + (realMin - p.startMinutes))
|
|
||||||
}
|
|
||||||
let first = periods.first!
|
|
||||||
if realMin < first.startMinutes {
|
|
||||||
return CGFloat(realMin - first.startMinutes + first.virtualStartMinutes)
|
|
||||||
}
|
|
||||||
let last = periods.last!
|
|
||||||
if realMin > last.endMinutes {
|
|
||||||
return CGFloat(last.virtualEndMinutes + (realMin - last.endMinutes))
|
|
||||||
}
|
|
||||||
var prev = first
|
|
||||||
for i in 1..<periods.count {
|
|
||||||
let curr = periods[i]
|
|
||||||
if realMin > prev.endMinutes && realMin < curr.startMinutes {
|
|
||||||
let gap = curr.startMinutes - prev.endMinutes
|
|
||||||
let virtualGap = curr.virtualStartMinutes - prev.virtualEndMinutes
|
|
||||||
if gap > 0 {
|
|
||||||
return CGFloat(prev.virtualEndMinutes) +
|
|
||||||
CGFloat(realMin - prev.endMinutes) * CGFloat(virtualGap) / CGFloat(gap)
|
|
||||||
}
|
|
||||||
return CGFloat(curr.virtualStartMinutes)
|
|
||||||
}
|
|
||||||
prev = curr
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Below these tile heights the secondary stack collapses progressively:
|
|
||||||
// shorter than ROOM_MIN drops everything, between ROOM_MIN and
|
|
||||||
// TEACHER_SEPARATE_MIN keeps the room but drops the teacher.
|
|
||||||
let BLOCK_SHOW_ROOM_MIN: CGFloat = 13
|
|
||||||
let BLOCK_SHOW_TEACHER_SEPARATE_MIN: CGFloat = 20
|
|
||||||
|
|
||||||
let MIN_SUBJECT_FONT: CGFloat = 9
|
|
||||||
let MAX_SUBJECT_FONT: CGFloat = 14
|
|
||||||
let MIN_SECONDARY_FONT: CGFloat = 7
|
|
||||||
|
|
||||||
func subjectFont(forHourHeight hourHeight: CGFloat) -> CGFloat {
|
|
||||||
let t = max(0, min(1, (hourHeight - MIN_HOUR_HEIGHT) / (MAX_HOUR_HEIGHT - MIN_HOUR_HEIGHT)))
|
|
||||||
return MIN_SUBJECT_FONT + t * (MAX_SUBJECT_FONT - MIN_SUBJECT_FONT)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TimetableDayView: View {
|
struct TimetableDayView: View {
|
||||||
let entry: TimetableEntry
|
let entry: TimetableEntry
|
||||||
@Environment(\.widgetPalette) private var palette
|
@Environment(\.widgetPalette) private var palette
|
||||||
@@ -73,7 +20,7 @@ struct TimetableDayView: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func content(data: WidgetTimetableData) -> some View {
|
private func content(data: WidgetTimetableData) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 3) {
|
VStack(alignment: .leading, spacing: GRID_MARGIN_TOP) {
|
||||||
header(data: data)
|
header(data: data)
|
||||||
if data.isHoliday {
|
if data.isHoliday {
|
||||||
emptyState(text: data.holidayName ?? "Ferien")
|
emptyState(text: data.holidayName ?? "Ferien")
|
||||||
@@ -81,37 +28,41 @@ struct TimetableDayView: View {
|
|||||||
emptyState(text: "Keine Stunden")
|
emptyState(text: "Keine Stunden")
|
||||||
} else {
|
} else {
|
||||||
GeometryReader { geo in
|
GeometryReader { geo in
|
||||||
let totalMin = CGFloat(data.periods.last?.virtualEndMinutes ?? FALLBACK_VIRTUAL_MINUTES)
|
let periods = trimTrailingPeriods(data.periods, lessons: data.lessons)
|
||||||
|
HStack(alignment: .top, spacing: 0) {
|
||||||
|
PeriodRuler(
|
||||||
|
periods: periods,
|
||||||
|
hourHeight: resolveHourHeight(forGridHeight: geo.size.height, periods: periods)
|
||||||
|
)
|
||||||
|
Rectangle()
|
||||||
|
.fill(palette.divider)
|
||||||
|
.frame(width: 1)
|
||||||
TimeGridView(
|
TimeGridView(
|
||||||
lessons: data.lessons,
|
lessons: data.lessons,
|
||||||
periods: data.periods,
|
periods: periods,
|
||||||
hourHeight: max(
|
hourHeight: resolveHourHeight(forGridHeight: geo.size.height, periods: periods)
|
||||||
MIN_HOUR_HEIGHT,
|
|
||||||
min(MAX_HOUR_HEIGHT, geo.size.height / max(totalMin, 60) * 60)
|
|
||||||
),
|
|
||||||
showRoom: true,
|
|
||||||
showTeacher: true,
|
|
||||||
showTimeLabels: true
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func header(data: WidgetTimetableData) -> some View {
|
private func header(data: WidgetTimetableData) -> some View {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 6) {
|
||||||
Text(dayLabel(for: data.anchorDate, relativeTo: entry.date))
|
Text(dayLabel(for: data.anchorDate, relativeTo: entry.date))
|
||||||
.font(.system(size: 13, weight: .semibold))
|
.font(.system(size: 14, weight: .bold))
|
||||||
.foregroundStyle(palette.textPrimary)
|
.foregroundStyle(palette.textPrimary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.minimumScaleFactor(0.7)
|
.minimumScaleFactor(0.65)
|
||||||
Spacer(minLength: 4)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
Text("Stand: \(freshnessLabel(for: data.fetchedAt, relativeTo: entry.date))")
|
Text("Stand: \(freshnessLabel(for: data.fetchedAt, relativeTo: entry.date))")
|
||||||
.font(.system(size: 9))
|
.font(.system(size: 10))
|
||||||
.foregroundStyle(palette.textSecondary)
|
.foregroundStyle(palette.textSecondary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.minimumScaleFactor(0.7)
|
.fixedSize()
|
||||||
}
|
}
|
||||||
|
.frame(height: TITLE_ROW_HEIGHT)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func emptyState(text: String) -> some View {
|
private func emptyState(text: String) -> some View {
|
||||||
@@ -139,54 +90,23 @@ struct TimetableDayView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TimeGridView: View {
|
/// Mirrors the in-app _PeriodLabel: period number centred, start time above,
|
||||||
let lessons: [WidgetLesson]
|
/// end time below, a cup in the breaks.
|
||||||
|
struct PeriodRuler: View {
|
||||||
let periods: [WidgetPeriod]
|
let periods: [WidgetPeriod]
|
||||||
let hourHeight: CGFloat
|
let hourHeight: CGFloat
|
||||||
let showRoom: Bool
|
|
||||||
let showTeacher: Bool
|
|
||||||
let showTimeLabels: Bool
|
|
||||||
/// Week-widget passes 3 for narrow columns; day-widget keeps 7.
|
|
||||||
var horizontalPadding: CGFloat = 7
|
|
||||||
|
|
||||||
@Environment(\.widgetPalette) private var palette
|
@Environment(\.widgetPalette) private var palette
|
||||||
|
|
||||||
private var totalVirtualMinutes: Int {
|
|
||||||
periods.last?.virtualEndMinutes ?? FALLBACK_VIRTUAL_MINUTES
|
|
||||||
}
|
|
||||||
|
|
||||||
private var totalHeight: CGFloat {
|
private var totalHeight: CGFloat {
|
||||||
CGFloat(totalVirtualMinutes) * hourHeight / 60.0
|
CGFloat(totalVirtualMinutes(periods)) * hourHeight / 60.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Below this per-hour height the two-line label collapses to a single
|
|
||||||
/// period number — time + number overlap otherwise.
|
|
||||||
private var compactLabels: Bool { hourHeight < 26 }
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .top, spacing: 0) {
|
// Every child has layout-y 0 (`.offset` only shifts the rendering),
|
||||||
if showTimeLabels {
|
// so both the ZStack and its frame need `.top` — the default
|
||||||
// 28pt matches the Week widget's time-label column so the
|
// `.center` would anchor the whole ruler at the column's midpoint.
|
||||||
// visual rhythm is identical across both widgets.
|
|
||||||
timeLabelsColumn
|
|
||||||
.frame(width: 28, alignment: .topTrailing)
|
|
||||||
Rectangle()
|
|
||||||
.fill(palette.divider)
|
|
||||||
.frame(width: 1)
|
|
||||||
}
|
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
gridLines
|
|
||||||
breakBlocks
|
|
||||||
ForEach(lessons.indices, id: \.self) { idx in
|
|
||||||
lessonBlock(lessons[idx])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, minHeight: totalHeight, alignment: .top)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var timeLabelsColumn: some View {
|
|
||||||
ZStack(alignment: .topTrailing) {
|
|
||||||
ForEach(periodBoundaries(periods), id: \.self) { virtualMin in
|
ForEach(periodBoundaries(periods), id: \.self) { virtualMin in
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.fill(palette.divider)
|
.fill(palette.divider)
|
||||||
@@ -194,39 +114,91 @@ struct TimeGridView: View {
|
|||||||
.offset(y: CGFloat(virtualMin) * hourHeight / 60.0)
|
.offset(y: CGFloat(virtualMin) * hourHeight / 60.0)
|
||||||
}
|
}
|
||||||
ForEach(periods, id: \.startMinutes) { period in
|
ForEach(periods, id: \.startMinutes) { period in
|
||||||
VStack(alignment: .trailing, spacing: -2) {
|
cell(period)
|
||||||
if compactLabels {
|
}
|
||||||
Text("\(period.name).")
|
ForEach(periodBreaks(periods), id: \.self) { pause in
|
||||||
.font(.system(size: 9, weight: .bold))
|
let height = CGFloat(pause.virtualMinutes) * hourHeight / 60.0
|
||||||
|
if height >= BREAK_ICON_MIN_HEIGHT {
|
||||||
|
Image(systemName: "cup.and.saucer")
|
||||||
|
.font(.system(size: 8))
|
||||||
|
.foregroundStyle(palette.textSecondary)
|
||||||
|
.frame(width: RULER_WIDTH, height: height)
|
||||||
|
.offset(y: CGFloat(pause.virtualStart) * hourHeight / 60.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(width: RULER_WIDTH, height: totalHeight, alignment: .top)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func cell(_ period: WidgetPeriod) -> some View {
|
||||||
|
let height = CGFloat(period.virtualEndMinutes - period.virtualStartMinutes) * hourHeight / 60.0
|
||||||
|
let timeFont = max(6, min(8.5, height * 0.19))
|
||||||
|
return ZStack {
|
||||||
|
Text(period.name)
|
||||||
|
.font(.system(size: max(8, min(13, height * 0.3)), weight: .medium))
|
||||||
.foregroundStyle(palette.textPrimary)
|
.foregroundStyle(palette.textPrimary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
} else {
|
if height >= RULER_TIMES_MIN_CELL {
|
||||||
|
VStack(spacing: 0) {
|
||||||
Text(formatHm(period.startMinutes))
|
Text(formatHm(period.startMinutes))
|
||||||
.font(.system(size: 9))
|
Spacer(minLength: 0)
|
||||||
.foregroundStyle(palette.textPrimary)
|
Text(formatHm(period.endMinutes))
|
||||||
.lineLimit(1)
|
}
|
||||||
Text("\(period.name).")
|
.font(.system(size: timeFont))
|
||||||
.font(.system(size: 7, weight: .bold))
|
|
||||||
.foregroundStyle(palette.textSecondary)
|
.foregroundStyle(palette.textSecondary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
|
.padding(.vertical, 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.trailing, 4)
|
.frame(width: RULER_WIDTH, height: height)
|
||||||
.offset(y: CGFloat(period.virtualStartMinutes) * hourHeight / 60.0)
|
.offset(y: CGFloat(period.virtualStartMinutes) * hourHeight / 60.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func formatHm(_ minutes: Int) -> String {
|
||||||
|
String(format: "%02d:%02d", minutes / 60, minutes % 60)
|
||||||
}
|
}
|
||||||
.frame(height: totalHeight, alignment: .topTrailing)
|
}
|
||||||
|
|
||||||
|
/// One day column: grid lines, break bands and the lesson tiles. Shared by
|
||||||
|
/// the day widget and each of the week widget's five columns.
|
||||||
|
struct TimeGridView: View {
|
||||||
|
let lessons: [WidgetLesson]
|
||||||
|
let periods: [WidgetPeriod]
|
||||||
|
let hourHeight: CGFloat
|
||||||
|
var holiday: WidgetDayInfo? = nil
|
||||||
|
var isToday: Bool = false
|
||||||
|
|
||||||
|
@Environment(\.widgetPalette) private var palette
|
||||||
|
|
||||||
|
private var totalHeight: CGFloat {
|
||||||
|
CGFloat(totalVirtualMinutes(periods)) * hourHeight / 60.0
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { geo in
|
||||||
|
let metrics = TileMetrics(periods: periods, hourHeight: hourHeight)
|
||||||
|
let innerWidth = geo.size.width - 2 * (TILE_MARGIN_H + TILE_PADDING_H)
|
||||||
|
ZStack(alignment: .top) {
|
||||||
|
if let holiday {
|
||||||
|
holidayColumn(holiday)
|
||||||
|
} else {
|
||||||
|
if isToday {
|
||||||
|
palette.todayTint.frame(height: totalHeight)
|
||||||
|
}
|
||||||
|
gridLines
|
||||||
|
breakBlocks
|
||||||
|
}
|
||||||
|
ForEach(lessons.indices, id: \.self) { idx in
|
||||||
|
lessonBlock(lessons[idx], metrics: metrics, innerWidth: innerWidth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(width: geo.size.width, height: totalHeight, alignment: .top)
|
||||||
|
}
|
||||||
|
.frame(height: totalHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var gridLines: some View {
|
private var gridLines: some View {
|
||||||
// `.frame(height: totalHeight, alignment: .top)` — without the
|
// `.top` on both the ZStack and the frame — see PeriodRuler.
|
||||||
// explicit `.top`, SwiftUI's frame modifier defaults to `.center`.
|
|
||||||
// Every Rectangle here has layout-y=0 (the `.offset()`s shift the
|
|
||||||
// rendered position only, not layout), so the inner ZStack's
|
|
||||||
// natural height is just 1pt; a 1pt ZStack centred in a 300pt
|
|
||||||
// frame anchors at the middle, and every line then renders
|
|
||||||
// relative to that midpoint — the grid visually starts halfway
|
|
||||||
// down the column and the later periods clip past the bottom.
|
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
ForEach(periodBoundaries(periods), id: \.self) { virtualMin in
|
ForEach(periodBoundaries(periods), id: \.self) { virtualMin in
|
||||||
Rectangle()
|
Rectangle()
|
||||||
@@ -239,103 +211,136 @@ struct TimeGridView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var breakBlocks: some View {
|
private var breakBlocks: some View {
|
||||||
// Same `.center`-vs-`.top` bug as gridLines — see comment above.
|
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
ForEach(0..<max(0, periods.count - 1), id: \.self) { i in
|
ForEach(periodBreaks(periods), id: \.self) { pause in
|
||||||
let curr = periods[i]
|
let height = CGFloat(pause.virtualMinutes) * hourHeight / 60.0
|
||||||
let next = periods[i + 1]
|
ZStack {
|
||||||
let virtualGap = next.virtualStartMinutes - curr.virtualEndMinutes
|
palette.breakBlock
|
||||||
if virtualGap > 0 {
|
if height >= BREAK_ICON_MIN_HEIGHT {
|
||||||
Rectangle()
|
Image(systemName: "fork.knife")
|
||||||
.fill(palette.breakBlock)
|
.font(.system(size: 9))
|
||||||
.frame(height: CGFloat(virtualGap) * hourHeight / 60.0)
|
.foregroundStyle(palette.accent)
|
||||||
.padding(.horizontal, 1)
|
|
||||||
.offset(y: CGFloat(curr.virtualEndMinutes) * hourHeight / 60.0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.frame(height: height)
|
||||||
|
.offset(y: CGFloat(pause.virtualStart) * hourHeight / 60.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.frame(height: totalHeight, alignment: .top)
|
.frame(height: totalHeight, alignment: .top)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func formatHm(_ minutes: Int) -> String {
|
/// Mirrors the in-app holiday region: icon and "Schulfrei" on top, the
|
||||||
String(format: "%02d:%02d", minutes / 60, minutes % 60)
|
/// holiday name rotated along the column.
|
||||||
|
private func holidayColumn(_ holiday: WidgetDayInfo) -> some View {
|
||||||
|
let nameLength = max(40, totalHeight - 2 * HOLIDAY_NAME_INSET)
|
||||||
|
return ZStack {
|
||||||
|
palette.holidayTint
|
||||||
|
VStack(spacing: 2) {
|
||||||
|
Image(systemName: "calendar")
|
||||||
|
.font(.system(size: 12))
|
||||||
|
Text("Schulfrei")
|
||||||
|
.font(.system(size: 9))
|
||||||
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(0.6)
|
||||||
|
}
|
||||||
|
.padding(.top, 8)
|
||||||
|
.padding(.horizontal, 2)
|
||||||
|
.frame(maxHeight: .infinity, alignment: .top)
|
||||||
|
// The outer frame carries the rotated footprint: rotationEffect
|
||||||
|
// alone leaves the layout box at its unrotated size.
|
||||||
|
Text(holiday.holidayName ?? "")
|
||||||
|
.font(.system(size: 11, weight: .bold))
|
||||||
|
.kerning(1)
|
||||||
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(0.6)
|
||||||
|
.frame(width: nameLength, height: 14)
|
||||||
|
.rotationEffect(.degrees(90))
|
||||||
|
.frame(width: 14, height: nameLength)
|
||||||
|
}
|
||||||
|
.foregroundStyle(palette.textPrimary)
|
||||||
|
.frame(height: totalHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func lessonBlock(_ lesson: WidgetLesson) -> some View {
|
private func lessonBlock(
|
||||||
let cal = Calendar.current
|
_ lesson: WidgetLesson,
|
||||||
let comps = cal.dateComponents([.hour, .minute], from: lesson.start)
|
metrics: TileMetrics,
|
||||||
let startMinutes = (comps.hour ?? 0) * 60 + (comps.minute ?? 0)
|
innerWidth: CGFloat
|
||||||
|
) -> some View {
|
||||||
|
let startMinutes = minutesOfDay(lesson.start)
|
||||||
let durationMinutes = max(15, Int(lesson.end.timeIntervalSince(lesson.start) / 60))
|
let durationMinutes = max(15, Int(lesson.end.timeIntervalSince(lesson.start) / 60))
|
||||||
let virtualStart = realMinutesToVirtual(startMinutes, periods: periods)
|
let virtualStart = realMinutesToVirtual(startMinutes, periods: periods)
|
||||||
let virtualEnd = realMinutesToVirtual(startMinutes + durationMinutes, periods: periods)
|
let virtualEnd = realMinutesToVirtual(startMinutes + durationMinutes, periods: periods)
|
||||||
|
|
||||||
if virtualEnd > virtualStart {
|
if virtualEnd > virtualStart {
|
||||||
|
// Half the gap above + half below so the grid line under the
|
||||||
|
// tile stays visible.
|
||||||
let top = virtualStart * hourHeight / 60.0 + LESSON_GAP / 2
|
let top = virtualStart * hourHeight / 60.0 + LESSON_GAP / 2
|
||||||
let height = max(
|
let height = max(
|
||||||
MIN_BLOCK_HEIGHT,
|
MIN_BLOCK_HEIGHT,
|
||||||
(virtualEnd - virtualStart) * hourHeight / 60.0 - LESSON_GAP
|
(virtualEnd - virtualStart) * hourHeight / 60.0 - LESSON_GAP
|
||||||
)
|
)
|
||||||
let subjectSize = subjectFont(forHourHeight: hourHeight)
|
LessonTile(
|
||||||
let secondarySize = max(MIN_SECONDARY_FONT, subjectSize - 2)
|
lesson: lesson,
|
||||||
let room = lesson.room
|
height: height,
|
||||||
let teacher = lesson.teacher ?? lesson.originalTeacher
|
metrics: metrics,
|
||||||
let hasSecondary = (room?.isEmpty == false) || (teacher?.isEmpty == false)
|
layout: TileTextLayout(
|
||||||
HStack(alignment: .top, spacing: 4) {
|
lesson: lesson,
|
||||||
Text(subjectLabel(lesson))
|
innerHeight: height - 2 * TILE_PADDING_V,
|
||||||
.font(.system(size: subjectSize, weight: .semibold))
|
innerWidth: innerWidth,
|
||||||
|
metrics: metrics
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.offset(y: top)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mirrors the in-app AppointmentTile: title on top, room and teacher
|
||||||
|
/// stacked below, each line shrinking on its own to fit the tile width.
|
||||||
|
private struct LessonTile: View {
|
||||||
|
let lesson: WidgetLesson
|
||||||
|
let height: CGFloat
|
||||||
|
let metrics: TileMetrics
|
||||||
|
let layout: TileTextLayout
|
||||||
|
|
||||||
|
private static let cancelledRed =
|
||||||
|
Color(red: 244 / 255, green: 67 / 255, blue: 54 / 255).opacity(200 / 255)
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
let shape = RoundedRectangle(cornerRadius: TILE_CORNER_RADIUS)
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
titleRow
|
||||||
|
ForEach(Array(layout.body.enumerated()), id: \.offset) { index, line in
|
||||||
|
Text(line)
|
||||||
|
.font(.system(size: metrics.bodyFont))
|
||||||
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(MIN_BODY_LINE_FONT / metrics.bodyFont)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.frame(height: metrics.bodyBox)
|
||||||
|
.padding(.trailing, badgeInset(line: index + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
.lineLimit(hasSecondary ? 1 : 2)
|
.padding(.horizontal, TILE_PADDING_H)
|
||||||
.minimumScaleFactor(0.5)
|
.padding(.vertical, TILE_PADDING_V)
|
||||||
if hasSecondary {
|
|
||||||
Spacer(minLength: 0)
|
|
||||||
VStack(alignment: .trailing, spacing: -1) {
|
|
||||||
if showRoom && height >= BLOCK_SHOW_ROOM_MIN {
|
|
||||||
if let room, !room.isEmpty {
|
|
||||||
Text(room)
|
|
||||||
.font(.system(size: secondarySize))
|
|
||||||
.foregroundStyle(.white.opacity(0.85))
|
|
||||||
.lineLimit(1)
|
|
||||||
.minimumScaleFactor(0.5)
|
|
||||||
}
|
|
||||||
if showTeacher,
|
|
||||||
height >= BLOCK_SHOW_TEACHER_SEPARATE_MIN,
|
|
||||||
let teacher,
|
|
||||||
!teacher.isEmpty {
|
|
||||||
Text(teacher)
|
|
||||||
.font(.system(size: secondarySize))
|
|
||||||
.foregroundStyle(.white.opacity(0.7))
|
|
||||||
.lineLimit(1)
|
|
||||||
.minimumScaleFactor(0.5)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal, horizontalPadding)
|
|
||||||
.padding(.vertical, 3)
|
|
||||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||||
.frame(height: height, alignment: .topLeading)
|
.frame(height: height, alignment: .topLeading)
|
||||||
.background(blockColor(lesson))
|
.background(lesson.tileColor)
|
||||||
.cornerRadius(6)
|
.clipShape(shape)
|
||||||
.overlay(alignment: .bottomLeading) {
|
.overlay(alignment: .bottomTrailing) {
|
||||||
// Separate fixed-size badge so the +N hint stays readable
|
|
||||||
// when the subject autoshrinks on narrow tiles.
|
|
||||||
if let count = lesson.siblingCount, count > 0 {
|
if let count = lesson.siblingCount, count > 0 {
|
||||||
Text("+\(count)")
|
Text("+\(count)")
|
||||||
.font(.system(size: 12, weight: .bold))
|
.font(.system(size: 9, weight: .bold))
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
.padding(.leading, horizontalPadding)
|
.padding(.trailing, TILE_PADDING_H)
|
||||||
.padding(.bottom, 2)
|
.padding(.bottom, TILE_PADDING_V)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.overlay {
|
.overlay {
|
||||||
// CrossPainter parity: clip cross to the rounded shape so
|
|
||||||
// the diagonals don't bleed past the corners.
|
|
||||||
if lesson.status == .cancelled {
|
if lesson.status == .cancelled {
|
||||||
ZStack {
|
ZStack {
|
||||||
RoundedRectangle(cornerRadius: 6)
|
shape.strokeBorder(LessonTile.cancelledRed, lineWidth: 2)
|
||||||
.stroke(Color.red.opacity(0.78), lineWidth: 1.5)
|
|
||||||
GeometryReader { geo in
|
GeometryReader { geo in
|
||||||
Path { p in
|
Path { p in
|
||||||
p.move(to: .zero)
|
p.move(to: .zero)
|
||||||
@@ -343,63 +348,37 @@ struct TimeGridView: View {
|
|||||||
p.move(to: CGPoint(x: geo.size.width, y: 0))
|
p.move(to: CGPoint(x: geo.size.width, y: 0))
|
||||||
p.addLine(to: CGPoint(x: 0, y: geo.size.height))
|
p.addLine(to: CGPoint(x: 0, y: geo.size.height))
|
||||||
}
|
}
|
||||||
.stroke(Color.red.opacity(0.78), lineWidth: 3)
|
.stroke(LessonTile.cancelledRed, lineWidth: 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 6))
|
.clipShape(shape)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, showRoom ? 2 : 1)
|
.padding(.horizontal, TILE_MARGIN_H)
|
||||||
.offset(y: top)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func subjectLabel(_ lesson: WidgetLesson) -> String {
|
private var titleRow: some View {
|
||||||
!lesson.subjectShort.isEmpty
|
HStack(alignment: .center, spacing: 3) {
|
||||||
? lesson.subjectShort
|
Text(layout.title)
|
||||||
: (lesson.subjectLong ?? "—")
|
.font(.system(size: layout.titleFont, weight: .medium))
|
||||||
|
.lineLimit(layout.titleLines)
|
||||||
|
.minimumScaleFactor(min(1, MIN_TITLE_FONT / layout.titleFont))
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
if let inline = layout.inline {
|
||||||
|
Text(inline)
|
||||||
|
.font(.system(size: metrics.bodyFont))
|
||||||
|
.lineLimit(1)
|
||||||
|
.fixedSize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: layout.titleBox)
|
||||||
|
.padding(.trailing, badgeInset(line: 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mirrors lesson_color.dart + custom_event_colors.dart so the widget
|
private func badgeInset(line: Int) -> CGFloat {
|
||||||
/// matches the in-app calendar exactly.
|
layout.badgeLine == line ? BADGE_WIDTH : 0
|
||||||
private func blockColor(_ lesson: WidgetLesson) -> Color {
|
|
||||||
if lesson.status == .event, let custom = lesson.customColor {
|
|
||||||
switch custom {
|
|
||||||
case "orange": return Color(red: 239/255.0, green: 108/255.0, blue: 0/255.0)
|
|
||||||
case "red": return Color(red: 153/255.0, green: 51/255.0, blue: 51/255.0)
|
|
||||||
case "green": return Color(red: 76/255.0, green: 175/255.0, blue: 80/255.0)
|
|
||||||
case "blue": return Color(red: 33/255.0, green: 150/255.0, blue: 243/255.0)
|
|
||||||
default: break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch lesson.status {
|
|
||||||
// `.ongoing` deliberately collapses into the regular red — the
|
|
||||||
// widget timeline only ticks every ~30min, so highlighting the
|
|
||||||
// "current" lesson would be stale most of the day and misleads
|
|
||||||
// more than it helps.
|
|
||||||
case .regular, .past, .ongoing:
|
|
||||||
return Color(red: 153/255.0, green: 51/255.0, blue: 51/255.0)
|
|
||||||
case .cancelled: return .black
|
|
||||||
case .irregular: return Color(red: 143/255.0, green: 25/255.0, blue: 179/255.0)
|
|
||||||
case .teacherChanged: return Color(red: 41/255.0, green: 99/255.0, blue: 155/255.0)
|
|
||||||
case .event: return Color(red: 239/255.0, green: 108/255.0, blue: 0/255.0)
|
|
||||||
case .duty: return Color(red: 0/255.0, green: 121/255.0, blue: 107/255.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Period boundaries deduped: adjacent periods share a line, periods on
|
|
||||||
/// either side of a break get their own (bracketing the break block).
|
|
||||||
func periodBoundaries(_ periods: [WidgetPeriod]) -> [Int] {
|
|
||||||
var seen = Set<Int>()
|
|
||||||
var result: [Int] = []
|
|
||||||
for p in periods {
|
|
||||||
for v in [p.virtualStartMinutes, p.virtualEndMinutes] {
|
|
||||||
if seen.insert(v).inserted { result.append(v) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.sorted()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fixed-locale formatters cached once — DateFormatter setup is the
|
/// Fixed-locale formatters cached once — DateFormatter setup is the
|
||||||
/// expensive part and the multi-entry timelines render up to ~8 entries per
|
/// expensive part and the multi-entry timelines render up to ~8 entries per
|
||||||
|
|||||||
@@ -0,0 +1,279 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// Layout constants and rules — must mirror WidgetRenderer.kt on Android,
|
||||||
|
// otherwise the platforms drift apart on the same widget size.
|
||||||
|
let FALLBACK_VIRTUAL_MINUTES = 11 * 60
|
||||||
|
let FALLBACK_SLOT_MINUTES = 45
|
||||||
|
|
||||||
|
/// Trailing periods without lessons are cut off so the tiles get more
|
||||||
|
/// height — but never below this many periods, or a short day would blow up
|
||||||
|
/// to a handful of oversized tiles.
|
||||||
|
let MIN_VISIBLE_PERIODS = 6
|
||||||
|
|
||||||
|
let MIN_HOUR_HEIGHT: CGFloat = 18
|
||||||
|
let MAX_HOUR_HEIGHT: CGFloat = 72
|
||||||
|
let MIN_BLOCK_HEIGHT: CGFloat = 16
|
||||||
|
let LESSON_GAP: CGFloat = 1.5
|
||||||
|
|
||||||
|
let TITLE_ROW_HEIGHT: CGFloat = 20
|
||||||
|
let GRID_MARGIN_TOP: CGFloat = 4
|
||||||
|
let WEEK_HEADER_HEIGHT: CGFloat = 32
|
||||||
|
let RULER_WIDTH: CGFloat = 30
|
||||||
|
|
||||||
|
let TILE_MARGIN_H: CGFloat = 1
|
||||||
|
let TILE_PADDING_H: CGFloat = 4
|
||||||
|
let TILE_PADDING_V: CGFloat = 2
|
||||||
|
let TILE_CORNER_RADIUS: CGFloat = 7
|
||||||
|
|
||||||
|
// In-app tile: 15sp title over 10sp body lines. The widget keeps that ratio
|
||||||
|
// and scales both so title + room + teacher fill one period.
|
||||||
|
let TITLE_TO_BODY_RATIO: CGFloat = 1.5
|
||||||
|
let MAX_BODY_FONT: CGFloat = 10
|
||||||
|
let MIN_BODY_FONT: CGFloat = 6.5
|
||||||
|
let MIN_TITLE_FONT: CGFloat = 6.5
|
||||||
|
let MIN_BODY_LINE_FONT: CGFloat = 5.5
|
||||||
|
let LINE_BOX_PER_FONT: CGFloat = 1.2
|
||||||
|
|
||||||
|
/// If stacking room and teacher would push the body font below this, both
|
||||||
|
/// share one line instead so the text stays readable.
|
||||||
|
let STACKED_BODY_MIN_FONT: CGFloat = 7.5
|
||||||
|
|
||||||
|
/// A title that needs to shrink below this to fit on one line wraps onto a
|
||||||
|
/// second line instead, if the tile has the height for it.
|
||||||
|
let TITLE_WRAP_BELOW_FONT: CGFloat = 8
|
||||||
|
|
||||||
|
// Footprint of the "+N" badge in the tile's bottom trailing corner.
|
||||||
|
let BADGE_WIDTH: CGFloat = 16
|
||||||
|
let BADGE_HEIGHT: CGFloat = 11
|
||||||
|
|
||||||
|
let RULER_TIMES_MIN_CELL: CGFloat = 30
|
||||||
|
let BREAK_ICON_MIN_HEIGHT: CGFloat = 12
|
||||||
|
|
||||||
|
/// Space the holiday column keeps free above and below the rotated name for
|
||||||
|
/// the icon and the "Schulfrei" label.
|
||||||
|
let HOLIDAY_NAME_INSET: CGFloat = 44
|
||||||
|
|
||||||
|
func realMinutesToVirtual(_ realMin: Int, periods: [WidgetPeriod]) -> CGFloat {
|
||||||
|
guard !periods.isEmpty else { return CGFloat(realMin) }
|
||||||
|
for p in periods where realMin >= p.startMinutes && realMin <= p.endMinutes {
|
||||||
|
return CGFloat(p.virtualStartMinutes + (realMin - p.startMinutes))
|
||||||
|
}
|
||||||
|
let first = periods.first!
|
||||||
|
if realMin < first.startMinutes {
|
||||||
|
return CGFloat(realMin - first.startMinutes + first.virtualStartMinutes)
|
||||||
|
}
|
||||||
|
let last = periods.last!
|
||||||
|
if realMin > last.endMinutes {
|
||||||
|
return CGFloat(last.virtualEndMinutes + (realMin - last.endMinutes))
|
||||||
|
}
|
||||||
|
var prev = first
|
||||||
|
for i in 1..<periods.count {
|
||||||
|
let curr = periods[i]
|
||||||
|
if realMin > prev.endMinutes && realMin < curr.startMinutes {
|
||||||
|
let gap = curr.startMinutes - prev.endMinutes
|
||||||
|
let virtualGap = curr.virtualStartMinutes - prev.virtualEndMinutes
|
||||||
|
if gap > 0 {
|
||||||
|
return CGFloat(prev.virtualEndMinutes) +
|
||||||
|
CGFloat(realMin - prev.endMinutes) * CGFloat(virtualGap) / CGFloat(gap)
|
||||||
|
}
|
||||||
|
return CGFloat(curr.virtualStartMinutes)
|
||||||
|
}
|
||||||
|
prev = curr
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func minutesOfDay(_ date: Date) -> Int {
|
||||||
|
let comps = Calendar.current.dateComponents([.hour, .minute], from: date)
|
||||||
|
return (comps.hour ?? 0) * 60 + (comps.minute ?? 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimTrailingPeriods(_ periods: [WidgetPeriod], lessons: [WidgetLesson]) -> [WidgetPeriod] {
|
||||||
|
guard let latestEnd = lessons.map({ minutesOfDay($0.end) }).max() else { return periods }
|
||||||
|
let lastUsed = periods.lastIndex { $0.startMinutes < latestEnd } ?? -1
|
||||||
|
return Array(periods.prefix(max(lastUsed + 1, MIN_VISIBLE_PERIODS)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func totalVirtualMinutes(_ periods: [WidgetPeriod]) -> Int {
|
||||||
|
periods.last?.virtualEndMinutes ?? FALLBACK_VIRTUAL_MINUTES
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveHourHeight(forGridHeight height: CGFloat, periods: [WidgetPeriod]) -> CGFloat {
|
||||||
|
let virtualHours = max(CGFloat(totalVirtualMinutes(periods)) / 60, 1)
|
||||||
|
return max(MIN_HOUR_HEIGHT, min(MAX_HOUR_HEIGHT, height / virtualHours))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Period boundaries deduped: adjacent periods share a line, periods on
|
||||||
|
/// either side of a break get their own (bracketing the break block).
|
||||||
|
func periodBoundaries(_ periods: [WidgetPeriod]) -> [Int] {
|
||||||
|
var seen = Set<Int>()
|
||||||
|
var result: [Int] = []
|
||||||
|
for p in periods {
|
||||||
|
for v in [p.virtualStartMinutes, p.virtualEndMinutes] {
|
||||||
|
if seen.insert(v).inserted { result.append(v) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.sorted()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PeriodBreak: Hashable {
|
||||||
|
let virtualStart: Int
|
||||||
|
let virtualMinutes: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only big breaks (Hofpause, Mittagspause) survive the mapper's small-break
|
||||||
|
/// collapse as a virtual gap between two periods.
|
||||||
|
func periodBreaks(_ periods: [WidgetPeriod]) -> [PeriodBreak] {
|
||||||
|
guard periods.count > 1 else { return [] }
|
||||||
|
return (0..<periods.count - 1).compactMap { i in
|
||||||
|
let gap = periods[i + 1].virtualStartMinutes - periods[i].virtualEndMinutes
|
||||||
|
return gap > 0
|
||||||
|
? PeriodBreak(virtualStart: periods[i].virtualEndMinutes, virtualMinutes: gap)
|
||||||
|
: nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Font and line-box sizes shared by every tile of one widget, derived from
|
||||||
|
/// the height of a single period.
|
||||||
|
struct TileMetrics {
|
||||||
|
let maxBodyLines: Int
|
||||||
|
let bodyFont: CGFloat
|
||||||
|
let titleBox: CGFloat
|
||||||
|
let bodyBox: CGFloat
|
||||||
|
|
||||||
|
init(periods: [WidgetPeriod], hourHeight: CGFloat) {
|
||||||
|
let slotMinutes = periods.first.map { $0.virtualEndMinutes - $0.virtualStartMinutes }
|
||||||
|
?? FALLBACK_SLOT_MINUTES
|
||||||
|
let slotHeight = CGFloat(slotMinutes) * hourHeight / 60
|
||||||
|
let inner = slotHeight - LESSON_GAP - 2 * TILE_PADDING_V
|
||||||
|
func font(lines: CGFloat) -> CGFloat {
|
||||||
|
inner / (LINE_BOX_PER_FONT * (TITLE_TO_BODY_RATIO + lines))
|
||||||
|
}
|
||||||
|
maxBodyLines = font(lines: 2) < STACKED_BODY_MIN_FONT ? 1 : 2
|
||||||
|
bodyFont = max(MIN_BODY_FONT, min(MAX_BODY_FONT, font(lines: CGFloat(maxBodyLines))))
|
||||||
|
titleBox = bodyFont * TITLE_TO_BODY_RATIO * LINE_BOX_PER_FONT
|
||||||
|
bodyBox = bodyFont * LINE_BOX_PER_FONT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Distributes the tile height like the in-app _TileContent: the title comes
|
||||||
|
/// first, then as many body lines as fit.
|
||||||
|
struct TileTextLayout {
|
||||||
|
let title: String
|
||||||
|
let titleBox: CGFloat
|
||||||
|
let titleFont: CGFloat
|
||||||
|
let titleLines: Int
|
||||||
|
let body: [String]
|
||||||
|
/// Set when there is no room for a line below the title: the room moves
|
||||||
|
/// next to it instead.
|
||||||
|
let inline: String?
|
||||||
|
/// Index of the line (0 = title row) that has to stay clear of the "+N"
|
||||||
|
/// badge, or nil when the text ends above it.
|
||||||
|
let badgeLine: Int?
|
||||||
|
|
||||||
|
init(lesson: WidgetLesson, innerHeight: CGFloat, innerWidth: CGFloat, metrics: TileMetrics) {
|
||||||
|
title = !lesson.subjectShort.isEmpty ? lesson.subjectShort : (lesson.subjectLong ?? "—")
|
||||||
|
let candidates = [lesson.room, lesson.teacher ?? lesson.originalTeacher]
|
||||||
|
.compactMap { $0 }
|
||||||
|
.filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
|
||||||
|
|
||||||
|
let singleTitleBox = min(metrics.titleBox, innerHeight)
|
||||||
|
let capacity = max(
|
||||||
|
0,
|
||||||
|
min(metrics.maxBodyLines, Int((innerHeight - singleTitleBox) / metrics.bodyBox + 0.05))
|
||||||
|
)
|
||||||
|
if capacity >= candidates.count {
|
||||||
|
body = candidates
|
||||||
|
} else if capacity == 1 {
|
||||||
|
// Rather share one line than drop the teacher entirely.
|
||||||
|
body = [candidates.joined(separator: " · ")]
|
||||||
|
} else {
|
||||||
|
body = []
|
||||||
|
}
|
||||||
|
if capacity == 0, let first = candidates.first, !title.contains(first) {
|
||||||
|
inline = first
|
||||||
|
} else {
|
||||||
|
inline = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let spare = innerHeight - singleTitleBox - CGFloat(body.count) * metrics.bodyBox
|
||||||
|
if spare >= singleTitleBox,
|
||||||
|
let wrappedLine = TileTextLayout.wrappedTitleLineBox(
|
||||||
|
title, innerWidth: innerWidth, metrics: metrics
|
||||||
|
) {
|
||||||
|
titleBox = 2 * wrappedLine
|
||||||
|
titleFont = wrappedLine / LINE_BOX_PER_FONT
|
||||||
|
titleLines = 2
|
||||||
|
} else {
|
||||||
|
titleBox = singleTitleBox
|
||||||
|
titleFont = singleTitleBox / LINE_BOX_PER_FONT
|
||||||
|
titleLines = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = titleBox + CGFloat(body.count) * metrics.bodyBox
|
||||||
|
let hasBadge = (lesson.siblingCount ?? 0) > 0
|
||||||
|
badgeLine = hasBadge && innerHeight - content < BADGE_HEIGHT ? body.count : nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Line box for a title wrapped onto two lines, or nil when it should
|
||||||
|
/// stay on one: it fits there at a readable size, or it has no word
|
||||||
|
/// boundary to break at. The box is capped so the longest word still
|
||||||
|
/// fits the tile width.
|
||||||
|
private static func wrappedTitleLineBox(
|
||||||
|
_ title: String,
|
||||||
|
innerWidth: CGFloat,
|
||||||
|
metrics: TileMetrics
|
||||||
|
) -> CGFloat? {
|
||||||
|
func widthPerFontPoint(_ text: String) -> CGFloat {
|
||||||
|
let font = UIFont.systemFont(ofSize: 100, weight: .medium)
|
||||||
|
return (text as NSString).size(withAttributes: [.font: font]).width / 100
|
||||||
|
}
|
||||||
|
if widthPerFontPoint(title) * TITLE_WRAP_BELOW_FONT <= innerWidth { return nil }
|
||||||
|
let words = title
|
||||||
|
.replacingOccurrences(of: "-", with: "- ")
|
||||||
|
.split(separator: " ")
|
||||||
|
.map(String.init)
|
||||||
|
guard words.count > 1, let widest = words.map(widthPerFontPoint).max(), widest > 0 else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return min(metrics.titleBox, innerWidth / widest * LINE_BOX_PER_FONT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Color {
|
||||||
|
/// Parses the `#RRGGBB` tile colour resolved in Dart.
|
||||||
|
init?(widgetHex hex: String?) {
|
||||||
|
guard let hex, hex.count == 7, hex.hasPrefix("#"),
|
||||||
|
let value = UInt32(hex.dropFirst(), radix: 16) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
self.init(
|
||||||
|
red: Double((value >> 16) & 0xFF) / 255,
|
||||||
|
green: Double((value >> 8) & 0xFF) / 255,
|
||||||
|
blue: Double(value & 0xFF) / 255
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension WidgetLesson {
|
||||||
|
var tileColor: Color {
|
||||||
|
if let resolved = Color(widgetHex: color) { return resolved }
|
||||||
|
// Only reached for payloads written before Dart shipped the resolved
|
||||||
|
// tile colour; mirrors lesson_color.dart.
|
||||||
|
switch status {
|
||||||
|
// No time-based highlight: the widget timeline only ticks every
|
||||||
|
// ~30min, so "current" and "past" would be stale most of the day.
|
||||||
|
case .regular, .past, .ongoing:
|
||||||
|
return Color(red: 153 / 255, green: 51 / 255, blue: 51 / 255)
|
||||||
|
case .cancelled: return .black
|
||||||
|
case .irregular: return Color(red: 143 / 255, green: 25 / 255, blue: 179 / 255)
|
||||||
|
case .teacherChanged: return Color(red: 41 / 255, green: 99 / 255, blue: 155 / 255)
|
||||||
|
case .duty: return Color(red: 0, green: 121 / 255, blue: 107 / 255)
|
||||||
|
case .event:
|
||||||
|
return customColor != nil
|
||||||
|
? Color(red: 1, green: 160 / 255, blue: 0)
|
||||||
|
: Color(red: 46 / 255, green: 125 / 255, blue: 50 / 255)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,36 +20,44 @@ struct TimetableWeekView: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func content(data: WidgetTimetableData) -> some View {
|
private func content(data: WidgetTimetableData) -> some View {
|
||||||
|
let cal = Calendar.current
|
||||||
|
let days = (0..<5).map { offset in
|
||||||
|
cal.date(byAdding: .day, value: offset, to: data.anchorDate) ?? data.anchorDate
|
||||||
|
}
|
||||||
|
let weekLessons = data.lessons.filter { lesson in
|
||||||
|
days.contains { cal.isDate(lesson.start, inSameDayAs: $0) }
|
||||||
|
}
|
||||||
|
let periods = trimTrailingPeriods(data.periods, lessons: weekLessons)
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
header(data: data)
|
header(data: data)
|
||||||
dayHeaderRow(data: data)
|
dayHeaderRow(days: days)
|
||||||
|
.padding(.top, GRID_MARGIN_TOP)
|
||||||
GeometryReader { geo in
|
GeometryReader { geo in
|
||||||
let totalMin = CGFloat(data.periods.last?.virtualEndMinutes ?? FALLBACK_VIRTUAL_MINUTES)
|
let height = resolveHourHeight(forGridHeight: geo.size.height, periods: periods)
|
||||||
let hourHeight = max(
|
|
||||||
MIN_HOUR_HEIGHT,
|
|
||||||
min(MAX_HOUR_HEIGHT, geo.size.height / max(totalMin, 60) * 60)
|
|
||||||
)
|
|
||||||
let dayColumnWidth = (geo.size.width - 28 - 4) / 5
|
|
||||||
// 45pt is the minimum where a 7pt-font room (e.g. "F2.04")
|
|
||||||
// plus the subject fits without `minimumScaleFactor`
|
|
||||||
// shrinking the subject into illegibility.
|
|
||||||
let subjectOnly = dayColumnWidth < 45
|
|
||||||
HStack(alignment: .top, spacing: 0) {
|
HStack(alignment: .top, spacing: 0) {
|
||||||
timeLabelsColumn(hourHeight: hourHeight, periods: data.periods)
|
PeriodRuler(periods: periods, hourHeight: height)
|
||||||
.frame(width: 28, alignment: .topTrailing)
|
|
||||||
columnDivider
|
columnDivider
|
||||||
ForEach(0..<5, id: \.self) { offset in
|
ForEach(days.indices, id: \.self) { index in
|
||||||
column(
|
TimeGridView(
|
||||||
data: data,
|
lessons: weekLessons.filter {
|
||||||
offset: offset,
|
cal.isDate($0.start, inSameDayAs: days[index])
|
||||||
hourHeight: hourHeight,
|
},
|
||||||
subjectOnly: subjectOnly
|
periods: periods,
|
||||||
|
hourHeight: height,
|
||||||
|
holiday: holiday(on: days[index], data: data),
|
||||||
|
isToday: cal.isDate(days[index], inSameDayAs: entry.date)
|
||||||
)
|
)
|
||||||
.frame(maxWidth: .infinity)
|
if index < 4 { columnDivider }
|
||||||
if offset < 4 { columnDivider }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.top, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func holiday(on day: Date, data: WidgetTimetableData) -> WidgetDayInfo? {
|
||||||
|
data.days?.first {
|
||||||
|
$0.isHoliday && Calendar.current.isDate($0.date, inSameDayAs: day)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,113 +71,56 @@ struct TimetableWeekView: View {
|
|||||||
let cal = Calendar.current
|
let cal = Calendar.current
|
||||||
let week = cal.component(.weekOfYear, from: data.anchorDate)
|
let week = cal.component(.weekOfYear, from: data.anchorDate)
|
||||||
let endDate = cal.date(byAdding: .day, value: 4, to: data.anchorDate) ?? data.anchorDate
|
let endDate = cal.date(byAdding: .day, value: 4, to: data.anchorDate) ?? data.anchorDate
|
||||||
return HStack {
|
return HStack(spacing: 6) {
|
||||||
Text("KW \(week) · \(shortDate(data.anchorDate))–\(shortDate(endDate))")
|
Text("KW \(week) · \(shortDate(data.anchorDate))–\(shortDate(endDate))")
|
||||||
.font(.system(size: 12, weight: .semibold))
|
.font(.system(size: 14, weight: .bold))
|
||||||
.foregroundStyle(palette.textPrimary)
|
.foregroundStyle(palette.textPrimary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.minimumScaleFactor(0.8)
|
.minimumScaleFactor(0.65)
|
||||||
Spacer()
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
Text("Stand: \(freshnessLabel(for: data.fetchedAt, relativeTo: entry.date))")
|
Text("Stand: \(freshnessLabel(for: data.fetchedAt, relativeTo: entry.date))")
|
||||||
.font(.system(size: 9))
|
.font(.system(size: 10))
|
||||||
.foregroundStyle(palette.textSecondary)
|
.foregroundStyle(palette.textSecondary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
|
.fixedSize()
|
||||||
}
|
}
|
||||||
|
.frame(height: TITLE_ROW_HEIGHT)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func dayHeaderRow(data: WidgetTimetableData) -> some View {
|
/// Mirrors the in-app _DayHeaderCell: weekday in caps, day number below,
|
||||||
let cal = Calendar.current
|
/// today inside an accent circle.
|
||||||
// `.frame(maxWidth: .infinity)` is critical: without an explicit
|
private func dayHeaderRow(days: [Date]) -> some View {
|
||||||
// greedy width (or a true `Spacer()` inside), this HStack collapses
|
|
||||||
// to the sum of its fixed-width children in a `.leading` VStack
|
|
||||||
// and the 5 day-columns end up sharing ~5pt of width — the labels
|
|
||||||
// crunch to the left and stop aligning with the grid columns below.
|
|
||||||
// The fixed height keeps `columnDivider`'s vertically-flexible
|
// The fixed height keeps `columnDivider`'s vertically-flexible
|
||||||
// Rectangle from stealing space from the GeometryReader.
|
// Rectangle from stealing space from the GeometryReader below.
|
||||||
return HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Spacer().frame(width: 28)
|
Spacer().frame(width: RULER_WIDTH)
|
||||||
columnDivider
|
columnDivider
|
||||||
ForEach(0..<5, id: \.self) { offset in
|
ForEach(days.indices, id: \.self) { index in
|
||||||
let day = cal.date(byAdding: .day, value: offset, to: data.anchorDate) ?? data.anchorDate
|
dayHeaderCell(days[index])
|
||||||
VStack(spacing: -1) {
|
|
||||||
Text(weekday(for: day))
|
|
||||||
.font(.system(size: 10, weight: .bold))
|
|
||||||
.foregroundStyle(palette.textPrimary)
|
|
||||||
Text(shortDate(day))
|
|
||||||
.font(.system(size: 8))
|
|
||||||
.foregroundStyle(palette.textSecondary)
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
if offset < 4 { columnDivider }
|
if index < 4 { columnDivider }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 20pt fits 10pt weekday + 8pt date with tight -1pt spacing.
|
.frame(maxWidth: .infinity, minHeight: WEEK_HEADER_HEIGHT, maxHeight: WEEK_HEADER_HEIGHT)
|
||||||
.frame(maxWidth: .infinity, minHeight: 20, maxHeight: 20)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func timeLabelsColumn(hourHeight: CGFloat, periods: [WidgetPeriod]) -> some View {
|
private func dayHeaderCell(_ day: Date) -> some View {
|
||||||
let totalMin = periods.last?.virtualEndMinutes ?? FALLBACK_VIRTUAL_MINUTES
|
// `entry.date`, not `Date()` — WidgetKit archives entries ahead of
|
||||||
let totalHeight = CGFloat(totalMin) * hourHeight / 60.0
|
// time, the midnight entry has to mark its own day.
|
||||||
// Mirrors TimeGridView.compactLabels — below 26pt/hour a stacked
|
let isToday = Calendar.current.isDate(day, inSameDayAs: entry.date)
|
||||||
// time+name no longer fits in one period slot and the two labels
|
return VStack(spacing: 1) {
|
||||||
// visually clip into the next slot. Drop to a single bold period
|
Text(WidgetDateFormatters.weekdayShort.string(from: day).uppercased())
|
||||||
// number until there's room to read both lines.
|
.font(.system(size: 9, weight: .medium))
|
||||||
let compact = hourHeight < 26
|
.foregroundStyle(isToday ? palette.accent : palette.textSecondary)
|
||||||
return ZStack(alignment: .topTrailing) {
|
|
||||||
ForEach(periodBoundaries(periods), id: \.self) { virtualMin in
|
|
||||||
Rectangle()
|
|
||||||
.fill(palette.divider)
|
|
||||||
.frame(height: 1)
|
|
||||||
.offset(y: CGFloat(virtualMin) * hourHeight / 60.0)
|
|
||||||
}
|
|
||||||
ForEach(periods, id: \.startMinutes) { period in
|
|
||||||
VStack(alignment: .trailing, spacing: -2) {
|
|
||||||
if compact {
|
|
||||||
Text("\(period.name).")
|
|
||||||
.font(.system(size: 9, weight: .bold))
|
|
||||||
.foregroundStyle(palette.textPrimary)
|
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
} else {
|
Text("\(Calendar.current.component(.day, from: day))")
|
||||||
Text(String(format: "%02d:%02d", period.startMinutes / 60, period.startMinutes % 60))
|
.font(.system(size: 11, weight: isToday ? .bold : .regular))
|
||||||
.font(.system(size: 9))
|
.foregroundStyle(isToday ? palette.onAccent : palette.textPrimary)
|
||||||
.foregroundStyle(palette.textPrimary)
|
|
||||||
.lineLimit(1)
|
|
||||||
Text("\(period.name).")
|
|
||||||
.font(.system(size: 7, weight: .bold))
|
|
||||||
.foregroundStyle(palette.textSecondary)
|
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
|
.frame(width: 18, height: 18)
|
||||||
|
.background(Circle().fill(isToday ? palette.accent : Color.clear))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.trailing, 4)
|
|
||||||
.offset(y: CGFloat(period.virtualStartMinutes) * hourHeight / 60.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(height: totalHeight, alignment: .topTrailing)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func column(
|
|
||||||
data: WidgetTimetableData,
|
|
||||||
offset: Int,
|
|
||||||
hourHeight: CGFloat,
|
|
||||||
subjectOnly: Bool
|
|
||||||
) -> some View {
|
|
||||||
let cal = Calendar.current
|
|
||||||
let day = cal.date(byAdding: .day, value: offset, to: data.anchorDate) ?? data.anchorDate
|
|
||||||
let lessonsForDay = data.lessons.filter { cal.isDate($0.start, inSameDayAs: day) }
|
|
||||||
return TimeGridView(
|
|
||||||
lessons: lessonsForDay,
|
|
||||||
periods: data.periods,
|
|
||||||
hourHeight: hourHeight,
|
|
||||||
showRoom: !subjectOnly,
|
|
||||||
showTeacher: !subjectOnly,
|
|
||||||
showTimeLabels: false,
|
|
||||||
horizontalPadding: 3
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func weekday(for date: Date) -> String {
|
|
||||||
WidgetDateFormatters.weekdayShort.string(from: date)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func placeholder(_ message: String) -> some View {
|
private func placeholder(_ message: String) -> some View {
|
||||||
VStack(spacing: 4) {
|
VStack(spacing: 4) {
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ struct WidgetLesson: Codable {
|
|||||||
let originalTeacher: String?
|
let originalTeacher: String?
|
||||||
let status: WidgetLessonStatus
|
let status: WidgetLessonStatus
|
||||||
let customColor: String?
|
let customColor: String?
|
||||||
|
// Tile colour resolved in Dart as "#RRGGBB" (subject colours, palettes);
|
||||||
|
// nil only for payloads written before the field existed.
|
||||||
|
let color: String?
|
||||||
let siblingCount: Int?
|
let siblingCount: Int?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,15 +9,27 @@ struct WidgetPalette {
|
|||||||
let textPrimary: Color
|
let textPrimary: Color
|
||||||
let textSecondary: Color
|
let textSecondary: Color
|
||||||
let divider: Color
|
let divider: Color
|
||||||
|
let accent: Color
|
||||||
|
let onAccent: Color
|
||||||
let breakBlock: Color
|
let breakBlock: Color
|
||||||
|
let todayTint: Color
|
||||||
|
let holidayTint: Color
|
||||||
let watermarkOpacity: Double
|
let watermarkOpacity: Double
|
||||||
|
|
||||||
|
// ColorScheme.fromSeed(#993333).primary of the app's light / dark theme.
|
||||||
|
private static let lightAccent = Color(red: 0x90 / 255, green: 0x4A / 255, blue: 0x47 / 255)
|
||||||
|
private static let darkAccent = Color(red: 0xFF / 255, green: 0xB3 / 255, blue: 0xAF / 255)
|
||||||
|
|
||||||
static let light = WidgetPalette(
|
static let light = WidgetPalette(
|
||||||
background: Color(red: 0xFC / 255, green: 0xF7 / 255, blue: 0xF5 / 255),
|
background: Color(red: 0xFC / 255, green: 0xF7 / 255, blue: 0xF5 / 255),
|
||||||
textPrimary: Color(red: 0x11 / 255, green: 0x11 / 255, blue: 0x11 / 255),
|
textPrimary: Color(red: 0x11 / 255, green: 0x11 / 255, blue: 0x11 / 255),
|
||||||
textSecondary: Color(red: 0x55 / 255, green: 0x55 / 255, blue: 0x55 / 255),
|
textSecondary: Color(red: 0x55 / 255, green: 0x55 / 255, blue: 0x55 / 255),
|
||||||
divider: Color.black.opacity(0x22 / 255.0),
|
divider: Color.black.opacity(0x22 / 255.0),
|
||||||
breakBlock: Color.black.opacity(0x0C / 255.0),
|
accent: lightAccent,
|
||||||
|
onAccent: .white,
|
||||||
|
breakBlock: lightAccent.opacity(0x32 / 255.0),
|
||||||
|
todayTint: lightAccent.opacity(0x0E / 255.0),
|
||||||
|
holidayTint: Color.black.opacity(0x32 / 255.0),
|
||||||
watermarkOpacity: 0.014
|
watermarkOpacity: 0.014
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,7 +38,11 @@ struct WidgetPalette {
|
|||||||
textPrimary: Color(red: 0xF1 / 255, green: 0xF1 / 255, blue: 0xF1 / 255),
|
textPrimary: Color(red: 0xF1 / 255, green: 0xF1 / 255, blue: 0xF1 / 255),
|
||||||
textSecondary: Color(red: 0xB0 / 255, green: 0xB0 / 255, blue: 0xB0 / 255),
|
textSecondary: Color(red: 0xB0 / 255, green: 0xB0 / 255, blue: 0xB0 / 255),
|
||||||
divider: Color.white.opacity(0x33 / 255.0),
|
divider: Color.white.opacity(0x33 / 255.0),
|
||||||
breakBlock: Color.white.opacity(0x14 / 255.0),
|
accent: darkAccent,
|
||||||
|
onAccent: Color(red: 0x57 / 255, green: 0x1D / 255, blue: 0x1D / 255),
|
||||||
|
breakBlock: darkAccent.opacity(0x32 / 255.0),
|
||||||
|
todayTint: darkAccent.opacity(0x0E / 255.0),
|
||||||
|
holidayTint: Color.white.opacity(0x32 / 255.0),
|
||||||
watermarkOpacity: 0.025
|
watermarkOpacity: 0.025
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ enum WidgetLessonStatus {
|
|||||||
irregular,
|
irregular,
|
||||||
teacherChanged,
|
teacherChanged,
|
||||||
event,
|
event,
|
||||||
// Sonder-Lesson-Types (Aufsicht, Sprechstunde, …) — Native-Widget-Renderer,
|
|
||||||
// die den Status noch nicht kennen, fallen still auf den regulären Stil
|
|
||||||
// zurück, sobald der String dort unbekannt ist.
|
|
||||||
duty,
|
duty,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +32,9 @@ abstract class WidgetLesson with _$WidgetLesson {
|
|||||||
String? originalTeacher,
|
String? originalTeacher,
|
||||||
required WidgetLessonStatus status,
|
required WidgetLessonStatus status,
|
||||||
String? customColor,
|
String? customColor,
|
||||||
|
/// Fertig aufgelöste Kachelfarbe als `#RRGGBB`. Native Renderer kennen die
|
||||||
|
/// Paletten nicht — Änderungen daran brauchen so keinen nativen Eingriff.
|
||||||
|
String? color,
|
||||||
@Default(0) int siblingCount,
|
@Default(0) int siblingCount,
|
||||||
}) = _WidgetLesson;
|
}) = _WidgetLesson;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint, type=warning, deprecated_member_use, deprecated_member_use_from_same_package
|
||||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
part of 'widget_data.dart';
|
part of 'widget_data.dart';
|
||||||
@@ -9,6 +9,7 @@ part of 'widget_data.dart';
|
|||||||
// FreezedGenerator
|
// FreezedGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
// dart format off
|
// dart format off
|
||||||
T _$identity<T>(T value) => value;
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
@@ -18,7 +19,9 @@ mixin _$WidgetLesson {
|
|||||||
DateTime get start; DateTime get end; String get subjectShort; String? get subjectLong; String? get room;/// On teacher accounts this carries the class label ("7a") instead of the
|
DateTime get start; DateTime get end; String get subjectShort; String? get subjectLong; String? get room;/// On teacher accounts this carries the class label ("7a") instead of the
|
||||||
/// teacher short name — see `WidgetDataMapper` `showClassInsteadOfTeacher`;
|
/// teacher short name — see `WidgetDataMapper` `showClassInsteadOfTeacher`;
|
||||||
/// [originalTeacher] is null in that case.
|
/// [originalTeacher] is null in that case.
|
||||||
String? get teacher; String? get originalTeacher; WidgetLessonStatus get status; String? get customColor; int get siblingCount;
|
String? get teacher; String? get originalTeacher; WidgetLessonStatus get status; String? get customColor;/// Fertig aufgelöste Kachelfarbe als `#RRGGBB`. Native Renderer kennen die
|
||||||
|
/// Paletten nicht — Änderungen daran brauchen so keinen nativen Eingriff.
|
||||||
|
String? get color; int get siblingCount;
|
||||||
/// Create a copy of WidgetLesson
|
/// Create a copy of WidgetLesson
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -31,16 +34,21 @@ $WidgetLessonCopyWith<WidgetLesson> get copyWith => _$WidgetLessonCopyWithImpl<W
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is WidgetLesson&&(identical(other.start, start) || other.start == start)&&(identical(other.end, end) || other.end == end)&&(identical(other.subjectShort, subjectShort) || other.subjectShort == subjectShort)&&(identical(other.subjectLong, subjectLong) || other.subjectLong == subjectLong)&&(identical(other.room, room) || other.room == room)&&(identical(other.teacher, teacher) || other.teacher == teacher)&&(identical(other.originalTeacher, originalTeacher) || other.originalTeacher == originalTeacher)&&(identical(other.status, status) || other.status == status)&&(identical(other.customColor, customColor) || other.customColor == customColor)&&(identical(other.siblingCount, siblingCount) || other.siblingCount == siblingCount));
|
final _this = this as WidgetLesson;
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is WidgetLesson&&(identical(other.start, _this.start) || other.start == _this.start)&&(identical(other.end, _this.end) || other.end == _this.end)&&(identical(other.subjectShort, _this.subjectShort) || other.subjectShort == _this.subjectShort)&&(identical(other.subjectLong, _this.subjectLong) || other.subjectLong == _this.subjectLong)&&(identical(other.room, _this.room) || other.room == _this.room)&&(identical(other.teacher, _this.teacher) || other.teacher == _this.teacher)&&(identical(other.originalTeacher, _this.originalTeacher) || other.originalTeacher == _this.originalTeacher)&&(identical(other.status, _this.status) || other.status == _this.status)&&(identical(other.customColor, _this.customColor) || other.customColor == _this.customColor)&&(identical(other.color, _this.color) || other.color == _this.color)&&(identical(other.siblingCount, _this.siblingCount) || other.siblingCount == _this.siblingCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,start,end,subjectShort,subjectLong,room,teacher,originalTeacher,status,customColor,siblingCount);
|
int get hashCode {
|
||||||
|
final _this = this as WidgetLesson;
|
||||||
|
return Object.hash(runtimeType,_this.start,_this.end,_this.subjectShort,_this.subjectLong,_this.room,_this.teacher,_this.originalTeacher,_this.status,_this.customColor,_this.color,_this.siblingCount);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'WidgetLesson(start: $start, end: $end, subjectShort: $subjectShort, subjectLong: $subjectLong, room: $room, teacher: $teacher, originalTeacher: $originalTeacher, status: $status, customColor: $customColor, siblingCount: $siblingCount)';
|
final _this = this as WidgetLesson;
|
||||||
|
return 'WidgetLesson(start: ${_this.start}, end: ${_this.end}, subjectShort: ${_this.subjectShort}, subjectLong: ${_this.subjectLong}, room: ${_this.room}, teacher: ${_this.teacher}, originalTeacher: ${_this.originalTeacher}, status: ${_this.status}, customColor: ${_this.customColor}, color: ${_this.color}, siblingCount: ${_this.siblingCount})';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -51,7 +59,7 @@ abstract mixin class $WidgetLessonCopyWith<$Res> {
|
|||||||
factory $WidgetLessonCopyWith(WidgetLesson value, $Res Function(WidgetLesson) _then) = _$WidgetLessonCopyWithImpl;
|
factory $WidgetLessonCopyWith(WidgetLesson value, $Res Function(WidgetLesson) _then) = _$WidgetLessonCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, int siblingCount
|
DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, String? color, int siblingCount
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -68,8 +76,8 @@ class _$WidgetLessonCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of WidgetLesson
|
/// Create a copy of WidgetLesson
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? start = null,Object? end = null,Object? subjectShort = null,Object? subjectLong = freezed,Object? room = freezed,Object? teacher = freezed,Object? originalTeacher = freezed,Object? status = null,Object? customColor = freezed,Object? siblingCount = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? start = null,Object? end = null,Object? subjectShort = null,Object? subjectLong = freezed,Object? room = freezed,Object? teacher = freezed,Object? originalTeacher = freezed,Object? status = null,Object? customColor = freezed,Object? color = freezed,Object? siblingCount = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(WidgetLesson(
|
||||||
start: null == start ? _self.start : start // ignore: cast_nullable_to_non_nullable
|
start: null == start ? _self.start : start // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,end: null == end ? _self.end : end // ignore: cast_nullable_to_non_nullable
|
as DateTime,end: null == end ? _self.end : end // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,subjectShort: null == subjectShort ? _self.subjectShort : subjectShort // ignore: cast_nullable_to_non_nullable
|
as DateTime,subjectShort: null == subjectShort ? _self.subjectShort : subjectShort // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -79,6 +87,7 @@ as String?,teacher: freezed == teacher ? _self.teacher : teacher // ignore: cast
|
|||||||
as String?,originalTeacher: freezed == originalTeacher ? _self.originalTeacher : originalTeacher // ignore: cast_nullable_to_non_nullable
|
as String?,originalTeacher: freezed == originalTeacher ? _self.originalTeacher : originalTeacher // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
as String?,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||||
as WidgetLessonStatus,customColor: freezed == customColor ? _self.customColor : customColor // ignore: cast_nullable_to_non_nullable
|
as WidgetLessonStatus,customColor: freezed == customColor ? _self.customColor : customColor // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,color: freezed == color ? _self.color : color // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,siblingCount: null == siblingCount ? _self.siblingCount : siblingCount // ignore: cast_nullable_to_non_nullable
|
as String?,siblingCount: null == siblingCount ? _self.siblingCount : siblingCount // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,
|
||||||
));
|
));
|
||||||
@@ -165,10 +174,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, int siblingCount)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, String? color, int siblingCount)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _WidgetLesson() when $default != null:
|
case _WidgetLesson() when $default != null:
|
||||||
return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that.room,_that.teacher,_that.originalTeacher,_that.status,_that.customColor,_that.siblingCount);case _:
|
return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that.room,_that.teacher,_that.originalTeacher,_that.status,_that.customColor,_that.color,_that.siblingCount);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -186,10 +195,10 @@ return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, int siblingCount) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, String? color, int siblingCount) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _WidgetLesson():
|
case _WidgetLesson():
|
||||||
return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that.room,_that.teacher,_that.originalTeacher,_that.status,_that.customColor,_that.siblingCount);case _:
|
return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that.room,_that.teacher,_that.originalTeacher,_that.status,_that.customColor,_that.color,_that.siblingCount);case _:
|
||||||
throw StateError('Unexpected subclass');
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -206,10 +215,10 @@ return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, int siblingCount)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, String? color, int siblingCount)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _WidgetLesson() when $default != null:
|
case _WidgetLesson() when $default != null:
|
||||||
return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that.room,_that.teacher,_that.originalTeacher,_that.status,_that.customColor,_that.siblingCount);case _:
|
return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that.room,_that.teacher,_that.originalTeacher,_that.status,_that.customColor,_that.color,_that.siblingCount);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -221,7 +230,7 @@ return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _WidgetLesson implements WidgetLesson {
|
class _WidgetLesson implements WidgetLesson {
|
||||||
const _WidgetLesson({required this.start, required this.end, required this.subjectShort, this.subjectLong, this.room, this.teacher, this.originalTeacher, required this.status, this.customColor, this.siblingCount = 0});
|
const _WidgetLesson({required this.start, required this.end, required this.subjectShort, this.subjectLong, this.room, this.teacher, this.originalTeacher, required this.status, this.customColor, this.color, this.siblingCount = 0});
|
||||||
factory _WidgetLesson.fromJson(Map<String, dynamic> json) => _$WidgetLessonFromJson(json);
|
factory _WidgetLesson.fromJson(Map<String, dynamic> json) => _$WidgetLessonFromJson(json);
|
||||||
|
|
||||||
@override final DateTime start;
|
@override final DateTime start;
|
||||||
@@ -236,6 +245,9 @@ class _WidgetLesson implements WidgetLesson {
|
|||||||
@override final String? originalTeacher;
|
@override final String? originalTeacher;
|
||||||
@override final WidgetLessonStatus status;
|
@override final WidgetLessonStatus status;
|
||||||
@override final String? customColor;
|
@override final String? customColor;
|
||||||
|
/// Fertig aufgelöste Kachelfarbe als `#RRGGBB`. Native Renderer kennen die
|
||||||
|
/// Paletten nicht — Änderungen daran brauchen so keinen nativen Eingriff.
|
||||||
|
@override final String? color;
|
||||||
@override@JsonKey() final int siblingCount;
|
@override@JsonKey() final int siblingCount;
|
||||||
|
|
||||||
/// Create a copy of WidgetLesson
|
/// Create a copy of WidgetLesson
|
||||||
@@ -251,16 +263,18 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WidgetLesson&&(identical(other.start, start) || other.start == start)&&(identical(other.end, end) || other.end == end)&&(identical(other.subjectShort, subjectShort) || other.subjectShort == subjectShort)&&(identical(other.subjectLong, subjectLong) || other.subjectLong == subjectLong)&&(identical(other.room, room) || other.room == room)&&(identical(other.teacher, teacher) || other.teacher == teacher)&&(identical(other.originalTeacher, originalTeacher) || other.originalTeacher == originalTeacher)&&(identical(other.status, status) || other.status == status)&&(identical(other.customColor, customColor) || other.customColor == customColor)&&(identical(other.siblingCount, siblingCount) || other.siblingCount == siblingCount));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WidgetLesson&&(identical(other.start, start) || other.start == start)&&(identical(other.end, end) || other.end == end)&&(identical(other.subjectShort, subjectShort) || other.subjectShort == subjectShort)&&(identical(other.subjectLong, subjectLong) || other.subjectLong == subjectLong)&&(identical(other.room, room) || other.room == room)&&(identical(other.teacher, teacher) || other.teacher == teacher)&&(identical(other.originalTeacher, originalTeacher) || other.originalTeacher == originalTeacher)&&(identical(other.status, status) || other.status == status)&&(identical(other.customColor, customColor) || other.customColor == customColor)&&(identical(other.color, color) || other.color == color)&&(identical(other.siblingCount, siblingCount) || other.siblingCount == siblingCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,start,end,subjectShort,subjectLong,room,teacher,originalTeacher,status,customColor,siblingCount);
|
int get hashCode {
|
||||||
|
return Object.hash(runtimeType,start,end,subjectShort,subjectLong,room,teacher,originalTeacher,status,customColor,color,siblingCount);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'WidgetLesson(start: $start, end: $end, subjectShort: $subjectShort, subjectLong: $subjectLong, room: $room, teacher: $teacher, originalTeacher: $originalTeacher, status: $status, customColor: $customColor, siblingCount: $siblingCount)';
|
return 'WidgetLesson(start: $start, end: $end, subjectShort: $subjectShort, subjectLong: $subjectLong, room: $room, teacher: $teacher, originalTeacher: $originalTeacher, status: $status, customColor: $customColor, color: $color, siblingCount: $siblingCount)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -271,7 +285,7 @@ abstract mixin class _$WidgetLessonCopyWith<$Res> implements $WidgetLessonCopyWi
|
|||||||
factory _$WidgetLessonCopyWith(_WidgetLesson value, $Res Function(_WidgetLesson) _then) = __$WidgetLessonCopyWithImpl;
|
factory _$WidgetLessonCopyWith(_WidgetLesson value, $Res Function(_WidgetLesson) _then) = __$WidgetLessonCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, int siblingCount
|
DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, String? color, int siblingCount
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -288,7 +302,7 @@ class __$WidgetLessonCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of WidgetLesson
|
/// Create a copy of WidgetLesson
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? start = null,Object? end = null,Object? subjectShort = null,Object? subjectLong = freezed,Object? room = freezed,Object? teacher = freezed,Object? originalTeacher = freezed,Object? status = null,Object? customColor = freezed,Object? siblingCount = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? start = null,Object? end = null,Object? subjectShort = null,Object? subjectLong = freezed,Object? room = freezed,Object? teacher = freezed,Object? originalTeacher = freezed,Object? status = null,Object? customColor = freezed,Object? color = freezed,Object? siblingCount = null,}) {
|
||||||
return _then(_WidgetLesson(
|
return _then(_WidgetLesson(
|
||||||
start: null == start ? _self.start : start // ignore: cast_nullable_to_non_nullable
|
start: null == start ? _self.start : start // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,end: null == end ? _self.end : end // ignore: cast_nullable_to_non_nullable
|
as DateTime,end: null == end ? _self.end : end // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -299,6 +313,7 @@ as String?,teacher: freezed == teacher ? _self.teacher : teacher // ignore: cast
|
|||||||
as String?,originalTeacher: freezed == originalTeacher ? _self.originalTeacher : originalTeacher // ignore: cast_nullable_to_non_nullable
|
as String?,originalTeacher: freezed == originalTeacher ? _self.originalTeacher : originalTeacher // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
as String?,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||||
as WidgetLessonStatus,customColor: freezed == customColor ? _self.customColor : customColor // ignore: cast_nullable_to_non_nullable
|
as WidgetLessonStatus,customColor: freezed == customColor ? _self.customColor : customColor // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,color: freezed == color ? _self.color : color // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,siblingCount: null == siblingCount ? _self.siblingCount : siblingCount // ignore: cast_nullable_to_non_nullable
|
as String?,siblingCount: null == siblingCount ? _self.siblingCount : siblingCount // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,
|
||||||
));
|
));
|
||||||
@@ -332,16 +347,21 @@ $WidgetPeriodCopyWith<WidgetPeriod> get copyWith => _$WidgetPeriodCopyWithImpl<W
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is WidgetPeriod&&(identical(other.name, name) || other.name == name)&&(identical(other.startMinutes, startMinutes) || other.startMinutes == startMinutes)&&(identical(other.endMinutes, endMinutes) || other.endMinutes == endMinutes)&&(identical(other.virtualStartMinutes, virtualStartMinutes) || other.virtualStartMinutes == virtualStartMinutes)&&(identical(other.virtualEndMinutes, virtualEndMinutes) || other.virtualEndMinutes == virtualEndMinutes));
|
final _this = this as WidgetPeriod;
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is WidgetPeriod&&(identical(other.name, _this.name) || other.name == _this.name)&&(identical(other.startMinutes, _this.startMinutes) || other.startMinutes == _this.startMinutes)&&(identical(other.endMinutes, _this.endMinutes) || other.endMinutes == _this.endMinutes)&&(identical(other.virtualStartMinutes, _this.virtualStartMinutes) || other.virtualStartMinutes == _this.virtualStartMinutes)&&(identical(other.virtualEndMinutes, _this.virtualEndMinutes) || other.virtualEndMinutes == _this.virtualEndMinutes));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,name,startMinutes,endMinutes,virtualStartMinutes,virtualEndMinutes);
|
int get hashCode {
|
||||||
|
final _this = this as WidgetPeriod;
|
||||||
|
return Object.hash(runtimeType,_this.name,_this.startMinutes,_this.endMinutes,_this.virtualStartMinutes,_this.virtualEndMinutes);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'WidgetPeriod(name: $name, startMinutes: $startMinutes, endMinutes: $endMinutes, virtualStartMinutes: $virtualStartMinutes, virtualEndMinutes: $virtualEndMinutes)';
|
final _this = this as WidgetPeriod;
|
||||||
|
return 'WidgetPeriod(name: ${_this.name}, startMinutes: ${_this.startMinutes}, endMinutes: ${_this.endMinutes}, virtualStartMinutes: ${_this.virtualStartMinutes}, virtualEndMinutes: ${_this.virtualEndMinutes})';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -370,7 +390,7 @@ class _$WidgetPeriodCopyWithImpl<$Res>
|
|||||||
/// Create a copy of WidgetPeriod
|
/// Create a copy of WidgetPeriod
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? startMinutes = null,Object? endMinutes = null,Object? virtualStartMinutes = null,Object? virtualEndMinutes = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? startMinutes = null,Object? endMinutes = null,Object? virtualStartMinutes = null,Object? virtualEndMinutes = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(WidgetPeriod(
|
||||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
as String,startMinutes: null == startMinutes ? _self.startMinutes : startMinutes // ignore: cast_nullable_to_non_nullable
|
as String,startMinutes: null == startMinutes ? _self.startMinutes : startMinutes // ignore: cast_nullable_to_non_nullable
|
||||||
as int,endMinutes: null == endMinutes ? _self.endMinutes : endMinutes // ignore: cast_nullable_to_non_nullable
|
as int,endMinutes: null == endMinutes ? _self.endMinutes : endMinutes // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -552,7 +572,9 @@ bool operator ==(Object other) {
|
|||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,name,startMinutes,endMinutes,virtualStartMinutes,virtualEndMinutes);
|
int get hashCode {
|
||||||
|
return Object.hash(runtimeType,name,startMinutes,endMinutes,virtualStartMinutes,virtualEndMinutes);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@@ -615,16 +637,21 @@ $WidgetDayInfoCopyWith<WidgetDayInfo> get copyWith => _$WidgetDayInfoCopyWithImp
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is WidgetDayInfo&&(identical(other.date, date) || other.date == date)&&(identical(other.isHoliday, isHoliday) || other.isHoliday == isHoliday)&&(identical(other.holidayName, holidayName) || other.holidayName == holidayName));
|
final _this = this as WidgetDayInfo;
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is WidgetDayInfo&&(identical(other.date, _this.date) || other.date == _this.date)&&(identical(other.isHoliday, _this.isHoliday) || other.isHoliday == _this.isHoliday)&&(identical(other.holidayName, _this.holidayName) || other.holidayName == _this.holidayName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,date,isHoliday,holidayName);
|
int get hashCode {
|
||||||
|
final _this = this as WidgetDayInfo;
|
||||||
|
return Object.hash(runtimeType,_this.date,_this.isHoliday,_this.holidayName);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'WidgetDayInfo(date: $date, isHoliday: $isHoliday, holidayName: $holidayName)';
|
final _this = this as WidgetDayInfo;
|
||||||
|
return 'WidgetDayInfo(date: ${_this.date}, isHoliday: ${_this.isHoliday}, holidayName: ${_this.holidayName})';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -653,7 +680,7 @@ class _$WidgetDayInfoCopyWithImpl<$Res>
|
|||||||
/// Create a copy of WidgetDayInfo
|
/// Create a copy of WidgetDayInfo
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? date = null,Object? isHoliday = null,Object? holidayName = freezed,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? date = null,Object? isHoliday = null,Object? holidayName = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(WidgetDayInfo(
|
||||||
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
|
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,isHoliday: null == isHoliday ? _self.isHoliday : isHoliday // ignore: cast_nullable_to_non_nullable
|
as DateTime,isHoliday: null == isHoliday ? _self.isHoliday : isHoliday // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,holidayName: freezed == holidayName ? _self.holidayName : holidayName // ignore: cast_nullable_to_non_nullable
|
as bool,holidayName: freezed == holidayName ? _self.holidayName : holidayName // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -823,7 +850,9 @@ bool operator ==(Object other) {
|
|||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,date,isHoliday,holidayName);
|
int get hashCode {
|
||||||
|
return Object.hash(runtimeType,date,isHoliday,holidayName);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@@ -888,16 +917,21 @@ $WidgetTimetableDataCopyWith<WidgetTimetableData> get copyWith => _$WidgetTimeta
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is WidgetTimetableData&&(identical(other.fetchedAt, fetchedAt) || other.fetchedAt == fetchedAt)&&(identical(other.anchorDate, anchorDate) || other.anchorDate == anchorDate)&&const DeepCollectionEquality().equals(other.lessons, lessons)&&const DeepCollectionEquality().equals(other.periods, periods)&&(identical(other.isHoliday, isHoliday) || other.isHoliday == isHoliday)&&(identical(other.holidayName, holidayName) || other.holidayName == holidayName)&&const DeepCollectionEquality().equals(other.days, days));
|
final _this = this as WidgetTimetableData;
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is WidgetTimetableData&&(identical(other.fetchedAt, _this.fetchedAt) || other.fetchedAt == _this.fetchedAt)&&(identical(other.anchorDate, _this.anchorDate) || other.anchorDate == _this.anchorDate)&&const DeepCollectionEquality().equals(other.lessons, _this.lessons)&&const DeepCollectionEquality().equals(other.periods, _this.periods)&&(identical(other.isHoliday, _this.isHoliday) || other.isHoliday == _this.isHoliday)&&(identical(other.holidayName, _this.holidayName) || other.holidayName == _this.holidayName)&&const DeepCollectionEquality().equals(other.days, _this.days));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,fetchedAt,anchorDate,const DeepCollectionEquality().hash(lessons),const DeepCollectionEquality().hash(periods),isHoliday,holidayName,const DeepCollectionEquality().hash(days));
|
int get hashCode {
|
||||||
|
final _this = this as WidgetTimetableData;
|
||||||
|
return Object.hash(runtimeType,_this.fetchedAt,_this.anchorDate,const DeepCollectionEquality().hash(_this.lessons),const DeepCollectionEquality().hash(_this.periods),_this.isHoliday,_this.holidayName,const DeepCollectionEquality().hash(_this.days));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'WidgetTimetableData(fetchedAt: $fetchedAt, anchorDate: $anchorDate, lessons: $lessons, periods: $periods, isHoliday: $isHoliday, holidayName: $holidayName, days: $days)';
|
final _this = this as WidgetTimetableData;
|
||||||
|
return 'WidgetTimetableData(fetchedAt: ${_this.fetchedAt}, anchorDate: ${_this.anchorDate}, lessons: ${_this.lessons}, periods: ${_this.periods}, isHoliday: ${_this.isHoliday}, holidayName: ${_this.holidayName}, days: ${_this.days})';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -926,7 +960,7 @@ class _$WidgetTimetableDataCopyWithImpl<$Res>
|
|||||||
/// Create a copy of WidgetTimetableData
|
/// Create a copy of WidgetTimetableData
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? fetchedAt = null,Object? anchorDate = null,Object? lessons = null,Object? periods = null,Object? isHoliday = null,Object? holidayName = freezed,Object? days = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? fetchedAt = null,Object? anchorDate = null,Object? lessons = null,Object? periods = null,Object? isHoliday = null,Object? holidayName = freezed,Object? days = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(WidgetTimetableData(
|
||||||
fetchedAt: null == fetchedAt ? _self.fetchedAt : fetchedAt // ignore: cast_nullable_to_non_nullable
|
fetchedAt: null == fetchedAt ? _self.fetchedAt : fetchedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,anchorDate: null == anchorDate ? _self.anchorDate : anchorDate // ignore: cast_nullable_to_non_nullable
|
as DateTime,anchorDate: null == anchorDate ? _self.anchorDate : anchorDate // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,lessons: null == lessons ? _self.lessons : lessons // ignore: cast_nullable_to_non_nullable
|
as DateTime,lessons: null == lessons ? _self.lessons : lessons // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -1075,7 +1109,7 @@ return $default(_that.fetchedAt,_that.anchorDate,_that.lessons,_that.periods,_th
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _WidgetTimetableData implements WidgetTimetableData {
|
class _WidgetTimetableData implements WidgetTimetableData {
|
||||||
const _WidgetTimetableData({required this.fetchedAt, required this.anchorDate, required final List<WidgetLesson> lessons, final List<WidgetPeriod> periods = const <WidgetPeriod>[], this.isHoliday = false, this.holidayName, final List<WidgetDayInfo> days = const <WidgetDayInfo>[]}): _lessons = lessons,_periods = periods,_days = days;
|
const _WidgetTimetableData({required this.fetchedAt, required this.anchorDate, required List<WidgetLesson> lessons, List<WidgetPeriod> periods = const <WidgetPeriod>[], this.isHoliday = false, this.holidayName, List<WidgetDayInfo> days = const <WidgetDayInfo>[]}): _lessons = lessons,_periods = periods,_days = days;
|
||||||
factory _WidgetTimetableData.fromJson(Map<String, dynamic> json) => _$WidgetTimetableDataFromJson(json);
|
factory _WidgetTimetableData.fromJson(Map<String, dynamic> json) => _$WidgetTimetableDataFromJson(json);
|
||||||
|
|
||||||
@override final DateTime fetchedAt;
|
@override final DateTime fetchedAt;
|
||||||
@@ -1122,12 +1156,14 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WidgetTimetableData&&(identical(other.fetchedAt, fetchedAt) || other.fetchedAt == fetchedAt)&&(identical(other.anchorDate, anchorDate) || other.anchorDate == anchorDate)&&const DeepCollectionEquality().equals(other._lessons, _lessons)&&const DeepCollectionEquality().equals(other._periods, _periods)&&(identical(other.isHoliday, isHoliday) || other.isHoliday == isHoliday)&&(identical(other.holidayName, holidayName) || other.holidayName == holidayName)&&const DeepCollectionEquality().equals(other._days, _days));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WidgetTimetableData&&(identical(other.fetchedAt, fetchedAt) || other.fetchedAt == fetchedAt)&&(identical(other.anchorDate, anchorDate) || other.anchorDate == anchorDate)&&const DeepCollectionEquality().equals(other.lessons, _lessons)&&const DeepCollectionEquality().equals(other.periods, _periods)&&(identical(other.isHoliday, isHoliday) || other.isHoliday == isHoliday)&&(identical(other.holidayName, holidayName) || other.holidayName == holidayName)&&const DeepCollectionEquality().equals(other.days, _days));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,fetchedAt,anchorDate,const DeepCollectionEquality().hash(_lessons),const DeepCollectionEquality().hash(_periods),isHoliday,holidayName,const DeepCollectionEquality().hash(_days));
|
int get hashCode {
|
||||||
|
return Object.hash(runtimeType,fetchedAt,anchorDate,const DeepCollectionEquality().hash(_lessons),const DeepCollectionEquality().hash(_periods),isHoliday,holidayName,const DeepCollectionEquality().hash(_days));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ _WidgetLesson _$WidgetLessonFromJson(Map<String, dynamic> json) =>
|
|||||||
originalTeacher: json['originalTeacher'] as String?,
|
originalTeacher: json['originalTeacher'] as String?,
|
||||||
status: $enumDecode(_$WidgetLessonStatusEnumMap, json['status']),
|
status: $enumDecode(_$WidgetLessonStatusEnumMap, json['status']),
|
||||||
customColor: json['customColor'] as String?,
|
customColor: json['customColor'] as String?,
|
||||||
|
color: json['color'] as String?,
|
||||||
siblingCount: (json['siblingCount'] as num?)?.toInt() ?? 0,
|
siblingCount: (json['siblingCount'] as num?)?.toInt() ?? 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ Map<String, dynamic> _$WidgetLessonToJson(_WidgetLesson instance) =>
|
|||||||
'originalTeacher': instance.originalTeacher,
|
'originalTeacher': instance.originalTeacher,
|
||||||
'status': _$WidgetLessonStatusEnumMap[instance.status]!,
|
'status': _$WidgetLessonStatusEnumMap[instance.status]!,
|
||||||
'customColor': instance.customColor,
|
'customColor': instance.customColor,
|
||||||
|
'color': instance.color,
|
||||||
'siblingCount': instance.siblingCount,
|
'siblingCount': instance.siblingCount,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import '../api/marianumconnect/queries/timetable_get_holidays/timetable_get_holidays_response.dart';
|
import '../api/marianumconnect/queries/timetable_get_holidays/timetable_get_holidays_response.dart';
|
||||||
import '../api/marianumconnect/queries/timetable_get_rooms/timetable_get_rooms_response.dart';
|
import '../api/marianumconnect/queries/timetable_get_rooms/timetable_get_rooms_response.dart';
|
||||||
@@ -9,11 +10,13 @@ import '../api/mhsl/custom_timetable_event/custom_timetable_event.dart';
|
|||||||
import '../api/mhsl/custom_timetable_event/get/get_custom_timetable_event_response.dart';
|
import '../api/mhsl/custom_timetable_event/get/get_custom_timetable_event_response.dart';
|
||||||
import '../extensions/date_time.dart';
|
import '../extensions/date_time.dart';
|
||||||
import '../utils/recurrence_occurrences.dart';
|
import '../utils/recurrence_occurrences.dart';
|
||||||
|
import '../view/pages/timetable/data/lesson_color.dart';
|
||||||
import '../view/pages/timetable/data/lesson_labels.dart';
|
import '../view/pages/timetable/data/lesson_labels.dart';
|
||||||
import '../view/pages/timetable/data/lesson_merger.dart';
|
import '../view/pages/timetable/data/lesson_merger.dart';
|
||||||
import '../view/pages/timetable/data/lesson_period_schedule.dart';
|
import '../view/pages/timetable/data/lesson_period_schedule.dart';
|
||||||
import '../view/pages/timetable/data/lesson_status.dart';
|
import '../view/pages/timetable/data/lesson_status.dart';
|
||||||
import '../view/pages/timetable/data/lesson_type_label.dart';
|
import '../view/pages/timetable/data/lesson_type_label.dart';
|
||||||
|
import '../view/pages/timetable/data/subject_color_palette.dart';
|
||||||
import 'widget_data.dart';
|
import 'widget_data.dart';
|
||||||
|
|
||||||
class WidgetDataMapper {
|
class WidgetDataMapper {
|
||||||
@@ -322,22 +325,26 @@ class WidgetDataMapper {
|
|||||||
}) {
|
}) {
|
||||||
final start = lesson.startDateTime;
|
final start = lesson.startDateTime;
|
||||||
final end = lesson.endDateTime;
|
final end = lesson.endDateTime;
|
||||||
final status = _mapStatus(
|
|
||||||
LessonStatusClassifier.classify(lesson, start, end, now),
|
|
||||||
);
|
|
||||||
final subjectShortRaw = lesson.subjects.firstOrNull?.trim() ?? '';
|
final subjectShortRaw = lesson.subjects.firstOrNull?.trim() ?? '';
|
||||||
// Webuntis sometimes ships subject-less entries (Wandertag etc.). Prefer
|
// Gleiche Einstufung wie TimetableAppointmentFactory, damit Aufsicht,
|
||||||
// their free text, else "Event" so the tile isn't just a dash.
|
// Prüfung & Co. im Widget dieselbe Farbe tragen wie in der App.
|
||||||
|
final lessonStatus = LessonStatusClassifier.classify(
|
||||||
|
lesson,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
now,
|
||||||
|
isEvent: subjectShortRaw.isEmpty && lesson.lessonType == 'LESSON',
|
||||||
|
isDuty: lesson.lessonType != 'LESSON',
|
||||||
|
);
|
||||||
final subjectShort = subjectShortRaw.isEmpty
|
final subjectShort = subjectShortRaw.isEmpty
|
||||||
? LessonTypeLabel.entryText(lesson) ?? LessonTypeLabel.fallback
|
? LessonTypeLabel.forEntry(lesson)
|
||||||
: subjectShortRaw;
|
: subjectShortRaw;
|
||||||
String? subjectLong;
|
final subject = subjects == null || subjectShortRaw.isEmpty
|
||||||
if (subjects != null && subjectShortRaw.isNotEmpty) {
|
? null
|
||||||
subjectLong = subjects.result
|
: subjects.result
|
||||||
.where((s) => s.shortName == subjectShortRaw)
|
.where((s) => s.shortName == subjectShortRaw)
|
||||||
.firstOrNull
|
.firstOrNull;
|
||||||
?.longName;
|
final subjectLong = subject?.longName;
|
||||||
}
|
|
||||||
final roomShort = lesson.rooms.firstOrNull;
|
final roomShort = lesson.rooms.firstOrNull;
|
||||||
var roomName = roomShort;
|
var roomName = roomShort;
|
||||||
if (rooms != null && roomShort != null) {
|
if (rooms != null && roomShort != null) {
|
||||||
@@ -365,10 +372,29 @@ class WidgetDataMapper {
|
|||||||
room: roomName,
|
room: roomName,
|
||||||
teacher: teacherName,
|
teacher: teacherName,
|
||||||
originalTeacher: originalTeacher,
|
originalTeacher: originalTeacher,
|
||||||
status: status,
|
status: _mapStatus(lessonStatus),
|
||||||
|
color: _hex(_lessonColor(lessonStatus, subject?.color)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Das Widget kennt keine Zeitzustände: laufende Stunden bekommen dieselbe
|
||||||
|
/// Farbe wie reguläre, sonst würde die Hervorhebung zwischen zwei
|
||||||
|
/// Aktualisierungen veralten.
|
||||||
|
static Color _lessonColor(LessonStatus status, String? subjectColor) {
|
||||||
|
final isRegular =
|
||||||
|
status == LessonStatus.regular ||
|
||||||
|
status == LessonStatus.ongoing ||
|
||||||
|
status == LessonStatus.past;
|
||||||
|
if (!isRegular) return LessonColor.forStatus(status);
|
||||||
|
return SubjectColorPalette.colorFromName(subjectColor) ??
|
||||||
|
LessonColor.regular;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _hex(Color color) {
|
||||||
|
final rgb = color.toARGB32() & 0xFFFFFF;
|
||||||
|
return '#${rgb.toRadixString(16).padLeft(6, '0').toUpperCase()}';
|
||||||
|
}
|
||||||
|
|
||||||
static WidgetLessonStatus _mapStatus(LessonStatus status) {
|
static WidgetLessonStatus _mapStatus(LessonStatus status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case LessonStatus.cancelled:
|
case LessonStatus.cancelled:
|
||||||
@@ -480,6 +506,12 @@ class WidgetDataMapper {
|
|||||||
subjectLong: title.isEmpty ? null : title,
|
subjectLong: title.isEmpty ? null : title,
|
||||||
status: WidgetLessonStatus.event,
|
status: WidgetLessonStatus.event,
|
||||||
customColor: event.color,
|
customColor: event.color,
|
||||||
|
color: _hex(
|
||||||
|
SubjectColorPalette.colorFromName(event.color) ??
|
||||||
|
SubjectColorPalette.displayOf(
|
||||||
|
SubjectColorPalette.defaultColor,
|
||||||
|
).color,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final isAllDay = duration == Duration.zero && _isMidnight(event.startDate);
|
final isAllDay = duration == Duration.zero && _isMidnight(event.startDate);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:marianum_mobile/api/marianumconnect/queries/timetable_get_holidays/timetable_get_holidays_response.dart';
|
import 'package:marianum_mobile/api/marianumconnect/queries/timetable_get_holidays/timetable_get_holidays_response.dart';
|
||||||
|
import 'package:marianum_mobile/api/marianumconnect/queries/timetable_get_subjects/timetable_get_subjects_response.dart';
|
||||||
import 'package:marianum_mobile/api/marianumconnect/queries/timetable_get_week/timetable_get_week_response.dart';
|
import 'package:marianum_mobile/api/marianumconnect/queries/timetable_get_week/timetable_get_week_response.dart';
|
||||||
import 'package:marianum_mobile/api/mhsl/custom_timetable_event/custom_timetable_event.dart';
|
import 'package:marianum_mobile/api/mhsl/custom_timetable_event/custom_timetable_event.dart';
|
||||||
import 'package:marianum_mobile/api/mhsl/custom_timetable_event/get/get_custom_timetable_event_response.dart';
|
import 'package:marianum_mobile/api/mhsl/custom_timetable_event/get/get_custom_timetable_event_response.dart';
|
||||||
@@ -33,6 +34,8 @@ McTimetableEntry _lesson({
|
|||||||
String? teacherName,
|
String? teacherName,
|
||||||
String? teacherOrgname,
|
String? teacherOrgname,
|
||||||
String? substText,
|
String? substText,
|
||||||
|
String lessonType = 'LESSON',
|
||||||
|
List<String> classNames = const [],
|
||||||
}) => McTimetableEntry(
|
}) => McTimetableEntry(
|
||||||
id: 1,
|
id: 1,
|
||||||
date: date,
|
date: date,
|
||||||
@@ -50,8 +53,8 @@ McTimetableEntry _lesson({
|
|||||||
]
|
]
|
||||||
: const [],
|
: const [],
|
||||||
rooms: room != null ? [room] : const [],
|
rooms: room != null ? [room] : const [],
|
||||||
classNames: const [],
|
classNames: classNames,
|
||||||
lessonType: 'LESSON',
|
lessonType: lessonType,
|
||||||
status: status,
|
status: status,
|
||||||
substitutionText: substText,
|
substitutionText: substText,
|
||||||
lessonText: null,
|
lessonText: null,
|
||||||
@@ -117,8 +120,18 @@ void main() {
|
|||||||
|
|
||||||
test('only includes lessons on the anchor day', () {
|
test('only includes lessons on the anchor day', () {
|
||||||
final lessons = [
|
final lessons = [
|
||||||
_lesson(date: DateTime(2026, 5, 6), startHhmm: 800, endHhmm: 845, subjectName: 'MA'),
|
_lesson(
|
||||||
_lesson(date: DateTime(2026, 5, 7), startHhmm: 800, endHhmm: 845, subjectName: 'EN'),
|
date: DateTime(2026, 5, 6),
|
||||||
|
startHhmm: 800,
|
||||||
|
endHhmm: 845,
|
||||||
|
subjectName: 'MA',
|
||||||
|
),
|
||||||
|
_lesson(
|
||||||
|
date: DateTime(2026, 5, 7),
|
||||||
|
startHhmm: 800,
|
||||||
|
endHhmm: 845,
|
||||||
|
subjectName: 'EN',
|
||||||
|
),
|
||||||
];
|
];
|
||||||
final data = WidgetDataMapper.buildDayData(
|
final data = WidgetDataMapper.buildDayData(
|
||||||
now: now,
|
now: now,
|
||||||
@@ -164,14 +177,11 @@ void main() {
|
|||||||
rooms: null,
|
rooms: null,
|
||||||
holidays: null,
|
holidays: null,
|
||||||
);
|
);
|
||||||
expect(
|
expect(data.lessons.map((l) => l.status).toList(), [
|
||||||
data.lessons.map((l) => l.status).toList(),
|
|
||||||
[
|
|
||||||
WidgetLessonStatus.cancelled,
|
WidgetLessonStatus.cancelled,
|
||||||
WidgetLessonStatus.irregular,
|
WidgetLessonStatus.irregular,
|
||||||
WidgetLessonStatus.teacherChanged,
|
WidgetLessonStatus.teacherChanged,
|
||||||
],
|
]);
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('marks day as holiday when in holiday range', () {
|
test('marks day as holiday when in holiday range', () {
|
||||||
@@ -224,10 +234,11 @@ void main() {
|
|||||||
rooms: null,
|
rooms: null,
|
||||||
holidays: null,
|
holidays: null,
|
||||||
);
|
);
|
||||||
expect(
|
expect(data.lessons.map((l) => l.subjectShort).toList(), [
|
||||||
data.lessons.map((l) => l.subjectShort).toList(),
|
'MA',
|
||||||
['MA', 'EN', 'BIO'],
|
'EN',
|
||||||
);
|
'BIO',
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -236,9 +247,24 @@ void main() {
|
|||||||
|
|
||||||
test('long event bumps every regular lesson it covers', () {
|
test('long event bumps every regular lesson it covers', () {
|
||||||
final lessons = [
|
final lessons = [
|
||||||
_lesson(date: DateTime(2026, 5, 6), startHhmm: 800, endHhmm: 845, subjectName: 'MA'),
|
_lesson(
|
||||||
_lesson(date: DateTime(2026, 5, 6), startHhmm: 900, endHhmm: 945, subjectName: 'EN'),
|
date: DateTime(2026, 5, 6),
|
||||||
_lesson(date: DateTime(2026, 5, 6), startHhmm: 1000, endHhmm: 1045, subjectName: 'BIO'),
|
startHhmm: 800,
|
||||||
|
endHhmm: 845,
|
||||||
|
subjectName: 'MA',
|
||||||
|
),
|
||||||
|
_lesson(
|
||||||
|
date: DateTime(2026, 5, 6),
|
||||||
|
startHhmm: 900,
|
||||||
|
endHhmm: 945,
|
||||||
|
subjectName: 'EN',
|
||||||
|
),
|
||||||
|
_lesson(
|
||||||
|
date: DateTime(2026, 5, 6),
|
||||||
|
startHhmm: 1000,
|
||||||
|
endHhmm: 1045,
|
||||||
|
subjectName: 'BIO',
|
||||||
|
),
|
||||||
];
|
];
|
||||||
final events = GetCustomTimetableEventResponse([
|
final events = GetCustomTimetableEventResponse([
|
||||||
_event(
|
_event(
|
||||||
@@ -264,8 +290,18 @@ void main() {
|
|||||||
|
|
||||||
test('event + same-slot duplicate regular: kept lesson shows +2', () {
|
test('event + same-slot duplicate regular: kept lesson shows +2', () {
|
||||||
final lessons = [
|
final lessons = [
|
||||||
_lesson(date: DateTime(2026, 5, 6), startHhmm: 900, endHhmm: 945, subjectName: 'EN'),
|
_lesson(
|
||||||
_lesson(date: DateTime(2026, 5, 6), startHhmm: 900, endHhmm: 945, subjectName: 'MA'),
|
date: DateTime(2026, 5, 6),
|
||||||
|
startHhmm: 900,
|
||||||
|
endHhmm: 945,
|
||||||
|
subjectName: 'EN',
|
||||||
|
),
|
||||||
|
_lesson(
|
||||||
|
date: DateTime(2026, 5, 6),
|
||||||
|
startHhmm: 900,
|
||||||
|
endHhmm: 945,
|
||||||
|
subjectName: 'MA',
|
||||||
|
),
|
||||||
];
|
];
|
||||||
final events = GetCustomTimetableEventResponse([
|
final events = GetCustomTimetableEventResponse([
|
||||||
_event(
|
_event(
|
||||||
@@ -315,7 +351,12 @@ void main() {
|
|||||||
|
|
||||||
test('two events covering the same regular lesson bump it twice', () {
|
test('two events covering the same regular lesson bump it twice', () {
|
||||||
final lessons = [
|
final lessons = [
|
||||||
_lesson(date: DateTime(2026, 5, 6), startHhmm: 900, endHhmm: 945, subjectName: 'EN'),
|
_lesson(
|
||||||
|
date: DateTime(2026, 5, 6),
|
||||||
|
startHhmm: 900,
|
||||||
|
endHhmm: 945,
|
||||||
|
subjectName: 'EN',
|
||||||
|
),
|
||||||
];
|
];
|
||||||
final events = GetCustomTimetableEventResponse([
|
final events = GetCustomTimetableEventResponse([
|
||||||
_event(
|
_event(
|
||||||
@@ -361,16 +402,198 @@ void main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('teacher plan', () {
|
||||||
|
final now = DateTime(2026, 5, 6, 7);
|
||||||
|
final day = DateTime(2026, 5, 6);
|
||||||
|
|
||||||
|
WidgetLesson single(McTimetableEntry lesson) =>
|
||||||
|
WidgetDataMapper.buildDayData(
|
||||||
|
now: now,
|
||||||
|
lessons: [lesson],
|
||||||
|
subjects: null,
|
||||||
|
rooms: null,
|
||||||
|
holidays: null,
|
||||||
|
showClassInsteadOfTeacher: true,
|
||||||
|
).lessons.single;
|
||||||
|
|
||||||
|
test('shows the classes in place of the teacher', () {
|
||||||
|
final lesson = single(
|
||||||
|
_lesson(
|
||||||
|
date: day,
|
||||||
|
startHhmm: 755,
|
||||||
|
endHhmm: 840,
|
||||||
|
subjectName: 'MA',
|
||||||
|
teacherName: 'MUE',
|
||||||
|
teacherOrgname: 'KUN',
|
||||||
|
classNames: const ['10a', '10b'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(lesson.teacher, '10a, 10b');
|
||||||
|
expect(lesson.originalTeacher, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('leaves the slot empty for entries without a class', () {
|
||||||
|
final lesson = single(
|
||||||
|
_lesson(
|
||||||
|
date: day,
|
||||||
|
startHhmm: 920,
|
||||||
|
endHhmm: 935,
|
||||||
|
room: 'Hof',
|
||||||
|
teacherName: 'MUE',
|
||||||
|
lessonType: 'BREAK_SUPERVISION',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(lesson.teacher, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('tile color', () {
|
||||||
|
final now = DateTime(2026, 5, 6, 7);
|
||||||
|
final day = DateTime(2026, 5, 6);
|
||||||
|
|
||||||
|
WidgetLesson single(
|
||||||
|
McTimetableEntry lesson, {
|
||||||
|
TimetableGetSubjectsResponse? subjects,
|
||||||
|
}) => WidgetDataMapper.buildDayData(
|
||||||
|
now: now,
|
||||||
|
lessons: [lesson],
|
||||||
|
subjects: subjects,
|
||||||
|
rooms: null,
|
||||||
|
holidays: null,
|
||||||
|
).lessons.single;
|
||||||
|
|
||||||
|
test('regular lesson without subject color uses the status red', () {
|
||||||
|
final lesson = single(
|
||||||
|
_lesson(
|
||||||
|
date: day,
|
||||||
|
startHhmm: 755,
|
||||||
|
endHhmm: 840,
|
||||||
|
subjectName: 'MA',
|
||||||
|
teacherName: 'MUE',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(lesson.color, '#993333');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('regular lesson takes the personal subject color', () {
|
||||||
|
final lesson = single(
|
||||||
|
_lesson(
|
||||||
|
date: day,
|
||||||
|
startHhmm: 755,
|
||||||
|
endHhmm: 840,
|
||||||
|
subjectName: 'MA',
|
||||||
|
teacherName: 'MUE',
|
||||||
|
),
|
||||||
|
subjects: TimetableGetSubjectsResponse(
|
||||||
|
result: [
|
||||||
|
McSubject(
|
||||||
|
id: 1,
|
||||||
|
shortName: 'MA',
|
||||||
|
longName: 'Mathematik',
|
||||||
|
color: 'indigo',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(lesson.color, '#5C6BC0');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('substitution keeps its signal color despite a subject color', () {
|
||||||
|
final lesson = single(
|
||||||
|
_lesson(
|
||||||
|
date: day,
|
||||||
|
startHhmm: 755,
|
||||||
|
endHhmm: 840,
|
||||||
|
status: 'IRREGULAR',
|
||||||
|
subjectName: 'MA',
|
||||||
|
teacherName: 'MUE',
|
||||||
|
),
|
||||||
|
subjects: TimetableGetSubjectsResponse(
|
||||||
|
result: [
|
||||||
|
McSubject(
|
||||||
|
id: 1,
|
||||||
|
shortName: 'MA',
|
||||||
|
longName: 'Mathematik',
|
||||||
|
color: 'indigo',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(lesson.status, WidgetLessonStatus.irregular);
|
||||||
|
expect(lesson.color, '#8F19B3');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supervision is classified as duty with room in the title', () {
|
||||||
|
final lesson = single(
|
||||||
|
_lesson(
|
||||||
|
date: day,
|
||||||
|
startHhmm: 920,
|
||||||
|
endHhmm: 935,
|
||||||
|
room: 'Hof',
|
||||||
|
teacherName: 'MUE',
|
||||||
|
lessonType: 'BREAK_SUPERVISION',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(lesson.status, WidgetLessonStatus.duty);
|
||||||
|
expect(lesson.subjectShort, 'Hof Aufsicht');
|
||||||
|
expect(lesson.color, '#00796B');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('custom event resolves legacy color names', () {
|
||||||
|
final data = WidgetDataMapper.buildDayData(
|
||||||
|
now: now,
|
||||||
|
lessons: const [],
|
||||||
|
subjects: null,
|
||||||
|
rooms: null,
|
||||||
|
holidays: null,
|
||||||
|
customEvents: GetCustomTimetableEventResponse([
|
||||||
|
_event(
|
||||||
|
id: 'e',
|
||||||
|
title: 'Probe',
|
||||||
|
start: DateTime(2026, 5, 6, 14),
|
||||||
|
end: DateTime(2026, 5, 6, 15),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect(data.lessons.single.color, '#FFA000');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
group('buildWeekData', () {
|
group('buildWeekData', () {
|
||||||
final now = DateTime(2026, 5, 5, 10); // Tuesday
|
final now = DateTime(2026, 5, 5, 10); // Tuesday
|
||||||
|
|
||||||
test('covers the 14-day window including next week', () {
|
test('covers the 14-day window including next week', () {
|
||||||
final lessons = [
|
final lessons = [
|
||||||
_lesson(date: DateTime(2026, 5, 4), startHhmm: 800, endHhmm: 845, subjectName: 'MO'),
|
_lesson(
|
||||||
_lesson(date: DateTime(2026, 5, 6), startHhmm: 800, endHhmm: 845, subjectName: 'WE'),
|
date: DateTime(2026, 5, 4),
|
||||||
_lesson(date: DateTime(2026, 5, 8), startHhmm: 800, endHhmm: 845, subjectName: 'FR'),
|
startHhmm: 800,
|
||||||
_lesson(date: DateTime(2026, 5, 11), startHhmm: 800, endHhmm: 845, subjectName: 'NEXT'),
|
endHhmm: 845,
|
||||||
_lesson(date: DateTime(2026, 5, 18), startHhmm: 800, endHhmm: 845, subjectName: 'FAR'),
|
subjectName: 'MO',
|
||||||
|
),
|
||||||
|
_lesson(
|
||||||
|
date: DateTime(2026, 5, 6),
|
||||||
|
startHhmm: 800,
|
||||||
|
endHhmm: 845,
|
||||||
|
subjectName: 'WE',
|
||||||
|
),
|
||||||
|
_lesson(
|
||||||
|
date: DateTime(2026, 5, 8),
|
||||||
|
startHhmm: 800,
|
||||||
|
endHhmm: 845,
|
||||||
|
subjectName: 'FR',
|
||||||
|
),
|
||||||
|
_lesson(
|
||||||
|
date: DateTime(2026, 5, 11),
|
||||||
|
startHhmm: 800,
|
||||||
|
endHhmm: 845,
|
||||||
|
subjectName: 'NEXT',
|
||||||
|
),
|
||||||
|
_lesson(
|
||||||
|
date: DateTime(2026, 5, 18),
|
||||||
|
startHhmm: 800,
|
||||||
|
endHhmm: 845,
|
||||||
|
subjectName: 'FAR',
|
||||||
|
),
|
||||||
];
|
];
|
||||||
final data = WidgetDataMapper.buildWeekData(
|
final data = WidgetDataMapper.buildWeekData(
|
||||||
now: now,
|
now: now,
|
||||||
@@ -380,10 +603,12 @@ void main() {
|
|||||||
holidays: null,
|
holidays: null,
|
||||||
);
|
);
|
||||||
expect(data.anchorDate, DateTime(2026, 5, 4));
|
expect(data.anchorDate, DateTime(2026, 5, 4));
|
||||||
expect(
|
expect(data.lessons.map((l) => l.subjectShort).toList(), [
|
||||||
data.lessons.map((l) => l.subjectShort).toList(),
|
'MO',
|
||||||
['MO', 'WE', 'FR', 'NEXT'],
|
'WE',
|
||||||
);
|
'FR',
|
||||||
|
'NEXT',
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('window stays on the current week when the anchor rolls forward', () {
|
test('window stays on the current week when the anchor rolls forward', () {
|
||||||
@@ -391,8 +616,18 @@ void main() {
|
|||||||
// still starts at the current week's Monday so today stays available.
|
// still starts at the current week's Monday so today stays available.
|
||||||
final fridayEvening = DateTime(2026, 5, 8, 18);
|
final fridayEvening = DateTime(2026, 5, 8, 18);
|
||||||
final lessons = [
|
final lessons = [
|
||||||
_lesson(date: DateTime(2026, 5, 8), startHhmm: 800, endHhmm: 845, subjectName: 'FR'),
|
_lesson(
|
||||||
_lesson(date: DateTime(2026, 5, 11), startHhmm: 800, endHhmm: 845, subjectName: 'NEXT'),
|
date: DateTime(2026, 5, 8),
|
||||||
|
startHhmm: 800,
|
||||||
|
endHhmm: 845,
|
||||||
|
subjectName: 'FR',
|
||||||
|
),
|
||||||
|
_lesson(
|
||||||
|
date: DateTime(2026, 5, 11),
|
||||||
|
startHhmm: 800,
|
||||||
|
endHhmm: 845,
|
||||||
|
subjectName: 'NEXT',
|
||||||
|
),
|
||||||
];
|
];
|
||||||
final data = WidgetDataMapper.buildWeekData(
|
final data = WidgetDataMapper.buildWeekData(
|
||||||
now: fridayEvening,
|
now: fridayEvening,
|
||||||
@@ -402,10 +637,7 @@ void main() {
|
|||||||
holidays: null,
|
holidays: null,
|
||||||
);
|
);
|
||||||
expect(data.anchorDate, DateTime(2026, 5, 11));
|
expect(data.anchorDate, DateTime(2026, 5, 11));
|
||||||
expect(
|
expect(data.lessons.map((l) => l.subjectShort).toList(), ['FR', 'NEXT']);
|
||||||
data.lessons.map((l) => l.subjectShort).toList(),
|
|
||||||
['FR', 'NEXT'],
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('carries per-day holiday info in days', () {
|
test('carries per-day holiday info in days', () {
|
||||||
@@ -429,8 +661,10 @@ void main() {
|
|||||||
expect(data.days, hasLength(WidgetDataMapper.weekWindowDays));
|
expect(data.days, hasLength(WidgetDataMapper.weekWindowDays));
|
||||||
expect(data.days.first.date, DateTime(2026, 5, 4));
|
expect(data.days.first.date, DateTime(2026, 5, 4));
|
||||||
expect(data.days.last.date, DateTime(2026, 5, 17));
|
expect(data.days.last.date, DateTime(2026, 5, 17));
|
||||||
final holidayDays =
|
final holidayDays = data.days
|
||||||
data.days.where((d) => d.isHoliday).map((d) => d.date).toList();
|
.where((d) => d.isHoliday)
|
||||||
|
.map((d) => d.date)
|
||||||
|
.toList();
|
||||||
expect(holidayDays, [DateTime(2026, 5, 14), DateTime(2026, 5, 15)]);
|
expect(holidayDays, [DateTime(2026, 5, 14), DateTime(2026, 5, 15)]);
|
||||||
expect(
|
expect(
|
||||||
data.days.firstWhere((d) => d.isHoliday).holidayName,
|
data.days.firstWhere((d) => d.isHoliday).holidayName,
|
||||||
|
|||||||
Reference in New Issue
Block a user