110 lines
4.0 KiB
Kotlin
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()
|
|
}
|