import 'package:flutter/material.dart'; import 'package:wisata_app/models/destination.dart'; class DestinationCardWidget extends StatefulWidget { const DestinationCardWidget({ super.key, required this.destination, required this.onTap, }); final Destination destination; final VoidCallback onTap; @override State createState() => _DestinationCardWidgetState(); } class _DestinationCardWidgetState extends State with SingleTickerProviderStateMixin { late final AnimationController _controller; late final Animation _scaleAnimation; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 160), reverseDuration: const Duration(milliseconds: 180), vsync: this, ); _scaleAnimation = Tween(begin: 1, end: 0.975).animate( CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic), ); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final destination = widget.destination; final categoryColor = _categoryColor(destination.kategori); return GestureDetector( onTapDown: (_) => _controller.forward(), onTapCancel: _controller.reverse, onTapUp: (_) { _controller.reverse(); widget.onTap(); }, child: ScaleTransition( scale: _scaleAnimation, child: DecoratedBox( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(22), boxShadow: [ BoxShadow( color: const Color(0xFF0B1F33).withValues(alpha: 0.10), blurRadius: 22, offset: const Offset(0, 12), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(22), child: Material( color: Colors.white, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 6, child: Stack( fit: StackFit.expand, children: [ Image.network( destination.imagePath, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { debugPrint( 'Gagal memuat gambar wisata: ${destination.imagePath} | $error', ); return _ImageFallback( label: destination.nama, imagePath: destination.imagePath, color: categoryColor, ); }, ), const DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color(0x22000000), Color(0x08000000), Color(0xAA0B1F33), ], ), ), ), Positioned( left: 12, right: 12, bottom: 12, child: Text( destination.nama, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Colors.white, fontSize: 17, fontWeight: FontWeight.w900, height: 1.08, ), ), ), Positioned( top: 12, left: 12, child: _Pill( backgroundColor: categoryColor, foregroundColor: Colors.white, child: Text(_categoryLabel(destination.kategori)), ), ), Positioned( top: 12, right: 12, child: _Pill( backgroundColor: Colors.white.withValues(alpha: 0.94), foregroundColor: const Color(0xFF8B6F47), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( '${destination.rating.toStringAsFixed(1)} ⭐', ), ], ), ), ), ], ), ), Expanded( flex: 5, child: Padding( padding: const EdgeInsets.fromLTRB(13, 12, 13, 13), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon( Icons.location_on_rounded, color: Color(0xFF0F4C81), size: 16, ), const SizedBox(width: 4), Expanded( child: Text( destination.lokasi, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Color(0xFF4B6475), fontSize: 11.5, fontWeight: FontWeight.w700, ), ), ), ], ), const Spacer(), SizedBox( width: double.infinity, height: 38, child: FilledButton( onPressed: widget.onTap, style: FilledButton.styleFrom( backgroundColor: const Color(0xFF0F4C81), foregroundColor: Colors.white, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), padding: EdgeInsets.zero, ), child: const Text( 'Lihat Detail', style: TextStyle( fontSize: 12.5, fontWeight: FontWeight.w800, ), ), ), ), ], ), ), ), ], ), ), ), ), ), ); } Color _categoryColor(String category) { switch (category.toLowerCase()) { case 'gunung': return const Color(0xFF8B6F47); case 'danau': return const Color(0xFF2F80C0); case 'pantai': return const Color(0xFF0EA5E9); case 'air terjun': return const Color(0xFF14B8C4); default: return const Color(0xFF0F4C81); } } String _categoryLabel(String category) { switch (category.toLowerCase()) { case 'gunung': return 'Gunung'; case 'danau': return 'Danau'; case 'pantai': return 'Pantai'; case 'air terjun': return 'Air Terjun'; default: return 'Wisata'; } } } class _Pill extends StatelessWidget { const _Pill({ required this.backgroundColor, required this.foregroundColor, required this.child, }); final Color backgroundColor; final Color foregroundColor; final Widget child; @override Widget build(BuildContext context) { return DefaultTextStyle( style: TextStyle( color: foregroundColor, fontSize: 11, fontWeight: FontWeight.w900, ), child: IconTheme( data: IconThemeData(color: foregroundColor), child: DecoratedBox( decoration: BoxDecoration( color: backgroundColor, borderRadius: BorderRadius.circular(999), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 6), child: child, ), ), ), ); } } class _ImageFallback extends StatelessWidget { const _ImageFallback({ required this.label, required this.imagePath, required this.color, }); final String label; final String imagePath; final Color color; @override Widget build(BuildContext context) { return DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ color, const Color(0xFF0F4C81), const Color(0xFF2F80C0), ], ), ), child: Center( child: Padding( padding: const EdgeInsets.all(14), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.broken_image_rounded, color: Colors.white.withValues(alpha: 0.92), size: 46, semanticLabel: label, ), const SizedBox(height: 8), Text( 'Gambar gagal dimuat\n$imagePath', textAlign: TextAlign.center, maxLines: 3, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Colors.white, fontSize: 11, fontWeight: FontWeight.w800, ), ), ], ), ), ), ); } }