import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Jadwal Pelajaran',
theme: ThemeData(primarySwatch: Colors.indigo, useMaterial3: true, fontFamily: 'Poppins'),
darkTheme: ThemeData(brightness: Brightness.dark, useMaterial3: true),
themeMode: ThemeMode.system,
home: JadwalPage(),
);
}
}
// ==================== MODEL DATA ====================
class JadwalModel {
String id, mataPelajaran, hari, jam, ruang, guru;
int warnaIndeks;
JadwalModel({required this.id, required this.mataPelajaran, required this.hari, required this.jam, required this.ruang, this.guru = '', this.warnaIndeks = 0});
Map<String,dynamic> toJson() => {'id':id,'mataPelajaran':mataPelajaran,'hari':hari,'jam':jam,'ruang':ruang,'guru':guru,'warnaIndeks':warnaIndeks};
factory JadwalModel.fromJson(Map<String,dynamic> json) => JadwalModel(id:json['id'],mataPelajaran:json['mataPelajaran'],hari:json['hari'],jam:json['jam'],ruang:json['ruang'],guru:json['guru']??'',warnaIndeks:json['warnaIndeks']??0);
}
// ==================== HALAMAN UTAMA ====================
class JadwalPage extends StatefulWidget {
@override
_JadwalPageState createState() => _JadwalPageState();
}
class _JadwalPageState extends State<JadwalPage> {
List<JadwalModel> jadwalList = [];
List<JadwalModel> filteredList = [];
String searchQuery = '';
String filterHari = 'Semua';
final List<String> daftarHari = ['Semua','Senin','Selasa','Rabu','Kamis','Jumat','Sabtu'];
final List<Map<String,dynamic>> warnaCard = [
{'warna': Colors.blue.shade50, 'aksen': Colors.blue.shade700, 'icon': Icons.school},
{'warna': Colors.green.shade50, 'aksen': Colors.green.shade700, 'icon': Icons.calculate},
{'warna': Colors.orange.shade50, 'aksen': Colors.orange.shade700, 'icon': Icons.language},
{'warna': Colors.purple.shade50, 'aksen': Colors.purple.shade700, 'icon': Icons.code},
{'warna': Colors.red.shade50, 'aksen': Colors.red.shade700, 'icon': Icons.art_track},
{'warna': Colors.teal.shade50, 'aksen': Colors.teal.shade700, 'icon': Icons.fitness_center},
];
@override
void initState() { super.initState(); _loadData(); }
// ==================== LOCAL STORAGE ====================
Future<void> _saveData() async {
final prefs = await SharedPreferences.getInstance();
List<String> jsonList = jadwalList.map((j) => jsonEncode(j.toJson())).toList();
await prefs.setStringList('jadwal_list', jsonList);
}
Future<void> _loadData() async {
final prefs = await SharedPreferences.getInstance();
List<String>? jsonList = prefs.getStringList('jadwal_list');
if (jsonList != null && jsonList.isNotEmpty) {
setState(() { jadwalList = jsonList.map((j) => JadwalModel.fromJson(jsonDecode(j))).toList(); _filterData(); });
} else {
setState(() {
jadwalList = [JadwalModel(id: DateTime.now().millisecondsSinceEpoch.toString(), mataPelajaran: 'Pemrograman Flutter', hari: 'Senin', jam: '07:30 - 09:30', ruang: 'Lab 1', guru: 'Bpk. Darsu', warnaIndeks: 0)];
_saveData(); _filterData();
});
}
}
void _filterData() => setState(() {
filteredList = jadwalList.where((j) =>
(filterHari == 'Semua' || j.hari == filterHari) &&
(j.mataPelajaran.toLowerCase().contains(searchQuery.toLowerCase()) ||
j.guru.toLowerCase().contains(searchQuery.toLowerCase()))
).toList();
});
// ==================== CRUD OPERATIONS ====================
void tambahData(JadwalModel j) { setState(() { jadwalList.add(j); _saveData(); _filterData(); }); }
void editData(int idx, JadwalModel j) { setState(() { jadwalList[idx] = j; _saveData(); _filterData(); }); }
// 🔥 KONFIRMASI SEBELUM HAPUS (Sesuai Tugas!)
void konfirmasiHapus(int index) {
showDialog(context: context, builder: (_) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
title: Row(children: [Icon(Icons.warning_amber_rounded, color: Colors.red), SizedBox(width: 10), Text('Hapus Jadwal?')]),
content: Text('Yakin ingin menghapus "${jadwalList[index].mataPelajaran}"?'),
actions: [
TextButton(onPressed: () => Navigator.pop(_), child: Text('Batal')),
ElevatedButton(onPressed: () {
setState(() { jadwalList.removeAt(index); _saveData(); _filterData(); });
Navigator.pop(_);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Jadwal dihapus'), backgroundColor: Colors.red));
}, style: ElevatedButton.styleFrom(backgroundColor: Colors.red), child: Text('Hapus')),
],
));
}
// ==================== DIALOG TAMBAH/EDIT ====================
void tampilDialog({JadwalModel? jadwalLama, int? index}) {
bool isEdit = jadwalLama != null;
TextEditingController mapelC = TextEditingController(text: jadwalLama?.mataPelajaran ?? '');
TextEditingController jamC = TextEditingController(text: jadwalLama?.jam ?? '');
TextEditingController ruangC = TextEditingController(text: jadwalLama?.ruang ?? '');
TextEditingController guruC = TextEditingController(text: jadwalLama?.guru ?? '');
String selectedHari = jadwalLama?.hari ?? daftarHari[1];
int selectedWarna = jadwalLama?.warnaIndeks ?? 0;
showModalBottomSheet(context: context, isScrollControlled: true, shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(25))), builder: (_) {
return StatefulBuilder(builder: (ctx, setStateBottom) {
return Container(padding: EdgeInsets.only(bottom: MediaQuery.of(ctx).viewInsets.bottom, left: 20, right: 20, top: 20),
child: SingleChildScrollView(child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
Center(child: Container(width: 50, height: 5, decoration: BoxDecoration(color: Colors.grey.shade300, borderRadius: BorderRadius.circular(10)))),
SizedBox(height: 20),
Text(isEdit ? '✏️ Edit Jadwal' : '➕ Tambah Jadwal', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
SizedBox(height: 20),
TextField(controller: mapelC, decoration: InputDecoration(labelText: 'Mata Pelajaran', prefixIcon: Icon(Icons.book), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)))),
SizedBox(height: 15),
DropdownButtonFormField<String>(value: selectedHari, decoration: InputDecoration(labelText: 'Hari', prefixIcon: Icon(Icons.calendar_today), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12))),
items: daftarHari.skip(1).map((h) => DropdownMenuItem(value: h, child: Text(h))).toList(),
onChanged: (v) => setStateBottom(() => selectedHari = v!)),
SizedBox(height: 15),
TextField(controller: jamC, decoration: InputDecoration(labelText: 'Jam', hintText: '07:30 - 09:00', prefixIcon: Icon(Icons.access_time), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)))),
SizedBox(height: 15),
TextField(controller: ruangC, decoration: InputDecoration(labelText: 'Ruang', prefixIcon: Icon(Icons.location_on), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)))),
SizedBox(height: 15),
TextField(controller: guruC, decoration: InputDecoration(labelText: 'Guru Pengajar', prefixIcon: Icon(Icons.person), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)))),
SizedBox(height: 15),
Text('🎨 Warna Card', style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(height: 10),
Container(height: 50, child: ListView.builder(scrollDirection: Axis.horizontal, itemCount: warnaCard.length, itemBuilder: (_, i) => GestureDetector(onTap: () => setStateBottom(() => selectedWarna = i),
child: Container(margin: EdgeInsets.only(right: 10), width: 50, decoration: BoxDecoration(color: warnaCard[i]['warna'], border: Border.all(color: selectedWarna == i ? warnaCard[i]['aksen'] : Colors.transparent, width: 3), borderRadius: BorderRadius.circular(25)),
child: Icon(warnaCard[i]['icon'], color: warnaCard[i]['aksen'])))),
SizedBox(height: 20),
Row(children: [
Expanded(child: OutlinedButton(onPressed: () => Navigator.pop(ctx), child: Text('Batal'), style: OutlinedButton.styleFrom(padding: EdgeInsets.symmetric(vertical: 15), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))))),
SizedBox(width: 10),
Expanded(child: ElevatedButton(onPressed: () {
if (mapelC.text.isNotEmpty) {
if (isEdit) editData(index!, JadwalModel(id: jadwalLama!.id, mataPelajaran: mapelC.text, hari: selectedHari, jam: jamC.text, ruang: ruangC.text, guru: guruC.text, warnaIndeks: selectedWarna));
else tambahData(JadwalModel(id: DateTime.now().millisecondsSinceEpoch.toString(), mataPelajaran: mapelC.text, hari: selectedHari, jam: jamC.text, ruang: ruangC.text, guru: guruC.text, warnaIndeks: selectedWarna));
Navigator.pop(ctx);
ScaffoldMessenger.of(ctx).showSnackBar(SnackBar(content: Text(isEdit ? 'Jadwal diupdate' : 'Jadwal ditambahkan'), backgroundColor: Colors.green));
}
}, style: ElevatedButton.styleFrom(padding: EdgeInsets.symmetric(vertical: 15), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))), child: Text(isEdit ? 'Update' : 'Simpan'))),
]),
SizedBox(height: 20),
])),
);
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(slivers: [
SliverAppBar(expandedHeight: 120, floating: true, pinned: true, title: Text('📅 Jadwal Pelajaran', style: TextStyle(fontWeight: FontWeight.bold)),
flexibleSpace: FlexibleSpaceBar(background: Container(decoration: BoxDecoration(gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Colors.indigo.shade700, Colors.blue.shade600, Colors.cyan.shade500])))),
actions: [IconButton(icon: Icon(Icons.info_outline), onPressed: () => showAboutDialog(context: context, applicationName: 'Jadwal Pelajaran', children: [Text('CRUD + Local Storage + Konfirmasi Hapus')]))]),
SliverToBoxAdapter(child: Padding(padding: EdgeInsets.all(16), child: Column(children: [
Container(decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(30), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)]),
child: TextField(onChanged: (v) { searchQuery = v; _filterData(); }, decoration: InputDecoration(hintText: '🔍 Cari jadwal...', prefixIcon: Icon(Icons.search), border: OutlineInputBorder(borderRadius: BorderRadius.circular(30), borderSide: BorderSide.none), contentPadding: EdgeInsets.symmetric(vertical: 15)))),
SizedBox(height: 15),
Container(height: 45, child: ListView.builder(scrollDirection: Axis.horizontal, itemCount: daftarHari.length, itemBuilder: (_, i) => Padding(padding: EdgeInsets.only(right: 8),
child: FilterChip(label: Text(daftarHari[i]), selected: filterHari == daftarHari[i], onSelected: (_) => setState(() { filterHari = daftarHari[i]; _filterData(); }), selectedColor: Colors.indigo.shade600, labelStyle: TextStyle(color: filterHari == daftarHari[i] ? Colors.white : null))))),
]))),
filteredList.isEmpty ? SliverToBoxAdapter(child: Container(height: 300, child: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [Icon(Icons.schedule, size: 80, color: Colors.grey), Text('Tidak ada jadwal', style: TextStyle(fontSize: 18)), Text('Tekan tombol + untuk menambah jadwal')]))))
: SliverPadding(padding: EdgeInsets.symmetric(horizontal: 16), sliver: SliverList(delegate: SliverChildBuilderDelegate((_, i) {
var j = filteredList[i];
var warna = warnaCard[j.warnaIndeks % warnaCard.length];
int originalIdx = jadwalList.indexOf(j);
return Card(elevation: 3, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), margin: EdgeInsets.only(bottom: 12),
child: Container(decoration: BoxDecoration(color: warna['warna'], borderRadius: BorderRadius.circular(16)),
child: ListTile(contentPadding: EdgeInsets.all(16), leading: CircleAvatar(radius: 28, backgroundColor: warna['aksen'], child: Icon(warna['icon'], color: Colors.white)),
title: Text(j.mataPelajaran, style: TextStyle(fontWeight: FontWeight.bold, color: warna['aksen'])),
subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [Icon(Icons.calendar_today, size: 14, color: warna['aksen']), SizedBox(width: 5), Text(j.hari), SizedBox(width: 15), Icon(Icons.access_time, size: 14, color: warna['aksen']), SizedBox(width: 5), Text(j.jam)]),
if (j.ruang.isNotEmpty) Row(children: [Icon(Icons.location_on, size: 14, color: warna['aksen']), SizedBox(width: 5), Text(j.ruang)]),
if (j.guru.isNotEmpty) Row(children: [Icon(Icons.person, size: 14, color: warna['aksen']), SizedBox(width: 5), Text(j.guru)]),
]),
trailing: Row(mainAxisSize: MainAxisSize.min, children: [
IconButton(icon: Icon(Icons.edit, color: Colors.blue), onPressed: () => tampilDialog(jadwalLama: j, index: originalIdx)),
IconButton(icon: Icon(Icons.delete, color: Colors.red), onPressed: () => konfirmasiHapus(originalIdx))
])
)
)
);
}, childCount: filteredList.length))),
SliverPadding(padding: EdgeInsets.only(bottom: 80)),
]),
floatingActionButton: FloatingActionButton.extended(onPressed: () => tampilDialog(), icon: Icon(Icons.add), label: Text('Tambah Jadwal'), backgroundColor: Colors.indigo.shade700),
);
}
}
Komentar
Posting Komentar