Fix build failures from rebrand: restore pbxproj exceptions, fix Kotlin casing, move missed source dirs
- Restore 6 missing PBXFileSystemSynchronizedBuildFileExceptionSet entries and exceptions arrays on 5 root groups (lost during sed rename) - Rename extension WidgetIconView.swift to avoid stringsdata collision (original had different names: MyCribIconView vs CaseraIconView) - Rename CaseraExtension.entitlements → HoneyDueExtension.entitlements - Fix Kotlin object casing: honeyDueShareCodec → HoneyDueShareCodec, honeyDuePackageType → HoneyDuePackageType - Move missed Kotlin source dirs (jsMain, webMain, androidMain/com/casera) to com/tt/honeyDue - Rename remaining Casera widget files to HoneyDue - Rename CaseraTests.swift → HoneyDueTests.swift All 4 projects (Go API, iOS, Android, Web) now compile clean. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,367 @@
|
||||
package com.tt.honeyDue.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
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.ActionParameters
|
||||
import androidx.glance.action.actionParametersOf
|
||||
import androidx.glance.action.clickable
|
||||
import androidx.glance.appwidget.GlanceAppWidget
|
||||
import androidx.glance.appwidget.GlanceAppWidgetReceiver
|
||||
import androidx.glance.appwidget.action.ActionCallback
|
||||
import androidx.glance.appwidget.action.actionRunCallback
|
||||
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.background
|
||||
import androidx.glance.currentState
|
||||
import androidx.glance.layout.Alignment
|
||||
import androidx.glance.layout.Box
|
||||
import androidx.glance.layout.Column
|
||||
import androidx.glance.layout.Row
|
||||
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.layout.size
|
||||
import androidx.glance.layout.width
|
||||
import androidx.glance.state.GlanceStateDefinition
|
||||
import androidx.glance.state.PreferencesGlanceStateDefinition
|
||||
import androidx.glance.text.FontWeight
|
||||
import androidx.glance.text.Text
|
||||
import androidx.glance.text.TextStyle
|
||||
import androidx.glance.unit.ColorProvider
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
/**
|
||||
* Large widget showing task list with stats and interactive actions (Pro only)
|
||||
* Size: 4x4
|
||||
*/
|
||||
class HoneyDueLargeWidget : GlanceAppWidget() {
|
||||
|
||||
override val stateDefinition: GlanceStateDefinition<*> = PreferencesGlanceStateDefinition
|
||||
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||
provideContent {
|
||||
GlanceTheme {
|
||||
LargeWidgetContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LargeWidgetContent() {
|
||||
val prefs = currentState<Preferences>()
|
||||
val overdueCount = prefs[intPreferencesKey("overdue_count")] ?: 0
|
||||
val dueSoonCount = prefs[intPreferencesKey("due_soon_count")] ?: 0
|
||||
val inProgressCount = prefs[intPreferencesKey("in_progress_count")] ?: 0
|
||||
val totalCount = prefs[intPreferencesKey("total_tasks_count")] ?: 0
|
||||
val tasksJson = prefs[stringPreferencesKey("tasks_json")] ?: "[]"
|
||||
val isProUser = prefs[stringPreferencesKey("is_pro_user")] == "true"
|
||||
|
||||
val tasks = try {
|
||||
json.decodeFromString<List<WidgetTask>>(tasksJson).take(8)
|
||||
} catch (e: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = GlanceModifier
|
||||
.fillMaxSize()
|
||||
.background(Color(0xFFFFF8E7)) // Cream background
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = GlanceModifier.fillMaxSize()
|
||||
) {
|
||||
// Header with logo
|
||||
Row(
|
||||
modifier = GlanceModifier
|
||||
.fillMaxWidth()
|
||||
.clickable(actionRunCallback<OpenAppAction>()),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "honeyDue",
|
||||
style = TextStyle(
|
||||
color = ColorProvider(Color(0xFF07A0C3)),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
)
|
||||
|
||||
Spacer(modifier = GlanceModifier.width(8.dp))
|
||||
|
||||
Text(
|
||||
text = "Tasks",
|
||||
style = TextStyle(
|
||||
color = ColorProvider(Color(0xFF666666)),
|
||||
fontSize = 14.sp
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = GlanceModifier.height(12.dp))
|
||||
|
||||
// Stats row
|
||||
Row(
|
||||
modifier = GlanceModifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
StatBox(
|
||||
count = overdueCount,
|
||||
label = "Overdue",
|
||||
color = Color(0xFFDD1C1A),
|
||||
bgColor = Color(0xFFFFEBEB)
|
||||
)
|
||||
|
||||
Spacer(modifier = GlanceModifier.width(8.dp))
|
||||
|
||||
StatBox(
|
||||
count = dueSoonCount,
|
||||
label = "Due Soon",
|
||||
color = Color(0xFFF5A623),
|
||||
bgColor = Color(0xFFFFF4E0)
|
||||
)
|
||||
|
||||
Spacer(modifier = GlanceModifier.width(8.dp))
|
||||
|
||||
StatBox(
|
||||
count = inProgressCount,
|
||||
label = "Active",
|
||||
color = Color(0xFF07A0C3),
|
||||
bgColor = Color(0xFFE0F4F8)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = GlanceModifier.height(12.dp))
|
||||
|
||||
// Divider
|
||||
Box(
|
||||
modifier = GlanceModifier
|
||||
.fillMaxWidth()
|
||||
.height(1.dp)
|
||||
.background(Color(0xFFE0E0E0))
|
||||
) {}
|
||||
|
||||
Spacer(modifier = GlanceModifier.height(8.dp))
|
||||
|
||||
// Task list
|
||||
if (tasks.isEmpty()) {
|
||||
Box(
|
||||
modifier = GlanceModifier
|
||||
.fillMaxSize()
|
||||
.clickable(actionRunCallback<OpenAppAction>()),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "All caught up!",
|
||||
style = TextStyle(
|
||||
color = ColorProvider(Color(0xFF07A0C3)),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
)
|
||||
Text(
|
||||
text = "No tasks need attention",
|
||||
style = TextStyle(
|
||||
color = ColorProvider(Color(0xFF888888)),
|
||||
fontSize = 12.sp
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = GlanceModifier.fillMaxSize()
|
||||
) {
|
||||
items(tasks) { task ->
|
||||
InteractiveTaskItem(
|
||||
task = task,
|
||||
isProUser = isProUser
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StatBox(count: Int, label: String, color: Color, bgColor: Color) {
|
||||
Box(
|
||||
modifier = GlanceModifier
|
||||
.background(bgColor)
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
.cornerRadius(8.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = count.toString(),
|
||||
style = TextStyle(
|
||||
color = ColorProvider(color),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
)
|
||||
Text(
|
||||
text = label,
|
||||
style = TextStyle(
|
||||
color = ColorProvider(color),
|
||||
fontSize = 10.sp
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InteractiveTaskItem(task: WidgetTask, isProUser: Boolean) {
|
||||
val taskIdKey = ActionParameters.Key<Int>("task_id")
|
||||
|
||||
Row(
|
||||
modifier = GlanceModifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 6.dp)
|
||||
.clickable(
|
||||
actionRunCallback<OpenTaskAction>(
|
||||
actionParametersOf(taskIdKey to task.id)
|
||||
)
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// Priority indicator
|
||||
Box(
|
||||
modifier = GlanceModifier
|
||||
.width(4.dp)
|
||||
.height(40.dp)
|
||||
.background(getPriorityColor(task.priorityLevel))
|
||||
) {}
|
||||
|
||||
Spacer(modifier = GlanceModifier.width(8.dp))
|
||||
|
||||
// Task details
|
||||
Column(
|
||||
modifier = GlanceModifier.defaultWeight()
|
||||
) {
|
||||
Text(
|
||||
text = task.title,
|
||||
style = TextStyle(
|
||||
color = ColorProvider(Color(0xFF1A1A1A)),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
),
|
||||
maxLines = 1
|
||||
)
|
||||
Row {
|
||||
Text(
|
||||
text = task.residenceName,
|
||||
style = TextStyle(
|
||||
color = ColorProvider(Color(0xFF666666)),
|
||||
fontSize = 11.sp
|
||||
),
|
||||
maxLines = 1
|
||||
)
|
||||
if (task.dueDate != null) {
|
||||
Text(
|
||||
text = " • ${task.dueDate}",
|
||||
style = TextStyle(
|
||||
color = ColorProvider(
|
||||
if (task.isOverdue) Color(0xFFDD1C1A) else Color(0xFF666666)
|
||||
),
|
||||
fontSize = 11.sp
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Action button (Pro only)
|
||||
if (isProUser) {
|
||||
Box(
|
||||
modifier = GlanceModifier
|
||||
.size(32.dp)
|
||||
.background(Color(0xFF07A0C3))
|
||||
.cornerRadius(16.dp)
|
||||
.clickable(
|
||||
actionRunCallback<CompleteTaskAction>(
|
||||
actionParametersOf(taskIdKey to task.id)
|
||||
)
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "✓",
|
||||
style = TextStyle(
|
||||
color = ColorProvider(Color.White),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPriorityColor(level: Int): Color {
|
||||
return when (level) {
|
||||
4 -> Color(0xFFDD1C1A) // Urgent - Red
|
||||
3 -> Color(0xFFF5A623) // High - Amber
|
||||
2 -> Color(0xFF07A0C3) // Medium - Primary
|
||||
else -> Color(0xFF888888) // Low - Gray
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to complete a task from the widget (Pro only)
|
||||
*/
|
||||
class CompleteTaskAction : ActionCallback {
|
||||
override suspend fun onAction(
|
||||
context: Context,
|
||||
glanceId: GlanceId,
|
||||
parameters: ActionParameters
|
||||
) {
|
||||
val taskId = parameters[ActionParameters.Key<Int>("task_id")] ?: return
|
||||
|
||||
// Send broadcast to app to complete the task
|
||||
val intent = Intent("com.tt.honeyDue.COMPLETE_TASK").apply {
|
||||
putExtra("task_id", taskId)
|
||||
setPackage(context.packageName)
|
||||
}
|
||||
context.sendBroadcast(intent)
|
||||
|
||||
// Update widget after action
|
||||
withContext(Dispatchers.Main) {
|
||||
HoneyDueLargeWidget().update(context, glanceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receiver for the large widget
|
||||
*/
|
||||
class HoneyDueLargeWidgetReceiver : GlanceAppWidgetReceiver() {
|
||||
override val glanceAppWidget: GlanceAppWidget = HoneyDueLargeWidget()
|
||||
}
|
||||
Reference in New Issue
Block a user