some changes

This commit is contained in:
MohammadTaha Basiri 2023-10-11 03:32:18 +03:30
parent 694529cf07
commit b5ee5887c6
25 changed files with 949 additions and 343 deletions

View File

@ -0,0 +1,27 @@
class MainPageResultType {
final int id;
final String title;
final String image;
final String link;
final String createdAt;
final String type;
MainPageResultType({
required this.id,
required this.title,
required this.image,
required this.link,
required this.createdAt,
required this.type,
});
factory MainPageResultType.fromJson(Map<String, dynamic> json) =>
MainPageResultType(
id: json['id'],
title: json['title'],
image: json['image'] ?? 'https://wallpapercave.com/fwp/wp12977378.jpg',
link: json['link'],
createdAt: json['createdAt'],
type: json['type'],
);
}

View File

@ -6,6 +6,7 @@ class AppBarData {
final bool hasBack; final bool hasBack;
final Widget? trailing; final Widget? trailing;
final bool isSmall; final bool isSmall;
final bool? hasElevation;
AppBarData({ AppBarData({
this.title, this.title,
@ -13,5 +14,6 @@ class AppBarData {
this.hasBack = false, this.hasBack = false,
this.trailing, this.trailing,
this.isSmall = false, this.isSmall = false,
this.hasElevation = true,
}); });
} }

View File

@ -161,21 +161,28 @@ class UserProvider extends CoreProvier {
); );
} }
static Future<void> changeItemMark(String type, int id, bool value) async { static Future<void> changeItemMark(String type, int id, bool? value,
{String? description}) async {
_itemMarkQueue.add({ _itemMarkQueue.add({
'type': type, 'type': type,
'id': id, 'id': id,
'value': value, 'value': value,
'description': description,
}); });
Future.delayed(const Duration(milliseconds: 500), () async { Future.delayed(const Duration(milliseconds: 500), () async {
final lastChange = final lastChange =
_itemMarkQueue.lastWhereOrNull((item) => item['id'] == id); _itemMarkQueue.lastWhereOrNull((item) => item['id'] == id);
if (lastChange == null) return; if (lastChange == null) return;
final service = RequestService(RequestHelper.editItemBookmark(type, id)); final service = RequestService(
if (lastChange['value']) { RequestHelper.editItemBookmark(type, id),
body: {'description': lastChange['description']},
);
if (lastChange['value'] == true) {
await service.post(); await service.post();
} else { } else if (lastChange['value'] == false) {
await service.delete(); await service.delete();
} else {
service.put();
} }
_itemMarkQueue.removeWhere((element) => element['id'] == id); _itemMarkQueue.removeWhere((element) => element['id'] == id);
}); });

View File

@ -18,7 +18,7 @@ class RequestHelper {
String? startDate, String? startDate,
String? endDate, String? endDate,
String? search, String? search,
List<String>? types, List<int>? types,
}) => }) =>
'$_baseHomeUrl/search${_urlConcatGenerator([ '$_baseHomeUrl/search${_urlConcatGenerator([
MapEntry('page', page), MapEntry('page', page),
@ -27,6 +27,16 @@ class RequestHelper {
MapEntry('q', search), MapEntry('q', search),
MapEntry('type', _urlListConcatGenerator(types)), MapEntry('type', _urlListConcatGenerator(types)),
])}'; ])}';
static String searchMarks({
required int page,
String? search,
List<int>? types,
}) =>
'$_baseHomeUrl/mark${_urlConcatGenerator([
MapEntry('page', page),
MapEntry('q', search),
MapEntry('type', _urlListConcatGenerator(types)),
])}';
static const String confirmUsername = '$_baseUserUrl/confirmUsername'; static const String confirmUsername = '$_baseUserUrl/confirmUsername';
static const String changePassword = '$_baseUserUrl/changePassword'; static const String changePassword = '$_baseUserUrl/changePassword';

View File

