Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42046b4e87 |
@@ -1,12 +1,12 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import '../database/database.dart' as db; // Import database with prefix 'db'
|
import '../database/database.dart' as db;
|
||||||
import '../models/transaction_record.dart';
|
import '../models/transaction_record.dart';
|
||||||
import '../utils/category_utils.dart'; // Import category utils
|
import '../utils/category_utils.dart';
|
||||||
|
|
||||||
class TransactionListItem extends StatelessWidget {
|
class TransactionListItem extends StatelessWidget {
|
||||||
final TransactionRecord transaction; // Use the Drift-generated Transaction class
|
final TransactionRecord transaction;
|
||||||
final Animation<double> animation; // Keep animation for potential future use
|
final Animation<double> animation;
|
||||||
|
|
||||||
const TransactionListItem({
|
const TransactionListItem({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -19,10 +19,10 @@ class TransactionListItem extends StatelessWidget {
|
|||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
// Get category details (icon, color) using the utility
|
// Get category details
|
||||||
final categoryDetails = CategoryUtils.getCategoryDetails(transaction.categoryName);
|
final categoryDetails = CategoryUtils.getCategoryDetails(transaction.categoryName);
|
||||||
|
|
||||||
// Format the date and time using intl package
|
// Format dates
|
||||||
final dateFormatter = DateFormat.MMMd(); // e.g., Sep 10
|
final dateFormatter = DateFormat.MMMd(); // e.g., Sep 10
|
||||||
final timeFormatter = DateFormat.jm(); // e.g., 5:08 PM
|
final timeFormatter = DateFormat.jm(); // e.g., 5:08 PM
|
||||||
|
|
||||||
@@ -34,87 +34,146 @@ class TransactionListItem extends StatelessWidget {
|
|||||||
|
|
||||||
String displayDate;
|
String displayDate;
|
||||||
if (transactionDay == today) {
|
if (transactionDay == today) {
|
||||||
displayDate = 'Today, ${timeFormatter.format(transaction.date)}';
|
displayDate = 'Today';
|
||||||
} else if (transactionDay == yesterday) {
|
} else if (transactionDay == yesterday) {
|
||||||
displayDate = 'Yesterday, ${timeFormatter.format(transaction.date)}';
|
displayDate = 'Yesterday';
|
||||||
} else {
|
} else {
|
||||||
// Format for other dates (e.g., "Sep 10, 5:08 PM")
|
displayDate = dateFormatter.format(transaction.date);
|
||||||
displayDate = '${dateFormatter.format(transaction.date)}, ${timeFormatter.format(transaction.date)}';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String displayTime = timeFormatter.format(transaction.date);
|
||||||
|
|
||||||
// Determine text colors based on theme
|
// Determine text colors based on theme
|
||||||
Color primaryTextColor = theme.textTheme.bodyLarge?.color ?? (isDark ? Colors.white : Colors.black87);
|
Color primaryTextColor = theme.textTheme.bodyLarge?.color ?? (isDark ? Colors.white : Colors.black87);
|
||||||
Color secondaryTextColor = theme.textTheme.bodyMedium?.color ?? (isDark ? Colors.white70 : Colors.grey.shade600);
|
Color secondaryTextColor = theme.textTheme.bodyMedium?.color ?? (isDark ? Colors.white70 : Colors.grey.shade600);
|
||||||
Color amountColor = isDark ? Colors.red.shade200 : Colors.red.shade700; // Expense color
|
Color amountColor = isDark ? Colors.red.shade200 : Colors.red.shade700;
|
||||||
|
|
||||||
// Use FadeTransition for item appearance (works with ListView.builder)
|
// Use FadeTransition for item appearance
|
||||||
return FadeTransition(
|
return FadeTransition(
|
||||||
opacity: animation, // Apply fade animation
|
opacity: animation,
|
||||||
child: InkWell( // Make the item tappable
|
child: Card(
|
||||||
onTap: () {
|
margin: const EdgeInsets.only(bottom: 6, top: 2),
|
||||||
// TODO: Implement navigation to transaction details screen or edit action
|
shape: RoundedRectangleBorder(
|
||||||
print('Tapped transaction: ${transaction.id}');
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
},
|
),
|
||||||
child: Padding(
|
child: SizedBox(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), // Consistent padding
|
height: 65, // Fixed height for consistency
|
||||||
child: Row(
|
child: InkWell(
|
||||||
children: [
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
// Icon container with category color/icon
|
onTap: () {
|
||||||
Container(
|
// TODO: Implement navigation to transaction details
|
||||||
padding: const EdgeInsets.all(10),
|
print('Tapped transaction: ${transaction.id}');
|
||||||
decoration: BoxDecoration(
|
},
|
||||||
color: categoryDetails.colorCode.withOpacity(isDark ? 0.25 : 0.15), // Use category color with opacity
|
child: Padding(
|
||||||
borderRadius: BorderRadius.circular(12), // Rounded corners
|
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
|
||||||
),
|
child: Row(
|
||||||
child: Icon(
|
children: [
|
||||||
categoryDetails.iconCode, // Use category icon
|
// Leading icon
|
||||||
color: categoryDetails.colorCode, // Use category color for icon
|
Container(
|
||||||
size: 20, // Icon size
|
width: 36,
|
||||||
),
|
height: 36,
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
const SizedBox(width: 12), // Spacing
|
color: categoryDetails.colorCode.withOpacity(isDark ? 0.2 : 0.15),
|
||||||
|
shape: BoxShape.circle,
|
||||||
// Transaction details (Merchant/Category and Date/Time)
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Display Merchant if available, otherwise Category Name
|
|
||||||
Text(
|
|
||||||
transaction.merchant.isNotEmpty ? transaction.merchant : transaction.categoryName,
|
|
||||||
style: theme.textTheme.bodyLarge?.copyWith(
|
|
||||||
fontWeight: FontWeight.w500, // Medium weight for primary text
|
|
||||||
color: primaryTextColor,
|
|
||||||
),
|
|
||||||
maxLines: 1, // Prevent wrapping
|
|
||||||
overflow: TextOverflow.ellipsis, // Handle long text
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4), // Spacing between lines
|
child: Icon(
|
||||||
// Display formatted date/time
|
categoryDetails.iconCode,
|
||||||
Text(
|
color: categoryDetails.colorCode.withOpacity(isDark ? 0.9 : 1.0),
|
||||||
displayDate,
|
size: 16
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
|
||||||
color: secondaryTextColor, // Lighter color for secondary text
|
|
||||||
fontSize: 12, // Smaller font size
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12), // Spacing before amount
|
|
||||||
|
|
||||||
// Transaction Amount
|
// Content
|
||||||
Text(
|
Expanded(
|
||||||
// Format amount as currency (negative for expense)
|
child: Padding(
|
||||||
NumberFormat.currency(symbol: '-\$', decimalDigits: 2).format(transaction.amount),
|
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||||
style: theme.textTheme.bodyLarge?.copyWith(
|
child: Column(
|
||||||
fontWeight: FontWeight.w600, // Bold weight for amount
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
color: amountColor, // Use expense color
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
),
|
children: [
|
||||||
|
// Category and merchant on same line
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
transaction.categoryName,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 13
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
'·', // Bullet separator
|
||||||
|
style: TextStyle(
|
||||||
|
color: isDark ? Colors.grey.shade400 : Colors.grey.shade700,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
transaction.merchant,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: isDark ? Colors.grey.shade400 : Colors.grey.shade700,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
|
||||||
|
// Date and time
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.access_time,
|
||||||
|
size: 10,
|
||||||
|
color: isDark ? Colors.grey.shade400 : Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
displayDate,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
color: isDark ? Colors.grey.shade400 : Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Amount and Time
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'- \$${transaction.amount.toStringAsFixed(2)}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: isDark ? Colors.redAccent.shade100 : Colors.red.shade700,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
displayTime,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
color: isDark ? Colors.grey.shade400 : Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user