import 'dart:io'; import 'package:flutter/material.dart'; import 'package:carousel_slider/carousel_slider.dart'; import '../models/sake_item.dart'; import '../screens/sake_detail_screen.dart'; /// 3D風カルーセルウィジェット (v2: carousel_slider版) /// 安定した無限スクロールと拡大アニメーションを提供 class Sake3DCarousel extends StatefulWidget { final List items; final double height; const Sake3DCarousel({ super.key, required this.items, this.height = 240, // 少し高さを確保 }); @override State createState() => _Sake3DCarouselState(); } class _Sake3DCarouselState extends State { int _currentIndex = 0; @override Widget build(BuildContext context) { if (widget.items.isEmpty) { return SizedBox( height: widget.height, child: const Center( child: Text('関連する日本酒がありません'), ), ); } return Column( children: [ CarouselSlider.builder( itemCount: widget.items.length, itemBuilder: (context, index, realIndex) { final item = widget.items[index]; return _buildCarouselItem(item); }, options: CarouselOptions( height: 200, aspectRatio: 16/9, viewportFraction: 0.6, // 隣が見える幅 initialPage: 0, enableInfiniteScroll: widget.items.length > 2, // 3枚以上で無限 reverse: false, autoPlay: false, enlargeCenterPage: true, // 中央を拡大 (ぽにょぽにょ感) enlargeFactor: 0.3, scrollDirection: Axis.horizontal, onPageChanged: (index, reason) { setState(() { _currentIndex = index; }); }, ), ), const SizedBox(height: 12), // インジケーター Row( mainAxisAlignment: MainAxisAlignment.center, children: widget.items.asMap().entries.map((entry) { return Container( width: 8.0, height: 8.0, margin: const EdgeInsets.symmetric(horizontal: 4.0), decoration: BoxDecoration( shape: BoxShape.circle, color: (Theme.of(context).brightness == Brightness.dark ? Colors.white : Theme.of(context).primaryColor) .withValues(alpha: _currentIndex == entry.key ? 0.9 : 0.2), ), ); }).toList(), ), ], ); } Widget _buildCarouselItem(SakeItem item) { return GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (_) => SakeDetailScreen(sake: item), ), ); }, child: Card( elevation: 6, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), clipBehavior: Clip.antiAlias, child: Stack( fit: StackFit.expand, children: [ // 背景画像 Hero( tag: item.id, child: item.displayData.imagePaths.isNotEmpty ? Image.file( File(item.displayData.imagePaths.first), fit: BoxFit.cover, ) : Container( color: Colors.grey[300], child: const Icon(Icons.image, size: 50, color: Colors.grey), ), ), // グラデーション Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.black.withValues(alpha: 0.8), ], ), ), ), // テキスト情報 Positioned( bottom: 12, left: 12, right: 12, child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( item.displayData.displayName, maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14, ), ), Text( item.displayData.displayPrefecture, style: const TextStyle( color: Colors.white70, fontSize: 10, ), ), ], ), ), ], ), ), ); } }