299 lines
12 KiB
Dart
299 lines
12 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.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,
|
||
),
|
||
],
|
||
),
|
||
],
|
||
],
|
||
);
|
||
}
|
||
}
|