303 lines
10 KiB
Dart
303 lines
10 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../app_design.dart';
|
|
import '../models/api_result_model.dart';
|
|
import '../utils/nutrition_display_utils.dart';
|
|
import 'consultation_chat_screen.dart';
|
|
import 'recommendation_screen.dart';
|
|
|
|
class ResultScreen extends StatefulWidget {
|
|
const ResultScreen({super.key, required this.result});
|
|
|
|
final ApiResultModel result;
|
|
|
|
@override
|
|
State<ResultScreen> createState() => _ResultScreenState();
|
|
}
|
|
|
|
class _ResultScreenState extends State<ResultScreen>
|
|
with SingleTickerProviderStateMixin {
|
|
late final AnimationController _controller;
|
|
late final Animation<double> _fade;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(milliseconds: 650),
|
|
)..forward();
|
|
_fade = CurvedAnimation(parent: _controller, curve: Curves.easeOut);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final result = widget.result;
|
|
final measurement = result.measurement;
|
|
final visual = nutritionStatusVisual(result.statusGabungan);
|
|
final isNormal =
|
|
result.statusGabungan.toLowerCase().contains('normal') ||
|
|
result.statusGabungan.toLowerCase().contains('baik');
|
|
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFFEFF8F7),
|
|
appBar: AppBar(title: const Text('Hasil Analisis')),
|
|
body: FadeTransition(
|
|
opacity: _fade,
|
|
child: ListView(
|
|
padding: const EdgeInsets.fromLTRB(24, 24, 24, 180),
|
|
children: [
|
|
HealthCard(
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
|
|
child: Column(
|
|
children: [
|
|
CircleAvatar(
|
|
radius: 32,
|
|
backgroundColor: visual.color.withValues(alpha: 0.14),
|
|
child: Icon(
|
|
visual.icon,
|
|
color: visual.color,
|
|
size: 38,
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
Text(
|
|
'STATUS GIZI',
|
|
style: AppTypography.caption.copyWith(
|
|
letterSpacing: 4,
|
|
fontWeight: FontWeight.w800,
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
Text(
|
|
result.statusGabungan,
|
|
style: AppTypography.h1.copyWith(fontSize: 32),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 16),
|
|
StatusBadge(
|
|
text: visual.badgeLabel,
|
|
color: visual.color,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
HealthCard(
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const CircleAvatar(
|
|
radius: 25,
|
|
backgroundColor: Color(0xFFEAF7F7),
|
|
child: Icon(
|
|
Icons.child_care_rounded,
|
|
color: SgColors.primary,
|
|
),
|
|
),
|
|
const SizedBox(width: 14),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
measurement?.childName ?? 'Data Anak',
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: AppTypography.h3,
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'${result.identitas.umurBulan.toStringAsFixed(0)} Bulan | ${_genderLabel(result.identitas.jenisKelamin)}',
|
|
style: AppTypography.caption,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
const Text('TERAKHIR UKUR', style: AppTypography.caption),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
measurement == null
|
|
? 'Hari ini'
|
|
: formatMeasurementDate(measurement.tanggalUkur),
|
|
style: AppTypography.h3,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
Row(
|
|
children: [
|
|
const Icon(
|
|
Icons.trending_up_rounded,
|
|
color: SgColors.primary,
|
|
size: 20,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text('Detail Indikator Gizi', style: AppTypography.h2),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
MetricProgress(
|
|
label: 'Berat Badan / Umur (BB/U)',
|
|
description: 'Mengukur berat terhadap usia',
|
|
status: result.kategori.bbu,
|
|
value: _scoreToProgress(result.zScore.bbu),
|
|
icon: Icons.monitor_weight_outlined,
|
|
),
|
|
MetricProgress(
|
|
label: 'Tinggi Badan / Umur (TB/U)',
|
|
description: 'Mengukur tinggi terhadap usia',
|
|
status: result.kategori.tbu,
|
|
value: _scoreToProgress(result.zScore.tbu),
|
|
icon: Icons.straighten_rounded,
|
|
),
|
|
MetricProgress(
|
|
label: 'Berat / Tinggi (BB/TB)',
|
|
description: 'Proporsi tubuh ideal',
|
|
status: result.kategori.bbtb,
|
|
value: _scoreToProgress(result.zScore.bbtb),
|
|
icon: Icons.verified_outlined,
|
|
),
|
|
const SizedBox(height: 8),
|
|
HealthCard(
|
|
color: const Color(0xFFF5FBFA),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Icon(
|
|
Icons.info_outline_rounded,
|
|
color: SgColors.primary,
|
|
size: 20,
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text('Catatan Nutrisi', style: AppTypography.h3),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
recommendationStatusExplanation(result.statusGabungan),
|
|
style: AppTypography.body,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
bottomSheet: Container(
|
|
padding: const EdgeInsets.fromLTRB(24, 16, 24, 24),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withValues(alpha: 0.96),
|
|
border: const Border(top: BorderSide(color: SgColors.border)),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withValues(alpha: 0.06),
|
|
blurRadius: 16,
|
|
offset: const Offset(0, -8),
|
|
),
|
|
],
|
|
),
|
|
child: SafeArea(
|
|
top: false,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
if (!isNormal) ...[
|
|
HealthCard(
|
|
color: const Color(0xFFFFFBEB),
|
|
borderColor: const Color(0xFFF7E7C1),
|
|
child: Row(
|
|
children: const [
|
|
Icon(
|
|
Icons.warning_amber_rounded,
|
|
color: SgColors.warning,
|
|
),
|
|
SizedBox(width: 12),
|
|
Expanded(
|
|
child: Text(
|
|
'Status perlu perhatian. Konsultasi ahli gizi menjadi prioritas.',
|
|
style: AppTypography.body,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
],
|
|
if (!isNormal)
|
|
PrimaryButton(
|
|
label: 'Konsultasi Ahli Gizi',
|
|
icon: Icons.chat_bubble_outline_rounded,
|
|
onPressed: () {
|
|
Navigator.of(
|
|
context,
|
|
).push(fadeRoute(const ConsultationChatScreen()));
|
|
},
|
|
),
|
|
if (!isNormal) const SizedBox(height: 12),
|
|
PrimaryButton(
|
|
label: 'Lihat Rekomendasi Menu',
|
|
icon: Icons.restaurant_menu_rounded,
|
|
onPressed: () {
|
|
Navigator.of(context).push(
|
|
fadeRoute(
|
|
RecommendationScreen(
|
|
status: result.statusGabungan,
|
|
childId: measurement?.childId,
|
|
riwayatId: measurement?.id,
|
|
childName: measurement?.childName,
|
|
measuredAt: measurement?.tanggalUkur,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
if (isNormal) ...[
|
|
const SizedBox(height: 12),
|
|
PrimaryButton(
|
|
label: 'Konsultasi Ahli Gizi',
|
|
icon: Icons.chat_bubble_outline_rounded,
|
|
isOutlined: true,
|
|
onPressed: () {
|
|
Navigator.of(
|
|
context,
|
|
).push(fadeRoute(const ConsultationChatScreen()));
|
|
},
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
double _scoreToProgress(double score) {
|
|
if (score.isNaN) return 0.62;
|
|
return ((score + 3) / 6).clamp(0.08, 0.96).toDouble();
|
|
}
|
|
|
|
String _genderLabel(String gender) {
|
|
if (gender.toLowerCase().startsWith('p')) return 'Perempuan';
|
|
return 'Laki-laki';
|
|
}
|
|
}
|