diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 9f3ed435..9f3069eb 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -251,6 +251,12 @@ "translatorBadgeName": "Translator", "translatorBadgeDescription": "Helping translate Solar Network into different languages", "wallet": "Wallet", + "walletStats": "Wallet Statistics", + "totalTransactions": "Total Transactions", + "totalOrders": "Total Orders", + "totalIncome": "Total Income", + "totalOutgoing": "Total Outgoing", + "netBalance": "Net Balance", "walletCurrencyPoints": "New Solar Points", "walletCurrencyShortPoints": "NSP", "walletCurrencyGolds": "The Solar Dollars", diff --git a/lib/models/wallet.dart b/lib/models/wallet.dart index 827848fb..fae33fed 100644 --- a/lib/models/wallet.dart +++ b/lib/models/wallet.dart @@ -20,6 +20,24 @@ sealed class SnWallet with _$SnWallet { _$SnWalletFromJson(json); } +@freezed +sealed class SnWalletStats with _$SnWalletStats { + const factory SnWalletStats({ + required DateTime periodBegin, + required DateTime periodEnd, + required int totalTransactions, + required int totalOrders, + required double totalIncome, + required double totalOutgoing, + required double sum, + @Default({}) Map incomeCategories, + @Default({}) Map outgoingCategories, + }) = _SnWalletStats; + + factory SnWalletStats.fromJson(Map json) => + _$SnWalletStatsFromJson(json); +} + @freezed sealed class SnWalletPocket with _$SnWalletPocket { const factory SnWalletPocket({ diff --git a/lib/models/wallet.freezed.dart b/lib/models/wallet.freezed.dart index 0eae114c..73c34303 100644 --- a/lib/models/wallet.freezed.dart +++ b/lib/models/wallet.freezed.dart @@ -317,6 +317,299 @@ $SnAccountCopyWith<$Res>? get account { } +/// @nodoc +mixin _$SnWalletStats { + + DateTime get periodBegin; DateTime get periodEnd; int get totalTransactions; int get totalOrders; double get totalIncome; double get totalOutgoing; double get sum; Map get incomeCategories; Map get outgoingCategories; +/// Create a copy of SnWalletStats +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SnWalletStatsCopyWith get copyWith => _$SnWalletStatsCopyWithImpl(this as SnWalletStats, _$identity); + + /// Serializes this SnWalletStats to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWalletStats&&(identical(other.periodBegin, periodBegin) || other.periodBegin == periodBegin)&&(identical(other.periodEnd, periodEnd) || other.periodEnd == periodEnd)&&(identical(other.totalTransactions, totalTransactions) || other.totalTransactions == totalTransactions)&&(identical(other.totalOrders, totalOrders) || other.totalOrders == totalOrders)&&(identical(other.totalIncome, totalIncome) || other.totalIncome == totalIncome)&&(identical(other.totalOutgoing, totalOutgoing) || other.totalOutgoing == totalOutgoing)&&(identical(other.sum, sum) || other.sum == sum)&&const DeepCollectionEquality().equals(other.incomeCategories, incomeCategories)&&const DeepCollectionEquality().equals(other.outgoingCategories, outgoingCategories)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,periodBegin,periodEnd,totalTransactions,totalOrders,totalIncome,totalOutgoing,sum,const DeepCollectionEquality().hash(incomeCategories),const DeepCollectionEquality().hash(outgoingCategories)); + +@override +String toString() { + return 'SnWalletStats(periodBegin: $periodBegin, periodEnd: $periodEnd, totalTransactions: $totalTransactions, totalOrders: $totalOrders, totalIncome: $totalIncome, totalOutgoing: $totalOutgoing, sum: $sum, incomeCategories: $incomeCategories, outgoingCategories: $outgoingCategories)'; +} + + +} + +/// @nodoc +abstract mixin class $SnWalletStatsCopyWith<$Res> { + factory $SnWalletStatsCopyWith(SnWalletStats value, $Res Function(SnWalletStats) _then) = _$SnWalletStatsCopyWithImpl; +@useResult +$Res call({ + DateTime periodBegin, DateTime periodEnd, int totalTransactions, int totalOrders, double totalIncome, double totalOutgoing, double sum, Map incomeCategories, Map outgoingCategories +}); + + + + +} +/// @nodoc +class _$SnWalletStatsCopyWithImpl<$Res> + implements $SnWalletStatsCopyWith<$Res> { + _$SnWalletStatsCopyWithImpl(this._self, this._then); + + final SnWalletStats _self; + final $Res Function(SnWalletStats) _then; + +/// Create a copy of SnWalletStats +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? periodBegin = null,Object? periodEnd = null,Object? totalTransactions = null,Object? totalOrders = null,Object? totalIncome = null,Object? totalOutgoing = null,Object? sum = null,Object? incomeCategories = null,Object? outgoingCategories = null,}) { + return _then(_self.copyWith( +periodBegin: null == periodBegin ? _self.periodBegin : periodBegin // ignore: cast_nullable_to_non_nullable +as DateTime,periodEnd: null == periodEnd ? _self.periodEnd : periodEnd // ignore: cast_nullable_to_non_nullable +as DateTime,totalTransactions: null == totalTransactions ? _self.totalTransactions : totalTransactions // ignore: cast_nullable_to_non_nullable +as int,totalOrders: null == totalOrders ? _self.totalOrders : totalOrders // ignore: cast_nullable_to_non_nullable +as int,totalIncome: null == totalIncome ? _self.totalIncome : totalIncome // ignore: cast_nullable_to_non_nullable +as double,totalOutgoing: null == totalOutgoing ? _self.totalOutgoing : totalOutgoing // ignore: cast_nullable_to_non_nullable +as double,sum: null == sum ? _self.sum : sum // ignore: cast_nullable_to_non_nullable +as double,incomeCategories: null == incomeCategories ? _self.incomeCategories : incomeCategories // ignore: cast_nullable_to_non_nullable +as Map,outgoingCategories: null == outgoingCategories ? _self.outgoingCategories : outgoingCategories // ignore: cast_nullable_to_non_nullable +as Map, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SnWalletStats]. +extension SnWalletStatsPatterns on SnWalletStats { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SnWalletStats value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SnWalletStats() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SnWalletStats value) $default,){ +final _that = this; +switch (_that) { +case _SnWalletStats(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SnWalletStats value)? $default,){ +final _that = this; +switch (_that) { +case _SnWalletStats() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( DateTime periodBegin, DateTime periodEnd, int totalTransactions, int totalOrders, double totalIncome, double totalOutgoing, double sum, Map incomeCategories, Map outgoingCategories)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SnWalletStats() when $default != null: +return $default(_that.periodBegin,_that.periodEnd,_that.totalTransactions,_that.totalOrders,_that.totalIncome,_that.totalOutgoing,_that.sum,_that.incomeCategories,_that.outgoingCategories);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( DateTime periodBegin, DateTime periodEnd, int totalTransactions, int totalOrders, double totalIncome, double totalOutgoing, double sum, Map incomeCategories, Map outgoingCategories) $default,) {final _that = this; +switch (_that) { +case _SnWalletStats(): +return $default(_that.periodBegin,_that.periodEnd,_that.totalTransactions,_that.totalOrders,_that.totalIncome,_that.totalOutgoing,_that.sum,_that.incomeCategories,_that.outgoingCategories);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( DateTime periodBegin, DateTime periodEnd, int totalTransactions, int totalOrders, double totalIncome, double totalOutgoing, double sum, Map incomeCategories, Map outgoingCategories)? $default,) {final _that = this; +switch (_that) { +case _SnWalletStats() when $default != null: +return $default(_that.periodBegin,_that.periodEnd,_that.totalTransactions,_that.totalOrders,_that.totalIncome,_that.totalOutgoing,_that.sum,_that.incomeCategories,_that.outgoingCategories);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SnWalletStats implements SnWalletStats { + const _SnWalletStats({required this.periodBegin, required this.periodEnd, required this.totalTransactions, required this.totalOrders, required this.totalIncome, required this.totalOutgoing, required this.sum, final Map incomeCategories = const {}, final Map outgoingCategories = const {}}): _incomeCategories = incomeCategories,_outgoingCategories = outgoingCategories; + factory _SnWalletStats.fromJson(Map json) => _$SnWalletStatsFromJson(json); + +@override final DateTime periodBegin; +@override final DateTime periodEnd; +@override final int totalTransactions; +@override final int totalOrders; +@override final double totalIncome; +@override final double totalOutgoing; +@override final double sum; + final Map _incomeCategories; +@override@JsonKey() Map get incomeCategories { + if (_incomeCategories is EqualUnmodifiableMapView) return _incomeCategories; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_incomeCategories); +} + + final Map _outgoingCategories; +@override@JsonKey() Map get outgoingCategories { + if (_outgoingCategories is EqualUnmodifiableMapView) return _outgoingCategories; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_outgoingCategories); +} + + +/// Create a copy of SnWalletStats +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SnWalletStatsCopyWith<_SnWalletStats> get copyWith => __$SnWalletStatsCopyWithImpl<_SnWalletStats>(this, _$identity); + +@override +Map toJson() { + return _$SnWalletStatsToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWalletStats&&(identical(other.periodBegin, periodBegin) || other.periodBegin == periodBegin)&&(identical(other.periodEnd, periodEnd) || other.periodEnd == periodEnd)&&(identical(other.totalTransactions, totalTransactions) || other.totalTransactions == totalTransactions)&&(identical(other.totalOrders, totalOrders) || other.totalOrders == totalOrders)&&(identical(other.totalIncome, totalIncome) || other.totalIncome == totalIncome)&&(identical(other.totalOutgoing, totalOutgoing) || other.totalOutgoing == totalOutgoing)&&(identical(other.sum, sum) || other.sum == sum)&&const DeepCollectionEquality().equals(other._incomeCategories, _incomeCategories)&&const DeepCollectionEquality().equals(other._outgoingCategories, _outgoingCategories)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,periodBegin,periodEnd,totalTransactions,totalOrders,totalIncome,totalOutgoing,sum,const DeepCollectionEquality().hash(_incomeCategories),const DeepCollectionEquality().hash(_outgoingCategories)); + +@override +String toString() { + return 'SnWalletStats(periodBegin: $periodBegin, periodEnd: $periodEnd, totalTransactions: $totalTransactions, totalOrders: $totalOrders, totalIncome: $totalIncome, totalOutgoing: $totalOutgoing, sum: $sum, incomeCategories: $incomeCategories, outgoingCategories: $outgoingCategories)'; +} + + +} + +/// @nodoc +abstract mixin class _$SnWalletStatsCopyWith<$Res> implements $SnWalletStatsCopyWith<$Res> { + factory _$SnWalletStatsCopyWith(_SnWalletStats value, $Res Function(_SnWalletStats) _then) = __$SnWalletStatsCopyWithImpl; +@override @useResult +$Res call({ + DateTime periodBegin, DateTime periodEnd, int totalTransactions, int totalOrders, double totalIncome, double totalOutgoing, double sum, Map incomeCategories, Map outgoingCategories +}); + + + + +} +/// @nodoc +class __$SnWalletStatsCopyWithImpl<$Res> + implements _$SnWalletStatsCopyWith<$Res> { + __$SnWalletStatsCopyWithImpl(this._self, this._then); + + final _SnWalletStats _self; + final $Res Function(_SnWalletStats) _then; + +/// Create a copy of SnWalletStats +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? periodBegin = null,Object? periodEnd = null,Object? totalTransactions = null,Object? totalOrders = null,Object? totalIncome = null,Object? totalOutgoing = null,Object? sum = null,Object? incomeCategories = null,Object? outgoingCategories = null,}) { + return _then(_SnWalletStats( +periodBegin: null == periodBegin ? _self.periodBegin : periodBegin // ignore: cast_nullable_to_non_nullable +as DateTime,periodEnd: null == periodEnd ? _self.periodEnd : periodEnd // ignore: cast_nullable_to_non_nullable +as DateTime,totalTransactions: null == totalTransactions ? _self.totalTransactions : totalTransactions // ignore: cast_nullable_to_non_nullable +as int,totalOrders: null == totalOrders ? _self.totalOrders : totalOrders // ignore: cast_nullable_to_non_nullable +as int,totalIncome: null == totalIncome ? _self.totalIncome : totalIncome // ignore: cast_nullable_to_non_nullable +as double,totalOutgoing: null == totalOutgoing ? _self.totalOutgoing : totalOutgoing // ignore: cast_nullable_to_non_nullable +as double,sum: null == sum ? _self.sum : sum // ignore: cast_nullable_to_non_nullable +as double,incomeCategories: null == incomeCategories ? _self._incomeCategories : incomeCategories // ignore: cast_nullable_to_non_nullable +as Map,outgoingCategories: null == outgoingCategories ? _self._outgoingCategories : outgoingCategories // ignore: cast_nullable_to_non_nullable +as Map, + )); +} + + +} + + /// @nodoc mixin _$SnWalletPocket { diff --git a/lib/models/wallet.g.dart b/lib/models/wallet.g.dart index fd894f95..6e7e73d4 100644 --- a/lib/models/wallet.g.dart +++ b/lib/models/wallet.g.dart @@ -35,6 +35,40 @@ Map _$SnWalletToJson(_SnWallet instance) => { 'deleted_at': instance.deletedAt?.toIso8601String(), }; +_SnWalletStats _$SnWalletStatsFromJson(Map json) => + _SnWalletStats( + periodBegin: DateTime.parse(json['period_begin'] as String), + periodEnd: DateTime.parse(json['period_end'] as String), + totalTransactions: (json['total_transactions'] as num).toInt(), + totalOrders: (json['total_orders'] as num).toInt(), + totalIncome: (json['total_income'] as num).toDouble(), + totalOutgoing: (json['total_outgoing'] as num).toDouble(), + sum: (json['sum'] as num).toDouble(), + incomeCategories: + (json['income_categories'] as Map?)?.map( + (k, e) => MapEntry(k, (e as num).toDouble()), + ) ?? + const {}, + outgoingCategories: + (json['outgoing_categories'] as Map?)?.map( + (k, e) => MapEntry(k, (e as num).toDouble()), + ) ?? + const {}, + ); + +Map _$SnWalletStatsToJson(_SnWalletStats instance) => + { + 'period_begin': instance.periodBegin.toIso8601String(), + 'period_end': instance.periodEnd.toIso8601String(), + 'total_transactions': instance.totalTransactions, + 'total_orders': instance.totalOrders, + 'total_income': instance.totalIncome, + 'total_outgoing': instance.totalOutgoing, + 'sum': instance.sum, + 'income_categories': instance.incomeCategories, + 'outgoing_categories': instance.outgoingCategories, + }; + _SnWalletPocket _$SnWalletPocketFromJson(Map json) => _SnWalletPocket( id: json['id'] as String, diff --git a/lib/screens/wallet.dart b/lib/screens/wallet.dart index d4788b87..91d66b05 100644 --- a/lib/screens/wallet.dart +++ b/lib/screens/wallet.dart @@ -37,6 +37,13 @@ Future walletCurrent(Ref ref) async { } } +@riverpod +Future walletStats(Ref ref) async { + final client = ref.watch(apiClientProvider); + final resp = await client.get('/id/wallets/stats'); + return SnWalletStats.fromJson(resp.data); +} + class CreateFundSheet extends StatefulWidget { const CreateFundSheet({super.key}); @@ -652,21 +659,13 @@ Future walletFund(Ref ref, String fundId) async { return SnWalletFund.fromJson(resp.data); } -@riverpod -Future> walletFundStats(Ref ref) async { - final client = ref.watch(apiClientProvider); - final resp = await client.get('/id/wallets/stats'); - return resp.data as Map; -} - class WalletScreen extends HookConsumerWidget { const WalletScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final wallet = ref.watch(walletCurrentProvider); - final tabController = useTabController(initialLength: 3); - final fundStats = ref.watch(walletFundStatsProvider); + final tabController = useTabController(initialLength: 2); Future createWallet() async { final client = ref.read(apiClientProvider); @@ -726,120 +725,73 @@ class WalletScreen extends HookConsumerWidget { ).center(); } - return Column( - children: [ - // Wallet Overview - Column( - spacing: 8, - children: [ - // Pockets - ...data.pockets.map( - (pocket) => Card( - margin: EdgeInsets.zero, - child: ListTile( - leading: Icon( - kCurrencyIconData[pocket.currency] ?? - Symbols.universal_currency_alt, + return NestedScrollView( + headerSliverBuilder: + (context, innerBoxIsScrolled) => [ + // Wallet Overview + SliverToBoxAdapter( + child: Column( + spacing: 8, + children: [ + // Wallet Stats + _buildCompactStatsWidget(context, ref), + // Pockets + Card( + margin: EdgeInsets.zero, + child: Column( + children: [ + ...data.pockets.map( + (pocket) => 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()}', + ), + ), + ), + ], + ), ), - title: - Text( - getCurrencyTranslationKey(pocket.currency), - ).tr(), - subtitle: Text( - '${pocket.amount.toStringAsFixed(2)} ${getCurrencyTranslationKey(pocket.currency, isShort: true).tr()}', - ), - ), + ], + ).padding(horizontal: 12, top: 12), + ), + + // Tab Bar + SliverToBoxAdapter( + child: TabBar( + controller: tabController, + tabs: [ + Tab(text: 'transactions'.tr()), + Tab(text: 'myFunds'.tr()), + ], ), ), - - // Fund Stats - fundStats.when( - data: - (stats) => Card( - margin: EdgeInsets.zero, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon( - Symbols.celebration, - color: - Theme.of(context).colorScheme.primary, - ), - const Gap(8), - Text( - 'fundOverview'.tr(), - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const Gap(8), - Row( - children: [ - Expanded( - child: _buildStatItem( - context, - 'totalFundsSent'.tr(), - '${stats['total_sent'] ?? 0}', - Icons.send, - ), - ), - Expanded( - child: _buildStatItem( - context, - 'totalFundsReceived'.tr(), - '${stats['total_received'] ?? 0}', - Icons.call_received, - ), - ), - ], - ), - ], - ), - ), - ), - loading: - () => const Card( - margin: EdgeInsets.zero, - child: Padding( - padding: EdgeInsets.all(16), - child: Center(child: CircularProgressIndicator()), - ), - ), - error: (error, stack) => const SizedBox.shrink(), - ), ], - ).padding(horizontal: 16, vertical: 16), - - // Tab Bar - TabBar( - controller: tabController, - tabs: [ - Tab(text: 'transactions'.tr()), - Tab(text: 'myFunds'.tr()), - ], - ), - - // Tab Content - Expanded( - child: TabBarView( - controller: tabController, - children: [ - // Transactions Tab - PagingHelperView( + body: TabBarView( + controller: tabController, + children: [ + // Transactions Tab + CustomScrollView( + slivers: [ + PagingHelperSliverView( provider: transactionListNotifierProvider, futureRefreshable: transactionListNotifierProvider.future, notifierRefreshable: transactionListNotifierProvider.notifier, contentBuilder: - (data, widgetCount, endItemView) => ListView.builder( - padding: EdgeInsets.zero, + ( + data, + widgetCount, + endItemView, + ) => SliverList.builder( itemCount: widgetCount, itemBuilder: (context, index) { if (index == widgetCount - 1) { @@ -854,10 +806,14 @@ class WalletScreen extends HookConsumerWidget { key: ValueKey(transaction.id), leading: Icon( isIncome - ? Symbols.arrow_upward - : Symbols.arrow_downward, + ? Symbols.payment_arrow_down + : Symbols.paid, + ), + title: Text( + transaction.remarks ?? '', + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - title: Text(transaction.remarks ?? ''), subtitle: Text( DateFormat.yMd().add_Hm().format( transaction.createdAt, @@ -873,13 +829,13 @@ class WalletScreen extends HookConsumerWidget { }, ), ), - - // My Funds Tab - _buildFundsList(context, ref), ], ), - ), - ], + + // My Funds Tab + _buildFundsList(context, ref), + ], + ), ); }, error: @@ -1040,6 +996,112 @@ class WalletScreen extends HookConsumerWidget { ); } + Widget _buildCompactStatsWidget(BuildContext context, WidgetRef ref) { + final stats = ref.watch(walletStatsProvider); + + return stats.when( + data: (statsData) { + return Card( + margin: EdgeInsets.zero, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + 'walletStats'.tr(), + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + Text( + '${DateFormat.yMd().format(statsData.periodBegin)} - ${DateFormat.yMd().format(statsData.periodEnd)}', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ], + ), + const Gap(12), + Row( + children: [ + Expanded( + child: _buildStatItem( + context, + 'totalTransactions'.tr(), + statsData.totalTransactions.toString(), + Symbols.swap_horiz, + ), + ), + const Gap(16), + Expanded( + child: _buildStatItem( + context, + 'totalOrders'.tr(), + statsData.totalOrders.toString(), + Symbols.receipt_long, + ), + ), + ], + ), + const Gap(12), + Row( + children: [ + Expanded( + child: _buildStatItem( + context, + 'totalIncome'.tr(), + statsData.totalIncome.toStringAsFixed(2), + Symbols.arrow_upward, + ), + ), + const Gap(16), + Expanded( + child: _buildStatItem( + context, + 'totalOutgoing'.tr(), + statsData.totalOutgoing.toStringAsFixed(2), + Symbols.arrow_downward, + ), + ), + const Gap(16), + Expanded( + child: _buildStatItem( + context, + 'netBalance'.tr(), + statsData.sum.toStringAsFixed(2), + Symbols.account_balance, + ), + ), + ], + ), + ], + ), + ), + ); + }, + loading: + () => Card( + margin: EdgeInsets.zero, + child: const Padding( + padding: EdgeInsets.all(16), + child: Center(child: CircularProgressIndicator()), + ), + ), + error: + (error, stack) => Card( + margin: EdgeInsets.zero, + child: Padding( + padding: const EdgeInsets.all(16), + child: Center(child: Text('Error loading stats')), + ), + ), + ); + } + Future _handleFundCreation( BuildContext context, WidgetRef ref, @@ -1075,7 +1137,6 @@ class WalletScreen extends HookConsumerWidget { // Wait for server to handle order await Future.delayed(const Duration(seconds: 1)); ref.invalidate(walletFundsProvider); - ref.invalidate(walletFundStatsProvider); ref.invalidate(walletCurrentProvider); if (context.mounted) { showSnackBar('fundCreatedSuccessfully'.tr()); diff --git a/lib/screens/wallet.g.dart b/lib/screens/wallet.g.dart index 9fb4320e..841c1ae4 100644 --- a/lib/screens/wallet.g.dart +++ b/lib/screens/wallet.g.dart @@ -24,6 +24,22 @@ final walletCurrentProvider = AutoDisposeFutureProvider.internal( @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef WalletCurrentRef = AutoDisposeFutureProviderRef; +String _$walletStatsHash() => r'23d692a922c2388135be6a46afa73c018762eb57'; + +/// See also [walletStats]. +@ProviderFor(walletStats) +final walletStatsProvider = AutoDisposeFutureProvider.internal( + walletStats, + name: r'walletStatsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$walletStatsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef WalletStatsRef = AutoDisposeFutureProviderRef; String _$walletFundsHash() => r'7ceb415f64fcadab2b10461e27b95bf92352c707'; /// Copied from Dart SDK @@ -443,25 +459,6 @@ class _WalletFundProviderElement String get fundId => (origin as WalletFundProvider).fundId; } -String _$walletFundStatsHash() => r'fac8761cf7828fa151e8cc9115416265148bd00e'; - -/// See also [walletFundStats]. -@ProviderFor(walletFundStats) -final walletFundStatsProvider = - AutoDisposeFutureProvider>.internal( - walletFundStats, - name: r'walletFundStatsProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$walletFundStatsHash, - dependencies: null, - allTransitiveDependencies: null, - ); - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -typedef WalletFundStatsRef = AutoDisposeFutureProviderRef>; String _$transactionListNotifierHash() => r'7b777cd44f3351f68f7bd1dd76bfe8b388381bdb';