Closer/app/src/main/java/app/closer/widget/TodayWidget.kt

110 lines
4.0 KiB
Kotlin

package app.closer.widget
import android.content.Context
import android.content.Intent
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.GlanceTheme
import androidx.glance.action.clickable
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import androidx.glance.appwidget.action.actionStartActivity
import androidx.glance.appwidget.cornerRadius
import androidx.glance.appwidget.provideContent
import androidx.glance.background
import androidx.glance.layout.Alignment
import androidx.glance.layout.Column
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.padding
import androidx.glance.text.FontWeight
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import app.closer.MainActivity
import app.closer.core.navigation.AppRoute
/**
* A glanceable home-screen widget: today's daily-question state + streak, nothing more.
* Content-free by design — no question or answer text ever renders here. When the biometric
* app-lock is on, it drops to a generic card so partner-activity state doesn't sit on the home
* screen the user chose to protect. Tapping opens the relevant screen.
*/
class TodayWidget : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
val snapshot = TodayWidgetState.read(context)
provideContent {
GlanceTheme {
TodayWidgetContent(context, snapshot)
}
}
}
}
@Composable
private fun TodayWidgetContent(context: Context, snapshot: TodayWidgetSnapshot) {
val launch = Intent(context, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// Route straight to the daily question only when it's safe + meaningful; otherwise a plain
// launch. app_route is read by MainActivity.deepLinkRouteFromIntent.
if (snapshot.signedIn && snapshot.paired && !snapshot.appLockEnabled) {
putExtra("app_route", AppRoute.DAILY_QUESTION)
}
}
Column(
modifier = GlanceModifier
.fillMaxSize()
.background(GlanceTheme.colors.primaryContainer)
.cornerRadius(20.dp)
.padding(16.dp)
.clickable(actionStartActivity(launch)),
verticalAlignment = Alignment.Vertical.CenterVertically,
horizontalAlignment = Alignment.Horizontal.Start,
) {
Text(
text = "Closer",
style = TextStyle(
color = GlanceTheme.colors.onPrimaryContainer,
fontSize = 13.sp,
fontWeight = FontWeight.Medium,
)
)
Text(
text = headlineFor(snapshot),
style = TextStyle(
color = GlanceTheme.colors.onPrimaryContainer,
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
),
maxLines = 3,
)
if (snapshot.paired && snapshot.streak > 0 && !snapshot.appLockEnabled) {
Text(
text = "🔥 ${snapshot.streak} day streak",
style = TextStyle(color = GlanceTheme.colors.onPrimaryContainer, fontSize = 13.sp)
)
}
}
}
/** Content-free headline derived only from state flags. */
internal fun headlineFor(s: TodayWidgetSnapshot): String = when {
!s.signedIn -> "Open Closer"
!s.paired -> "Invite your partner to begin"
s.appLockEnabled -> "Something's waiting in Closer"
else -> when (s.dailyState) {
"USER_ANSWERED_PARTNER_PENDING" -> "Waiting for your partner"
"PARTNER_ANSWERED_USER_PENDING" -> "Your turn — answer to reveal"
"BOTH_ANSWERED" -> "Your reveal is waiting"
"REVEALED" -> "You opened tonight's question"
else -> "Tonight's question is ready"
}
}
class TodayWidgetReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget = TodayWidget()
}