Membangun To-Do List dengan Fitur Custom Background & Tema Dinamis

 🚀 Level Up UI Flutter: Membangun To-Do List dengan Fitur Custom Background & Tema Dinamis




Halo para pengembang! Pernahkah kalian merasa bosan dengan tampilan aplikasi To-Do List yang itu-itu saja? Putih, kaku, dan membosankan. Hari ini, saya akan berbagi proyek seru: Fito's To-Do List, sebuah aplikasi produktivitas yang mengutamakan personalisasi visual bagi penggunanya.

Di artikel ini, kita akan membedah bagaimana logika Flutter bekerja untuk mengubah warna tema dan latar belakang secara real-time.


✨ Apa yang Spesial dari Aplikasi Ini?

Aplikasi ini bukan sekadar pencatat tugas. Saya menyematkan beberapa fitur kustomisasi tingkat lanjut:

  1. Dynamic AppBar Color: Bosan dengan warna biru? Pengguna bisa mengganti warna tema (Biru, Hijau, Ungu, Merah) hanya dengan dua klik.

  2. Image Background via URL: Kamu bisa memasukkan link foto favoritmu dari internet dan menjadikannya latar belakang aplikasi.

  3. Modern Glassmorphism UI: List tugas didesain dengan efek semi-transparan agar tetap kontras namun tetap memperlihatkan keindahan gambar di belakangnya.


🛠️ Bedah Arsitektur UI (The Logic Behind)

Untuk menciptakan tampilan yang menarik, saya menggunakan teknik Layering menggunakan widget Stack. Mari kita lihat bagaimana struktur ini disusun:

1. Rahasia Latar Belakang yang Elegan

Inti dari kustomisasi gambar ada pada penggunaan URL dinamis. Dengan memanfaatkan Image.network(), aplikasi akan menarik gambar dari web. Namun, agar teks tetap terbaca, saya menambahkan Color Filter:

Dart
color: Colors.black.withOpacity(0.2),
colorBlendMode: BlendMode.darken,

Teknik ini berfungsi seperti "kaca film" yang menggelapkan gambar sehingga konten di atasnya tetap menonjol.

2. State Management untuk Tema Warna

Aplikasi ini menggunakan setState yang efisien untuk mendengarkan perubahan warna yang dipilih pengguna melalui PopupMenuButton. Ketika warna dipilih, variabel appBarColor akan terupdate dan secara otomatis mengubah:

  • Gradien warna pada AppBar.

  • Warna tombol "Add".

  • Warna Checkbox aktif.


💻Kode Utama

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: TodoPage(),
    );
  }
}

class TodoItem {
  String text;
  bool isDone;
  TodoItem(this.text, {this.isDone = false});
}

class TodoPage extends StatefulWidget {
  @override
  _TodoPageState createState() => _TodoPageState();
}

class _TodoPageState extends State<TodoPage> {
  TextEditingController todoController = TextEditingController();
  TextEditingController urlController = TextEditingController();
  List<TodoItem> todoList = [];
  Color appBarColor = Colors.blue;
  String? backgroundImageUrl;

  void tambahTodo() {
    if (todoController.text.isNotEmpty) {
      setState(() {
        todoList.add(TodoItem(todoController.text));
        todoController.clear();
      });
    }
  }

  void hapusTodo(int index) {
    setState(() {
      todoList.removeAt(index);
    });
  }

  void toggleSelesai(int index) {
    setState(() {
      todoList[index].isDone = !todoList[index].isDone;
    });
  }

  void gantiWarna(Color warnaBaru) {
    setState(() {
      appBarColor = warnaBaru;
    });
  }

