Ngulik To-Do List Monokrom: Simple, Elegan, Tapi Tetep GG!

 Bro, jadi kemarin gue iseng pengen ngulik project kecil, tapi pengen tampilannya tuh kalem, elegan, ga rame warna, gitu deh — jadilah gue bikin To-Do List Monokrom ini.

Jujur, ide awalnya cuma pengen "nyatet kerjaan biar ga numpuk", tapi lama-lama malah jadi seru wkwk. Gue set tema hitam-abu minimalis, biar kesannya kayak app yang serius tapi tetep chill gitu.

Fitur-fiturnya sih ga ribet:

  • ✅ Bisa nambah & hapus task

  • πŸ’Ύ Data ke-save otomatis

  • πŸ•’ Ada waktu buat tiap tugas

  • πŸ’« Animasi transisi halus biar ga kaku

Dan yang paling penting, tampilannya tuh clean banget — kayak workspace developer yang ga suka rame-rame tapi tetep stylish. Ga perlu warna gonjreng, cukup monokrom aja, udah keliatan elegan.

Pas udah jadi, gue liat hasilnya...
anjir, keren juga ya kalo simple tapi rapih! 😎
Kayak lo ga berusaha tampil keren, tapi emang udah keren aja — ngerti kan maksud gue? πŸ˜†

Pokoknya ini project cocok banget buat lo yang pengen:

  • Latihan Flutter dengan style clean

  • Pengen app fungsional tapi ga ribet

  • Dan pastinya suka vibes monokrom yang calm tapi classy.

Nih code nya bro :

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';

void main() => runApp(const JarvisTodoApp());

class JarvisTodoApp extends StatelessWidget {
  const JarvisTodoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Jarvis Todo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: const Color(0xFF121212),
        colorScheme: const ColorScheme.dark(
          primary: Colors.white,
          secondary: Colors.grey,
          background: Color(0xFF121212),
        ),
        textTheme: const TextTheme(
          bodyMedium: TextStyle(color: Colors.white70),
        ),
      ),
      home: const TodoHome(),
    );
  }
}

class TodoItem {
  final String title;
  final DateTime date;
  bool done;

  TodoItem({
    required this.title,
    required this.date,
    this.done = false,
  });

  Map<String, dynamic> toJson() => {
        'title': title,
        'date': date.toIso8601String(),
        'done': done,
      };

  static TodoItem fromJson(Map<String, dynamic> json) => TodoItem(
        title: json['title'],
        date: DateTime.parse(json['date']),
        done: json['done'],
      );
}

class TodoHome extends StatefulWidget {
  const TodoHome({super.key});

  @override
  State<TodoHome> createState() => _TodoHomeState();
}

class _TodoHomeState extends State<TodoHome> with TickerProviderStateMixin {
  final List<TodoItem> _todos = [];
  final TextEditingController _controller = TextEditingController();
  DateTime? _selectedTime;

  @override
  void initState() {
    super.initState();
    _loadTodos();
  }

  Future<void> _loadTodos() async {
    final prefs = await SharedPreferences.getInstance();
    final data = prefs.getString('todos');
    if (data != null) {
      final List list = jsonDecode(data);
      setState(() {
        _todos.addAll(list.map((e) => TodoItem.fromJson(e)).toList());
      });
    }
  }

  Future<void> _saveTodos() async {
    final prefs = await SharedPreferences.getInstance();
    final data = jsonEncode(_todos.map((e) => e.toJson()).toList());
    await prefs.setString('todos', data);
  }

  void _addTodo() async {
    if (_controller.text.trim().isEmpty || _selectedTime == null) return;

    setState(() {
      _todos.insert(
        0,
        TodoItem(title: _controller.text.trim(), date: _selectedTime!),
      );
    });
    _controller.clear();
    _selectedTime = null;
    await _saveTodos();
    if (mounted) Navigator.pop(context);
  }