@ -1,10 +1,7 @@
import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/models/view/app_bar_data.dart'; import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/views/home/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart'; import 'package:didvan/views/home/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart';
import 'package:didvan/views/widgets/overview/news.dart'; import 'package:didvan/views/widgets/overview/multitype.dart';
import 'package:didvan/views/widgets/overview/podcast.dart';
import 'package:didvan/views/widgets/overview/radar.dart'; import 'package:didvan/views/widgets/overview/radar.dart';
import 'package:didvan/views/widgets/overview/video.dart';
import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart';
import 'package:didvan/views/widgets/state_handlers/empty_list.dart'; import 'package:didvan/views/widgets/state_handlers/empty_list.dart';
import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart';
@ -32,13 +29,15 @@ class _FilteredBookmarksState extends State<FilteredBookmarks> {
String get _appBarTitle { String get _appBarTitle {
switch (context.read<FilteredBookmarksState>().type) { switch (context.read<FilteredBookmarksState>().type) {
case 'radar': case 'radar':
return 'تحلیل‌های رادار'; return 'پویش افق';
case 'news': case 'news':
return 'اخبار'; return 'دنیای فولاد';
case 'video': case 'video':
return 'ویدئوها'; return 'ویدئوکستها';
case 'podcast': case 'podcast':
return 'پادکست‌ها'; return 'پادکست‌ها';
case 'new-radar':
return 'تحلیل‌های رادار';
default: default:
return 'پادکست‌ها'; return 'پادکست‌ها';
} }
@ -63,37 +62,46 @@ class _FilteredBookmarksState extends State<FilteredBookmarks> {
state.getBookmarks(page: state.page + 1); state.getBookmarks(page: state.page + 1);
} }
index--; index--;
if (state.type == 'radar') { return MultitypeOverview(
return RadarOverview( item: state.bookmarks[index],
radar: state.bookmarks[index], enableCaption: true,
onMarkChanged: _onBookmarkChanged, onMarkChanged: (id, value) => _onBookmarkChanged(
onCommentsChanged: state.onCommentsChanged, id,
hasUnmarkConfirmation: true, value,
); true,
} ),
if (state.type == 'news') {
return NewsOverview(
news: state.bookmarks[index],
onMarkChanged: _onBookmarkChanged,
hasUnmarkConfirmation: true,
);
}
if (state.type == 'podcast') {
return PodcastOverview(
studioRequestArgs:
const StudioRequestArgs(page: 0, type: 'podcast'),
podcast: state.bookmarks[index],
onMarkChanged: _onBookmarkChanged,
hasUnmarkConfirmation: true,
);
}
return VideoOverview(
studioRequestArgs:
const StudioRequestArgs(page: 0, type: 'video'),
video: state.bookmarks[index],
onMarkChanged: _onBookmarkChanged,
hasUnmarkConfirmation: true,
); );
// if (state.type == 'radar') {
// return RadarOverview(
// radar: state.bookmarks[index],
// onMarkChanged: _onBookmarkChanged,
// onCommentsChanged: state.onCommentsChanged,
// hasUnmarkConfirmation: true,
// );
// }
// if (state.type == 'news') {
// return NewsOverview(
// news: state.bookmarks[index],
// onMarkChanged: _onBookmarkChanged,
// hasUnmarkConfirmation: true,
// );
// }
// if (state.type == 'podcast') {
// return PodcastOverview(
// studioRequestArgs:
// const StudioRequestArgs(page: 0, type: 'podcast'),
// podcast: state.bookmarks[index],
// onMarkChanged: _onBookmarkChanged,
// hasUnmarkConfirmation: true,
// );
// }
// return VideoOverview(
// studioRequestArgs:
// const StudioRequestArgs(page: 0, type: 'video'),
// video: state.bookmarks[index],
// onMarkChanged: _onBookmarkChanged,
// hasUnmarkConfirmation: true,
// );
}, },
childCount: state.bookmarks.length, childCount: state.bookmarks.length,
onRetry: () => state.getBookmarks(page: state.page), onRetry: () => state.getBookmarks(page: state.page),

View File

@ -27,6 +27,7 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
final state = context.read<HomeState>(); final state = context.read<HomeState>();
DesignConfig.updateSystemUiOverlayStyle(); DesignConfig.updateSystemUiOverlayStyle();
_tabController = TabController(length: 4, vsync: this, initialIndex: 0); _tabController = TabController(length: 4, vsync: this, initialIndex: 0);
state.tabController = _tabController;
_tabController.addListener(() { _tabController.addListener(() {
state.currentPageIndex = _tabController.index; state.currentPageIndex = _tabController.index;
}); });
@ -43,14 +44,17 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: const LogoAppBar(), appBar: LogoAppBar(),
body: Consumer<HomeState>( body: Consumer<HomeState>(
builder: (context, state, child) => AnimatedCrossFade( builder: (context, state, child) => AnimatedCrossFade(
duration: DesignConfig.lowAnimationDuration, duration: DesignConfig.lowAnimationDuration,
crossFadeState: state.showSearchPage crossFadeState: state.filtering
? CrossFadeState.showSecond ? CrossFadeState.showSecond
: CrossFadeState.showFirst, : CrossFadeState.showFirst,
firstChild: TabBarView( firstChild: SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: TabBarView(
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
controller: _tabController, controller: _tabController,
children: const [ children: const [
@ -60,6 +64,7 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
Bookmarks(), Bookmarks(),
], ],
), ),
),
secondChild: const SearchPage(), secondChild: const SearchPage(),
), ),
), ),

View File

