Wallet

This commit is contained in:
2025-05-16 00:11:59 +08:00
parent dfd216b84b
commit 9fcc70d659
5 changed files with 399 additions and 14 deletions

View File

@ -7,6 +7,7 @@ import 'package:island/pods/network.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart';
part 'wallet.g.dart';
@ -23,6 +24,44 @@ const Map<String, IconData> kCurrencyIconData = {
'golds': Symbols.attach_money,
};
@riverpod
class TransactionListNotifier extends _$TransactionListNotifier
with CursorPagingNotifierMixin<SnTransaction> {
static const int _pageSize = 20;
@override
Future<CursorPagingData<SnTransaction>> build() => fetch(cursor: null);
@override
Future<CursorPagingData<SnTransaction>> fetch({
required String? cursor,
}) async {
final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final queryParams = {'offset': offset, 'take': _pageSize};
final response = await client.get(
'/wallets/transactions',
queryParameters: queryParams,
);
final total = int.parse(response.headers.value('X-Total') ?? '0');
final List<dynamic> data = response.data;
final transactions =
data.map((json) => SnTransaction.fromJson(json)).toList();
final hasMore = offset + transactions.length < total;
final nextCursor =
hasMore ? (offset + transactions.length).toString() : null;
return CursorPagingData(
items: transactions,
hasMore: hasMore,
nextCursor: nextCursor,
);
}
}
@RoutePage()
class WalletScreen extends HookConsumerWidget {
const WalletScreen({super.key});
@ -40,26 +79,75 @@ class WalletScreen extends HookConsumerWidget {
body: wallet.when(
data: (data) {
return Column(
spacing: 8,
children: [
...data.pockets.map(
(pocket) => Card(
margin: EdgeInsets.zero,
child: ListTile(
leading: Icon(
kCurrencyIconData[pocket.currency] ??
Symbols.universal_currency_alt,
),
title:
Text(getCurrencyTranslationKey(pocket.currency)).tr(),
subtitle: Text(
'${pocket.amount.toStringAsFixed(2)} ${getCurrencyTranslationKey(pocket.currency, isShort: true).tr()}',
Column(
spacing: 8,
children: [
...data.pockets.map(
(pocket) => Card(
margin: EdgeInsets.zero,
child: ListTile(
leading: Icon(
kCurrencyIconData[pocket.currency] ??
Symbols.universal_currency_alt,
),
title:
Text(
getCurrencyTranslationKey(pocket.currency),
).tr(),
subtitle: Text(
'${pocket.amount.toStringAsFixed(2)} ${getCurrencyTranslationKey(pocket.currency, isShort: true).tr()}',
),
),
),
),
],
).padding(horizontal: 16, vertical: 16),
const Divider(height: 1),
Expanded(
child: PagingHelperView(
provider: transactionListNotifierProvider,
futureRefreshable: transactionListNotifierProvider.future,
notifierRefreshable: transactionListNotifierProvider.notifier,
contentBuilder:
(data, widgetCount, endItemView) => ListView.builder(
padding: EdgeInsets.zero,
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
final transaction = data.items[index];
final isIncome =
transaction.payeeWalletId == wallet.value?.id;
return ListTile(
key: ValueKey(transaction.id),
leading: Icon(
isIncome
? Symbols.arrow_upward
: Symbols.arrow_downward,
),
title: Text(transaction.remarks ?? ''),
subtitle: Text(
DateFormat.yMd().add_Hm().format(
transaction.createdAt,
),
),
trailing: Text(
'${isIncome ? '+' : '-'}${transaction.amount.toStringAsFixed(2)} ${transaction.currency}',
style: TextStyle(
color: isIncome ? Colors.green : Colors.red,
),
),
);
},
),
),
),
],
).padding(horizontal: 16, vertical: 16);
);
},
error: (error, stackTrace) => Center(child: Text('Error: $error')),
loading: () => const Center(child: CircularProgressIndicator()),