173 lines
6 KiB
Dart
173 lines
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.
|