From 71c372ab6cd74c3caf1ee637781a8c9b65030079 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 6 Dec 2025 21:13:18 +0800 Subject: [PATCH] :zap: Prefer auto dispose riverpods --- lib/pods/drive/file_list.dart | 28 +- lib/screens/account/credits.dart | 85 +- lib/screens/account/leveling.dart | 80 +- lib/screens/account/relationship.dart | 167 ++-- .../discovery/feeds/feed_marketplace.dart | 2 +- lib/screens/discovery/realms.dart | 2 +- lib/screens/notification.dart | 2 +- lib/screens/stickers/sticker_marketplace.dart | 5 +- lib/screens/wallet.dart | 874 +++++++++--------- lib/widgets/realm/realm_list.dart | 26 +- 10 files changed, 600 insertions(+), 671 deletions(-) diff --git a/lib/pods/drive/file_list.dart b/lib/pods/drive/file_list.dart index 5348c8ea..8994b0af 100644 --- a/lib/pods/drive/file_list.dart +++ b/lib/pods/drive/file_list.dart @@ -14,7 +14,7 @@ Future?> billingUsage(Ref ref) async { return response.data; } -final indexedCloudFileListProvider = AsyncNotifierProvider( +final indexedCloudFileListProvider = AsyncNotifierProvider.autoDispose( IndexedCloudFileListNotifier.new, ); @@ -76,12 +76,12 @@ class IndexedCloudFileListNotifier extends AsyncNotifier> queryParameters: queryParameters, ); - final List folders = - (response.data['folders'] as List).map((e) => e as String).toList(); - final List files = - (response.data['files'] as List) - .map((e) => SnCloudFileIndex.fromJson(e as Map)) - .toList(); + final List folders = (response.data['folders'] as List) + .map((e) => e as String) + .toList(); + final List files = (response.data['files'] as List) + .map((e) => SnCloudFileIndex.fromJson(e as Map)) + .toList(); final List items = [ ...folders.map((folderName) => FileListItem.folder(folderName)), @@ -92,7 +92,7 @@ class IndexedCloudFileListNotifier extends AsyncNotifier> } } -final unindexedFileListProvider = AsyncNotifierProvider( +final unindexedFileListProvider = AsyncNotifierProvider.autoDispose( UnindexedFileListNotifier.new, ); @@ -165,13 +165,13 @@ class UnindexedFileListNotifier extends AsyncNotifier> totalCount = int.tryParse(response.headers.value('x-total') ?? '0') ?? 0; - final List files = - (response.data as List) - .map((e) => SnCloudFile.fromJson(e as Map)) - .toList(); + final List files = (response.data as List) + .map((e) => SnCloudFile.fromJson(e as Map)) + .toList(); - final List items = - files.map((file) => FileListItem.unindexedFile(file)).toList(); + final List items = files + .map((file) => FileListItem.unindexedFile(file)) + .toList(); return items; } diff --git a/lib/screens/account/credits.dart b/lib/screens/account/credits.dart index 62f14d50..88291329 100644 --- a/lib/screens/account/credits.dart +++ b/lib/screens/account/credits.dart @@ -23,7 +23,7 @@ Future socialCredits(Ref ref) async { return response.data?.toDouble() ?? 0.0; } -final socialCreditHistoryNotifierProvider = AsyncNotifierProvider( +final socialCreditHistoryNotifierProvider = AsyncNotifierProvider.autoDispose( SocialCreditHistoryNotifier.new, ); @@ -45,11 +45,10 @@ class SocialCreditHistoryNotifier totalCount = int.parse(response.headers.value('X-Total') ?? '0'); - final records = - response.data - .map((json) => SnSocialCreditRecord.fromJson(json)) - .cast() - .toList(); + final records = response.data + .map((json) => SnSocialCreditRecord.fromJson(json)) + .cast() + .toList(); return records; } @@ -68,39 +67,36 @@ class SocialCreditsTab extends HookConsumerWidget { margin: const EdgeInsets.only(left: 16, right: 16, top: 8), child: socialCredits .when( - data: - (credits) => Stack( + data: (credits) => Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - credits < 100 - ? 'socialCreditsLevelPoor'.tr() - : credits < 150 - ? 'socialCreditsLevelNormal'.tr() - : credits < 200 - ? 'socialCreditsLevelGood'.tr() - : 'socialCreditsLevelExcellent'.tr(), - ).tr().bold().fontSize(20), - Text( - '${credits.toStringAsFixed(2)} pts', - ).fontSize(14), - const Gap(8), - LinearProgressIndicator(value: credits / 200), - ], - ), - Positioned( - right: 0, - top: 0, - child: IconButton( - onPressed: () {}, - icon: const Icon(Symbols.info), - tooltip: 'socialCreditsDescription'.tr(), - ), - ), + Text( + credits < 100 + ? 'socialCreditsLevelPoor'.tr() + : credits < 150 + ? 'socialCreditsLevelNormal'.tr() + : credits < 200 + ? 'socialCreditsLevelGood'.tr() + : 'socialCreditsLevelExcellent'.tr(), + ).tr().bold().fontSize(20), + Text('${credits.toStringAsFixed(2)} pts').fontSize(14), + const Gap(8), + LinearProgressIndicator(value: credits / 200), ], ), + Positioned( + right: 0, + top: 0, + child: IconButton( + onPressed: () {}, + icon: const Icon(Symbols.info), + tooltip: 'socialCreditsDescription'.tr(), + ), + ), + ], + ), error: (_, _) => Text('Error loading credits'), loading: () => const LinearProgressIndicator(), ) @@ -119,15 +115,14 @@ class SocialCreditsTab extends HookConsumerWidget { contentPadding: const EdgeInsets.symmetric(horizontal: 24), title: Text( record.reason, - style: - isExpired - ? TextStyle( - decoration: TextDecoration.lineThrough, - color: Theme.of( - context, - ).colorScheme.onSurface.withOpacity(0.8), - ) - : null, + style: isExpired + ? TextStyle( + decoration: TextDecoration.lineThrough, + color: Theme.of( + context, + ).colorScheme.onSurface.withOpacity(0.8), + ) + : null, ), subtitle: Row( spacing: 4, diff --git a/lib/screens/account/leveling.dart b/lib/screens/account/leveling.dart index 46bc8907..0325cddc 100644 --- a/lib/screens/account/leveling.dart +++ b/lib/screens/account/leveling.dart @@ -14,7 +14,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:island/widgets/paging/pagination_list.dart'; import 'package:styled_widget/styled_widget.dart'; -final levelingHistoryNotifierProvider = AsyncNotifierProvider( +final levelingHistoryNotifierProvider = AsyncNotifierProvider.autoDispose( LevelingHistoryNotifier.new, ); @@ -35,11 +35,10 @@ class LevelingHistoryNotifier extends AsyncNotifier> totalCount = int.parse(response.headers.value('X-Total') ?? '0'); - final List records = - response.data - .map((json) => SnExperienceRecord.fromJson(json)) - .cast() - .toList(); + final List records = response.data + .map((json) => SnExperienceRecord.fromJson(json)) + .cast() + .toList(); return records; } @@ -162,8 +161,9 @@ class LevelingScreen extends HookConsumerWidget { stopIndicatorRadius: 0, trackGap: 0, color: Theme.of(context).colorScheme.primary, - backgroundColor: - Theme.of(context).colorScheme.surfaceContainerHigh, + backgroundColor: Theme.of( + context, + ).colorScheme.surfaceContainerHigh, borderRadius: BorderRadius.circular(32), ), ], @@ -186,38 +186,35 @@ class LevelingScreen extends HookConsumerWidget { notifier: levelingHistoryNotifierProvider.notifier, isRefreshable: false, isSliver: true, - itemBuilder: - (context, idx, record) => ListTile( - title: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text(record.reason), - Row( - spacing: 4, - children: [ - Text( - record.createdAt.formatRelative(context), - ).fontSize(13), - Text('·').fontSize(13).bold(), - Text(record.createdAt.formatSystem()).fontSize(13), - ], - ).opacity(0.8), - ], - ), - subtitle: Row( - spacing: 8, + itemBuilder: (context, idx, record) => ListTile( + title: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text(record.reason), + Row( + spacing: 4, children: [ Text( - '${record.delta > 0 ? '+' : ''}${record.delta} EXP', - ), - if (record.bonusMultiplier != 1.0) - Text('x${record.bonusMultiplier}'), + record.createdAt.formatRelative(context), + ).fontSize(13), + Text('·').fontSize(13).bold(), + Text(record.createdAt.formatSystem()).fontSize(13), ], - ), - minTileHeight: 56, - contentPadding: EdgeInsets.symmetric(horizontal: 4), - ), + ).opacity(0.8), + ], + ), + subtitle: Row( + spacing: 8, + children: [ + Text('${record.delta > 0 ? '+' : ''}${record.delta} EXP'), + if (record.bonusMultiplier != 1.0) + Text('x${record.bonusMultiplier}'), + ], + ), + minTileHeight: 56, + contentPadding: EdgeInsets.symmetric(horizontal: 4), + ), ), SliverGap(20), @@ -249,11 +246,10 @@ class LevelStairsPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { - final paint = - Paint() - ..color = surfaceColor.withOpacity(0.2) - ..strokeWidth = 1.5 - ..style = PaintingStyle.stroke; + final paint = Paint() + ..color = surfaceColor.withOpacity(0.2) + ..strokeWidth = 1.5 + ..style = PaintingStyle.stroke; // Draw connecting lines between stairs for (int i = 0; i < totalLevels - 1; i++) { diff --git a/lib/screens/account/relationship.dart b/lib/screens/account/relationship.dart index ee0f8f07..9e75a294 100644 --- a/lib/screens/account/relationship.dart +++ b/lib/screens/account/relationship.dart @@ -29,7 +29,7 @@ Future> sentFriendRequest(Ref ref) async { .toList(); } -final relationshipListNotifierProvider = AsyncNotifierProvider( +final relationshipListNotifierProvider = AsyncNotifierProvider.autoDispose( RelationshipListNotifier.new, ); @@ -45,11 +45,10 @@ class RelationshipListNotifier extends AsyncNotifier> queryParameters: {'offset': fetchedCount.toString(), 'take': take}, ); - final List items = - (response.data as List) - .map((e) => SnRelationship.fromJson(e as Map)) - .cast() - .toList(); + final List items = (response.data as List) + .map((e) => SnRelationship.fromJson(e as Map)) + .cast() + .toList(); totalCount = int.tryParse(response.headers['x-total']?.first ?? '') ?? 0; @@ -83,8 +82,9 @@ class RelationshipListTile extends StatelessWidget { @override Widget build(BuildContext context) { - final account = - showRelatedAccount ? relationship.related : relationship.account; + final account = showRelatedAccount + ? relationship.related + : relationship.account; final isPending = relationship.status == 0 && relationship.relatedId == currentUserId; final isWaiting = @@ -138,64 +138,56 @@ class RelationshipListTile extends StatelessWidget { ], ), subtitle: Text('@${account.name}'), - trailing: - showActions - ? Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (isPending && onAccept != null) - IconButton( - padding: EdgeInsets.zero, - onPressed: submitting ? null : onAccept, - icon: const Icon(Symbols.check), - ), - if (isPending && onDecline != null) - IconButton( - padding: EdgeInsets.zero, - onPressed: submitting ? null : onDecline, - icon: const Icon(Symbols.close), - ), - if (isWaiting && onCancel != null) - IconButton( - padding: EdgeInsets.zero, - onPressed: submitting ? null : onCancel, - icon: const Icon(Symbols.close), - ), - if (isEstablished && onUpdateStatus != null) - PopupMenuButton( - padding: EdgeInsets.zero, - icon: const Icon(Symbols.more_vert), - itemBuilder: - (context) => [ - if (relationship.status >= 100) // If friend - PopupMenuItem( - child: ListTile( - leading: const Icon(Symbols.block), - title: Text('blockUser').tr(), - contentPadding: EdgeInsets.zero, - ), - onTap: - () => onUpdateStatus?.call( - relationship, - -100, - ), - ) - else if (relationship.status <= -100) // If blocked - PopupMenuItem( - child: ListTile( - leading: const Icon(Symbols.person_add), - title: Text('unblockUser').tr(), - contentPadding: EdgeInsets.zero, - ), - onTap: - () => - onUpdateStatus?.call(relationship, 100), - ), - ], - ), - ], - ) - : null, + trailing: showActions + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (isPending && onAccept != null) + IconButton( + padding: EdgeInsets.zero, + onPressed: submitting ? null : onAccept, + icon: const Icon(Symbols.check), + ), + if (isPending && onDecline != null) + IconButton( + padding: EdgeInsets.zero, + onPressed: submitting ? null : onDecline, + icon: const Icon(Symbols.close), + ), + if (isWaiting && onCancel != null) + IconButton( + padding: EdgeInsets.zero, + onPressed: submitting ? null : onCancel, + icon: const Icon(Symbols.close), + ), + if (isEstablished && onUpdateStatus != null) + PopupMenuButton( + padding: EdgeInsets.zero, + icon: const Icon(Symbols.more_vert), + itemBuilder: (context) => [ + if (relationship.status >= 100) // If friend + PopupMenuItem( + child: ListTile( + leading: const Icon(Symbols.block), + title: Text('blockUser').tr(), + contentPadding: EdgeInsets.zero, + ), + onTap: () => onUpdateStatus?.call(relationship, -100), + ) + else if (relationship.status <= -100) // If blocked + PopupMenuItem( + child: ListTile( + leading: const Icon(Symbols.person_add), + title: Text('unblockUser').tr(), + contentPadding: EdgeInsets.zero, + ), + onTap: () => onUpdateStatus?.call(relationship, 100), + ), + ], + ), + ], + ) + : null, ); } } @@ -299,6 +291,7 @@ class RelationshipScreen extends HookConsumerWidget { const Divider(height: 1), Expanded( child: PaginationList( + padding: EdgeInsets.zero, provider: relationshipListNotifierProvider, notifier: relationshipListNotifierProvider.notifier, itemBuilder: (context, index, relationship) { @@ -380,28 +373,26 @@ class _SentFriendRequestsSheet extends HookConsumerWidget { const Divider(height: 1), Expanded( child: requests.when( - data: - (items) => - items.isEmpty - ? Center( - child: Text( - 'friendSentRequestEmpty'.tr(), - textAlign: TextAlign.center, - ), - ) - : ListView.builder( - shrinkWrap: true, - itemCount: items.length, - itemBuilder: (context, index) { - final request = items[index]; - return RelationshipListTile( - relationship: request, - onCancel: () => cancelRequest(request), - currentUserId: user.value?.id, - showRelatedAccount: true, - ); - }, - ), + data: (items) => items.isEmpty + ? Center( + child: Text( + 'friendSentRequestEmpty'.tr(), + textAlign: TextAlign.center, + ), + ) + : ListView.builder( + shrinkWrap: true, + itemCount: items.length, + itemBuilder: (context, index) { + final request = items[index]; + return RelationshipListTile( + relationship: request, + onCancel: () => cancelRequest(request), + currentUserId: user.value?.id, + showRelatedAccount: true, + ); + }, + ), loading: () => const Center(child: CircularProgressIndicator()), error: (error, stack) => Center(child: Text('Error: $error')), ), diff --git a/lib/screens/discovery/feeds/feed_marketplace.dart b/lib/screens/discovery/feeds/feed_marketplace.dart index d623b1a6..8295e264 100644 --- a/lib/screens/discovery/feeds/feed_marketplace.dart +++ b/lib/screens/discovery/feeds/feed_marketplace.dart @@ -12,7 +12,7 @@ import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/paging/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; -final marketplaceWebFeedsNotifierProvider = AsyncNotifierProvider( +final marketplaceWebFeedsNotifierProvider = AsyncNotifierProvider.autoDispose( MarketplaceWebFeedsNotifier.new, ); diff --git a/lib/screens/discovery/realms.dart b/lib/screens/discovery/realms.dart index ac6110fd..2c41cec2 100644 --- a/lib/screens/discovery/realms.dart +++ b/lib/screens/discovery/realms.dart @@ -23,7 +23,7 @@ class DiscoveryRealmsScreen extends HookConsumerWidget { children: [ CustomScrollView( slivers: [ - SliverGap(80), + SliverGap(88), SliverRealmList( query: currentQuery.value, key: ValueKey(currentQuery.value), diff --git a/lib/screens/notification.dart b/lib/screens/notification.dart index b40eed90..c10c6edc 100644 --- a/lib/screens/notification.dart +++ b/lib/screens/notification.dart @@ -163,7 +163,7 @@ class NotificationUnreadCountNotifier } } -final notificationListProvider = AsyncNotifierProvider( +final notificationListProvider = AsyncNotifierProvider.autoDispose( NotificationListNotifier.new, ); diff --git a/lib/screens/stickers/sticker_marketplace.dart b/lib/screens/stickers/sticker_marketplace.dart index 6d14a4b4..22a4b531 100644 --- a/lib/screens/stickers/sticker_marketplace.dart +++ b/lib/screens/stickers/sticker_marketplace.dart @@ -28,9 +28,8 @@ sealed class MarketplaceStickerQuery with _$MarketplaceStickerQuery { }) = _MarketplaceStickerQuery; } -final marketplaceStickerPacksNotifierProvider = AsyncNotifierProvider( - MarketplaceStickerPacksNotifier.new, -); +final marketplaceStickerPacksNotifierProvider = + AsyncNotifierProvider.autoDispose(MarketplaceStickerPacksNotifier.new); class MarketplaceStickerPacksNotifier extends AsyncNotifier> with diff --git a/lib/screens/wallet.dart b/lib/screens/wallet.dart index a026165c..25431c47 100644 --- a/lib/screens/wallet.dart +++ b/lib/screens/wallet.dart @@ -112,8 +112,8 @@ class _CreateFundSheetState extends State { ), ), ), - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), ), const Gap(16), @@ -137,22 +137,21 @@ class _CreateFundSheetState extends State { ), ), ), - items: - kCurrencyIconData.keys.map((currency) { - return DropdownMenuItem( - value: currency, - child: Row( - children: [ - Icon(kCurrencyIconData[currency]), - const Gap(8), - Text( - 'walletCurrency${currency[0].toUpperCase()}${currency.substring(1).toLowerCase()}' - .tr(), - ), - ], + items: kCurrencyIconData.keys.map((currency) { + return DropdownMenuItem( + value: currency, + child: Row( + children: [ + Icon(kCurrencyIconData[currency]), + const Gap(8), + Text( + 'walletCurrency${currency[0].toUpperCase()}${currency.substring(1).toLowerCase()}' + .tr(), ), - ); - }).toList(), + ], + ), + ); + }).toList(), onChanged: (value) { if (value != null) { setState(() => selectedCurrency = value); @@ -178,22 +177,21 @@ class _CreateFundSheetState extends State { inputFormatters: [FilteringTextInputFormatter.digitsOnly], decoration: InputDecoration( labelText: 'enterNumberOfSplits'.tr(), - hintText: - selectedRecipients.isNotEmpty - ? selectedRecipients.length.toString() - : '1', + hintText: selectedRecipients.isNotEmpty + ? selectedRecipients.length.toString() + : '1', border: OutlineInputBorder( borderRadius: const BorderRadius.all( Radius.circular(12), ), ), ), - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), onChanged: (value) { if (value.isEmpty && selectedRecipients.isNotEmpty) { - splitsController.text = - selectedRecipients.length.toString(); + splitsController.text = selectedRecipients.length + .toString(); } }, ), @@ -261,93 +259,84 @@ class _CreateFundSheetState extends State { ).colorScheme.outline.withOpacity(0.2), ), ), - child: - selectedRecipients.isNotEmpty - ? Column( - children: [ - ...selectedRecipients.map((recipient) { - return ListTile( - contentPadding: const EdgeInsets.only( - left: 20, - right: 12, + child: selectedRecipients.isNotEmpty + ? Column( + children: [ + ...selectedRecipients.map((recipient) { + return ListTile( + contentPadding: const EdgeInsets.only( + left: 20, + right: 12, + ), + leading: ProfilePictureWidget( + file: recipient.profile.picture, + ), + title: Text( + recipient.nick, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, ), - leading: ProfilePictureWidget( - file: recipient.profile.picture, + ), + subtitle: Text( + 'selectedRecipient'.tr(), + style: Theme.of(context).textTheme.bodySmall + ?.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant, + ), + ), + trailing: IconButton( + onPressed: () => setState( + () => + selectedRecipients.remove(recipient), ), - title: Text( - recipient.nick, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - subtitle: Text( - 'selectedRecipient'.tr(), - style: Theme.of( + icon: Icon( + Icons.clear, + color: Theme.of( context, - ).textTheme.bodySmall?.copyWith( - color: - Theme.of( - context, - ).colorScheme.onSurfaceVariant, - ), + ).colorScheme.error, ), - trailing: IconButton( - onPressed: - () => setState( - () => selectedRecipients.remove( - recipient, - ), - ), - icon: Icon( - Icons.clear, - color: - Theme.of(context).colorScheme.error, - ), - tooltip: 'Remove recipient', - ), - ); - }), - ], - ) - : Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.person_add_outlined, - size: 48, - color: - Theme.of( + tooltip: 'Remove recipient', + ), + ); + }), + ], + ) + : Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.person_add_outlined, + size: 48, + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant, + ), + const Gap(8), + Text( + 'noRecipientsSelected'.tr(), + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith( + color: Theme.of( context, ).colorScheme.onSurfaceVariant, - ), - const Gap(8), - Text( - 'noRecipientsSelected'.tr(), - style: Theme.of( - context, - ).textTheme.bodyMedium?.copyWith( - color: - Theme.of( - context, - ).colorScheme.onSurfaceVariant, - ), - ), - const Gap(4), - Text( - 'selectRecipientsToSendFund'.tr(), - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith( - color: - Theme.of( - context, - ).colorScheme.onSurfaceVariant, - ), - textAlign: TextAlign.center, - ), - ], - ).padding(vertical: 32), + ), + ), + const Gap(4), + Text( + 'selectRecipientsToSendFund'.tr(), + style: Theme.of(context).textTheme.bodySmall + ?.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant, + ), + textAlign: TextAlign.center, + ), + ], + ).padding(vertical: 32), ), const Gap(12), OutlinedButton.icon( @@ -399,8 +388,8 @@ class _CreateFundSheetState extends State { ), ), maxLines: 3, - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), ), ], ), @@ -441,92 +430,87 @@ class _CreateFundSheetState extends State { isScrollControlled: true, backgroundColor: Colors.transparent, useSafeArea: true, - builder: - (context) => Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(16), - ), - ), + builder: (context) => Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), + ), + child: Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: SheetScaffold( + titleText: 'enterPin'.tr(), + heightFactor: 0.5, child: Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom, - ), - child: SheetScaffold( - titleText: 'enterPin'.tr(), - heightFactor: 0.5, - child: Padding( - padding: const EdgeInsets.all(20), - child: Column( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'enterPinToConfirmPayment'.tr(), + style: Theme.of(context).textTheme.titleMedium + ?.copyWith(fontWeight: FontWeight.w500), + textAlign: TextAlign.center, + ), + const Gap(24), + OtpTextField( + numberOfFields: 6, + borderColor: Theme.of(context).colorScheme.outline, + focusedBorderColor: Theme.of( + context, + ).colorScheme.primary, + showFieldAsBox: true, + obscureText: true, + keyboardType: TextInputType.number, + fieldWidth: 48, + fieldHeight: 56, + borderRadius: BorderRadius.circular(8), + borderWidth: 1, + textStyle: Theme.of(context).textTheme.headlineSmall + ?.copyWith(fontWeight: FontWeight.w600), + onSubmit: (pin) { + enteredPin = pin; + Navigator.of(context).pop(pin); + }, + onCodeChanged: (String code) { + enteredPin = code; + }, + ), + ], + ), + ), + const Gap(24), + Row( children: [ Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'enterPinToConfirmPayment'.tr(), - style: Theme.of(context).textTheme.titleMedium - ?.copyWith(fontWeight: FontWeight.w500), - textAlign: TextAlign.center, - ), - const Gap(24), - OtpTextField( - numberOfFields: 6, - borderColor: - Theme.of(context).colorScheme.outline, - focusedBorderColor: - Theme.of(context).colorScheme.primary, - showFieldAsBox: true, - obscureText: true, - keyboardType: TextInputType.number, - fieldWidth: 48, - fieldHeight: 56, - borderRadius: BorderRadius.circular(8), - borderWidth: 1, - textStyle: Theme.of(context) - .textTheme - .headlineSmall - ?.copyWith(fontWeight: FontWeight.w600), - onSubmit: (pin) { - enteredPin = pin; - Navigator.of(context).pop(pin); - }, - onCodeChanged: (String code) { - enteredPin = code; - }, - ), - ], + child: OutlinedButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('cancel'.tr()), ), ), - const Gap(24), - Row( - children: [ - Expanded( - child: OutlinedButton( - onPressed: () => Navigator.of(context).pop(), - child: Text('cancel'.tr()), - ), + if (enteredPin.length == 6) ...[ + const Gap(12), + Expanded( + child: FilledButton( + onPressed: () { + Navigator.of(context).pop(enteredPin); + }, + child: Text('confirm'.tr()), ), - if (enteredPin.length == 6) ...[ - const Gap(12), - Expanded( - child: FilledButton( - onPressed: () { - Navigator.of(context).pop(enteredPin); - }, - child: Text('confirm'.tr()), - ), - ), - ], - ], - ), + ), + ], ], ), - ), + ], ), ), ), + ), + ), ); return enteredPin.isNotEmpty ? enteredPin : null; @@ -552,10 +536,9 @@ class _CreateFundSheetState extends State { 'split_type': selectedSplitType, 'amount_of_splits': splits, 'recipient_account_ids': selectedRecipients.map((r) => r.id).toList(), - 'message': - messageController.text.trim().isEmpty - ? null - : messageController.text.trim(), + 'message': messageController.text.trim().isEmpty + ? null + : messageController.text.trim(), 'pin_code': '', // Will be filled by PIN verification }; @@ -632,8 +615,8 @@ class _CreateTransferSheetState extends State { ), ), ), - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), ), const Gap(16), @@ -657,22 +640,21 @@ class _CreateTransferSheetState extends State { ), ), ), - items: - kCurrencyIconData.keys.map((currency) { - return DropdownMenuItem( - value: currency, - child: Row( - children: [ - Icon(kCurrencyIconData[currency]), - const Gap(8), - Text( - 'walletCurrency${currency[0].toUpperCase()}${currency.substring(1).toLowerCase()}' - .tr(), - ), - ], + items: kCurrencyIconData.keys.map((currency) { + return DropdownMenuItem( + value: currency, + child: Row( + children: [ + Icon(kCurrencyIconData[currency]), + const Gap(8), + Text( + 'walletCurrency${currency[0].toUpperCase()}${currency.substring(1).toLowerCase()}' + .tr(), ), - ); - }).toList(), + ], + ), + ); + }).toList(), onChanged: (value) { if (value != null) { setState(() => selectedCurrency = value); @@ -702,82 +684,74 @@ class _CreateTransferSheetState extends State { ).colorScheme.outline.withOpacity(0.2), ), ), - child: - selectedPayee != null - ? ListTile( - contentPadding: const EdgeInsets.only( - left: 20, - right: 12, + child: selectedPayee != null + ? ListTile( + contentPadding: const EdgeInsets.only( + left: 20, + right: 12, + ), + leading: ProfilePictureWidget( + file: selectedPayee!.profile.picture, + ), + title: Text( + selectedPayee!.nick, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, ), - leading: ProfilePictureWidget( - file: selectedPayee!.profile.picture, + ), + subtitle: Text( + 'selectedPayee'.tr(), + style: Theme.of(context).textTheme.bodySmall + ?.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant, + ), + ), + trailing: IconButton( + onPressed: () => + setState(() => selectedPayee = null), + icon: Icon( + Icons.clear, + color: Theme.of(context).colorScheme.error, ), - title: Text( - selectedPayee!.nick, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - subtitle: Text( - 'selectedPayee'.tr(), - style: Theme.of( + tooltip: 'Remove payee', + ), + ) + : Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.person_add_outlined, + size: 48, + color: Theme.of( context, - ).textTheme.bodySmall?.copyWith( - color: - Theme.of( + ).colorScheme.onSurfaceVariant, + ), + const Gap(8), + Text( + 'noPayeeSelected'.tr(), + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith( + color: Theme.of( context, ).colorScheme.onSurfaceVariant, - ), + ), ), - trailing: IconButton( - onPressed: - () => setState(() => selectedPayee = null), - icon: Icon( - Icons.clear, - color: Theme.of(context).colorScheme.error, - ), - tooltip: 'Remove payee', - ), - ) - : Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.person_add_outlined, - size: 48, - color: - Theme.of( + const Gap(4), + Text( + 'selectPayeeToTransfer'.tr(), + style: Theme.of(context).textTheme.bodySmall + ?.copyWith( + color: Theme.of( context, ).colorScheme.onSurfaceVariant, - ), - const Gap(8), - Text( - 'noPayeeSelected'.tr(), - style: Theme.of( - context, - ).textTheme.bodyMedium?.copyWith( - color: - Theme.of( - context, - ).colorScheme.onSurfaceVariant, - ), - ), - const Gap(4), - Text( - 'selectPayeeToTransfer'.tr(), - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith( - color: - Theme.of( - context, - ).colorScheme.onSurfaceVariant, - ), - textAlign: TextAlign.center, - ), - ], - ).padding(vertical: 32), + ), + textAlign: TextAlign.center, + ), + ], + ).padding(vertical: 32), ), const Gap(12), OutlinedButton.icon( @@ -824,8 +798,8 @@ class _CreateTransferSheetState extends State { ), ), maxLines: 3, - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => + FocusManager.instance.primaryFocus?.unfocus(), ), ], ), @@ -866,92 +840,87 @@ class _CreateTransferSheetState extends State { isScrollControlled: true, backgroundColor: Colors.transparent, useSafeArea: true, - builder: - (context) => Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(16), - ), - ), + builder: (context) => Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), + ), + child: Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: SheetScaffold( + titleText: 'enterPin'.tr(), + heightFactor: 0.5, child: Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom, - ), - child: SheetScaffold( - titleText: 'enterPin'.tr(), - heightFactor: 0.5, - child: Padding( - padding: const EdgeInsets.all(20), - child: Column( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'enterPinToConfirmTransfer'.tr(), + style: Theme.of(context).textTheme.titleMedium + ?.copyWith(fontWeight: FontWeight.w500), + textAlign: TextAlign.center, + ), + const Gap(24), + OtpTextField( + numberOfFields: 6, + borderColor: Theme.of(context).colorScheme.outline, + focusedBorderColor: Theme.of( + context, + ).colorScheme.primary, + showFieldAsBox: true, + obscureText: true, + keyboardType: TextInputType.number, + fieldWidth: 48, + fieldHeight: 56, + borderRadius: BorderRadius.circular(8), + borderWidth: 1, + textStyle: Theme.of(context).textTheme.headlineSmall + ?.copyWith(fontWeight: FontWeight.w600), + onSubmit: (pin) { + enteredPin = pin; + Navigator.of(context).pop(pin); + }, + onCodeChanged: (String code) { + enteredPin = code; + }, + ), + ], + ), + ), + const Gap(24), + Row( children: [ Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'enterPinToConfirmTransfer'.tr(), - style: Theme.of(context).textTheme.titleMedium - ?.copyWith(fontWeight: FontWeight.w500), - textAlign: TextAlign.center, - ), - const Gap(24), - OtpTextField( - numberOfFields: 6, - borderColor: - Theme.of(context).colorScheme.outline, - focusedBorderColor: - Theme.of(context).colorScheme.primary, - showFieldAsBox: true, - obscureText: true, - keyboardType: TextInputType.number, - fieldWidth: 48, - fieldHeight: 56, - borderRadius: BorderRadius.circular(8), - borderWidth: 1, - textStyle: Theme.of(context) - .textTheme - .headlineSmall - ?.copyWith(fontWeight: FontWeight.w600), - onSubmit: (pin) { - enteredPin = pin; - Navigator.of(context).pop(pin); - }, - onCodeChanged: (String code) { - enteredPin = code; - }, - ), - ], + child: OutlinedButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('cancel'.tr()), ), ), - const Gap(24), - Row( - children: [ - Expanded( - child: OutlinedButton( - onPressed: () => Navigator.of(context).pop(), - child: Text('cancel'.tr()), - ), + if (enteredPin.length == 6) ...[ + const Gap(12), + Expanded( + child: FilledButton( + onPressed: () { + Navigator.of(context).pop(enteredPin); + }, + child: Text('confirm'.tr()), ), - if (enteredPin.length == 6) ...[ - const Gap(12), - Expanded( - child: FilledButton( - onPressed: () { - Navigator.of(context).pop(enteredPin); - }, - child: Text('confirm'.tr()), - ), - ), - ], - ], - ), + ), + ], ], ), - ), + ], ), ), ), + ), + ), ); return enteredPin.isNotEmpty ? enteredPin : null; @@ -974,10 +943,9 @@ class _CreateTransferSheetState extends State { 'amount': amount, 'currency': selectedCurrency, 'payee_account_id': selectedPayee!.id, - 'remark': - remarkController.text.trim().isEmpty - ? null - : remarkController.text.trim(), + 'remark': remarkController.text.trim().isEmpty + ? null + : remarkController.text.trim(), }; // Ask for PIN confirmation before creating transfer @@ -991,7 +959,7 @@ class _CreateTransferSheetState extends State { } } -final transactionListProvider = AsyncNotifierProvider( +final transactionListProvider = AsyncNotifierProvider.autoDispose( TransactionListNotifier.new, ); @@ -1012,14 +980,17 @@ class TransactionListNotifier extends AsyncNotifier> ); totalCount = int.parse(response.headers.value('X-Total') ?? '0'); final List data = response.data; - final transactions = - data.map((json) => SnTransaction.fromJson(json)).toList(); + final transactions = data + .map((json) => SnTransaction.fromJson(json)) + .toList(); return transactions; } } -final walletFundsProvider = AsyncNotifierProvider(WalletFundsNotifier.new); +final walletFundsProvider = AsyncNotifierProvider.autoDispose( + WalletFundsNotifier.new, +); class WalletFundsNotifier extends AsyncNotifier> with AsyncPaginationController { @@ -1034,8 +1005,9 @@ class WalletFundsNotifier extends AsyncNotifier> '/pass/wallets/funds?offset=$offset&take=$pageSize', ); // Assuming total count header is present or we just check if list is empty - final list = - (response.data as List).map((e) => SnWalletFund.fromJson(e)).toList(); + final list = (response.data as List) + .map((e) => SnWalletFund.fromJson(e)) + .toList(); if (list.length < pageSize) { totalCount = fetchedCount + list.length; } @@ -1043,7 +1015,7 @@ class WalletFundsNotifier extends AsyncNotifier> } } -final walletFundRecipientsProvider = AsyncNotifierProvider( +final walletFundRecipientsProvider = AsyncNotifierProvider.autoDispose( WalletFundRecipientsNotifier.new, ); @@ -1060,10 +1032,9 @@ class WalletFundRecipientsNotifier final response = await client.get( '/pass/wallets/funds/recipients?offset=$offset&take=$_pageSize', ); - final list = - (response.data as List) - .map((e) => SnWalletFundRecipient.fromJson(e)) - .toList(); + final list = (response.data as List) + .map((e) => SnWalletFundRecipient.fromJson(e)) + .toList(); if (list.length < _pageSize) { totalCount = fetchedCount + list.length; @@ -1312,16 +1283,15 @@ class WalletScreen extends HookConsumerWidget { return allCurrencies.map((currency) { final existingPocket = pockets.firstWhere( (p) => p.currency == currency, - orElse: - () => SnWalletPocket( - id: '', - currency: currency, - amount: 0.0, - walletId: '', - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - deletedAt: null, - ), + orElse: () => SnWalletPocket( + id: '', + currency: currency, + amount: 0.0, + walletId: '', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + deletedAt: null, + ), ); return existingPocket; }).toList(); @@ -1338,12 +1308,12 @@ class WalletScreen extends HookConsumerWidget { ? Symbols.money_bag : Symbols.swap_horiz, ), - onPressed: - currentTabIndex.value == 1 ? createFund : createTransfer, - tooltip: - currentTabIndex.value == 1 - ? 'createFund'.tr() - : 'createTransfer'.tr(), + onPressed: currentTabIndex.value == 1 + ? createFund + : createTransfer, + tooltip: currentTabIndex.value == 1 + ? 'createFund'.tr() + : 'createTransfer'.tr(), ), const Gap(8), ], @@ -1368,58 +1338,54 @@ class WalletScreen extends HookConsumerWidget { } 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: [ - ...getAllCurrencies(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()}', - ), - ), + headerSliverBuilder: (context, innerBoxIsScrolled) => [ + // Wallet Overview + SliverToBoxAdapter( + child: Column( + spacing: 8, + children: [ + // Wallet Stats + _buildCompactStatsWidget(context, ref), + // Pockets + Card( + margin: EdgeInsets.zero, + child: Column( + children: [ + ...getAllCurrencies(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()}', + ), + ), ), - ), - ], - ).padding(horizontal: 12, top: 12), - ), - - SliverGap(8), - - // Tab Bar - SliverToBoxAdapter( - child: TabBar( - controller: tabController, - tabs: [ - Tab(text: 'transactions'.tr()), - Tab(text: 'myFunds'.tr()), - Tab(text: 'lottery'.tr()), - ], + ], + ), ), - ), - ], + ], + ).padding(horizontal: 12, top: 12), + ), + + SliverGap(8), + + // Tab Bar + SliverToBoxAdapter( + child: TabBar( + controller: tabController, + tabs: [ + Tab(text: 'transactions'.tr()), + Tab(text: 'myFunds'.tr()), + Tab(text: 'lottery'.tr()), + ], + ), + ), + ], body: TabBarView( controller: tabController, children: [ @@ -1438,10 +1404,8 @@ class WalletScreen extends HookConsumerWidget { context: context, useRootNavigator: true, isScrollControlled: true, - builder: - (context) => TransactionDetailSheet( - transaction: transaction, - ), + builder: (context) => + TransactionDetailSheet(transaction: transaction), ); }, child: ListTile( @@ -1479,11 +1443,10 @@ class WalletScreen extends HookConsumerWidget { ), ); }, - error: - (error, stackTrace) => ResponseErrorWidget( - error: error, - onRetry: () => ref.invalidate(walletCurrentProvider), - ), + error: (error, stackTrace) => ResponseErrorWidget( + error: error, + onRetry: () => ref.invalidate(walletCurrentProvider), + ), loading: () => const Center(child: CircularProgressIndicator()), ), ); @@ -1554,8 +1517,9 @@ class WalletScreen extends HookConsumerWidget { itemCount: fundList.length, itemBuilder: (context, index) { final fund = fundList[index]; - final claimedCount = - fund.recipients.where((r) => r.isReceived).length; + final claimedCount = fund.recipients + .where((r) => r.isReceived) + .length; final totalRecipients = fund.recipients.length; return Card( @@ -1724,22 +1688,20 @@ class WalletScreen extends HookConsumerWidget { ), ); }, - 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')), - ), - ), + 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')), + ), + ), ); } diff --git a/lib/widgets/realm/realm_list.dart b/lib/widgets/realm/realm_list.dart index f502e4fe..aa4f2066 100644 --- a/lib/widgets/realm/realm_list.dart +++ b/lib/widgets/realm/realm_list.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/realm.dart'; import 'package:island/pods/network.dart'; import 'package:island/pods/paging.dart'; import 'package:island/widgets/paging/pagination_list.dart'; -import 'package:island/widgets/realm/realm_card.dart'; +import 'package:island/widgets/realm/realm_list_tile.dart'; import 'package:styled_widget/styled_widget.dart'; final realmListNotifierProvider = AsyncNotifierProvider.autoDispose @@ -51,25 +50,12 @@ class SliverRealmList extends HookConsumerWidget { notifier: provider.notifier, isSliver: true, isRefreshable: false, + spacing: 8, itemBuilder: (context, index, realm) { - return Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: - ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 540), - child: RealmCard(realm: realm), - ).center(), - ), - if (index < - (ref.read(provider).value?.length ?? 0) - - 1) // Add gap except for last item? Actually PaginationList handles loading indicator which might look like last item. - // Wait, ref.read(provider).value?.length might change. - // Simpler to just add bottom padding to all, or Gap. - const Gap(8), - ], - ); + return ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 540), + child: RealmListTile(realm: realm), + ).center(); }, ); }