Browser-Tab-Manager/lib/widgets/item_card.dart

173 lines
No EOL
6 KiB
Dart

import 'package:flutter/material.dart';
import '../models/tab_data.dart';
class ItemCard extends StatelessWidget {
final TabData item;
final String icon;
final VoidCallback onTap;
final VoidCallback onDelete;
final VoidCallback onTogglePin;
final bool extensionMode;
const ItemCard({
super.key,
required this.item,
required this.icon,
required this.onTap,
required this.onDelete,
required this.onTogglePin,
this.extensionMode = false,
});
@override
Widget build(BuildContext context) {
return Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 60,
color: Theme.of(context).colorScheme.primaryContainer,
child: Center(
child: item.favicon.isNotEmpty && item.favicon.startsWith('http')
? Image.network(
item.favicon,
width: 32,
height: 32,
errorBuilder: (context, error, stackTrace) {
return Text(icon, style: const TextStyle(fontSize: 32));
},
)
: Text(icon, style: const TextStyle(fontSize: 32)),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
if (item.isPinned)
Icon(
Icons.push_pin,
size: 14,
color: Theme.of(context).colorScheme.primary,
),
if (item.isPinned) const SizedBox(width: 4),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: _getTypeColor(context),
borderRadius: BorderRadius.circular(4),
),
child: Text(
item.type.toUpperCase(),
style: TextStyle(
fontSize: 10,
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
),
const SizedBox(height: 6),
Expanded(
child: Text(
item.title,
style: Theme.of(context).textTheme.titleSmall,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(height: 4),
Text(
item.url,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (item.visitCount != null) ...[
const SizedBox(height: 4),
Text(
'${item.visitCount} visits',
style: Theme.of(context).textTheme.bodySmall,
),
],
],
),
),
),
if (!extensionMode)
ButtonBar(
alignment: MainAxisAlignment.end,
buttonPadding: EdgeInsets.zero,
children: [
if (item.type == 'tab')
IconButton(
icon: Icon(
item.isPinned ? Icons.push_pin : Icons.push_pin_outlined,
size: 18,
),
onPressed: onTogglePin,
tooltip: item.isPinned ? 'Unpin' : 'Pin',
),
if (item.type != 'history')
IconButton(
icon: const Icon(Icons.delete_outline, size: 18),
onPressed: onDelete,
tooltip: 'Delete',
),
],
),
],
),
),
);
}
Color _getTypeColor(BuildContext context) {
switch (item.type) {
case 'tab':
return Colors.blue;
case 'bookmark':
return Colors.orange;
case 'history':
return Colors.purple;
default:
return Theme.of(context).colorScheme.primary;
}
}
}
// This widget displays a single tab, bookmark, or history item as a card in the grid view.
//
// It is a stateless widget that receives all its data from the parent component.
//
// The card shows a favicon or emoji icon at the top in a colored header section.
//
// Below that it displays a type badge, the item title, URL, and optional visit count.
//
// If the item is pinned, a pin icon appears next to the type badge.
//
// The card is tappable and triggers the onTap callback to open the item.
//
// Action buttons at the bottom allow pinning tabs and deleting tabs or bookmarks.
//
// These action buttons are hidden in extension mode since you cannot modify tracked tabs.
//
// History items do not show a delete button since browser history cannot be removed this way.
//
// The getTypeColor method assigns different colors to the type badge based on item type.
//
// This card design provides a clean visual representation of browser items in grid layout.