Files
CityCards_Customer_Flutter/lib/home/widgets/attractions_list.dart
2026-02-26 15:54:57 +05:30

233 lines
8.2 KiB
Dart

import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../attraction_details/views/attraction_details_view.dart';
import '../model/home_model.dart';
class AttractionsListView extends StatefulWidget {
final List<Attraction> attractions;
const AttractionsListView({super.key, required this.attractions});
@override
State<AttractionsListView> createState() => _AttractionsListViewState();
}
class _AttractionsListViewState extends State<AttractionsListView> {
final ScrollController _scrollController = ScrollController();
double _scrollProgress = 0.0;
@override
void initState() {
super.initState();
_scrollController.addListener(_updateScrollProgress);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void _updateScrollProgress() {
if (!_scrollController.hasClients ||
_scrollController.position.maxScrollExtent == 0) return;
setState(() {
_scrollProgress = (_scrollController.offset /
_scrollController.position.maxScrollExtent)
.clamp(0.0, 1.0);
});
}
String? _getCoverImage(Attraction attraction) {
if (attraction.attractionGalleries == null ||
attraction.attractionGalleries!.isEmpty) {
return null;
}
final coverImage = attraction.attractionGalleries!.firstWhere(
(gallery) => gallery.isCoverImage == true,
orElse: () => attraction.attractionGalleries!.first,
);
return coverImage.filePathUrl;
}
@override
Widget build(BuildContext context) {
if (widget.attractions.isEmpty) {
return Center(
child: Padding(
padding: EdgeInsets.all(20.w),
child: Text(
'No attractions available',
style: TextStyle(fontSize: 16.sp, color: Colors.grey),
),
),
);
}
return Column(
children: [
SizedBox(
height: 240.h,
child: ListView.builder(
controller: _scrollController,
scrollDirection: Axis.horizontal,
padding: EdgeInsets.only(right: 16.w),
itemCount: widget.attractions.length,
itemBuilder: (context, index) {
final attraction = widget.attractions[index];
final imageUrl = _getCoverImage(attraction);
return InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
AttractionDetailsView(attractionId: attraction.id),
),
);
},
child: Container(
alignment: Alignment.center,
margin: EdgeInsets.only(right: 16.w),
padding: EdgeInsets.all(4.r),
decoration: BoxDecoration(
border: Border.all(
color: const Color(0xFFF95F62).withOpacity(0.24),
),
borderRadius: BorderRadius.circular(16.r),
),
child: Container(
height: 232.h,
width: 161.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.r),
color: Colors.grey[300],
),
child: Stack(
children: [
// Image
if (imageUrl != null)
ClipRRect(
borderRadius: BorderRadius.circular(16.r),
child: CachedNetworkImage(
imageUrl: imageUrl,
height: 232.h,
width: 161.w,
fit: BoxFit.cover,
memCacheWidth: 400,
memCacheHeight: 600,
placeholder: (context, url) => const Center(
child: CircularProgressIndicator(color: Color(0xffF95F62)),
),
errorWidget: (context, url, error) => _buildPlaceholder(),
),
)
else
_buildPlaceholder(),
// Title + Description Overlay
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 12.h,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(16.r),
bottomRight: Radius.circular(16.r),
),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.black.withOpacity(0.7),
],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
attraction.title ?? 'Untitled',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: 14.sp,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4.h),
Text(
attraction.description ?? '',
textAlign: TextAlign.left,
style: GoogleFonts.poppins(
color:
Colors.white.withOpacity(0.8),
fontSize: 12.sp,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
),
],
),
),
),
);
},
),
),
SizedBox(height: 20.h),
Align(
alignment: Alignment.center,
child: SizedBox(
width: 200.w,
child: ClipRRect(
borderRadius: BorderRadius.circular(10.r),
child: LinearProgressIndicator(
value: _scrollProgress,
minHeight: 6.h,
backgroundColor: const Color(0xffFEE7E7),
color: const Color(0xffF95F62),
),
),
),
),
],
);
}
Widget _buildPlaceholder() {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.r),
color: Colors.grey[300],
),
child: Center(
child: Icon(
Icons.image_outlined,
size: 50.sp,
color: Colors.grey,
),
),
);
}
}