updating UI inconsistency

This commit is contained in:
2026-06-24 18:31:21 +02:00
parent a6b4a04bd5
commit 57358f6233
5 changed files with 330 additions and 227 deletions

View File

@@ -61,6 +61,7 @@ GroupBatchItem _$GroupBatchItemFromJson(Map<String, dynamic> json) =>
billProductName: json['billProductName'] as String?, billProductName: json['billProductName'] as String?,
providerLabel: json['providerLabel'] as String?, providerLabel: json['providerLabel'] as String?,
providerImage: json['providerImage'] as String?, providerImage: json['providerImage'] as String?,
creditAddress: json['creditAddress'] as String?,
); );
Map<String, dynamic> _$GroupBatchItemToJson(GroupBatchItem instance) => Map<String, dynamic> _$GroupBatchItemToJson(GroupBatchItem instance) =>
@@ -79,6 +80,7 @@ Map<String, dynamic> _$GroupBatchItemToJson(GroupBatchItem instance) =>
'billProductName': instance.billProductName, 'billProductName': instance.billProductName,
'providerLabel': instance.providerLabel, 'providerLabel': instance.providerLabel,
'providerImage': instance.providerImage, 'providerImage': instance.providerImage,
'creditAddress': instance.creditAddress,
}; };
CreateBatchRequest _$CreateBatchRequestFromJson(Map<String, dynamic> json) => CreateBatchRequest _$CreateBatchRequestFromJson(Map<String, dynamic> json) =>

View File

