redesign infography and fooladinfo

This commit is contained in:
mohamadmahdi jebeli 2025-10-22 14:50:12 +03:30
parent 84422637d9
commit 26fcda7a49
19 changed files with 688 additions and 158 deletions

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.8199 2H7.17995C5.04995 2 3.31995 3.74 3.31995 5.86V19.95C3.31995 21.75 4.60995 22.51 6.18995 21.64L11.0699 18.93C11.5899 18.64 12.4299 18.64 12.9399 18.93L17.8199 21.64C19.3999 22.52 20.6899 21.76 20.6899 19.95V5.86C20.6799 3.74 18.9499 2 16.8199 2Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 450 B

View File

@ -0,0 +1,3 @@
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.6825 15.0292L11.5725 10.1392C12.15 9.56167 12.15 8.61667 11.5725 8.03917L6.6825 3.14917" stroke="#2196F3" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 312 B

View File

@ -0,0 +1,3 @@
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5114 0H3.85862C1.737 0 0 1.737 0 3.85862V17.9531C0 19.7522 1.29034 20.5214 2.86605 19.6405L7.74206 16.9233C8.26316 16.638 9.10684 16.638 9.61554 16.9233L14.4915 19.6405C16.0797 20.509 17.37 19.7522 17.37 17.9531V3.85862C17.37 1.737 15.633 0 13.5114 0Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 384 B

View File

@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22 8.27V4.23C22 2.64 21.36 2 19.77 2H15.73C14.14 2 13.5 2.64 13.5 4.23V8.27C13.5 9.86 14.14 10.5 15.73 10.5H19.77C21.36 10.5 22 9.86 22 8.27Z" stroke="#F8F8FA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.5 8.52V3.98C10.5 2.57 9.86 2 8.27 2H4.23C2.64 2 2 2.57 2 3.98V8.51C2 9.93 2.64 10.49 4.23 10.49H8.27C9.86 10.5 10.5 9.93 10.5 8.52Z" stroke="#F8F8FA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.5 19.77V15.73C10.5 14.14 9.86 13.5 8.27 13.5H4.23C2.64 13.5 2 14.14 2 15.73V19.77C2 21.36 2.64 22 4.23 22H8.27C9.86 22 10.5 21.36 10.5 19.77Z" stroke="#F8F8FA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.5 17.5H20.5" stroke="#F8F8FA" stroke-width="1.5" stroke-linecap="round"/>
<path d="M17.5 20.5V14.5" stroke="#F8F8FA" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 987 B

View File

@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.465 15.6075C9.21 15.6975 8.79 15.6975 8.535 15.6075C6.36 14.865 1.5 11.7675 1.5 6.51745C1.5 4.19995 3.3675 2.32495 5.67 2.32495C7.035 2.32495 8.2425 2.98495 9 4.00495C9.7575 2.98495 10.9725 2.32495 12.33 2.32495C14.6325 2.32495 16.5 4.19995 16.5 6.51745C16.5 11.7675 11.64 14.865 9.465 15.6075Z" stroke="#2196F3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 477 B

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.44 3.1001C14.63 3.1001 13.01 3.9801 12 5.3301C10.99 3.9801 9.37 3.1001 7.56 3.1001C4.49 3.1001 2 5.6001 2 8.6901C2 9.8801 2.19 10.9801 2.52 12.0001C4.1 17.0001 8.97 19.9901 11.38 20.8101C11.72 20.9301 12.28 20.9301 12.62 20.8101C15.03 19.9901 19.9 17.0001 21.48 12.0001C21.81 10.9801 22 9.8801 22 8.6901C22 5.6001 19.51 3.1001 16.44 3.1001Z" fill="#292D32"/>
</svg>

After

Width:  |  Height:  |  Size: 475 B

View File

@ -0,0 +1,3 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.25 7.02V2.98C9.25 1.39 8.61 0.75 7.02 0.75H2.98C1.39 0.75 0.75 1.39 0.75 2.98V7.02C0.75 8.61 1.39 9.25 2.98 9.25H7.02C8.61 9.25 9.25 8.61 9.25 7.02Z" stroke="#F8F8FA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 350 B

View File

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.40005 6.32015L15.8901 3.49015C19.7001 2.22015 21.7701 4.30015 20.5101 8.11015L17.6801 16.6002C15.7801 22.3102 12.6601 22.3102 10.7601 16.6002L9.92005 14.0802L7.40005 13.2402C1.69005 11.3402 1.69005 8.23015 7.40005 6.32015Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.11 13.6501L13.69 10.0601" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 544 B

