NIM_E31222534/Androidnya/lib/screens/dashboard/profile_screen.dart

1685 lines
63 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../services/auth_service.dart';
import '../../services/profile_service.dart';
import '../../services/api_service.dart';
import '../../services/dashboard_service.dart';
import '../../models/profile_model.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:math';
import '../../services/perkembangan_service.dart';
import '../../services/jadwal_service.dart';
import 'anak_screen.dart';
import 'edit_profile_screen.dart';
import '../../services/stunting_service.dart';
class ProfileScreen extends StatefulWidget {
@override
_ProfileScreenState createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
late Animation<double> _slideAnimation;
final AuthService _authService = AuthService();
final ProfileService _profileService = ProfileService();
final ApiService _apiService = ApiService();
final DashboardService _dashboardService = DashboardService();
final JadwalService _jadwalService = JadwalService();
// User data
String _userName = 'User';
String _userInitials = 'U';
String _childName = '';
String _childAge = '';
String _childGender = '';
String _email = '';
String _phone = '';
String _address = '';
String _nik = '';
double _childHeight = 0;
double _childWeight = 0;
String _childStatus = '-';
bool _isChildStunting = false;
int _childCount = 0;
int _scheduleCount = 0;
bool _isLoading = true;
// Dashboard data
Map<String, dynamic> _dashboardData = {};
int? _selectedAnakId;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 1000),
);
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: Interval(0.1, 1.0, curve: Curves.easeOut),
));
_slideAnimation = Tween<double>(
begin: 50.0,
end: 0.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: Interval(0.2, 0.8, curve: Curves.easeOut),
));
// Clear any API cache first
_apiService.clearChildCache();
// Load data initially
_loadUserData();
_animationController.forward();
}
Future<void> _loadUserData() async {
setState(() {
_isLoading = true;
});
try {
// Get data from SharedPreferences first
final prefs = await SharedPreferences.getInstance();
// Reset child count to 0
_childCount = 0;
await prefs.setInt('child_count', 0);
// Basic user info
String? userName = prefs.getString('nama_ibu');
String? nik = prefs.getString('nik');
int? userId = prefs.getInt('user_id');
// Other profile info - Explicitly get from SharedPreferences first
String? email = prefs.getString('email') ?? '';
String? phone = prefs.getString('no_telp') ?? '';
String? address = prefs.getString('alamat') ?? '';
print('Initial data from SharedPreferences:');
print('Email: $email');
print('Phone: $phone');
// Load dashboard data to get child info (same as dashboard screen)
try {
// Get last selected anak ID
_selectedAnakId = await _dashboardService.getLastSelectedAnakId();
print('Selected Anak ID: $_selectedAnakId');
if (_selectedAnakId != null) {
// Get dashboard summary
_dashboardData = await _dashboardService.getDashboardSummary(anakId: _selectedAnakId);
print('Dashboard data loaded: ${_dashboardData.toString()}');
if (_dashboardData['success'] == true && _dashboardData['data'] != null) {
// Extract child info from dashboard data
final childData = _dashboardData['data']['anak'] ?? {};
final statsData = _dashboardData['data']['statistik'] ?? {};
final growthData = _dashboardData['data']['pertumbuhan'] ?? {};
// Debug the data structure
print('Child data: $childData');
print('Stats data: $statsData');
print('Growth data: $growthData');
// First try to get direct data from perkembangan service to ensure consistency
try {
final perkembanganService = PerkembanganService();
final perkembanganList = await perkembanganService.getPerkembanganByAnakId(_selectedAnakId!);
if (perkembanganList.isNotEmpty) {
// Sort by date descending to get the most recent
perkembanganList.sort((a, b) {
final dateA = DateTime.parse(a['tanggal']);
final dateB = DateTime.parse(b['tanggal']);
return dateB.compareTo(dateA); // Most recent first
});
// Get the most recent record
final latestRecord = perkembanganList.first;
print('Using most recent perkembangan data: $latestRecord');
_childHeight = double.tryParse(latestRecord['tinggi_badan'].toString()) ?? 0;
_childWeight = double.tryParse(latestRecord['berat_badan'].toString()) ?? 0;
print('Perkembangan height: $_childHeight, weight: $_childWeight');
}
} catch (e) {
print('Error getting perkembangan data: $e, falling back to dashboard data');
}
// If we couldn't get perkembangan data, fallback to dashboard data
if (_childHeight <= 0 || _childWeight <= 0) {
// Update child info
String childName = childData['nama_anak'] ?? '';
String childAge = childData['usia'] ?? statsData['age'] ?? '';
String childGender = childData['jenis_kelamin'] ?? '';
// Extract height and weight - try all possible paths
_childHeight = 0;
_childWeight = 0;
try {
// Try directly from growth data (pertumbuhan)
if (growthData is Map && growthData.isNotEmpty) {
if (growthData.containsKey('tinggi_badan')) {
var tinggi = growthData['tinggi_badan'];
var berat = growthData['berat_badan'];
print('Found direct growth data: tinggi=$tinggi, berat=$berat');
_childHeight = double.tryParse(tinggi.toString()) ?? 0;
_childWeight = double.tryParse(berat.toString()) ?? 0;
}
}
// If not found, try from stats data
if (_childHeight <= 0 && statsData is Map && statsData.isNotEmpty) {
if (statsData.containsKey('height') && statsData['height'] is Map) {
var heightData = statsData['height'];
var weightData = statsData['weight'];
print('Found stats data: height=$heightData, weight=$weightData');
if (heightData.containsKey('value')) {
_childHeight = double.tryParse(heightData['value'].toString()) ?? 0;
}
if (weightData != null && weightData.containsKey('value')) {
_childWeight = double.tryParse(weightData['value'].toString()) ?? 0;
}
}
}
// Last resort - check if values are directly in anak data
if (_childHeight <= 0 && childData is Map && childData.isNotEmpty) {
if (childData.containsKey('tinggi_badan') || childData.containsKey('tinggi')) {
var tinggi = childData['tinggi_badan'] ?? childData['tinggi'];
var berat = childData['berat_badan'] ?? childData['berat'];
print('Found child data: tinggi=$tinggi, berat=$berat');
_childHeight = double.tryParse(tinggi.toString()) ?? 0;
_childWeight = double.tryParse(berat.toString()) ?? 0;
}
}
} catch (e) {
print('Error extracting height/weight: $e');
}
print('Final extracted values: height=$_childHeight, weight=$_childWeight');
// Update local state with dashboard data
_childName = childName;
_childAge = childAge;
_childGender = childGender;
} else {
// We already got data from perkembangan service, just update other fields
String childName = childData['nama_anak'] ?? '';
String childAge = childData['usia'] ?? statsData['age'] ?? '';
String childGender = childData['jenis_kelamin'] ?? '';
_childName = childName;
_childAge = childAge;
_childGender = childGender;
}
// Try to get stunting data first
try {
final stuntingService = StuntingService();
final stuntingData = await stuntingService.getStuntingByAnakId(_selectedAnakId!);
if (stuntingData.isNotEmpty) {
// Sort by latest date
stuntingData.sort((a, b) => b.tanggalPemeriksaan.compareTo(a.tanggalPemeriksaan));
// Get latest status
final latestStuntingData = stuntingData.first;
setState(() {
_childStatus = latestStuntingData.status;
_isChildStunting = _childStatus.toLowerCase().contains('stunting');
});
print('Status from stunting data: $_childStatus');
}
} catch (e) {
print('Error getting stunting data: $e');
// Fallback to stats data if stunting data not available
if (statsData.isNotEmpty) {
String? status;
if (statsData['overall_status'] != null) {
status = statsData['overall_status'].toString();
} else if (statsData['status'] != null) {
status = statsData['status'].toString();
} else if (statsData['stunting_status'] != null) {
status = statsData['stunting_status'].toString();
}
if (status != null) {
final normalizedStatus = status.toLowerCase().trim();
setState(() {
if (normalizedStatus.contains('stunting')) {
if (normalizedStatus.contains('resiko') || normalizedStatus.contains('risiko')) {
_childStatus = 'Resiko Stunting';
} else if (!normalizedStatus.contains('tidak')) {
_childStatus = 'Stunting';
} else if (normalizedStatus.contains('tidak')) {
_childStatus = 'Tidak Stunting';
}
} else if (normalizedStatus.contains('normal')) {
_childStatus = 'Tidak Stunting';
}
_isChildStunting = _childStatus.toLowerCase() == 'stunting';
});
print('Status from stats data: $_childStatus');
}
}
}
}
}
} catch (e) {
print('Error loading dashboard data: $e');
}
// Try to get fresh user data from API if possible
bool apiHasAnakData = false;
try {
if (nik != null && nik.isNotEmpty) {
print('Fetching fresh user data from API');
final userInfo = await _authService.getCurrentUser();
print('API Response: ${userInfo.toString().substring(0, min(userInfo.toString().length, 500))}...');
if (userInfo['success'] == true && userInfo['data'] != null) {
final userData = userInfo['data'];
// Update user data with better null checking
userName = userData['nama'] ?? userData['nama_ibu'] ?? userName;
// For email - check multiple possible field names and sources
if (userData['email'] != null && userData['email'].toString().isNotEmpty) {
email = userData['email'].toString();
print('Email from API: $email');
await prefs.setString('email', email);
}
// For phone - check multiple possible field names
if (userData['no_telp'] != null && userData['no_telp'].toString().isNotEmpty) {
phone = userData['no_telp'].toString();
print('Phone from API (no_telp): $phone');
await prefs.setString('no_telp', phone);
} else if (userData['telepon'] != null && userData['telepon'].toString().isNotEmpty) {
phone = userData['telepon'].toString();
print('Phone from API (telepon): $phone');
await prefs.setString('no_telp', phone);
} else if (userData['nomor_telepon'] != null && userData['nomor_telepon'].toString().isNotEmpty) {
phone = userData['nomor_telepon'].toString();
print('Phone from API (nomor_telepon): $phone');
await prefs.setString('no_telp', phone);
}
address = userData['alamat'] ?? address;
// Update preferences with fresh data
if (userName != null) await prefs.setString('nama_ibu', userName);
if (address != null) await prefs.setString('alamat', address);
// Check for child data in API response
if (userData['anak'] != null && userData['anak'] is List) {
// Update child count dengan memeriksa apakah array benar-benar berisi data
if (userData['anak'].isNotEmpty) {
_childCount = userData['anak'].length;
await prefs.setInt('child_count', _childCount);
apiHasAnakData = true;
print('Updated child count from API: $_childCount');
} else {
// Array kosong, berarti tidak ada anak
_childCount = 0;
await prefs.setInt('child_count', 0);
apiHasAnakData = true; // API memberikan data valid (array kosong)
print('API reports no children (empty array)');
}
}
}
}
} catch (e) {
print('Error fetching user data from API: $e');
// Continue with stored data
}
// If API has confirmed there are no children, don't try to get more data
if (!apiHasAnakData && userId != null) {
try {
final List<dynamic> children = await _profileService.getChildren(userId: userId);
if (children.isNotEmpty) {
// Pastikan ini bukan dummy data
bool isDummyData = children.length == 1 &&
(children[0]['nama']?.toString() == 'Data Dummy - API Error' ||
children[0]['nama']?.toString() == 'Error Data');
if (!isDummyData) {
_childCount = children.length;
await prefs.setInt('child_count', _childCount);
print('Updated child count from ProfileService: $_childCount');
// Ambil jadwal dari semua anak
_scheduleCount = 0;
for (var child in children) {
try {
if (child['id'] != null) {
final jadwalList = await _jadwalService.getRiwayatJadwalAnak(child['id']);
// Filter jadwal yang belum terlaksana
final jadwalBelumTerlaksana = jadwalList.where((jadwal) =>
jadwal.status?.toLowerCase() == 'belum' ||
jadwal.status?.toLowerCase() == 'jadwal' ||
jadwal.isImplemented == false
).toList();
_scheduleCount += jadwalBelumTerlaksana.length;
print('Jadwal belum terlaksana untuk anak ${child['id']}: ${jadwalBelumTerlaksana.length} dari total ${jadwalList.length}');
}
} catch (e) {
print('Error mengambil jadwal untuk anak ${child['id']}: $e');
}
}
print('Total jadwal belum terlaksana untuk semua anak: $_scheduleCount');
} else {
print('Ignoring dummy data from ProfileService');
_childCount = 0;
await prefs.setInt('child_count', 0);
}
} else {
// List kosong, berarti tidak ada anak
_childCount = 0;
await prefs.setInt('child_count', 0);
print('ProfileService reports no children');
}
} catch (e) {
print('Error fetching children via ProfileService: $e');
}
} else if (apiHasAnakData && _selectedAnakId != null) {
// Ambil jadwal untuk anak yang dipilih
try {
final jadwalList = await _jadwalService.getUpcomingJadwalForChild(_selectedAnakId!);
_scheduleCount = jadwalList.length;
print('Jadwal untuk anak $_selectedAnakId: $_scheduleCount');
} catch (e) {
print('Error mengambil jadwal untuk anak $_selectedAnakId: $e');
}
}
// Double-check data after API call to ensure we have the latest
if (email == null || email.isEmpty) {
email = prefs.getString('email') ?? '';
}
if (phone == null || phone.isEmpty) {
phone = prefs.getString('no_telp') ?? '';
}
// Update state with all the data we've gathered
setState(() {
_isLoading = false;
_userName = userName ?? 'User';
_nik = nik ?? '';
_email = email ?? '';
_phone = phone ?? '';
_address = address ?? '';
// Generate user initials
if (_userName.isNotEmpty) {
if (_userName.contains(' ')) {
final nameParts = _userName.split(' ');
if (nameParts.length >= 2) {
_userInitials = '${nameParts[0][0]}${nameParts[1][0]}';
} else {
_userInitials = _userName.substring(0, _userName.length > 1 ? 2 : 1);
}
} else {
_userInitials = _userName.substring(0, _userName.length > 1 ? 2 : 1);
}
_userInitials = _userInitials.toUpperCase();
}
});
} catch (e) {
print('Error loading user data: $e');
setState(() {
_isLoading = false;
});
}
}
// Method to refresh data from API
Future<void> _refreshData() async {
setState(() {
_isLoading = true;
});
try {
// Ambil ID anak yang terakhir dipilih dari dashboard
_selectedAnakId = await _dashboardService.getLastSelectedAnakId();
print('Refreshing data for anak ID: $_selectedAnakId');
if (_selectedAnakId != null) {
// Pastikan cache dihapus untuk mendapatkan data terbaru
_apiService.clearChildCache();
// Ambil data dashboard untuk anak yang dipilih
_dashboardData = await _dashboardService.getDashboardSummary(anakId: _selectedAnakId);
print('Dashboard data refreshed: ${_dashboardData.toString()}');
if (_dashboardData['success'] == true && _dashboardData['data'] != null) {
final childData = _dashboardData['data']['anak'] ?? {};
final statsData = _dashboardData['data']['statistik'] ?? {};
final growthData = _dashboardData['data']['pertumbuhan'] ?? {};
print('Child data: $childData');
print('Stats data: $statsData');
print('Growth data: $growthData');
// Reset nilai awal data anak
double childHeight = 0;
double childWeight = 0;
String childName = childData['nama_anak'] ?? '';
String childAge = childData['usia'] ?? statsData['age'] ?? '';
String childGender = childData['jenis_kelamin'] ?? '';
String childStatus = '-';
bool isChildStunting = false;
// Ekstrak tinggi dan berat badan dari growth data
if (growthData is Map && growthData.isNotEmpty) {
if (growthData['tinggi_badan'] != null) {
childHeight = double.tryParse(growthData['tinggi_badan'].toString()) ?? 0;
print('Extracted height from growth data: $childHeight');
}
if (growthData['berat_badan'] != null) {
childWeight = double.tryParse(growthData['berat_badan'].toString()) ?? 0;
print('Extracted weight from growth data: $childWeight');
}
} else {
print('Growth data empty or invalid');
}
// Jika tidak berhasil mendapatkan tinggi dan berat dari growth data, coba dari stats data
if (childHeight <= 0 || childWeight <= 0) {
if (statsData is Map && statsData.isNotEmpty) {
// Cek tinggi dari stats data
if (statsData['height'] is Map && statsData['height']['value'] != null) {
childHeight = double.tryParse(statsData['height']['value'].toString()) ?? 0;
print('Extracted height from stats data: $childHeight');
}
// Cek berat dari stats data
if (statsData['weight'] is Map && statsData['weight']['value'] != null) {
childWeight = double.tryParse(statsData['weight']['value'].toString()) ?? 0;
print('Extracted weight from stats data: $childWeight');
}
}
}
// Jika masih tidak berhasil, cek langsung dari child data
if (childHeight <= 0 || childWeight <= 0) {
if (childData is Map && childData.isNotEmpty) {
if (childData['tinggi_badan'] != null) {
childHeight = double.tryParse(childData['tinggi_badan'].toString()) ?? 0;
print('Extracted height from child data: $childHeight');
}
if (childData['berat_badan'] != null) {
childWeight = double.tryParse(childData['berat_badan'].toString()) ?? 0;
print('Extracted weight from child data: $childWeight');
}
}
}
// Coba dapatkan data stunting
try {
final stuntingService = StuntingService();
final stuntingData = await stuntingService.getStuntingByAnakId(_selectedAnakId!);
if (stuntingData.isNotEmpty) {
// Urutkan berdasarkan tanggal terbaru
stuntingData.sort((a, b) => b.tanggalPemeriksaan.compareTo(a.tanggalPemeriksaan));
final latestStuntingData = stuntingData.first;
// Jika data stunting memiliki tinggi dan berat, gunakan data tersebut
if (latestStuntingData.tinggiBadan > 0) {
childHeight = latestStuntingData.tinggiBadan;
print('Updated height from stunting data: $childHeight');
}
if (latestStuntingData.beratBadan > 0) {
childWeight = latestStuntingData.beratBadan;
print('Updated weight from stunting data: $childWeight');
}
// Ambil dan standardisasi status stunting
final status = latestStuntingData.status.toLowerCase().trim();
if (status.contains('stunting')) {
if (status.contains('resiko') || status.contains('risiko')) {
childStatus = 'Resiko Stunting';
} else if (!status.contains('tidak')) {
childStatus = 'Stunting';
} else if (status.contains('tidak')) {
childStatus = 'Tidak Stunting';
}
} else if (status.contains('normal')) {
childStatus = 'Tidak Stunting';
}
isChildStunting = childStatus == 'Stunting';
print('Status dari data stunting terbaru: $childStatus');
} else {
// Jika tidak ada data stunting, coba dari statistik dashboard
String? dashboardStatus;
if (statsData.isNotEmpty) {
if (statsData['overall_status'] != null) {
dashboardStatus = statsData['overall_status'].toString();
} else if (statsData['status'] != null) {
dashboardStatus = statsData['status'].toString();
} else if (statsData['stunting_status'] != null) {
dashboardStatus = statsData['stunting_status'].toString();
}
if (dashboardStatus != null) {
final statusNormalized = dashboardStatus.toLowerCase().trim();
if (statusNormalized.contains('stunting')) {
if (statusNormalized.contains('resiko') || statusNormalized.contains('risiko')) {
childStatus = 'Resiko Stunting';
} else if (!statusNormalized.contains('tidak')) {
childStatus = 'Stunting';
} else if (statusNormalized.contains('tidak')) {
childStatus = 'Tidak Stunting';
}
} else if (statusNormalized.contains('normal')) {
childStatus = 'Tidak Stunting';
}
isChildStunting = childStatus == 'Stunting';
print('Status dari statistik dashboard: $childStatus');
}
}
}
} catch (e) {
print('Error mendapatkan data stunting: $e');
// Tetap gunakan nilai default jika terjadi error
}
// Update semua data ke state sekaligus
setState(() {
_childName = childName;
_childAge = childAge;
_childGender = childGender;
_childHeight = childHeight;
_childWeight = childWeight;
_childStatus = childStatus;
_isChildStunting = isChildStunting;
_childCount = 1; // Since we're viewing a specific child
});
}
}
} catch (e) {
print('Error in _refreshData: $e');
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final screenSize = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.grey[100],
body: _isLoading
? Center(child: CircularProgressIndicator())
: RefreshIndicator(
onRefresh: _refreshData,
child: CustomScrollView(
physics: AlwaysScrollableScrollPhysics(),
slivers: [
// App Bar
SliverAppBar(
expandedHeight: screenSize.height * 0.35,
pinned: true,
stretch: true,
systemOverlayStyle: SystemUiOverlayStyle.light,
backgroundColor: Colors.transparent,
elevation: 0,
leading: Padding(
padding: EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.black.withOpacity(0.2),
),
child: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context),
),
),
),
flexibleSpace: FlexibleSpaceBar(
background: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.teal.shade400,
Colors.teal.shade800,
],
),
),
child: Stack(
children: [
// Background patterns
Positioned(
top: -50,
right: -20,
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: Colors.white.withOpacity(0.1),
),
),
),
Positioned(
bottom: -50,
left: -50,
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: Colors.white.withOpacity(0.1),
),
),
),
// Profile content
Center(
child: FadeTransition(
opacity: _fadeAnimation,
child: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, _slideAnimation.value),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Hero(
tag: 'profile_pic',
child: Container(
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: Colors.white,
width: 3,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 15,
spreadRadius: 2,
),
],
),
child: CircleAvatar(
radius: screenSize.width * 0.15,
backgroundColor: Colors.white.withOpacity(0.2),
child: Text(
_userInitials,
style: TextStyle(
fontSize: screenSize.width * 0.08,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
),
SizedBox(height: 20),
Text(
_userName,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 0.5,
shadows: [
Shadow(
color: Colors.black26,
offset: Offset(0, 2),
blurRadius: 4,
),
],
),
),
SizedBox(height: 6),
Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.white24,
borderRadius: BorderRadius.circular(20),
),
child: Text(
_childCount > 0
? _childName.isNotEmpty
? (_childCount > 1
? 'Ibu dari $_childName dan ${_childCount-1} anak lainnya'
: 'Ibu dari $_childName')
: 'Memiliki $_childCount anak'
: 'Belum memiliki data anak',
style: TextStyle(
color: Colors.white,
fontSize: 14,
),
),
),
],
),
);
},
),
),
),
],
),
),
collapseMode: CollapseMode.parallax,
),
bottom: PreferredSize(
preferredSize: Size.fromHeight(20),
child: Container(
height: 32,
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
),
),
),
),
// Content
SliverToBoxAdapter(
child: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Opacity(
opacity: _fadeAnimation.value,
child: Transform.translate(
offset: Offset(0, _slideAnimation.value),
child: child,
),
);
},
child: Padding(
padding: EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Quick stats
Row(
children: [
Expanded(
flex: 1,
child: _buildStatCard(
context: context,
title: 'Anak',
value: '$_childCount',
icon: Icons.child_care,
color: Colors.blue,
),
),
],
),
SizedBox(height: 24),
// Personal info section
Text(
'Informasi Pribadi',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
),
SizedBox(height: 12),
// Info cards
_buildProfileCard(
context: context,
title: 'Email',
content: _email.isNotEmpty ? _email : '-',
icon: Icons.email_outlined,
iconColor: Colors.red,
),
SizedBox(height: 12),
_buildProfileCard(
context: context,
title: 'No. Handphone',
content: _phone.isNotEmpty ? _phone : '-',
icon: Icons.phone_outlined,
iconColor: Colors.green,
),
SizedBox(height: 12),
_buildProfileCard(
context: context,
title: 'Alamat',
content: _address.isNotEmpty ? _address : '-',
icon: Icons.location_on_outlined,
iconColor: Colors.blue,
),
SizedBox(height: 12),
_buildProfileCard(
context: context,
title: 'NIK',
content: _nik.isNotEmpty ? _maskNIK(_nik) : '-',
icon: Icons.credit_card_outlined,
iconColor: Colors.purple,
),
SizedBox(height: 24),
// Child info section - only show if there's child data
if (_childName.isNotEmpty) ...[
Text(
'Informasi Anak',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
),
SizedBox(height: 12),
_buildChildCard(),
SizedBox(height: 24),
],
// Buttons section
_buildActionButtons(context),
SizedBox(height: 40),
],
),
),
),
),
],
),
),
bottomNavigationBar: BottomAppBar(
shape: CircularNotchedRectangle(),
notchMargin: 8,
elevation: 8,
child: Container(
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
child: IconButton(
icon: Icon(
Icons.home,
color: Colors.grey[500],
size: 28,
),
onPressed: () {
Navigator.pop(context);
},
),
),
Expanded(child: SizedBox(width: 40)),
Expanded(
child: IconButton(
icon: Icon(
Icons.person,
color: Colors.green[600],
size: 28,
),
onPressed: null, // Sedang di halaman profile
),
),
],
),
),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.green,
elevation: 4,
child: Icon(Icons.add_chart, size: 28),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AnakScreen(),
),
).then((_) => _refreshData());
},
tooltip: 'Tambah Data Anak',
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
}
// Helper method to mask NIK for privacy
String _maskNIK(String nik) {
if (nik.length <= 8) return nik;
return nik.substring(0, 8) + 'XXXXXXXX';
}
Widget _buildStatCard({
required BuildContext context,
required String title,
required String value,
required IconData icon,
required Color color,
}) {
// Verifikasi nilai untuk menghindari tampilan yang tidak akurat
String displayValue = value;
if (title == 'Anak' && value != '0') {
try {
final intValue = int.parse(value);
if (intValue > 10) {
displayValue = '1'; // Tampilkan nilai yang lebih masuk akal
}
} catch (e) {
// Gunakan nilai asli jika parsing gagal
}
}
return Container(
padding: EdgeInsets.symmetric(vertical: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
icon,
color: color,
size: 24,
),
),
SizedBox(height: 8),
Text(
displayValue,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
),
SizedBox(height: 4),
Text(
title,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
);
}
Widget _buildProfileCard({
required BuildContext context,
required String title,
required String content,
required IconData icon,
required Color iconColor,
}) {
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Row(
children: [
Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: iconColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
icon,
color: iconColor,
size: 24,
),
),
SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
SizedBox(height: 4),
Text(
content,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
),
],
),
),
],
),
);
}
Widget _buildChildCard() {
// Check if we have dashboard data
if (_dashboardData.isEmpty || _dashboardData['success'] != true || _dashboardData['data'] == null || _childCount <= 0) {
// No dashboard data or no children, show default/empty state
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Column(
children: [
Row(
children: [
Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
Icons.child_care,
color: Colors.orange,
size: 24,
),
),
SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Belum ada data anak",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
),
SizedBox(height: 4),
Text(
"Tambahkan data anak terlebih dahulu",
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
),
],
),
],
),
);
}
// We have dashboard data, extract and format height and weight
print('Building child card with height: $_childHeight, weight: $_childWeight');
String heightStr;
if (_childHeight > 0) {
// Format to 1 decimal place if needed
heightStr = _childHeight == _childHeight.roundToDouble()
? _childHeight.round().toString()
: _childHeight.toStringAsFixed(1);
heightStr += ' cm';
} else {
heightStr = "-";
}
String weightStr;
if (_childWeight > 0) {
// Format to 1 decimal place if needed
weightStr = _childWeight == _childWeight.roundToDouble()
? _childWeight.round().toString()
: _childWeight.toStringAsFixed(1);
weightStr += ' kg';
} else {
weightStr = "-";
}
// Gunakan status yang sudah disimpan dari _refreshData
String status = _childStatus;
Color statusColor = Colors.blue;
IconData statusIcon = Icons.info_outline;
// Tentukan warna dan icon sesuai dengan status
if (status == 'Stunting') {
statusColor = Colors.red;
statusIcon = Icons.warning_rounded;
} else if (status == 'Resiko Stunting') {
statusColor = Colors.orange;
statusIcon = Icons.warning_amber_rounded;
} else if (status == 'Tidak Stunting') {
statusColor = Colors.green;
statusIcon = Icons.check_circle;
} else if (status == '-') {
statusColor = Colors.grey;
statusIcon = Icons.info_outline;
}
print('Status untuk card anak: $status, Color: $statusColor');
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Column(
children: [
Row(
children: [
Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
Icons.child_care,
color: Colors.blue,
size: 24,
),
),
SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_childName.isNotEmpty ? _childName : '-',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
),
SizedBox(height: 4),
Text(
_childAge.isNotEmpty && _childGender.isNotEmpty
? '$_childAge$_childGender'
: (_childAge.isNotEmpty ? _childAge : (_childGender.isNotEmpty ? _childGender : '-')),
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
),
],
),
SizedBox(height: 16),
Divider(),
SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildChildStat('Tinggi', heightStr, Icons.height),
_buildChildStat('Berat', weightStr, Icons.monitor_weight),
_buildChildStat(
'Status',
status,
statusIcon,
customColor: statusColor
),
],
),
],
),
);
}
Widget _buildChildStat(
String label,
String value,
IconData icon,
{Color? customColor}
) {
bool isEmpty = value == "-" || value.isEmpty;
Color color = isEmpty
? Colors.grey
: (customColor ?? Colors.blue);
return Column(
children: [
Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
icon,
color: color,
size: 20,
),
),
SizedBox(height: 6),
Text(
value == "-" ? "-" : value,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: color,
),
),
SizedBox(height: 4),
Text(
label,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
);
}
Widget _buildActionButtons(BuildContext context) {
return Column(
children: [
Container(
width: double.infinity,
child: ElevatedButton.icon(
icon: Icon(Icons.edit),
label: Text('Edit Profil'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () => _showEditProfileDialog(context),
),
),
SizedBox(height: 16),
Container(
width: double.infinity,
child: OutlinedButton.icon(
icon: Icon(Icons.logout),
label: Text('Keluar'),
style: OutlinedButton.styleFrom(
foregroundColor: Colors.red,
side: BorderSide(color: Colors.red),
padding: EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () => _showLogoutDialog(context),
),
),
],
);
}
void _showLogoutDialog(BuildContext context) {
final navContext = Navigator.of(context);
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
title: Row(
children: [
Icon(
Icons.logout,
color: Colors.red,
),
SizedBox(width: 10),
Text(
"Konfirmasi",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
content: Text(
"Apakah Anda yakin ingin keluar dari aplikasi?",
style: TextStyle(fontSize: 16),
),
actions: [
TextButton(
style: TextButton.styleFrom(
foregroundColor: Colors.grey,
),
onPressed: () {
Navigator.pop(context);
},
child: Text("Batal"),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
onPressed: () async {
Navigator.pop(context);
// Implement logout logic using the profile service
try {
await _authService.logout();
} catch (e) {
print('Logout error: $e');
// Continue with navigation even if API logout fails
}
// Gunakan navContext yang disiapkan di awal
navContext.pushNamedAndRemoveUntil(
'/login',
(route) => false, // This clears the navigation stack
);
},
child: Text("Keluar"),
),
],
);
},
);
}
void _showEditProfileDialog(BuildContext context) {
final nameController = TextEditingController(text: _userName);
final emailController = TextEditingController(text: _email);
final phoneController = TextEditingController(text: _phone);
final addressController = TextEditingController(text: _address);
final nikController = TextEditingController(text: _nik);
final formKey = GlobalKey<FormState>();
bool isLoading = false;
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Row(
children: [
Icon(Icons.edit, color: Colors.teal),
SizedBox(width: 10),
Text('Edit Profil'),
],
),
content: SingleChildScrollView(
child: Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: nameController,
decoration: InputDecoration(
labelText: 'Nama Ibu',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Nama tidak boleh kosong';
}
return null;
},
),
SizedBox(height: 16),
TextFormField(
controller: emailController,
decoration: InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Email tidak boleh kosong';
}
if (!value.contains('@') || !value.contains('.')) {
return 'Email tidak valid';
}
return null;
},
),
SizedBox(height: 16),
TextFormField(
controller: phoneController,
decoration: InputDecoration(
labelText: 'No. Handphone',
prefixIcon: Icon(Icons.phone),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
keyboardType: TextInputType.phone,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Nomor handphone tidak boleh kosong';
}
if (value.length < 10) {
return 'Nomor handphone minimal 10 digit';
}
return null;
},
),
SizedBox(height: 16),
TextFormField(
controller: addressController,
decoration: InputDecoration(
labelText: 'Alamat',
prefixIcon: Icon(Icons.location_on),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
maxLines: 3,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Alamat tidak boleh kosong';
}
return null;
},
),
SizedBox(height: 16),
TextFormField(
controller: nikController,
decoration: InputDecoration(
labelText: 'NIK',
prefixIcon: Icon(Icons.credit_card),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
keyboardType: TextInputType.number,
maxLength: 16,
validator: (value) {
if (value == null || value.isEmpty) {
return 'NIK tidak boleh kosong';
}
if (value.length != 16) {
return 'NIK harus 16 digit';
}
return null;
},
),
],
),
),
),
actions: [
TextButton(
onPressed: isLoading ? null : () => Navigator.pop(context),
child: Text('Batal'),
style: TextButton.styleFrom(
foregroundColor: Colors.grey,
),
),
ElevatedButton(
onPressed: isLoading ? null : () async {
if (formKey.currentState!.validate()) {
setState(() => isLoading = true);
try {
final profile = ProfileModel(
id: 0, // ID akan diambil dari API
name: nameController.text,
email: emailController.text,
phone: phoneController.text,
address: addressController.text,
nik: nikController.text,
children: [], // Tidak perlu mengirim data anak
);
await _profileService.updateProfile(profile);
if (mounted) {
Navigator.pop(context, true); // Return true to indicate success
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Profil berhasil diperbarui'),
backgroundColor: Colors.green,
),
);
_refreshData(); // Refresh data setelah update
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Gagal memperbarui profil: ${e.toString()}'),
backgroundColor: Colors.red,
),
);
}
} finally {
if (mounted) {
setState(() => isLoading = false);
}
}
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: isLoading
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Text('Simpan'),
),
],
);
},
);
},
);
}
}