@ -2,10 +2,13 @@ import 'dart:async';
import 'package:didvan/constants/assets.dart'; import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/category.dart'; import 'package:didvan/models/category.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart';
import 'package:didvan/providers/core.dart'; import 'package:didvan/providers/core.dart';
import 'package:didvan/routes/routes.dart'; import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart'; import 'package:didvan/services/network/request_helper.dart';
import 'package:flutter/material.dart';
class MenuItemType { class MenuItemType {
final String label; final String label;
@ -25,6 +28,24 @@ class HomeState extends CoreProvier {
String lastSearch = ''; String lastSearch = '';
bool _showSearchPage = false; bool _showSearchPage = false;
Timer? timer; Timer? timer;
String? startDate;
String? endDate;
int page = 1;
int lastPage = 1;
final List<CategoryData> selectedCats = [];
final List<OverviewData> results = [];
late TabController tabController;
void resetFilters(bool isInit) {
startDate = null;
endDate = null;
selectedCats.clear();
search = '';
lastSearch = '';
if (!isInit) {
searchAll(page: 1);
}
}
set showSearchPage(bool value) { set showSearchPage(bool value) {
_showSearchPage = value; _showSearchPage = value;
@ -43,12 +64,76 @@ class HomeState extends CoreProvier {
List<MenuItemType> menuItems = []; List<MenuItemType> menuItems = [];
List<CategoryData> categories = []; List<CategoryData> categories = [];
Future<void> searchAll({required int page}) async { bool get filtering =>
selectedCats.isNotEmpty ||
startDate != null ||
endDate != null ||
search.isNotEmpty;
Future<void> searchMarks({required int page}) async {
this.page = page;
if (page == 1) {
results.clear();
appState = AppState.busy;
}
lastSearch = search; lastSearch = search;
final service = RequestService( final service = RequestService(
RequestHelper.searchAll(page: page, search: search), RequestHelper.searchAll(
page: page,
search: search,
endDate: endDate,
types: selectedCats.map((e) => e.id).toList(),
startDate: startDate,
),
); );
await service.httpGet(); await service.httpGet();
if (service.isSuccess) {
lastPage = service.result['lastPage'];
results.addAll(
List<OverviewData>.from(
service.result['contents'].map(
(e) => OverviewData.fromJson(e),
),
),
);
lastPage = service.result['lastPage'];
appState = AppState.idle;
return;
}
appState = AppState.failed;
}
Future<void> searchAll({required int page}) async {
this.page = page;
if (page == 1) {
results.clear();
appState = AppState.busy;
}
lastSearch = search;
final service = RequestService(
RequestHelper.searchAll(
page: page,
search: search,
endDate: endDate,
types: selectedCats.map((e) => e.id).toList(),
startDate: startDate,
),
);
await service.httpGet();
if (service.isSuccess) {
lastPage = service.result['lastPage'];
results.addAll(
List<OverviewData>.from(
service.result['contents'].map(
(e) => OverviewData.fromJson(e),
),
),
);
lastPage = service.result['lastPage'];
appState = AppState.idle;
return;
}
appState = AppState.failed;
} }
void refresh() { void refresh() {
@ -62,7 +147,7 @@ class HomeState extends CoreProvier {
), ),
MenuItemType( MenuItemType(
label: 'پویش افق', label: 'پویش افق',
asset: Assets.fooladWorld, asset: Assets.ofogh,
link: Routes.radars, link: Routes.radars,
), ),
MenuItemType( MenuItemType(

View File

@ -134,6 +134,7 @@ class _MainPageSection extends StatelessWidget {
], ],
), ),
), ),
if (list.type != 'podcast')
DidvanSlider( DidvanSlider(
height: 260, height: 260,
itemCount: list.contents.length, itemCount: list.contents.length,
@ -171,7 +172,9 @@ class _MainPageSection extends StatelessWidget {
// ), // ),
// ), // ),
if (list.type == 'podcast') if (list.type == 'podcast')
Column( Padding(
padding: const EdgeInsets.only(top: 28),
child: Column(
children: list.contents children: list.contents
.map( .map(
(e) => Padding( (e) => Padding(
@ -187,6 +190,7 @@ class _MainPageSection extends StatelessWidget {
) )
.toList(), .toList(),
), ),
),
// if (list.type != 'news' && // if (list.type != 'news' &&
// list.type != 'radar' && // list.type != 'radar' &&
// list.type != 'video' && // list.type != 'video' &&

View File

@ -1,8 +1,14 @@
import 'package:didvan/models/enums.dart'; import 'package:didvan/models/enums.dart';
import 'package:didvan/models/home_page_content/home_page_content.dart'; import 'package:didvan/models/home_page_content/home_page_content.dart';
import 'package:didvan/models/requests/news.dart';
import 'package:didvan/models/requests/radar.dart';
import 'package:didvan/providers/core.dart'; import 'package:didvan/providers/core.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart'; import 'package:didvan/services/network/request_helper.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher_string.dart';
class MainPageState extends CoreProvier { class MainPageState extends CoreProvier {
late MainPageContent content; late MainPageContent content;
@ -22,4 +28,63 @@ class MainPageState extends CoreProvier {
_getMainPageContent(); _getMainPageContent();
}); });
} }
void markChangeHandler(String type, int id, bool value) {
content.lists
.firstWhere((element) => element.type == type)
.contents
.firstWhere((element) => element.id == id)
.marked = value;
notifyListeners();
}
void navigationHandler(
String type,
int id,
String? link,
) {
link = link ?? '';
dynamic args;
switch (type) {
case 'news':
{
link = Routes.newsDetails;
args = {
'onMarkChanged': (id, value) => markChangeHandler(type, id, value),
'id': id,
'args': const NewsRequestArgs(page: 0),
'hasUnmarkConfirmation': false,
};
break;
}
case 'radar':
{
link = Routes.radarDetails;
args = {
'onMarkChanged': (id, value) => markChangeHandler(type, id, value),
'id': id,
'args': const RadarRequestArgs(page: 0),
'hasUnmarkConfirmation': false,
};
break;
}
case 'video':
{
link = Routes.studioDetails;
args = {
type: 'podcast',
id: 'id',
};
break;
}
}
if (link == '') {
return;
}
if (link.startsWith('http')) {
launchUrlString('$link?accessToken=${RequestService.token}');
return;
}
Navigator.of(ActionSheetUtils.context).pushNamed(link, arguments: args);
}
} }

View File

@ -3,11 +3,15 @@ import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/home_page_content/content.dart'; import 'package:didvan/models/home_page_content/content.dart';
import 'package:didvan/providers/user.dart'; import 'package:didvan/providers/user.dart';
import 'package:didvan/utils/date_time.dart'; import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/home/home_state.dart';
import 'package:didvan/views/home/main/main_page_state.dart';
import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/card.dart';
import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text.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:persian_number_utility/persian_number_utility.dart'; import 'package:persian_number_utility/persian_number_utility.dart';
import 'package:provider/provider.dart';
class MainPageGeneralItem extends StatefulWidget { class MainPageGeneralItem extends StatefulWidget {
final MainPageContentType content; final MainPageContentType content;
@ -74,19 +78,79 @@ class _MainPageGeneralItemState extends State<MainPageGeneralItem> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DidvanCard( return GestureDetector(
onTap: () => context.read<MainPageState>().navigationHandler(
widget.type,
widget.content.id,
widget.content.link,
),
child: DidvanCard(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
child: Column( child: Column(
children: [
Stack(
children: [ children: [
SkeletonImage( SkeletonImage(
imageUrl: widget.content.image, imageUrl: widget.content.image,
height: 140, height: 140,
width: double.infinity, width: double.infinity,
borderRadius: const BorderRadius.vertical(top: Radius.circular(10)), borderRadius:
const BorderRadius.vertical(top: Radius.circular(10)),
),
if (widget.type == 'video')
Positioned.fill(
child: 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,
),
),
),
),
if (widget.type == 'radar')
Positioned(
left: 0,
bottom: 0,
child: Container(
width: 36,
height: 36,
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
),
color: Theme.of(context).colorScheme.surface,
),
child: SvgPicture.asset(
context
.read<HomeState>()
.categories
.firstWhere(
(element) =>
element.id.toString() ==
widget.content.subtitles[2],
)
.asset ??
'',
),
),
),
],
), ),
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -148,6 +212,7 @@ class _MainPageGeneralItemState extends State<MainPageGeneralItem> {
) )
], ],
), ),
),
); );
} }

