1051 lines
46 KiB
Dart
1051 lines
46 KiB
Dart
// ignore_for_file: deprecated_member_use
|
|
|
|
import 'dart:async';
|
|
import 'dart:math';
|
|
|
|
import 'package:carousel_slider/carousel_slider.dart';
|
|
import 'package:didvan/config/design_config.dart';
|
|
import 'package:didvan/config/theme_data.dart';
|
|
import 'package:didvan/constants/app_icons.dart';
|
|
import 'package:didvan/main.dart';
|
|
import 'package:didvan/models/requests/news.dart';
|
|
import 'package:didvan/models/requests/radar.dart';
|
|
import 'package:didvan/routes/routes.dart';
|
|
import 'package:didvan/services/app_initalizer.dart';
|
|
import 'package:didvan/services/media/media.dart';
|
|
import 'package:didvan/utils/action_sheet.dart';
|
|
import 'package:didvan/views/home/media/widgets/audio_waveform_progress.dart';
|
|
import 'package:didvan/views/widgets/animated_visibility.dart';
|
|
import 'package:didvan/views/widgets/didvan/button.dart';
|
|
import 'package:didvan/views/widgets/didvan/card.dart';
|
|
import 'package:didvan/views/widgets/didvan/divider.dart';
|
|
import 'package:didvan/views/widgets/didvan/text.dart';
|
|
import 'package:didvan/views/widgets/ink_wrapper.dart';
|
|
import 'package:didvan/views/widgets/item_title.dart';
|
|
import 'package:didvan/views/widgets/overview/multitype.dart';
|
|
import 'package:didvan/views/widgets/skeleton_image.dart';
|
|
import 'package:didvan/views/widgets/tag_item.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_html/flutter_html.dart';
|
|
import 'package:flutter_svg/svg.dart';
|
|
import 'package:just_audio/just_audio.dart';
|
|
import 'package:persian_number_utility/persian_number_utility.dart';
|
|
import 'package:url_launcher/url_launcher_string.dart';
|
|
|
|
class DidvanPageView extends StatefulWidget {
|
|
final List items;
|
|
final int initialIndex;
|
|
final int currentIndex;
|
|
final bool isRadar;
|
|
final void Function(int id, bool value) onMarkChanged;
|
|
final ScrollController scrollController;
|
|
|
|
final void Function(int index) onPageChanged;
|
|
const DidvanPageView({
|
|
Key? key,
|
|
required this.initialIndex,
|
|
required this.items,
|
|
required this.scrollController,
|
|
required this.onPageChanged,
|
|
required this.isRadar,
|
|
required this.currentIndex,
|
|
required this.onMarkChanged,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
State<DidvanPageView> createState() => _DidvanPageViewState();
|
|
}
|
|
|
|
class _DidvanPageViewState extends State<DidvanPageView> {
|
|
static String? _lastPlayedUrl;
|
|
|
|
String _formatDuration(Duration duration) {
|
|
String twoDigits(int n) => n.toString().padLeft(2, "0");
|
|
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
|
|
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
|
|
return "${duration.inHours > 0 ? '${twoDigits(duration.inHours)}:' : ''}$twoDigitMinutes:$twoDigitSeconds";
|
|
}
|
|
|
|
PopupMenuItem<double> popUpSpeed({required double value}) {
|
|
return PopupMenuItem<double>(
|
|
value: value,
|
|
child: DidvanText('$value X'),
|
|
);
|
|
}
|
|
|
|
String _getCategoryIcon(String label) {
|
|
if (label.contains('سیاسی')) {
|
|
return 'lib/assets/icons/SiasiNoBG.svg';
|
|
} else if (label.contains('کسب')) {
|
|
return 'lib/assets/icons/KasbokarNoBG.svg';
|
|
} else if (label.contains('اقتصادی')) {
|
|
return 'lib/assets/icons/Economic_NoBG.svg';
|
|
} else if (label.contains('اجتماعی')) {
|
|
return 'lib/assets/icons/EjtemaeiNoBg.svg';
|
|
} else if (label.contains('محیطی') || label.contains('زیست')) {
|
|
return 'lib/assets/icons/ZistMohitNoBG.svg';
|
|
} else if (label.contains('فناوری')) {
|
|
return 'lib/assets/icons/FanavariNoBG.svg';
|
|
}
|
|
return 'lib/assets/icons/discovery.svg';
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
MediaService.audioPlayer.stop();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: Theme.of(context).colorScheme.background,
|
|
appBar: PreferredSize(
|
|
preferredSize: const Size.fromHeight(90.0),
|
|
child: AppBar(
|
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
|
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: Theme.of(context).colorScheme.caption,
|
|
height: 24,
|
|
),
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
)),
|
|
body: Column(
|
|
children: [
|
|
Expanded(
|
|
child: Stack(
|
|
children: [
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: CarouselSlider.builder(
|
|
itemCount: widget.items.length,
|
|
options: CarouselOptions(
|
|
onPageChanged: (index, reason) =>
|
|
widget.onPageChanged(index),
|
|
height: double.infinity,
|
|
initialPage: widget.initialIndex,
|
|
viewportFraction: 1.0,
|
|
enableInfiniteScroll: false,
|
|
scrollPhysics: const BouncingScrollPhysics(),
|
|
),
|
|
itemBuilder: (context, index, realIndex) {
|
|
final item = widget.items[index];
|
|
if (item == null) {
|
|
return const SizedBox();
|
|
}
|
|
final bool isActive = index == widget.currentIndex;
|
|
|
|
return Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
final double imageHeight =
|
|
constraints.maxWidth / (16 / 9);
|
|
const double overlap = 24.0;
|
|
|
|
return SizedBox(
|
|
height: constraints.maxHeight,
|
|
child: SingleChildScrollView(
|
|
controller:
|
|
isActive ? widget.scrollController : null,
|
|
physics: const BouncingScrollPhysics(),
|
|
padding: const EdgeInsets.only(bottom: 92),
|
|
child: Stack(
|
|
children: [
|
|
Positioned(
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
height: imageHeight,
|
|
child: SkeletonImage(
|
|
imageUrl: item.image,
|
|
borderRadius: BorderRadius.zero,
|
|
width: double.infinity,
|
|
),
|
|
),
|
|
if (widget.isRadar &&
|
|
item.categories != null &&
|
|
item.categories.isNotEmpty)
|
|
Positioned(
|
|
top: 16,
|
|
left: 16,
|
|
child: Wrap(
|
|
spacing: 8.0,
|
|
runSpacing: 8.0,
|
|
direction: Axis.horizontal,
|
|
alignment: WrapAlignment.start,
|
|
children: [
|
|
for (var i = 0;
|
|
i < item.categories.length;
|
|
i++)
|
|
Container(
|
|
padding:
|
|
const EdgeInsets.symmetric(
|
|
horizontal: 12,
|
|
vertical: 6),
|
|
decoration: BoxDecoration(
|
|
color: const Color.fromARGB(
|
|
255, 230, 242, 246),
|
|
borderRadius:
|
|
BorderRadius.circular(16),
|
|
),
|
|
child: Row(
|
|
mainAxisSize:
|
|
MainAxisSize.min,
|
|
children: [
|
|
SvgPicture.asset(
|
|
_getCategoryIcon(item
|
|
.categories[i].label),
|
|
height: 16,
|
|
width: 16,
|
|
color:
|
|
const Color.fromARGB(
|
|
255, 25, 93, 128),
|
|
),
|
|
const SizedBox(width: 6),
|
|
DidvanText(
|
|
item.categories[i].label,
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.bodySmall
|
|
?.copyWith(
|
|
color: const Color
|
|
.fromARGB(255,
|
|
25, 93, 128),
|
|
fontWeight:
|
|
FontWeight.bold,
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Container(
|
|
margin: EdgeInsets.only(
|
|
top: imageHeight - overlap),
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context)
|
|
.colorScheme
|
|
.background,
|
|
borderRadius:
|
|
const BorderRadius.vertical(
|
|
top: Radius.circular(24),
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color:
|
|
Colors.black.withOpacity(0.05),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, -5),
|
|
),
|
|
],
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 24),
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.start,
|
|
children: [
|
|
if (widget.isRadar &&
|
|
item.podcast != null)
|
|
_AnimatedEntry(
|
|
isActive: isActive,
|
|
delay: 0,
|
|
child: _buildPodcastStylePlayer(
|
|
context, item),
|
|
),
|
|
_AnimatedEntry(
|
|
isActive: isActive,
|
|
delay: 100,
|
|
child: Padding(
|
|
padding:
|
|
const EdgeInsets.symmetric(
|
|
horizontal: 16),
|
|
child: DidvanText(
|
|
item.title,
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleMedium,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
if (!widget.isRadar &&
|
|
item.description != null &&
|
|
item.description!.isNotEmpty)
|
|
_AnimatedEntry(
|
|
isActive: isActive,
|
|
delay: 200,
|
|
child: Padding(
|
|
padding: const EdgeInsets
|
|
.symmetric(
|
|
horizontal: 16),
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment
|
|
.start,
|
|
children: [
|
|
DidvanText(
|
|
'خلاصه خبر:',
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.bodyMedium
|
|
?.copyWith(
|
|
color: Theme.of(
|
|
context)
|
|
.colorScheme
|
|
.primary,
|
|
fontWeight:
|
|
FontWeight
|
|
.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
DidvanText(
|
|
item.description!,
|
|
style:
|
|
Theme.of(context)
|
|
.textTheme
|
|
.bodyMedium
|
|
?.copyWith(
|
|
color: const Color
|
|
.fromARGB(
|
|
255,
|
|
102,
|
|
102,
|
|
102),
|
|
)),
|
|
const SizedBox(height: 2),
|
|
const DidvanDivider(),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
if (widget.isRadar &&
|
|
item.description != null &&
|
|
item.description!.isNotEmpty)
|
|
_AnimatedEntry(
|
|
isActive: isActive,
|
|
delay: 200,
|
|
child: Padding(
|
|
padding: const EdgeInsets
|
|
.symmetric(
|
|
horizontal: 16),
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment
|
|
.start,
|
|
children: [
|
|
DidvanText(
|
|
'خلاصه پویش افق:',
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.bodyMedium
|
|
?.copyWith(
|
|
color: Theme.of(
|
|
context)
|
|
.colorScheme
|
|
.primary,
|
|
fontWeight:
|
|
FontWeight
|
|
.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
DidvanText(
|
|
item.description!,
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.bodyMedium
|
|
?.copyWith(
|
|
color: const Color
|
|
.fromARGB(
|
|
255,
|
|
102,
|
|
102,
|
|
102),
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
const DidvanDivider(),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
_AnimatedEntry(
|
|
isActive: isActive,
|
|
delay: 300,
|
|
child: Padding(
|
|
padding:
|
|
const EdgeInsets.symmetric(
|
|
horizontal: 16),
|
|
child: _subtitle(item),
|
|
),
|
|
),
|
|
_AnimatedEntry(
|
|
isActive: isActive,
|
|
delay: 400,
|
|
child: Column(
|
|
children: [
|
|
for (var i = 0;
|
|
i < item.contents.length;
|
|
i++)
|
|
Padding(
|
|
padding: const EdgeInsets
|
|
.symmetric(
|
|
horizontal: 16,
|
|
vertical: 4,
|
|
),
|
|
child: _contentBuilder(
|
|
item, i),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (item.tags.isNotEmpty)
|
|
const SizedBox(height: 20),
|
|
Padding(
|
|
padding:
|
|
const EdgeInsets.symmetric(
|
|
horizontal: 16),
|
|
child: Wrap(
|
|
spacing: 8,
|
|
runSpacing: 8,
|
|
children: [
|
|
for (var i = 0;
|
|
i < item.tags.length;
|
|
i++)
|
|
TagItem(
|
|
tag: item.tags[i],
|
|
onMarkChanged:
|
|
widget.onMarkChanged,
|
|
type: widget.isRadar
|
|
? 'radar'
|
|
: 'news',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const Padding(
|
|
padding: EdgeInsets.only(
|
|
left: 16,
|
|
right: 16,
|
|
top: 16,
|
|
),
|
|
child: DidvanDivider(
|
|
verticalPadding: 0),
|
|
),
|
|
const Padding(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 16.0,
|
|
vertical: 16.0),
|
|
child: ItemTitle(
|
|
title: 'مطالب مرتبط:'),
|
|
),
|
|
if (item.relatedContents.isEmpty &&
|
|
!item.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 < item.relatedContents.length;
|
|
i++)
|
|
Padding(
|
|
padding:
|
|
const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 4),
|
|
child: MultitypeOverview(
|
|
item: item.relatedContents[i],
|
|
onMarkChanged: (id, value) {},
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPodcastStylePlayer(BuildContext context, dynamic item) {
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const SizedBox(
|
|
height: 16,
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: Row(
|
|
children: [
|
|
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(
|
|
minutes:
|
|
int.tryParse(item.duration.toString()) ??
|
|
0);
|
|
|
|
final progress = totalDuration.inMilliseconds > 0
|
|
? position.inMilliseconds /
|
|
totalDuration.inMilliseconds
|
|
: 0.0;
|
|
|
|
return Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
DidvanText(
|
|
_formatDuration(totalDuration),
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Theme.of(context).colorScheme.caption,
|
|
),
|
|
),
|
|
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),
|
|
DidvanText(
|
|
_formatDuration(position),
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Theme.of(context).colorScheme.caption,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
IconButton(
|
|
icon: SvgPicture.asset(
|
|
'lib/assets/icons/timer-pause.svg',
|
|
width: 24,
|
|
height: 24,
|
|
color: Theme.of(context).colorScheme.caption,
|
|
),
|
|
onPressed: () {
|
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
|
content: Text("تایمر خواب در این بخش فعال نیست")));
|
|
},
|
|
),
|
|
IconButton(
|
|
onPressed: () {
|
|
MediaService.audioPlayer.seek(
|
|
Duration(
|
|
seconds: max(
|
|
0, MediaService.audioPlayer.position.inSeconds + 10),
|
|
),
|
|
);
|
|
},
|
|
icon: SvgPicture.asset('lib/assets/icons/forward-10-seconds.svg'),
|
|
),
|
|
StreamBuilder<PlayerState>(
|
|
stream: MediaService.audioPlayer.playerStateStream,
|
|
builder: (context, snapshot) {
|
|
final playerState = snapshot.data;
|
|
final processingState = playerState?.processingState;
|
|
final playing = playerState?.playing;
|
|
|
|
if (processingState == ProcessingState.loading ||
|
|
processingState == ProcessingState.buffering) {
|
|
return Container(
|
|
margin: const EdgeInsets.all(8.0),
|
|
width: 48.0,
|
|
height: 48.0,
|
|
child: const CircularProgressIndicator(),
|
|
);
|
|
} else if (playing != true) {
|
|
return IconButton(
|
|
icon: const Icon(Icons.play_circle_fill, size: 68),
|
|
color: Theme.of(context).primaryColor,
|
|
onPressed: () async {
|
|
if (_lastPlayedUrl != item.podcast) {
|
|
_lastPlayedUrl = item.podcast;
|
|
await MediaService.audioPlayer.setUrl(item.podcast);
|
|
}
|
|
MediaService.audioPlayer.play();
|
|
},
|
|
);
|
|
} else if (processingState != ProcessingState.completed) {
|
|
return IconButton(
|
|
icon: const Icon(Icons.pause_circle_filled, size: 68),
|
|
color: Theme.of(context).primaryColor,
|
|
onPressed: MediaService.audioPlayer.pause,
|
|
);
|
|
} else {
|
|
return IconButton(
|
|
icon: const Icon(Icons.replay_circle_filled, size: 48),
|
|
color: Theme.of(context).primaryColor,
|
|
onPressed: () =>
|
|
MediaService.audioPlayer.seek(Duration.zero),
|
|
);
|
|
}
|
|
},
|
|
),
|
|
IconButton(
|
|
onPressed: () {
|
|
MediaService.audioPlayer.seek(
|
|
Duration(
|
|
seconds:
|
|
max(0, MediaService.audioPlayer.position.inSeconds - 5),
|
|
),
|
|
);
|
|
},
|
|
icon: SvgPicture.asset('lib/assets/icons/backward-5-seconds.svg'),
|
|
),
|
|
StreamBuilder<double>(
|
|
stream: MediaService.audioPlayer.speedStream,
|
|
builder: (context, snapshot) {
|
|
if (!snapshot.hasData) return const SizedBox();
|
|
return PopupMenuButton<double>(
|
|
child: Container(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
borderRadius: DesignConfig.mediumBorderRadius,
|
|
border: Border.all(
|
|
color: Theme.of(context).colorScheme.caption)),
|
|
child: DidvanText(
|
|
'${snapshot.data!.toString().replaceAll('.0', '')}X',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.w900,
|
|
color: Theme.of(context).colorScheme.caption,
|
|
fontSize: 12),
|
|
),
|
|
),
|
|
onSelected: (value) async {
|
|
await MediaService.audioPlayer.setSpeed(value);
|
|
},
|
|
itemBuilder: (BuildContext context) =>
|
|
<PopupMenuEntry<double>>[
|
|
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),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _contentBuilder(dynamic item, int index) {
|
|
final content = item.contents[index];
|
|
if (content.text != null) {
|
|
return Html(
|
|
data: content.text,
|
|
onAnchorTap: (href, _, element) {
|
|
if (href!.contains('navigate-')) {
|
|
if (href.contains('statistic')) {
|
|
Navigator.of(navigatorKey.currentContext!)
|
|
.pushNamed(Routes.statisticDetails, arguments: {
|
|
'onMarkChanged': (value) {},
|
|
'label': href.split('-')[2],
|
|
'title': href.split('-').last,
|
|
'marked': false,
|
|
});
|
|
} else if (href.contains('radar')) {
|
|
Navigator.of(navigatorKey.currentContext!).pushNamed(
|
|
Routes.radarDetails,
|
|
arguments: {
|
|
'onMarkChanged': (id, value) {},
|
|
'onCommentsChanged': (id, count) {},
|
|
'id': int.parse(href.split('-').last),
|
|
'args': const RadarRequestArgs(page: 0),
|
|
'hasUnmarkConfirmation': false,
|
|
},
|
|
);
|
|
} else if (href.contains('news')) {
|
|
Navigator.of(navigatorKey.currentContext!).pushNamed(
|
|
Routes.newsDetails,
|
|
arguments: {
|
|
'onMarkChanged': (id, value) {},
|
|
'id': int.parse(href.split('-').last),
|
|
'args': const NewsRequestArgs(page: 0),
|
|
'hasUnmarkConfirmation': false,
|
|
},
|
|
);
|
|
}
|
|
} else if (href.contains('popup-')) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => Dialog(
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: DesignConfig.lowBorderRadius,
|
|
),
|
|
alignment: Alignment.center,
|
|
child: SizedBox(
|
|
width: ActionSheetUtils(context).mediaQueryData.size.width,
|
|
child: DidvanCard(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
DidvanIcons.info_circle_solid,
|
|
color: Theme.of(context).colorScheme.primary,
|
|
),
|
|
const SizedBox(width: 8),
|
|
DidvanText(
|
|
element!.text,
|
|
style: Theme.of(context).textTheme.titleSmall,
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
DidvanText(href.split('-').last),
|
|
const SizedBox(height: 16),
|
|
DidvanButton(
|
|
title: 'بستن',
|
|
onPressed: ActionSheetUtils(context).pop,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
AppInitializer.openWebLink(context, href,
|
|
mode: LaunchMode.inAppWebView);
|
|
}
|
|
},
|
|
style: {
|
|
'*': Style(
|
|
direction: TextDirection.rtl,
|
|
textAlign: TextAlign.right,
|
|
lineHeight: LineHeight.percent(135),
|
|
margin: const Margins(),
|
|
padding: HtmlPaddings.zero,
|
|
color: const Color.fromRGBO(102, 102, 102, 1),
|
|
),
|
|
'a': Style(
|
|
textAlign: TextAlign.left,
|
|
color: const Color.fromARGB(255, 0, 126, 167),
|
|
textDecoration: TextDecoration.none,
|
|
fontWeight: FontWeight.normal,
|
|
),
|
|
},
|
|
);
|
|
}
|
|
if (content.image != null) {
|
|
return Column(
|
|
children: [
|
|
GestureDetector(
|
|
onTap: () {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => Stack(
|
|
children: [
|
|
Positioned.fill(
|
|
child: InteractiveViewer(
|
|
child: Center(
|
|
child: SkeletonImage(
|
|
width: min(MediaQuery.of(context).size.width,
|
|
MediaQuery.of(context).size.height),
|
|
imageUrl: content.largeImage ?? content.image,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const Positioned(
|
|
right: 24,
|
|
top: 24,
|
|
child: _BackButton(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
child: SkeletonImage(
|
|
imageUrl: content.image!,
|
|
width: double.infinity,
|
|
),
|
|
),
|
|
if (content.caption != null)
|
|
Center(
|
|
child: DidvanText(
|
|
content.caption,
|
|
textAlign: TextAlign.center,
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
return const SizedBox();
|
|
}
|
|
|
|
Widget _subtitle(dynamic item) {
|
|
if (widget.isRadar) {
|
|
return Wrap(
|
|
crossAxisAlignment: WrapCrossAlignment.center,
|
|
children: [
|
|
SvgPicture.asset('lib/assets/icons/calendar.svg'),
|
|
const SizedBox(
|
|
width: 5,
|
|
),
|
|
DidvanText(
|
|
DateTime.parse(item.createdAt!).toPersianDateStr(),
|
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
color: Theme.of(context).colorScheme.caption,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
} else {
|
|
return Row(
|
|
children: [
|
|
SvgPicture.asset('lib/assets/icons/calendar.svg'),
|
|
const SizedBox(
|
|
width: 5,
|
|
),
|
|
DidvanText(
|
|
DateTime.parse(item.createdAt!).toPersianDateStr(),
|
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
color: Theme.of(context).colorScheme.caption,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
class _BackButton extends StatefulWidget {
|
|
final ScrollController? scrollController;
|
|
// ignore: unused_element_parameter
|
|
const _BackButton({Key? key, this.scrollController}) : super(key: key);
|
|
|
|
@override
|
|
__BackButtonState createState() => __BackButtonState();
|
|
}
|
|
|
|
class __BackButtonState extends State<_BackButton> {
|
|
bool _isVisible = false;
|
|
|
|
@override
|
|
void didUpdateWidget(covariant _BackButton oldWidget) {
|
|
if (widget.scrollController != null) {
|
|
_isVisible = false;
|
|
_handleScroll();
|
|
}
|
|
super.didUpdateWidget(oldWidget);
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
if (widget.scrollController != null) {
|
|
_handleScroll();
|
|
} else {
|
|
_isVisible = true;
|
|
}
|
|
super.initState();
|
|
}
|
|
|
|
void _handleScroll() {
|
|
widget.scrollController!.addListener(() {
|
|
final position = widget.scrollController!.position.pixels;
|
|
final size = MediaQuery.of(context).size.height * 0.3;
|
|
if (position > size && _isVisible == false) {
|
|
setState(() {
|
|
_isVisible = true;
|
|
});
|
|
}
|
|
if (position < size && _isVisible == true) {
|
|
setState(() {
|
|
_isVisible = false;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AnimatedVisibility(
|
|
duration: DesignConfig.lowAnimationDuration,
|
|
isVisible: _isVisible,
|
|
child: InkWrapper(
|
|
borderRadius: DesignConfig.lowBorderRadius,
|
|
onPressed: Navigator.of(context).pop,
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.splash,
|
|
border: Border.all(color: Theme.of(context).colorScheme.border),
|
|
borderRadius: DesignConfig.lowBorderRadius,
|
|
),
|
|
child: const Icon(
|
|
DidvanIcons.back_regular,
|
|
size: 32,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _AnimatedEntry extends StatefulWidget {
|
|
final Widget child;
|
|
final int delay;
|
|
final bool isActive;
|
|
|
|
const _AnimatedEntry({
|
|
Key? key,
|
|
required this.child,
|
|
required this.delay,
|
|
required this.isActive,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
State<_AnimatedEntry> createState() => _AnimatedEntryState();
|
|
}
|
|
|
|
class _AnimatedEntryState extends State<_AnimatedEntry> {
|
|
bool _shouldAnimate = false;
|
|
Timer? _timer;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_checkAnimation();
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(covariant _AnimatedEntry oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
if (widget.isActive != oldWidget.isActive) {
|
|
_checkAnimation();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_timer?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
void _checkAnimation() {
|
|
if (widget.isActive) {
|
|
_timer?.cancel();
|
|
_timer = Timer(Duration(milliseconds: widget.delay), () {
|
|
if (mounted) {
|
|
setState(() {
|
|
_shouldAnimate = true;
|
|
});
|
|
}
|
|
});
|
|
} else {
|
|
_timer?.cancel();
|
|
if (_shouldAnimate) {
|
|
setState(() {
|
|
_shouldAnimate = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AnimatedOpacity(
|
|
duration: const Duration(milliseconds: 500),
|
|
curve: Curves.easeOut,
|
|
opacity: _shouldAnimate ? 1.0 : 0.0,
|
|
child: AnimatedPadding(
|
|
duration: const Duration(milliseconds: 600),
|
|
curve: Curves.easeOutQuart,
|
|
padding: EdgeInsets.only(top: _shouldAnimate ? 0 : 50),
|
|
child: widget.child,
|
|
),
|
|
);
|
|
}
|
|
}
|