Penjelasan Aplikasi To Do List
1️⃣.Screenshoot Program Aplikasi To Do List
2️⃣ Potongan Kode Program (yang ngontrol tampilan)
Bagian ini fokus ke UI utama yang biasanya muncul di screenshot: card tiap tugas (ListTile), layar utama (build) dan sheet tambah/edit tugas (AddTaskSheet).
// --- Potongan: Task item (card + listtile) ---
Widget _buildTaskItem(Task t) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: ListTile(
onTap: () => _openAddTaskSheet(editTask: t),
leading: CircleAvatar(
backgroundColor: t.color,
child: Icon(
t.done ? Icons.check : Icons.schedule,
color: Colors.white,
),
),
title: Text(
t.title,
style: TextStyle(
fontWeight: FontWeight.w600,
decoration: t.done ? TextDecoration.lineThrough : null,
),
),
subtitle: Text('${_formatTime(t.time)} • ${t.category}'),
trailing: Wrap(
spacing: 4,
children: [
IconButton(
icon: Icon(
t.done ? Icons.check_circle : Icons.circle_outlined,
color: t.done ? Colors.green : Colors.grey,
),
onPressed: () => _toggleDone(t),
),
IconButton(
icon: const Icon(Icons.delete_forever_rounded, color: Colors.redAccent),
onPressed: () => _deleteTask(t),
),
],
),
),
);
}
// --- Potongan: Scaffold utama (list / empty state) ---
@override
Widget build(BuildContext context) {
final now = DateTime.now();
final dateLabel = '${now.day}/${now.month}/${now.year}';
return Scaffold(
appBar: AppBar(
title: const Text("Jadwal Harian"),
actions: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Center(
child: Text(dateLabel, style: const TextStyle(fontSize: 14)),
),
)
],
),
body: _tasks.isEmpty
? _buildEmpty()
: ListView.builder(
itemCount: _tasks.length,
itemBuilder: (context, i) => _buildTaskItem(_tasks[i]),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () => _openAddTaskSheet(),
icon: const Icon(Icons.add),
label: const Text("Tambah"),
),
);
}
// --- Potongan: Bottom sheet tambah / edit tugas (form) ---
class AddTaskSheet extends StatefulWidget {
final void Function(String, TimeOfDay, String, Color) onSubmit;
final Task? existing;
const AddTaskSheet({super.key, required this.onSubmit, this.existing});
// ...
}
class _AddTaskSheetState extends State<AddTaskSheet> {
final _formKey = GlobalKey<FormState>();
late String _title;
late TimeOfDay _time;
late String _category;
late Color _color;
final _categories = ['Rutinitas', 'Pribadi', 'Kerja', 'Belajar', 'Lainnya'];
final _colors = [
Colors.purple.shade300,
Colors.orange.shade300,
Colors.teal.shade300,
Colors.blueGrey.shade300,
Colors.pink.shade300,
];
@override
void initState() {
super.initState();
if (widget.existing != null) {
_title = widget.existing!.title;
_time = widget.existing!.time;
_category = widget.existing!.category;
_color = widget.existing!.color;
} else {
_title = '';
_time = TimeOfDay.now();
_category = _categories.first;
_color = _colors.first;
}
}
Future<void> _pickTime() async {
final picked = await showTimePicker(context: context, initialTime: _time);
if (picked != null) setState(() => _time = picked);
}
@override
Widget build(BuildContext context) {
final isEdit = widget.existing != null;
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
top: 16,
left: 16,
right: 16,
),
child: Form(
key: _formKey,
child: ListView(
shrinkWrap: true,
children: [
Text(isEdit ? "Edit Tugas" : "Tambah Tugas",
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
TextFormField(
initialValue: _title,
decoration: const InputDecoration(
labelText: 'Nama Tugas',
border: OutlineInputBorder(),
),
validator: (v) =>
v == null || v.trim().isEmpty ? 'Masukkan nama tugas' : null,
onChanged: (v) => _title = v,
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: InkWell(
onTap: _pickTime,
child: InputDecorator(
decoration: const InputDecoration(
labelText: 'Waktu',
border: OutlineInputBorder(),
),
child: Row(
children: [
const Icon(Icons.access_time),
const SizedBox(width: 8),
Text(_time.format(context)),
],
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: DropdownButtonFormField<String>(
value: _category,
items: _categories
.map((c) => DropdownMenuItem(value: c, child: Text(c)))
.toList(),
onChanged: (v) => setState(() => _category = v!),
decoration: const InputDecoration(
labelText: 'Kategori',
border: OutlineInputBorder(),
),
),
),
],
),
const SizedBox(height: 12),
Wrap(
spacing: 8,
children: _colors
.map((c) => GestureDetector(
onTap: () => setState(() => _color = c),
child: Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: c,
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: _color == c ? Colors.black54 : Colors.transparent,
width: 2,
),
),
),
))
.toList(),
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: () {
if (_formKey.currentState!.validate()) {
widget.onSubmit(_title.trim(), _time, _category, _color);
}
},
icon: Icon(isEdit ? Icons.save : Icons.add),
label: Text(isEdit ? 'Simpan Perubahan' : 'Tambah Tugas'),
),
const SizedBox(height: 10),
],
),
),
);
}
}
3️⃣ Penjelasan Cara Kerja
Oke ini penjelasannya biar bisa kamu salin ke posting blog:
Tampilan daftar tugas: tiap tugas dirender sebagai Card yang berisi ListTile. Di kiri ada CircleAvatar warna tugas, di tengah judul + subtitle (waktu & kategori), dan di kanan ada tombol untuk toggle done & hapus. Ketuk ListTile = buka sheet edit.
Tambah / Edit tugas: pake showModalBottomSheet yang menampilkan AddTaskSheet. Form di sheet ini punya:
TextFormField untuk nama tugas (wajib diisi),
InkWell + showTimePicker untuk pilih jam,
DropdownButtonFormField untuk kategori,
pilihan warna (warna- warna kecil) yang bisa tap untuk pilih warna,
tombol submit (Tambah / Simpan) yang validasi form lalu panggil callback onSubmit.
Logika data:
Saat awal aplikasi initState() manggil _addSampleTasks() untuk mengisi contoh tugas (biar gak kosong).
Semua tugas disimpan di _tasks (List<Task>).
Setelah nambah atau edit, panggil _sortTasks() yang mengurutkan tugas berdasarkan jam supaya daftar terurut dari pagi ke malam.
Tombol check toggle memanggil _toggleDone() yang menandai tugas selesai/ belum, dan perubahan ini langsung dipanggil setState() agar UI update.
Hapus tugas pake _deleteTask() yang pertama tampilkan AlertDialog konfirmasi; kalau user pilih “Hapus”, tugas di-remove dan snack bar muncul.
UX kecil yang enak:
Waktu ditampilkeun pake _formatTime(...) biar selalu HH:MM.
Saat sheet muncul, padding bawah menyesuaikan MediaQuery.of(context).viewInsets.bottom supaya form gak ketutup keyboard.
Warna tiap tugas membantu visual untuk membedakan kategori/jenis tugas.

Komentar
Posting Komentar