@@ -2467,7 +2467,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDark ? const Color(0xFF1A1A2E) : Colors.white, color: isDark ? const Color(0xFF1A1A2E) : Colors.white,
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(16),
border: Border.all( border: Border.all(
color: isDark color: isDark
? Colors.white.withValues(alpha: 0.06) ? Colors.white.withValues(alpha: 0.06)
@@ -2483,24 +2483,31 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
), ),
], ],
), ),
padding: const EdgeInsets.all(10), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14),
child: Row( child: Row(
children: [ children: [
CircleAvatar( Container(
radius: 18, width: 44,
backgroundColor: theme.colorScheme.primary.withAlpha(25), height: 44,
child: Text( padding: const EdgeInsets.all(10),
item.recipientName?.isNotEmpty == true decoration: BoxDecoration(
? item.recipientName![0].toUpperCase() color: theme.colorScheme.primary.withValues(alpha: 0.1),
: '?', borderRadius: BorderRadius.circular(12),
style: TextStyle( ),
fontSize: 14, child: Center(
fontWeight: FontWeight.w600, child: Text(
color: theme.colorScheme.primary, 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( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -2513,16 +2520,38 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
color: isDark ? Colors.white : Colors.black87, color: isDark ? Colors.white : Colors.black87,
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 4),
Text( Row(
item.recipientAccount ?? '', children: [
style: TextStyle(fontSize: 12, color: Colors.grey.shade500), 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) Text(item.creditAddress ?? ''),
const SizedBox(height: 4),
if (item.billName != null || item.billProductName != null)
_buildBillInfoRow(item, isDark),
if (item.creditAddress != null) Text(item.creditAddress ?? ''),
], ],
), ),
), ),
@@ -2533,7 +2562,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
Text( Text(
'${batch.currency ?? 'USD'} ${item.amount?.toStringAsFixed(2) ?? ''}', '${batch.currency ?? 'USD'} ${item.amount?.toStringAsFixed(2) ?? ''}',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: isDark ? Colors.white : Colors.black87, color: isDark ? Colors.white : Colors.black87,
), ),

View File

@@ -315,6 +315,8 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
} }
Widget _buildMemberCard(RecipientGroupMember member) { Widget _buildMemberCard(RecipientGroupMember member) {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
final recipient = member.recipient; final recipient = member.recipient;
final name = recipient.name?.isNotEmpty == true final name = recipient.name?.isNotEmpty == true
? recipient.name! ? recipient.name!
@@ -329,19 +331,26 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
}, },
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: isDark ? const Color(0xFF1A1A2E) : Colors.white,
borderRadius: BorderRadius.circular(16), 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: [
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, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
], ],
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 14), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -353,22 +362,27 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
children: [ children: [
Text( Text(
name, name,
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.black87, color: isDark ? Colors.white : Colors.black87,
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
const SizedBox(height: 6), const SizedBox(height: 4),
_buildMemberSubtitleRow(recipient, member.account), _buildMemberSubtitleRow(
recipient,
member.account,
isDark,
),
if (recipient.phoneNumber != null && if (recipient.phoneNumber != null &&
recipient.phoneNumber!.isNotEmpty) ...[ recipient.phoneNumber!.isNotEmpty) ...[
const SizedBox(height: 6), const SizedBox(height: 4),
_buildContactLine( _buildContactLine(
Icons.phone_outlined, Icons.phone_outlined,
recipient.phoneNumber!, recipient.phoneNumber!,
isDark,
), ),
], ],
if (recipient.email != null && if (recipient.email != null &&
@@ -377,6 +391,7 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
_buildContactLine( _buildContactLine(
Icons.email_outlined, Icons.email_outlined,
recipient.email!, recipient.email!,
isDark,
), ),
], ],
], ],
@@ -393,42 +408,29 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
Widget _buildMemberAvatar(String initials) { Widget _buildMemberAvatar(String initials) {
return Container( return Container(
width: 48, width: 44,
height: 48, height: 44,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1),
colors: [ borderRadius: BorderRadius.circular(12),
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),
),
],
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
initials, initials,
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.w600,
color: Colors.white, color: Theme.of(context).colorScheme.primary,
letterSpacing: 0.5,
), ),
), ),
); );
} }
Widget _buildMemberSubtitleRow(Recipient recipient, String? memberAccount) { Widget _buildMemberSubtitleRow(
Recipient recipient,
String? memberAccount,
bool isDark,
) {
final account = memberAccount?.isNotEmpty == true final account = memberAccount?.isNotEmpty == true
? memberAccount ? memberAccount
: recipient.account; : recipient.account;
@@ -463,29 +465,38 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
), ),
), ),
if (hasAccount) if (hasAccount)
Text( Flexible(
account!, child: Text(
style: TextStyle( account!,
fontSize: 13, style: TextStyle(
color: Colors.grey.shade700, fontSize: 12,
fontWeight: FontWeight.w500, 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( return Row(
children: [ children: [
Icon(icon, size: 13, color: Colors.grey.shade500), Icon(
icon,
size: 13,
color: isDark ? Colors.white38 : Colors.grey.shade500,
),
const SizedBox(width: 4), const SizedBox(width: 4),
Flexible( Flexible(
child: Text( child: Text(
text, text,
style: TextStyle(fontSize: 12, color: Colors.grey.shade600), style: TextStyle(
fontSize: 12,
color: isDark ? Colors.white54 : Colors.grey.shade600,
),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -1127,131 +1138,166 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
final status = batch.status ?? 'UNKNOWN'; final status = batch.status ?? 'UNKNOWN';
final createdAt = batch.createdAt; final createdAt = batch.createdAt;
final parsedDate = createdAt != null ? DateTime.tryParse(createdAt) : null; final parsedDate = createdAt != null ? DateTime.tryParse(createdAt) : null;
return Card( final theme = Theme.of(context);
elevation: 0, final isDark = theme.brightness == Brightness.dark;
shape: RoundedRectangleBorder( return Padding(
borderRadius: BorderRadius.circular(12), padding: const EdgeInsets.only(bottom: 8),
side: BorderSide( child: Material(
color: inSelectMode && isSelected color: Colors.transparent,
? Theme.of(context).colorScheme.primary child: InkWell(
: Colors.black12.withAlpha(30), borderRadius: BorderRadius.circular(16),
width: inSelectMode && isSelected ? 2 : 1, onTap: inSelectMode
), ? () => batchController.toggleBatchSelection(batch.id!)
), : () => context.push('/groups/batches/${batch.id}'),
child: InkWell( child: Container(
borderRadius: BorderRadius.circular(12), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14),
onTap: inSelectMode decoration: BoxDecoration(
? () => batchController.toggleBatchSelection(batch.id!) color: isDark ? const Color(0xFF1A1A2E) : Colors.white,
: () => context.push('/groups/batches/${batch.id}'), borderRadius: BorderRadius.circular(16),
child: Padding( border: Border.all(
padding: const EdgeInsets.all(10), color: inSelectMode && isSelected
child: Row( ? theme.colorScheme.primary
children: [ : isDark
if (inSelectMode) ? Colors.white.withValues(alpha: 0.06)
Padding( : Colors.grey.shade200,
padding: const EdgeInsets.only(right: 8), width: inSelectMode && isSelected ? 2 : 1,
child: Icon( ),
isSelected boxShadow: [
? Icons.check_box BoxShadow(
: Icons.check_box_outline_blank, color: isDark
size: 20, ? Colors.black.withValues(alpha: 0.2)
color: isSelected : Colors.black.withValues(alpha: 0.03),
? Theme.of(context).colorScheme.primary blurRadius: 8,
: Colors.grey.shade400, offset: const Offset(0, 2),
),
), ),
Expanded( ],
child: Column( ),
crossAxisAlignment: CrossAxisAlignment.start, child: Row(
children: [ children: [
Row( if (inSelectMode)
children: [ Padding(
if (batch.description != null && padding: const EdgeInsets.only(right: 12),
batch.description!.isNotEmpty) child: Icon(
Flexible( isSelected
child: Text( ? Icons.check_box
batch.description!, : Icons.check_box_outline_blank,
style: const TextStyle( size: 20,
fontSize: 15, color: isSelected
fontWeight: FontWeight.w700, ? 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), if (parsedDate != null) ...[
StatusChip(status: status), Text(
], '',
), style: TextStyle(
const SizedBox(height: 4), fontSize: 11,
Text( color: isDark
'${batch.currency ?? 'USD'} ${batch.value?.toStringAsFixed(2) ?? ''}', ? Colors.white24
style: TextStyle( : Colors.grey.shade400,
fontSize: 13, ),
color: Colors.grey.shade600, ),
), Text(
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (parsedDate != null)
Padding(
padding: const EdgeInsets.only(top: 6),
child: Text(
DateFormat.yMMMd().add_jm().format(parsedDate), DateFormat.yMMMd().add_jm().format(parsedDate),
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.grey.shade500, fontWeight: FontWeight.w400,
color: isDark
? Colors.white54
: Colors.grey.shade500,
), ),
), ),
), ],
if (batch.count != null) const Spacer(),
Padding( if (batch.count != null)
padding: const EdgeInsets.only(top: 4), Row(
child: Row(
children: [ children: [
_statIcon( _statIcon(
Icons.people_outline, Icons.people_outline,
'${batch.count}', '${batch.count}',
isDark,
), ),
const SizedBox(width: 4), const SizedBox(width: 6),
_statIcon( _statIcon(
Icons.check_circle_outline, Icons.check_circle_outline,
'${batch.successfulCount ?? 0}', '${batch.successfulCount ?? 0}',
isDark,
), ),
const SizedBox(width: 4), const SizedBox(width: 6),
_statIcon( _statIcon(
Icons.cancel_outlined, Icons.cancel_outlined,
'${batch.failedCount ?? 0}', '${batch.failedCount ?? 0}',
isDark,
), ),
], ],
), ),
), ],
], ),
), ],
], ),
), ),
), if (!inSelectMode)
if (!inSelectMode) Icon(Icons.chevron_right, color: Colors.grey.shade400),
Icon(Icons.chevron_right, color: Colors.grey.shade400), ],
], ),
), ),
), ),
), ),
); );
} }
Widget _statIcon(IconData icon, String label) { Widget _statIcon(IconData icon, String label, bool isDark) {
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(icon, size: 12, color: Colors.grey.shade500), Icon(
icon,
size: 12,
color: isDark ? Colors.white38 : Colors.grey.shade500,
),
const SizedBox(width: 3), const SizedBox(width: 3),
Text( Text(
label, label,
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
color: Colors.grey.shade600, color: isDark ? Colors.white54 : Colors.grey.shade600,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),

View File

@@ -435,86 +435,112 @@ class _GroupsScreenState extends State<GroupsScreen> {
} }
Widget _buildGroupCard(RecipientGroup group) { Widget _buildGroupCard(RecipientGroup group) {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
final isSelected = controller.model.selectedGroupIds.contains(group.id); final isSelected = controller.model.selectedGroupIds.contains(group.id);
final inSelectMode = controller.model.selectMode; final inSelectMode = controller.model.selectMode;
return Card( return Padding(
elevation: 0, padding: const EdgeInsets.only(bottom: 8),
shape: RoundedRectangleBorder( child: Material(
borderRadius: BorderRadius.circular(12), color: Colors.transparent,
side: BorderSide( child: InkWell(
color: inSelectMode && isSelected borderRadius: BorderRadius.circular(16),
? Theme.of(context).colorScheme.primary onTap: inSelectMode
: Colors.black12.withAlpha(30), ? () => controller.toggleGroupSelection(group.id!)
width: inSelectMode && isSelected ? 2 : 1, : () => context.push('/groups/${group.id}', extra: group),
), child: Container(
), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14),
child: InkWell( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12), color: isDark ? const Color(0xFF1A1A2E) : Colors.white,
onTap: inSelectMode borderRadius: BorderRadius.circular(16),
? () => controller.toggleGroupSelection(group.id!) border: Border.all(
: () => context.push('/groups/${group.id}', extra: group), color: inSelectMode && isSelected
child: Padding( ? theme.colorScheme.primary
padding: const EdgeInsets.all(16), : isDark
child: Row( ? Colors.white.withValues(alpha: 0.06)
children: [ : Colors.grey.shade200,
if (inSelectMode) width: inSelectMode && isSelected ? 2 : 1,
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,
),
),
), ),
const SizedBox(width: 16), boxShadow: [
Expanded( BoxShadow(
child: Column( color: isDark
crossAxisAlignment: CrossAxisAlignment.start, ? Colors.black.withValues(alpha: 0.2)
children: [ : Colors.black.withValues(alpha: 0.03),
Text( blurRadius: 8,
group.name, offset: const Offset(0, 2),
style: const TextStyle( ),
fontSize: 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.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, fontWeight: FontWeight.w600,
fontSize: 16,
), ),
), ),
if (group.description != null && ),
group.description!.isNotEmpty) ),
Padding( const SizedBox(width: 14),
padding: const EdgeInsets.only(top: 4), Expanded(
child: Text( child: Column(
group.description!, crossAxisAlignment: CrossAxisAlignment.start,
style: TextStyle( children: [
fontSize: 13, Text(
color: Colors.grey.shade600, group.name,
), style: TextStyle(
maxLines: 1, fontSize: 14,
overflow: TextOverflow.ellipsis, 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)
if (!inSelectMode) Icon(Icons.chevron_right, color: Colors.grey.shade400),
Icon(Icons.chevron_right, color: Colors.grey.shade400), ],
], ),
), ),
), ),
), ),