From 8837eaf667ba50cb271d9428ef58ada1bfb1e739 Mon Sep 17 00:00:00 2001 From: I-Info Date: Thu, 26 Oct 2023 01:47:27 +0800 Subject: [PATCH] feat(widget): using work to refresh widget --- .../com/zjutjh/ijh/widget/ScheduleWidget.kt | 8 +++ .../ijh/widget/ScheduleWidgetReceiver.kt | 6 ++ .../zjutjh/ijh/work/WidgetRefreshWorker.kt | 66 ++++++++++++++++++- app/src/main/res/xml/app_widget_schedule.xml | 2 +- 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/zjutjh/ijh/widget/ScheduleWidget.kt b/app/src/main/kotlin/com/zjutjh/ijh/widget/ScheduleWidget.kt index 0561f43..68e61cb 100644 --- a/app/src/main/kotlin/com/zjutjh/ijh/widget/ScheduleWidget.kt +++ b/app/src/main/kotlin/com/zjutjh/ijh/widget/ScheduleWidget.kt @@ -1,6 +1,7 @@ package com.zjutjh.ijh.widget import android.content.Context +import android.util.Log import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -20,6 +21,7 @@ import androidx.glance.appwidget.cornerRadius import androidx.glance.appwidget.lazy.LazyColumn import androidx.glance.appwidget.lazy.items import androidx.glance.appwidget.provideContent +import androidx.glance.appwidget.updateAll import androidx.glance.background import androidx.glance.layout.Alignment import androidx.glance.layout.Box @@ -51,6 +53,8 @@ import java.time.format.DateTimeFormatter class ScheduleWidget : GlanceAppWidget() { @OptIn(ExperimentalCoroutinesApi::class) override suspend fun provideGlance(context: Context, id: GlanceId) { + Log.d("ScheduleWidget", "provideGlance called.") + val entryPoint = EntryPointAccessors.fromApplication(context) @@ -58,6 +62,7 @@ class ScheduleWidget : GlanceAppWidget() { .map { it?.toTermDayState() } val coursesFlow = dayStateFlow.flatMapLatest { it ?: return@flatMapLatest flowOf(null) + entryPoint.courseRepository .getCourses(it.year, it.term, it.week, it.dayOfWeek) } @@ -68,6 +73,9 @@ class ScheduleWidget : GlanceAppWidget() { DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(it.withZoneSameInstant(ZoneId.systemDefault())) } + // Force update, or the widget will not show data when it is first created + updateAll(context) + provideContent { val termDay by dayStateFlow.collectAsState(initial = null) val courses by coursesFlow.collectAsState(initial = null) diff --git a/app/src/main/kotlin/com/zjutjh/ijh/widget/ScheduleWidgetReceiver.kt b/app/src/main/kotlin/com/zjutjh/ijh/widget/ScheduleWidgetReceiver.kt index afd65c1..c4ed9f6 100644 --- a/app/src/main/kotlin/com/zjutjh/ijh/widget/ScheduleWidgetReceiver.kt +++ b/app/src/main/kotlin/com/zjutjh/ijh/widget/ScheduleWidgetReceiver.kt @@ -10,6 +10,7 @@ import androidx.work.WorkManager import com.zjutjh.ijh.data.repository.CourseRepository import com.zjutjh.ijh.data.repository.WeJhInfoRepository import com.zjutjh.ijh.work.ScheduleWidgetUpdater +import com.zjutjh.ijh.work.WidgetRefreshWorker import dagger.hilt.EntryPoint import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent @@ -46,6 +47,8 @@ class ScheduleWidgetReceiver : GlanceAppWidgetReceiver() { request ) + WidgetRefreshWorker.enqueue(context) + Log.i("ScheduleWidget", "Updater enqueued.") } @@ -54,6 +57,9 @@ class ScheduleWidgetReceiver : GlanceAppWidgetReceiver() { // Enter relevant functionality for when the last widget is disabled val manager = WorkManager.getInstance(context) manager.cancelUniqueWork(ScheduleWidgetUpdater.PERIODIC_UNIQUE_NAME) + + WidgetRefreshWorker.cancel(context) + Log.i("ScheduleWidget", "Updater canceled.") } } diff --git a/app/src/main/kotlin/com/zjutjh/ijh/work/WidgetRefreshWorker.kt b/app/src/main/kotlin/com/zjutjh/ijh/work/WidgetRefreshWorker.kt index b1c2d5b..761600d 100644 --- a/app/src/main/kotlin/com/zjutjh/ijh/work/WidgetRefreshWorker.kt +++ b/app/src/main/kotlin/com/zjutjh/ijh/work/WidgetRefreshWorker.kt @@ -1,20 +1,80 @@ package com.zjutjh.ijh.work import android.content.Context +import android.util.Log +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.updateAll +import androidx.work.Constraints import androidx.work.CoroutineWorker +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager import androidx.work.WorkerParameters +import androidx.work.workDataOf +import java.time.Duration +/** + * This worker is used to refresh the widget periodically. + */ class WidgetRefreshWorker( private val context: Context, workerParameters: WorkerParameters ) : CoroutineWorker(context, workerParameters) { - override suspend fun doWork(): Result = - try { - // TODO + companion object { + const val INPUT_CLASS_NAME = "className" + + inline fun getUniqueWorkName() = + WidgetRefreshWorker::class.java.simpleName + "_" + T::class.java.simpleName + + /** + * Enqueue a [WidgetRefreshWorker] for the given [GlanceAppWidget]. + * Do nothing if the worker is already enqueued. + * Default refresh interval is 20 minutes. + */ + inline fun enqueue(context: Context) { + val manager = WorkManager.getInstance(context) + val request = PeriodicWorkRequestBuilder( + Duration.ofMinutes(20), Duration.ofMinutes(5) + ).setConstraints( + Constraints(requiresBatteryNotLow = true, requiresDeviceIdle = true) + ).setInputData( + workDataOf(INPUT_CLASS_NAME to T::class.java.name) + ).build() + + manager.enqueueUniquePeriodicWork( + getUniqueWorkName(), + ExistingPeriodicWorkPolicy.KEEP, + request + ) + } + + inline fun cancel(context: Context) { + val manager = WorkManager.getInstance(context) + manager.cancelUniqueWork(getUniqueWorkName()) + } + } + + /** + * Using reflection to get the class name of the widget, then update it. + */ + override suspend fun doWork(): Result { + return try { + val className = inputData.getString(INPUT_CLASS_NAME) ?: return Result.failure() + val clazz = Class.forName(className) + + // check if clazz is a subclass of GlanceAppWidget + if (!GlanceAppWidget::class.java.isAssignableFrom(clazz)) return Result.failure() + + val obj = clazz.getConstructor().newInstance() as GlanceAppWidget + + obj.updateAll(context) + Result.success() } catch (e: Exception) { + Log.e("WidgetRefreshWorker", "Error when refreshing widget.", e) Result.failure() } + } } \ No newline at end of file diff --git a/app/src/main/res/xml/app_widget_schedule.xml b/app/src/main/res/xml/app_widget_schedule.xml index 51191a0..1e87da7 100644 --- a/app/src/main/res/xml/app_widget_schedule.xml +++ b/app/src/main/res/xml/app_widget_schedule.xml @@ -12,6 +12,6 @@ android:resizeMode="horizontal|vertical" android:targetCellWidth="3" android:targetCellHeight="3" - android:updatePeriodMillis="1800000" + android:updatePeriodMillis="0" android:widgetCategory="home_screen" tools:targetApi="s" /> \ No newline at end of file