package org.terst.doot.widget.ui import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.glance.appwidget.updateAll import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import okhttp3.OkHttpClient import org.terst.doot.widget.data.Keys import org.terst.doot.widget.data.WidgetRepository import org.terst.doot.widget.data.dataStore import org.terst.doot.widget.work.CompleteWorker import java.time.LocalDate import java.time.format.DateTimeFormatter import java.util.Calendar import java.util.TimeZone class TaskDetailActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val id = intent.getStringExtra(EXTRA_ID) ?: return finish() val source = intent.getStringExtra(EXTRA_SOURCE) ?: return finish() val title = intent.getStringExtra(EXTRA_TITLE) ?: "" val completable = intent.getBooleanExtra(EXTRA_COMPLETABLE, false) val dueDate = intent.getStringExtra(EXTRA_DUE_DATE) setContent { MaterialTheme(colorScheme = darkColorScheme()) { TaskDetailSheet( title = title, source = source, completable = completable, dueDate = dueDate, onComplete = { CompleteWorker.enqueue(this, id, source) finish() }, onReschedule = { dateISO -> lifecycleScope.launch { val prefs = this@TaskDetailActivity.dataStore.data.first() val url = prefs[Keys.SERVER_URL]?.trimEnd('/') ?: return@launch val token = prefs[Keys.TOKEN] ?: return@launch val repo = WidgetRepository(OkHttpClient(), url, token) repo.reschedule(id, source, dateISO).onSuccess { repo.fetchAndPersist(this@TaskDetailActivity) DootWidget().updateAll(this@TaskDetailActivity) finish() } } }, onDismiss = ::finish ) } } } companion object { const val EXTRA_ID = "task_id" const val EXTRA_SOURCE = "task_source" const val EXTRA_TITLE = "task_title" const val EXTRA_COMPLETABLE = "task_completable" const val EXTRA_DUE_DATE = "task_due_date" } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun TaskDetailSheet( title: String, source: String, completable: Boolean, dueDate: String?, onComplete: () -> Unit, onReschedule: (String) -> Unit, onDismiss: () -> Unit ) { var showDatePicker by remember { mutableStateOf(false) } val datePickerState = rememberDatePickerState( initialSelectedDateMillis = System.currentTimeMillis() ) if (showDatePicker) { DatePickerDialog( onDismissRequest = { showDatePicker = false }, confirmButton = { TextButton(onClick = { datePickerState.selectedDateMillis?.let { millis -> val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")) cal.timeInMillis = millis val dateISO = "%04d-%02d-%02d".format( cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH) ) onReschedule(dateISO) } showDatePicker = false }) { Text("Set date") } }, dismissButton = { TextButton(onClick = { showDatePicker = false }) { Text("Cancel") } } ) { DatePicker(state = datePickerState) } } ModalBottomSheet( onDismissRequest = onDismiss, sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), containerColor = Color(0xFF1E293B), tonalElevation = 0.dp, dragHandle = { Box( modifier = Modifier .padding(vertical = 12.dp) .width(36.dp) .height(4.dp) .background(Color.White.copy(alpha = 0.25f), RoundedCornerShape(2.dp)) ) } ) { Column( modifier = Modifier .fillMaxWidth() .padding(horizontal = 20.dp) .navigationBarsPadding() ) { Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) { Box( modifier = Modifier .size(10.dp) .background(sourceColor(source), RoundedCornerShape(5.dp)) ) Spacer(Modifier.width(10.dp)) Text( text = title, fontSize = 17.sp, fontWeight = FontWeight.SemiBold, color = Color.White, modifier = Modifier.weight(1f) ) } Spacer(Modifier.height(20.dp)) if (completable) { Button( onClick = onComplete, modifier = Modifier.fillMaxWidth(), colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF3B82F6)) ) { Text("Mark Complete", fontSize = 15.sp) } Spacer(Modifier.height(8.dp)) } if (source == "doot") { OutlinedButton( onClick = { showDatePicker = true }, modifier = Modifier.fillMaxWidth(), colors = ButtonDefaults.outlinedButtonColors(contentColor = Color.White), border = androidx.compose.foundation.BorderStroke(1.dp, Color.White.copy(alpha = 0.3f)) ) { Text(formatDueDateLabel(dueDate), fontSize = 15.sp) } } Spacer(Modifier.height(20.dp)) } } } private fun formatDueDateLabel(dueDate: String?): String { if (dueDate == null) return "No due date · tap to schedule" return runCatching { val date = LocalDate.parse(dueDate.substring(0, 10)) "Due " + date.format(DateTimeFormatter.ofPattern("MMM d")) }.getOrDefault("No due date · tap to schedule") }