home page v1
This commit is contained in:
parent
0349a8939b
commit
1fe0e8def5
|
|
@ -0,0 +1,13 @@
|
|||
class HomePageBannerType {
|
||||
final String image;
|
||||
final String? link;
|
||||
|
||||
HomePageBannerType({required this.image, required this.link});
|
||||
|
||||
factory HomePageBannerType.fromJson(Map<String, dynamic> json) {
|
||||
return HomePageBannerType(
|
||||
image: json['image'],
|
||||
link: json['link'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +1,30 @@
|
|||
class Content {
|
||||
final int? id;
|
||||
class HomePageContentType {
|
||||
final int id;
|
||||
final String title;
|
||||
final String image;
|
||||
final String link;
|
||||
final bool marked;
|
||||
final List<String> subtitles;
|
||||
final int? duration;
|
||||
|
||||
const Content({
|
||||
const HomePageContentType({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.image,
|
||||
required this.link,
|
||||
required this.marked,
|
||||
required this.subtitles,
|
||||
this.duration,
|
||||
});
|
||||
|
||||
factory Content.fromJson(Map<String, dynamic> json) => Content(
|
||||
factory HomePageContentType.fromJson(Map<String, dynamic> json) =>
|
||||
HomePageContentType(
|
||||
id: json['id'],
|
||||
title: json['title'],
|
||||
image: json['image'],
|
||||
link: json['link'],
|
||||
marked: json['marked'],
|
||||
subtitles: List<String>.from(json['subtitles']),
|
||||
duration: json['duration'],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import 'package:didvan/models/home_page_content/banner.dart';
|
||||
|
||||
import 'home_page_list.dart';
|
||||
|
||||
class HomePageContent {
|
||||
final List<dynamic> banners;
|
||||
final List<HomePageBannerType> banners;
|
||||
final List<HomePageList> lists;
|
||||
final int unread;
|
||||
|
||||
|
|
@ -10,7 +12,9 @@ class HomePageContent {
|
|||
|
||||
factory HomePageContent.fromJson(Map<String, dynamic> json) {
|
||||
return HomePageContent(
|
||||
banners: json['banners'],
|
||||
banners: List<HomePageBannerType>.from(json['banners'].map(
|
||||
(x) => HomePageBannerType.fromJson(x),
|
||||
)),
|
||||
lists: List<HomePageList>.from(
|
||||
json['lists'].map(
|
||||
(x) => HomePageList.fromJson(x),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ class HomePageList {
|
|||
final String header;
|
||||
final String more;
|
||||
final String link;
|
||||
final List<Content> contents;
|
||||
final List<HomePageContentType> contents;
|
||||
|
||||
const HomePageList({
|
||||
required this.type,
|
||||
|
|
@ -20,7 +20,7 @@ class HomePageList {
|
|||
header: json['header'],
|
||||
more: json['more'],
|
||||
link: json['link'],
|
||||
contents: List<Content>.from(json['contents'].map(
|
||||
(x) => Content.fromJson(x),
|
||||
contents: List<HomePageContentType>.from(json['contents'].map(
|
||||
(x) => HomePageContentType.fromJson(x),
|
||||
)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,9 @@ class RouteGenerator {
|
|||
ChangeNotifierProvider<StatisticState>(
|
||||
create: (context) => StatisticState(),
|
||||
),
|
||||
ChangeNotifierProvider<PodcastsState>(
|
||||
create: (context) => PodcastsState(),
|
||||
),
|
||||
],
|
||||
child: const Home(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@ import 'dart:io';
|
|||
import 'package:assets_audio_player/assets_audio_player.dart';
|
||||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/models/requests/studio.dart';
|
||||
import 'package:didvan/models/studio_details_data.dart';
|
||||
import 'package:didvan/services/media/media.dart';
|
||||
import 'package:didvan/views/home/widgets/audio/audio_slider.dart';
|
||||
import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart';
|
||||
import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class AudioWidget extends StatelessWidget {
|
||||
final String? audioUrl;
|
||||
|
|
@ -34,7 +37,9 @@ class AudioWidget extends StatelessWidget {
|
|||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: AudioSlider(
|
||||
tag: audioMetaData != null ? 'radar-$id' : 'message-$id',
|
||||
tag: audioMetaData != null
|
||||
? '${audioMetaData!.type}-$id'
|
||||
: 'message-$id',
|
||||
duration: audioMetaData?.duration,
|
||||
showTimer: true,
|
||||
),
|
||||
|
|
@ -70,7 +75,7 @@ class _AudioControllerButton extends StatelessWidget {
|
|||
|
||||
bool get _nowPlaying =>
|
||||
MediaService.audioPlayerTag ==
|
||||
(audioMetaData != null ? 'radar-$id' : 'message-$id');
|
||||
(audioMetaData != null ? '${audioMetaData!.type}-$id' : 'message-$id');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -84,6 +89,15 @@ class _AudioControllerButton extends StatelessWidget {
|
|||
gestureSize: 36,
|
||||
color: Theme.of(context).colorScheme.focusedBorder,
|
||||
onPressed: () async {
|
||||
if (audioMetaData?.type == 'podcast') {
|
||||
final state = context.read<StudioDetailsState>();
|
||||
if (MediaService.currentPodcast == null) {
|
||||
await state.getStudioDetails(
|
||||
id,
|
||||
args: const StudioRequestArgs(page: 0, type: 'podcast'),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (snapshot.data == null && _nowPlaying) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
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/models/home_page_content/home_page_list.dart';
|
||||
import 'package:didvan/views/home/home/home_page_state.dart';
|
||||
import 'package:didvan/views/widgets/didvan/card.dart';
|
||||
import 'package:didvan/views/home/home/widgets/banner.dart';
|
||||
import 'package:didvan/views/home/home/widgets/general_item.dart';
|
||||
import 'package:didvan/views/home/home/widgets/main_content.dart';
|
||||
import 'package:didvan/views/home/home/widgets/news_item.dart';
|
||||
import 'package:didvan/views/home/home/widgets/podcast_item.dart';
|
||||
import 'package:didvan/views/home/home/widgets/radar_item.dart';
|
||||
import 'package:didvan/views/home/home/widgets/videocast_item.dart';
|
||||
import 'package:didvan/views/widgets/didvan/slider.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.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:provider/provider.dart';
|
||||
|
|
@ -21,10 +27,7 @@ class HomePage extends StatefulWidget {
|
|||
class _HomePageState extends State<HomePage> {
|
||||
@override
|
||||
void initState() {
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
context.read<HomePageState>().getMainPageContent,
|
||||
);
|
||||
context.read<HomePageState>().init();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
|
@ -33,37 +36,42 @@ class _HomePageState extends State<HomePage> {
|
|||
return StateHandler<HomePageState>(
|
||||
onRetry: () {},
|
||||
state: context.watch<HomePageState>(),
|
||||
builder: (context, state) =>
|
||||
ListView.builder(itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return CarouselSlider(
|
||||
items: state.content.banners
|
||||
.map((e) => SkeletonImage(
|
||||
imageUrl: e.imageUrl,
|
||||
))
|
||||
.toList(),
|
||||
options: CarouselOptions(
|
||||
viewportFraction: 1.0,
|
||||
),
|
||||
builder: (context, state) => ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return const HomePageMainContent();
|
||||
}
|
||||
index--;
|
||||
if (index == 4) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(top: 32),
|
||||
child: HomePageBanner(
|
||||
isFirst: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (index > 3) {
|
||||
index--;
|
||||
}
|
||||
final list = state.content.lists[index];
|
||||
return _HomePageSection(
|
||||
list: list,
|
||||
isLast: index == state.content.lists.length - 1,
|
||||
);
|
||||
}
|
||||
return const SizedBox();
|
||||
// state.content.banners.map((e) => const SizedBox()).toList(),
|
||||
// for (int i = 0; i < state.content.lists.length; i += 2)
|
||||
// ...state.content.lists
|
||||
// .sublist(i, i + 2)
|
||||
// .map((e) => _HomePageItem(list: e))
|
||||
// .toList(),
|
||||
}),
|
||||
},
|
||||
itemCount: state.content.lists.length + 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HomePageItem extends StatelessWidget {
|
||||
class _HomePageSection extends StatelessWidget {
|
||||
final HomePageList list;
|
||||
const _HomePageItem({required this.list});
|
||||
final bool isLast;
|
||||
const _HomePageSection({required this.list, required this.isLast});
|
||||
|
||||
void moreHandler(BuildContext context) {
|
||||
void _moreHandler(BuildContext context) {
|
||||
if (list.link.startsWith('http')) {
|
||||
launchUrl(Uri.parse(list.link));
|
||||
return;
|
||||
|
|
@ -71,8 +79,34 @@ class _HomePageItem extends StatelessWidget {
|
|||
Navigator.of(context).pushNamed(list.link);
|
||||
}
|
||||
|
||||
IconData? _generateIcon() {
|
||||
switch (list.type) {
|
||||
case 'news':
|
||||
return DidvanIcons.news_solid;
|
||||
case 'radar':
|
||||
return DidvanIcons.radar_solid;
|
||||
case 'video':
|
||||
return DidvanIcons.video_solid;
|
||||
case 'podcast':
|
||||
return DidvanIcons.podcast_solid;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
int _maxSublistCount() {
|
||||
int max = 1;
|
||||
for (var i = 0; i < list.contents.length; i++) {
|
||||
if (list.contents[i].subtitles.length > max) {
|
||||
max = list.contents[i].subtitles.length;
|
||||
}
|
||||
}
|
||||
return max - 1;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final icon = _generateIcon();
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
|
|
@ -85,13 +119,19 @@ class _HomePageItem extends StatelessWidget {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
DidvanText(
|
||||
list.header,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
color: Theme.of(context).colorScheme.title,
|
||||
Row(
|
||||
children: [
|
||||
if (icon != null) Icon(icon),
|
||||
const SizedBox(width: 4),
|
||||
DidvanText(
|
||||
list.header,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
color: Theme.of(context).colorScheme.title,
|
||||
),
|
||||
],
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => moreHandler(context),
|
||||
onTap: () => _moreHandler(context),
|
||||
child: Row(
|
||||
children: [
|
||||
DidvanText(
|
||||
|
|
@ -108,63 +148,97 @@ class _HomePageItem extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
CarouselSlider.builder(
|
||||
itemCount: list.contents.length,
|
||||
itemBuilder: (context, index, realIndex) => DidvanCard(
|
||||
margin: const EdgeInsets.only(left: 8),
|
||||
padding: EdgeInsets.zero,
|
||||
child: Column(
|
||||
children: [
|
||||
SkeletonImage(
|
||||
imageUrl: list.contents[index].image,
|
||||
height: MediaQuery.of(context).size.height / 6,
|
||||
width: double.infinity,
|
||||
),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DidvanText(
|
||||
list.contents[index].title,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
DidvanIcons.puzzle_light,
|
||||
size: 16,
|
||||
),
|
||||
...list.contents[index].subtitles.map(
|
||||
(e) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: DidvanText(
|
||||
e,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
if (list.type == 'news')
|
||||
DidvanSlider(
|
||||
height: 232,
|
||||
itemCount: list.contents.length,
|
||||
viewportFraction: 0.45,
|
||||
itemBuilder: (context, index, realIndex) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: HomePageNewsItem(
|
||||
content: list.contents[index],
|
||||
),
|
||||
),
|
||||
),
|
||||
options: CarouselOptions(
|
||||
autoPlay: true,
|
||||
padEnds: false,
|
||||
viewportFraction: 0.7,
|
||||
if (list.type == 'radar')
|
||||
DidvanSlider(
|
||||
height: 148,
|
||||
itemCount: list.contents.length,
|
||||
viewportFraction: 0.6,
|
||||
itemBuilder: (context, index, realIndex) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: HomePageRadarItem(
|
||||
content: list.contents[index],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (list.type == 'video')
|
||||
DidvanSlider(
|
||||
height: 180,
|
||||
itemCount: list.contents.length,
|
||||
viewportFraction: 0.6,
|
||||
itemBuilder: (context, index, realIndex) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: HomePageVideocastItem(
|
||||
content: list.contents[index],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (list.type == 'podcast')
|
||||
Column(
|
||||
children: list.contents
|
||||
.map(
|
||||
(e) => Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 12,
|
||||
left: 28,
|
||||
right: 28,
|
||||
),
|
||||
child: HomePagePodcastItem(
|
||||
content: e,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
if (list.type != 'news' &&
|
||||
list.type != 'radar' &&
|
||||
list.type != 'video' &&
|
||||
list.type != 'podcast')
|
||||
DidvanSlider(
|
||||
itemBuilder: (context, index, realIndex) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: HomePageGeneralItem(
|
||||
content: list.contents[index],
|
||||
),
|
||||
),
|
||||
itemCount: list.contents.length,
|
||||
viewportFraction: 0.7,
|
||||
height: 196 + _maxSublistCount() * 20,
|
||||
),
|
||||
if (!isLast) const _HomePageDivider(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HomePageDivider extends StatelessWidget {
|
||||
const _HomePageDivider();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 2,
|
||||
margin: const EdgeInsets.only(
|
||||
top: 8,
|
||||
left: 20,
|
||||
right: 20,
|
||||
),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: DesignConfig.highBorderRadius,
|
||||
color: Theme.of(context).colorScheme.border,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'package:didvan/constants/assets.dart';
|
||||
import 'package:didvan/models/category.dart';
|
||||
import 'package:didvan/models/enums.dart';
|
||||
import 'package:didvan/models/home_page_content/home_page_content.dart';
|
||||
import 'package:didvan/providers/core.dart';
|
||||
|
|
@ -5,8 +7,9 @@ import 'package:didvan/services/network/request.dart';
|
|||
import 'package:didvan/services/network/request_helper.dart';
|
||||
|
||||
class HomePageState extends CoreProvier {
|
||||
late final HomePageContent content;
|
||||
Future<void> getMainPageContent() async {
|
||||
late HomePageContent content;
|
||||
List<CategoryData> categories = [];
|
||||
Future<void> _getMainPageContent() async {
|
||||
final service = RequestService(RequestHelper.mainPageContent);
|
||||
await service.httpGet();
|
||||
if (service.isSuccess) {
|
||||
|
|
@ -16,4 +19,64 @@ class HomePageState extends CoreProvier {
|
|||
}
|
||||
appState = AppState.failed;
|
||||
}
|
||||
|
||||
void init() {
|
||||
if (categories.isEmpty) {
|
||||
categories = [
|
||||
CategoryData(
|
||||
id: 1,
|
||||
label: 'اقتصادی',
|
||||
asset: Assets.economicCategoryIcon,
|
||||
),
|
||||
CategoryData(
|
||||
id: 2,
|
||||
label: 'سیاسی',
|
||||
asset: Assets.politicalCategoryIcon,
|
||||
),
|
||||
CategoryData(
|
||||
id: 3,
|
||||
label: 'فناوری',
|
||||
asset: Assets.techCategoryIcon,
|
||||
),
|
||||
CategoryData(
|
||||
id: 4,
|
||||
label: 'کسب و کار',
|
||||
asset: Assets.businessCategoryIcon,
|
||||
),
|
||||
CategoryData(
|
||||
id: 5,
|
||||
label: 'زیست محیطی',
|
||||
asset: Assets.enviromentalCategoryIcon,
|
||||
),
|
||||
CategoryData(
|
||||
id: 6,
|
||||
label: 'اجتماعی',
|
||||
asset: Assets.socialCategoryIcon,
|
||||
),
|
||||
CategoryData(
|
||||
id: 1,
|
||||
label: 'اقتصادی',
|
||||
asset: Assets.economicCategoryIcon,
|
||||
),
|
||||
CategoryData(
|
||||
id: 2,
|
||||
label: 'سیاسی',
|
||||
asset: Assets.politicalCategoryIcon,
|
||||
),
|
||||
CategoryData(
|
||||
id: 3,
|
||||
label: 'فناوری',
|
||||
asset: Assets.techCategoryIcon,
|
||||
),
|
||||
CategoryData(
|
||||
id: 4,
|
||||
label: 'کسب و کار',
|
||||
asset: Assets.businessCategoryIcon,
|
||||
),
|
||||
];
|
||||
}
|
||||
Future.delayed(Duration.zero, () {
|
||||
_getMainPageContent();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import 'package:didvan/views/widgets/didvan/slider.dart';
|
||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HomePageBanner extends StatelessWidget {
|
||||
final bool isFirst;
|
||||
const HomePageBanner({super.key, required this.isFirst});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// final state = context.read<HomePageState>();
|
||||
final banners = [1, 2];
|
||||
return DidvanSlider(
|
||||
itemBuilder: (context, index, realIndex) => const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4),
|
||||
child: SkeletonImage(
|
||||
imageUrl: 'https://wallpapercave.com/fwp/wp12963122.jpg',
|
||||
),
|
||||
),
|
||||
itemCount: banners.length,
|
||||
viewportFraction: 1,
|
||||
enableIndicator: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/models/home_page_content/content.dart';
|
||||
import 'package:didvan/views/widgets/didvan/card.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HomePageGeneralItem extends StatelessWidget {
|
||||
final HomePageContentType content;
|
||||
const HomePageGeneralItem({super.key, required this.content});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DidvanCard(
|
||||
padding: EdgeInsets.zero,
|
||||
child: Column(
|
||||
children: [
|
||||
SkeletonImage(
|
||||
imageUrl: content.image,
|
||||
height: 124,
|
||||
width: double.infinity,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DidvanText(
|
||||
content.title,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Column(
|
||||
children: content.subtitles
|
||||
.map(
|
||||
(e) => Row(
|
||||
children: [
|
||||
const Icon(
|
||||
DidvanIcons.puzzle_light,
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: DidvanText(
|
||||
e,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import 'package:didvan/config/design_config.dart';
|
||||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/views/home/home/home_page_state.dart';
|
||||
import 'package:didvan/views/home/home/widgets/banner.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class HomePageMainContent extends StatelessWidget {
|
||||
const HomePageMainContent({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final state = context.read<HomePageState>();
|
||||
return Column(
|
||||
children: [
|
||||
const HomePageBanner(
|
||||
isFirst: true,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
bottom: 13,
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 2,
|
||||
color: Theme.of(context).colorScheme.border,
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: DidvanText(
|
||||
'دیدوان در یک نگاه',
|
||||
color: Theme.of(context).colorScheme.title,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 20,
|
||||
right: 20,
|
||||
top: 16,
|
||||
),
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
children: state.categories
|
||||
.map(
|
||||
(e) => SizedBox(
|
||||
width: (MediaQuery.of(context).size.width - 40) / 4,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 56,
|
||||
height: 56,
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: DesignConfig.lowBorderRadius,
|
||||
border: Border.all(
|
||||
color:
|
||||
Theme.of(context).colorScheme.focusedBorder,
|
||||
),
|
||||
),
|
||||
child: SvgPicture.asset(e.asset!),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
DidvanText(
|
||||
e.label,
|
||||
color: Theme.of(context).colorScheme.title,
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/models/home_page_content/content.dart';
|
||||
import 'package:didvan/views/widgets/didvan/card.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:persian_number_utility/persian_number_utility.dart';
|
||||
|
||||
class HomePageNewsItem extends StatelessWidget {
|
||||
final HomePageContentType content;
|
||||
const HomePageNewsItem({super.key, required this.content});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DidvanCard(
|
||||
padding: EdgeInsets.zero,
|
||||
child: Column(
|
||||
children: [
|
||||
SkeletonImage(
|
||||
imageUrl: content.image,
|
||||
width: double.infinity,
|
||||
height: 160,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DidvanText(
|
||||
content.title,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(
|
||||
DidvanIcons.calendar_day_light,
|
||||
size: 16,
|
||||
),
|
||||
DidvanText(
|
||||
DateTime.parse(content.subtitles[0]).toPersianDateStr(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/models/home_page_content/content.dart';
|
||||
import 'package:didvan/models/studio_details_data.dart';
|
||||
import 'package:didvan/views/home/direct/widgets/audio_widget.dart';
|
||||
import 'package:didvan/views/widgets/didvan/card.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:persian_number_utility/persian_number_utility.dart';
|
||||
|
||||
class HomePagePodcastItem extends StatelessWidget {
|
||||
final HomePageContentType content;
|
||||
const HomePagePodcastItem({super.key, required this.content});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 180,
|
||||
width: double.infinity,
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Center(
|
||||
child: DidvanCard(
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width / 3,
|
||||
),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 2 / 3 - 90,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 2 / 3 - 90,
|
||||
child: AudioWidget(
|
||||
id: content.id,
|
||||
audioUrl: content.link,
|
||||
audioMetaData: StudioDetailsData(
|
||||
id: content.id,
|
||||
duration: content.duration!,
|
||||
title: content.title,
|
||||
description: '',
|
||||
image: content.image,
|
||||
link: content.link,
|
||||
iframe: null,
|
||||
createdAt: '',
|
||||
order: 1,
|
||||
marked: content.marked,
|
||||
comments: 0,
|
||||
tags: [],
|
||||
type: 'podcast',
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DidvanText(
|
||||
content.title,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(
|
||||
DidvanIcons.calendar_day_light,
|
||||
size: 16,
|
||||
),
|
||||
DidvanText(
|
||||
DateTime.parse(content.subtitles[0])
|
||||
.toPersianDateStr(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style:
|
||||
Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SkeletonImage(
|
||||
width: MediaQuery.of(context).size.width / 3,
|
||||
height: 180,
|
||||
imageUrl: content.image,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/models/home_page_content/content.dart';
|
||||
import 'package:didvan/utils/date_time.dart';
|
||||
import 'package:didvan/views/home/home/home_page_state.dart';
|
||||
import 'package:didvan/views/widgets/didvan/card.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class HomePageRadarItem extends StatelessWidget {
|
||||
final HomePageContentType content;
|
||||
const HomePageRadarItem({super.key, required this.content});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DidvanCard(
|
||||
padding: EdgeInsets.zero,
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SkeletonImage(
|
||||
imageUrl: content.image,
|
||||
width: double.infinity,
|
||||
height: 76,
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DidvanText(
|
||||
content.title,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(
|
||||
DidvanIcons.calendar_day_light,
|
||||
size: 16,
|
||||
),
|
||||
DidvanText(
|
||||
DateTimeUtils.momentGenerator(
|
||||
content.subtitles[0]),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(
|
||||
DidvanIcons.timer_light,
|
||||
size: 16,
|
||||
),
|
||||
DidvanText(
|
||||
'${content.subtitles[1]} دقیقه',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Center(
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(
|
||||
right: MediaQuery.of(context).size.width * 0.4,
|
||||
),
|
||||
width: 36,
|
||||
height: 36,
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
context
|
||||
.read<HomePageState>()
|
||||
.categories
|
||||
.firstWhere((element) =>
|
||||
element.id.toString() == content.subtitles[2])
|
||||
.asset ??
|
||||
'',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import 'package:didvan/config/design_config.dart';
|
||||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/models/home_page_content/content.dart';
|
||||
import 'package:didvan/views/widgets/didvan/card.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:persian_number_utility/persian_number_utility.dart';
|
||||
|
||||
class HomePageVideocastItem extends StatelessWidget {
|
||||
final HomePageContentType content;
|
||||
const HomePageVideocastItem({super.key, required this.content});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DidvanCard(
|
||||
padding: EdgeInsets.zero,
|
||||
child: Stack(
|
||||
children: [
|
||||
SkeletonImage(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
imageUrl: content.image,
|
||||
),
|
||||
Center(
|
||||
child: Container(
|
||||
height: 36,
|
||||
width: 36,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context).colorScheme.secondary.withOpacity(0.7),
|
||||
),
|
||||
child: Icon(
|
||||
DidvanIcons.play_solid,
|
||||
color: Theme.of(context).colorScheme.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
bottomRight: Radius.circular(12),
|
||||
),
|
||||
boxShadow: DesignConfig.defaultShadow,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
width: MediaQuery.of(context).size.width / 3,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DidvanText(
|
||||
content.title,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(
|
||||
DidvanIcons.calendar_day_light,
|
||||
size: 16,
|
||||
),
|
||||
DidvanText(
|
||||
DateTime.parse(content.subtitles[0])
|
||||
.toPersianDateStr(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:didvan/providers/core.dart';
|
||||
|
||||
class HomeState extends CoreProvier {
|
||||
int _currentPageIndex = 2;
|
||||
int _currentPageIndex = 0;
|
||||
|
||||
set currentPageIndex(int value) {
|
||||
_currentPageIndex = value;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import 'package:didvan/views/home/statistic/statistic_state.dart';
|
|||
import 'package:didvan/views/home/statistic/widgets/statistic_overview.dart';
|
||||
import 'package:didvan/views/home/widgets/categories_gird.dart';
|
||||
import 'package:didvan/views/home/widgets/categories_list.dart';
|
||||
import 'package:didvan/views/home/widgets/logo_app_bar.dart';
|
||||
import 'package:didvan/views/widgets/animated_visibility.dart';
|
||||
import 'package:didvan/views/widgets/didvan/divider.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
|
|
@ -57,10 +56,9 @@ class _StatisticState extends State<Statistic> {
|
|||
: const ClampingScrollPhysics(),
|
||||
controller: _scrollController,
|
||||
slivers: [
|
||||
const SliverToBoxAdapter(child: LogoAppBar()),
|
||||
if (state.appState != AppState.failed)
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(height: 180),
|
||||
child: SizedBox(height: 120),
|
||||
),
|
||||
if (state.appState != AppState.failed &&
|
||||
state.markedStatistics.isNotEmpty)
|
||||
|
|
@ -135,7 +133,7 @@ class _StatisticState extends State<Statistic> {
|
|||
onSelected: _onCategorySelected,
|
||||
categories: List.from(state.categories)..removeAt(0),
|
||||
isColapsed: state.isColapsed,
|
||||
topPadding: 144,
|
||||
topPadding: 20,
|
||||
rightPadding: 300,
|
||||
),
|
||||
if (state.appState != AppState.failed)
|
||||
|
|
@ -177,21 +175,20 @@ class _StatisticState extends State<Statistic> {
|
|||
_isAnimating = true;
|
||||
setState(() {});
|
||||
await _scrollController.animateTo(
|
||||
228,
|
||||
duration: DesignConfig.mediumAnimationDuration,
|
||||
60,
|
||||
duration: DesignConfig.lowAnimationDuration,
|
||||
curve: Curves.easeIn,
|
||||
);
|
||||
_isAnimating = false;
|
||||
setState(() {});
|
||||
} else if (position <
|
||||
min(_scrollController.position.maxScrollExtent, 228) &&
|
||||
} else if (position < min(_scrollController.position.maxScrollExtent, 40) &&
|
||||
state.isColapsed) {
|
||||
state.isScrolled = false;
|
||||
_isAnimating = true;
|
||||
setState(() {});
|
||||
await _scrollController.animateTo(
|
||||
0,
|
||||
duration: DesignConfig.mediumAnimationDuration,
|
||||
duration: DesignConfig.lowAnimationDuration,
|
||||
curve: Curves.easeIn,
|
||||
);
|
||||
_isAnimating = false;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ class AudioSlider extends StatelessWidget {
|
|||
: Theme.of(context).colorScheme.primary,
|
||||
baseBarColor: Theme.of(context).colorScheme.border,
|
||||
bufferedBarColor: Theme.of(context).colorScheme.splash,
|
||||
total: MediaService.duration ?? Duration(seconds: duration ?? 0),
|
||||
total: Duration(
|
||||
seconds: duration ?? MediaService.duration?.inSeconds ?? 0),
|
||||
progress: snapshot.data ?? Duration.zero,
|
||||
thumbRadius: disableThumb ? 0 : 6,
|
||||
barHeight: 3,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,26 @@
|
|||
import 'package:assets_audio_player/assets_audio_player.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/models/enums.dart';
|
||||
import 'package:didvan/models/requests/radar.dart';
|
||||
import 'package:didvan/models/view/app_bar_data.dart';
|
||||
import 'package:didvan/routes/routes.dart';
|
||||
import 'package:didvan/services/media/media.dart';
|
||||
import 'package:didvan/utils/action_sheet.dart';
|
||||
import 'package:didvan/views/home/widgets/audio/audio_player_widget.dart';
|
||||
import 'package:didvan/views/home/widgets/audio/audio_slider.dart';
|
||||
import 'package:didvan/views/podcasts/podcasts_state.dart';
|
||||
import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart';
|
||||
import 'package:didvan/views/podcasts/studio_details/widgets/studio_details_widget.dart';
|
||||
import 'package:didvan/views/widgets/didvan/app_bar.dart';
|
||||
import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||
import 'package:expandable_bottom_sheet/expandable_bottom_sheet.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class DidvanScaffold extends StatefulWidget {
|
||||
final List<Widget>? slivers;
|
||||
|
|
@ -42,75 +62,88 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double statusBarHeight = MediaQuery.of(context).padding.top;
|
||||
final double systemNavigationBarHeight =
|
||||
MediaQuery.of(context).padding.bottom;
|
||||
return Scaffold(
|
||||
backgroundColor: widget.backgroundColor,
|
||||
body: Padding(
|
||||
padding: widget.appBarData == null
|
||||
? EdgeInsets.zero
|
||||
: EdgeInsets.only(top: statusBarHeight),
|
||||
child: Stack(
|
||||
children: [
|
||||
CustomScrollView(
|
||||
physics: widget.physics,
|
||||
controller: _scrollController,
|
||||
reverse: widget.reverse,
|
||||
slivers: [
|
||||
if (!widget.reverse && widget.appBarData != null)
|
||||
SliverAppBar(
|
||||
toolbarHeight: (widget.appBarData!.isSmall ? 56 : 72) -
|
||||
statusBarHeight,
|
||||
automaticallyImplyLeading: false,
|
||||
pinned: true,
|
||||
backgroundColor: widget.backgroundColor ??
|
||||
Theme.of(context).colorScheme.background,
|
||||
flexibleSpace: DidvanAppBar(
|
||||
appBarData: widget.appBarData!,
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height -
|
||||
statusBarHeight -
|
||||
systemNavigationBarHeight,
|
||||
child: Stack(
|
||||
children: [
|
||||
CustomScrollView(
|
||||
physics: widget.physics,
|
||||
controller: _scrollController,
|
||||
reverse: widget.reverse,
|
||||
slivers: [
|
||||
if (!widget.reverse && widget.appBarData != null)
|
||||
SliverAppBar(
|
||||
toolbarHeight: (widget.appBarData!.isSmall ? 56 : 72) -
|
||||
statusBarHeight,
|
||||
automaticallyImplyLeading: false,
|
||||
pinned: true,
|
||||
backgroundColor: widget.backgroundColor ??
|
||||
Theme.of(context).colorScheme.background,
|
||||
),
|
||||
),
|
||||
if (widget.children != null && !widget.showSliversFirst)
|
||||
SliverPadding(
|
||||
padding: widget.padding,
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => widget.children![index],
|
||||
childCount: widget.children!.length,
|
||||
flexibleSpace: DidvanAppBar(
|
||||
appBarData: widget.appBarData!,
|
||||
backgroundColor: widget.backgroundColor ??
|
||||
Theme.of(context).colorScheme.background,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.slivers != null)
|
||||
for (var i = 0; i < widget.slivers!.length; i++)
|
||||
if (widget.children != null && !widget.showSliversFirst)
|
||||
SliverPadding(
|
||||
padding: widget.padding,
|
||||
sliver: widget.slivers![i],
|
||||
),
|
||||
if (widget.children != null && widget.showSliversFirst)
|
||||
SliverPadding(
|
||||
padding: widget.padding,
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => widget.children![index],
|
||||
childCount: widget.children!.length,
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => widget.children![index],
|
||||
childCount: widget.children!.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.reverse)
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: kToolbarHeight +
|
||||
MediaQuery.of(context).padding.top +
|
||||
12,
|
||||
if (widget.slivers != null)
|
||||
for (var i = 0; i < widget.slivers!.length; i++)
|
||||
SliverPadding(
|
||||
padding: widget.padding,
|
||||
sliver: widget.slivers![i],
|
||||
),
|
||||
if (widget.children != null && widget.showSliversFirst)
|
||||
SliverPadding(
|
||||
padding: widget.padding,
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => widget.children![index],
|
||||
childCount: widget.children!.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (widget.reverse && widget.appBarData != null)
|
||||
_AppBar(
|
||||
appBarData: widget.appBarData!,
|
||||
scrollController: _scrollController,
|
||||
if (widget.reverse)
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: kToolbarHeight +
|
||||
MediaQuery.of(context).padding.top +
|
||||
12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
if (widget.reverse && widget.appBarData != null)
|
||||
_AppBar(
|
||||
appBarData: widget.appBarData!,
|
||||
scrollController: _scrollController,
|
||||
),
|
||||
const Positioned(
|
||||
bottom: 20,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: _PlayerNavBar(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -160,3 +193,272 @@ class __AppBarState extends State<_AppBar> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PlayerNavBar extends StatelessWidget {
|
||||
const _PlayerNavBar({Key? key}) : super(key: key);
|
||||
|
||||
bool _enablePlayerController(StudioDetailsState state) =>
|
||||
MediaService.currentPodcast != null ||
|
||||
(MediaService.audioPlayerTag?.contains('podcast') ?? false);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<bool>(
|
||||
stream: MediaService.audioPlayer.isPlaying,
|
||||
builder: (context, snapshot) => GestureDetector(
|
||||
onTap: () => MediaService.currentPodcast == null ||
|
||||
MediaService.currentPodcast?.description == 'radar'
|
||||
? Navigator.of(context).pushNamed(
|
||||
Routes.radarDetails,
|
||||
arguments: {
|
||||
'onMarkChanged': (id, value) {},
|
||||
'onCommentsChanged': (id, value) {},
|
||||
'id': MediaService.currentPodcast?.id,
|
||||
'args': const RadarRequestArgs(page: 0),
|
||||
'hasUnmarkConfirmation': false,
|
||||
},
|
||||
)
|
||||
: _showPlayerBottomSheet(context),
|
||||
child: Consumer<StudioDetailsState>(
|
||||
builder: (context, state, child) => AnimatedContainer(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
duration: DesignConfig.lowAnimationDuration,
|
||||
height: _enablePlayerController(state) ? 60 : 0,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 20),
|
||||
decoration: BoxDecoration(
|
||||
color: DesignConfig.isDark
|
||||
? Theme.of(context).colorScheme.focused
|
||||
: Theme.of(context).colorScheme.navigation,
|
||||
borderRadius: BorderRadius.circular(200),
|
||||
),
|
||||
alignment: Alignment.topCenter,
|
||||
child: Builder(builder: (context) {
|
||||
if (!_enablePlayerController(state)) return const SizedBox();
|
||||
if (state.appState == AppState.failed) {
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
MediaService.resetAudioPlayer();
|
||||
});
|
||||
return DidvanText(
|
||||
'اتصال اینترنت برقرار نمیباشد',
|
||||
color: DesignConfig.isDark
|
||||
? Theme.of(context).colorScheme.title
|
||||
: Theme.of(context).colorScheme.secondCTA,
|
||||
);
|
||||
}
|
||||
if (MediaService.currentPodcast == null) {
|
||||
return SizedBox(
|
||||
height: 32,
|
||||
child: Center(
|
||||
child: SpinKitThreeBounce(
|
||||
size: 18,
|
||||
color: DesignConfig.isDark
|
||||
? Theme.of(context).colorScheme.title
|
||||
: Theme.of(context).colorScheme.secondCTA,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return SizedBox(
|
||||
height: 56,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 12,
|
||||
left: 8,
|
||||
),
|
||||
child: DidvanIconButton(
|
||||
icon: DidvanIcons.close_regular,
|
||||
color: DesignConfig.isDark
|
||||
? null
|
||||
: Theme.of(context).colorScheme.secondCTA,
|
||||
gestureSize: 32,
|
||||
onPressed: MediaService.resetAudioPlayer,
|
||||
),
|
||||
),
|
||||
SkeletonImage(
|
||||
imageUrl: MediaService.currentPodcast!.image,
|
||||
width: 32,
|
||||
height: 32,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DidvanText(
|
||||
MediaService.currentPodcast!.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: DesignConfig.isDark
|
||||
? null
|
||||
: Theme.of(context).colorScheme.secondCTA,
|
||||
),
|
||||
AudioSlider(
|
||||
disableThumb: true,
|
||||
tag: MediaService.audioPlayerTag!,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
StreamBuilder<PlayingAudio?>(
|
||||
stream: MediaService.audioPlayer.onReadyToPlay,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data == null ||
|
||||
state.appState == AppState.busy &&
|
||||
MediaService.currentPodcast?.description !=
|
||||
'radar') {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 4,
|
||||
left: 16,
|
||||
right: 16,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 18,
|
||||
width: 18,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: DesignConfig.isDark
|
||||
? Theme.of(context).colorScheme.title
|
||||
: Theme.of(context).colorScheme.secondCTA,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
if (state.appState != AppState.busy &&
|
||||
snapshot.data != null ||
|
||||
MediaService.currentPodcast?.description == 'radar')
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12,
|
||||
right: 12,
|
||||
),
|
||||
child: DidvanIconButton(
|
||||
gestureSize: 32,
|
||||
color: DesignConfig.isDark
|
||||
? null
|
||||
: Theme.of(context).colorScheme.secondCTA,
|
||||
icon: snapshot.data!
|
||||
? DidvanIcons.pause_solid
|
||||
: DidvanIcons.play_solid,
|
||||
onPressed: () {
|
||||
if (state.args?.type == 'video') {
|
||||
state.getStudioDetails(
|
||||
MediaService.currentPodcast!.id,
|
||||
args: state.podcastArgs,
|
||||
fetchOnly: true,
|
||||
);
|
||||
}
|
||||
MediaService.handleAudioPlayback(
|
||||
audioSource: MediaService.currentPodcast!.link,
|
||||
id: MediaService.currentPodcast!.id,
|
||||
isVoiceMessage: false,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showPlayerBottomSheet(BuildContext context) {
|
||||
final sheetKey = GlobalKey<ExpandableBottomSheetState>();
|
||||
bool isExpanded = false;
|
||||
final detailsState = context.read<StudioDetailsState>();
|
||||
if (detailsState.args?.type == 'video') {
|
||||
detailsState.getStudioDetails(
|
||||
MediaService.currentPodcast!.id,
|
||||
args: detailsState.podcastArgs,
|
||||
fetchOnly: true,
|
||||
);
|
||||
}
|
||||
final state = context.read<PodcastsState>();
|
||||
showModalBottomSheet(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: ActionSheetUtils.mediaQueryData.size.width,
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => ChangeNotifierProvider<PodcastsState>.value(
|
||||
value: state,
|
||||
child: Consumer<StudioDetailsState>(
|
||||
builder: (context, state, child) => MediaQuery(
|
||||
data: ActionSheetUtils.mediaQueryData,
|
||||
child: ExpandableBottomSheet(
|
||||
key: sheetKey,
|
||||
background: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height * 0.7,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
),
|
||||
persistentHeader: GestureDetector(
|
||||
onVerticalDragUpdate: (details) {
|
||||
if (details.delta.dy > 10) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
AudioPlayerWidget(
|
||||
podcast: MediaService.currentPodcast!,
|
||||
),
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Column(
|
||||
children: [
|
||||
DidvanIconButton(
|
||||
size: 32,
|
||||
icon: DidvanIcons.angle_up_regular,
|
||||
onPressed: () {
|
||||
if (!isExpanded) {
|
||||
sheetKey.currentState?.expand();
|
||||
isExpanded = true;
|
||||
} else {
|
||||
isExpanded = false;
|
||||
sheetKey.currentState?.contract();
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
expandableContent: state.appState == AppState.busy
|
||||
? Container(
|
||||
height: MediaQuery.of(context).size.height / 2,
|
||||
alignment: Alignment.center,
|
||||
child: SpinKitSpinningLines(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
)
|
||||
: StudioDetailsWidget(
|
||||
onMarkChanged: (id, value) => context
|
||||
.read<PodcastsState>()
|
||||
.changeMark(id, value, true),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
import 'package:carousel_slider/carousel_slider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DidvanSlider extends StatefulWidget {
|
||||
final Widget Function(BuildContext context, int index, int realIndex)
|
||||
itemBuilder;
|
||||
final int itemCount;
|
||||
final double viewportFraction;
|
||||
final bool? enableIndicator;
|
||||
final double? height;
|
||||
final void Function(int, CarouselPageChangedReason)? onPageChanged;
|
||||
const DidvanSlider({
|
||||
super.key,
|
||||
required this.itemBuilder,
|
||||
required this.itemCount,
|
||||
required this.viewportFraction,
|
||||
this.onPageChanged,
|
||||
this.enableIndicator,
|
||||
this.height,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DidvanSlider> createState() => _DidvanSliderState();
|
||||
}
|
||||
|
||||
class _DidvanSliderState extends State<DidvanSlider> {
|
||||
int _currentIndex = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
CarouselSlider.builder(
|
||||
itemCount: widget.itemCount,
|
||||
itemBuilder: widget.itemBuilder,
|
||||
options: CarouselOptions(
|
||||
height: widget.height,
|
||||
autoPlay: true,
|
||||
padEnds: false,
|
||||
viewportFraction: widget.viewportFraction,
|
||||
onPageChanged: (index, reason) {
|
||||
widget.onPageChanged?.call(index, reason);
|
||||
if (widget.enableIndicator == true) {
|
||||
setState(() {
|
||||
_currentIndex = index;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
if (widget.enableIndicator == true) ...[
|
||||
const SizedBox(height: 8),
|
||||
_CarouselIndicator(
|
||||
count: widget.itemCount,
|
||||
currentIndex: _currentIndex,
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CarouselIndicator extends StatelessWidget {
|
||||
final int count;
|
||||
final int currentIndex;
|
||||
const _CarouselIndicator({required this.count, required this.currentIndex});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
for (int i = 0; i < count; i++)
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 2),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
width: 1,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
color: currentIndex == i
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.transparent,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,9 @@ class SkeletonImage extends StatelessWidget {
|
|||
httpHeaders: {'Authorization': 'Bearer ${RequestService.token}'},
|
||||
width: width,
|
||||
height: height,
|
||||
imageUrl: RequestHelper.baseUrl + imageUrl,
|
||||
imageUrl: imageUrl.startsWith('http')
|
||||
? imageUrl
|
||||
: RequestHelper.baseUrl + imageUrl,
|
||||
placeholder: (context, _) => const ShimmerPlaceholder(),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
Loading…
Reference in New Issue