import 'dart:io'; import 'package:flutter/material.dart'; import 'package:carousel_slider/carousel_slider.dart'; import '../services/sake_recommendation_service.dart'; import '../screens/sake_detail_screen.dart'; import 'package:lucide_icons/lucide_icons.dart'; /// 3D風カルーセルウィジェット(推薦理由付き) /// 円形配置で左右対称、奥行き感のあるローリングスライダー class Sake3DCarouselWithReason extends StatefulWidget { final List recommendations; final double height; const Sake3DCarouselWithReason({ super.key, required this.recommendations, this.height = 240, }); @override State createState() => _Sake3DCarouselWithReasonState(); } class _Sake3DCarouselWithReasonState extends State { int _currentIndex = 0; @override Widget build(BuildContext context) { if (widget.recommendations.isEmpty) { return SizedBox( height: widget.height, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(LucideIcons.info, color: Colors.grey[400], size: 32), const SizedBox(height: 8), Text( '関連する日本酒を追加すると\nおすすめが表示されます', textAlign: TextAlign.center, style: TextStyle(color: Colors.grey[600], fontSize: 12), ), ], ), ), ); } return Column( children: [ CarouselSlider.builder( itemCount: widget.recommendations.length, options: CarouselOptions( height: widget.height - 40, enlargeCenterPage: true, enlargeFactor: 0.3, // 中央の拡大率 viewportFraction: 0.45, // 左右の見える範囲(広くすると両側が見える) enableInfiniteScroll: widget.recommendations.length > 2, // 3枚以上で無限ループ autoPlay: false, onPageChanged: (index, reason) { setState(() { _currentIndex = index; }); }, ), itemBuilder: (context, index, realIndex) { final rec = widget.recommendations[index]; final isCurrent = index == _currentIndex; return _buildCarouselCard(rec, isCurrent); }, ), const SizedBox(height: 12), // インジケーター Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate( widget.recommendations.length, (index) => Container( width: 8, height: 8, margin: const EdgeInsets.symmetric(horizontal: 4), decoration: BoxDecoration( shape: BoxShape.circle, color: _currentIndex == index ? Theme.of(context).colorScheme.secondary : Colors.grey[400], ), ), ), ), ], ); } Widget _buildCarouselCard(RecommendedSake rec, bool isCurrent) { return GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (_) => SakeDetailScreen(sake: rec.item), ), ); }, child: AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.easeOut, margin: EdgeInsets.symmetric( vertical: isCurrent ? 0 : 12, horizontal: 8, ), child: Card( elevation: isCurrent ? 12 : 6, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), clipBehavior: Clip.antiAlias, child: Stack( fit: StackFit.expand, children: [ // 背景画像 rec.item.displayData.imagePaths.isNotEmpty ? Image.file( File(rec.item.displayData.imagePaths.first), fit: BoxFit.cover, ) : Container( color: Colors.grey[300], child: Icon( LucideIcons.image, size: 50, color: Colors.grey[600], ), ), // グラデーションオーバーレイ Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.black.withValues(alpha: 0.7), ], ), ), ), // テキスト情報 Positioned( bottom: 16, left: 12, right: 12, child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Text( rec.item.displayData.name, maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: const TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold, shadows: [ Shadow( color: Colors.black, blurRadius: 4, ), ], ), ), const SizedBox(height: 4), Text( rec.item.displayData.prefecture, style: TextStyle( color: Colors.white.withValues(alpha: 0.9), fontSize: 11, shadows: const [ Shadow( color: Colors.black, blurRadius: 4, ), ], ), ), const SizedBox(height: 8), // 推薦理由 Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: Theme.of(context).colorScheme.secondary.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(4), ), child: Text( rec.reason, style: const TextStyle( color: Colors.white, fontSize: 10, fontWeight: FontWeight.w500, ), maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, ), ), ], ), ), // 中央のカードにインジケーター if (isCurrent) Positioned( top: 8, right: 8, child: Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: Theme.of(context).colorScheme.secondary, shape: BoxShape.circle, ), child: const Icon( Icons.star, color: Colors.white, size: 16, ), ), ), ], ), ), ), ); } }