glp-companion/lib/features/health_sync/presentation/widgets/log_weight_dialog.dart

147 lines
4.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../controllers/biometric_controller.dart';
/// Opens the manual weight-entry dialog (Web/Desktop fallback path, also
/// available on mobile for offline logging).
///
/// Resolves to `true` when the sample was persisted, `false` when saving
/// failed, and `null` when the user dismissed the dialog.
Future<bool?> showLogWeightDialog(BuildContext context) {
return showDialog<bool>(
context: context,
builder: (context) => const LogWeightDialog(),
);
}
/// Form for logging a body-weight measurement without native health access.
class LogWeightDialog extends ConsumerStatefulWidget {
const LogWeightDialog({super.key});
@override
ConsumerState<LogWeightDialog> createState() => _LogWeightDialogState();
}
class _LogWeightDialogState extends ConsumerState<LogWeightDialog> {
late final TextEditingController _weightController = TextEditingController();
late DateTime _measuredAt = DateTime.now();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
bool _isSaving = false;
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Log Weight'),
content: SizedBox(
width: 440,
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
controller: _weightController,
decoration: const InputDecoration(
labelText: 'Weight (lbs)',
border: OutlineInputBorder(),
),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
validator: (value) {
final parsed = double.tryParse((value ?? '').trim());
if (parsed == null || parsed <= 0) {
return 'Enter a valid weight';
}
return null;
},
),
const SizedBox(height: 16),
OutlinedButton.icon(
onPressed: _pickMeasuredAt,
icon: const Icon(Icons.calendar_today, size: 16),
label: Text('Measured: ${_formatDate(_measuredAt)}'),
),
],
),
),
),
),
actions: [
TextButton(
onPressed: _isSaving ? null : () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
FilledButton.icon(
onPressed: _isSaving ? null : _save,
icon: _isSaving
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.monitor_weight_outlined),
label: const Text('Save Weight'),
),
],
);
}
String _formatDate(DateTime time) {
const months = [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
];
return '${months[time.month - 1]} ${time.day}, ${time.year}';
}
Future<void> _pickMeasuredAt() async {
final now = DateTime.now();
final pickedDate = await showDatePicker(
context: context,
initialDate: _measuredAt,
firstDate: DateTime(now.year - 5),
lastDate: now,
);
if (pickedDate == null || !mounted) return;
final pickedTime = await showTimePicker(
context: context,
initialTime: TimeOfDay.fromDateTime(_measuredAt),
);
if (!mounted) return;
setState(() {
_measuredAt = DateTime(
pickedDate.year,
pickedDate.month,
pickedDate.day,
pickedTime?.hour ?? _measuredAt.hour,
pickedTime?.minute ?? _measuredAt.minute,
);
});
}
Future<void> _save() async {
if (!(_formKey.currentState?.validate() ?? false)) return;
setState(() => _isSaving = true);
final saved =
await ref.read(biometricControllerProvider.notifier).logWeight(
weightLbs: double.parse(_weightController.text.trim()),
timestamp: _measuredAt,
);
if (!mounted) return;
Navigator.of(context).pop(saved);
}
@override
void dispose() {
_weightController.dispose();
super.dispose();
}
}