first commit
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
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);
|
||||
Reference in New Issue
Block a user