import 'package:flutter/material.dart'; import '../../core/app_colors.dart'; import '../../core/app_theme.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/navigation_provider.dart'; import '../../providers/data_providers.dart'; class MedicationEditPage extends ConsumerStatefulWidget { final String? id; const MedicationEditPage({super.key, this.id}); @override ConsumerState createState() => _MedicationEditPageState(); } class _MedicationEditPageState extends ConsumerState { final _nameCtrl = TextEditingController(); final _dosageCtrl = TextEditingController(); final _notesCtrl = TextEditingController(); int _timesPerDay = 2; List _times = [const TimeOfDay(hour: 8, minute: 0), const TimeOfDay(hour: 18, minute: 0)]; DateTime _start = DateTime.now(); DateTime? _end; bool _loading = false; @override void initState() { super.initState(); if (widget.id != null) _load(); } @override void dispose() { _nameCtrl.dispose(); _dosageCtrl.dispose(); _notesCtrl.dispose(); super.dispose(); } Future _load() async { final srv = ref.read(medicationServiceProvider); final list = await srv.getList(); final m = list.cast?>().firstWhere((x) => x?['id']?.toString() == widget.id, orElse: () => null); if (m == null || !mounted) return; _nameCtrl.text = m['name']?.toString() ?? ''; _dosageCtrl.text = m['dosage']?.toString() ?? ''; _notesCtrl.text = m['notes']?.toString() ?? ''; final tod = (m['timeOfDay'] as List?)?.map((t) { final parts = t.toString().split(':'); return TimeOfDay(hour: int.tryParse(parts[0]) ?? 8, minute: int.tryParse(parts.length > 1 ? parts[1] : '0') ?? 0); }).toList() ?? [const TimeOfDay(hour: 8, minute: 0), const TimeOfDay(hour: 18, minute: 0)]; _timesPerDay = tod.length; _times = tod; if (m['startDate'] != null) _start = DateTime.tryParse(m['startDate'].toString()) ?? DateTime.now(); if (m['endDate'] != null) _end = DateTime.tryParse(m['endDate'].toString()); setState(() {}); } Future _save() async { final name = _nameCtrl.text.trim(); if (name.isEmpty) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('请输入药品名称'))); return; } setState(() => _loading = true); final data = { 'name': name, 'dosage': _dosageCtrl.text.trim(), 'frequency': 'Daily', 'notes': _notesCtrl.text.trim(), 'timeOfDay': _times.map((t) => '${t.hour.toString().padLeft(2,'0')}:${t.minute.toString().padLeft(2,'0')}').toList(), 'startDate': '${_start.year}-${_start.month.toString().padLeft(2,'0')}-${_start.day.toString().padLeft(2,'0')}', if (_end != null) 'endDate': '${_end!.year}-${_end!.month.toString().padLeft(2,'0')}-${_end!.day.toString().padLeft(2,'0')}', 'source': 'Manual', }; final srv = ref.read(medicationServiceProvider); try { if (widget.id != null) { await srv.update(widget.id!, data); } else { await srv.create(data); } ref.invalidate(medicationListProvider); ref.invalidate(medicationReminderProvider); if (mounted) { popRoute(ref); } } catch (_) { if (mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('保存失败'), backgroundColor: AppTheme.error)); } if (mounted) setState(() => _loading = false); } void _updateTimes(int n) { setState(() { _timesPerDay = n; while (_times.length < n) _times.add(const TimeOfDay(hour: 12, minute: 0)); while (_times.length > n) _times.removeLast(); }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppTheme.bg, appBar: AppBar(title: Text(widget.id != null ? '编辑用药' : '添加用药')), body: ListView(padding: const EdgeInsets.all(16), children: [ // 名称+剂量 一行 Row(children: [ Expanded(flex: 3, child: _field('药品名称', _nameCtrl, hint: '如: 阿司匹林')), const SizedBox(width: 12), Expanded(flex: 2, child: _field('剂量', _dosageCtrl, hint: '如: 100mg')), ]), const SizedBox(height: 16), // 频率选择 _label('每日服药次数'), const SizedBox(height: 8), Wrap(spacing: 8, children: List.generate(4, (i) => _chip('${i + 1}次', _timesPerDay == i + 1, () => _updateTimes(i + 1)))), const SizedBox(height: 16), // 时间选择 _label('服药时间'), const SizedBox(height: 8), Wrap(spacing: 8, runSpacing: 8, children: List.generate(_timesPerDay, (i) => GestureDetector( onTap: () async { final t = await showTimePicker(context: context, initialTime: _times[i]); if (t != null) setState(() => _times[i] = t); }, child: Container(padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration(color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(12)), child: Text('${_times[i].hour.toString().padLeft(2,'0')}:${_times[i].minute.toString().padLeft(2,'0')}', style: const TextStyle(fontSize: 18, color: AppTheme.primary, fontWeight: FontWeight.w600))), ))), const SizedBox(height: 16), // 开始+结束 一行 Row(children: [ Expanded(child: _dateField('开始日期', _start, (d) => setState(() => _start = d))), const SizedBox(width: 12), Expanded(child: _dateFieldOpt('结束日期(可选)', _end, (d) => setState(() => _end = d))), ]), const SizedBox(height: 16), _field('备注', _notesCtrl, hint: '如: 饭后服用、睡前'), const SizedBox(height: 32), SizedBox(width: double.infinity, child: ElevatedButton(onPressed: _loading ? null : _save, style: ElevatedButton.styleFrom(backgroundColor: AppTheme.primary, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), padding: const EdgeInsets.symmetric(vertical: 14)), child: Text(_loading ? '保存中...' : '保存', style: const TextStyle(fontSize: 19)))), const SizedBox(height: 20), ]), ); } Widget _label(String text) => Text(text, style: TextStyle(fontSize: 17, color: AppColors.textSecondary)); Widget _field(String label, TextEditingController ctrl, {String? hint}) => Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ _label(label), const SizedBox(height: 6), TextField(controller: ctrl, decoration: InputDecoration(hintText: hint, filled: true, fillColor: AppTheme.surface, border: OutlineInputBorder(borderRadius: BorderRadius.circular(AppTheme.rMd), borderSide: BorderSide.none), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12)), style: const TextStyle(fontSize: 19)), ]); Widget _chip(String label, bool sel, VoidCallback onTap) => GestureDetector(onTap: onTap, child: Container(padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration(color: sel ? AppTheme.primary : AppTheme.primaryLight, borderRadius: BorderRadius.circular(16)), child: Text(label, style: TextStyle(fontSize: 16, color: sel ? Colors.white : AppTheme.primary, fontWeight: FontWeight.w500)))); Widget _dateField(String label, DateTime val, ValueChanged cb) => Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ _label(label), const SizedBox(height: 6), GestureDetector(onTap: () async { final d = await showDatePicker(context: context, initialDate: val, firstDate: DateTime(2024), lastDate: DateTime(2030)); if (d != null) cb(d); }, child: Container(width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration(color: AppTheme.surface, borderRadius: BorderRadius.circular(AppTheme.rMd)), child: Text('${val.month}/${val.day}', style: const TextStyle(fontSize: 18)))), ]); Widget _dateFieldOpt(String label, DateTime? val, ValueChanged cb) => Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ _label(label), const SizedBox(height: 6), GestureDetector(onTap: () async { final d = await showDatePicker(context: context, initialDate: val ?? DateTime.now(), firstDate: DateTime(2024), lastDate: DateTime(2030)); if (d != null) cb(d); }, child: Container(width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration(color: AppTheme.surface, borderRadius: BorderRadius.circular(AppTheme.rMd)), child: Row(children: [ Text(val != null ? '${val.month}/${val.day}' : '不设置', style: TextStyle(fontSize: 18, color: val != null ? null : AppTheme.textHint)), if (val != null) const Spacer(), if (val != null) GestureDetector(onTap: () => cb(null), child: const Icon(Icons.close, size: 19, color: AppColors.textHint)), ]))), ]); }