152 lines
4.4 KiB
Dart
152 lines
4.4 KiB
Dart
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<BillEditState> {
|
|
BillEditNotifier(this.billId);
|
|
|
|
final int billId;
|
|
|
|
@override
|
|
BillEditState build() {
|
|
_load();
|
|
return BillEditState(billDate: formatDate(DateTime.now()), loading: true);
|
|
}
|
|
|
|
Future<void> _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<void> 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<void> 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, BillEditState, int>(BillEditNotifier.new);
|