MIF_E31230549/lib/ibu/dashboard_ibu.dart

809 lines
29 KiB
Dart

import 'dart:convert';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import 'package:audioplayers/audioplayers.dart';
import '../layout/main_layout.dart';
import 'ibu_drawer.dart';
class DashboardIbuPage extends StatefulWidget {
const DashboardIbuPage({super.key});
@override
State<DashboardIbuPage> createState() => _DashboardIbuPageState();
}
class _DashboardIbuPageState extends State<DashboardIbuPage> {
DateTime _selectedDate = DateTime.now();
static const String baseUrl = "http://ta.myhost.id/E31230549/mposyandu_api";
Timer? _pollingTimer;
final AudioPlayer _audioPlayer = AudioPlayer();
int _notificationCount = 0;
List<String> _knownJadwalIds = [];
Map? dataKehamilan;
List dataBalita = [];
List dataJadwal = [];
List dataJadwalAnc = [];
String namaUser = "Ibu";
bool isLoading = true;
bool _isPasswordVisible = false;
@override
void initState() {
super.initState();
_audioPlayer.setReleaseMode(ReleaseMode.stop);
_initDashboard();
_pollingTimer = Timer.periodic(const Duration(seconds: 15), (timer) {
getDashboardData(isPolling: true);
});
}
@override
void dispose() {
_pollingTimer?.cancel();
_audioPlayer.dispose();
super.dispose();
}
Future<void> _initDashboard() async {
final prefs = await SharedPreferences.getInstance();
String? idUser = prefs.getString("id_user");
setState(() {
namaUser = prefs.getString("nama") ?? "Ibu";
_notificationCount = prefs.getInt('notif_count_$idUser') ?? 0;
_knownJadwalIds = prefs.getStringList('known_ids_$idUser') ?? [];
});
await _checkLoginStatus();
_checkPasswordStatus();
await getDashboardData(isPolling: false);
}
// ================= LOGIKA CEK & UPDATE PASSWORD DEFAULT (NIK) =================
Future<void> _checkPasswordStatus() async {
final prefs = await SharedPreferences.getInstance();
String? id = prefs.getString("id_user");
if (id == null) return;
bool isChanged = prefs.getBool("is_password_changed_$id") ?? false;
if (isChanged) return;
try {
final res = await http.post(
Uri.parse("$baseUrl/cek_password_default_ibu.php"),
body: {"id": id}).timeout(const Duration(seconds: 10));
if (res.statusCode == 200) {
final data = json.decode(res.body);
if (data["success"] == true && data["password_default"] == true) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_showChangePasswordDialog();
});
} else if (data["success"] == true) {
await prefs.setBool("is_password_changed_$id", true);
}
}
} catch (e) {
debugPrint("Gagal cek password default: $e");
}
}
void _showChangePasswordDialog() {
final TextEditingController passwordController = TextEditingController();
String? localError;
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => StatefulBuilder(
builder: (context, setDialogState) {
return AlertDialog(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
title: Text("Keamanan Akun",
style: GoogleFonts.poppins(fontWeight: FontWeight.bold)),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Akun Anda terdeteksi masih menggunakan password bawaan. Silakan ubah password (6 digit, berisi huruf & angka) demi keamanan data.",
style: GoogleFonts.poppins(fontSize: 13),
),
const SizedBox(height: 20),
TextField(
controller: passwordController,
obscureText: !_isPasswordVisible,
maxLength: 6,
keyboardType: TextInputType.visiblePassword,
decoration: InputDecoration(
labelText: "Password Baru",
hintText: "Contoh: abc123",
errorText: localError,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12)),
suffixIcon: IconButton(
icon: Icon(_isPasswordVisible
? Icons.visibility
: Icons.visibility_off),
onPressed: () => setDialogState(
() => _isPasswordVisible = !_isPasswordVisible),
),
),
),
],
),
actions: [
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
onPressed: () async {
String val = passwordController.text.trim();
bool hasLetter = val.contains(RegExp(r'[a-zA-Z]'));
bool hasNumber = val.contains(RegExp(r'[0-9]'));
if (val.isEmpty) {
setDialogState(() => localError = "Wajib diisi");
} else if (val.length != 6) {
setDialogState(
() => localError = "Harus tepat 6 karakter");
} else if (!hasLetter || !hasNumber) {
setDialogState(
() => localError = "Harus kombinasi huruf & angka");
} else {
setDialogState(() => localError = null);
await _updatePassword(val);
}
},
child: Text("Simpan Password",
style: GoogleFonts.poppins(
color: Colors.white, fontWeight: FontWeight.bold)),
),
),
],
);
},
),
);
}
Future<void> _updatePassword(String p) async {
final prefs = await SharedPreferences.getInstance();
String? id = prefs.getString("id_user");
if (id == null) return;
try {
final res = await http.post(Uri.parse("$baseUrl/update_password_ibu.php"),
body: {"id": id, "password": p});
final data = json.decode(res.body);
if (data["success"]) {
await prefs.setBool("is_password_changed_$id", true);
if (mounted) {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("Password berhasil diperbarui"),
backgroundColor: Colors.green));
}
}
} catch (e) {
debugPrint("Update error: $e");
}
}
// ================= LOGIKA DATA & NOTIFIKASI =================
Future<void> getDashboardData({required bool isPolling}) async {
final prefs = await SharedPreferences.getInstance();
String? idUser = prefs.getString("id_user");
if (idUser == null) return;
try {
final response = await http.post(Uri.parse("$baseUrl/dashboard_ibu.php"),
body: {"user_id": idUser});
final data = json.decode(response.body);
if (data["success"]) {
List currentPosyandu = data["jadwal"] ?? [];
List currentAnc = data["jadwal_anc"] ?? [];
List allIncoming = [...currentPosyandu, ...currentAnc];
bool hasNewJadwal = false;
int newItemsCount = 0;
for (var item in allIncoming) {
String uniqueId = "${item['id']}_${item['tanggal']}";
if (!_knownJadwalIds.contains(uniqueId)) {
_knownJadwalIds.add(uniqueId);
newItemsCount++;
hasNewJadwal = true;
}
}
if (hasNewJadwal) {
await prefs.setStringList('known_ids_$idUser', _knownJadwalIds);
if (isPolling) {
_playNotifSound();
setState(() {
_notificationCount += newItemsCount;
});
await prefs.setInt('notif_count_$idUser', _notificationCount);
}
}
if (mounted) {
setState(() {
dataKehamilan = data["kehamilan"];
dataBalita = data["balita"] ?? [];
dataJadwal = currentPosyandu;
dataJadwalAnc = currentAnc;
isLoading = false;
});
}
}
} catch (e) {
if (mounted) setState(() => isLoading = false);
}
}
Future<void> _playNotifSound() async {
try {
await _audioPlayer.stop();
await _audioPlayer.play(AssetSource('sounds/notif.mp3'));
} catch (e) {
debugPrint("Audio Error: $e");
}
}
Map<String, dynamic> _getJadwalStatus(Map item, bool isAnc) {
try {
DateTime sekarang = DateTime.now();
String tglStr = item["tanggal"] ?? "";
String jamMulaiStr = item["jam_mulai"]?.toString() ?? "08:00:00";
if (jamMulaiStr.length == 5) jamMulaiStr = "$jamMulaiStr:00";
DateTime mulai = DateTime.parse("${tglStr.trim()} ${jamMulaiStr.trim()}");
DateTime selesai;
if (isAnc) {
selesai = mulai.add(const Duration(hours: 4));
} else {
String jamSelesaiStr = item["jam_selesai"]?.toString() ?? "";
if (jamSelesaiStr == "-" || jamSelesaiStr.isEmpty) {
selesai = mulai.add(const Duration(hours: 4));
} else {
if (jamSelesaiStr.length == 5) jamSelesaiStr = "$jamSelesaiStr:00";
selesai = DateTime.parse("${tglStr.trim()} ${jamSelesaiStr.trim()}");
}
}
if (sekarang.isAfter(selesai)) {
return {"label": "Selesai", "color": Colors.white.withOpacity(0.4)};
} else if (sekarang.isAfter(mulai) && sekarang.isBefore(selesai)) {
return {"label": "Berlangsung", "color": Colors.greenAccent};
} else {
return {"label": "Mendatang", "color": Colors.white.withOpacity(0.2)};
}
} catch (e) {
return {"label": "-", "color": Colors.transparent};
}
}
@override
Widget build(BuildContext context) {
final selectedKey =
"${_selectedDate.year}-${_selectedDate.month.toString().padLeft(2, '0')}-${_selectedDate.day.toString().padLeft(2, '0')}";
final posyanduAtDate =
dataJadwal.where((j) => j["tanggal"] == selectedKey).toList();
final ancAtDate =
dataJadwalAnc.where((j) => j["tanggal"] == selectedKey).toList();
if (isLoading) {
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) async {
if (didPop) return;
await SystemNavigator.pop();
},
child: Theme(
data: Theme.of(context).copyWith(
textTheme:
GoogleFonts.poppinsTextTheme(Theme.of(context).textTheme)),
child: Stack(
children: [
MainLayout(
title: "",
drawer: const IbuDrawer(),
body: RefreshIndicator(
onRefresh: () => getDashboardData(isPolling: false),
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
const SizedBox(height: 20),
_buildHeroImage(),
const SizedBox(height: 30),
if (dataKehamilan != null) ...[
_buildKehamilanCard(),
const SizedBox(height: 15)
],
if (dataBalita.isNotEmpty) ...[
_buildBalitaCard(),
const SizedBox(height: 30)
],
_buildCalendarSection(posyanduAtDate, ancAtDate),
const SizedBox(height: 20),
],
),
),
),
),
_buildNotificationIcon(),
],
),
),
);
}
Widget _buildNotificationIcon() {
return Positioned(
top: MediaQuery.of(context).padding.top + 5,
right: 12,
child: Stack(
alignment: Alignment.center,
children: [
IconButton(
icon: const Icon(Icons.notifications,
color: Colors.white, size: 28),
onPressed: _showNotifDialog),
if (_notificationCount > 0)
Positioned(
right: 8,
top: 8,
child: Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 1.5)),
constraints: const BoxConstraints(minWidth: 18, minHeight: 18),
child: Center(
child: Text(
_notificationCount > 9 ? "9+" : "$_notificationCount",
style: const TextStyle(
color: Colors.white,
fontSize: 9,
fontWeight: FontWeight.bold))),
),
)
],
),
);
}
void _showNotifDialog() async {
final prefs = await SharedPreferences.getInstance();
String? idUser = prefs.getString("id_user");
setState(() {
_notificationCount = 0;
});
await prefs.setInt('notif_count_$idUser', 0);
showDialog(
context: context,
builder: (_) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
title: Text("Pemberitahuan Jadwal",
textAlign: TextAlign.center,
style:
GoogleFonts.poppins(fontWeight: FontWeight.bold, fontSize: 16)),
content: Container(
width: 300,
constraints: const BoxConstraints(maxHeight: 450),
child: dataJadwal.isEmpty && dataJadwalAnc.isEmpty
? Text("Tidak ada riwayat jadwal.",
textAlign: TextAlign.center,
style: GoogleFonts.poppins(fontSize: 13))
: SingleChildScrollView(
child: Column(mainAxisSize: MainAxisSize.min, children: [
...dataJadwal
.map((j) => _buildMiniSchedule(j, Colors.blue, false)),
...dataJadwalAnc
.map((j) => _buildMiniSchedule(j, Colors.pink, true)),
]),
),
),
actions: [
Center(
child: TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("Tutup")))
],
),
);
}
Widget _buildMiniSchedule(Map item, Color color, bool isAnc) {
var status = _getJadwalStatus(item, isAnc);
return Container(
width: double.infinity,
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2))
]),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
const Icon(Icons.event_available, size: 18, color: Colors.white),
const SizedBox(width: 8),
Expanded(
child: Text(item["keterangan"] ?? "Kegiatan",
style: GoogleFonts.poppins(
fontWeight: FontWeight.bold,
fontSize: 14,
color: Colors.white))),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: status["color"],
borderRadius: BorderRadius.circular(6)),
child: Text(status["label"],
style: const TextStyle(
fontSize: 10,
color: Colors.white,
fontWeight: FontWeight.bold)))
]),
Divider(height: 20, color: Colors.white.withOpacity(0.3), thickness: 1),
Row(children: [
const Icon(Icons.calendar_month, size: 14, color: Colors.white),
const SizedBox(width: 8),
Text(_formatDateToIndo(item["tanggal"]),
style: const TextStyle(
fontSize: 12,
color: Colors.white,
fontWeight: FontWeight.w500))
]),
const SizedBox(height: 6),
Row(children: [
const Icon(Icons.location_on, size: 14, color: Colors.white),
const SizedBox(width: 8),
Expanded(
child: Text(item["lokasi"] ?? "-",
style: const TextStyle(fontSize: 12, color: Colors.white)))
]),
]),
);
}
Widget _buildHeader() => RichText(
text: TextSpan(children: [
TextSpan(
text: 'Selamat Datang Ibu $namaUser\n',
style: GoogleFonts.poppins(
fontSize: 18, fontWeight: FontWeight.bold, color: Colors.blue)),
TextSpan(
text: 'Pantau kesehatan ibu dan buah hati tercinta ',
style: GoogleFonts.poppins(fontSize: 13, color: Colors.black87)),
const WidgetSpan(
child: Icon(Icons.favorite, color: Colors.blue, size: 18)),
]));
Widget _buildHeroImage() => Center(
child: Image.asset('assets/images/logoo.webp',
width: 300,
height: 180,
errorBuilder: (_, __, ___) =>
const Icon(Icons.image, size: 100, color: Colors.grey)));
// ================= UPDATE CARD INFORMASI KEHAMILAN =================
Widget _buildKehamilanCard() =>
_infoBox(color: Colors.lightGreen, title: "Informasi Kehamilan", items: [
_infoItem(Icons.info_outline, "Status Kehamilan",
_capitalize(dataKehamilan?["status"])),
_infoItem(Icons.calendar_today, "HPHT",
_formatDateToIndo(dataKehamilan?["hpht"])),
_infoItem(Icons.pregnant_woman, "Usia Kandungan",
"${dataKehamilan!["hpht"] != "0000-00-00" && dataKehamilan?["status"] == "aktif" ? (DateTime.now().difference(DateTime.parse(dataKehamilan!["hpht"])).inDays ~/ 7) : 0} Minggu"),
_infoItem(Icons.event, "HPL", _formatDateToIndo(dataKehamilan?["hpl"])),
_infoItem(Icons.history, "Tgl Persalinan Sblm",
_formatDateToIndo(dataKehamilan?["tanggal_persalinan_sebelumnya"])),
_infoItem(Icons.looks_one, "Gravida",
dataKehamilan?["gravida"].toString() ?? "-"),
_infoItem(
Icons.looks_two, "Para", dataKehamilan?["para"].toString() ?? "-"),
_infoItem(Icons.looks_3, "Abortus",
dataKehamilan?["abortus"].toString() ?? "-"),
_infoItem(Icons.favorite_border, "Hidup",
dataKehamilan?["hidup"].toString() ?? "-"),
_infoItem(
Icons.payment, "Pembiayaan", dataKehamilan?["pembiayaan"] ?? "-"),
]);
// ================= UPDATE CARD INFORMASI ANAK =================
Widget _buildBalitaCard() {
bool isFirstChildFemale = dataBalita.isNotEmpty &&
dataBalita[0]["jenis_kelamin"]?.toString().toUpperCase() == "P";
return Container(
width: double.infinity,
decoration: BoxDecoration(
color: isFirstChildFemale ? Colors.pink : Colors.blue,
borderRadius: BorderRadius.circular(16),
boxShadow: const [
BoxShadow(
blurRadius: 8, color: Colors.black12, offset: Offset(0, 4))
]),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text("Informasi Anak",
style: GoogleFonts.poppins(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16))),
...dataBalita.asMap().entries.map((entry) {
var anak = entry.value;
bool isP = anak["jenis_kelamin"]?.toString().toUpperCase() == "P";
String tempatLahir = anak["tempat_lahir"] ?? "-";
String tanggalLahirIndo = _formatDateToIndo(anak["tanggal_lahir"]);
String tempatTanggalLahir = "$tempatLahir, $tanggalLahirIndo";
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isP ? Colors.pink : Colors.blue,
borderRadius: entry.key == dataBalita.length - 1
? const BorderRadius.vertical(
bottom: Radius.circular(16))
: null),
child: Column(children: [
_infoItemCustom(Icons.info_outline, "Status Balita",
_capitalize(anak["status"]), Colors.white),
_infoItemCustom(Icons.child_care, "Nama Balita",
anak["nama"] ?? "-", Colors.white),
_infoItemCustom(Icons.person, "Jenis Kelamin",
formatJenisKelamin(anak["jenis_kelamin"]), Colors.white),
_infoItemCustom(Icons.location_city, "Tempat Tanggal Lahir",
tempatTanggalLahir, Colors.white),
_infoItemCustom(Icons.cake, "Usia",
hitungUsiaBalita(anak["tanggal_lahir"]), Colors.white),
_infoItemCustom(Icons.format_list_numbered, "Anak Ke",
anak["anak_ke"]?.toString() ?? "-", Colors.white),
if (entry.key < dataBalita.length - 1)
const Divider(color: Colors.white30, height: 20)
]));
}).toList(),
]));
}
Widget _buildCalendarSection(List s, List a) => Column(children: [
Center(
child: Text("Jadwal Kegiatan",
style: GoogleFonts.poppins(
fontWeight: FontWeight.bold, fontSize: 16))),
const SizedBox(height: 20),
if (s.isEmpty && a.isEmpty)
_emptySchedule()
else
Column(children: [
...s.map((i) => _scheduleDetail(i, Colors.blue)),
...a.map((i) => _scheduleDetail(i, Colors.pink))
]),
const SizedBox(height: 20),
_buildCalendarPicker(),
]);
Widget _buildCalendarPicker() => Center(
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)
]),
child: SizedBox(
width: 300,
height: 280,
child: CalendarDatePicker(
initialDate: _selectedDate,
firstDate: DateTime(2020),
lastDate: DateTime(2030),
onDateChanged: (d) => setState(() => _selectedDate = d)))));
Widget _scheduleDetail(Map item, Color color) {
String jamSelesai = item["jam_selesai"]?.toString() ?? "-";
String displayJam = (jamSelesai == "-" || jamSelesai.isEmpty)
? "${item["jam_mulai"] ?? "00:00"} - Selesai"
: "${item["jam_mulai"] ?? "00:00"} - $jamSelesai";
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
child: Column(children: [
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Icon(Icons.stars, color: color, size: 20),
const SizedBox(width: 8),
Flexible(
child: Text(item["keterangan"] ?? "Kegiatan",
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.bold,
color: color)))
]),
const SizedBox(height: 8),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
const Icon(Icons.access_time, size: 14, color: Colors.grey),
const SizedBox(width: 6),
Text(displayJam, style: GoogleFonts.poppins(fontSize: 12))
]),
const SizedBox(height: 4),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
const Icon(Icons.location_on_outlined,
size: 14, color: Colors.grey),
const SizedBox(width: 6),
Flexible(
child: Text("Lokasi: ${item["lokasi"] ?? "-"}",
textAlign: TextAlign.center,
style: GoogleFonts.poppins(fontSize: 12)))
]),
Divider(thickness: 1, color: color.withOpacity(0.2)),
]));
}
String _formatDateToIndo(String? dateString) {
if (dateString == null || dateString.isEmpty || dateString == "0000-00-00")
return "-";
try {
DateTime date = DateTime.parse(dateString);
const bulan = [
"",
"Januari",
"Februari",
"Maret",
"April",
"Mei",
"Juni",
"Juli",
"Agustus",
"September",
"Oktober",
"November",
"Desember"
];
return "${date.day} ${bulan[date.month]} ${date.year}";
} catch (e) {
return "-";
}
}
String hitungUsiaBalita(String? tanggalLahir) {
if (tanggalLahir == null || tanggalLahir.isEmpty) return "-";
try {
DateTime lahir = DateTime.parse(tanggalLahir);
DateTime sekarang = DateTime.now();
int tahun = sekarang.year - lahir.year;
int selisihBulan = ClinicalMonthDiff(sekarang, lahir);
if (sekarang.day < lahir.day) selisihBulan--;
if (selisihBulan < 0) {
tahun--;
selisihBulan += 12;
}
return "$tahun Thn $selisihBulan Bln";
} catch (e) {
return "-";
}
}
int ClinicalMonthDiff(DateTime a, DateTime b) {
return a.month - b.month;
}
String formatJenisKelamin(String? jk) {
if (jk == null) return "-";
return (jk.toUpperCase() == "L")
? "Laki-laki"
: (jk.toUpperCase() == "P" ? "Perempuan" : jk);
}
String _capitalize(String? text) {
if (text == null || text.isEmpty) return "-";
return text[0].toUpperCase() + text.substring(1).toLowerCase();
}
Widget _infoBox(
{required Color color,
required String title,
required List<Widget> items}) =>
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(16),
boxShadow: const [
BoxShadow(
blurRadius: 8, color: Colors.black12, offset: Offset(0, 4))
]),
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(title,
style: GoogleFonts.poppins(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16)),
const SizedBox(height: 12),
...items
]));
Widget _infoItem(IconData i, String t, String v) => Padding(
padding: const EdgeInsets.only(bottom: 6),
child: Row(children: [
Icon(i, color: Colors.white, size: 18),
const SizedBox(width: 10),
SizedBox(
width: 150,
child: Text(t,
style: GoogleFonts.poppins(color: Colors.white, fontSize: 13))),
const Text(": ", style: TextStyle(color: Colors.white)),
Expanded(
child: Text(v,
style: GoogleFonts.poppins(color: Colors.white, fontSize: 13)))
]));
Widget _infoItemCustom(IconData i, String t, String v, Color c) => Padding(
padding: const EdgeInsets.only(bottom: 6),
child: Row(children: [
Icon(i, color: c, size: 18),
const SizedBox(width: 10),
SizedBox(
width: 150,
child: Text(t, style: GoogleFonts.poppins(color: c, fontSize: 13))),
Text(": ", style: TextStyle(color: c)),
Expanded(
child: Text(v, style: GoogleFonts.poppins(color: c, fontSize: 13)))
]));
Widget _emptySchedule() => Column(children: [
const Icon(Icons.event_busy, size: 30, color: Colors.grey),
Text("Tidak ada kegiatan",
style: GoogleFonts.poppins(color: Colors.grey, fontSize: 12))
]);
Future<void> _checkLoginStatus() async {
final prefs = await SharedPreferences.getInstance();
if (!(prefs.getBool("isLogin") ?? false) && mounted) {
Navigator.pushReplacementNamed(context, "/login");
}
}
}