View File

@ -1,10 +1,37 @@
import 'package:didvan/models/enums.dart';
import 'package:didvan/views/home/home_state.dart';
import 'package:didvan/views/home/search/widgets/search_result_item.dart';
import 'package:didvan/views/widgets/state_handlers/empty_list.dart';
import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class SearchPage extends StatelessWidget { class SearchPage extends StatelessWidget {
const SearchPage({super.key}); const SearchPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const Placeholder(); final state = context.watch<HomeState>();
return SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: StateHandler<HomeState>(
state: state,
enableEmptyState:
state.appState == AppState.idle && state.results.isEmpty,
emptyState: const EmptyList(),
onRetry: () => state.searchAll(page: state.page),
builder: (context, state) => ListView.builder(
padding: const EdgeInsets.all(16),
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.only(bottom: 16),
child: SearchResultItem(
item: state.results[index],
),
),
itemCount: state.results.length,
),
),
);
} }
} }

View File

@ -0,0 +1,171 @@
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/requests/news.dart';
import 'package:didvan/models/requests/radar.dart';
import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/media/media.dart';
import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart';
import 'package:didvan/views/widgets/didvan/card.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:flutter/material.dart';
import 'package:persian_number_utility/persian_number_utility.dart';
import 'package:provider/provider.dart';
class SearchResultItem extends StatelessWidget {
final OverviewData item;
const SearchResultItem({
Key? key,
required this.item,
}) : super(key: key);
get _targetPageArgs {
if (item.type == 'radar') {
return const RadarRequestArgs(page: 0);
}
if (item.type == 'news') {
return const NewsRequestArgs(page: 0);
}
return StudioRequestArgs(page: 0, type: item.type);
}
String get _targetPageRouteName {
if (item.type == 'radar') {
return Routes.radarDetails;
}
if (item.type == 'news') {
return Routes.newsDetails;
}
return Routes.studioDetails;
}
IconData get _icon {
if (item.type == 'radar') {
return DidvanIcons.radar_light;
}
if (item.type == 'news') {
return DidvanIcons.news_light;
}
if (item.type == 'video') {
return DidvanIcons.video_light;
}
return DidvanIcons.podcast_light;
}
@override
Widget build(BuildContext context) {
return DidvanCard(
onTap: () async {
if (item.type == 'podcast') {
final state = context.read<StudioDetailsState>();
await state.getStudioDetails(
item.id,
args: const StudioRequestArgs(page: 0, type: 'podcast'),
);
MediaService.handleAudioPlayback(
audioSource: item.link,
id: item.id,
isNetworkAudio: true,
isVoiceMessage: false,
);
return;
}
Navigator.of(context).pushNamed(
_targetPageRouteName,
arguments: {
'id': item.id,
'args': _targetPageArgs,
},
);
},
child: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
SkeletonImage(imageUrl: item.image, height: 80, width: 80),
Container(
padding:
const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondary,
borderRadius: const BorderRadius.horizontal(
left: Radius.circular(10),
),
),
child: Icon(
_icon,
color: Theme.of(context).colorScheme.white,
size: 18,
),
),
],
),
const SizedBox(width: 8),
Expanded(
child: SizedBox(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
DidvanText(
item.title,
style: Theme.of(context).textTheme.bodyLarge,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
Row(
children: [
const Icon(
DidvanIcons.calendar_day_light,
size: 18,
),
const SizedBox(width: 4),
DidvanText(
DateTime.parse(item.createdAt).toPersianDateStr(),
style: Theme.of(context).textTheme.labelSmall,
),
const Spacer(),
],
),
],
),
),
),
],
),
],
),
);
}
static Widget get placeholder => const DidvanCard(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ShimmerPlaceholder(height: 80, width: 80),
SizedBox(width: 8),
SizedBox(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ShimmerPlaceholder(height: 18, width: 150),
SizedBox(height: 8),
ShimmerPlaceholder(height: 18, width: 100),
Spacer(),
ShimmerPlaceholder(height: 14, width: 80),
],
),
),
],
),
);
}

