didvan-app/lib/views/home/media/widgets/media_banner_with_thumbnail...

251 lines
9.9 KiB
Dart

import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/home/main/main_page_state.dart';
import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:carousel_slider/carousel_slider.dart';
class MediaBannerWithThumbnails extends StatefulWidget {
final bool isFirst;
const MediaBannerWithThumbnails({super.key, required this.isFirst});
@override
State<MediaBannerWithThumbnails> createState() => _MediaBannerWithThumbnailsState();
}
class _MediaBannerWithThumbnailsState extends State<MediaBannerWithThumbnails> {
int _currentIndex = 0;
int _thumbnailStartIndex = 0;
final CarouselSliderController _carouselController = CarouselSliderController();
@override
Widget build(BuildContext context) {
final state = context.read<MainPageState>();
final banners = state.content!.banners[widget.isFirst ? 0 : 1];
return Column(
children: [
// Top Banner - only show on first section
if (widget.isFirst && state.topBanner != null) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: SkeletonImage(
imageUrl: state.topBanner!.image,
borderRadius: BorderRadius.circular(16),
),
),
),
const SizedBox(height: 12),
],
CarouselSlider.builder(
carouselController: _carouselController,
itemCount: banners.length,
itemBuilder: (context, index, realIndex) {
final item = banners[index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: GestureDetector(
onTap: () => item.link == null || item.link!.isEmpty
? ActionSheetUtils(context)
.openInteractiveViewer(context, item.image, false)
: launchUrlString(item.link!, mode: LaunchMode.inAppWebView),
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: SkeletonImage(
imageUrl: item.image,
borderRadius: BorderRadius.circular(16),
),
),
),
);
},
options: CarouselOptions(
height: (MediaQuery.of(context).size.width - 8) * 9.0 / 16.0,
viewportFraction: 1.0,
enableInfiniteScroll: true,
autoPlay: true,
autoPlayInterval: const Duration(seconds: 5),
onPageChanged: (index, reason) {
setState(() {
_currentIndex = index;
if (index >= _thumbnailStartIndex + 3) {
_thumbnailStartIndex = index - 2;
} else if (index < _thumbnailStartIndex) {
_thumbnailStartIndex = index;
}
});
},
),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: banners.asMap().entries.map((entry) {
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: _currentIndex == entry.key ? 10 : 5,
height: _currentIndex == entry.key ? 10 : 5,
margin: const EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(300),
boxShadow: [
BoxShadow(
color: _currentIndex == entry.key
? const Color.fromARGB(100, 0, 126, 167)
: const Color.fromARGB(0, 0, 0, 0),
spreadRadius: 2.5,
offset: const Offset(0, 0),
),
],
color: _currentIndex == entry.key
? const Color.fromARGB(255, 0, 126, 167)
: const Color.fromARGB(255, 200, 200, 200),
),
);
}).toList(),
),
const SizedBox(height: 16),
Row(
children: [
IconButton(
icon: SvgPicture.asset(
'lib/assets/icons/arrow-right.svg',
width: 24,
height: 24,
colorFilter: ColorFilter.mode(
_thumbnailStartIndex > 0
? const Color.fromARGB(255, 0, 126, 167)
: const Color.fromARGB(255, 200, 200, 200),
BlendMode.srcIn,
),
),
onPressed: _thumbnailStartIndex > 0
? () {
setState(() {
_thumbnailStartIndex = (_thumbnailStartIndex - 1).clamp(0, banners.length - 3);
});
}
: null,
),
Expanded(
child: SizedBox(
height: 60,
width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(3, (index) {
if (_thumbnailStartIndex + index >= banners.length) {
return const SizedBox(width: 0);
}
final actualIndex = _thumbnailStartIndex + index;
final item = banners[actualIndex];
final isSelected = actualIndex == _currentIndex;
return Expanded(
child: GestureDetector(
onTap: () {
if (actualIndex == _currentIndex) return;
_carouselController.animateToPage(
actualIndex,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
setState(() {
_currentIndex = actualIndex;
if (actualIndex < _thumbnailStartIndex) {
_thumbnailStartIndex = actualIndex;
} else if (actualIndex >= _thumbnailStartIndex + 3) {
_thumbnailStartIndex = (actualIndex - 2).clamp(0, banners.length - 3);
}
});
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
margin: const EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isSelected
? const Color.fromARGB(255, 0, 126, 167)
: Colors.transparent,
width: 2,
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: AnimatedOpacity(
duration: const Duration(milliseconds: 200),
opacity: isSelected ? 1.0 : 0.6,
child: ColorFiltered(
colorFilter: isSelected
? const ColorFilter.mode(
Colors.transparent,
BlendMode.multiply,
)
: const ColorFilter.matrix(<double>[
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0, 0, 0, 1, 0,
]),
child: AspectRatio(
aspectRatio: 19 / 13,
child: SkeletonImage(
imageUrl: item.image,
borderRadius: BorderRadius.circular(8),
),
),
),
),
),
),
),
);
}),
),
),
),
// فلش چپ (برای رفتن به جلو)
IconButton(
icon: SvgPicture.asset(
'lib/assets/icons/arrow-left.svg',
width: 24,
height: 24,
colorFilter: ColorFilter.mode(
_thumbnailStartIndex < banners.length - 3
? const Color.fromARGB(255, 0, 126, 167)
: const Color.fromARGB(255, 200, 200, 200),
BlendMode.srcIn,
),
),
onPressed: _thumbnailStartIndex < banners.length - 3
? () {
setState(() {
_thumbnailStartIndex = (_thumbnailStartIndex + 1).clamp(0, banners.length - 3);
});
}
: null,
),
],
),
],
);
}
}