package com.tt.honeyDue.widget import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.glance.GlanceId import androidx.glance.GlanceModifier import androidx.glance.action.clickable import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.GlanceAppWidgetReceiver import androidx.glance.appwidget.SizeMode import androidx.glance.appwidget.action.actionRunCallback import androidx.glance.appwidget.provideContent import androidx.glance.background import androidx.glance.layout.Alignment import androidx.glance.layout.Box import androidx.glance.layout.Column import androidx.glance.layout.Spacer import androidx.glance.layout.fillMaxSize import androidx.glance.layout.fillMaxWidth import androidx.glance.layout.height import androidx.glance.layout.padding import androidx.glance.text.FontWeight import androidx.glance.text.Text import androidx.glance.text.TextStyle /** * Large (4x4) widget. * * Mirrors iOS `LargeWidgetView`: * - When there are tasks: list of up to 5 tasks with residence/due * labels, optional "+N more" text, and a 3-pill stats row at the * bottom (Overdue / 7 Days / 30 Days). * - When empty: centered "All caught up!" state above the stats. * - Free tier collapses to the count-only layout. * * Glance restriction: no LazyColumn here because the list is bounded * (max 5), so a plain Column is fine and lets us compose the stats row * at the bottom without nesting a second scroll container. */ class HoneyDueLargeWidget : GlanceAppWidget() { override val sizeMode: SizeMode = SizeMode.Single override suspend fun provideGlance(context: Context, id: GlanceId) { val repo = WidgetDataRepository.get(context) // Per-instance residence scoping (gitea#6). Stats are computed // off the same filtered list so the bottom-tile counters // ("Overdue / 7 days / 30 days") match the visible tasks // instead of aggregating across every residence. val appWidgetId = androidx.glance.appwidget.GlanceAppWidgetManager(context).getAppWidgetId(id) val tasks = repo.loadTasksForWidget(appWidgetId) val stats = repo.computeStatsFromTasks(tasks) val tier = repo.loadTierState() val isPremium = tier.equals("premium", ignoreCase = true) provideContent { LargeWidgetContent(tasks, stats, isPremium) } } @Composable private fun LargeWidgetContent( tasks: List, stats: WidgetStats, isPremium: Boolean ) { val openApp = actionRunCallback() Box( modifier = GlanceModifier .fillMaxSize() .background(WidgetColors.BACKGROUND_PRIMARY) .padding(14.dp) .clickable(openApp) ) { if (!isPremium) { Column( modifier = GlanceModifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalAlignment = Alignment.CenterVertically ) { TaskCountBlock(count = tasks.size, long = true) } } else { Column(modifier = GlanceModifier.fillMaxSize()) { WidgetHeader(taskCount = tasks.size, onTap = openApp) Spacer(modifier = GlanceModifier.height(10.dp)) if (tasks.isEmpty()) { Box( modifier = GlanceModifier.defaultWeight().fillMaxWidth(), contentAlignment = Alignment.Center ) { EmptyState(compact = false, onTap = openApp) } } else { val shown = tasks.take(MAX_TASKS) shown.forEachIndexed { index, task -> TaskRow( task = task, compact = false, showResidence = true, onTaskClick = openApp, trailing = { CompleteButton(taskId = task.id) } ) if (index < shown.lastIndex) { Spacer(modifier = GlanceModifier.height(4.dp)) } } if (tasks.size > MAX_TASKS) { Spacer(modifier = GlanceModifier.height(4.dp)) Text( text = "+ ${tasks.size - MAX_TASKS} more", style = TextStyle( color = WidgetColors.textSecondary, fontSize = 10.sp, fontWeight = FontWeight.Medium ), modifier = GlanceModifier.fillMaxWidth() ) } Spacer(modifier = GlanceModifier.defaultWeight()) } Spacer(modifier = GlanceModifier.height(10.dp)) StatsRow(stats = stats) } } } } companion object { private const val MAX_TASKS = 5 } } /** AppWidget receiver for the large widget. */ class HoneyDueLargeWidgetReceiver : GlanceAppWidgetReceiver() { override val glanceAppWidget: GlanceAppWidget = HoneyDueLargeWidget() override fun onDeleted(context: Context, appWidgetIds: IntArray) { super.onDeleted(context, appWidgetIds) WidgetReceiverHelpers.purgeResidenceScopes(context, appWidgetIds) } }