View File

@ -205,8 +205,8 @@ class RequestHelper {
static String deleteComment(int id) => '$baseUrl/comment/$id';
static String reportComment(int id) => '$baseUrl/comment/$id/report';
static String widgetNews() => '$baseUrl/user/widget';
static String aiChats() => '$baseUrl/ai/chat/v2';
static String aiArchived() => '$baseUrl/ai/chat/v2${_urlConcatGenerator([
static String aiChats() => '$baseUrl/ai/aichat';
static String aiArchived() => '$baseUrl/ai/aichat${_urlConcatGenerator([
const MapEntry('archived', true),
])}';
static String aiBots() => '$baseUrl/ai/bot/v2';

View File

@ -8,14 +8,13 @@ import 'package:didvan/routes/routes.dart';
import 'package:didvan/views/widgets/animated_visibility.dart';
import 'package:didvan/views/widgets/bookmark_button.dart';
import 'package:didvan/views/widgets/didvan/card.dart';
import 'package:didvan/views/widgets/didvan/divider.dart';
import 'package:didvan/views/widgets/didvan/icon_button.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/infography_tag.dart';
import 'package:didvan/views/widgets/ink_wrapper.dart';
import 'package:didvan/views/widgets/liked_button.dart';
import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class _BackButton extends StatefulWidget {
const _BackButton({Key? key}) : super(key: key);
@ -117,77 +116,106 @@ class InfographyItem extends StatelessWidget {
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(
height: 10,
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
padding: const EdgeInsets.symmetric(vertical: 0.0),
child: GestureDetector(
onTap: () => _openInteractiveViewer(context, image),
child: SkeletonImage(
imageUrl: image,
aspectRatio: 16 / 9,
),
imageUrl: image,
aspectRatio: 16 / 9,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(0.0),
bottomRight: Radius.circular(0.0),
topLeft: Radius.circular(13.0),
topRight: Radius.circular(13.0),
)),
),
),
Container(
decoration: const BoxDecoration(
color: Color.fromARGB(255, 0, 69, 92),
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(13.0),
bottomLeft: Radius.circular(13.0)),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
InfoCat(
category: category,
createdAt: createdAt,
),
Row(
children: [
GestureDetector(
onTap: () => Navigator.of(context).pushNamed(
Routes.mentions,
arguments: {
'id': id,
'type': 'banner',
'title': title,
},
),
child: SvgPicture.asset(
'lib/assets/icons/send-2.svg',
width: 25,
height: 25,
colorFilter: const ColorFilter.mode(
Colors.white,
BlendMode.srcIn,
),
),
),
BookmarkButton(
itemId: id,
type: 'infography',
gestureSize: 22,
value: marked,
onMarkChanged: (value) => onMarkChanged(id, value, true),
svgIconOn: 'lib/assets/icons/bookmark_fill.svg',
svgIconOff: 'lib/assets/icons/archive-tick.svg',
),
Padding(
padding: const EdgeInsets.only(left: 8),
child: LikedButton(
itemId: id,
type: 'infography',
gestureSize: 32,
value: liked,
onMarkChanged: (value) => onLikedChanged(id, value, true),
likes: likes,
),
),
],
),
],
),
),
const SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Wrap(
spacing: 4,
runSpacing: 4,
children: [
for (var i = 0; i < tag.length; i++)
InfographyTag(
tag: tag[i],
onMarkChanged: onMarkChanged,
),
],
Expanded(
child: Wrap(
spacing: 4,
runSpacing: 4,
children: [
for (var i = 0; i < tag.length; i++)
InfographyTag(
tag: tag[i],
onMarkChanged: onMarkChanged,
),
],
),
),
],
),
const DidvanDivider(),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
InfoCat(
category: category,
createdAt: createdAt,
),
Row(
children: [
LikedButton(
itemId: id,
type: 'infography',
gestureSize: 32,
value: liked,
onMarkChanged: (value) => onLikedChanged(id, value, true),
likes: likes,
),
const SizedBox(
width: 4.0,
),
DidvanIconButton(
gestureSize: 32,
onPressed: () => Navigator.of(context).pushNamed(
Routes.mentions,
arguments: {
'id': id,
'type': 'banner',
'title': title,
},
),
icon: DidvanIcons.mention_icon,
),
BookmarkButton(
itemId: id,
type: 'infography',
gestureSize: 32,
value: marked,
onMarkChanged: (value) => onMarkChanged(id, value, true),
),
],
),
],
)
],
),
);

View File

