Files
jizhi/frontend/lib/shared/services/current_ledger_store.dart
T

217 lines
6.9 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:miaoji_zhang/shared/api/api_client.dart';
import 'package:miaoji_zhang/shared/services/local_database.dart';
import 'package:miaoji_zhang/shared/services/session_store.dart';
class LedgerInfo {
final int id;
final String name;
final String iconKey;
final bool isDefault;
final int transactionCount;
const LedgerInfo({
required this.id,
required this.name,
required this.iconKey,
required this.isDefault,
required this.transactionCount,
});
factory LedgerInfo.fromJson(Map<String, dynamic> json) => LedgerInfo(
id: (json['id'] as num).toInt(),
name: json['name'] as String,
iconKey: json['iconKey'] as String? ?? 'wallet',
isDefault: json['isDefault'] as bool? ?? false,
transactionCount: (json['txCount'] as num?)?.toInt() ?? 0,
);
}
class CurrentLedgerStore extends ChangeNotifier {
CurrentLedgerStore._();
static final instance = CurrentLedgerStore._();
final _dio = ApiClient.instance.dio;
List<LedgerInfo> _ledgers = const [];
LedgerInfo? _current;
Future<void>? _loading;
List<LedgerInfo> get ledgers => List.unmodifiable(_ledgers);
LedgerInfo? get current => _current;
int? get currentId => _current?.id;
String get currentName => _current?.name ?? '账本';
Future<void> ensureLoaded({bool force = false}) {
if (!force && _current != null) return Future.value();
return _loading ??= _load().whenComplete(() => _loading = null);
}
Future<void> loadCached({bool force = false}) {
if (!force && _current != null) return Future.value();
return _loading ??= _loadCached().whenComplete(() => _loading = null);
}
Future<void> refreshRemote() async {
if (!SessionStore.instance.isAccount ||
SessionStore.instance.shouldUseLocalOnly) {
return;
}
final response = await _dio.get('/api/ledgers');
final values = response.data as List;
LocalDatabase.instance.cacheLedgers(values);
_apply(values);
}
Future<void> _loadCached() async {
_apply(LocalDatabase.instance.ledgers());
}
Future<void> _load() async {
final session = SessionStore.instance;
List<dynamic> values;
if (session.shouldUseLocalOnly) {
values = LocalDatabase.instance.ledgers();
} else {
try {
final response = await _dio.get('/api/ledgers');
values = response.data as List;
LocalDatabase.instance.cacheLedgers(values);
} catch (error) {
if (!isConnectivityError(error) || !session.isAccount) rethrow;
values = LocalDatabase.instance.ledgers();
}
}
_apply(values);
}
void _apply(List<dynamic> values) {
final items = values
.map((item) => LedgerInfo.fromJson(item as Map<String, dynamic>))
.toList();
final current =
items.where((item) => item.isDefault).firstOrNull ??
(items.isEmpty ? null : items.first);
final changed =
_current?.id != current?.id ||
_ledgers.length != items.length ||
Iterable<int>.generate(items.length).any((index) {
final before = _ledgers[index];
final after = items[index];
return before.id != after.id ||
before.name != after.name ||
before.iconKey != after.iconKey ||
before.isDefault != after.isDefault ||
before.transactionCount != after.transactionCount;
});
_ledgers = items;
_current = current;
if (changed) notifyListeners();
}
Future<void> select(int id) async {
if (_current?.id == id) return;
final session = SessionStore.instance;
if (session.shouldUseLocalOnly || id < 0) {
LocalDatabase.instance.selectLedger(id);
if (session.isAccount && session.cloudSyncEnabled) {
LocalDatabase.instance.enqueueSync('ledger', id, 'select', {'id': id});
}
await ensureLoaded(force: true);
return;
}
try {
await _dio.put('/api/ledgers/$id/default');
} catch (error) {
if (!isConnectivityError(error)) rethrow;
LocalDatabase.instance.selectLedger(id);
LocalDatabase.instance.enqueueSync('ledger', id, 'select', {'id': id});
}
await ensureLoaded(force: true);
}
Future<void> create(String name) async {
final session = SessionStore.instance;
if (session.shouldUseLocalOnly) {
final id = LocalDatabase.instance.createLedger(name.trim());
if (session.isAccount && session.cloudSyncEnabled) {
LocalDatabase.instance.enqueueSync('ledger', id, 'create', {
'name': name.trim(),
'iconKey': 'wallet',
});
}
await ensureLoaded(force: true);
return;
}
try {
await _dio.post(
'/api/ledgers',
data: {'name': name.trim(), 'iconKey': 'wallet'},
);
} catch (error) {
if (!isConnectivityError(error)) rethrow;
final id = LocalDatabase.instance.createLedger(name.trim());
LocalDatabase.instance.enqueueSync('ledger', id, 'create', {
'name': name.trim(),
'iconKey': 'wallet',
});
}
await ensureLoaded(force: true);
}
Future<void> rename(int id, String name, String iconKey) async {
final session = SessionStore.instance;
if (session.shouldUseLocalOnly || id < 0) {
LocalDatabase.instance.renameLedger(id, name.trim(), iconKey);
if (session.isAccount && session.cloudSyncEnabled) {
LocalDatabase.instance.enqueueSync('ledger', id, 'update', {
'name': name.trim(),
'iconKey': iconKey,
});
}
await ensureLoaded(force: true);
return;
}
try {
await _dio.put(
'/api/ledgers/$id',
data: {'name': name.trim(), 'iconKey': iconKey},
);
} catch (error) {
if (!isConnectivityError(error)) rethrow;
LocalDatabase.instance.renameLedger(id, name.trim(), iconKey);
LocalDatabase.instance.enqueueSync('ledger', id, 'update', {
'name': name.trim(),
'iconKey': iconKey,
});
}
await ensureLoaded(force: true);
}
Future<void> delete(int id) async {
final session = SessionStore.instance;
if (session.shouldUseLocalOnly || id < 0) {
LocalDatabase.instance.deleteLedger(id);
if (session.isAccount && session.cloudSyncEnabled) {
LocalDatabase.instance.enqueueSync('ledger', id, 'delete', {'id': id});
}
await ensureLoaded(force: true);
return;
}
try {
await _dio.delete('/api/ledgers/$id');
} catch (error) {
if (!isConnectivityError(error)) rethrow;
LocalDatabase.instance.deleteLedger(id);
LocalDatabase.instance.enqueueSync('ledger', id, 'delete', {'id': id});
}
await ensureLoaded(force: true);
}
void clear() {
_ledgers = const [];
_current = null;
notifyListeners();
}
}