Files
velocity-pay-flutter/lib/widgets/transaction_item_widget.dart

249 lines
9.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:timeago/timeago.dart' as timeago;
/// A reusable transaction list item widget.
/// Used by both [HomeScreen] and [HistoryScreen].
class TransactionItemWidget extends StatefulWidget {
final Map<String, dynamic> transaction;
final bool enabled;
final Future<void> Function()? onRepeat;
const TransactionItemWidget({
super.key,
required this.transaction,
this.enabled = false,
this.onRepeat,
});
@override
State<TransactionItemWidget> createState() => _TransactionItemWidgetState();
}
class _TransactionItemWidgetState extends State<TransactionItemWidget> {
bool _isRepeating = false;
Future<void> _handleRepeat() async {
if (_isRepeating || widget.onRepeat == null) return;
setState(() => _isRepeating = true);
try {
await widget.onRepeat!();
} finally {
if (mounted) setState(() => _isRepeating = false);
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
final status = widget.transaction['integrationStatus'] as String?;
final isSuccess = status == 'SUCCESS';
final isPending = status == 'PENDING';
final statusIcon = isSuccess
? Icons.check_circle_rounded
: isPending
? Icons.pending_rounded
: Icons.cancel_rounded;
final statusColor = isSuccess
? const Color(0xFF10B981)
: isPending
? const Color(0xFFF59E0B)
: const Color(0xFFEF4444);
return Skeletonizer(
enabled: widget.enabled,
child: Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {
context.push("/receipt/${widget.transaction['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: isDark
? Colors.white.withValues(alpha: 0.06)
: Colors.grey.shade200,
width: 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: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Main row: provider image, bill name & date, amount
Row(
children: [
// Provider image
Container(
width: 44,
height: 44,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: theme.colorScheme.primary.withValues(
alpha: 0.1,
),
borderRadius: BorderRadius.circular(12),
),
child: Image(
image: AssetImage(
"assets/${widget.transaction['paymentProcessorImage'] ?? ''}",
),
errorBuilder: (_, __, ___) => Icon(
Icons.receipt_long_rounded,
size: 20,
color: theme.colorScheme.primary,
),
),
),
const SizedBox(width: 14),
// Bill name & date
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Flexible(
child: Text(
widget.transaction['billName'] ?? '',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: isDark
? Colors.white
: Colors.black87,
),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 8),
Icon(
statusIcon,
size: 16,
color: statusColor,
),
],
),
const SizedBox(height: 4),
Text(
widget.transaction['createdAt'] != null
? timeago.format(
DateTime.parse(
widget.transaction['createdAt'],
),
)
: '',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: isDark
? Colors.white54
: Colors.grey.shade500,
),
),
],
),
),
// Amount
Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
(widget.transaction['amount'] as num?)
?.toStringAsFixed(2) ??
'',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: isDark ? Colors.white : Colors.black87,
),
),
Text(
(widget.transaction['totalAmount'] as num?)
?.toStringAsFixed(2) ??
'',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: theme.colorScheme.primary.withValues(
alpha: 0.7,
),
),
),
],
),
],
),
// Quick actions row (only shown when onRepeat is provided)
if (widget.onRepeat != null) ...[
const SizedBox(height: 10),
Divider(
height: 1,
color: isDark
? Colors.white.withValues(alpha: 0.06)
: Colors.grey.shade200,
),
const SizedBox(height: 8),
Align(
alignment: Alignment.centerRight,
child: TextButton.icon(
onPressed: _handleRepeat,
icon: _isRepeating
? SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
color: theme.colorScheme.primary,
),
)
: const Icon(Icons.repeat_rounded, size: 16),
label: Text(
_isRepeating ? 'Repeating...' : 'Repeat',
style: const TextStyle(fontSize: 12),
),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
],
],
),
),
),
),
),
);
}
}