@ -1,12 +1,13 @@
import 'package:didvan/views/home/media/podcast_tab_page.dart';
import 'package:didvan/views/home/media/videocast_tab_page.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/home/main/widgets/banner.dart';
import 'package:didvan/views/home/media/widgets/media_banner_with_thumbnails.dart';
import 'package:didvan/views/home/media/widgets/banner_grid_view.dart';
import 'package:didvan/views/widgets/home_app_bar.dart';
import 'package:didvan/views/widgets/custom_media_tab_bar.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:didvan/views/podcasts/podcasts_state.dart';
import 'package:didvan/views/home/infography/infography_screen_state.dart';
class MediaPage extends StatefulWidget {
const MediaPage({super.key});
@ -101,25 +102,18 @@ class _MediaPageState extends State<MediaPage> {
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 16.0, vertical: 16.0),
child: MainPageBanner(isFirst: true),
child: MediaBannerWithThumbnails(isFirst: true),
),
),
Center(
key: const ValueKey('InfographyPage'),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.photo_library,
size: 48,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(height: 8),
DidvanText(
'صفحه اینفوگرافی',
style: Theme.of(context).textTheme.titleSmall,
),
],
ChangeNotifierProvider(
create: (_) => InfographyScreenState(),
child: const SingleChildScrollView(
key: ValueKey('InfographyPage'),
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 8.0, vertical: 16.0),
child: BannerGridView(),
),
),
),
],

View File

@ -0,0 +1,264 @@
import 'package:didvan/views/home/infography/infography_screen_state.dart';
import 'package:didvan/views/home/main/widgets/infography_item.dart';
import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
import 'package:didvan/views/widgets/didvan/card.dart';
import 'package:didvan/views/widgets/didvan/divider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
class BannerGridView extends StatefulWidget {
const BannerGridView({super.key});
@override
State<BannerGridView> createState() => _BannerGridViewState();
}
class _BannerGridViewState extends State<BannerGridView> {
int _visibleItemCount = 4;
final ScrollController _scrollController = ScrollController();
int pageNumber = 1;
@override
void initState() {
super.initState();
Future.microtask(() {
context.read<InfographyScreenState>().init();
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer<InfographyScreenState>(
builder: (context, state, child) {
return StateHandler<InfographyScreenState>(
placeholder: Column(
children: [
_buildPlaceholder(),
const SizedBox(height: 8.0),
_buildPlaceholder(),
],
),
topPadding: 0,
onRetry: () => state.init(),
state: state,
builder: (context, state) {
final contents = state.contents;
final itemsToShow = _visibleItemCount > contents.length
? contents.length
: _visibleItemCount;
final hasMoreItems = itemsToShow < contents.length;
return Column(
children: [
_buildCategoryFilters(state),
const SizedBox(height: 16),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(8.0),
itemCount: itemsToShow,
separatorBuilder: (context, index) =>
const SizedBox(height: 8.0),
itemBuilder: (context, index) {
final item = contents[index];
return InfographyItem(
id: item.id,
onMarkChanged: (id, value, _) =>
state.changeMark(id, value),
image: item.image,
category: item.category,
createdAt: item.createdAt,
title: item.title,
tag: item.tags,
marked: item.marked,
liked: item.liked,
onLikedChanged: (id, value, _) =>
state.changeLiked(id, value),
likes: item.likes,
);
},
),
if (hasMoreItems)
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: GestureDetector(
onTap: () {
setState(() {
_visibleItemCount += 4;
});
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 50.0,
vertical: 12.0,
),
decoration: BoxDecoration(
color: const Color.fromARGB(255, 0, 126, 167),
borderRadius: BorderRadius.circular(15),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
'lib/assets/icons/element-plus.svg',
),
const SizedBox(width: 8),
const Text(
'بارگذاری بیشتر',
style: TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
),
],
);
},
);
},
);
}
Widget _buildCategoryFilters(InfographyScreenState state) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: FilterChip(
label: const Padding(
padding: EdgeInsets.all(1.0),
child: Text('همه'),
),
selected: state.selectedCats.isEmpty,
onSelected: (selected) {
if (selected) {
state.selectedCats.clear();
state.getInfographyContent(page: 1);
setState(() {
_visibleItemCount = 4;
});
}
},
selectedColor: const Color.fromARGB(255, 200, 224, 244),
backgroundColor: state.selectedCats.isEmpty
? const Color.fromARGB(255, 200, 224, 244)
: const Color.fromARGB(255, 224, 224, 224),
labelStyle: TextStyle(
color: state.selectedCats.isEmpty
? const Color.fromARGB(255, 0, 115, 153)
: const Color.fromARGB(255, 102, 102, 102),
fontWeight: state.selectedCats.isEmpty
? FontWeight.bold
: FontWeight.normal,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
showCheckmark: false,
),
),
...state.categories.map((category) {
final isSelected = state.selectedCats.contains(category);
return Padding(
padding: const EdgeInsets.only(left: 8.0),
child: FilterChip(
label: Padding(
padding: const EdgeInsets.all(1.0),
child: Text(category.label),
),
selected: isSelected,
onSelected: (selected) {
if (selected) {
state.selectedCats.add(category);
} else {
state.selectedCats.remove(category);
}
state.getInfographyContent(page: 1);
setState(() {
_visibleItemCount = 4;
});
},
selectedColor: const Color.fromARGB(255, 200, 224, 244),
backgroundColor: isSelected
? const Color.fromARGB(255, 200, 224, 244)
: const Color.fromARGB(255, 224, 224, 224),
labelStyle: TextStyle(
color: isSelected
? const Color.fromARGB(255, 0, 115, 153)
: const Color.fromARGB(255, 102, 102, 102),
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
showCheckmark: false,
),
);
}).toList(),
],
),
);
}
Widget _buildPlaceholder() {
return const DidvanCard(
padding: EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ShimmerPlaceholder(
height: 16,
width: 240,
),
],
),
SizedBox(height: 12),
ShimmerPlaceholder(height: 200, width: 400),
SizedBox(height: 12),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ShimmerPlaceholder(
height: 32,
width: 100,
),
],
),
DidvanDivider(
verticalPadding: 12,
),
ShimmerPlaceholder(
height: 16,
width: double.infinity,
),
],
),
);
}
}

