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.
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? π
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),
),
),
);
},
),
);
}
}
Komentar
Posting Komentar