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 GradientScaffold( appBar: AppBar( leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => popRoute(ref)), 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), GestureDetector( onTap: () async { final n = await showAppCountPicker(context, initialValue: _timesPerDay, min: 1, max: 4, label: ' 次'); if (n != null) _updateTimes(n); }, child: Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppTheme.surface, borderRadius: BorderRadius.circular(AppTheme.rMd), border: Border.all(color: AppColors.border), ), child: Text( '$_timesPerDay 次/天', style: const TextStyle(fontSize: 18), ), ), ), 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 showAppTimePicker( context, initialTime: _times[i], ); if (t != null) setState(() => _times[i] = t); }, child: Container( padding: const EdgeInsets.symmetric( horizontal: 14, vertical: 10, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.border, width: 1), ), child: Text( '${_times[i].hour.toString().padLeft(2, '0')}:${_times[i].minute.toString().padLeft(2, '0')}', style: const TextStyle( fontSize: 18, color: AppColors.textPrimary, 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: Colors.white, foregroundColor: AppColors.primary, side: const BorderSide(color: AppColors.primary, width: 1.5), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), padding: const EdgeInsets.symmetric(vertical: 14), ), child: Text( _loading ? '保存中...' : '保存', style: const TextStyle( fontSize: 19, fontWeight: FontWeight.w600, ), ), ), ), 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: const BorderSide(color: AppColors.border), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(AppTheme.rMd), borderSide: const BorderSide(color: AppColors.border), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(AppTheme.rMd), borderSide: const BorderSide(color: AppColors.primary), ), contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 12, ), ), style: const TextStyle(fontSize: 19), ), ], ); 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 showAppDatePicker( 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), border: Border.all(color: AppColors.border), ), child: Text( '${val.year} ${val.month.toString().padLeft(2, '0')} ${val.day.toString().padLeft(2, '0')}', 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 showAppDatePicker( 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), border: Border.all(color: AppColors.border), ), child: Row( children: [ Text( val != null ? '${val.year} ${val.month.toString().padLeft(2, '0')} ${val.day.toString().padLeft(2, '0')}' : '不设置', 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, ), ), ], ), ), ), ], ); }