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,
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: theme.colorScheme.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text( child: Text(
item.recipientName?.isNotEmpty == true item.recipientName?.isNotEmpty == true
? item.recipientName![0].toUpperCase() ? item.recipientName![0].toUpperCase()
: '?', : '?',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: theme.colorScheme.primary, 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),
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( Text(
item.recipientAccount ?? '', item.recipientAccount ?? '',
style: TextStyle(fontSize: 12, color: Colors.grey.shade500), 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), Text(item.creditAddress ?? ''),
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(
child: Text(
account!, account!,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 12,
color: Colors.grey.shade700, color: isDark ? Colors.white54 : Colors.grey.shade600,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, 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,36 +1138,52 @@ 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
: Colors.black12.withAlpha(30),
width: inSelectMode && isSelected ? 2 : 1,
),
),
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(16),
onTap: inSelectMode onTap: inSelectMode
? () => batchController.toggleBatchSelection(batch.id!) ? () => batchController.toggleBatchSelection(batch.id!)
: () => context.push('/groups/batches/${batch.id}'), : () => context.push('/groups/batches/${batch.id}'),
child: Padding( child: Container(
padding: const EdgeInsets.all(10), 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),
),
],
),
child: Row( child: Row(
children: [ children: [
if (inSelectMode) if (inSelectMode)
Padding( Padding(
padding: const EdgeInsets.only(right: 8), padding: const EdgeInsets.only(right: 12),
child: Icon( child: Icon(
isSelected isSelected
? Icons.check_box ? Icons.check_box
: Icons.check_box_outline_blank, : Icons.check_box_outline_blank,
size: 20, size: 20,
color: isSelected color: isSelected
? Theme.of(context).colorScheme.primary ? theme.colorScheme.primary
: Colors.grey.shade400, : Colors.grey.shade400,
), ),
), ),
@@ -1171,62 +1198,76 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
Flexible( Flexible(
child: Text( child: Text(
batch.description!, batch.description!,
style: const TextStyle( style: TextStyle(
fontSize: 15, fontSize: 14,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w600,
color: isDark
? Colors.white
: const Color.fromARGB(221, 58, 58, 58),
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
const SizedBox(width: 6), const SizedBox(width: 8),
StatusChip(status: status), StatusChip(status: status),
], ],
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Row(
children: [
Text( Text(
'${batch.currency ?? 'USD'} ${batch.value?.toStringAsFixed(2) ?? ''}', '${batch.currency ?? 'USD'} ${batch.value?.toStringAsFixed(2) ?? ''}',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 16,
color: Colors.grey.shade600, fontWeight: FontWeight.w700,
color: isDark ? Colors.white : Colors.black87,
), ),
), ),
Row( if (parsedDate != null) ...[
mainAxisAlignment: MainAxisAlignment.spaceBetween, Text(
children: [ '',
if (parsedDate != null) style: TextStyle(
Padding( fontSize: 11,
padding: const EdgeInsets.only(top: 6), color: isDark
child: Text( ? Colors.white24
: Colors.grey.shade400,
),
),
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,
), ),
), ),
],
const Spacer(),
if (batch.count != null) if (batch.count != null)
Padding( Row(
padding: const EdgeInsets.only(top: 4),
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,
), ),
], ],
), ),
),
], ],
), ),
], ],
@@ -1238,20 +1279,25 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
), ),
), ),
), ),
),
); );
} }
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,27 +435,43 @@ 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(
color: inSelectMode && isSelected
? Theme.of(context).colorScheme.primary
: Colors.black12.withAlpha(30),
width: inSelectMode && isSelected ? 2 : 1,
),
),
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(16),
onTap: inSelectMode onTap: inSelectMode
? () => controller.toggleGroupSelection(group.id!) ? () => controller.toggleGroupSelection(group.id!)
: () => context.push('/groups/${group.id}', extra: group), : () => context.push('/groups/${group.id}', extra: group),
child: Padding( child: Container(
padding: const EdgeInsets.all(16), 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),
),
],
),
child: Row( child: Row(
children: [ children: [
if (inSelectMode) if (inSelectMode)
@@ -466,33 +482,39 @@ class _GroupsScreenState extends State<GroupsScreen> {
? Icons.check_box ? Icons.check_box
: Icons.check_box_outline_blank, : Icons.check_box_outline_blank,
color: isSelected color: isSelected
? Theme.of(context).colorScheme.primary ? theme.colorScheme.primary
: Colors.grey.shade400, : Colors.grey.shade400,
), ),
), ),
CircleAvatar( Container(
backgroundColor: Theme.of( width: 44,
context, height: 44,
).colorScheme.primary.withAlpha(30), decoration: BoxDecoration(
color: theme.colorScheme.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text( child: Text(
_getGroupAbbreviation(group.name), _getGroupAbbreviation(group.name),
style: TextStyle( style: TextStyle(
color: Theme.of(context).colorScheme.primary, color: theme.colorScheme.primary,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontSize: 16, fontSize: 16,
), ),
), ),
), ),
const SizedBox(width: 16), ),
const SizedBox(width: 14),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
group.name, group.name,
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: isDark ? Colors.white : Colors.black87,
), ),
), ),
if (group.description != null && if (group.description != null &&
@@ -502,8 +524,11 @@ class _GroupsScreenState extends State<GroupsScreen> {
child: Text( child: Text(
group.description!, group.description!,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 12,
color: Colors.grey.shade600, fontWeight: FontWeight.w400,
color: isDark
? Colors.white54
: Colors.grey.shade500,
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -518,6 +543,7 @@ class _GroupsScreenState extends State<GroupsScreen> {
), ),
), ),
), ),
),
); );
} }