didvan-app/lib/views/podcasts/studio_details/widgets/studio_details_widget.dart

350 lines
14 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

import 'dart:math';
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/studio_details_data.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/details_tab_bar.dart';
import 'package:didvan/views/widgets/overview/multitype.dart';
import 'package:didvan/views/widgets/tag_item.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
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';
class StudioDetailsWidget extends StatelessWidget {
final void Function(int id, bool value) onMarkChanged;
const StudioDetailsWidget({
Key? key,
required this.onMarkChanged,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final ds = MediaQuery.of(context).size;
return SafeArea(
bottom: true,
child: Consumer<StudioDetailsState>(
builder: (context, state, child) {
bool isVideo = state.studio.iframe != null;
double topOffset = isVideo
? ds.width * 9 / 16
: 400;
return Container(
height: max(
ds.height - topOffset - 72 - MediaQuery.of(context).padding.top,
0),
color: Theme.of(context).colorScheme.surface,
child: Stack(
children: [
Positioned(
top: 72,
left: 0,
right: 0,
bottom: 0,
child: StateHandler<StudioDetailsState>(
onRetry: () {},
state: state,
builder: (context, state) {
if (state.selectedDetailsIndex == 0) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
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,
),
},
),
if (state.studio.tags.isNotEmpty)
const SizedBox(height: 20),
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) =>
_onMarkChanged(id, value, state),
type: isVideo ? '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(),
],
),
],
),
);
}
if (state.selectedDetailsIndex == 1) {
return ChangeNotifierProvider<CommentsState>(
create: (context) => CommentsState(),
child: SizedBox(
height: ds.height -
ds.width * 9 / 16 -
172 -
MediaQuery.of(context).padding.top,
child: Comments(
pageData: {
'id': state.studio.id,
'type': 'studio',
'title': state.studio.title,
'onCommentsChanged': state.onCommentsChanged,
'isPage': false,
},
),
),
);
}
return Padding(
padding: const EdgeInsets.only(top: 24.0),
child: Column(
children: [
if (state.studio.relatedContents.isEmpty &&
!state.studio.relatedContentsIsEmpty)
for (var i = 0; i < 3; i++)
Padding(
padding: const EdgeInsets.only(
bottom: 8,
left: 16,
right: 16,
),
child: MultitypeOverview.placeholder,
),
for (var i = 0;
i < state.studio.relatedContents.length;
i++)
Padding(
padding: const EdgeInsets.only(
bottom: 8,
left: 16,
right: 16,
),
child: MultitypeOverview(
item: state.studio.relatedContents[i],
onMarkChanged: (id, value) {},
),
),
],
),
);
},
),
),
DetailsTabBar(
isVideo: isVideo,
),
],
),
);
},
),
);
}
void _onMarkChanged(id, value, state) {
onMarkChanged(id, value);
if (state.studio.id == id) {
state.studio.marked = value;
} else if (state.nextStudio?.id == id) {
state.nextStudio!.marked = value;
} else if (state.prevStudio?.id == id) {
state.prevStudio!.marked = value;
}
}
}
class StudioPreview extends StatelessWidget {
final bool isNext;
final StudioDetailsData studio;
const StudioPreview({
Key? key,
required this.isNext,
required this.studio,
}) : super(key: key);
String get _previewTitle {
if (studio.type == 'video') {
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(
onTap: () {
final state = context.read<StudioDetailsState>();
state.getStudioDetails(
isNext ? state.nextStudio!.id : state.prevStudio!.id,
args: state.args,
isForward: isNext,
);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: 170,
height: 235,
decoration: BoxDecoration(
color: DesignConfig.isDark? const Color.fromARGB(255, 83, 83, 83): 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: Theme.of(context).colorScheme.caption,
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: DesignConfig.isDark? Colors.white70 : Colors.black87,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row(
children: [
SvgPicture.asset(
'lib/assets/icons/clock.svg',
color: Theme.of(context).colorScheme.caption,
),
const SizedBox(width: 4),
DidvanText(
_formatDuration(studio.duration).toPersianDigit(),
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(
color: Theme.of(context).colorScheme.caption,
),
),
],
),
],
),
),
),
],
),
),
),
);
}
static Widget get placeHolder => const SizedBox(
width: 88,
height: 216,
child: Column(
children: [
ShimmerPlaceholder(width: 88, height: 88),
SizedBox(height: 8),
ShimmerPlaceholder(height: 20, width: 20),
SizedBox(height: 16),
ShimmerPlaceholder(height: 14, width: 60),
SizedBox(height: 16),
ShimmerPlaceholder(height: 12, width: double.infinity),
SizedBox(height: 8),
ShimmerPlaceholder(height: 12, width: 40),
],
),
);
}