import sys with open('c:/dev/git/Android/ShaarIt/app/src/main/java/com/shaarit/presentation/todo/TodoScreen.kt', 'r', encoding='utf-8') as f: lines = f.readlines() replacement = \"\"\"// ====== Edit / Create Todo Screen ====== @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @Composable private fun EditTodoDialog( state: EditTodoDialogUiState, groupNames: List, timezoneId: String = java.util.TimeZone.getDefault().id, onDismiss: () -> Unit, onContentChanged: (String) -> Unit, onDueDateChanged: (Long?) -> Unit, onGroupChanged: (String) -> Unit, onNewSubtaskTextChanged: (String) -> Unit, onAddSubtask: () -> Unit, onRemoveSubtask: (Int) -> Unit, onToggleSubtask: (Int) -> Unit, onUpdateSubtaskContent: (Int, String) -> Unit, onNewTagTextChanged: (String) -> Unit, onAddTag: () -> Unit, onRemoveTag: (String) -> Unit, onSave: () -> Unit ) { var showDatePicker by remember { mutableStateOf(false) } androidx.compose.ui.window.Dialog( onDismissRequest = onDismiss, properties = androidx.compose.ui.window.DialogProperties( usePlatformDefaultWidth = false, decorFitsSystemWindows = true ) ) { Scaffold( topBar = { TopAppBar( title = { }, navigationIcon = { IconButton(onClick = onSave, enabled = !state.isSaving && state.content.isNotBlank()) { if (state.isSaving) { CircularProgressIndicator(modifier = Modifier.size(24.dp)) } else { Icon(Icons.Default.Check, contentDescription = "Enregistrer") } } }, actions = { IconButton(onClick = onDismiss) { Icon(Icons.Default.Close, contentDescription = "Fermer") } }, colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.surface ) ) }, containerColor = MaterialTheme.colorScheme.surface ) { paddingValues -> Column( modifier = Modifier .fillMaxSize() .padding(paddingValues) .verticalScroll(rememberScrollState()) ) { // Task Title Row( verticalAlignment = Alignment.Top, modifier = Modifier.padding(16.dp).fillMaxWidth() ) { Icon( androidx.compose.material.icons.outlined.CheckBoxOutlineBlank, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.padding(top = 4.dp) ) Spacer(Modifier.width(16.dp)) androidx.compose.foundation.text.BasicTextField( value = state.content, onValueChange = onContentChanged, textStyle = MaterialTheme.typography.titleLarge.copy(color = MaterialTheme.colorScheme.onSurface), modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences), decorationBox = { innerTextField -> if (state.content.isEmpty()) { Text("Nom de la tâche", color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.titleLarge) } innerTextField() } ) } androidx.compose.material3.HorizontalDivider(color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)) // Due Date val dateText = state.dueDate?.let { formatDateTime(it, timezoneId) } ?: "Aucune date d'échéance" TaskActionRow( icon = Icons.Default.Schedule, text = dateText, onClick = { showDatePicker = true }, isSet = state.dueDate != null ) androidx.compose.material3.HorizontalDivider(color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)) // Group (Famille) TaskGroupRow( groupName = state.groupName, groupNames = groupNames, onGroupChanged = onGroupChanged ) androidx.compose.material3.HorizontalDivider(color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)) // Tags TaskTagsRow( tags = state.tags, newTagText = state.newTagText, onNewTagTextChanged = onNewTagTextChanged, onAddTag = onAddTag, onRemoveTag = onRemoveTag ) androidx.compose.material3.HorizontalDivider(color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)) // Subtasks TaskSubtasksSection( subtasks = state.subtasks, newSubtaskText = state.newSubtaskText, onNewSubtaskTextChanged = onNewSubtaskTextChanged, onAddSubtask = onAddSubtask, onRemoveSubtask = onRemoveSubtask, onToggleSubtask = onToggleSubtask, onUpdateSubtaskContent = onUpdateSubtaskContent ) Spacer(modifier = Modifier.height(32.dp)) } } if (showDatePicker) { DateTimePickerBottomSheet( initialDate = state.dueDate, timezoneId = timezoneId, onDismiss = { showDatePicker = false }, onDateSelected = onDueDateChanged ) } } } @Composable private fun TaskActionRow( icon: androidx.compose.ui.graphics.vector.ImageVector, text: String, onClick: () -> Unit = {}, isSet: Boolean = false ) { Row( modifier = Modifier .fillMaxWidth() .clickable(onClick = onClick) .padding(horizontal = 16.dp, vertical = 16.dp), verticalAlignment = Alignment.CenterVertically ) { Icon( icon, contentDescription = null, tint = if (isSet) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant ) Spacer(modifier = Modifier.width(16.dp)) Text( text, color = if (isSet) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyLarge ) } } @Composable private fun TaskGroupRow( groupName: String, groupNames: List, onGroupChanged: (String) -> Unit ) { var expanded by remember { mutableStateOf(false) } var isEditing by remember { mutableStateOf(false) } var customGroup by remember { mutableStateOf("") } Row( modifier = Modifier .fillMaxWidth() .clickable { expanded = true } .padding(horizontal = 16.dp, vertical = 12.dp), verticalAlignment = Alignment.CenterVertically ) { Icon(Icons.Default.List, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant) Spacer(modifier = Modifier.width(16.dp)) if (groupName.isNotBlank()) { Surface( color = MaterialTheme.colorScheme.surfaceVariant, shape = MaterialTheme.shapes.small, onClick = { expanded = true } ) { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp)) { Icon(Icons.Default.List, contentDescription = null, modifier = Modifier.size(16.dp)) Spacer(Modifier.width(6.dp)) Text(groupName.uppercase(), style = MaterialTheme.typography.labelMedium) } } } else { Text("Aucune liste", color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyLarge) } androidx.compose.material3.DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false } ) { androidx.compose.material3.DropdownMenuItem( text = { Text("Aucune liste") }, onClick = { onGroupChanged(""); expanded = false } ) groupNames.forEach { group -> androidx.compose.material3.DropdownMenuItem( text = { Text(group) }, onClick = { onGroupChanged(group); expanded = false } ) } androidx.compose.material3.HorizontalDivider() androidx.compose.material3.DropdownMenuItem( text = { Text("Nouvelle liste...") }, onClick = { isEditing = true; expanded = false } ) } } if (isEditing) { AlertDialog( onDismissRequest = { isEditing = false }, title = { Text("Nouvelle liste") }, text = { OutlinedTextField( value = customGroup, onValueChange = { customGroup = it }, singleLine = true, placeholder = { Text("Nom de la liste") } ) }, confirmButton = { TextButton(onClick = { if (customGroup.isNotBlank()) { onGroupChanged(customGroup) } isEditing = false }) { Text("Ajouter") } }, dismissButton = { TextButton(onClick = { isEditing = false }) { Text("Annuler") } } ) } } @OptIn(ExperimentalLayoutApi::class) @Composable private fun TaskTagsRow( tags: List, newTagText: String, onNewTagTextChanged: (String) -> Unit, onAddTag: () -> Unit, onRemoveTag: (String) -> Unit ) { var isEditing by remember { mutableStateOf(false) } Column( modifier = Modifier .fillMaxWidth() .clickable { isEditing = true } .padding(horizontal = 16.dp, vertical = 12.dp) ) { Row(verticalAlignment = Alignment.CenterVertically) { Icon(Icons.Default.Folder, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant) Spacer(modifier = Modifier.width(16.dp)) if (tags.isEmpty() && !isEditing) { Text("Ajouter des étiquettes", color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyLarge) } else { FlowRow( horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { tags.forEach { tag -> Surface( color = MaterialTheme.colorScheme.secondaryContainer, shape = MaterialTheme.shapes.small, onClick = { onRemoveTag(tag) } ) { Row( modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp), verticalAlignment = Alignment.CenterVertically ) { Text("#\", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSecondaryContainer) Spacer(modifier = Modifier.width(4.dp)) Icon(Icons.Default.Close, contentDescription = null, modifier = Modifier.size(12.dp), tint = MaterialTheme.colorScheme.onSecondaryContainer) } } } if (isEditing) { androidx.compose.foundation.text.BasicTextField( value = newTagText, onValueChange = onNewTagTextChanged, textStyle = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onSurface), modifier = Modifier.width(100.dp).align(Alignment.CenterVertically), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { onAddTag(); isEditing = false }), decorationBox = { inner -> if (newTagText.isEmpty()) Text("Nouveau tag...", color = MaterialTheme.colorScheme.onSurfaceVariant) inner() } ) } } } } } } @Composable private fun TaskSubtasksSection( subtasks: List, newSubtaskText: String, onNewSubtaskTextChanged: (String) -> Unit, onAddSubtask: () -> Unit, onRemoveSubtask: (Int) -> Unit, onToggleSubtask: (Int) -> Unit, onUpdateSubtaskContent: (Int, String) -> Unit ) { Column(modifier = Modifier.fillMaxWidth()) { Row( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 16.dp), verticalAlignment = Alignment.CenterVertically ) { Icon(Icons.Default.Add, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant) Spacer(modifier = Modifier.width(16.dp)) Text("Ajouter une sous-tâche", color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyLarge) } subtasks.forEachIndexed { index, sub -> Row( modifier = Modifier.fillMaxWidth().padding(start = 48.dp, end = 16.dp, top = 4.dp, bottom = 4.dp), verticalAlignment = Alignment.CenterVertically ) { Checkbox( checked = sub.isDone, onCheckedChange = { onToggleSubtask(index) }, modifier = Modifier.size(32.dp) ) androidx.compose.foundation.text.BasicTextField( value = sub.content, onValueChange = { onUpdateSubtaskContent(index, it) }, modifier = Modifier.weight(1f).padding(horizontal = 8.dp), textStyle = MaterialTheme.typography.bodyMedium.copy( color = MaterialTheme.colorScheme.onSurface, textDecoration = if (sub.isDone) TextDecoration.LineThrough else TextDecoration.None ) ) IconButton(onClick = { onRemoveSubtask(index) }, modifier = Modifier.size(32.dp)) { Icon(Icons.Default.Close, contentDescription = "Supprimer", modifier = Modifier.size(16.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant) } } } Row( modifier = Modifier.fillMaxWidth().padding(start = 48.dp, end = 16.dp, top = 4.dp, bottom = 12.dp), verticalAlignment = Alignment.CenterVertically ) { Icon(Icons.Default.Add, contentDescription = null, modifier = Modifier.size(24.dp).padding(4.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant) androidx.compose.foundation.text.BasicTextField( value = newSubtaskText, onValueChange = onNewSubtaskTextChanged, modifier = Modifier.weight(1f).padding(horizontal = 8.dp), textStyle = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onSurface), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = onAddSubtask), decorationBox = { inner -> if (newSubtaskText.isEmpty()) Text("Nouvelle sous-tâche...", color = MaterialTheme.colorScheme.onSurfaceVariant) inner() } ) } } } @OptIn(ExperimentalMaterial3Api::class) @Composable private fun DateTimePickerBottomSheet( initialDate: Long?, timezoneId: String, onDismiss: () -> Unit, onDateSelected: (Long?) -> Unit ) { val sheetState = androidx.compose.material3.rememberModalBottomSheetState(skipPartiallyExpanded = true) var showTimePicker by remember { mutableStateOf(false) } val tz = TimeZone.getTimeZone(timezoneId) val calendar = remember { Calendar.getInstance(tz).apply { if (initialDate != null) timeInMillis = initialDate } } var selectedDateMillis by remember { mutableStateOf(initialDate) } val datePickerState = androidx.compose.material3.rememberDatePickerState( initialSelectedDateMillis = initialDate ) LaunchedEffect(datePickerState.selectedDateMillis) { if (datePickerState.selectedDateMillis != null) { val newDateCal = Calendar.getInstance(tz).apply { timeInMillis = datePickerState.selectedDateMillis!! } calendar.set(Calendar.YEAR, newDateCal.get(Calendar.YEAR)) calendar.set(Calendar.MONTH, newDateCal.get(Calendar.MONTH)) calendar.set(Calendar.DAY_OF_MONTH, newDateCal.get(Calendar.DAY_OF_MONTH)) selectedDateMillis = calendar.timeInMillis } } if (showTimePicker) { CustomTimePickerDialog( initialHour = calendar.get(Calendar.HOUR_OF_DAY), initialMinute = calendar.get(Calendar.MINUTE), onDismiss = { showTimePicker = false }, onTimeSelected = { hour, minute -> calendar.set(Calendar.HOUR_OF_DAY, hour) calendar.set(Calendar.MINUTE, minute) selectedDateMillis = calendar.timeInMillis showTimePicker = false } ) } androidx.compose.material3.ModalBottomSheet( onDismissRequest = onDismiss, sheetState = sheetState, dragHandle = { androidx.compose.material3.BottomSheetDefaults.DragHandle() }, containerColor = MaterialTheme.colorScheme.surface ) { Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp)) { Row(modifier = Modifier.fillMaxWidth()) { Column(modifier = Modifier.weight(1f)) { ActionItem(Icons.Default.Event, "Date d'échéance") { } ActionItem(Icons.Default.Schedule, "Heure d'échéance") { showTimePicker = true } ActionItem(Icons.Default.Close, "Aucune date", tint = MaterialTheme.colorScheme.primary) { onDateSelected(null) onDismiss() } } Column(modifier = Modifier.weight(1f)) { ActionItem(androidx.compose.material.icons.outlined.Coffee, "9 h 00 a.m.") { calendar.set(Calendar.HOUR_OF_DAY, 9) calendar.set(Calendar.MINUTE, 0) selectedDateMillis = calendar.timeInMillis } ActionItem(androidx.compose.material.icons.outlined.WbSunny, "1 h 00 p.m.") { calendar.set(Calendar.HOUR_OF_DAY, 13) calendar.set(Calendar.MINUTE, 0) selectedDateMillis = calendar.timeInMillis } ActionItem(androidx.compose.material.icons.outlined.WbTwilight, "5 h 00 p.m.") { calendar.set(Calendar.HOUR_OF_DAY, 17) calendar.set(Calendar.MINUTE, 0) selectedDateMillis = calendar.timeInMillis } ActionItem(androidx.compose.material.icons.outlined.NightsStay, "8 h 00 p.m.") { calendar.set(Calendar.HOUR_OF_DAY, 20) calendar.set(Calendar.MINUTE, 0) selectedDateMillis = calendar.timeInMillis } ActionItem(Icons.Default.Schedule, "Choisir l'heure") { showTimePicker = true } ActionItem(Icons.Default.Close, "Aucune heure", tint = MaterialTheme.colorScheme.primary) { calendar.set(Calendar.HOUR_OF_DAY, 0) calendar.set(Calendar.MINUTE, 0) selectedDateMillis = calendar.timeInMillis } } } Spacer(modifier = Modifier.height(16.dp)) androidx.compose.material3.DatePicker( state = datePickerState, showModeToggle = false, title = null, headline = null, modifier = Modifier.fillMaxWidth() ) Row( modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp, top = 8.dp), horizontalArrangement = Arrangement.End ) { TextButton(onClick = onDismiss) { Text("Annuler") } Spacer(modifier = Modifier.width(8.dp)) TextButton(onClick = { onDateSelected(selectedDateMillis) onDismiss() }) { Text("Valider") } } } } } @Composable private fun ActionItem( icon: androidx.compose.ui.graphics.vector.ImageVector, text: String, tint: androidx.compose.ui.graphics.Color = MaterialTheme.colorScheme.onSurfaceVariant, onClick: () -> Unit ) { Row( modifier = Modifier .fillMaxWidth() .clickable(onClick = onClick) .padding(vertical = 12.dp, horizontal = 4.dp), verticalAlignment = Alignment.CenterVertically ) { Icon(icon, contentDescription = null, modifier = Modifier.size(20.dp), tint = tint) Spacer(modifier = Modifier.width(12.dp)) Text(text, style = MaterialTheme.typography.bodyMedium, color = tint) } } @OptIn(ExperimentalMaterial3Api::class) @Composable private fun CustomTimePickerDialog( initialHour: Int, initialMinute: Int, onDismiss: () -> Unit, onTimeSelected: (Int, Int) -> Unit ) { val context = androidx.compose.ui.platform.LocalContext.current val prefs = context.getSharedPreferences("time_picker_prefs", android.content.Context.MODE_PRIVATE) var isDialMode by remember { mutableStateOf(prefs.getBoolean("is_dial_mode", true)) } val timePickerState = androidx.compose.material3.rememberTimePickerState( initialHour = initialHour, initialMinute = initialMinute, is24Hour = false ) androidx.compose.ui.window.Dialog(onDismissRequest = onDismiss) { Surface( shape = MaterialTheme.shapes.extraLarge, color = MaterialTheme.colorScheme.surface, modifier = Modifier.wrapContentSize() ) { Column( modifier = Modifier.padding(24.dp), horizontalAlignment = Alignment.CenterHorizontally ) { if (isDialMode) { androidx.compose.material3.TimePicker(state = timePickerState) } else { androidx.compose.material3.TimeInput(state = timePickerState) } Spacer(modifier = Modifier.height(24.dp)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { IconButton(onClick = { isDialMode = !isDialMode prefs.edit().putBoolean("is_dial_mode", isDialMode).apply() }) { Icon( if (isDialMode) androidx.compose.material.icons.outlined.Keyboard else Icons.Default.Schedule, contentDescription = "Changer de mode" ) } Row { TextButton(onClick = onDismiss) { Text("Annuler") } Spacer(modifier = Modifier.width(8.dp)) TextButton(onClick = { onTimeSelected(timePickerState.hour, timePickerState.minute) }) { Text("Valider") } } } } } } } \"\"\" new_lines = lines[:640] + [replacement + \"\\n\"] + lines[918:] with open('c:/dev/git/Android/ShaarIt/app/src/main/java/com/shaarit/presentation/todo/TodoScreen.kt', 'w', encoding='utf-8') as f: f.writelines(new_lines) print('Success')