APLIKASI CRUD Jadwal Kegiatan/olahraga
π️♂️Aplikasi Jadwal Olahraga — Kegiatan
SharedPreferences yang nyimpen data di cache browser. π✨ Fitur-Fitur Keren
Edit Jadwal → Tiba-tiba mau geser ke jam 7? Gampang.
Hapus Jadwal → Lagi males? Bye bye jadwal.
Countdown Real-Time → Ada timer kece yang kasih tau berapa menit lagi lo harus gerak.
Notif Sekali Aja → Pas jamnya tiba, ada notifikasi full screen. Nggak spamming, cukup sekali biar nggak nyebelin.
UI Rapi → Kotak-kotak jadwal yang estetik, bikin mood olahraga naik.
πKODE NYA KAYA GINI LAH KIRA KIRAπ
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'List Kegiatan/Olahraga',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: JadwalPage(),
);
}
}
class JadwalPage extends StatefulWidget {
@override
_JadwalPageState createState() => _JadwalPageState();
}
class _JadwalPageState extends State<JadwalPage> {
List<Map<String, String>> jadwalList = [];
String countdownText = "";
Set<String> notifiedEvents = {};
final _namaController = TextEditingController();
final _editNamaController = TextEditingController();
TimeOfDay? _selectedTime;
TimeOfDay? _editSelectedTime;
int? _editingIndex;
@override
void initState() {
super.initState();
_loadData();
_startTimer();
}
Future<void> _loadData() async {
final prefs = await SharedPreferences.getInstance();
final data = prefs.getStringList('jadwal') ?? [];
setState(() {
jadwalList = data
.map((e) => {
'nama': e.split('|')[0],
'waktu': e.split('|')[1],
})
.toList();
});
}
Future<void> _saveData() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setStringList(
'jadwal', jadwalList.map((e) => "${e['nama']}|${e['waktu']}").toList());
}
void _startTimer() {
Future.doWhile(() async {
await Future.delayed(Duration(seconds: 1));
_checkNextEvent();
return true;
});
}
void _checkNextEvent() {
if (jadwalList.isEmpty) {
setState(() {
countdownText = "Belum ada jadwal";
});
return;
}
jadwalList.sort((a, b) => a['waktu']!.compareTo(b['waktu']!));
final now = TimeOfDay.now();
for (var item in jadwalList) {
final parts = item['waktu']!.split(':');
final itemTime =
TimeOfDay(hour: int.parse(parts[0]), minute: int.parse(parts[1]));
final nowMinutes = now.hour * 60 + now.minute;
final itemMinutes = itemTime.hour * 60 + itemTime.minute;
if (itemMinutes >= nowMinutes) {
final diff = itemMinutes - nowMinutes;
final hours = diff ~/ 60;
final minutes = diff % 60;
setState(() {
countdownText =
"Jadwal berikut: ${item['nama']} dalam ${hours} jam ${minutes} menit";
});
String eventId = "${item['nama']}-${item['waktu']}";
if (diff == 0 && !notifiedEvents.contains(eventId)) {
notifiedEvents.add(eventId);
_showNotif(item['nama']!);
}
return;
}
}
setState(() {
countdownText = "Semua jadwal hari ini sudah lewat";
});
}
void _showNotif(String nama) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Saatnya olahraga: $nama"),
backgroundColor: Colors.green,
duration: Duration(seconds: 4),
),
);
}
void _addJadwal() {
if (_namaController.text.isNotEmpty && _selectedTime != null) {
setState(() {
jadwalList.add({
'nama': _namaController.text,
'waktu':
"${_selectedTime!.hour.toString().padLeft(2, '0')}:${_selectedTime!.minute.toString().padLeft(2, '0')}",
});
_namaController.clear();
_selectedTime = null;
});
_saveData();
}
}
void _editJadwal() {
if (_editNamaController.text.isNotEmpty && _editSelectedTime != null && _editingIndex != null) {
setState(() {
jadwalList[_editingIndex!] = {
'nama': _editNamaController.text,
'waktu':
"${_editSelectedTime!.hour.toString().padLeft(2, '0')}:${_editSelectedTime!.minute.toString().padLeft(2, '0')}",
};
_editNamaController.clear();
_editSelectedTime = null;
_editingIndex = null;
});
_saveData();
}
}
void _deleteJadwal(int index) {
setState(() {
jadwalList.removeAt(index);
});
_saveData();
}
Future<void> _pickTime({bool isEdit = false}) async {
final time = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (time != null) {
setState(() {
if (isEdit) {
_editSelectedTime = time;
} else {
_selectedTime = time;
}
});
}
}
void _startEditing(int index) {
setState(() {
_editingIndex = index;
_editNamaController.text = jadwalList[index]['nama']!;
final parts = jadwalList[index]['waktu']!.split(':');
_editSelectedTime = TimeOfDay(
hour: int.parse(parts[0]),
minute: int.parse(parts[1]),
);
});
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
left: 16,
right: 16,
top: 16,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _editNamaController,
decoration: InputDecoration(labelText: "Nama Olahraga"),
),
SizedBox(height: 10),
Row(
children: [
ElevatedButton(
onPressed: () => _pickTime(isEdit: true),
child: Text("Pilih Waktu"),
),
SizedBox(width: 10),
Text(_editSelectedTime == null
? "Belum dipilih"
: "${_editSelectedTime!.hour.toString().padLeft(2, '0')}:${_editSelectedTime!.minute.toString().padLeft(2, '0')}"),
],
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
_editJadwal();
Navigator.pop(context);
},
child: Text("Simpan Perubahan"),
),
SizedBox(height: 10),
],
),
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Jadwal Olahraga"),
centerTitle: true,
),
body: Column(
children: [
SizedBox(height: 10),
Text(
countdownText,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
SizedBox(height: 10),
Expanded(
child: GridView.builder(
padding: EdgeInsets.all(10),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
childAspectRatio: 1,
),
itemCount: jadwalList.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () => _startEditing(index),
onLongPress: () => _deleteJadwal(index),
child: Container(
decoration: BoxDecoration(
color: Colors.blue.shade100,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 4,
offset: Offset(2, 2),
),
],
),
padding: EdgeInsets.all(12),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.fitness_center,
size: 40, color: Colors.blueAccent),
SizedBox(height: 8),
Text(
jadwalList[index]['nama']!,
style: TextStyle(
fontSize: 16, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
SizedBox(height: 5),
Text(
jadwalList[index]['waktu']!,
style: TextStyle(fontSize: 14, color: Colors.black54),
),
],
),
),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
left: 16,
right: 16,
top: 16,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _namaController,
decoration:
InputDecoration(labelText: "Nama Olahraga"),
),
SizedBox(height: 10),
Row(
children: [
ElevatedButton(
onPressed: () => _pickTime(),
child: Text("Pilih Waktu"),
),
SizedBox(width: 10),
Text(_selectedTime == null
? "Belum dipilih"
: "${_selectedTime!.hour.toString().padLeft(2, '0')}:${_selectedTime!.minute.toString().padLeft(2, '0')}"),
],
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
_addJadwal();
Navigator.pop(context);
},
child: Text("Tambah Jadwal"),
),
SizedBox(height: 10),
],
),
);
},
);
},
child: Icon(Icons.add),
),
);
}
}
⚙️ Cara Kerja Singkat
-
Kamu input nama olahraga + jamnya → Disimpan ke list & cache.
-
Timer jalan tiap detik → Cek, udah waktunya olahraga belum?
-
Kalau udah waktu → Boom! Muncul notif full screen yang langsung bikin kamu inget.
-
Data tetap aman → Walau reload browser, jadwal nggak ilang.
Kamu input nama olahraga + jamnya → Disimpan ke list & cache.
Timer jalan tiap detik → Cek, udah waktunya olahraga belum?
Kalau udah waktu → Boom! Muncul notif full screen yang langsung bikin kamu inget.
Data tetap aman → Walau reload browser, jadwal nggak ilang.
π‘ Kenapa Worth It?
-
Anti Lupa: Pas jamnya tiba, kamu langsung diingetin.
-
Anti Ribet: Tambah/edit/hapus tinggal klik.
-
Anti Ilang Data: Cache browser jagain jadwal kamu.
-
Anti Bosen: UI rapi dan modern bikin aplikasi ini nggak keliatan jadul.
Anti Lupa: Pas jamnya tiba, kamu langsung diingetin.
Anti Ribet: Tambah/edit/hapus tinggal klik.
Anti Ilang Data: Cache browser jagain jadwal kamu.
Anti Bosen: UI rapi dan modern bikin aplikasi ini nggak keliatan jadul.
π Kesimpulan Singkat
Aplikasi ini cocok banget buat kamu yang:
-
Mau olahraga rutin tapi suka lupa.
-
Pengen UI simple tapi vibes-nya modern.
-
Butuh notif yang nggak nyampah tapi tetep efektif.
Pokoknya ini kayak personal trainer digital yang nggak pernah protes kalau kamu skip hari ini… tapi tetep ngingetin besok. ππͺ

Komentar
Posting Komentar