  void _showAddDialog() {
    _controller.clear();
    _selectedTime = DateTime.now().add(const Duration(hours: 1));

    showModalBottomSheet(
      context: context,
      backgroundColor: const Color(0xFF1E1E1E),
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
      ),
      builder: (context) => Padding(
        padding: const EdgeInsets.all(16.0),
        child: Wrap(
          runSpacing: 12,
          children: [
            const Text(
              "Tambah To-Do",
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            TextField(
              controller: _controller,
              style: const TextStyle(color: Colors.white),
              decoration: const InputDecoration(
                labelText: "Judul",
                labelStyle: TextStyle(color: Colors.grey),
                enabledBorder: UnderlineInputBorder(
                  borderSide: BorderSide(color: Colors.grey),
                ),
              ),
            ),
            Row(
              children: [
                Expanded(
                  child: Text(
                    "Waktu: ${DateFormat('EEE, d MMM HH:mm').format(_selectedTime!)}",
                  ),
                ),
                TextButton(
                  onPressed: () async {
                    final date = await showDatePicker(
                      context: context,
                      initialDate: _selectedTime!,
                      firstDate: DateTime.now(),
                      lastDate: DateTime(2100),
                    );
                    if (date == null) return;
                    final time = await showTimePicker(
                      context: context,
                      initialTime: TimeOfDay.fromDateTime(_selectedTime!),
                    );
                    if (time == null) return;
                    setState(() {
                      _selectedTime = DateTime(
                        date.year,
                        date.month,
                        date.day,
                        time.hour,
                        time.minute,
                      );
                    });
                  },
                  child: const Text("Ubah Waktu"),
                ),
              ],
            ),
            const SizedBox(height: 8),
            ElevatedButton(
              onPressed: _addTodo,
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.white,
                foregroundColor: Colors.black,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(12),
                ),
                minimumSize: const Size(double.infinity, 45),
              ),
              child: const Text("Tambah"),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _toggleDone(int index) async {
    setState(() {
      _todos[index].done = !_todos[index].done;
    });
    await _saveTodos();
  }

  Future<void> _deleteTodo(int index) async {
    setState(() {
      _todos.removeAt(index);
    });
    await _saveTodos();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("To-Do List"),
        backgroundColor: Colors.transparent,
        centerTitle: true,
        elevation: 0,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _showAddDialog,
        backgroundColor: Colors.white,
        child: const Icon(Icons.add, color: Colors.black),
      ),
      body: _todos.isEmpty
          ? const Center(
              child: Text(
                "Belum ada to-do.\nTekan + buat nambah.",
                textAlign: TextAlign.center,
                style: TextStyle(color: Colors.grey),
              ),
            )
          : ListView.builder(
              physics: const BouncingScrollPhysics(),
              padding: const EdgeInsets.all(12),
              itemCount: _todos.length,
              itemBuilder: (context, index) {
                final todo = _todos[index];
                return AnimatedContainer(
                  duration: const Duration(milliseconds: 300),
                  curve: Curves.easeInOut,
                  margin: const EdgeInsets.symmetric(vertical: 6),
                  decoration: BoxDecoration(
                    color: todo.done
                        ? const Color(0xFF2B2B2B)
                        : const Color(0xFF1A1A1A),
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(
                      color: todo.done
                          ? Colors.grey.shade800
                          : Colors.grey.shade700,
                    ),
                  ),
                  child: ListTile(
                    onTap: () => _toggleDone(index),
                    leading: AnimatedSwitcher(
                      duration: const Duration(milliseconds: 300),
                      child: todo.done
                          ? const Icon(Icons.check_circle,
                              color: Colors.white, key: ValueKey('done'))
                          : const Icon(Icons.circle_outlined,
                              color: Colors.grey, key: ValueKey('notdone')),
                    ),
                    title: Text(
                      todo.title,
                      style: TextStyle(
                        fontSize: 16,
                        decoration: todo.done
                            ? TextDecoration.lineThrough
                            : TextDecoration.none,
                        color: todo.done
                            ? Colors.grey
                            : Colors.white.withOpacity(0.9),
                      ),
                    ),
                    subtitle: Text(
                      DateFormat('EEE, d MMM • HH:mm').format(todo.date),
                      style: const TextStyle(color: Colors.grey),
                    ),
                    trailing: IconButton(
                      icon:
                          const Icon(Icons.delete_outline, color: Colors.grey),
                      onPressed: () => _deleteTodo(index),
                    ),
                  ),
                );
              },
            ),
    );
  }
}

🧩 Penutup:

Project kecil, tapi banyak pelajaran.
Kadang yang simpel itu yang paling nyentuh — kayak to-do list ini, yang keliatannya biasa, tapi bikin hidup lo lebih teratur dan tenang πŸ’­

Jadi kalo lo pengen sesuatu yang ga ribet tapi tetep elegan, To-Do List Monokrom ini bisa jadi inspirasi lo selanjutnya.

“Less color, more focus.” — Iqbal, 2025 😎

Disini kalo pengen nyobain bro : https://z53m06mq53n0.zapp.page/#/ 

Komentar

Postingan populer dari blog ini

πŸš€ Belajar Flutter Sambil Ngabuburit: Bikin Widget Gampang Banget!

BIKIN TAMPILAN IG SENDIRI PAKE FIGMA YANG KECE DAN KEREN

Bikin aplikasi pake konsep CRUD di zapp.run, GAMPANG BANGET!!