  void showImageDialog() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text("Masukkan URL Foto"),
        content: TextField(
          controller: urlController,
          decoration: InputDecoration(
            hintText: "https://example.com/gambar.jpg",
            border: OutlineInputBorder(),
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: Text("Batal"),
          ),
          ElevatedButton(
            onPressed: () {
              setState(() {
                backgroundImageUrl = urlController.text;
              });
              urlController.clear();
              Navigator.pop(context);
            },
            child: Text("Simpan"),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        elevation: 0,
        centerTitle: true,
        title: Text(
          "To Do List - Fito",
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        flexibleSpace: Container(
          decoration: BoxDecoration(
            gradient: LinearGradient(
              colors: [appBarColor, appBarColor.withOpacity(0.7)],
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
            ),
          ),
        ),
        actions: [
          PopupMenuButton<dynamic>(
            icon: Icon(Icons.more_vert),
            onSelected: (value) {
              if (value is Color) {
                gantiWarna(value);
              } else if (value == "ganti_foto") {
                showImageDialog();
              } else if (value == "reset_foto") {
                setState(() => backgroundImageUrl = null);
              }
            },
            itemBuilder: (context) => <PopupMenuEntry<dynamic>>[
              PopupMenuItem(value: Colors.blue, child: Text("Tema Biru")),
              PopupMenuItem(value: Colors.green, child: Text("Tema Hijau")),
              PopupMenuItem(value: Colors.purple, child: Text("Tema Ungu")),
              PopupMenuItem(value: Colors.red, child: Text("Tema Merah")),
              PopupMenuItem(value: Colors.yellow, child: Text("Tema Kuning")),
              PopupMenuItem(value: Colors.white, child: Text("Tema Kuning")),
              PopupMenuItem(value: Colors.purple, child: Text("Tema Kuning")),
              const PopupMenuDivider(),
              PopupMenuItem(
                value: "ganti_foto",
                child: Row(
                  children: [
                    Icon(Icons.image, size: 20, color: Colors.grey),
                    SizedBox(width: 8),
                    Text("Ganti Background"),
                  ],
                ),
              ),
              PopupMenuItem(
                value: "reset_foto",
                child: Row(
                  children: [
                    Icon(Icons.delete_forever, size: 20, color: Colors.red),
                    SizedBox(width: 8),
                    Text("Hapus Background"),
                  ],
                ),
              ),
            ],
          ),
        ],
      ),
      body: Stack(
        children: [
          // Background Image Layer
          if (backgroundImageUrl != null && backgroundImageUrl!.isNotEmpty)
            Positioned.fill(
              child: Image.network(
                backgroundImageUrl!,
                fit: BoxFit.cover,
                errorBuilder: (context, error, stackTrace) {
                  return Container(color: Colors.white); // Jika URL salah
                },
                color: Colors.black.withOpacity(0.2),
                colorBlendMode: BlendMode.darken,
              ),
            ),
         
          // Content Layer
          Column(
            children: [
              Container(
                padding: EdgeInsets.all(20),
                width: double.infinity,
                decoration: BoxDecoration(
                  color: Colors.white.withOpacity(0.8),
                  borderRadius: BorderRadius.vertical(bottom: Radius.circular(25)),
                ),
                child: Text(
                  "Hari ini mau ngapain? 🚀",
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
                ),
              ),
              Padding(
                padding: EdgeInsets.all(16),
                child: Row(
                  children: [
                    Expanded(
                      child: TextField(
                        controller: todoController,
                        decoration: InputDecoration(
                          hintText: "Tambahkan tugas baru...",
                          filled: true,
                          fillColor: Colors.white.withOpacity(0.9),
                          border: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(15),
                            borderSide: BorderSide.none,
                          ),
                        ),
                      ),
                    ),
                    SizedBox(width: 10),
                    ElevatedButton(
                      onPressed: tambahTodo,
                      style: ElevatedButton.styleFrom(
                        backgroundColor: appBarColor,
                        padding: EdgeInsets.all(18),
                        shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(15),
                        ),
                      ),
                      child: Icon(Icons.add, color: Colors.white),
                    ),
                  ],
                ),
              ),
              Expanded(
                child: todoList.isEmpty
                    ? Center(
                        child: Text(
                          "Belum ada tugas ✨",
                          style: TextStyle(
                            color: backgroundImageUrl != null ? Colors.white : Colors.grey,
                            fontWeight: FontWeight.bold,
                            backgroundColor: Colors.black12,
                          ),
                        ),
                      )
                    : ListView.builder(
                        itemCount: todoList.length,
                        itemBuilder: (context, index) {
                          final item = todoList[index];
                          return AnimatedContainer(
                            duration: Duration(milliseconds: 300),
                            margin: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
                            decoration: BoxDecoration(
                              color: item.isDone
                                  ? Colors.green.withOpacity(0.7)
                                  : Colors.white.withOpacity(0.85),
                              borderRadius: BorderRadius.circular(15),
                              boxShadow: [
                                BoxShadow(
                                  color: Colors.black12,
                                  blurRadius: 6,
                                  offset: Offset(0, 3),
                                )
                              ],
                            ),
                            child: ListTile(
                              leading: Checkbox(
                                value: item.isDone,
                                activeColor: appBarColor,
                                onChanged: (_) => toggleSelesai(index),
                              ),
                              title: Text(
                                item.text,
                                style: TextStyle(
                                  fontSize: 16,
                                  decoration: item.isDone
                                      ? TextDecoration.lineThrough
                                      : TextDecoration.none,
                                ),
                              ),
                              trailing: IconButton(
                                icon: Icon(Icons.delete, color: Colors.red),
                                onPressed: () => hapusTodo(index),
                              ),
                            ),
                          );
                        },
                      ),
              ),
            ],
          ),
        ],
      ),
    );
  }

}🎨 UI yang User-Friendly

Untuk kenyamanan pengguna, saya menggunakan beberapa elemen desain modern:

  • AnimatedContainer: Memberikan transisi halus saat item list berubah status.

  • Rounded Corners: Memberikan kesan lembut pada setiap kartu tugas dan input field.

  • Shadows (BoxShadow): Memberikan efek kedalaman (depth) agar aplikasi tidak terlihat "flat".


🎯 Kesimpulan

Membangun aplikasi yang fungsional itu bagus, tapi membangun aplikasi yang personal itu jauh lebih keren. Melalui proyek ini, saya belajar banyak tentang cara mengelola state yang dinamis dan bagaimana Stack widget bisa mengubah tampilan aplikasi secara total.

Tertarik mencoba? Kamu hanya butuh Flutter SDK dan sedikit kreativitas untuk mulai mengutak-atik kode ini!

LINK APLIKASI TODO-LIST JIKA INGIN MENCOBA

https://zhho06m2hhp0.zapp.page/#/

Komentar

Postingan populer dari blog ini

MEMBUAT TAMPILAN APLIKASI SEDERANA MENGGUNAKAN DART FLUTTER

Aplication Flutter With Image 7/31/2025