firka(android): timetable widget responsive layout, resize handling, centered lessons

This commit is contained in:
2026-02-28 22:05:30 +01:00
parent 5c205a9844
commit a11f118861
3 changed files with 69 additions and 15 deletions

View File

@@ -9,8 +9,10 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.glance.GlanceId import androidx.glance.GlanceId
import androidx.glance.GlanceModifier import androidx.glance.GlanceModifier
import androidx.glance.LocalSize
import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.provideContent import androidx.glance.appwidget.provideContent
import androidx.glance.appwidget.SizeMode
import androidx.glance.background import androidx.glance.background
import androidx.glance.color.ColorProvider import androidx.glance.color.ColorProvider
import androidx.glance.currentState import androidx.glance.currentState
@@ -39,6 +41,9 @@ class TimetableWidget : GlanceAppWidget() {
override val stateDefinition: GlanceStateDefinition<*>? override val stateDefinition: GlanceStateDefinition<*>?
get() = HomeWidgetGlanceStateDefinition() get() = HomeWidgetGlanceStateDefinition()
override val sizeMode: SizeMode
get() = SizeMode.Exact
override suspend fun provideGlance(context: Context, id: GlanceId) { override suspend fun provideGlance(context: Context, id: GlanceId) {
val data = withContext(Dispatchers.IO) { val data = withContext(Dispatchers.IO) {
loadWidgetData(context) loadWidgetData(context)
@@ -73,7 +78,7 @@ class TimetableWidget : GlanceAppWidget() {
val end = start.plusHours(23) val end = start.plusHours(23)
val filtered = lessons.filter { it.start.isAfter(start) && it.end.isBefore(end) } val filtered = lessons.filter { it.start.isAfter(start) && it.end.isBefore(end) }
val headerText = if (displayDateStr.isNotEmpty()) displayDateStr else "Mai órarend" val headerText = if (displayDateStr.isNotEmpty()) displayDateStr else "Mai órarend"
return WidgetData(colors, headerText, filtered.take(4)) return WidgetData(colors, headerText, filtered)
} }
@Composable @Composable
@@ -98,26 +103,55 @@ class TimetableWidget : GlanceAppWidget() {
return return
} }
val size = LocalSize.current
val lessonRowHeightDp = 52f
val scale = lessonRowHeightDp / 52f
val headerHeightDp = 20f * scale
val verticalPaddingDp = 32f * scale
val spacerDp = 4f * scale
val paddingDp = 16f * scale
val availableHeightDp = size.height.value - verticalPaddingDp - headerHeightDp - spacerDp
val maxVisibleLessons = (availableHeightDp / lessonRowHeightDp).toInt().coerceAtLeast(0)
val maxLessons = (maxVisibleLessons.coerceAtMost(16) / 2 * 2).coerceAtLeast(1)
val displayLessons = data.lessons.take(maxLessons)
val lessonChunks = displayLessons.chunked(2)
val showDate = maxLessons > 1
val dateSectionHeight = if (showDate) headerHeightDp + spacerDp else 0f
val lessonListHeight = when (val n = displayLessons.size) {
0 -> 0f
else -> n * lessonRowHeightDp + (n - 1) * spacerDp
}
val remainingHeight = (size.height.value - 2 * paddingDp - dateSectionHeight - lessonListHeight).coerceAtLeast(0f)
val verticalPaddingAroundLessons = remainingHeight / 2f
Box( Box(
modifier = GlanceModifier modifier = GlanceModifier
.background(data.colors.background) .background(data.colors.background)
.padding(16.dp) .padding(paddingDp.dp)
.fillMaxSize() .fillMaxSize()
) { ) {
Column { Column {
Text( if (showDate) {
data.headerText, Text(
style = TextStyle( data.headerText,
color = ColorProvider(data.colors.textSecondary, data.colors.textSecondary), style = TextStyle(
fontSize = 12.sp, color = ColorProvider(data.colors.textSecondary, data.colors.textSecondary),
fontWeight = FontWeight.Medium fontSize = 12.sp,
fontWeight = FontWeight.Medium
)
) )
) Spacer(modifier = GlanceModifier.height(spacerDp.dp))
Spacer(modifier = GlanceModifier.height(4.dp))
for (lesson in data.lessons) {
LessonCard(lesson, data.colors)
Spacer(modifier = GlanceModifier.height(4.dp))
} }
Spacer(modifier = GlanceModifier.height(verticalPaddingAroundLessons.dp))
for (chunk in lessonChunks) {
Column {
for (lesson in chunk) {
LessonCard(lesson, data.colors)
Spacer(modifier = GlanceModifier.height(spacerDp.dp))
}
}
}
Spacer(modifier = GlanceModifier.height(verticalPaddingAroundLessons.dp))
} }
} }
} }

View File

@@ -1,7 +1,25 @@
package app.firka.naplo.glance package app.firka.naplo.glance
import android.appwidget.AppWidgetManager
import android.content.Context
import android.os.Bundle
import HomeWidgetGlanceWidgetReceiver import HomeWidgetGlanceWidgetReceiver
import androidx.glance.appwidget.GlanceAppWidgetManager
import kotlinx.coroutines.runBlocking
class TimetableWidgetReceiver : HomeWidgetGlanceWidgetReceiver<TimetableWidget>() { class TimetableWidgetReceiver : HomeWidgetGlanceWidgetReceiver<TimetableWidget>() {
override val glanceAppWidget = TimetableWidget() override val glanceAppWidget = TimetableWidget()
override fun onAppWidgetOptionsChanged(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int,
newOptions: Bundle,
) {
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
runBlocking {
val glanceId = GlanceAppWidgetManager(context).getGlanceIdBy(appWidgetId)
glanceAppWidget.update(context, glanceId)
}
}
} }

View File

@@ -1,7 +1,9 @@
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/glance_default_loading_layout" android:initialLayout="@layout/glance_default_loading_layout"
android:minWidth="300dp" android:minWidth="250dp"
android:minHeight="100dp" android:minHeight="93dp"
android:minResizeWidth="180dp"
android:minResizeHeight="93dp"
android:resizeMode="horizontal|vertical" android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="1800000"> android:updatePeriodMillis="1800000">
</appwidget-provider> </appwidget-provider>