View File

@ -0,0 +1,235 @@
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: [
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,
),
],
),
],
);
}
}

View File

@ -1,4 +1,3 @@
import 'package:didvan/config/design_config.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/providers/user.dart';
@ -78,6 +77,9 @@ class _BookmarkButtonState extends State<BookmarkButton> {
Widget build(BuildContext context) {
print("BookmarkButton build - value: $_value");
final iconOn = widget.svgIconOn ?? 'lib/assets/icons/bookmark_on.svg';
final iconOff = widget.svgIconOff ?? 'lib/assets/icons/bookmark_off.svg';
return IconButton(
iconSize: widget.gestureSize,
onPressed: _handleTap,
@ -87,7 +89,7 @@ class _BookmarkButtonState extends State<BookmarkButton> {
return ScaleTransition(scale: animation, child: child);
},
child: SvgPicture.asset(
_value ? 'lib/assets/icons/bookmark_on.svg' : 'lib/assets/icons/bookmark_off.svg',
_value ? iconOn : iconOff,
key: ValueKey('bookmark_$_value'),
width: widget.gestureSize,
height: widget.gestureSize,

View File

@ -5,8 +5,6 @@ import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../audio/player_navbar.dart';
class DidvanBNB extends StatelessWidget {
final int currentTabIndex;
final void Function(int index) onTabChanged;
@ -17,28 +15,19 @@ class DidvanBNB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Stack(
children: [
const PlayerNavBar(
inHome: true,
return Container(
height: 72,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius:
const BorderRadius.vertical(top: Radius.circular(0)),
border: const Border(
top: BorderSide(
color: Color.fromARGB(255, 224, 224, 224),
width: 1.5,
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
height: 72,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius:
const BorderRadius.vertical(top: Radius.circular(0)),
border: const Border(
top: BorderSide(
color: Color.fromARGB(255, 224, 224, 224),
width: 1.5,
),
),
),
),
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
@ -87,10 +76,7 @@ class DidvanBNB extends StatelessWidget {
),
],
),
),
),
],
);
);
}
}
@ -229,7 +215,6 @@ class _NavBarItemState extends State<_NavBarItem>
child: Stack(
alignment: Alignment.center,
children: [
// Ripple effect background
AnimatedBuilder(
animation: _rippleAnimation,
builder: (context, child) {
@ -245,7 +230,6 @@ class _NavBarItemState extends State<_NavBarItem>
);
},
),
// Main content
Column(
children: [
const SizedBox(height: 4),
@ -261,7 +245,6 @@ class _NavBarItemState extends State<_NavBarItem>
padding: EdgeInsets.all(widget.isHomeButton ? 8 : 4),
duration: DesignConfig.lowAnimationDuration,
//shadow
decoration:
BoxDecoration(
shape: BoxShape.circle,
@ -302,7 +285,6 @@ class _NavBarItemState extends State<_NavBarItem>
);
},
),
// Animated text with fade and slide
AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: (!widget.isHomeButton && !widget.isSelected)

View File

@ -2,8 +2,6 @@ import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/views/widgets/didvan/app_bar.dart';
import 'package:flutter/material.dart';
import '../audio/player_navbar.dart';
class DidvanScaffold extends StatefulWidget {
final List<Widget>? slivers;
final List<Widget>? children;
@ -137,19 +135,6 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
appBarData: widget.appBarData!,
scrollController: _scrollController,
),
if (!widget.hidePlayer)
const Positioned(
bottom: 16,
left: 16,
right: 16,
child: SizedBox(
child: Center(
child: PlayerNavBar(
inHome: false,
),
),
),
),
],
),
),

