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

299 lines
12 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.watch<MainPageState>();
// بنرِ فولادینفو از content-service (دسته‌ی «تصویر») می‌آید.
final banners = state.fooladInfoBanners;
final hasTopBanner = widget.isFirst && state.topBanner != null;
if (banners.isEmpty && !hasTopBanner) {
return const SizedBox.shrink();
}
return Column(
children: [
if (hasTopBanner) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: AspectRatio(
aspectRatio: 16 / 9,
child: SkeletonImage(
imageUrl: state.topBanner!.image,
borderRadius: BorderRadius.circular(16),
),
),
),
),
const SizedBox(height: 12),
],
// وقتی فقط ۱ بنر داریم، dots و thumbnails navigation معنا ندارد و
// پنهان می‌شوند؛ فقط همان یک تصویر نمایش داده می‌شود.
if (banners.length == 1) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: GestureDetector(
onTap: () => banners.first.link == null ||
banners.first.link!.isEmpty
? ActionSheetUtils(context).openInteractiveViewer(
context, banners.first.image, false)
: launchUrlString(banners.first.link!,
mode: LaunchMode.inAppWebView),
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: AspectRatio(
aspectRatio: 16 / 9,
child: SkeletonImage(
imageUrl: banners.first.image,
borderRadius: BorderRadius.circular(16),
),
),
),
),
),
] else if (banners.length > 1) ...[
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,
),
],
),
],
],
);
}
}