diff --git a/lib/screens/accounts/widgets/account_balance_widget.dart b/lib/screens/accounts/widgets/account_balance_widget.dart index b8a1b67..0eb3ca2 100644 --- a/lib/screens/accounts/widgets/account_balance_widget.dart +++ b/lib/screens/accounts/widgets/account_balance_widget.dart @@ -523,4 +523,4 @@ class _AccountBalanceWidgetState extends State { final formatter = NumberFormat('#,##0.00', 'en_US'); return '\$${formatter.format(balance)}'; } -} \ No newline at end of file +} diff --git a/lib/screens/groups/models/group_batch_model.g.dart b/lib/screens/groups/models/group_batch_model.g.dart index 29b8bed..d018201 100644 --- a/lib/screens/groups/models/group_batch_model.g.dart +++ b/lib/screens/groups/models/group_batch_model.g.dart @@ -61,6 +61,7 @@ GroupBatchItem _$GroupBatchItemFromJson(Map json) => billProductName: json['billProductName'] as String?, providerLabel: json['providerLabel'] as String?, providerImage: json['providerImage'] as String?, + creditAddress: json['creditAddress'] as String?, ); Map _$GroupBatchItemToJson(GroupBatchItem instance) => @@ -79,6 +80,7 @@ Map _$GroupBatchItemToJson(GroupBatchItem instance) => 'billProductName': instance.billProductName, 'providerLabel': instance.providerLabel, 'providerImage': instance.providerImage, + 'creditAddress': instance.creditAddress, }; CreateBatchRequest _$CreateBatchRequestFromJson(Map json) => diff --git a/lib/screens/groups/screens/batch_detail_screen.dart b/lib/screens/groups/screens/batch_detail_screen.dart index 65ba30f..6c75cb2 100644 --- a/lib/screens/groups/screens/batch_detail_screen.dart +++ b/lib/screens/groups/screens/batch_detail_screen.dart @@ -2467,7 +2467,7 @@ class _BatchDetailScreenState extends State return Container( decoration: BoxDecoration( color: isDark ? const Color(0xFF1A1A2E) : Colors.white, - borderRadius: BorderRadius.circular(14), + borderRadius: BorderRadius.circular(16), border: Border.all( color: isDark ? Colors.white.withValues(alpha: 0.06) @@ -2483,24 +2483,31 @@ class _BatchDetailScreenState extends State ), ], ), - padding: const EdgeInsets.all(10), + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14), child: Row( children: [ - CircleAvatar( - radius: 18, - backgroundColor: theme.colorScheme.primary.withAlpha(25), - child: Text( - item.recipientName?.isNotEmpty == true - ? item.recipientName![0].toUpperCase() - : '?', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: theme.colorScheme.primary, + Container( + width: 44, + height: 44, + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Center( + child: Text( + item.recipientName?.isNotEmpty == true + ? item.recipientName![0].toUpperCase() + : '?', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: theme.colorScheme.primary, + ), ), ), ), - const SizedBox(width: 10), + const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -2513,16 +2520,38 @@ class _BatchDetailScreenState extends State color: isDark ? Colors.white : Colors.black87, ), ), - const SizedBox(height: 2), - Text( - item.recipientAccount ?? '', - style: TextStyle(fontSize: 12, color: Colors.grey.shade500), + const SizedBox(height: 4), + Row( + children: [ + if (item.billProductName != null && + item.billProductName!.isNotEmpty) ...[ + Text( + item.billProductName!, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: isDark ? Colors.white54 : Colors.grey.shade500, + ), + ), + Text( + ' • ', + style: TextStyle( + fontSize: 11, + color: isDark ? Colors.white24 : Colors.grey.shade400, + ), + ), + ], + Text( + item.recipientAccount ?? '', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: isDark ? Colors.white54 : Colors.grey.shade500, + ), + ), + ], ), - if (item.billName != null || item.billProductName != null) - const SizedBox(height: 4), - if (item.billName != null || item.billProductName != null) - _buildBillInfoRow(item, isDark), - if (item.creditAddress != null) Text(item.creditAddress ?? ''), + Text(item.creditAddress ?? ''), ], ), ), @@ -2533,7 +2562,7 @@ class _BatchDetailScreenState extends State Text( '${batch.currency ?? 'USD'} ${item.amount?.toStringAsFixed(2) ?? '—'}', style: TextStyle( - fontSize: 14, + fontSize: 16, fontWeight: FontWeight.w700, color: isDark ? Colors.white : Colors.black87, ), diff --git a/lib/screens/groups/screens/group_detail_screen.dart b/lib/screens/groups/screens/group_detail_screen.dart index e31d8a7..31b210e 100644 --- a/lib/screens/groups/screens/group_detail_screen.dart +++ b/lib/screens/groups/screens/group_detail_screen.dart @@ -315,6 +315,8 @@ class _GroupDetailScreenState extends State } Widget _buildMemberCard(RecipientGroupMember member) { + final theme = Theme.of(context); + final isDark = theme.brightness == Brightness.dark; final recipient = member.recipient; final name = recipient.name?.isNotEmpty == true ? recipient.name! @@ -329,19 +331,26 @@ class _GroupDetailScreenState extends State }, child: Container( decoration: BoxDecoration( - color: Colors.white, + color: isDark ? const Color(0xFF1A1A2E) : Colors.white, borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.grey.shade200, width: 1), + border: Border.all( + color: isDark + ? Colors.white.withValues(alpha: 0.06) + : Colors.grey.shade200, + width: 1, + ), boxShadow: [ BoxShadow( - color: Colors.black.withValues(alpha: 0.04), + color: isDark + ? Colors.black.withValues(alpha: 0.2) + : Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Padding( - padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 14), + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -353,22 +362,27 @@ class _GroupDetailScreenState extends State children: [ Text( name, - style: const TextStyle( - fontSize: 16, + style: TextStyle( + fontSize: 14, fontWeight: FontWeight.w600, - color: Colors.black87, + color: isDark ? Colors.white : Colors.black87, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), - const SizedBox(height: 6), - _buildMemberSubtitleRow(recipient, member.account), + const SizedBox(height: 4), + _buildMemberSubtitleRow( + recipient, + member.account, + isDark, + ), if (recipient.phoneNumber != null && recipient.phoneNumber!.isNotEmpty) ...[ - const SizedBox(height: 6), + const SizedBox(height: 4), _buildContactLine( Icons.phone_outlined, recipient.phoneNumber!, + isDark, ), ], if (recipient.email != null && @@ -377,6 +391,7 @@ class _GroupDetailScreenState extends State _buildContactLine( Icons.email_outlined, recipient.email!, + isDark, ), ], ], @@ -393,42 +408,29 @@ class _GroupDetailScreenState extends State Widget _buildMemberAvatar(String initials) { return Container( - width: 48, - height: 48, + width: 44, + height: 44, decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Theme.of(context).colorScheme.primary, - Theme.of(context).colorScheme.primary.withValues(alpha: 0.7), - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Theme.of( - context, - ).colorScheme.primary.withValues(alpha: 0.25), - blurRadius: 6, - offset: const Offset(0, 2), - ), - ], + color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), ), alignment: Alignment.center, child: Text( initials, - style: const TextStyle( + style: TextStyle( fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.white, - letterSpacing: 0.5, + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.primary, ), ), ); } - Widget _buildMemberSubtitleRow(Recipient recipient, String? memberAccount) { + Widget _buildMemberSubtitleRow( + Recipient recipient, + String? memberAccount, + bool isDark, + ) { final account = memberAccount?.isNotEmpty == true ? memberAccount : recipient.account; @@ -463,29 +465,38 @@ class _GroupDetailScreenState extends State ), ), if (hasAccount) - Text( - account!, - style: TextStyle( - fontSize: 13, - color: Colors.grey.shade700, - fontWeight: FontWeight.w500, + Flexible( + child: Text( + account!, + style: TextStyle( + fontSize: 12, + color: isDark ? Colors.white54 : Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - maxLines: 1, - overflow: TextOverflow.ellipsis, ), ], ); } - Widget _buildContactLine(IconData icon, String text) { + Widget _buildContactLine(IconData icon, String text, bool isDark) { return Row( children: [ - Icon(icon, size: 13, color: Colors.grey.shade500), + Icon( + icon, + size: 13, + color: isDark ? Colors.white38 : Colors.grey.shade500, + ), const SizedBox(width: 4), Flexible( child: Text( text, - style: TextStyle(fontSize: 12, color: Colors.grey.shade600), + style: TextStyle( + fontSize: 12, + color: isDark ? Colors.white54 : Colors.grey.shade600, + ), maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -1127,131 +1138,166 @@ class _GroupDetailScreenState extends State final status = batch.status ?? 'UNKNOWN'; final createdAt = batch.createdAt; final parsedDate = createdAt != null ? DateTime.tryParse(createdAt) : null; - return Card( - elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - side: BorderSide( - color: inSelectMode && isSelected - ? Theme.of(context).colorScheme.primary - : Colors.black12.withAlpha(30), - width: inSelectMode && isSelected ? 2 : 1, - ), - ), - child: InkWell( - borderRadius: BorderRadius.circular(12), - onTap: inSelectMode - ? () => batchController.toggleBatchSelection(batch.id!) - : () => context.push('/groups/batches/${batch.id}'), - child: Padding( - padding: const EdgeInsets.all(10), - child: Row( - children: [ - if (inSelectMode) - Padding( - padding: const EdgeInsets.only(right: 8), - child: Icon( - isSelected - ? Icons.check_box - : Icons.check_box_outline_blank, - size: 20, - color: isSelected - ? Theme.of(context).colorScheme.primary - : Colors.grey.shade400, - ), + final theme = Theme.of(context); + final isDark = theme.brightness == Brightness.dark; + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: inSelectMode + ? () => batchController.toggleBatchSelection(batch.id!) + : () => context.push('/groups/batches/${batch.id}'), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF1A1A2E) : Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: inSelectMode && isSelected + ? theme.colorScheme.primary + : isDark + ? Colors.white.withValues(alpha: 0.06) + : Colors.grey.shade200, + width: inSelectMode && isSelected ? 2 : 1, + ), + boxShadow: [ + BoxShadow( + color: isDark + ? Colors.black.withValues(alpha: 0.2) + : Colors.black.withValues(alpha: 0.03), + blurRadius: 8, + offset: const Offset(0, 2), ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - if (batch.description != null && - batch.description!.isNotEmpty) - Flexible( - child: Text( - batch.description!, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w700, + ], + ), + child: Row( + children: [ + if (inSelectMode) + Padding( + padding: const EdgeInsets.only(right: 12), + child: Icon( + isSelected + ? Icons.check_box + : Icons.check_box_outline_blank, + size: 20, + color: isSelected + ? theme.colorScheme.primary + : Colors.grey.shade400, + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if (batch.description != null && + batch.description!.isNotEmpty) + Flexible( + child: Text( + batch.description!, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: isDark + ? Colors.white + : const Color.fromARGB(221, 58, 58, 58), + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - maxLines: 1, - overflow: TextOverflow.ellipsis, + ), + const SizedBox(width: 8), + StatusChip(status: status), + ], + ), + const SizedBox(height: 4), + Row( + children: [ + Text( + '${batch.currency ?? 'USD'} ${batch.value?.toStringAsFixed(2) ?? '—'}', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: isDark ? Colors.white : Colors.black87, ), ), - const SizedBox(width: 6), - StatusChip(status: status), - ], - ), - const SizedBox(height: 4), - Text( - '${batch.currency ?? 'USD'} ${batch.value?.toStringAsFixed(2) ?? '—'}', - style: TextStyle( - fontSize: 13, - color: Colors.grey.shade600, - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (parsedDate != null) - Padding( - padding: const EdgeInsets.only(top: 6), - child: Text( + if (parsedDate != null) ...[ + Text( + ' • ', + style: TextStyle( + fontSize: 11, + color: isDark + ? Colors.white24 + : Colors.grey.shade400, + ), + ), + Text( DateFormat.yMMMd().add_jm().format(parsedDate), style: TextStyle( fontSize: 12, - color: Colors.grey.shade500, + fontWeight: FontWeight.w400, + color: isDark + ? Colors.white54 + : Colors.grey.shade500, ), ), - ), - if (batch.count != null) - Padding( - padding: const EdgeInsets.only(top: 4), - child: Row( + ], + const Spacer(), + if (batch.count != null) + Row( children: [ _statIcon( Icons.people_outline, '${batch.count}', + isDark, ), - const SizedBox(width: 4), + const SizedBox(width: 6), _statIcon( Icons.check_circle_outline, '${batch.successfulCount ?? 0}', + isDark, ), - const SizedBox(width: 4), + const SizedBox(width: 6), _statIcon( Icons.cancel_outlined, '${batch.failedCount ?? 0}', + isDark, ), ], ), - ), - ], - ), - ], + ], + ), + ], + ), ), - ), - if (!inSelectMode) - Icon(Icons.chevron_right, color: Colors.grey.shade400), - ], + if (!inSelectMode) + Icon(Icons.chevron_right, color: Colors.grey.shade400), + ], + ), ), ), ), ); } - Widget _statIcon(IconData icon, String label) { + Widget _statIcon(IconData icon, String label, bool isDark) { return Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(icon, size: 12, color: Colors.grey.shade500), + Icon( + icon, + size: 12, + color: isDark ? Colors.white38 : Colors.grey.shade500, + ), const SizedBox(width: 3), Text( label, style: TextStyle( fontSize: 11, - color: Colors.grey.shade600, + color: isDark ? Colors.white54 : Colors.grey.shade600, fontWeight: FontWeight.w500, ), ), diff --git a/lib/screens/groups/screens/groups_screen.dart b/lib/screens/groups/screens/groups_screen.dart index d5c4a76..876271f 100644 --- a/lib/screens/groups/screens/groups_screen.dart +++ b/lib/screens/groups/screens/groups_screen.dart @@ -435,86 +435,112 @@ class _GroupsScreenState extends State { } Widget _buildGroupCard(RecipientGroup group) { + final theme = Theme.of(context); + final isDark = theme.brightness == Brightness.dark; final isSelected = controller.model.selectedGroupIds.contains(group.id); final inSelectMode = controller.model.selectMode; - return Card( - elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - side: BorderSide( - color: inSelectMode && isSelected - ? Theme.of(context).colorScheme.primary - : Colors.black12.withAlpha(30), - width: inSelectMode && isSelected ? 2 : 1, - ), - ), - child: InkWell( - borderRadius: BorderRadius.circular(12), - onTap: inSelectMode - ? () => controller.toggleGroupSelection(group.id!) - : () => context.push('/groups/${group.id}', extra: group), - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - if (inSelectMode) - Padding( - padding: const EdgeInsets.only(right: 12), - child: Icon( - isSelected - ? Icons.check_box - : Icons.check_box_outline_blank, - color: isSelected - ? Theme.of(context).colorScheme.primary - : Colors.grey.shade400, - ), - ), - CircleAvatar( - backgroundColor: Theme.of( - context, - ).colorScheme.primary.withAlpha(30), - child: Text( - _getGroupAbbreviation(group.name), - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.w600, - fontSize: 16, - ), - ), + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: inSelectMode + ? () => controller.toggleGroupSelection(group.id!) + : () => context.push('/groups/${group.id}', extra: group), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF1A1A2E) : Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: inSelectMode && isSelected + ? theme.colorScheme.primary + : isDark + ? Colors.white.withValues(alpha: 0.06) + : Colors.grey.shade200, + width: inSelectMode && isSelected ? 2 : 1, ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - group.name, - style: const TextStyle( - fontSize: 16, + boxShadow: [ + BoxShadow( + color: isDark + ? Colors.black.withValues(alpha: 0.2) + : Colors.black.withValues(alpha: 0.03), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + if (inSelectMode) + Padding( + padding: const EdgeInsets.only(right: 12), + child: Icon( + isSelected + ? Icons.check_box + : Icons.check_box_outline_blank, + color: isSelected + ? theme.colorScheme.primary + : Colors.grey.shade400, + ), + ), + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Center( + child: Text( + _getGroupAbbreviation(group.name), + style: TextStyle( + color: theme.colorScheme.primary, fontWeight: FontWeight.w600, + fontSize: 16, ), ), - if (group.description != null && - group.description!.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 4), - child: Text( - group.description!, - style: TextStyle( - fontSize: 13, - color: Colors.grey.shade600, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: 14), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + group.name, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: isDark ? Colors.white : Colors.black87, ), ), - ], + if (group.description != null && + group.description!.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + group.description!, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: isDark + ? Colors.white54 + : Colors.grey.shade500, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), ), - ), - if (!inSelectMode) - Icon(Icons.chevron_right, color: Colors.grey.shade400), - ], + if (!inSelectMode) + Icon(Icons.chevron_right, color: Colors.grey.shade400), + ], + ), ), ), ),