View File

@ -72,18 +72,22 @@ class _CarouselIndicator extends StatelessWidget {
children: [
for (int i = 0; i < count; i++)
Container(
width: 8,
height: 8,
width: 7,
height: 7,
margin: const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
width: 1,
color: Theme.of(context).colorScheme.primary,
),
boxShadow: [
BoxShadow(
color: currentIndex == i ? const Color.fromARGB(255, 176, 215, 228) : Colors.transparent,
blurRadius: 0,
spreadRadius: 2.5,
offset: const Offset(0, 0),
),
],
color: currentIndex == i
? Theme.of(context).colorScheme.primary
: Colors.transparent,
? const Color.fromARGB(255, 0, 126, 167)
: const Color.fromARGB(255, 173, 173, 173),
),
)
],

View File

@ -1,11 +1,11 @@
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/tag.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/ink_wrapper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:persian_number_utility/persian_number_utility.dart';
class InfographyTag extends StatelessWidget {
@ -34,23 +34,26 @@ class InfographyTag extends StatelessWidget {
horizontal: 4,
),
decoration: BoxDecoration(
borderRadius: DesignConfig.lowBorderRadius,
borderRadius: DesignConfig.mediumBorderRadius,
border: Border.all(
color: Theme.of(context).colorScheme.focusedBorder,
color: const Color.fromARGB(255, 184, 184, 184),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
const Icon(
DidvanIcons.hashtag_regular,
color: Theme.of(context).colorScheme.focusedBorder,
color: Color.fromARGB(255, 102, 102, 102),
size: 16,
),
DidvanText(
tag.label,
color: Theme.of(context).colorScheme.focusedBorder,
style: Theme.of(context).textTheme.labelLarge,
Padding(
padding: const EdgeInsets.all(2.0),
child: DidvanText(
tag.label,
color: const Color.fromARGB(255, 102, 102, 102),
style: Theme.of(context).textTheme.labelMedium,
),
),
],
),
@ -73,13 +76,16 @@ class InfoCat extends StatelessWidget {
Widget build(BuildContext context) {
return Row(
children: [
const SizedBox(width: 15,),
SvgPicture.asset('lib/assets/icons/calendar.svg',color: Colors.white,height: 22,),
const SizedBox(width: 5,),
// DidvanText(
// category,
// style: Theme.of(context).textTheme.bodySmall,
// ),
DidvanText(
category,
style: Theme.of(context).textTheme.bodySmall,
),
DidvanText(
' / ${DateTime.parse(createdAt).toPersianDateStr()}',
style: Theme.of(context).textTheme.bodySmall,
DateTime.parse(createdAt).toPersianDateStr(),
style: const TextStyle(color: Colors.white),
),
],
);

View File

@ -3,8 +3,8 @@ import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/providers/user.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
class LikedButton extends StatefulWidget {
final bool value;
@ -59,6 +59,7 @@ class _LikedButtonState extends State<LikedButton> {
child: DidvanText(
likes.toString(),
fontSize: 14,
color: Colors.white,
),
),
InkWell(
@ -90,11 +91,11 @@ class _LikedButtonState extends State<LikedButton> {
UserProvider.changeItemLiked(widget.type, widget.itemId, _value);
}
},
child: Icon(
_value ? CupertinoIcons.heart_fill : CupertinoIcons.heart,
size: 24,
child: SvgPicture.asset(
_value ? 'lib/assets/icons/heart_fill.svg':'lib/assets/icons/heart.svg',
height: 24,
color: widget.color ??
(!_value ? null : Theme.of(context).colorScheme.error),
(!_value ? Colors.white : Theme.of(context).colorScheme.error),
),
),
],