201 lines
8.5 KiB
Dart
201 lines
8.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'dart:io';
|
|
import '../../models/sake_item.dart';
|
|
import '../../providers/menu_providers.dart';
|
|
import '../../screens/sake_detail_screen.dart';
|
|
import '../../theme/app_theme.dart';
|
|
import 'package:lucide_icons/lucide_icons.dart';
|
|
// Haptic via InkWell? No, explicit HapticFeedback used generally.
|
|
|
|
class SakeListItem extends ConsumerWidget {
|
|
final SakeItem sake;
|
|
final bool isMenuMode;
|
|
final int? index; // For ReorderableDragStartListener
|
|
|
|
const SakeListItem({
|
|
super.key,
|
|
required this.sake,
|
|
required this.isMenuMode,
|
|
this.index,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final isSelected = ref.watch(selectedMenuSakeIdsProvider).contains(sake.id);
|
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
|
|
// Adaptive selection color
|
|
final selectedColor = isDark ? Colors.orange.withValues(alpha: 0.3) : Colors.orange.shade50;
|
|
|
|
return Card(
|
|
clipBehavior: Clip.antiAlias,
|
|
elevation: 1, // Slight elevation
|
|
color: isMenuMode && isSelected ? selectedColor : null,
|
|
shape: isMenuMode && isSelected
|
|
? RoundedRectangleBorder(side: const BorderSide(color: Colors.orange, width: 2), borderRadius: BorderRadius.circular(12))
|
|
: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
child: InkWell(
|
|
onTap: () {
|
|
if (isMenuMode) {
|
|
ref.read(selectedMenuSakeIdsProvider.notifier).toggle(sake.id);
|
|
return;
|
|
}
|
|
Navigator.of(context).push(
|
|
MaterialPageRoute(
|
|
builder: (context) => SakeDetailScreen(sake: sake),
|
|
),
|
|
);
|
|
},
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Selection Checkbox for ListView
|
|
if (isMenuMode)
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: AppTheme.spacingXLarge, horizontal: AppTheme.spacingMedium),
|
|
child: Icon(
|
|
isSelected ? Icons.check_circle : Icons.check_circle_outline,
|
|
color: isSelected ? AppTheme.posimaiBlue : Colors.grey[300],
|
|
size: 28,
|
|
),
|
|
),
|
|
|
|
SizedBox(
|
|
width: 100,
|
|
height: 100,
|
|
child: Hero(
|
|
tag: sake.id,
|
|
child: sake.displayData.imagePaths.isNotEmpty
|
|
? Image.file(
|
|
File(sake.displayData.imagePaths.first),
|
|
fit: BoxFit.cover,
|
|
errorBuilder: (context, error, stackTrace) {
|
|
return Container(
|
|
color: isDark ? Colors.grey[800] : Colors.grey[300],
|
|
child: Center(
|
|
child: Icon(
|
|
LucideIcons.imageOff,
|
|
color: isDark ? Colors.grey[600] : Colors.grey[500],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
)
|
|
: (sake.itemType == ItemType.set
|
|
? Image.asset(
|
|
'assets/images/set_placeholder.png',
|
|
fit: BoxFit.cover,
|
|
)
|
|
: Container(
|
|
color: isDark ? Colors.grey[800] : Colors.grey[300],
|
|
child: Center(
|
|
child: Icon(
|
|
LucideIcons.image,
|
|
size: 40,
|
|
color: isDark ? Colors.grey[600] : Colors.grey[500],
|
|
),
|
|
),
|
|
)),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(AppTheme.spacingMedium),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: Row(
|
|
children: [
|
|
// セット商品バッジ
|
|
if (sake.itemType == ItemType.set)
|
|
Container(
|
|
margin: const EdgeInsets.only(right: 6),
|
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: Colors.orange,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
child: const Text(
|
|
'セット',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
sake.displayData.name,
|
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: (isMenuMode && isSelected && !isDark) ? Colors.brown[900] : null,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (sake.userData.isFavorite && !isMenuMode)
|
|
const Icon(Icons.favorite, color: Colors.pink, size: 16),
|
|
],
|
|
),
|
|
const SizedBox(height: AppTheme.spacingTiny),
|
|
// Brand / Prefecture (セット商品では非表示)
|
|
if (sake.itemType != ItemType.set &&
|
|
(sake.displayData.brewery.isNotEmpty || sake.displayData.prefecture.isNotEmpty))
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
'${sake.displayData.brewery} / ${sake.displayData.prefecture}',
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
// セット商品の説明文表示
|
|
if (sake.itemType == ItemType.set && sake.displayData.catchCopy != null)
|
|
Text(
|
|
sake.displayData.catchCopy!,
|
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
fontStyle: FontStyle.italic,
|
|
),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
if (!isMenuMode && sake.hiddenSpecs.flavorTags.isNotEmpty) ...[
|
|
const SizedBox(height: AppTheme.spacingSmall),
|
|
Wrap(
|
|
spacing: 4,
|
|
runSpacing: 4,
|
|
children: sake.hiddenSpecs.flavorTags.take(3).map((tag) => Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: isDark ? Colors.grey[800] : Colors.grey[200],
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
child: Text(
|
|
tag,
|
|
style: TextStyle(fontSize: 10, color: isDark ? Colors.grey[300] : Colors.grey[800]),
|
|
),
|
|
)).toList(),
|
|
)
|
|
]
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|