import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../api/bill_api.dart'; import '../core/network/api_exception.dart'; import '../models/bill_request.dart'; import '../utils/date_util.dart'; import 'bill_refresh.dart'; /// 账单编辑页的表单状态 class BillEditState { final bool loading; // 详情加载中 final String? loadError; // 详情加载失败提示 final int type; // 1=支出 2=收入 final int? categoryId; final double? amount; final String billDate; // yyyy-MM-dd final String description; final bool submitting; final int revision; // 服务端数据回填时 +1,供页面同步非受控输入框 const BillEditState({ this.loading = false, this.loadError, this.type = 1, this.categoryId, this.amount, required this.billDate, this.description = '', this.submitting = false, this.revision = 0, }); BillEditState copyWith({ bool? loading, Object? loadError = _sentinel, int? type, int? categoryId, Object? amount = _sentinel, String? billDate, String? description, bool? submitting, int? revision, }) { return BillEditState( loading: loading ?? this.loading, loadError: loadError == _sentinel ? this.loadError : loadError as String?, type: type ?? this.type, categoryId: categoryId ?? this.categoryId, amount: amount == _sentinel ? this.amount : amount as double?, billDate: billDate ?? this.billDate, description: description ?? this.description, submitting: submitting ?? this.submitting, revision: revision ?? this.revision, ); } static const _sentinel = Object(); } class BillEditNotifier extends Notifier { BillEditNotifier(this.billId); final int billId; @override BillEditState build() { _load(); return BillEditState(billDate: formatDate(DateTime.now()), loading: true); } Future _load() async { try { final bill = await ref.read(billApiProvider).getBill(billId); if (!ref.mounted) return; state = BillEditState( type: bill.type, categoryId: bill.categoryId, amount: bill.absAmount, billDate: bill.billDate, description: bill.description, revision: state.revision + 1, ); } on ApiException catch (e) { if (!ref.mounted) return; state = state.copyWith(loading: false, loadError: e.message); } catch (_) { if (!ref.mounted) return; state = state.copyWith(loading: false, loadError: '账单加载失败'); } } /// 重新加载(错误重试用) void reload() { state = state.copyWith(loading: true, loadError: null); _load(); } void setType(int type) { if (type == state.type) return; // 切换收支后分类需重选(与记账页行为一致) state = state.copyWith(type: type, categoryId: null); } void setCategory(int id) => state = state.copyWith(categoryId: id); void setAmount(double? value) => state = state.copyWith(amount: value); void setDate(String date) => state = state.copyWith(billDate: date); void setDescription(String desc) => state = state.copyWith(description: desc); /// 保存:必须回传全部字段,否则后端会把 description 覆盖成 null Future save() async { final s = state; if (s.categoryId == null) throw ApiException(-1, '请选择分类'); if (s.amount == null || s.amount! <= 0) throw ApiException(-1, '请输入有效金额'); state = state.copyWith(submitting: true); try { await ref.read(billApiProvider).updateBill( billId, BillRequest( categoryId: s.categoryId!, amount: s.amount!, description: s.description, billDate: s.billDate, type: s.type, ), ); if (!ref.mounted) return; invalidateBillData(ref); } finally { if (ref.mounted) state = state.copyWith(submitting: false); } } /// 删除该账单 Future remove() async { state = state.copyWith(submitting: true); try { await ref.read(billApiProvider).deleteBill(billId); if (!ref.mounted) return; invalidateBillData(ref); } finally { if (ref.mounted) state = state.copyWith(submitting: false); } } } final billEditProvider = NotifierProvider.autoDispose .family(BillEditNotifier.new);