I want the radio buttons to be hidden, and only appear when the TextField is focused/selected. I tried Visibility(visible: _textFocus.hasFocus || isEditing,
to show the row of radio buttons when I the textField has focus either to create a new task, or editing an existing task. But that didnt work. I also tried putting the row in a container and that does nothing.
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
//import 'package:testy/page2.dart';
void main() {
runApp(MaterialApp(
home: const TodoApp(),
theme: ThemeData(primarySwatch: Colors.yellow),
));
}
class Todo {
final String text;
final bool checked;
int priority;
static int _tasknumber = 0;
final int key;
//TODO: UNDERSTAND THIS
Todo({
required this.text,
required this.checked,
required this.priority,
}) : key = _tasknumber++;
}
class TodoApp extends StatefulWidget {
const TodoApp({super.key});
@override
State<TodoApp> createState() => _TodoAppState();
}
class _TodoAppState extends State<TodoApp> {
final _textyController = TextEditingController();
List<Todo> data = [];
final FocusNode _textFocus = FocusNode();
int? _radioValue = 1;
bool isEditing = false;
int editingIndex =
-1; // Initialize as -1 to indicate no active edit initially.
//clear completed tasks
void deleteComplete() {
List<Todo> completedTasks = data.where((todo) => todo.checked).toList();
setState(() {
data.removeWhere((todo) => todo.checked);
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('${completedTasks.length} completed tasks deleted'),
duration: const Duration(seconds: 4),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
setState(() {
// Re-insert the completed tasks
data.insertAll(0, completedTasks);
});
},
),
),
);
}
//Untick all tasks
void untickAll() {
// Create a copy of the tasks before unticking them.
List<Todo> tasksBeforeUntick = List.from(data);
setState(() {
for (int index = 0; index < data.length; index++) {
if (data[index].checked) {
data[index] = Todo(
text: data[index].text,
checked: false, // Untick the task
priority: data[index].priority,
);
}
}
});
// Show a Snackbar with an "Undo" option.
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('All completed tasks unticked'),
duration: const Duration(seconds: 4),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
// Restore the tasks to their previous state.
setState(() {
data = List.from(tasksBeforeUntick);
});
},
),
),
);
}
//undo deleted tasks
void deleteTasks(int index, Todo itemToDelete) {
setState(() {
data.removeAt(index);
});
// Show a SnackBar with an "Undo" action.
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Task deleted'),
duration: const Duration(seconds: 4),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
setState(() {
data.insert(index, itemToDelete);
});
},
),
),
);
}
@override
Widget build(context) {
data.sort((a, b) {
if (a.checked == b.checked) {
// If checked status is the same, sort by priority
if (a.priority == b.priority) {
return 0; // Same priority
} else if (a.priority < b.priority) {
return -1; // Sort by ascending priority
} else {
return 1; // Sort by descending priority
}
} else if (a.checked) {
return 1; // Move completed tasks to the bottom
} else {
return -1; // Keep incomplete tasks at the top
}
});
priorityLabel(int priority) {
if (priority == 1) {
return 'High';
} else if (priority == 2) {
return 'Medium';
} else {
return 'Low';
}
}
Color tileColor(Todo todo) {
if (todo.checked) {
// If the task is checked, use a grey color.
return Colors.grey.withOpacity(0.2);
} else {
// If the task is not checked, determine the color based on priority.
if (todo.priority == 1) {
return Colors.red[300] ?? Colors.red; // High priority
} else if (todo.priority == 2) {
return Colors.orange[300] ?? Colors.yellow; // Medium priority
} else {
return Colors.yellow[300] ?? Colors.green; // Low priority
}
}
}
return Scaffold(
backgroundColor: Colors.yellow[100],
appBar: AppBar(
title: const Text("Todo"),
centerTitle: true,
elevation: 0,
),
body: Column(
children: [
ElevatedButton(
onPressed: deleteComplete,
child: Text('Clear Completed'),
),
ElevatedButton(
onPressed: untickAll,
child: Text('Untick All'),
),
Expanded(
//
// LIST STARTS
//
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 8.0, horizontal: (15.0)),
child: ReorderableListView.builder(
reverse: false,
itemCount: data.length,
onReorder: (oldIndex, newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final Todo item = data.removeAt(oldIndex);
data.insert(newIndex, item);
});
},
itemBuilder: (context, index) {
if (data.isEmpty) {
// Handle the case when the list is empty
return const ListTile(
title: Text("No tasks to display"),
);
} else {
//
//{STYLING} LIST TILE STARTS
//
return Container(
key: ValueKey(data[index].key),
padding: const EdgeInsets.symmetric(vertical: 10),
child: Slidable(
endActionPane: ActionPane(
motion: const ScrollMotion(),
children: [
SlidableAction(
backgroundColor: Colors.green,
icon: Icons.edit,
label: 'Edit',
onPressed: (context) => {
setState(() {
isEditing = true;
editingIndex = index;
_textyController.text = data[index].text;
_radioValue = data[index].priority;
}),
},
),
SlidableAction(
backgroundColor: Colors.red[400] ?? Colors.red,
icon: Icons.delete_forever,
label: 'Delete',
onPressed: (context) => {
setState(() {
if (isEditing &&
editingIndex >= 0 &&
editingIndex < data.length) {
isEditing = false;
_textyController.text = '';
_radioValue = 1;
}
deleteTasks(index, data[index]);
}),
},
),
],
),
child: Container(
decoration: BoxDecoration(
//borderRadius: BorderRadius.circular(10),
color: tileColor(data[index]),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
//
//LIST TILE STARTS
//
child: CheckboxListTile(
activeColor: Colors.black,
controlAffinity:
ListTileControlAffinity.leading,
title: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
data[index].text,
style: TextStyle(
color: data[index].checked
? Colors.grey.withOpacity(0.6)
: Colors.black,
decoration: data[index].checked
? TextDecoration.lineThrough
: TextDecoration.none),
),
),
Text(
data[index].checked ? '' : '${index + 1}',
style: TextStyle(
color: Colors.black.withOpacity(0.1),
fontSize: 21,
fontWeight: FontWeight.w800),
),
],
),
subtitle:
Text(priorityLabel((data[index].priority))),
value: data[index].checked,
onChanged: (newCheckedValue) {
setState(() {
data[index] = Todo(
text: data[index].text,
checked: newCheckedValue ?? false,
priority: data[index].priority,
);
});
},
),
),
)),
);
}
},
),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 15),
child: Column(
children: [
Visibility(
visible: _textFocus.hasFocus || isEditing,
child: Container(
color: Colors.red,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Radio(
value: 1,
groupValue: isEditing
? data[editingIndex].priority
: _radioValue,
onChanged: (changedRadio) {
setState(() {
if (isEditing) {
data[editingIndex] = Todo(
text: _textyController.text,
checked: data[editingIndex].checked,
priority: changedRadio ?? 1,
);
} else {
_radioValue = changedRadio;
}
});
},
),
Text('High'),
],
),
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Radio(
value: 2,
groupValue: isEditing
? data[editingIndex].priority
: _radioValue,
onChanged: (changedRadio) {
setState(() {
if (isEditing) {
data[editingIndex] = Todo(
text: _textyController.text,
checked: data[editingIndex].checked,
priority: changedRadio ?? 1,
);
} else {
_radioValue = changedRadio;
}
});
},
),
Text('Medium'),
],
),
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Radio(
value: 3,
groupValue: isEditing
? data[editingIndex].priority
: _radioValue,
onChanged: (changedRadio) {
setState(() {
if (isEditing) {
data[editingIndex] = Todo(
text: _textyController.text,
checked: data[editingIndex].checked,
priority: changedRadio ?? 1,
);
} else {
_radioValue = changedRadio;
}
});
},
),
Text('Low'),
],
),
],
),
),
),
TextField(
focusNode: _textFocus,
controller: isEditing
? TextEditingController(text: data[editingIndex].text)
: _textyController,
onSubmitted: (value) {
_textFocus.requestFocus();
setState(() {
if (isEditing) {
data[editingIndex] = Todo(
text: value,
checked: data[editingIndex].checked,
priority: _radioValue ?? 1,
);
isEditing = false;
} else {
data.insert(
0,
Todo(
text: value,
checked: false,
priority: _radioValue ?? 1,
));
}
_textyController.clear();
for (var todo in data) {
print(
'Text: ${todo.text}, Completed: ${todo.checked}, Priority: ${todo.priority}, Key: ${todo.key}');
}
});
},
decoration: const InputDecoration(
labelText: '+ Add a task',
labelStyle: TextStyle(
color: Colors.grey,
),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusColor: Colors.blue,
),
cursorColor: Colors.grey[400],
),
],
),
),
],
),
);
}
}
2
Answers
You can make an additional boolean variable to control the visibility of the
Container
and listen to theFocusNode
changes by usingaddListener
:You should try this and make sure to use TextFromField instead TextField: