APLIKASI CRUD Jadwal Kegiatan/olahraga

πŸ‹️‍♂️Aplikasi Jadwal Olahraga — Kegiatan



Jadi gini…
Aplikasi ini basically kayak temen setia yang selalu ngingetin kamu buat olahraga atau menjadwalkan sebuah kegiatan.Aplikasi Dibikin pake Flutter (biar kekinian), bisa jalan di web atau HP, dan yang paling mantap: jadwalnya nggak bakal hilang walau kamu tutup aplikasinya. Semua ini thanks to si SharedPreferences yang nyimpen data di cache browser. πŸ”’


Fitur-Fitur Keren

Tambah Jadwal → Mau jogging jam 5 pagi? Bisa.
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

  1. Kamu input nama olahraga + jamnya → Disimpan ke list & cache.

  2. Timer jalan tiap detik → Cek, udah waktunya olahraga belum?

  3. Kalau udah waktu → Boom! Muncul notif full screen yang langsung bikin kamu inget.

  4. 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.


🏁 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. 😏πŸ’ͺ


CONTOH NYA DISINI GUIS BESERTA LINK UNTUK MENGAKSES NYA




Akses aja disini

πŸ‘‡


Komentar

Postingan populer dari blog ini

MEMBUAT TAMPILAN APLIKASI SEDERANA MENGGUNAKAN DART FLUTTER

Aplication Flutter With Image 7/31/2025