redesign single podcast page
This commit is contained in:
parent
386ed90101
commit
84422637d9
|
|
@ -0,0 +1,7 @@
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M9 16.5C5.3775 16.5 2.4375 13.56 2.4375 9.9375C2.4375 6.315 5.3775 3.375 9 3.375C12.6225 3.375 15.5625 6.315 15.5625 9.9375" stroke="#FCFCFC" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M9 6V9.75" stroke="#FCFCFC" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M6.75 1.5H11.25" stroke="#FCFCFC" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M14.25 12.75V15.75" stroke="#FCFCFC" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 12.75V15.75" stroke="#FCFCFC" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 691 B |
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M28 4.66699C15.12 4.66699 4.66667 15.1203 4.66667 28.0003C4.66667 40.8803 15.12 51.3337 28 51.3337C40.88 51.3337 51.3333 40.8803 51.3333 28.0003C51.3333 15.1203 40.88 4.66699 28 4.66699ZM34.2067 32.037L31.22 33.7637L28.2333 35.4903C24.3833 37.707 21.2333 35.887 21.2333 31.4537V28.0003V24.547C21.2333 20.0903 24.3833 18.2937 28.2333 20.5103L31.22 22.237L34.2067 23.9637C38.0567 26.1803 38.0567 29.8203 34.2067 32.037Z" fill="#007EA7"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 548 B |
|
|
@ -226,7 +226,7 @@ class RequestHelper {
|
||||||
static String aiAChat(int id) => '$baseUrl/ai/chat/$id/v2';
|
static String aiAChat(int id) => '$baseUrl/ai/chat/$id/v2';
|
||||||
static String aiChatId() => '$baseUrl/ai/chat/id';
|
static String aiChatId() => '$baseUrl/ai/chat/id';
|
||||||
static String aiDeleteChats() => '$baseUrl/ai/chat';
|
static String aiDeleteChats() => '$baseUrl/ai/chat';
|
||||||
static String aiChangeChats(int id) => '$baseUrl/ai/chat/$id/title';
|
static String aiChangeChats(int id) => '$baseUrl/ai/chat/$id/title';
|
||||||
static String deleteChat(int id) => '$baseUrl/ai/chat/$id';
|
static String deleteChat(int id) => '$baseUrl/ai/chat/$id';
|
||||||
static String deleteMessage(int chatId, int messageId) =>
|
static String deleteMessage(int chatId, int messageId) =>
|
||||||
'$baseUrl/ai/chat/$chatId/message/$messageId';
|
'$baseUrl/ai/chat/$chatId/message/$messageId';
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import 'package:didvan/utils/action_sheet.dart';
|
||||||
import 'package:didvan/utils/date_time.dart';
|
import 'package:didvan/utils/date_time.dart';
|
||||||
import 'package:didvan/views/comments/comments_state.dart';
|
import 'package:didvan/views/comments/comments_state.dart';
|
||||||
import 'package:didvan/views/widgets/menu_item.dart';
|
import 'package:didvan/views/widgets/menu_item.dart';
|
||||||
import 'package:didvan/views/widgets/animated_visibility.dart';
|
|
||||||
import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
||||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||||
import 'package:didvan/views/widgets/ink_wrapper.dart';
|
import 'package:didvan/views/widgets/ink_wrapper.dart';
|
||||||
|
|
@ -34,8 +33,6 @@ class Comment extends StatefulWidget {
|
||||||
class CommentState extends State<Comment> {
|
class CommentState extends State<Comment> {
|
||||||
late final CommentsState state;
|
late final CommentsState state;
|
||||||
|
|
||||||
bool _showSubComments = false;
|
|
||||||
|
|
||||||
CommentData get _comment => widget.comment;
|
CommentData get _comment => widget.comment;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -51,191 +48,147 @@ class CommentState extends State<Comment> {
|
||||||
_commentBuilder(comment: _comment),
|
_commentBuilder(comment: _comment),
|
||||||
if (_comment.replies.isNotEmpty) const SizedBox(height: 16),
|
if (_comment.replies.isNotEmpty) const SizedBox(height: 16),
|
||||||
for (var i = 0; i < _comment.replies.length; i++)
|
for (var i = 0; i < _comment.replies.length; i++)
|
||||||
AnimatedVisibility(
|
_commentBuilder(
|
||||||
duration: DesignConfig.lowAnimationDuration,
|
isReply: true,
|
||||||
isVisible: _showSubComments,
|
comment: _comment.replies[i],
|
||||||
child: _commentBuilder(
|
|
||||||
isReply: true,
|
|
||||||
comment: _comment.replies[i],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _commentBuilder({required comment, bool isReply = false}) => Container(
|
Widget _commentBuilder({required comment, bool isReply = false}) => Container(
|
||||||
|
margin: EdgeInsets.only(right: isReply ? 24.0 : 0.0),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
right: isReply
|
right: isReply
|
||||||
? BorderSide(color: Theme.of(context).colorScheme.caption)
|
? BorderSide(
|
||||||
|
color: Theme.of(context).colorScheme.caption,
|
||||||
|
width: 3.0,
|
||||||
|
)
|
||||||
: BorderSide.none,
|
: BorderSide.none,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
padding: EdgeInsets.only(right: isReply ? 16.0 : 0.0),
|
||||||
child: Row(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Row(
|
||||||
child: Column(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
if (comment.user.photo == null)
|
||||||
children: [
|
const Icon(
|
||||||
Row(
|
DidvanIcons.avatar_light,
|
||||||
children: [
|
size: 50,
|
||||||
if (isReply) const SizedBox(width: 12),
|
|
||||||
if (comment.user.photo == null)
|
|
||||||
const Icon(
|
|
||||||
DidvanIcons.avatar_light,
|
|
||||||
size: 50,
|
|
||||||
),
|
|
||||||
if (comment.user.photo != null)
|
|
||||||
SkeletonImage(
|
|
||||||
imageUrl: comment.user.photo,
|
|
||||||
height: 50,
|
|
||||||
width: 50,
|
|
||||||
borderRadius: DesignConfig.highBorderRadius,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
DidvanText(
|
|
||||||
comment.user.fullName,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
color: Color.fromARGB(255, 102, 102, 102)),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
DidvanText(
|
|
||||||
DateTimeUtils.momentGenerator(comment.createdAt),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
color: const Color.fromARGB(255, 0, 126, 167),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
DidvanIconButton(
|
|
||||||
size: 18,
|
|
||||||
gestureSize: 24,
|
|
||||||
icon: DidvanIcons.menu_light,
|
|
||||||
onPressed: () => _showCommentActions(comment),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
if (comment.user.photo != null)
|
||||||
if (isReply)
|
SkeletonImage(
|
||||||
|
imageUrl: comment.user.photo,
|
||||||
|
height: 50,
|
||||||
|
width: 50,
|
||||||
|
borderRadius: DesignConfig.highBorderRadius,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
DidvanText(
|
DidvanText(
|
||||||
'پاسخ به ${comment.toUser.fullName}',
|
comment.user.fullName,
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: const TextStyle(
|
||||||
color: Theme.of(context).colorScheme.caption,
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
color: Color.fromARGB(255, 102, 102, 102)),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
DidvanText(
|
||||||
|
DateTimeUtils.momentGenerator(comment.createdAt),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
color: const Color.fromARGB(255, 0, 126, 167),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
DidvanIconButton(
|
||||||
|
size: 18,
|
||||||
|
gestureSize: 24,
|
||||||
|
icon: DidvanIcons.menu_light,
|
||||||
|
onPressed: () => _showCommentActions(comment),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
DidvanText(
|
||||||
|
comment.text,
|
||||||
|
color: const Color.fromARGB(255, 102, 102, 102),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (comment.status == 2)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.circle,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondary
|
||||||
|
.withValues(alpha: 0.3),
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
DidvanText(
|
||||||
|
'در انتظار تایید',
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondary
|
||||||
|
.withValues(alpha: 0.3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (_comment.status != 2)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
_FeedbackButtons(
|
||||||
|
likeCount: comment.feedback.like,
|
||||||
|
dislikeCount: comment.feedback.dislike,
|
||||||
|
likeValue: comment.liked,
|
||||||
|
dislikeValue: comment.disliked,
|
||||||
|
onFeedback:
|
||||||
|
(like, dislike, likeCount, dislikeCount) =>
|
||||||
|
state.feedback(
|
||||||
|
id: _comment.id,
|
||||||
|
like: like,
|
||||||
|
dislike: dislike,
|
||||||
|
likeCount: likeCount,
|
||||||
|
dislikeCount: dislikeCount,
|
||||||
|
replyId: isReply ? comment.id : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
),
|
||||||
|
InkWrapper(
|
||||||
|
onPressed: () {
|
||||||
|
state.commentId = _comment.id;
|
||||||
|
state.replyingTo = comment.user;
|
||||||
|
state.showReplyBox = true;
|
||||||
|
state.update();
|
||||||
|
widget.focusNode.requestFocus();
|
||||||
|
},
|
||||||
|
child: DidvanText(
|
||||||
|
'پاسخ',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
color: const Color.fromARGB(255, 102, 102, 102),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
|
||||||
DidvanText(
|
|
||||||
comment.text,
|
|
||||||
color: const Color.fromARGB(255, 102, 102, 102),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
|
||||||
if (comment.status == 2)
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.circle,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.secondary
|
|
||||||
.withValues(alpha: 0.3),
|
|
||||||
size: 18,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
DidvanText(
|
|
||||||
'در انتظار تایید',
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.secondary
|
|
||||||
.withValues(alpha: 0.3),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
if (_comment.status != 2)
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
_FeedbackButtons(
|
|
||||||
likeCount: comment.feedback.like,
|
|
||||||
dislikeCount: comment.feedback.dislike,
|
|
||||||
likeValue: comment.liked,
|
|
||||||
dislikeValue: comment.disliked,
|
|
||||||
onFeedback:
|
|
||||||
(like, dislike, likeCount, dislikeCount) =>
|
|
||||||
state.feedback(
|
|
||||||
id: _comment.id,
|
|
||||||
like: like,
|
|
||||||
dislike: dislike,
|
|
||||||
likeCount: likeCount,
|
|
||||||
dislikeCount: dislikeCount,
|
|
||||||
replyId: isReply ? comment.id : null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: 20,
|
|
||||||
),
|
|
||||||
InkWrapper(
|
|
||||||
onPressed: () {
|
|
||||||
state.commentId = _comment.id;
|
|
||||||
state.replyingTo = comment.user;
|
|
||||||
state.showReplyBox = true;
|
|
||||||
state.update();
|
|
||||||
widget.focusNode.requestFocus();
|
|
||||||
},
|
|
||||||
child: DidvanText(
|
|
||||||
'پاسخ',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
color: const Color.fromARGB(255, 102, 102, 102),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!isReply) const SizedBox(width: 20),
|
|
||||||
if (!isReply && comment.replies.isNotEmpty)
|
|
||||||
InkWrapper(
|
|
||||||
onPressed: () => setState(
|
|
||||||
() => _showSubComments = !_showSubComments,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
DidvanText(
|
|
||||||
'پاسخها(${comment.replies.length})',
|
|
||||||
style:
|
|
||||||
Theme.of(context).textTheme.bodyLarge,
|
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
AnimatedRotation(
|
|
||||||
duration: DesignConfig.lowAnimationDuration,
|
|
||||||
turns: _showSubComments ? 0.5 : 0,
|
|
||||||
child: Icon(
|
|
||||||
DidvanIcons.angle_down_regular,
|
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<void> _showCommentActions(comment) async {
|
Future<void> _showCommentActions(comment) async {
|
||||||
ActionSheetUtils(context).showBottomSheet(
|
ActionSheetUtils(context).showBottomSheet(
|
||||||
data: ActionSheetData(
|
data: ActionSheetData(
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import 'package:didvan/views/home/main/widgets/banner.dart';
|
||||||
import 'package:didvan/views/widgets/home_app_bar.dart';
|
import 'package:didvan/views/widgets/home_app_bar.dart';
|
||||||
import 'package:didvan/views/widgets/custom_media_tab_bar.dart';
|
import 'package:didvan/views/widgets/custom_media_tab_bar.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:didvan/views/podcasts/podcasts_state.dart';
|
||||||
|
|
||||||
class MediaPage extends StatefulWidget {
|
class MediaPage extends StatefulWidget {
|
||||||
const MediaPage({super.key});
|
const MediaPage({super.key});
|
||||||
|
|
@ -84,8 +86,16 @@ class _MediaPageState extends State<MediaPage> {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
const PodcastTabPage(key: ValueKey('PodcastTabPage')),
|
ChangeNotifierProvider(
|
||||||
const VideoCastTabPage(key: ValueKey('VideoCastTabPage')),
|
create: (_) => PodcastsState(),
|
||||||
|
child: const PodcastTabPage(key: ValueKey('PodcastTabPage')),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (_) => PodcastsState(),
|
||||||
|
child:
|
||||||
|
const VideoCastTabPage(key: ValueKey('VideoCastTabPage')),
|
||||||
|
),
|
||||||
|
|
||||||
const SingleChildScrollView(
|
const SingleChildScrollView(
|
||||||
key: ValueKey('MainPageBanner'),
|
key: ValueKey('MainPageBanner'),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|
|
||||||
|
|
@ -289,93 +289,127 @@ class _VideoDetailsPageState extends State<VideoDetailsPage>
|
||||||
color: Color.fromARGB(255, 0, 53, 70)),
|
color: Color.fromARGB(255, 0, 53, 70)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Stack(
|
Container(
|
||||||
alignment: Alignment.bottomCenter,
|
decoration: BoxDecoration(
|
||||||
children: [
|
borderRadius: BorderRadius.vertical(
|
||||||
AnimatedSize(
|
bottom: Radius.circular(
|
||||||
duration: const Duration(milliseconds: 300),
|
_isDescriptionExpanded ? 0 : 16.0),
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.vertical(
|
|
||||||
bottom: Radius.circular(
|
|
||||||
_isDescriptionExpanded ? 0 : 16.0),
|
|
||||||
),
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Positioned(
|
boxShadow: _isDescriptionExpanded
|
||||||
bottom: 0,
|
? null
|
||||||
left: 0,
|
: [
|
||||||
right: 0,
|
BoxShadow(
|
||||||
child: AnimatedOpacity(
|
color: Colors.black.withOpacity(0.1),
|
||||||
|
blurRadius: 8.0,
|
||||||
|
spreadRadius: -2.0,
|
||||||
|
offset: const Offset(0, 6),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
children: [
|
||||||
|
AnimatedSize(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
opacity: _isDescriptionExpanded ? 0.0 : 1.0,
|
child: ClipRRect(
|
||||||
child: Container(
|
borderRadius: BorderRadius.vertical(
|
||||||
height: 40,
|
bottom: Radius.circular(
|
||||||
decoration: BoxDecoration(
|
_isDescriptionExpanded ? 0 : 16.0),
|
||||||
borderRadius: const BorderRadius.vertical(
|
top: Radius.circular(
|
||||||
bottom: Radius.circular(16.0),
|
_isDescriptionExpanded ? 0 : 16.0),
|
||||||
),
|
),
|
||||||
gradient: LinearGradient(
|
child: Container(
|
||||||
begin: Alignment.topCenter,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
end: Alignment.bottomCenter,
|
child: ConstrainedBox(
|
||||||
colors: [
|
constraints: BoxConstraints(
|
||||||
Theme.of(context)
|
maxHeight: _isDescriptionExpanded
|
||||||
.colorScheme
|
? double.infinity
|
||||||
.surface
|
: 100.0,
|
||||||
.withOpacity(0.0),
|
),
|
||||||
Theme.of(context).colorScheme.surface,
|
child: Html(
|
||||||
],
|
key: ValueKey(state.studio.id),
|
||||||
stops: const [0.0, 0.9],
|
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,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Positioned(
|
||||||
],
|
bottom: 0,
|
||||||
),
|
left: 0,
|
||||||
InkWell(
|
right: 0,
|
||||||
onTap: () {
|
child: AnimatedOpacity(
|
||||||
setState(() {
|
duration: const Duration(milliseconds: 300),
|
||||||
_isDescriptionExpanded = !_isDescriptionExpanded;
|
opacity: _isDescriptionExpanded ? 0.0 : 1.0,
|
||||||
});
|
child: Container(
|
||||||
},
|
height: 40,
|
||||||
child: Padding(
|
decoration: BoxDecoration(
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
borderRadius: const BorderRadius.vertical(
|
||||||
child: Row(
|
bottom: Radius.circular(16.0),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
top: Radius.circular(16.0),
|
||||||
children: [
|
),
|
||||||
SvgPicture.asset(
|
gradient: LinearGradient(
|
||||||
_isDescriptionExpanded
|
begin: Alignment.topCenter,
|
||||||
? 'lib/assets/icons/arrow-up2.svg'
|
end: Alignment.bottomCenter,
|
||||||
: 'lib/assets/icons/arrow-down.svg',
|
colors: [
|
||||||
color: Theme.of(context).primaryColor,
|
Theme.of(context)
|
||||||
height: 20,
|
.colorScheme
|
||||||
|
.surface
|
||||||
|
.withOpacity(0.0),
|
||||||
|
Theme.of(context).colorScheme.surface,
|
||||||
|
],
|
||||||
|
stops: const [0.0, 0.9],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Transform.translate(
|
||||||
|
offset: const Offset(0, -14.0),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isDescriptionExpanded = !_isDescriptionExpanded;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(4.0),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color.fromARGB(255, 230, 243, 250),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
_isDescriptionExpanded
|
||||||
|
? 'lib/assets/icons/arrow-up2.svg'
|
||||||
|
: 'lib/assets/icons/arrow-down.svg',
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
height: 25,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -449,7 +483,7 @@ class _VideoDetailsPageState extends State<VideoDetailsPage>
|
||||||
builder: (context, userProvider, child) {
|
builder: (context, userProvider, child) {
|
||||||
final user = userProvider.user;
|
final user = userProvider.user;
|
||||||
final hasProfileImage =
|
final hasProfileImage =
|
||||||
user?.photo != null && user!.photo!.isNotEmpty;
|
user.photo != null && user.photo!.isNotEmpty;
|
||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,8 @@ class WaveformPainter extends CustomPainter {
|
||||||
final y2 = y1 + barHeight;
|
final y2 = y1 + barHeight;
|
||||||
|
|
||||||
// تعیین رنگ بر اساس progress
|
// تعیین رنگ بر اساس progress
|
||||||
final barProgress = i / barCount;
|
// استفاده از (i + 1) تا میله اول از 1/60 شروع بشه نه 0/60
|
||||||
|
final barProgress = (i + 1) / barCount;
|
||||||
final paint = barProgress <= progress && isActive ? activePaint : inactivePaint;
|
final paint = barProgress <= progress && isActive ? activePaint : inactivePaint;
|
||||||
|
|
||||||
canvas.drawLine(
|
canvas.drawLine(
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,31 @@
|
||||||
// ignore_for_file: use_build_context_synchronously, deprecated_member_use
|
// ignore_for_file: use_build_context_synchronously, deprecated_member_use
|
||||||
|
|
||||||
import 'package:chewie/chewie.dart';
|
import 'package:chewie/chewie.dart';
|
||||||
|
|
||||||
import 'package:didvan/config/theme_data.dart';
|
import 'package:didvan/config/theme_data.dart';
|
||||||
|
import 'package:didvan/constants/app_icons.dart';
|
||||||
import 'package:didvan/constants/assets.dart';
|
import 'package:didvan/constants/assets.dart';
|
||||||
|
import 'package:didvan/models/enums.dart';
|
||||||
import 'package:didvan/models/studio_details_data.dart';
|
import 'package:didvan/models/studio_details_data.dart';
|
||||||
import 'package:didvan/models/view/app_bar_data.dart';
|
import 'package:didvan/providers/user.dart';
|
||||||
|
import 'package:didvan/routes/routes.dart';
|
||||||
import 'package:didvan/services/media/media.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/studio_details_state.dart';
|
||||||
import 'package:didvan/views/podcasts/studio_details/widgets/studio_details_widget.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/bookmark_button.dart';
|
||||||
import 'package:didvan/views/widgets/audio/audio_player_widget.dart';
|
import 'package:didvan/views/widgets/audio/audio_player_widget.dart';
|
||||||
import 'package:didvan/views/widgets/didvan/app_bar.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/state_handlers/state_handler.dart';
|
||||||
|
import 'package:didvan/views/widgets/tag_item.dart';
|
||||||
import 'package:didvan/views/widgets/video/primary_controls.dart';
|
import 'package:didvan/views/widgets/video/primary_controls.dart';
|
||||||
import 'package:flutter/material.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:provider/provider.dart';
|
||||||
import 'package:didvan/routes/routes.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
class StudioDetails extends StatefulWidget {
|
class StudioDetails extends StatefulWidget {
|
||||||
|
|
@ -27,14 +37,132 @@ class StudioDetails extends StatefulWidget {
|
||||||
State<StudioDetails> createState() => _StudioDetailsState();
|
State<StudioDetails> createState() => _StudioDetailsState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _StudioDetailsState extends State<StudioDetails> {
|
class _StudioDetailsState extends State<StudioDetails>
|
||||||
|
with TickerProviderStateMixin, WidgetsBindingObserver {
|
||||||
int _currentlyPlayingId = 0;
|
int _currentlyPlayingId = 0;
|
||||||
VideoPlayerController? _videoPlayerController;
|
VideoPlayerController? _videoPlayerController;
|
||||||
ChewieController? _chewieController;
|
ChewieController? _chewieController;
|
||||||
|
bool _isDescriptionExpanded = false;
|
||||||
|
final _focusNode = FocusNode();
|
||||||
|
|
||||||
|
late AnimationController _mainAnimationController;
|
||||||
|
late Animation<double> _fadeAnimation;
|
||||||
|
late Animation<Offset> _slideAnimation;
|
||||||
|
|
||||||
|
late AnimationController _playerAnimationController;
|
||||||
|
late Animation<double> _playerScaleAnimation;
|
||||||
|
late Animation<double> _playerFadeAnimation;
|
||||||
|
|
||||||
|
late AnimationController _titleAnimationController;
|
||||||
|
late Animation<Offset> _titleSlideAnimation;
|
||||||
|
late Animation<double> _titleFadeAnimation;
|
||||||
|
|
||||||
|
late AnimationController _tagsAnimationController;
|
||||||
|
late Animation<double> _tagsFadeAnimation;
|
||||||
|
|
||||||
|
late AnimationController _bookmarkAnimationController;
|
||||||
|
late Animation<double> _bookmarkScaleAnimation;
|
||||||
|
late Animation<double> _bookmarkRotationAnimation;
|
||||||
|
|
||||||
|
final GlobalKey<AnimatedListState> _relatedContentKey =
|
||||||
|
GlobalKey<AnimatedListState>();
|
||||||
|
final GlobalKey<AnimatedListState> _commentsKey =
|
||||||
|
GlobalKey<AnimatedListState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
_mainAnimationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 800),
|
||||||
|
);
|
||||||
|
|
||||||
|
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _mainAnimationController,
|
||||||
|
curve: const Interval(0.0, 0.5, curve: Curves.easeIn),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_slideAnimation =
|
||||||
|
Tween<Offset>(begin: const Offset(0, 0.3), end: Offset.zero).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _mainAnimationController,
|
||||||
|
curve: const Interval(0.0, 0.6, curve: Curves.easeOutCubic),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_playerAnimationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 1000),
|
||||||
|
);
|
||||||
|
|
||||||
|
_playerScaleAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _playerAnimationController,
|
||||||
|
curve: Curves.elasticOut,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_playerFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _playerAnimationController,
|
||||||
|
curve: const Interval(0.0, 0.5, curve: Curves.easeIn),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_titleAnimationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 800),
|
||||||
|
);
|
||||||
|
|
||||||
|
_titleSlideAnimation =
|
||||||
|
Tween<Offset>(begin: const Offset(-0.3, 0), end: Offset.zero).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _titleAnimationController,
|
||||||
|
curve: Curves.easeOutBack,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_titleFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _titleAnimationController,
|
||||||
|
curve: Curves.easeIn,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_tagsAnimationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 600),
|
||||||
|
);
|
||||||
|
|
||||||
|
_tagsFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _tagsAnimationController,
|
||||||
|
curve: Curves.easeIn,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_bookmarkAnimationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 600),
|
||||||
|
);
|
||||||
|
|
||||||
|
_bookmarkScaleAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _bookmarkAnimationController,
|
||||||
|
curve: Curves.elasticOut,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_bookmarkRotationAnimation = Tween<double>(begin: -0.5, end: 0.0).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _bookmarkAnimationController,
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final state = context.read<StudioDetailsState>();
|
final state = context.read<StudioDetailsState>();
|
||||||
state.args = widget.pageData['args'];
|
state.args = widget.pageData['args'];
|
||||||
|
|
||||||
|
|
@ -43,32 +171,62 @@ class _StudioDetailsState extends State<StudioDetails> {
|
||||||
() => state.getStudioDetails(widget.pageData['id']).then((_) {
|
() => state.getStudioDetails(widget.pageData['id']).then((_) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_initializePlayer(state.studio);
|
_initializePlayer(state.studio);
|
||||||
|
Future.delayed(const Duration(milliseconds: 300), () {
|
||||||
|
if (mounted) {
|
||||||
|
state.getRelatedContents();
|
||||||
|
_mainAnimationController.forward();
|
||||||
|
Future.delayed(const Duration(milliseconds: 200), () {
|
||||||
|
if (mounted) {
|
||||||
|
_playerAnimationController.forward();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Future.delayed(const Duration(milliseconds: 400), () {
|
||||||
|
if (mounted) {
|
||||||
|
_titleAnimationController.forward();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Future.delayed(const Duration(milliseconds: 600), () {
|
||||||
|
if (mounted) {
|
||||||
|
_tagsAnimationController.forward();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Future.delayed(const Duration(milliseconds: 800), () {
|
||||||
|
if (mounted) {
|
||||||
|
_bookmarkAnimationController.forward();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (widget.pageData['goToComment'] != null) {
|
@override
|
||||||
var openComments = widget.pageData['goToComment'];
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
if (openComments) {
|
@override
|
||||||
Future.delayed(
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
const Duration(seconds: 1),
|
super.didChangeAppLifecycleState(state);
|
||||||
() => Navigator.of(context).pushNamed(
|
if (state == AppLifecycleState.paused ||
|
||||||
Routes.mentions,
|
state == AppLifecycleState.inactive) {
|
||||||
arguments: {
|
_stopPodcast();
|
||||||
'id': context.read<StudioDetailsState>().studio.id,
|
|
||||||
'type': 'studio',
|
|
||||||
'title': context.read<StudioDetailsState>().studio.title,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _stopPodcast() {
|
||||||
|
if (MediaService.audioPlayer.playing) {
|
||||||
|
MediaService.audioPlayer.stop();
|
||||||
|
}
|
||||||
|
MediaService.currentPodcast = null;
|
||||||
|
MediaService.audioPlayerTag = null;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _initializePlayer(StudioDetailsData studio) async {
|
Future<void> _initializePlayer(StudioDetailsData studio) async {
|
||||||
if (studio.type == 'video') {
|
if (studio.type == 'video') {
|
||||||
// Disposing old controllers before creating new ones.
|
|
||||||
_videoPlayerController?.dispose();
|
_videoPlayerController?.dispose();
|
||||||
_chewieController?.dispose();
|
_chewieController?.dispose();
|
||||||
|
|
||||||
|
|
@ -96,25 +254,28 @@ class _StudioDetailsState extends State<StudioDetails> {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Error initializing video player: $e");
|
debugPrint("Error initializing video player: $e");
|
||||||
}
|
}
|
||||||
} else {
|
} else if (studio.type == 'podcast') {
|
||||||
// Handle audio playback using MediaService
|
|
||||||
await MediaService.handleAudioPlayback(
|
await MediaService.handleAudioPlayback(
|
||||||
audioSource: studio.link,
|
audioSource: studio.link,
|
||||||
id: studio.id,
|
id: studio.id,
|
||||||
isVoiceMessage: false,
|
isVoiceMessage: false,
|
||||||
);
|
);
|
||||||
_currentlyPlayingId = studio.id;
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_currentlyPlayingId = studio.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final d = MediaQuery.of(context);
|
|
||||||
return Consumer<StudioDetailsState>(
|
return Consumer<StudioDetailsState>(
|
||||||
builder: (context, state, child) {
|
builder: (context, state, child) {
|
||||||
if (state.isStudioLoaded && _currentlyPlayingId != state.studio.id) {
|
if (state.isStudioLoaded && _currentlyPlayingId != state.studio.id) {
|
||||||
Future.microtask(() => _initializePlayer(state.studio));
|
Future.microtask(() => _initializePlayer(state.studio));
|
||||||
}
|
}
|
||||||
|
|
||||||
return StateHandler<StudioDetailsState>(
|
return StateHandler<StudioDetailsState>(
|
||||||
state: state,
|
state: state,
|
||||||
onRetry: () {
|
onRetry: () {
|
||||||
|
|
@ -126,14 +287,17 @@ class _StudioDetailsState extends State<StudioDetails> {
|
||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (!state.isStudioLoaded) {
|
if (!state.isStudioLoaded) {
|
||||||
return Center(
|
return Scaffold(
|
||||||
child: Image.asset(
|
body: Center(
|
||||||
Assets.loadingAnimation,
|
child: Image.asset(
|
||||||
width: 100,
|
Assets.loadingAnimation,
|
||||||
height: 100,
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: () async {
|
onWillPop: () async {
|
||||||
if (MediaService.currentPodcast != null) {
|
if (MediaService.currentPodcast != null) {
|
||||||
|
|
@ -142,58 +306,116 @@ class _StudioDetailsState extends State<StudioDetails> {
|
||||||
state.handleTracking(id: state.studio.id);
|
state.handleTracking(id: state.studio.id);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
child: SafeArea(
|
child: Scaffold(
|
||||||
child: Scaffold(
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
appBar: PreferredSize(
|
||||||
appBar: PreferredSize(
|
preferredSize: const Size.fromHeight(90.0),
|
||||||
preferredSize: const Size.fromHeight(56),
|
child: AppBar(
|
||||||
child: DidvanAppBar(
|
backgroundColor: Colors.white,
|
||||||
appBarData: AppBarData(
|
elevation: 0,
|
||||||
trailing: BookmarkButton(
|
automaticallyImplyLeading: false,
|
||||||
itemId: state.studio.id,
|
flexibleSpace: SafeArea(
|
||||||
type: state.studio.type == 'video'
|
child: Container(
|
||||||
? 'video'
|
padding: const EdgeInsets.symmetric(
|
||||||
: 'podcast',
|
horizontal: 16.0, vertical: 8.0),
|
||||||
value: state.studio.marked,
|
child: Row(
|
||||||
onMarkChanged: (value) {
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
widget.pageData['onMarkChanged'](
|
children: [
|
||||||
state.studio.id, value, true);
|
Center(
|
||||||
},
|
child: SvgPicture.asset(
|
||||||
gestureSize: 48,
|
'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);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
isSmall: true,
|
|
||||||
title: state.studio.title,
|
|
||||||
),
|
),
|
||||||
),
|
)),
|
||||||
),
|
body: SingleChildScrollView(
|
||||||
body: SingleChildScrollView(
|
child: FadeTransition(
|
||||||
child: SizedBox(
|
opacity: _fadeAnimation,
|
||||||
height: d.size.height - d.padding.top - 56,
|
child: SlideTransition(
|
||||||
|
position: _slideAnimation,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (state.studio.type == 'video')
|
Hero(
|
||||||
AspectRatio(
|
tag: 'media-${state.studio.id}',
|
||||||
aspectRatio: 16 / 9,
|
child: FadeTransition(
|
||||||
child: (_chewieController != null &&
|
opacity: _playerFadeAnimation,
|
||||||
_chewieController!.videoPlayerController
|
child: ScaleTransition(
|
||||||
.value.isInitialized)
|
scale: _playerScaleAnimation,
|
||||||
? Chewie(controller: _chewieController!)
|
child: Stack(
|
||||||
: Center(
|
children: [
|
||||||
child: Image.asset(
|
if (state.studio.type == 'video')
|
||||||
Assets.loadingAnimation,
|
AspectRatio(
|
||||||
width: 100,
|
aspectRatio: 16 / 9,
|
||||||
height: 100,
|
child: (_chewieController != null &&
|
||||||
|
_chewieController!
|
||||||
|
.videoPlayerController
|
||||||
|
.value
|
||||||
|
.isInitialized)
|
||||||
|
? Chewie(
|
||||||
|
controller: _chewieController!)
|
||||||
|
: Center(
|
||||||
|
child: Image.asset(
|
||||||
|
Assets.loadingAnimation,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (state.studio.type == 'podcast')
|
||||||
|
AudioPlayerWidget(podcast: state.studio),
|
||||||
|
Positioned(
|
||||||
|
top: 1,
|
||||||
|
left: 1,
|
||||||
|
child: ScaleTransition(
|
||||||
|
scale: _bookmarkScaleAnimation,
|
||||||
|
child: RotationTransition(
|
||||||
|
turns: _bookmarkRotationAnimation,
|
||||||
|
child: BookmarkButton(
|
||||||
|
value: state.studio.marked,
|
||||||
|
onMarkChanged: (value) {
|
||||||
|
if (widget.pageData[
|
||||||
|
'onMarkChanged'] !=
|
||||||
|
null) {
|
||||||
|
widget.pageData[
|
||||||
|
'onMarkChanged'](
|
||||||
|
state.studio.id,
|
||||||
|
value,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
gestureSize: 35,
|
||||||
|
type: state.studio.type == 'video'
|
||||||
|
? 'video'
|
||||||
|
: 'podcast',
|
||||||
|
itemId: state.studio.id,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
if (state.studio.type == 'podcast')
|
),
|
||||||
AudioPlayerWidget(podcast: state.studio),
|
),
|
||||||
Expanded(
|
|
||||||
child: StudioDetailsWidget(
|
|
||||||
onMarkChanged: (id, value) => widget
|
|
||||||
.pageData['onMarkChanged'](id, value, true),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
_buildDescriptionSection(state),
|
||||||
|
_buildRelatedContentSection(state),
|
||||||
|
_buildCommentsSection(state),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -207,10 +429,490 @@ class _StudioDetailsState extends State<StudioDetails> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildDescriptionSection(StudioDetailsState state) {
|
||||||
|
return AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
child: KeyedSubtree(
|
||||||
|
key: ValueKey('description-${state.studio.id}'),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SlideTransition(
|
||||||
|
position: _titleSlideAnimation,
|
||||||
|
child: FadeTransition(
|
||||||
|
opacity: _titleFadeAnimation,
|
||||||
|
child: 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)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.vertical(
|
||||||
|
bottom:
|
||||||
|
Radius.circular(_isDescriptionExpanded ? 0 : 16.0),
|
||||||
|
),
|
||||||
|
boxShadow: _isDescriptionExpanded
|
||||||
|
? null
|
||||||
|
: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.1),
|
||||||
|
blurRadius: 8.0,
|
||||||
|
spreadRadius: -2.0,
|
||||||
|
offset: const Offset(0, 6),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
children: [
|
||||||
|
AnimatedSize(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.vertical(
|
||||||
|
bottom: Radius.circular(
|
||||||
|
_isDescriptionExpanded ? 0 : 16.0),
|
||||||
|
top: Radius.circular(
|
||||||
|
_isDescriptionExpanded ? 0 : 16.0),
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: _isDescriptionExpanded
|
||||||
|
? double.infinity
|
||||||
|
: 140.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,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
opacity: _isDescriptionExpanded ? 0.0 : 1.0,
|
||||||
|
child: Container(
|
||||||
|
height: 50,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.vertical(
|
||||||
|
bottom: Radius.circular(16.0),
|
||||||
|
top: Radius.circular(16.0),
|
||||||
|
),
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [
|
||||||
|
Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surface
|
||||||
|
.withOpacity(0.5),
|
||||||
|
Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surface
|
||||||
|
.withOpacity(0.9),
|
||||||
|
],
|
||||||
|
stops: const [0.0, 0.5],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Transform.translate(
|
||||||
|
offset: const Offset(0, -14.0),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isDescriptionExpanded = !_isDescriptionExpanded;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(5.0),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color.fromARGB(255, 230, 243, 250),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
_isDescriptionExpanded
|
||||||
|
? 'lib/assets/icons/arrow-up2.svg'
|
||||||
|
: 'lib/assets/icons/arrow-down.svg',
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
height: 25,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (state.studio.tags.isNotEmpty) const SizedBox(height: 16),
|
||||||
|
if (state.studio.tags.isNotEmpty)
|
||||||
|
FadeTransition(
|
||||||
|
opacity: _tagsFadeAnimation,
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: [
|
||||||
|
for (var i = 0; i < state.studio.tags.length; i++)
|
||||||
|
TweenAnimationBuilder<double>(
|
||||||
|
tween: Tween(begin: 0.0, end: 1.0),
|
||||||
|
duration: Duration(milliseconds: 400 + (i * 100)),
|
||||||
|
curve: Curves.easeOutBack,
|
||||||
|
builder: (context, value, child) {
|
||||||
|
return Transform.scale(
|
||||||
|
scale: value,
|
||||||
|
child: Opacity(
|
||||||
|
opacity: value,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: TagItem(
|
||||||
|
tag: state.studio.tags[i],
|
||||||
|
onMarkChanged: (id, value) {
|
||||||
|
if (widget.pageData['onMarkChanged'] !=
|
||||||
|
null) {
|
||||||
|
widget.pageData['onMarkChanged'](
|
||||||
|
id, value, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
type: state.studio.type == 'video'
|
||||||
|
? 'video'
|
||||||
|
: 'podcast',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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 ChangeNotifierProvider<CommentsState>(
|
||||||
|
create: (context) => CommentsState()
|
||||||
|
..itemId = state.studio.id
|
||||||
|
..type = 'studio'
|
||||||
|
..onCommentsChanged = state.onCommentsChanged
|
||||||
|
..getComments(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Consumer<UserProvider>(
|
||||||
|
builder: (context, userProvider, child) {
|
||||||
|
final user = userProvider.user;
|
||||||
|
final hasProfileImage =
|
||||||
|
user.photo != null && user.photo!.isNotEmpty;
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 20,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
backgroundImage:
|
||||||
|
hasProfileImage ? NetworkImage(user.photo!) : null,
|
||||||
|
child: !hasProfileImage
|
||||||
|
? const Icon(
|
||||||
|
DidvanIcons.avatar_light,
|
||||||
|
size: 50,
|
||||||
|
color: Colors.black,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: CommentMessageBox(focusNode: _focusNode),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: DidvanText(
|
||||||
|
'نظرات کاربران:',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color.fromARGB(255, 0, 53, 70),
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SizedBox(
|
||||||
|
height: 400,
|
||||||
|
child: Comments(
|
||||||
|
key: _commentsKey,
|
||||||
|
pageData: const {'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: 18,
|
||||||
|
color: Color.fromARGB(255, 0, 53, 70),
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) {
|
||||||
|
if (state.studio.relatedContents.isNotEmpty) {
|
||||||
|
return AnimatedList(
|
||||||
|
key: _relatedContentKey,
|
||||||
|
initialItemCount: state.studio.relatedContents.length,
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemBuilder: (context, index, animation) {
|
||||||
|
final item = state.studio.relatedContents[index];
|
||||||
|
return FadeTransition(
|
||||||
|
opacity: animation,
|
||||||
|
child: SlideTransition(
|
||||||
|
position: Tween<Offset>(
|
||||||
|
begin: const Offset(0, 0.2),
|
||||||
|
end: Offset.zero,
|
||||||
|
).animate(animation),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
onTap: () {
|
||||||
|
_stopPodcast();
|
||||||
|
|
||||||
|
String routeName;
|
||||||
|
Map<String, dynamic> arguments;
|
||||||
|
|
||||||
|
if (item.type == 'video') {
|
||||||
|
routeName = Routes.videoDetails;
|
||||||
|
arguments = {
|
||||||
|
'id': item.id,
|
||||||
|
'type': item.type,
|
||||||
|
'onMarkChanged': (int id, bool value) {
|
||||||
|
if (widget.pageData['onMarkChanged'] !=
|
||||||
|
null) {
|
||||||
|
widget.pageData['onMarkChanged'](
|
||||||
|
id, value, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (item.type == 'podcast') {
|
||||||
|
routeName = Routes.studioDetails;
|
||||||
|
arguments = {
|
||||||
|
'id': item.id,
|
||||||
|
'type': item.type,
|
||||||
|
'onMarkChanged': (int id, bool value,
|
||||||
|
[bool shouldUpdate = true]) {
|
||||||
|
if (widget.pageData['onMarkChanged'] !=
|
||||||
|
null) {
|
||||||
|
widget.pageData['onMarkChanged'](
|
||||||
|
id, value, shouldUpdate);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (item.type == 'news') {
|
||||||
|
routeName = Routes.newsDetails;
|
||||||
|
arguments = {
|
||||||
|
'id': item.id,
|
||||||
|
};
|
||||||
|
} else if (item.type == 'radar') {
|
||||||
|
routeName = Routes.radarDetails;
|
||||||
|
arguments = {
|
||||||
|
'id': item.id,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
routeName = Routes.studioDetails;
|
||||||
|
arguments = {
|
||||||
|
'id': item.id,
|
||||||
|
'type': item.type,
|
||||||
|
'onMarkChanged': (int id, bool value,
|
||||||
|
[bool shouldUpdate = true]) {
|
||||||
|
// Match original
|
||||||
|
if (widget.pageData['onMarkChanged'] !=
|
||||||
|
null) {
|
||||||
|
widget.pageData['onMarkChanged'](
|
||||||
|
id, value, shouldUpdate);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
routeName,
|
||||||
|
arguments: arguments,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: MultitypeOverview(
|
||||||
|
item: item,
|
||||||
|
onMarkChanged: (id, value) {
|
||||||
|
if (widget.pageData['onMarkChanged'] !=
|
||||||
|
null) {
|
||||||
|
widget.pageData['onMarkChanged'](
|
||||||
|
id, value, true); // Modified
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (state.studio.relatedContentsIsEmpty) {
|
||||||
|
return const Padding(
|
||||||
|
padding: EdgeInsets.all(32.0),
|
||||||
|
child: Center(
|
||||||
|
child: DidvanText(
|
||||||
|
'مطالب مرتبطی یافت نشد',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
|
||||||
|
_stopPodcast();
|
||||||
|
|
||||||
|
_mainAnimationController.dispose();
|
||||||
|
_playerAnimationController.dispose();
|
||||||
|
_titleAnimationController.dispose();
|
||||||
|
_tagsAnimationController.dispose();
|
||||||
|
_bookmarkAnimationController.dispose();
|
||||||
|
|
||||||
_videoPlayerController?.dispose();
|
_videoPlayerController?.dispose();
|
||||||
_chewieController?.dispose();
|
_chewieController?.dispose();
|
||||||
|
_focusNode.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:didvan/config/design_config.dart';
|
import 'package:didvan/config/design_config.dart';
|
||||||
|
|
@ -10,16 +11,13 @@ import 'package:didvan/models/view/action_sheet_data.dart';
|
||||||
import 'package:didvan/services/media/media.dart';
|
import 'package:didvan/services/media/media.dart';
|
||||||
import 'package:didvan/utils/action_sheet.dart';
|
import 'package:didvan/utils/action_sheet.dart';
|
||||||
import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart';
|
import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart';
|
||||||
import 'package:didvan/views/podcasts/podcasts_state.dart';
|
import 'package:didvan/views/home/media/widgets/audio_waveform_progress.dart';
|
||||||
import 'package:didvan/views/widgets/audio/audio_slider.dart';
|
|
||||||
import 'package:didvan/views/widgets/bookmark_button.dart';
|
|
||||||
import 'package:didvan/views/widgets/didvan/button.dart';
|
import 'package:didvan/views/widgets/didvan/button.dart';
|
||||||
import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
|
||||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||||
import 'package:didvan/views/widgets/ink_wrapper.dart';
|
import 'package:didvan/views/widgets/ink_wrapper.dart';
|
||||||
import 'package:didvan/views/widgets/item_title.dart';
|
|
||||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:just_audio/just_audio.dart';
|
import 'package:just_audio/just_audio.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
|
@ -30,217 +28,351 @@ class AudioPlayerWidget extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final state = context.read<StudioDetailsState>();
|
final state = context.read<StudioDetailsState>();
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(8)),
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
margin: const EdgeInsets.symmetric(vertical: 20),
|
|
||||||
height: 3,
|
|
||||||
width: 50,
|
|
||||||
color: Theme.of(context).colorScheme.hint,
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
child: SkeletonImage(
|
|
||||||
imageUrl: podcast.image,
|
|
||||||
aspectRatio: 1 / 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
DidvanText(
|
|
||||||
podcast.title,
|
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
StreamBuilder<double>(
|
|
||||||
stream: MediaService.audioPlayer.speedStream,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (!snapshot.hasData) {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Column(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
PopupMenuButton(
|
SkeletonImage(
|
||||||
child: Container(
|
imageUrl: podcast.image,
|
||||||
width: 46,
|
aspectRatio: 1 / 1.3,
|
||||||
alignment: Alignment.center,
|
borderRadius: BorderRadius.circular(0),
|
||||||
margin: const EdgeInsets.fromLTRB(12, 0, 0, 46),
|
),
|
||||||
padding: const EdgeInsets.only(top: 2),
|
|
||||||
decoration: BoxDecoration(
|
Positioned(
|
||||||
borderRadius: DesignConfig.mediumBorderRadius,
|
bottom: 0,
|
||||||
border: Border.all(
|
left: 0,
|
||||||
color:
|
right: 0,
|
||||||
Theme.of(context).colorScheme.title)),
|
child: Container(
|
||||||
child: DidvanText(
|
decoration: BoxDecoration(
|
||||||
'${snapshot.data!.toString().replaceAll('.0', '')}X'),
|
borderRadius:
|
||||||
),
|
const BorderRadius.vertical(top: Radius.circular(24.0)),
|
||||||
onSelected: (value) async {
|
color: Theme.of(context).colorScheme.surface,
|
||||||
await MediaService.audioPlayer.setSpeed(value);
|
),
|
||||||
},
|
child: Column(
|
||||||
itemBuilder: (BuildContext context) =>
|
mainAxisSize: MainAxisSize.min,
|
||||||
<PopupMenuEntry>[
|
children: [
|
||||||
popUpSpeed(value: 0.5),
|
const SizedBox(
|
||||||
popUpSpeed(value: 0.75),
|
height: 30,
|
||||||
popUpSpeed(value: 1.0),
|
),
|
||||||
popUpSpeed(value: 1.25),
|
Padding(
|
||||||
popUpSpeed(value: 1.5),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
popUpSpeed(value: 2.0),
|
child: Row(
|
||||||
],
|
children: [
|
||||||
),
|
StreamBuilder<double>(
|
||||||
],
|
stream: MediaService.audioPlayer.speedStream,
|
||||||
);
|
builder: (context, snapshot) {
|
||||||
}),
|
if (!snapshot.hasData) {
|
||||||
Expanded(
|
return const SizedBox();
|
||||||
child: AudioSlider(
|
}
|
||||||
tag: 'podcast-${podcast.id}',
|
|
||||||
showTimer: true,
|
return const Column(
|
||||||
duration: podcast.duration,
|
children: [
|
||||||
|
SizedBox(),
|
||||||
|
// PopupMenuButton(
|
||||||
|
// child: Container(
|
||||||
|
// width: 46,
|
||||||
|
// alignment: Alignment.center,
|
||||||
|
// margin: const EdgeInsets.fromLTRB(12, 0, 0, 0), // تغییر: 46 پایین حذف شد
|
||||||
|
// padding: const EdgeInsets.only(top: 2),
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// borderRadius: DesignConfig.mediumBorderRadius,
|
||||||
|
// border: Border.all(
|
||||||
|
// color:
|
||||||
|
// Theme.of(context).colorScheme.title)),
|
||||||
|
// child: DidvanText(
|
||||||
|
// '${snapshot.data!.toString().replaceAll('.0', '')}X'),
|
||||||
|
// ),
|
||||||
|
// onSelected: (value) async {
|
||||||
|
// await MediaService.audioPlayer.setSpeed(value);
|
||||||
|
// },
|
||||||
|
// itemBuilder: (BuildContext context) =>
|
||||||
|
// <PopupMenuEntry>[
|
||||||
|
// popUpSpeed(value: 0.5),
|
||||||
|
// popUpSpeed(value: 0.75),
|
||||||
|
// popUpSpeed(value: 1.0),
|
||||||
|
// popUpSpeed(value: 1.25),
|
||||||
|
// popUpSpeed(value: 1.5),
|
||||||
|
// popUpSpeed(value: 2.0),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
Expanded(
|
||||||
|
child: StreamBuilder<Duration>(
|
||||||
|
stream: MediaService.audioPlayer.positionStream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final position = snapshot.data ?? Duration.zero;
|
||||||
|
|
||||||
|
return StreamBuilder<Duration?>(
|
||||||
|
stream: MediaService.audioPlayer.durationStream,
|
||||||
|
builder: (context, durationSnapshot) {
|
||||||
|
final totalDuration = durationSnapshot.data ??
|
||||||
|
Duration(milliseconds: podcast.duration);
|
||||||
|
final progress =
|
||||||
|
totalDuration.inMilliseconds > 0
|
||||||
|
? position.inMilliseconds /
|
||||||
|
totalDuration.inMilliseconds
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
_formatDuration(totalDuration),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.title
|
||||||
|
.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
|
||||||
|
Expanded(
|
||||||
|
child: AudioWaveformProgress(
|
||||||
|
progress: progress.clamp(0.0, 1.0),
|
||||||
|
isActive: true,
|
||||||
|
onChanged: (value) {
|
||||||
|
final newPosition = Duration(
|
||||||
|
milliseconds:
|
||||||
|
(totalDuration.inMilliseconds *
|
||||||
|
value)
|
||||||
|
.round(),
|
||||||
|
);
|
||||||
|
MediaService.audioPlayer
|
||||||
|
.seek(newPosition);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
|
||||||
|
Text(
|
||||||
|
_formatDuration(position),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.title
|
||||||
|
.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: StatefulBuilder(
|
||||||
|
builder: (context, setState) => Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(8, 8, 20, 8),
|
||||||
|
child: Center(
|
||||||
|
child: IconButton(
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
'lib/assets/icons/timer-pause.svg',
|
||||||
|
width: 35,
|
||||||
|
height: 35,
|
||||||
|
colorFilter: const ColorFilter.mode(
|
||||||
|
Color.fromARGB(255, 102, 102, 102),
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () => _showSleepTimer(
|
||||||
|
context,
|
||||||
|
state,
|
||||||
|
() => setState(() {}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// DidvanIconButton(
|
||||||
|
// icon: state.timer == null &&
|
||||||
|
// !state.stopOnPodcastEnds
|
||||||
|
// ? DidvanIcons.sleep_timer_regular
|
||||||
|
// : DidvanIcons.sleep_enabled_regular,
|
||||||
|
// color: Theme.of(context).colorScheme.title,
|
||||||
|
// onPressed: () => _showSleepTimer(
|
||||||
|
// context,
|
||||||
|
// state,
|
||||||
|
// () => setState(() {}),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// if (state.timer != null)
|
||||||
|
// DidvanText(
|
||||||
|
// state.stopOnPodcastEnds
|
||||||
|
// ? 'پایان پادکست'
|
||||||
|
// : '\'${state.timerValue}',
|
||||||
|
// isEnglishFont: true,
|
||||||
|
// style: Theme.of(context).textTheme.labelSmall,
|
||||||
|
// color: Theme.of(context).colorScheme.title,
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
MediaService.audioPlayer.seek(
|
||||||
|
Duration(
|
||||||
|
seconds: max(
|
||||||
|
0,
|
||||||
|
MediaService
|
||||||
|
.audioPlayer.position.inSeconds +
|
||||||
|
10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
'lib/assets/icons/forward-10-seconds.svg'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: StreamBuilder<PlayerState>(
|
||||||
|
stream: MediaService.audioPlayer.playerStateStream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return const CircularProgressIndicator();
|
||||||
|
}
|
||||||
|
return StreamBuilder<bool>(
|
||||||
|
stream: MediaService.audioPlayer.playingStream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final isPlaying = snapshot.data ?? false;
|
||||||
|
return _PlayPouseAnimatedIcon(
|
||||||
|
audioSource: podcast.link,
|
||||||
|
id: podcast.id,
|
||||||
|
isPlaying: isPlaying,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
MediaService.audioPlayer.seek(
|
||||||
|
Duration(
|
||||||
|
seconds: max(
|
||||||
|
0,
|
||||||
|
MediaService
|
||||||
|
.audioPlayer.position.inSeconds -
|
||||||
|
5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
'lib/assets/icons/backward-5-seconds.svg'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: StreamBuilder<double>(
|
||||||
|
stream: MediaService.audioPlayer.speedStream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
return Center(
|
||||||
|
child: PopupMenuButton(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12.0),
|
||||||
|
child: Container(
|
||||||
|
width: 46,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
margin: const EdgeInsets.fromLTRB(
|
||||||
|
12, 0, 0, 0), // تغییر: 46 پایین حذف شد
|
||||||
|
padding: const EdgeInsets.only(top: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius:
|
||||||
|
DesignConfig.mediumBorderRadius,
|
||||||
|
border: Border.all(
|
||||||
|
color: const Color.fromARGB(
|
||||||
|
255, 102, 102, 102))),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: DidvanText(
|
||||||
|
'${snapshot.data!.toString().replaceAll('.0', '')}X',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w900,
|
||||||
|
color: Color.fromARGB(
|
||||||
|
255, 102, 102, 102)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSelected: (value) async {
|
||||||
|
await MediaService.audioPlayer.setSpeed(value);
|
||||||
|
},
|
||||||
|
itemBuilder: (BuildContext context) =>
|
||||||
|
<PopupMenuEntry>[
|
||||||
|
popUpSpeed(value: 0.5),
|
||||||
|
popUpSpeed(value: 0.75),
|
||||||
|
popUpSpeed(value: 1.0),
|
||||||
|
popUpSpeed(value: 1.25),
|
||||||
|
popUpSpeed(value: 1.5),
|
||||||
|
popUpSpeed(value: 2.0),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// اضافه کردن کمی فاصله در پایین
|
||||||
|
const SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Row(
|
),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
],
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: StatefulBuilder(
|
|
||||||
builder: (context, setState) => Column(
|
|
||||||
children: [
|
|
||||||
DidvanIconButton(
|
|
||||||
icon: state.timer == null && !state.stopOnPodcastEnds
|
|
||||||
? DidvanIcons.sleep_timer_regular
|
|
||||||
: DidvanIcons.sleep_enabled_regular,
|
|
||||||
color: Theme.of(context).colorScheme.title,
|
|
||||||
onPressed: () => _showSleepTimer(
|
|
||||||
context,
|
|
||||||
state,
|
|
||||||
() => setState(() {}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (state.timer != null)
|
|
||||||
DidvanText(
|
|
||||||
state.stopOnPodcastEnds
|
|
||||||
? 'پایان پادکست'
|
|
||||||
: '\'${state.timerValue}',
|
|
||||||
isEnglishFont: true,
|
|
||||||
style: Theme.of(context).textTheme.labelSmall,
|
|
||||||
color: Theme.of(context).colorScheme.title,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
DidvanIconButton(
|
|
||||||
color: Theme.of(context).colorScheme.title,
|
|
||||||
size: 32,
|
|
||||||
icon: DidvanIcons.media_forward_solid,
|
|
||||||
onPressed: () {
|
|
||||||
MediaService.audioPlayer.seek(
|
|
||||||
Duration(
|
|
||||||
seconds:
|
|
||||||
MediaService.audioPlayer.position.inSeconds +
|
|
||||||
30,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
DidvanText(
|
|
||||||
'30',
|
|
||||||
isEnglishFont: true,
|
|
||||||
color: Theme.of(context).colorScheme.title,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: StreamBuilder<PlayerState>(
|
|
||||||
stream: MediaService.audioPlayer.playerStateStream,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.data == null) {
|
|
||||||
return const CircularProgressIndicator();
|
|
||||||
}
|
|
||||||
return StreamBuilder<bool>(
|
|
||||||
stream: MediaService.audioPlayer.playingStream,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
return _PlayPouseAnimatedIcon(
|
|
||||||
audioSource: podcast.link,
|
|
||||||
id: podcast.id,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
DidvanIconButton(
|
|
||||||
size: 32,
|
|
||||||
icon: DidvanIcons.media_backward_solid,
|
|
||||||
color: Theme.of(context).colorScheme.title,
|
|
||||||
onPressed: () {
|
|
||||||
MediaService.audioPlayer.seek(
|
|
||||||
Duration(
|
|
||||||
seconds: max(
|
|
||||||
0,
|
|
||||||
MediaService.audioPlayer.position.inSeconds -
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
DidvanText(
|
|
||||||
'10',
|
|
||||||
isEnglishFont: true,
|
|
||||||
color: Theme.of(context).colorScheme.title,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: BookmarkButton(
|
|
||||||
itemId: state.studio.id,
|
|
||||||
type: 'podcast',
|
|
||||||
gestureSize: 48,
|
|
||||||
color: Theme.of(context).colorScheme.title,
|
|
||||||
value: podcast.marked,
|
|
||||||
onMarkChanged: (value) => context
|
|
||||||
.read<PodcastsState>()
|
|
||||||
.changeMark(podcast.id, value, true),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _formatDuration(Duration duration) {
|
||||||
|
String twoDigits(int n) => n.toString().padLeft(2, '0');
|
||||||
|
final hours = duration.inHours;
|
||||||
|
final minutes = duration.inMinutes.remainder(60);
|
||||||
|
final seconds = duration.inSeconds.remainder(60);
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return '$hours:${twoDigits(minutes)}:${twoDigits(seconds)}';
|
||||||
|
} else {
|
||||||
|
return '${twoDigits(minutes)}:${twoDigits(seconds)}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PopupMenuItem<dynamic> popUpSpeed({required double value}) {
|
PopupMenuItem<dynamic> popUpSpeed({required double value}) {
|
||||||
return PopupMenuItem(
|
return PopupMenuItem(
|
||||||
value: value,
|
value: value,
|
||||||
|
|
@ -272,9 +404,22 @@ class AudioPlayerWidget extends StatelessWidget {
|
||||||
content: StatefulBuilder(
|
content: StatefulBuilder(
|
||||||
builder: (context, setState) => Column(
|
builder: (context, setState) => Column(
|
||||||
children: [
|
children: [
|
||||||
const ItemTitle(
|
Row(
|
||||||
title: 'زمان خواب',
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
icon: DidvanIcons.sleep_timer_regular,
|
children: [
|
||||||
|
SvgPicture.asset(
|
||||||
|
'lib/assets/icons/timer-pause.svg',
|
||||||
|
height: 24,
|
||||||
|
color: const Color.fromARGB(255, 102, 102, 102),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
'زمان خواب',
|
||||||
|
style: TextStyle(color: Color.fromARGB(255, 102, 102, 102)),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
DidvanText(
|
DidvanText(
|
||||||
|
|
@ -378,41 +523,19 @@ class AudioPlayerWidget extends StatelessWidget {
|
||||||
class _PlayPouseAnimatedIcon extends StatefulWidget {
|
class _PlayPouseAnimatedIcon extends StatefulWidget {
|
||||||
final String audioSource;
|
final String audioSource;
|
||||||
final int id;
|
final int id;
|
||||||
|
final bool isPlaying;
|
||||||
const _PlayPouseAnimatedIcon(
|
const _PlayPouseAnimatedIcon(
|
||||||
{Key? key, required this.audioSource, required this.id})
|
{Key? key,
|
||||||
|
required this.audioSource,
|
||||||
|
required this.id,
|
||||||
|
required this.isPlaying})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_PlayPouseAnimatedIcon> createState() => __PlayPouseAnimatedIconState();
|
State<_PlayPouseAnimatedIcon> createState() => __PlayPouseAnimatedIconState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class __PlayPouseAnimatedIconState extends State<_PlayPouseAnimatedIcon>
|
class __PlayPouseAnimatedIconState extends State<_PlayPouseAnimatedIcon> {
|
||||||
with SingleTickerProviderStateMixin {
|
|
||||||
late final AnimationController _animationController;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(covariant _PlayPouseAnimatedIcon oldWidget) {
|
|
||||||
_handleAnimation();
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_animationController = AnimationController(
|
|
||||||
vsync: this,
|
|
||||||
duration: DesignConfig.lowAnimationDuration,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleAnimation() {
|
|
||||||
if (MediaService.audioPlayer.playing) {
|
|
||||||
_animationController.forward();
|
|
||||||
} else {
|
|
||||||
_animationController.reverse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InkWrapper(
|
return InkWrapper(
|
||||||
|
|
@ -423,27 +546,14 @@ class __PlayPouseAnimatedIconState extends State<_PlayPouseAnimatedIcon>
|
||||||
isVoiceMessage: false,
|
isVoiceMessage: false,
|
||||||
id: widget.id,
|
id: widget.id,
|
||||||
);
|
);
|
||||||
_handleAnimation();
|
|
||||||
},
|
},
|
||||||
child: Container(
|
child: SvgPicture.asset(
|
||||||
padding: const EdgeInsets.all(8),
|
widget.isPlaying
|
||||||
decoration: BoxDecoration(
|
? 'lib/assets/icons/pause-circle.svg'
|
||||||
color: Theme.of(context).colorScheme.title,
|
: 'lib/assets/icons/video-circle.svg',
|
||||||
shape: BoxShape.circle,
|
width: 65,
|
||||||
),
|
height: 65,
|
||||||
child: AnimatedIcon(
|
|
||||||
size: 40,
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
icon: AnimatedIcons.play_pause,
|
|
||||||
progress: _animationController,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_animationController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -189,11 +189,11 @@ class _Carousel3DState extends State<Carousel3D> with SingleTickerProviderStateM
|
||||||
return AnimatedContainer(
|
return AnimatedContainer(
|
||||||
duration: const Duration(milliseconds: 150),
|
duration: const Duration(milliseconds: 150),
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 3),
|
margin: const EdgeInsets.symmetric(horizontal: 3),
|
||||||
width: _currentIndex == index ? 12.0 : 8.0,
|
width: _currentIndex == index ? 9.0 : 7.0,
|
||||||
height: _currentIndex == index ? 12.0 : 8.0,
|
height: _currentIndex == index ? 9.0 : 7.0,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _currentIndex == index
|
color: _currentIndex == index
|
||||||
? const Color.fromRGBO(0, 69, 92, 1)
|
? const Color.fromARGB(255, 0, 126, 167)
|
||||||
: Colors.grey.withOpacity(0.5),
|
: Colors.grey.withOpacity(0.5),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue