forked from firka/firka
firka(android): timetable widget responsive layout, resize handling, centered lessons
This commit is contained in:
@@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user