package com.tt.honeyDue.widget import android.app.Activity import android.appwidget.AppWidgetManager import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Home import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.glance.appwidget.GlanceAppWidgetManager import androidx.glance.appwidget.updateAll import com.tt.honeyDue.ui.theme.AppSpacing import com.tt.honeyDue.ui.theme.HoneyDueTheme import com.tt.honeyDue.ui.theme.ThemeManager import kotlinx.coroutines.launch import androidx.lifecycle.lifecycleScope /** * Per-widget residence selector. Launched by the system when the user * pins a new honeyDue widget (because each widget provider XML now * declares `android:configure`) and again when they hit "Edit Widget". * * Saves the chosen residence id under * `widget_residence_id_` in [WidgetDataStore] so each * widget instance can independently scope its task list (gitea#6). * Selecting "All residences" clears the key and the widget reverts to * the legacy unscoped behaviour. */ class WidgetConfigActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // The system passes the just-created widget id in the extras. // Without it we don't know which widget to configure — bail // with CANCELED so the system removes the placeholder tile. val appWidgetId = intent?.extras?.getInt( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID ) ?: AppWidgetManager.INVALID_APPWIDGET_ID if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { setResult(Activity.RESULT_CANCELED) finish() return } // Default a CANCEL result so the widget is removed if the user // dismisses without saving (matches the Android convention). setResult( Activity.RESULT_CANCELED, Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) ) setContent { val theme = ThemeManager.currentTheme HoneyDueTheme(themeColors = theme) { WidgetConfigScreen( appWidgetId = appWidgetId, onCommit = { residenceId -> lifecycleScope.launch { val repo = WidgetDataRepository.get(this@WidgetConfigActivity) repo.saveResidenceIdFor(appWidgetId, residenceId) // Repaint every widget tile so this one // picks up the new scope on the next frame // (Glance handles which `appWidgetId` we // belong to via the per-instance state). HoneyDueSmallWidget().updateAll(this@WidgetConfigActivity) HoneyDueMediumWidget().updateAll(this@WidgetConfigActivity) HoneyDueLargeWidget().updateAll(this@WidgetConfigActivity) setResult( Activity.RESULT_OK, Intent().putExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId ) ) finish() } } ) } } } } /** * The actual picker UI. Loads residences from [WidgetDataRepository] * and offers an "All residences" option above them. Empty state shows * a helper message instead of an empty list (user hasn't created any * residences yet, or the main app hasn't synced). */ @Composable private fun WidgetConfigScreen( appWidgetId: Int, onCommit: (Long?) -> Unit ) { var residences by remember { mutableStateOf?>(null) } var selectedId by remember { mutableStateOf(null) } val context = androidx.compose.ui.platform.LocalContext.current LaunchedEffect(appWidgetId) { val repo = WidgetDataRepository.get(context) // Pre-select whatever the user picked last time they configured // this same widget; falls back to "All residences" on first run. selectedId = repo.loadResidenceIdFor(appWidgetId) residences = repo.loadResidences() } Column( modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background) .padding(AppSpacing.lg) ) { Text( text = "Choose a residence", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onBackground ) Spacer(Modifier.height(AppSpacing.sm)) Text( text = "This widget will only show tasks for the residence you pick. " + "Choose \"All residences\" to keep showing every home.", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) Spacer(Modifier.height(AppSpacing.lg)) val items = residences if (items == null) { // Loading state — DataStore reads off the IO dispatcher. Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator() } return@Column } LazyColumn( modifier = Modifier.weight(1f, fill = true), verticalArrangement = Arrangement.spacedBy(AppSpacing.sm) ) { // "All residences" — selecting clears the per-widget key. item { ResidenceRow( title = "All residences", isSelected = selectedId == null, onClick = { selectedId = null } ) } if (items.isEmpty()) { item { EmptyResidencesNote() } } else { items(items, key = { it.id }) { residence -> ResidenceRow( title = residence.name, isSelected = selectedId == residence.id, onClick = { selectedId = residence.id } ) } } } Spacer(Modifier.height(AppSpacing.lg)) Button( onClick = { onCommit(selectedId) }, modifier = Modifier.fillMaxWidth() ) { Text("Save") } } } @Composable private fun ResidenceRow( title: String, isSelected: Boolean, onClick: () -> Unit ) { Box( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(AppSpacing.md)) .background( if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceVariant ) .clickable(onClick = onClick) .padding(AppSpacing.lg) ) { androidx.compose.foundation.layout.Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() ) { Icon( imageVector = Icons.Default.Home, contentDescription = null, tint = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant ) Spacer(Modifier.fillMaxWidth(0.04f)) Text( text = title, style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onBackground, modifier = Modifier.weight(1f) ) if (isSelected) { Icon( imageVector = Icons.Default.Check, contentDescription = "Selected", tint = MaterialTheme.colorScheme.primary ) } } } } @Composable private fun EmptyResidencesNote() { Box( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(AppSpacing.md)) .background(MaterialTheme.colorScheme.surfaceVariant) .padding(AppSpacing.lg) ) { Text( text = "No residences yet — open honeyDue and add a property first, " + "then come back to configure this widget.", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) } }