View File

@ -138,6 +138,7 @@ class _StatisticState extends State<Statistic> {
), ),
if (state.appState != AppState.failed) if (state.appState != AppState.failed)
CategoriesList( CategoriesList(
top: 0,
categories: state.categories, categories: state.categories,
isColapsed: state.isColapsed, isColapsed: state.isColapsed,
onSelected: (id) { onSelected: (id) {

View File

@ -159,6 +159,7 @@ class _StatisticDetailsState extends State<StatisticDetails> {
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
CategoriesList( CategoriesList(
top: 0,
isColapsed: false, isColapsed: false,
isAppBar: false, isAppBar: false,
selectedCats: [state.currentDateRange], selectedCats: [state.currentDateRange],

View File

@ -14,7 +14,9 @@ class MainCategories extends StatelessWidget {
if (link.startsWith('http')) { if (link.startsWith('http')) {
launchUrlString(link); launchUrlString(link);
} else if (link.startsWith('tab-')) { } else if (link.startsWith('tab-')) {
context.read<HomeState>().currentPageIndex = 1; final state = context.read<HomeState>();
state.currentPageIndex = 1;
state.tabController.animateTo(1);
} else { } else {
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(
link.contains('audio') ? link.replaceAll('/audio', '') : link, link.contains('audio') ? link.replaceAll('/audio', '') : link,

View File

@ -41,10 +41,12 @@ class _NewsState extends State<News> {
appBarData: AppBarData( appBarData: AppBarData(
title: 'دنیای فولاد', title: 'دنیای فولاد',
hasBack: true, hasBack: true,
hasElevation: false,
), ),
slivers: [ slivers: [
if (state.appState != AppState.failed) if (state.appState != AppState.failed)
SliverAppBar( SliverAppBar(
backgroundColor: Theme.of(context).colorScheme.surface,
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
pinned: true, pinned: true,

View File

@ -8,6 +8,7 @@ import 'package:didvan/providers/core.dart';
import 'package:didvan/services/app_initalizer.dart'; import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart'; import 'package:didvan/services/network/request_helper.dart';
import 'package:collection/collection.dart';
class PodcastsState extends CoreProvier { class PodcastsState extends CoreProvier {
final List<OverviewData> studios = []; final List<OverviewData> studios = [];
@ -129,7 +130,7 @@ class PodcastsState extends CoreProvier {
} }
Future<void> changeMark(int id, bool value, bool shouldUpdate) async { Future<void> changeMark(int id, bool value, bool shouldUpdate) async {
studios.firstWhere((element) => element.id == id).marked = value; studios.firstWhereOrNull((element) => element.id == id)?.marked = value;
if (shouldUpdate) { if (shouldUpdate) {
notifyListeners(); notifyListeners();
} }

View File

@ -8,11 +8,12 @@ import 'package:didvan/models/category.dart';
import 'package:didvan/models/enums.dart'; import 'package:didvan/models/enums.dart';
import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/models/requests/radar.dart';
import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/views/radar/radar_state.dart'; import 'package:didvan/views/radar/radar_state.dart';
import 'package:didvan/views/widgets/categories_gird.dart'; import 'package:didvan/views/widgets/categories_gird.dart';
import 'package:didvan/views/widgets/categories_list.dart'; import 'package:didvan/views/widgets/categories_list.dart';
import 'package:didvan/views/widgets/date_picker_button.dart'; import 'package:didvan/views/widgets/date_picker_button.dart';
import 'package:didvan/views/widgets/logo_app_bar.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart';
import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/widgets/overview/radar.dart'; import 'package:didvan/views/widgets/overview/radar.dart';
import 'package:didvan/views/widgets/search_field.dart'; import 'package:didvan/views/widgets/search_field.dart';
@ -59,7 +60,17 @@ class _RadarState extends State<Radar> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<RadarState>( return Consumer<RadarState>(
builder: (context, state, child) => Stack( builder: (context, state, child) => DidvanScaffold(
padding: EdgeInsets.zero,
appBarData: AppBarData(
hasBack: true,
hasElevation: false,
title: 'پویش‌های افق',
),
children: [
SizedBox(
height: MediaQuery.of(context).size.height,
child: Stack(
children: [ children: [
CustomScrollView( CustomScrollView(
physics: _isAnimating physics: _isAnimating
@ -67,29 +78,11 @@ class _RadarState extends State<Radar> {
: const ClampingScrollPhysics(), : const ClampingScrollPhysics(),
controller: _scrollController, controller: _scrollController,
slivers: [ slivers: [
const SliverToBoxAdapter(child: LogoAppBar()),
if (state.appState != AppState.failed)
SliverPadding(
padding: const EdgeInsets.only(
left: 16,
right: 16,
bottom: 16,
),
sliver: SliverToBoxAdapter(
child: SearchField(
focusNode: _focusNode,
isFiltered: state.filtering,
title: 'رادار',
onChanged: _onChanged,
onFilterButtonPressed: _showFilterBottomSheet,
),
),
),
if (!state.filtering && if (!state.filtering &&
!state.searching && !state.searching &&
state.appState != AppState.failed) state.appState != AppState.failed)
const SliverToBoxAdapter( const SliverToBoxAdapter(
child: SizedBox(height: 276), child: SizedBox(height: 320),
), ),
if (state.appState != AppState.failed) if (state.appState != AppState.failed)
SliverPadding( SliverPadding(
@ -122,8 +115,8 @@ class _RadarState extends State<Radar> {
enableEmptyState: state.radars.isEmpty, enableEmptyState: state.radars.isEmpty,
emptyState: Padding( emptyState: Padding(
padding: const EdgeInsets.only(bottom: 120), padding: const EdgeInsets.only(bottom: 120),
child: child: EmptyResult(
EmptyResult(onNewSearch: () => _focusNode.requestFocus()), onNewSearch: () => _focusNode.requestFocus()),
), ),
placeholder: RadarOverview.placeholder, placeholder: RadarOverview.placeholder,
builder: (context, state, index) { builder: (context, state, index) {
@ -143,8 +136,8 @@ class _RadarState extends State<Radar> {
state.onCommentsChanged(id, count), state.onCommentsChanged(id, count),
radarRequestArgs: RadarRequestArgs( radarRequestArgs: RadarRequestArgs(
page: state.page, page: state.page,
categories: categories: List.from(
List.from(state.selectedCats.map((cat) => cat.id)), state.selectedCats.map((cat) => cat.id)),
endDate: state.endDate, endDate: state.endDate,
isSingleItem: false, isSingleItem: false,
search: state.search, search: state.search,
@ -163,7 +156,7 @@ class _RadarState extends State<Radar> {
), ),
if (state.appState != AppState.failed) if (state.appState != AppState.failed)
CategoriesRow1( CategoriesRow1(
topPadding: 300, topPadding: 212,
rightPadding: 124, rightPadding: 124,
onSelected: _onCategorySelected, onSelected: _onCategorySelected,
categories: state.categories, categories: state.categories,
@ -181,12 +174,33 @@ class _RadarState extends State<Radar> {
!state.searching && !state.searching &&
!state.filtering) !state.filtering)
CategoriesList( CategoriesList(
top: 60,
categories: state.categories, categories: state.categories,
isColapsed: isColapsed:
state.isColapsed || state.searching || state.filtering, state.isColapsed || state.searching || state.filtering,
onSelected: (_) => state.getRadars(page: 1), onSelected: (_) => state.getRadars(page: 1),
selectedCats: state.selectedCats, selectedCats: state.selectedCats,
), ),
if (state.appState != AppState.failed)
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.vertical(
bottom: Radius.circular(12),
),
),
child: SearchField(
focusNode: _focusNode,
isFiltered: state.filtering,
title: 'رادار',
onChanged: _onChanged,
onFilterButtonPressed: _showFilterBottomSheet,
),
),
],
),
),
], ],
), ),
); );

View File

@ -66,7 +66,7 @@ class CategoriesRow2 extends StatelessWidget {
return AnimatedPositioned( return AnimatedPositioned(
curve: Curves.easeIn, curve: Curves.easeIn,
duration: DesignConfig.mediumAnimationDuration, duration: DesignConfig.mediumAnimationDuration,
top: isColapsed ? 12 : 176 + d.padding.top, top: isColapsed ? 12 : 92 + d.padding.top,
left: isColapsed ? -d.size.width : 0, left: isColapsed ? -d.size.width : 0,
right: isColapsed ? d.size.width : 0, right: isColapsed ? d.size.width : 0,
child: Row( child: Row(

View File

@ -11,6 +11,7 @@ class CategoriesList extends StatefulWidget {
final List<CategoryData> selectedCats; final List<CategoryData> selectedCats;
final List<CategoryData> categories; final List<CategoryData> categories;
final void Function(int id) onSelected; final void Function(int id) onSelected;
final double top;
const CategoriesList({ const CategoriesList({
Key? key, Key? key,
required this.isColapsed, required this.isColapsed,
@ -18,6 +19,7 @@ class CategoriesList extends StatefulWidget {
required this.categories, required this.categories,
required this.onSelected, required this.onSelected,
this.isAppBar = true, this.isAppBar = true,
required this.top,
}) : super(key: key); }) : super(key: key);
@override @override
@ -73,7 +75,7 @@ class _CategoriesListState extends State<CategoriesList> {
); );
if (widget.isAppBar) { if (widget.isAppBar) {
return Positioned( return Positioned(
top: 0, top: widget.top,
left: 0, left: 0,
right: 0, right: 0,
child: AnimatedCrossFade( child: AnimatedCrossFade(

View File

@ -89,6 +89,8 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
pinned: true, pinned: true,
backgroundColor: widget.backgroundColor ?? backgroundColor: widget.backgroundColor ??
Theme.of(context).colorScheme.surface, Theme.of(context).colorScheme.surface,
elevation:
widget.appBarData?.hasElevation == false ? 0 : null,
flexibleSpace: DidvanAppBar( flexibleSpace: DidvanAppBar(
appBarData: widget.appBarData!, appBarData: widget.appBarData!,
backgroundColor: widget.backgroundColor ?? backgroundColor: widget.backgroundColor ??

View File

@ -2,11 +2,14 @@ import 'dart:async';
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/app_icons.dart';
import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/category.dart';
import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/routes/routes.dart'; import 'package:didvan/routes/routes.dart';
import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/home/home_state.dart'; import 'package:didvan/views/home/home_state.dart';
import 'package:didvan/views/widgets/date_picker_button.dart';
import 'package:didvan/views/widgets/didvan/checkbox.dart';
import 'package:didvan/views/widgets/item_title.dart';
import 'package:didvan/views/widgets/search_field.dart'; import 'package:didvan/views/widgets/search_field.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';
@ -14,10 +17,9 @@ import 'package:didvan/views/widgets/logos/didvan_vertical_logo.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher_string.dart';
class LogoAppBar extends StatelessWidget implements PreferredSizeWidget { class LogoAppBar extends StatelessWidget implements PreferredSizeWidget {
const LogoAppBar({Key? key}) : super(key: key); LogoAppBar({Key? key}) : super(key: key);
@override @override
Size get preferredSize => const Size(double.infinity, 144); Size get preferredSize => const Size(double.infinity, 144);
@ -64,19 +66,21 @@ class LogoAppBar extends StatelessWidget implements PreferredSizeWidget {
DidvanIconButton( DidvanIconButton(
icon: DidvanIcons.notification_light, icon: DidvanIcons.notification_light,
size: 32, size: 32,
onPressed: () => _handleMenuOpen(context), onPressed: () => {},
), ),
], ],
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
SearchField( Consumer<HomeState>(
title: 'دیدوان', builder: (context, state, child) => SearchField(
title: state.currentPageIndex == 3 ? 'رصدهای من' : 'دیدوان',
onChanged: (value) => _onChanged(value, context), onChanged: (value) => _onChanged(value, context),
focusNode: FocusNode(), focusNode: FocusNode(),
onFilterButtonPressed: () => _handleMenuOpen(context), onFilterButtonPressed: () => _showFilterBottomSheet(context),
isFiltered: false, isFiltered: state.filtering && state.search == '',
),
), ),
], ],
), ),
@ -85,82 +89,167 @@ class LogoAppBar extends StatelessWidget implements PreferredSizeWidget {
void _onChanged(String value, BuildContext context) { void _onChanged(String value, BuildContext context) {
final state = context.read<HomeState>(); final state = context.read<HomeState>();
if (state.currentPageIndex == 3) {
//search bookmarks
}
if (value.length < 3 && value.isNotEmpty || state.lastSearch == value) { if (value.length < 3 && value.isNotEmpty || state.lastSearch == value) {
return; return;
} }
state.timer?.cancel(); state.timer?.cancel();
state.timer = Timer(const Duration(seconds: 1), () { state.timer = Timer(const Duration(seconds: 1), () {
state.search = value; state.search = value;
if (state.currentPageIndex == 3) {
state.searchMarks(page: 1);
} else {
state.searchAll(page: 1); state.searchAll(page: 1);
}
}); });
} }
void _handleMenuOpen(BuildContext context) { final categoryFilters = [
ActionSheetUtils.showBottomSheet( CategoryData(id: 1, label: 'پویش افق'),
CategoryData(id: 2, label: 'دنیای فولاد'),
CategoryData(id: 3, label: 'ویدئوکست'),
CategoryData(id: 4, label: 'پادکست'),
CategoryData(id: 1, label: 'تحلیل‌های راداری'),
CategoryData(id: 1, label: 'سها'),
];
Future<void> _showFilterBottomSheet(BuildContext context) async {
final state = context.read<HomeState>();
await ActionSheetUtils.showBottomSheet(
data: ActionSheetData( data: ActionSheetData(
title: 'فیلتر جستجو',
smallDismissButton: true,
titleIcon: DidvanIcons.filter_regular,
dismissTitle: 'حذف فیلتر',
confrimTitle: 'نمایش نتایج',
onDismissed: () => state.resetFilters(false),
onConfirmed: () => state.searchAll(page: 1),
content: Column( content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( if (state.currentPageIndex != 3) ...[
crossAxisAlignment: CrossAxisAlignment.center, ItemTitle(
children: [ title: 'تاریخ ایجاد',
SvgPicture.asset( style: Theme.of(context).textTheme.bodyMedium,
Assets.strategicRadarIcon, icon: DidvanIcons.calendar_range_regular,
width: 48,
height: 48,
), ),
const SizedBox(width: 4), const SizedBox(height: 8),
DidvanText( StatefulBuilder(
'سامانه رادارهای استراتژیک', builder: (context, setState) => Row(
style: Theme.of(context).textTheme.titleMedium, children: [
DatePickerButton(
initialValue: state.startDate,
emptyText: 'از تاریخ',
onPicked: (date) =>
setState(() => state.startDate = date),
lastDate: state.endDate,
),
const SizedBox(width: 8),
DatePickerButton(
initialValue: state.endDate,
emptyText: 'تا تاریخ',
onPicked: (date) => setState(() => state.endDate = date),
firstDate: state.startDate,
), ),
], ],
), ),
const SizedBox(height: 16), ),
_BottomSheetItem( const SizedBox(height: 28),
icon: Assets.progressRadarIcon, ],
title: 'رادار روند', ItemTitle(
enabled: true, title: 'دسته بندی',
onTap: () { icon: DidvanIcons.radar_regular,
launchUrlString( style: Theme.of(context).textTheme.bodyMedium,
'https://trend.didvan.app/', ),
mode: LaunchMode.inAppWebView, const SizedBox(height: 12),
); Wrap(
children: [
for (var i = 0; i < categoryFilters.length; i++)
SizedBox(
width: (MediaQuery.of(context).size.width - 40) / 2,
child: DidvanCheckbox(
title: categoryFilters[i].label,
value: state.selectedCats.contains(state.categories[i]),
onChanged: (value) {
if (value) {
state.selectedCats.add(state.categories[i]);
return;
}
state.selectedCats.remove(state.categories[i]);
}, },
), ),
const SizedBox(height: 16),
_BottomSheetItem(
icon: Assets.techRadarIcon,
title: 'رادار تکنولوژی',
onTap: () {},
),
const SizedBox(height: 16),
_BottomSheetItem(
icon: Assets.riskRadarIcon,
title: 'رادار ریسک',
enabled: true,
onTap: () {
launchUrlString(
'https://risk.didvan.app/',
mode: LaunchMode.inAppWebView,
);
},
),
const SizedBox(height: 16),
_BottomSheetItem(
icon: Assets.startupRadarIcon,
title: 'رادار استارت‌آپ',
onTap: () {},
), ),
], ],
), ),
hasConfirmButton: false, ],
hasDismissButton: false, ),
), ),
); );
} }
// void _handleMenuOpen(BuildContext context) {
// ActionSheetUtils.showBottomSheet(
// data: ActionSheetData(
// content: Column(
// children: [
// Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// SvgPicture.asset(
// Assets.strategicRadarIcon,
// width: 48,
// height: 48,
// ),
// const SizedBox(width: 4),
// DidvanText(
// 'سامانه رادارهای استراتژیک',
// style: Theme.of(context).textTheme.titleMedium,
// ),
// ],
// ),
// const SizedBox(height: 16),
// _BottomSheetItem(
// icon: Assets.progressRadarIcon,
// title: 'رادار روند',
// enabled: true,
// onTap: () {
// launchUrlString(
// 'https://trend.didvan.app/',
// mode: LaunchMode.inAppWebView,
// );
// },
// ),
// const SizedBox(height: 16),
// _BottomSheetItem(
// icon: Assets.techRadarIcon,
// title: 'رادار تکنولوژی',
// onTap: () {},
// ),
// const SizedBox(height: 16),
// _BottomSheetItem(
// icon: Assets.riskRadarIcon,
// title: 'رادار ریسک',
// enabled: true,
// onTap: () {
// launchUrlString(
// 'https://risk.didvan.app/',
// mode: LaunchMode.inAppWebView,
// );
// },
// ),
// const SizedBox(height: 16),
// _BottomSheetItem(
// icon: Assets.startupRadarIcon,
// title: 'رادار استارت‌آپ',
// onTap: () {},
// ),
// ],
// ),
// hasConfirmButton: false,
// hasDismissButton: false,
// ),
// );
// }
} }
class _BottomSheetItem extends StatelessWidget { class _BottomSheetItem extends StatelessWidget {

View File

@ -4,7 +4,9 @@ import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/requests/news.dart'; import 'package:didvan/models/requests/news.dart';
import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/models/requests/radar.dart';
import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/providers/user.dart';
import 'package:didvan/routes/routes.dart'; import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/media/media.dart';
import 'package:didvan/utils/date_time.dart'; import 'package:didvan/utils/date_time.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/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/card.dart';
@ -66,11 +68,18 @@ class MultitypeOverview extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DidvanCard( return DidvanCard(
onTap: () { onTap: () async {
if (item.type == 'podcast') { if (item.type == 'podcast') {
context.read<StudioDetailsState>().getStudioDetails( final state = context.read<StudioDetailsState>();
await state.getStudioDetails(
item.id, item.id,
args: StudioRequestArgs(page: 0, type: item.type), args: const StudioRequestArgs(page: 0, type: 'podcast'),
);
MediaService.handleAudioPlayback(
audioSource: item.link,
id: item.id,
isNetworkAudio: true,
isVoiceMessage: false,
); );
return; return;
} }
@ -196,8 +205,14 @@ class MultitypeOverview extends StatelessWidget {
), ),
DidvanTextField( DidvanTextField(
disableBorders: true, disableBorders: true,
initialValue: item.description,
hintText: 'برای اضافه کردن یادداشت لمس کنید.', hintText: 'برای اضافه کردن یادداشت لمس کنید.',
onChanged: (value) {}, onChanged: (value) => UserProvider.changeItemMark(
item.type,
item.id,
null,
description: value,
),
isSmall: true, isSmall: true,
), ),
], ],

View File

@ -131,7 +131,7 @@ packages:
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
collection: collection:
dependency: transitive dependency: "direct main"
description: description:
name: collection name: collection
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687

View File

@ -68,6 +68,7 @@ dependencies:
url: https://github.com/tintran-dev/betterplayer.git url: https://github.com/tintran-dev/betterplayer.git
assets_audio_player: ^3.1.1 assets_audio_player: ^3.1.1
fl_chart: ^0.63.0 fl_chart: ^0.63.0
collection: ^1.17.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: