diff --git a/lib/assets/icons/arrow-down.svg b/lib/assets/icons/arrow-down.svg
new file mode 100644
index 0000000..c1464f2
--- /dev/null
+++ b/lib/assets/icons/arrow-down.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/assets/icons/arrow-left.svg b/lib/assets/icons/arrow-left.svg
new file mode 100644
index 0000000..7098359
--- /dev/null
+++ b/lib/assets/icons/arrow-left.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/assets/icons/arrow-up2.svg b/lib/assets/icons/arrow-up2.svg
new file mode 100644
index 0000000..b0e1075
--- /dev/null
+++ b/lib/assets/icons/arrow-up2.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/assets/icons/bookmark_off.svg b/lib/assets/icons/bookmark_off.svg
new file mode 100644
index 0000000..1d862cb
--- /dev/null
+++ b/lib/assets/icons/bookmark_off.svg
@@ -0,0 +1,4 @@
+
diff --git a/lib/assets/icons/bookmark_on.svg b/lib/assets/icons/bookmark_on.svg
new file mode 100644
index 0000000..41cb3cf
--- /dev/null
+++ b/lib/assets/icons/bookmark_on.svg
@@ -0,0 +1,4 @@
+
diff --git a/lib/routes/route_generator.dart b/lib/routes/route_generator.dart
index 881f4f6..6c79f0e 100644
--- a/lib/routes/route_generator.dart
+++ b/lib/routes/route_generator.dart
@@ -64,6 +64,7 @@ import 'package:didvan/views/home/statistic/statistic_state.dart';
import 'package:didvan/views/podcasts/studio_details/studio_details.mobile.dart'
if (dart.library.io) 'package:didvan/views/podcasts/studio_details/studio_details.mobile.dart'
if (dart.library.html) 'package:didvan/views/podcasts/studio_details/studio_details.web.dart';
+import 'package:didvan/views/home/media/video_details_page.dart';
import 'package:didvan/views/splash/splash.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/views/story_viewer/story_viewer_page.dart';
@@ -313,6 +314,20 @@ class RouteGenerator {
return _errorRoute(
'Invalid arguments for ${settings.name}: Expected Map.');
+ case Routes.videoDetails:
+ if (settings.arguments is Map) {
+ return _createRoute(
+ ChangeNotifierProvider(
+ create: (context) => StudioDetailsState(),
+ child: VideoDetailsPage(
+ pageData: settings.arguments as Map,
+ ),
+ ),
+ );
+ }
+ return _errorRoute(
+ 'Invalid arguments for ${settings.name}: Expected Map.');
+
case Routes.statisticDetails:
if (settings.arguments is Map) {
return _createRoute(
diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart
index 09376e3..05f8962 100644
--- a/lib/routes/routes.dart
+++ b/lib/routes/routes.dart
@@ -42,4 +42,5 @@ class Routes {
static const String web = '/web';
static const String storyViewer = '/story-viewer';
static const String media = '/media';
+ static const String videoDetails = '/video-details';
}
\ No newline at end of file
diff --git a/lib/views/home/media/video_details_page.dart b/lib/views/home/media/video_details_page.dart
new file mode 100644
index 0000000..b7d7be9
--- /dev/null
+++ b/lib/views/home/media/video_details_page.dart
@@ -0,0 +1,507 @@
+// ignore_for_file: use_build_context_synchronously, deprecated_member_use
+
+import 'package:chewie/chewie.dart';
+import 'package:didvan/config/theme_data.dart';
+import 'package:didvan/constants/assets.dart';
+import 'package:didvan/models/enums.dart';
+import 'package:didvan/models/studio_details_data.dart';
+import 'package:didvan/services/media/media.dart';
+import 'package:didvan/views/comments/comments.dart';
+import 'package:didvan/views/comments/comments_state.dart';
+import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart';
+import 'package:didvan/views/podcasts/studio_details/widgets/studio_details_widget.dart';
+import 'package:didvan/views/widgets/bookmark_button.dart';
+import 'package:didvan/views/widgets/didvan/text.dart';
+import 'package:didvan/views/widgets/overview/multitype.dart';
+import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
+import 'package:didvan/views/widgets/tag_item.dart';
+import 'package:didvan/views/widgets/video/primary_controls.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_html/flutter_html.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:provider/provider.dart';
+import 'package:url_launcher/url_launcher_string.dart';
+import 'package:video_player/video_player.dart';
+
+class VideoDetailsPage extends StatefulWidget {
+ final Map pageData;
+
+ const VideoDetailsPage({Key? key, required this.pageData}) : super(key: key);
+
+ @override
+ State createState() => _VideoDetailsPageState();
+}
+
+class _VideoDetailsPageState extends State {
+ int _currentlyPlayingId = 0;
+ VideoPlayerController? _videoPlayerController;
+ ChewieController? _chewieController;
+ bool _isDescriptionExpanded = false;
+
+ @override
+ void initState() {
+ super.initState();
+ final state = context.read();
+ state.args = widget.pageData['args'];
+
+ Future.delayed(
+ Duration.zero,
+ () => state.getStudioDetails(widget.pageData['id']).then((_) {
+ if (mounted) {
+ _initializePlayer(state.studio);
+ Future.delayed(const Duration(milliseconds: 500), () {
+ if (mounted) {
+ state.getRelatedContents();
+ }
+ });
+ }
+ }),
+ );
+ }
+
+ Future _initializePlayer(StudioDetailsData studio) async {
+ if (studio.type == 'video') {
+ _videoPlayerController?.dispose();
+ _chewieController?.dispose();
+
+ debugPrint("Playing video from URL: ${studio.link}");
+ _videoPlayerController = VideoPlayerController.network(studio.link);
+
+ try {
+ await _videoPlayerController!.initialize();
+ if (mounted) {
+ setState(() {
+ _chewieController = ChewieController(
+ videoPlayerController: _videoPlayerController!,
+ customControls: const PrimaryControls(),
+ autoPlay: true,
+ looping: true,
+ aspectRatio: 16 / 9,
+ materialProgressColors: ChewieProgressColors(
+ playedColor: Theme.of(context).colorScheme.title,
+ handleColor: Theme.of(context).colorScheme.title,
+ ),
+ );
+ _currentlyPlayingId = studio.id;
+ });
+ }
+ } catch (e) {
+ debugPrint("Error initializing video player: $e");
+ }
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Consumer(
+ builder: (context, state, child) {
+ if (state.isStudioLoaded && _currentlyPlayingId != state.studio.id) {
+ Future.microtask(() => _initializePlayer(state.studio));
+ }
+
+ return StateHandler(
+ state: state,
+ onRetry: () {
+ try {
+ state.getStudioDetails(state.studio.id);
+ } catch (e) {
+ state.getStudioDetails(widget.pageData['id']);
+ }
+ },
+ builder: (context, state) {
+ if (!state.isStudioLoaded) {
+ return Scaffold(
+ body: Center(
+ child: Image.asset(
+ Assets.loadingAnimation,
+ width: 100,
+ height: 100,
+ ),
+ ),
+ );
+ }
+
+ return WillPopScope(
+ onWillPop: () async {
+ if (MediaService.currentPodcast != null) {
+ state.studio = MediaService.currentPodcast!;
+ }
+ state.handleTracking(id: state.studio.id);
+ return true;
+ },
+ child: Scaffold(
+ backgroundColor: Theme.of(context).colorScheme.surface,
+ appBar: PreferredSize(
+ preferredSize: const Size.fromHeight(90.0),
+ child: AppBar(
+ backgroundColor: Colors.white,
+ elevation: 0,
+ automaticallyImplyLeading: false,
+ flexibleSpace: SafeArea(
+ child: Container(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16.0, vertical: 8.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Center(
+ child: SvgPicture.asset(
+ 'lib/assets/images/logos/logo-horizontal-light.svg',
+ height: 55,
+ ),
+ ),
+ IconButton(
+ icon: SvgPicture.asset(
+ 'lib/assets/icons/arrow-left.svg',
+ color:
+ const Color.fromARGB(255, 102, 102, 102),
+ height: 24,
+ ),
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ ),
+ ],
+ ),
+ ),
+ ),
+ )),
+ body: SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ AspectRatio(
+ aspectRatio: 16 / 9,
+ child: Stack(
+ children: [
+ (_chewieController != null &&
+ _chewieController!.videoPlayerController
+ .value.isInitialized)
+ ? Chewie(controller: _chewieController!)
+ : Center(
+ child: Image.asset(
+ Assets.loadingAnimation,
+ width: 100,
+ height: 100,
+ ),
+ ),
+ Positioned(
+ top: 1,
+ left: 1,
+ child: BookmarkButton(
+ value: state.studio.marked,
+ onMarkChanged: (value) {
+ if (widget.pageData['onMarkChanged'] !=
+ null) {
+ widget.pageData['onMarkChanged'](
+ state.studio.id, value);
+ }
+ },
+ gestureSize: 35,
+ type: 'video',
+ itemId: state.studio.id,
+ ),
+ ),
+ ],
+ ),
+ ),
+ _buildDescriptionSection(state),
+ _buildRelatedContentSection(state),
+ _buildCommentsSection(state),
+ ],
+ ),
+ ),
+ ),
+ );
+ },
+ );
+ },
+ );
+ }
+
+ Widget _buildDescriptionSection(StudioDetailsState state) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Text(
+ state.studio.title,
+ style: const TextStyle(
+ fontSize: 17,
+ fontWeight: FontWeight.bold,
+ color: Color.fromARGB(255, 0, 53, 70)),
+ ),
+ ),
+ AnimatedSize(
+ duration: const Duration(milliseconds: 300),
+ child: ConstrainedBox(
+ constraints: BoxConstraints(
+ maxHeight: _isDescriptionExpanded ? double.infinity : 100.0,
+ ),
+ child: Html(
+ key: ValueKey(state.studio.id),
+ data: state.studio.description,
+ onAnchorTap: (href, _, __) => launchUrlString(href!),
+ style: {
+ '*': Style(
+ direction: TextDirection.rtl,
+ textAlign: TextAlign.right,
+ lineHeight: LineHeight.percent(135),
+ margin: const Margins(),
+ padding: HtmlPaddings.zero,
+ color: const Color.fromARGB(255, 102, 102, 102),
+ fontWeight: FontWeight.normal,
+ ),
+ },
+ ),
+ ),
+ ),
+ InkWell(
+ onTap: () {
+ setState(() {
+ _isDescriptionExpanded = !_isDescriptionExpanded;
+ });
+ },
+ child: Padding(
+ padding: const EdgeInsets.only(top: 8.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SvgPicture.asset(
+ _isDescriptionExpanded
+ ? 'lib/assets/icons/arrow-up2.svg'
+ : 'lib/assets/icons/arrow-down.svg',
+ color: Theme.of(context).primaryColor,
+ height: 20,
+ ),
+ const SizedBox(width: 4),
+ Text(
+ _isDescriptionExpanded ? 'کمتر' : 'بیشتر',
+ style: TextStyle(
+ color: Theme.of(context).primaryColor,
+ fontWeight: FontWeight.normal,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ if (state.studio.tags.isNotEmpty) const SizedBox(height: 16),
+ if (state.studio.tags.isNotEmpty)
+ Wrap(
+ spacing: 8,
+ runSpacing: 8,
+ children: [
+ for (var i = 0; i < state.studio.tags.length; i++)
+ TagItem(
+ tag: state.studio.tags[i],
+ onMarkChanged: (id, value) {
+ if (widget.pageData['onMarkChanged'] != null) {
+ widget.pageData['onMarkChanged'](id, value);
+ }
+ },
+ type: 'video',
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 20),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ const SizedBox(),
+ if (state.nextStudio != null &&
+ state.alongSideState == AppState.idle)
+ StudioPreview(
+ isNext: true,
+ studio: state.nextStudio!,
+ ),
+ if (state.alongSideState == AppState.busy)
+ StudioPreview.placeHolder,
+ if (state.prevStudio != null &&
+ state.alongSideState == AppState.idle)
+ StudioPreview(
+ isNext: false,
+ studio: state.prevStudio!,
+ ),
+ if (state.alongSideState == AppState.busy)
+ StudioPreview.placeHolder,
+ const SizedBox(),
+ ],
+ ),
+ ],
+ );
+ }
+
+ Widget _buildCommentsSection(StudioDetailsState state) {
+ return Container(
+ width: double.infinity,
+ margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(12),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.black.withOpacity(0.05),
+ blurRadius: 8,
+ offset: const Offset(0, 2),
+ ),
+ ],
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Container(
+ width: double.infinity,
+ padding: const EdgeInsets.all(16),
+ decoration: const BoxDecoration(
+ color: Color(0xFF059669),
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(12),
+ topRight: Radius.circular(12),
+ ),
+ ),
+ child: const DidvanText(
+ 'نظرات',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 18,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ SizedBox(
+ height: 400,
+ child: ChangeNotifierProvider(
+ create: (context) => CommentsState(),
+ child: Comments(
+ pageData: {
+ 'id': state.studio.id,
+ 'type': 'studio',
+ 'title': state.studio.title,
+ 'onCommentsChanged': state.onCommentsChanged,
+ 'isPage': false,
+ },
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildRelatedContentSection(StudioDetailsState state) {
+ debugPrint("تعداد مطالب مرتبط: ${state.studio.relatedContents.length}");
+ debugPrint(
+ "آیا لیست مطالب مرتبط خالی است؟ ${state.studio.relatedContentsIsEmpty}");
+ debugPrint("تعداد tags: ${state.studio.tags.length}");
+
+ return Container(
+ width: double.infinity,
+ margin: const EdgeInsets.all(8),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Padding(
+ padding: EdgeInsets.all(8.0),
+ child: Text(
+ "مطالب مرتبط:",
+ style: TextStyle(
+ fontSize: 16,
+ color: Color.fromARGB(255, 0, 53, 70),
+ fontWeight: FontWeight.bold),
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(8),
+ child: Builder(
+ builder: (context) {
+ debugPrint("Building related content section:");
+ debugPrint(
+ " - relatedContents.length: ${state.studio.relatedContents.length}");
+ debugPrint(
+ " - relatedContentsIsEmpty: ${state.studio.relatedContentsIsEmpty}");
+ debugPrint(" - tags.length: ${state.studio.tags.length}");
+
+ if (state.studio.relatedContents.isNotEmpty) {
+ debugPrint(
+ " - Showing ${state.studio.relatedContents.length} related items");
+ return Column(
+ children: [
+ ...state.studio.relatedContents
+ .map((item) => Padding(
+ padding: const EdgeInsets.only(bottom: 8),
+ child: MultitypeOverview(
+ item: item,
+ onMarkChanged: (id, value) {
+ if (widget.pageData['onMarkChanged'] !=
+ null) {
+ widget.pageData['onMarkChanged'](
+ id, value);
+ }
+ },
+ ),
+ ))
+ .toList(),
+ ],
+ );
+ } else if (state.studio.relatedContentsIsEmpty) {
+ debugPrint(" - Showing empty message");
+ return const Padding(
+ padding: EdgeInsets.all(32.0),
+ child: Center(
+ child: DidvanText(
+ 'مطالب مرتبطی یافت نشد',
+ style: TextStyle(
+ color: Colors.grey,
+ fontSize: 16,
+ ),
+ ),
+ ),
+ );
+ } else {
+ debugPrint(" - Showing placeholders (loading state)");
+ return Column(
+ children: [
+ const Padding(
+ padding: EdgeInsets.all(8.0),
+ child: Text(
+ 'در حال بارگذاری مطالب مرتبط...',
+ style: TextStyle(
+ fontSize: 12,
+ color: Colors.grey,
+ ),
+ ),
+ ),
+ ...List.generate(
+ 3,
+ (index) => Padding(
+ padding: const EdgeInsets.only(bottom: 8),
+ child: MultitypeOverview.placeholder,
+ )),
+ ],
+ );
+ }
+ },
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ @override
+ void dispose() {
+ _videoPlayerController?.dispose();
+ _chewieController?.dispose();
+ super.dispose();
+ }
+}
diff --git a/lib/views/home/media/videocast_tab_page.dart b/lib/views/home/media/videocast_tab_page.dart
index cdbb3c5..99bbc17 100644
--- a/lib/views/home/media/videocast_tab_page.dart
+++ b/lib/views/home/media/videocast_tab_page.dart
@@ -136,7 +136,7 @@ class _VideoCastTabPageState extends State {
onTap: () {
Navigator.pushNamed(
context,
- Routes.studioDetails,
+ Routes.videoDetails,
arguments: {
'id': state.studios[_currentFeaturedIndex].id,
'type': state.studios[_currentFeaturedIndex].type,
@@ -198,7 +198,7 @@ class _VideoCastTabPageState extends State {
onTap: () {
Navigator.pushNamed(
context,
- Routes.studioDetails,
+ Routes.videoDetails,
arguments: {
'id': videocast.id,
'type': videocast.type,
diff --git a/lib/views/home/media/widgets/featured_video_card.dart b/lib/views/home/media/widgets/featured_video_card.dart
index cdd5671..7d65a09 100644
--- a/lib/views/home/media/widgets/featured_video_card.dart
+++ b/lib/views/home/media/widgets/featured_video_card.dart
@@ -25,7 +25,7 @@ class FeaturedVideoCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
- height: MediaQuery.of(context).size.height * 0.40,
+ height: 380,
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
@@ -112,6 +112,7 @@ class FeaturedVideoCard extends StatelessWidget {
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
color: const Color.fromARGB(255, 200, 224, 244),
fontWeight: FontWeight.bold,
+ fontSize: 18
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
diff --git a/lib/views/podcasts/studio_details/widgets/studio_details_widget.dart b/lib/views/podcasts/studio_details/widgets/studio_details_widget.dart
index 70bc75c..60def50 100644
--- a/lib/views/podcasts/studio_details/widgets/studio_details_widget.dart
+++ b/lib/views/podcasts/studio_details/widgets/studio_details_widget.dart
@@ -1,7 +1,5 @@
import 'dart:math';
-import 'package:didvan/config/theme_data.dart';
-import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/studio_details_data.dart';
import 'package:didvan/views/comments/comments.dart';
@@ -16,6 +14,8 @@ import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:persian_number_utility/persian_number_utility.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher_string.dart';
@@ -37,7 +37,7 @@ class StudioDetailsWidget extends StatelessWidget {
bool isVideo = state.studio.iframe != null;
double topOffset = isVideo
? ds.width * 9 / 16
- : 400; // برای ویدیو aspect ratio، برای پادکست ارتفاع AudioPlayerWidget
+ : 400;
return Container(
height: max(
ds.height - topOffset - 72 - MediaQuery.of(context).padding.top,
@@ -101,20 +101,20 @@ class StudioDetailsWidget extends StatelessWidget {
const SizedBox(),
if (state.nextStudio != null &&
state.alongSideState == AppState.idle)
- _StudioPreview(
+ StudioPreview(
isNext: true,
studio: state.nextStudio!,
),
if (state.alongSideState == AppState.busy)
- _StudioPreview.placeHolder,
+ StudioPreview.placeHolder,
if (state.prevStudio != null &&
state.alongSideState == AppState.idle)
- _StudioPreview(
+ StudioPreview(
isNext: false,
studio: state.prevStudio!,
),
if (state.alongSideState == AppState.busy)
- _StudioPreview.placeHolder,
+ StudioPreview.placeHolder,
const SizedBox(),
],
),
@@ -200,10 +200,10 @@ class StudioDetailsWidget extends StatelessWidget {
}
}
-class _StudioPreview extends StatelessWidget {
+class StudioPreview extends StatelessWidget {
final bool isNext;
final StudioDetailsData studio;
- const _StudioPreview({
+ const StudioPreview({
Key? key,
required this.isNext,
required this.studio,
@@ -211,11 +211,18 @@ class _StudioPreview extends StatelessWidget {
String get _previewTitle {
if (studio.type == 'video') {
- return 'ویدیوی ${isNext ? 'بعدی' : 'قبلی'} ';
+ return 'ویدیوکست ${isNext ? 'بعدی' : 'قبلی'} ';
}
return 'پادکست ${isNext ? 'بعدی' : 'قبلی'} ';
}
+ String _formatDuration(int? duration) {
+ if (duration == null) return '';
+ final minutes = duration ~/ 60;
+ final seconds = duration % 60;
+ return '”${seconds.toString().padLeft(2, '0')}:’${minutes.toString().padLeft(2, '0')}';
+ }
+
@override
Widget build(BuildContext context) {
return GestureDetector(
@@ -227,38 +234,94 @@ class _StudioPreview extends StatelessWidget {
isForward: isNext,
);
},
- child: Container(
- width: 88,
- height: 216,
- color: Colors.transparent,
- child: Column(
- children: [
- SkeletonImage(
- imageUrl: studio.image,
- aspectRatio: 1 / 1,
- ),
- const SizedBox(height: 8),
- Icon(
- isNext
- ? DidvanIcons.angle_right_regular
- : DidvanIcons.angle_left_regular,
- ),
- const SizedBox(height: 8),
- DidvanText(
- _previewTitle,
- style: Theme.of(context).textTheme.bodySmall,
- textAlign: TextAlign.center,
- ),
- const SizedBox(height: 8),
- DidvanText(
- studio.title,
- maxLines: 2,
- overflow: TextOverflow.ellipsis,
- textAlign: TextAlign.center,
- style: Theme.of(context).textTheme.labelSmall,
- color: Theme.of(context).colorScheme.caption,
- ),
- ],
+
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Container(
+ width: 170,
+ height: 235,
+ decoration: BoxDecoration(
+ color: const Color.fromRGBO(235, 235, 235, 1),
+ borderRadius: BorderRadius.circular(20),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.black.withOpacity(0.05),
+ blurRadius: 4,
+ offset: const Offset(0, 2),
+ ),
+ ],
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(6.0),
+ child: AspectRatio(
+ aspectRatio: 17 / 14,
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(20),
+ child: SkeletonImage(
+ imageUrl: studio.image,
+ width: double.infinity,
+ height: double.infinity,
+ borderRadius: BorderRadius.circular(20),
+ ),
+ ),
+ ),
+ ),
+
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsets.fromLTRB(12, 4, 8, 3),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ DidvanText(
+ _previewTitle,
+ style: Theme.of(context).textTheme.bodyMedium?.copyWith(
+ fontWeight: FontWeight.normal,
+ color: const Color.fromARGB(255, 102, 102, 102),
+ fontSize: 12
+ ),
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ ),
+ const SizedBox(height: 4),
+ DidvanText(
+ studio.title,
+ style: Theme.of(context).textTheme.bodyMedium?.copyWith(
+ fontWeight: FontWeight.normal,
+ color: Colors.black87,
+ ),
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ ),
+ const SizedBox(height: 4),
+ Row(
+ children: [
+ SvgPicture.asset(
+ 'lib/assets/icons/clock.svg',
+ color: const Color.fromARGB(255, 102, 102, 102),
+ ),
+ const SizedBox(width: 4),
+ DidvanText(
+ _formatDuration(studio.duration).toPersianDigit(),
+ style: Theme.of(context)
+ .textTheme
+ .bodySmall
+ ?.copyWith(
+ color: const Color.fromARGB(255, 102, 102, 102),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
),
),
);
diff --git a/lib/views/widgets/bookmark_button.dart b/lib/views/widgets/bookmark_button.dart
index 8067d3d..0578174 100644
--- a/lib/views/widgets/bookmark_button.dart
+++ b/lib/views/widgets/bookmark_button.dart
@@ -3,9 +3,9 @@ import 'package:didvan/constants/app_icons.dart';
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/icon_button.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
class BookmarkButton extends StatefulWidget {
final bool value;
@@ -15,6 +15,9 @@ class BookmarkButton extends StatefulWidget {
final double gestureSize;
final String type;
final int itemId;
+ final String? svgIconOn;
+ final String? svgIconOff;
+
const BookmarkButton({
Key? key,
required this.value,
@@ -24,6 +27,8 @@ class BookmarkButton extends StatefulWidget {
required this.itemId,
this.askForConfirmation = false,
this.color,
+ this.svgIconOn,
+ this.svgIconOff,
}) : super(key: key);
@override
@@ -45,38 +50,49 @@ class _BookmarkButtonState extends State {
super.initState();
}
+ void _handleTap() async {
+ bool confirm = false;
+ if (widget.askForConfirmation) {
+ await ActionSheetUtils(context).openDialog(
+ data: ActionSheetData(
+ content: const DidvanText(
+ 'آیا میخواهید این محتوا از نشان شدهها حذف شود؟',
+ ),
+ titleIcon: DidvanIcons.bookmark_regular,
+ titleColor: Theme.of(context).colorScheme.secondary,
+ title: 'تایید عملیات',
+ onConfirmed: () => confirm = true,
+ ),
+ );
+ }
+ if (!widget.askForConfirmation || confirm) {
+ setState(() {
+ _value = !_value;
+ });
+ widget.onMarkChanged(_value);
+ UserProvider.changeItemMark(widget.type, widget.itemId, _value);
+ }
+ }
+
@override
Widget build(BuildContext context) {
- return DidvanIconButton(
- gestureSize: widget.gestureSize,
- color: widget.color ??
- (DesignConfig.isDark || !_value
- ? null
- : Theme.of(context).colorScheme.primary),
- icon: _value ? DidvanIcons.bookmark_solid : DidvanIcons.bookmark_regular,
- onPressed: () async {
- bool confirm = false;
- if (widget.askForConfirmation) {
- await ActionSheetUtils(context).openDialog(
- data: ActionSheetData(
- content: const DidvanText(
- 'آیا میخواهید این محتوا از نشان شدهها حذف شود؟',
- ),
- titleIcon: DidvanIcons.bookmark_regular,
- titleColor: Theme.of(context).colorScheme.secondary,
- title: 'تایید عملیات',
- onConfirmed: () => confirm = true,
- ),
- );
- }
- if (!widget.askForConfirmation || confirm) {
- setState(() {
- _value = !_value;
- });
- widget.onMarkChanged(_value);
- UserProvider.changeItemMark(widget.type, widget.itemId, _value);
- }
- },
+ print("BookmarkButton build - value: $_value");
+
+ return IconButton(
+ iconSize: widget.gestureSize,
+ onPressed: _handleTap,
+ icon: AnimatedSwitcher(
+ duration: const Duration(milliseconds: 300),
+ transitionBuilder: (Widget child, Animation animation) {
+ return ScaleTransition(scale: animation, child: child);
+ },
+ child: SvgPicture.asset(
+ _value ? 'lib/assets/icons/bookmark_on.svg' : 'lib/assets/icons/bookmark_off.svg',
+ key: ValueKey('bookmark_$_value'),
+ width: widget.gestureSize,
+ height: widget.gestureSize,
+ ),
+ ),
);
}
-}
+}
\ No newline at end of file
diff --git a/lib/views/widgets/didvan/icon_button.dart b/lib/views/widgets/didvan/icon_button.dart
index 3046757..774f44a 100644
--- a/lib/views/widgets/didvan/icon_button.dart
+++ b/lib/views/widgets/didvan/icon_button.dart
@@ -35,6 +35,7 @@ class DidvanIconButton extends StatelessWidget {
size: size,
color: color,
),
+
),
);
}
diff --git a/lib/views/widgets/overview/multitype.dart b/lib/views/widgets/overview/multitype.dart
index f156ffb..54ae5f0 100644
--- a/lib/views/widgets/overview/multitype.dart
+++ b/lib/views/widgets/overview/multitype.dart
@@ -6,10 +6,7 @@ import 'package:didvan/models/requests/radar.dart';
import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/providers/user.dart';
import 'package:didvan/routes/routes.dart';
-import 'package:didvan/services/media/media.dart';
-import 'package:didvan/services/network/request.dart';
import 'package:didvan/utils/date_time.dart';
-import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart';
import 'package:didvan/views/widgets/bookmark_button.dart';
import 'package:didvan/views/widgets/didvan/card.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
@@ -17,10 +14,8 @@ import 'package:didvan/views/widgets/didvan/text_field.dart';
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
import 'package:persian_number_utility/persian_number_utility.dart';
-import 'package:provider/provider.dart';
-import 'package:didvan/services/app_initalizer.dart';
-import 'package:url_launcher/url_launcher_string.dart';
class MultitypeOverview extends StatelessWidget {
final OverviewData item;
@@ -64,227 +59,190 @@ class MultitypeOverview extends StatelessWidget {
return null;
}
- IconData get _icon {
+ String get _icon {
switch (item.type) {
case 'radar':
- return DidvanIcons.scanning_light;
+ return 'DidvanIcons.scanning_light';
case 'news':
- return DidvanIcons.foolad_light;
+ return 'DidvanIcons.foolad_light';
case 'video':
- return DidvanIcons.video_light;
+ return 'DidvanIcons.video_light';
case 'podcast':
- return DidvanIcons.podcast_light;
+ return 'DidvanIcons.podcast_light';
case 'delphi':
case 'survey':
- return DidvanIcons.saha_light;
+ return 'DidvanIcons.saha_light';
case 'infography':
- return DidvanIcons.infography_regular;
+ return 'DidvanIcons.infography_regular';
default:
- return DidvanIcons.radar_light;
+ return 'DidvanIcons.radar_light';
}
}
@override
Widget build(BuildContext context) {
- return DidvanCard(
- onTap: () async {
- if (item.type == 'infography') {
- return;
- }
- if (item.type == 'podcast') {
- final state = context.read();
- await state.getStudioDetails(
- item.id,
- args: const StudioRequestArgs(page: 0, type: 'podcast'),
- );
- MediaService.currentPodcast = state.studio;
- MediaService.handleAudioPlayback(
- audioSource: item.link,
- id: item.id,
- isNetworkAudio: true,
- isVoiceMessage: false,
- );
- return;
- }
- if (_targetPageRouteName == null && item.link != null) {
- AppInitializer.openWebLink(
- context,
- '${item.link!}?accessToken=${RequestService.token}',
- mode: LaunchMode.inAppWebView,
- );
- return;
- }
- Navigator.of(context).pushNamed(
- _targetPageRouteName!,
- arguments: {
- 'onMarkChanged': onMarkChanged,
- 'id': item.id,
- 'args': _targetPageArgs,
- 'hasUnmarkConfirmation': hasUnmarkConfirmation,
- },
- );
- },
- child: Column(
- children: [
- Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Stack(
- children: [
- SkeletonImage(imageUrl: item.image, height: 80, width: 80),
- Container(
- padding:
- const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
- decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.secondary,
- borderRadius: const BorderRadius.horizontal(
- left: Radius.circular(10),
- ),
- ),
- child: Icon(
- _icon,
- color: Theme.of(context).colorScheme.white,
- size: 18,
+ return Column(
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Stack(
+ children: [
+ SkeletonImage(imageUrl: item.image, height: 80, width: 80),
+ Container(
+ padding:
+ const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.secondary,
+ borderRadius: const BorderRadius.horizontal(
+ left: Radius.circular(20),
),
),
- ],
- ),
- const SizedBox(width: 8),
- Expanded(
- child: SizedBox(
- height: 80,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- DidvanText(
- item.title,
- style: Theme.of(context).textTheme.bodyLarge,
- maxLines: 2,
- overflow: TextOverflow.ellipsis,
- ),
- Flexible(
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
+ child: SvgPicture.asset(
+ _icon,
+ color: Theme.of(context).colorScheme.white,
+ height: 18,
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(width: 8),
+ Expanded(
+ child: SizedBox(
+ height: 80,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ DidvanText(
+ item.title,
+ style: Theme.of(context).textTheme.bodyLarge,
+ maxLines: 2,
+ overflow: TextOverflow.ellipsis,
+ ),
+ Flexible(
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Icon(
+ DidvanIcons.calendar_day_light,
+ size: 16,
+ ),
+ const SizedBox(width: 4),
+ DidvanText(
+ DateTime.parse(item.createdAt)
+ .toPersianDateStr(),
+ style: Theme.of(context).textTheme.labelSmall,
+ ),
+ ],
+ ),
+ const Spacer(),
+ if ((item.timeToRead ?? item.duration) != null)
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(
- DidvanIcons.calendar_day_light,
+ DidvanIcons.timer_light,
size: 16,
),
const SizedBox(width: 4),
DidvanText(
- DateTime.parse(item.createdAt)
- .toPersianDateStr(),
- style: Theme.of(context).textTheme.labelSmall,
+ item.timeToRead != null
+ ? '${item.timeToRead} دقیقه'
+ : DateTimeUtils.normalizeTimeDuration(
+ Duration(seconds: item.duration!),
+ ),
+ style:
+ Theme.of(context).textTheme.labelSmall,
),
],
),
- const Spacer(),
- if ((item.timeToRead ?? item.duration) != null)
- Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const Icon(
- DidvanIcons.timer_light,
- size: 16,
- ),
- const SizedBox(width: 4),
- DidvanText(
- item.timeToRead != null
- ? '${item.timeToRead} دقیقه'
- : DateTimeUtils.normalizeTimeDuration(
- Duration(seconds: item.duration!),
- ),
- style:
- Theme.of(context).textTheme.labelSmall,
- ),
- ],
- ),
- ],
- ),
+ ],
),
- ],
- ),
+ ),
+ ],
),
),
- ],
- ),
- Row(
- crossAxisAlignment: CrossAxisAlignment.end,
- children: [
- if (enableCaption)
- Expanded(
- child: Column(
- children: [
- const SizedBox(height: 8),
- Row(
- children: [
- Icon(
- Icons.edit_outlined,
- size: 16,
- color: Theme.of(context).colorScheme.caption,
- ),
- const SizedBox(width: 4),
- DidvanText(
- 'یادداشتهای من',
- style: Theme.of(context).textTheme.labelSmall,
- color: Theme.of(context).colorScheme.caption,
- ),
- ],
- ),
- Row(
- children: [
- Flexible(
- child: Container(
- height: 1,
- color: Theme.of(context).colorScheme.primary,
- ),
- ),
- Flexible(
- flex: 2,
- child: Container(
- height: 1,
- color: Theme.of(context).colorScheme.border,
- ),
- )
- ],
- ),
- DidvanTextField(
- disableBorders: true,
- initialValue: item.description,
- hintText: 'برای اضافه کردن یادداشت لمس کنید.',
- onChanged: (value) => UserProvider.changeItemMark(
- item.type,
- item.id,
- null,
- description: value,
+ ),
+ ],
+ ),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: [
+ if (enableCaption)
+ Expanded(
+ child: Column(
+ children: [
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ Icon(
+ Icons.edit_outlined,
+ size: 16,
+ color: Theme.of(context).colorScheme.caption,
),
- isSmall: true,
+ const SizedBox(width: 4),
+ DidvanText(
+ 'یادداشتهای من',
+ style: Theme.of(context).textTheme.labelSmall,
+ color: Theme.of(context).colorScheme.caption,
+ ),
+ ],
+ ),
+ Row(
+ children: [
+ Flexible(
+ child: Container(
+ height: 1,
+ color: Theme.of(context).colorScheme.primary,
+ ),
+ ),
+ Flexible(
+ flex: 2,
+ child: Container(
+ height: 1,
+ color: Theme.of(context).colorScheme.border,
+ ),
+ )
+ ],
+ ),
+ DidvanTextField(
+ disableBorders: true,
+ initialValue: item.description,
+ hintText: 'برای اضافه کردن یادداشت لمس کنید.',
+ onChanged: (value) => UserProvider.changeItemMark(
+ item.type,
+ item.id,
+ null,
+ description: value,
),
- ],
- ),
+ isSmall: true,
+ ),
+ ],
),
- if (!enableCaption) const Spacer(),
- if (enableBookmark) ...[
- const SizedBox(
- width: 12,
- ),
- BookmarkButton(
- value: item.marked,
- onMarkChanged: (value) => onMarkChanged(item.id, value),
- gestureSize: 32,
- type: item.type,
- itemId: item.id,
- askForConfirmation: true,
- ),
- ],
+ ),
+ if (!enableCaption) const Spacer(),
+ if (enableBookmark) ...[
+ const SizedBox(
+ width: 12,
+ ),
+ BookmarkButton(
+ value: item.marked,
+ onMarkChanged: (value) => onMarkChanged(item.id, value),
+ gestureSize: 32,
+ type: item.type,
+ itemId: item.id,
+ askForConfirmation: true,
+ ),
],
- ),
- ],
- ),
+ ],
+ ),
+ const SizedBox(height: 10,),
+ Divider(color: Theme.of(context).colorScheme.border),
+ ],
);
}
diff --git a/lib/views/widgets/tag_item.dart b/lib/views/widgets/tag_item.dart
index 4d2d603..b83bd87 100644
--- a/lib/views/widgets/tag_item.dart
+++ b/lib/views/widgets/tag_item.dart
@@ -1,5 +1,4 @@
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';
@@ -35,22 +34,25 @@ class TagItem extends StatelessWidget {
horizontal: 8,
),
decoration: BoxDecoration(
- borderRadius: DesignConfig.lowBorderRadius,
+ borderRadius: const BorderRadius.all(
+ Radius.circular(12),
+ ),
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: 17,
),
DidvanText(
tag.label,
- color: Theme.of(context).colorScheme.focusedBorder,
- style: Theme.of(context).textTheme.bodyLarge,
+ color: const Color.fromARGB(255, 102, 102, 102),
+ style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
),
],
),