MIF_E31231313/Android/flutter_app/lib/screens/profile_screen.dart

1092 lines
36 KiB
Dart

import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:phosphor_flutter/phosphor_flutter.dart';
import 'package:shimmer/shimmer.dart';
import '../app_design.dart';
import '../app_state.dart';
import '../models/mobile_child_model.dart';
import '../services/api_service.dart';
import '../utils/nutrition_display_utils.dart';
import 'add_child_screen.dart';
import 'account_info_screen.dart';
import 'auth_screen.dart';
import 'help_screen.dart';
import 'security_screen.dart';
class ProfileScreen extends StatefulWidget {
const ProfileScreen({super.key});
@override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
final _api = ApiService();
final _state = SgiziAppState.instance;
late Future<void> _future;
@override
void initState() {
super.initState();
_state.addListener(_onStateChanged);
_future = _load();
}
@override
void dispose() {
_state.removeListener(_onStateChanged);
super.dispose();
}
void _onStateChanged() {
if (!mounted) return;
setState(() {});
}
Future<void> _load() async {
if (!_state.isAuthenticated) return;
final results = await Future.wait([
_api.getChildren(),
_api.getProfile(),
]);
final children = results[0] as List<MobileChildModel>;
final profile = results[1] as Map<String, dynamic>;
_state.setChildren(children);
_state.setProfileData(profile);
}
void _retry() => setState(() => _future = _load());
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F7F6),
body: SafeArea(
child: FutureBuilder<void>(
future: _future,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const _ProfileSkeleton();
}
if (snapshot.hasError) {
return _ProfileError(onRetry: _retry);
}
final profile = _state.profileData;
final name = (profile?['name'] as String? ?? 'Siti Aminah').trim();
final phone = (profile?['phone'] as String? ?? '-').trim();
final role = (profile?['role'] as String? ?? 'orang_tua').trim();
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.fromLTRB(20, 14, 20, 28),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_ProfileHeaderCard(
name: name.isEmpty ? 'Siti Aminah' : name,
subtitle: _profileRoleToLabel(role, phone),
onEditTap: () async {
await Navigator.of(context).push(
fadeRoute(const AccountInfoScreen()),
);
if (!mounted) return;
_retry();
},
),
const SizedBox(height: 18),
_ChildrenSection(
children: _state.children,
activeChildId: _state.activeChildId,
onAdd: () => Navigator.of(context).push(
fadeRoute(const AddChildScreen()),
),
onSelect: (id) => _state.setActiveChild(id),
),
const SizedBox(height: 22),
Text(
'Pengaturan',
style: AppTypography.h2.copyWith(color: SgColors.textPrimary),
),
const SizedBox(height: 12),
_SettingsCard(
onTapAccount: () async {
await Navigator.of(context).push(
fadeRoute(const AccountInfoScreen()),
);
if (!mounted) return;
_retry();
},
onTapPrivacy: () => Navigator.of(context).push(
fadeRoute(const SecurityScreen()),
),
onTapHelp: () => Navigator.of(context).push(
fadeRoute(const HelpScreen()),
),
),
const SizedBox(height: 18),
_LogoutCard(
onLogout: () async {
final ok = await _confirmLogout(context);
if (ok != true) return;
_state.logout();
if (!context.mounted) return;
Navigator.of(context).pushAndRemoveUntil(
fadeRoute(const AuthScreen()),
(_) => false,
);
},
),
const SizedBox(height: 18),
Text(
'S-GIZI • PREMIUM HEALTHCARE UI',
textAlign: TextAlign.center,
style: AppTypography.caption.copyWith(
letterSpacing: 1,
fontWeight: FontWeight.w800,
),
),
],
),
)
.animate()
.fadeIn(duration: 260.ms, curve: Curves.easeOut)
.slideY(begin: 0.02, end: 0, duration: 260.ms, curve: Curves.easeOut);
},
),
),
);
}
}
class _ProfileHeaderCard extends StatelessWidget {
const _ProfileHeaderCard({
required this.name,
required this.subtitle,
required this.onEditTap,
});
final String name;
final String subtitle;
final VoidCallback onEditTap;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF085B63), Color(0xFF0B7A86)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(28),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.10),
blurRadius: 26,
offset: const Offset(0, 14),
),
],
),
child: Stack(
children: [
Positioned(
right: -30,
top: -40,
child: Container(
width: 140,
height: 140,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withValues(alpha: 0.08),
),
),
),
Positioned(
left: -50,
bottom: -50,
child: Container(
width: 170,
height: 170,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withValues(alpha: 0.06),
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(18, 18, 18, 18),
child: Row(
children: [
Stack(
clipBehavior: Clip.none,
children: [
Container(
width: 74,
height: 74,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withValues(alpha: 0.16),
border: Border.all(
color: Colors.white.withValues(alpha: 0.22),
),
),
child: ClipOval(
child: Padding(
padding: const EdgeInsets.all(10),
child: Image.asset(
'assets/image/onboarding_consultation.png',
fit: BoxFit.cover,
),
),
),
)
.animate(onPlay: (c) => c.repeat(reverse: true))
.moveY(begin: 0, end: -2, duration: 1800.ms),
Positioned(
right: 2,
bottom: 2,
child: Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: const Color(0xFF34C759),
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
),
),
),
],
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: AppTypography.h2.copyWith(
color: Colors.white,
fontSize: 20,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
subtitle,
style: AppTypography.body.copyWith(
color: Colors.white.withValues(alpha: 0.9),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 10),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.14),
borderRadius: BorderRadius.circular(999),
border: Border.all(
color: Colors.white.withValues(alpha: 0.18),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
PhosphorIconsRegular.sealCheck,
color: Colors.white,
size: 16,
),
const SizedBox(width: 6),
Text(
'Akun Terverifikasi',
style: AppTypography.caption.copyWith(
color: Colors.white,
fontWeight: FontWeight.w700,
),
),
],
),
),
],
),
),
const SizedBox(width: 10),
_IconPillButton(
icon: LucideIcons.pencil,
onTap: onEditTap,
),
],
),
),
],
),
)
.animate()
.fadeIn(duration: 320.ms, curve: Curves.easeOut)
.slideY(begin: -0.03, end: 0, duration: 320.ms, curve: Curves.easeOut);
}
}
class _IconPillButton extends StatelessWidget {
const _IconPillButton({required this.icon, required this.onTap});
final IconData icon;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return Material(
color: Colors.white.withValues(alpha: 0.14),
borderRadius: BorderRadius.circular(999),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(999),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(999),
border: Border.all(color: Colors.white.withValues(alpha: 0.18)),
),
child: Icon(icon, color: Colors.white, size: 18),
),
),
);
}
}
class _ChildrenSection extends StatelessWidget {
const _ChildrenSection({
required this.children,
required this.activeChildId,
required this.onAdd,
required this.onSelect,
});
final List<MobileChildModel> children;
final int? activeChildId;
final VoidCallback onAdd;
final ValueChanged<int> onSelect;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
'Daftar Anak',
style: AppTypography.h2.copyWith(color: SgColors.textPrimary),
overflow: TextOverflow.ellipsis,
),
),
TextButton.icon(
onPressed: onAdd,
icon: const Icon(LucideIcons.plus),
label: const Text('Tambah'),
style: TextButton.styleFrom(
foregroundColor: const Color(0xFF0B7A86),
),
),
],
),
const SizedBox(height: 10),
if (children.isEmpty)
HealthCard(
child: Row(
children: [
Container(
width: 46,
height: 46,
decoration: BoxDecoration(
color: const Color(0xFF7FD6C2).withValues(alpha: 0.18),
borderRadius: BorderRadius.circular(16),
),
child: const Icon(
LucideIcons.baby,
color: Color(0xFF0B7A86),
),
),
const SizedBox(width: 12),
const Expanded(
child: Text(
'Belum ada data anak. Tambahkan anak untuk mulai memantau status gizi.',
style: AppTypography.body,
),
),
],
),
)
else
SizedBox(
height: 152,
child: ListView.separated(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
itemCount: children.length,
separatorBuilder: (_, __) => const SizedBox(width: 12),
itemBuilder: (context, index) {
final c = children[index];
final active = c.id == activeChildId;
return _ChildCard(
child: c,
active: active,
onTap: () => onSelect(c.id),
);
},
),
),
],
)
.animate()
.fadeIn(duration: 320.ms, curve: Curves.easeOut)
.slideY(begin: 0.02, end: 0, duration: 320.ms, curve: Curves.easeOut);
}
}
class _ChildCard extends StatelessWidget {
const _ChildCard({
required this.child,
required this.active,
required this.onTap,
});
final MobileChildModel child;
final bool active;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final status = (child.latestStatus ?? '').trim().isEmpty
? 'Status: Belum ada'
: 'Status: ${child.latestStatus}';
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(22),
child: AnimatedContainer(
duration: const Duration(milliseconds: 220),
curve: Curves.easeOut,
width: 260,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22),
border: Border.all(
color: active ? const Color(0xFF0B7A86) : const Color(0xFFE2E8E6),
width: active ? 1.8 : 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: active ? 0.10 : 0.06),
blurRadius: active ? 26 : 18,
offset: const Offset(0, 12),
),
],
),
child: Row(
children: [
Stack(
children: [
ChildAvatar(
name: child.nama,
gender: child.jenisKelamin,
photoUrl: child.photoUrl,
radius: 28,
),
if (active)
Positioned(
right: 0,
top: 0,
child: Container(
width: 14,
height: 14,
decoration: BoxDecoration(
color: const Color(0xFF0B7A86),
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
),
),
),
],
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
child.nama,
style: AppTypography.h3.copyWith(color: SgColors.textPrimary),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
'${child.jenisKelamin == 'P' ? 'Perempuan' : 'Laki-laki'}${formatAgeFromBirthDate(child.tanggalLahir)}',
style: AppTypography.caption,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 10),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: active
? const Color(0xFF7FD6C2).withValues(alpha: 0.22)
: const Color(0xFFF1F5F4),
borderRadius: BorderRadius.circular(999),
),
child: Text(
status,
style: AppTypography.caption.copyWith(
color: active ? const Color(0xFF085B63) : SgColors.textSecondary,
fontWeight: FontWeight.w700,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
const SizedBox(width: 6),
PopupMenuButton<String>(
onSelected: (value) async {
if (value == 'edit') {
await _showEditChildDialog(context, child);
}
if (value == 'delete') {
await _showDeleteChildDialog(context, child);
}
},
itemBuilder: (context) => const [
PopupMenuItem(value: 'edit', child: Text('Edit')),
PopupMenuItem(value: 'delete', child: Text('Hapus')),
],
icon: Icon(
LucideIcons.moreVertical,
color: active ? const Color(0xFF0B7A86) : SgColors.textSecondary,
),
),
],
),
),
).animate().scale(
begin: const Offset(0.99, 0.99),
end: const Offset(1, 1),
duration: 220.ms,
);
}
}
class _SettingsCard extends StatelessWidget {
const _SettingsCard({
required this.onTapAccount,
required this.onTapPrivacy,
required this.onTapHelp,
});
final VoidCallback onTapAccount;
final VoidCallback onTapPrivacy;
final VoidCallback onTapHelp;
@override
Widget build(BuildContext context) {
return HealthCard(
padding: EdgeInsets.zero,
child: Column(
children: [
_SettingsTile(
icon: LucideIcons.user,
color: const Color(0xFF0B7A86),
title: 'Informasi Akun',
subtitle: 'Edit nama, foto, dan nomor telepon',
onTap: onTapAccount,
),
const Divider(height: 1, color: SgColors.border),
_SettingsTile(
icon: LucideIcons.shield,
color: const Color(0xFF34A853),
title: 'Privasi & Keamanan',
subtitle: 'Kebijakan privasi dan keamanan akun',
onTap: onTapPrivacy,
),
const Divider(height: 1, color: SgColors.border),
_SettingsTile(
icon: LucideIcons.messageCircle,
color: const Color(0xFF3B82F6),
title: 'Bantuan',
subtitle: 'FAQ, pusat bantuan, hubungi admin',
onTap: onTapHelp,
),
],
),
)
.animate()
.fadeIn(duration: 280.ms, curve: Curves.easeOut)
.slideY(begin: 0.02, end: 0, duration: 280.ms, curve: Curves.easeOut);
}
}
class _SettingsTile extends StatelessWidget {
const _SettingsTile({
required this.icon,
required this.color,
required this.title,
required this.subtitle,
required this.onTap,
});
final IconData icon;
final Color color;
final String title;
final String subtitle;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
_IconBadge(icon: icon, color: color),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: AppTypography.h3),
const SizedBox(height: 2),
Text(
subtitle,
style: AppTypography.caption,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
const Icon(
LucideIcons.chevronRight,
color: SgColors.textSecondary,
),
],
),
),
).animate().scale(
begin: const Offset(1, 1),
end: const Offset(0.99, 0.99),
duration: 140.ms,
);
}
}
class _IconBadge extends StatelessWidget {
const _IconBadge({required this.icon, required this.color});
final IconData icon;
final Color color;
@override
Widget build(BuildContext context) {
return Container(
width: 42,
height: 42,
decoration: BoxDecoration(
color: color.withValues(alpha: 0.14),
borderRadius: BorderRadius.circular(16),
),
child: Icon(icon, color: color, size: 20),
);
}
}
class _LogoutCard extends StatelessWidget {
const _LogoutCard({required this.onLogout});
final VoidCallback onLogout;
@override
Widget build(BuildContext context) {
return HealthCard(
color: const Color(0xFFFFF5F5),
borderColor: const Color(0xFFFFCDD2),
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: const Color(0xFFE53935).withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(16),
),
child: const Icon(
LucideIcons.logOut,
color: Color(0xFFE53935),
),
),
const SizedBox(width: 12),
const Expanded(
child: Text(
'Keluar Aplikasi',
style: AppTypography.h3,
overflow: TextOverflow.ellipsis,
),
),
TextButton(
onPressed: onLogout,
style: TextButton.styleFrom(foregroundColor: const Color(0xFFE53935)),
child: const Text('Keluar'),
),
],
),
)
.animate()
.fadeIn(duration: 240.ms, curve: Curves.easeOut)
.slideY(begin: 0.02, end: 0, duration: 240.ms, curve: Curves.easeOut);
}
}
class _ProfileSkeleton extends StatelessWidget {
const _ProfileSkeleton();
@override
Widget build(BuildContext context) {
return Shimmer.fromColors(
baseColor: const Color(0xFFE8EEEC),
highlightColor: const Color(0xFFF7FAF9),
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 14, 20, 28),
child: Column(
children: [
Container(
height: 124,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(28),
),
),
const SizedBox(height: 18),
Container(
height: 168,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22),
),
),
const SizedBox(height: 22),
Container(
height: 260,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22),
),
),
const SizedBox(height: 18),
Container(
height: 74,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22),
),
),
],
),
),
);
}
}
class _ProfileError extends StatelessWidget {
const _ProfileError({required this.onRetry});
final VoidCallback onRetry;
@override
Widget build(BuildContext context) {
return ErrorState(
message: 'Profil tidak dapat dimuat. Periksa koneksi lalu coba lagi.',
onRetry: onRetry,
);
}
}
Future<bool?> _confirmLogout(BuildContext context) {
return showGeneralDialog<bool>(
context: context,
barrierLabel: 'logout',
barrierDismissible: true,
barrierColor: Colors.black.withValues(alpha: 0.38),
pageBuilder: (context, _, __) {
return Center(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Padding(
padding: const EdgeInsets.all(20),
child: Material(
color: Colors.transparent,
child: Container(
width: double.infinity,
constraints: const BoxConstraints(maxWidth: 420),
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.12),
blurRadius: 30,
offset: const Offset(0, 18),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: const [
Icon(
LucideIcons.logOut,
color: Color(0xFFE53935),
),
SizedBox(width: 10),
Expanded(
child: Text(
'Konfirmasi Keluar',
style: AppTypography.h2,
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 10),
const Text(
'Apakah Anda yakin ingin keluar?',
style: AppTypography.body,
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: SizedBox(
height: 52,
child: OutlinedButton(
onPressed: () => Navigator.of(context).pop(false),
style: OutlinedButton.styleFrom(
foregroundColor: SgColors.textPrimary,
side: const BorderSide(color: SgColors.border),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: const Text(
'Batal',
style: TextStyle(fontWeight: FontWeight.w700),
),
),
),
),
const SizedBox(width: 10),
Expanded(
child: SizedBox(
height: 52,
child: FilledButton(
onPressed: () => Navigator.of(context).pop(true),
style: FilledButton.styleFrom(
backgroundColor: const Color(0xFFE53935),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: const Text(
'Keluar',
style: TextStyle(fontWeight: FontWeight.w800),
),
),
),
),
],
),
],
),
).animate().fadeIn(duration: 180.ms).scale(
begin: const Offset(0.98, 0.98),
end: const Offset(1, 1),
duration: 180.ms,
curve: Curves.easeOut,
),
),
),
),
);
},
transitionDuration: const Duration(milliseconds: 180),
transitionBuilder: (context, animation, _, child) {
return FadeTransition(
opacity: CurvedAnimation(parent: animation, curve: Curves.easeOut),
child: child,
);
},
);
}
void _toast(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
String _profileRoleToLabel(String role, String fallbackPhone) {
final v = role.toLowerCase();
if (v.contains('ortu') || v.contains('orang_tua') || v.contains('parent')) {
return 'Orang Tua Aktif';
}
if (v.contains('admin')) {
return 'Admin';
}
return fallbackPhone.isEmpty ? 'Akun Aktif' : fallbackPhone;
}
Future<void> _showEditChildDialog(BuildContext context, MobileChildModel child) async {
final api = ApiService();
final nameController = TextEditingController(text: child.nama);
String gender = child.jenisKelamin;
DateTime? birthDate = DateTime.tryParse(child.tanggalLahir);
await showDialog<void>(
context: context,
builder: (dialogContext) {
return StatefulBuilder(
builder: (context, setLocal) {
return AlertDialog(
title: const Text('Edit Data Anak'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: nameController,
decoration: const InputDecoration(labelText: 'Nama Anak'),
),
const SizedBox(height: 10),
DropdownButtonFormField<String>(
value: gender,
items: const [
DropdownMenuItem(value: 'L', child: Text('Laki-laki')),
DropdownMenuItem(value: 'P', child: Text('Perempuan')),
],
onChanged: (v) {
if (v == null) return;
setLocal(() => gender = v);
},
decoration: const InputDecoration(labelText: 'Jenis Kelamin'),
),
const SizedBox(height: 10),
OutlinedButton(
onPressed: () async {
final picked = await showDatePicker(
context: context,
initialDate: birthDate ?? DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime.now(),
);
if (picked != null) {
setLocal(() => birthDate = picked);
}
},
child: Text(
birthDate == null
? 'Pilih Tanggal Lahir'
: 'Tanggal: ${birthDate!.toIso8601String().split('T').first}',
),
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(),
child: const Text('Batal'),
),
FilledButton(
onPressed: () async {
try {
await api.updateChild(
childId: child.id,
data: {
'nama': nameController.text.trim(),
'tanggal_lahir': (birthDate ?? DateTime.now())
.toIso8601String()
.split('T')
.first,
'jenis_kelamin': gender,
},
);
final children = await api.getChildren();
SgiziAppState.instance.setChildren(children);
if (dialogContext.mounted) Navigator.of(dialogContext).pop();
} catch (e) {
if (!dialogContext.mounted) return;
ScaffoldMessenger.of(dialogContext).showSnackBar(
SnackBar(content: Text('Gagal edit anak: $e')),
);
}
},
child: const Text('Simpan'),
),
],
);
},
);
},
);
}
Future<void> _showDeleteChildDialog(BuildContext context, MobileChildModel child) async {
final api = ApiService();
final confirm = await showDialog<bool>(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('Hapus Anak'),
content: Text('Yakin ingin menghapus data ${child.nama}?'),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(false),
child: const Text('Batal'),
),
FilledButton(
onPressed: () => Navigator.of(dialogContext).pop(true),
style: FilledButton.styleFrom(backgroundColor: const Color(0xFFE53935)),
child: const Text('Hapus'),
),
],
),
);
if (confirm != true) return;
try {
await api.deleteChild(childId: child.id);
final children = await api.getChildren();
SgiziAppState.instance.setChildren(children);
} catch (e) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Gagal hapus anak: $e')),
);
}
}