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

@@ -523,4 +523,4 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
final formatter = NumberFormat('#,##0.00', 'en_US');
return '\$${formatter.format(balance)}';
}
}
}

View File

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

View File

@@ -2467,7 +2467,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
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<BatchDetailScreen>
),
],
),
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<BatchDetailScreen>
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<BatchDetailScreen>
Text(
'${batch.currency ?? 'USD'} ${item.amount?.toStringAsFixed(2) ?? ''}',
style: TextStyle(
fontSize: 14,
fontSize: 16,
fontWeight: FontWeight.w700,
color: isDark ? Colors.white : Colors.black87,
),

View File

@@ -315,6 +315,8 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
}
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<GroupDetailScreen>
},
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<GroupDetailScreen>
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<GroupDetailScreen>
_buildContactLine(
Icons.email_outlined,
recipient.email!,
isDark,
),
],
],
@@ -393,42 +408,29 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
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<GroupDetailScreen>
),
),
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<GroupDetailScreen>
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,
),
),

View File

@@ -435,86 +435,112 @@ class _GroupsScreenState extends State<GroupsScreen> {
}
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),
],
),
),
),
),