From 8c40b10dacc8c06e9ce3ed6a4678567a7388de50 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Fri, 22 Apr 2022 11:34:28 +0000 Subject: [PATCH] Dev --- .fandogh/fandogh.yaml | 8 +- lib/assets/images/categories/glob-dark.svg | 54 ++++ lib/assets/images/categories/glob-light.svg | 54 ++++ lib/assets/images/categories/steel-dark.svg | 9 + lib/assets/images/categories/steel-light.svg | 9 + lib/assets/images/categories/stock-dark.svg | 42 +++ lib/assets/images/categories/stock-light.svg | 42 +++ .../images/empty_states/studio-dark.svg | 48 --- .../images/empty_states/studio-light.svg | 55 ---- lib/config/theme_data.dart | 1 + lib/constants/app_strings.dart | 1 - lib/constants/assets.dart | 29 +- lib/models/category.dart | 3 +- lib/models/statistic_data/data.dart | 64 ++++ lib/models/statistic_data/statistic_data.dart | 29 ++ lib/models/view/radar_category.dart | 7 - lib/providers/server_data.dart | 12 +- lib/providers/user.dart | 17 + lib/routes/route_generator.dart | 20 +- lib/routes/routes.dart | 1 + lib/services/media/media.dart | 3 +- lib/services/network/request_helper.dart | 20 ++ lib/utils/date_time.dart | 7 + .../authentication/screens/username.dart | 14 +- lib/views/home/comments/comments.dart | 2 +- lib/views/home/comments/comments_state.dart | 45 +++ .../{comment_item.dart => comment.dart} | 50 ++- lib/views/home/direct/direct.dart | 45 ++- lib/views/home/direct/direct_state.dart | 13 + lib/views/home/direct/widgets/message.dart | 169 +++++----- lib/views/home/home.dart | 4 +- lib/views/home/news/news.dart | 3 +- lib/views/home/radar/radar.dart | 46 ++- lib/views/home/radar/radar_state.dart | 34 +- .../home/radar/widgets/categories_list.dart | 130 -------- .../home/settings/about_us/about_us.dart | 25 -- .../home/settings/bookmarks/bookmarks.dart | 2 +- lib/views/home/settings/settings.dart | 12 +- lib/views/home/statistic/statistic.dart | 205 ++++++++++++ .../statistic_details/statistic_details.dart | 298 ++++++++++++++++++ .../statistic_details_state.dart | 169 ++++++++++ lib/views/home/statistic/statistic_state.dart | 102 ++++++ .../statistic/widgets/statistic_overview.dart | 164 ++++++++++ lib/views/home/statistics/statistics.dart | 26 -- lib/views/home/studio/studio.dart | 2 +- .../studio_details/studio_details.mobile.dart | 1 - .../studio_details/studio_details.web.dart | 7 +- .../{radar => }/widgets/categories_gird.dart | 46 ++- lib/views/home/widgets/categories_list.dart | 146 +++++++++ .../{radar => }/widgets/category_item.dart | 21 +- lib/views/home/widgets/search_field.dart | 2 +- lib/views/widgets/didvan/app_bar.dart | 1 + lib/views/widgets/didvan/page_view.dart | 33 +- lib/views/widgets/didvan/scaffold.dart | 10 +- .../state_handlers/sliver_state_handler.dart | 9 +- .../widgets/state_handlers/state_handler.dart | 11 +- pubspec.lock | 54 +++- pubspec.yaml | 3 +- 58 files changed, 1943 insertions(+), 496 deletions(-) create mode 100644 lib/assets/images/categories/glob-dark.svg create mode 100644 lib/assets/images/categories/glob-light.svg create mode 100644 lib/assets/images/categories/steel-dark.svg create mode 100644 lib/assets/images/categories/steel-light.svg create mode 100644 lib/assets/images/categories/stock-dark.svg create mode 100644 lib/assets/images/categories/stock-light.svg delete mode 100644 lib/assets/images/empty_states/studio-dark.svg delete mode 100644 lib/assets/images/empty_states/studio-light.svg delete mode 100644 lib/constants/app_strings.dart create mode 100644 lib/models/statistic_data/data.dart create mode 100644 lib/models/statistic_data/statistic_data.dart delete mode 100644 lib/models/view/radar_category.dart rename lib/views/home/comments/widgets/{comment_item.dart => comment.dart} (83%) delete mode 100644 lib/views/home/radar/widgets/categories_list.dart delete mode 100644 lib/views/home/settings/about_us/about_us.dart create mode 100644 lib/views/home/statistic/statistic.dart create mode 100644 lib/views/home/statistic/statistic_details/statistic_details.dart create mode 100644 lib/views/home/statistic/statistic_details/statistic_details_state.dart create mode 100644 lib/views/home/statistic/statistic_state.dart create mode 100644 lib/views/home/statistic/widgets/statistic_overview.dart delete mode 100644 lib/views/home/statistics/statistics.dart rename lib/views/home/{radar => }/widgets/categories_gird.dart (60%) create mode 100644 lib/views/home/widgets/categories_list.dart rename lib/views/home/{radar => }/widgets/category_item.dart (83%) diff --git a/.fandogh/fandogh.yaml b/.fandogh/fandogh.yaml index 45fc5be..f3f18c9 100644 --- a/.fandogh/fandogh.yaml +++ b/.fandogh/fandogh.yaml @@ -1,13 +1,13 @@ kind: ExternalService -name: app-test +name: app-dev spec: allow_http: false disable_default_domains: true - image: app:1.2.0 + image: app-dev:1.5.11 image_pull_policy: IfNotPresent path: / replicas: 1 resources: - memory: 150Mi + memory: 100Mi domains: - - name: web.didvan.app \ No newline at end of file + - name: dev.didvan.app \ No newline at end of file diff --git a/lib/assets/images/categories/glob-dark.svg b/lib/assets/images/categories/glob-dark.svg new file mode 100644 index 0000000..a70de82 --- /dev/null +++ b/lib/assets/images/categories/glob-dark.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/assets/images/categories/glob-light.svg b/lib/assets/images/categories/glob-light.svg new file mode 100644 index 0000000..3888540 --- /dev/null +++ b/lib/assets/images/categories/glob-light.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/assets/images/categories/steel-dark.svg b/lib/assets/images/categories/steel-dark.svg new file mode 100644 index 0000000..6b56bc0 --- /dev/null +++ b/lib/assets/images/categories/steel-dark.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lib/assets/images/categories/steel-light.svg b/lib/assets/images/categories/steel-light.svg new file mode 100644 index 0000000..1c1cc9b --- /dev/null +++ b/lib/assets/images/categories/steel-light.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lib/assets/images/categories/stock-dark.svg b/lib/assets/images/categories/stock-dark.svg new file mode 100644 index 0000000..818f6e5 --- /dev/null +++ b/lib/assets/images/categories/stock-dark.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/assets/images/categories/stock-light.svg b/lib/assets/images/categories/stock-light.svg new file mode 100644 index 0000000..22ccde7 --- /dev/null +++ b/lib/assets/images/categories/stock-light.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/assets/images/empty_states/studio-dark.svg b/lib/assets/images/empty_states/studio-dark.svg deleted file mode 100644 index 392039b..0000000 --- a/lib/assets/images/empty_states/studio-dark.svg +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/assets/images/empty_states/studio-light.svg b/lib/assets/images/empty_states/studio-light.svg deleted file mode 100644 index 5808670..0000000 --- a/lib/assets/images/empty_states/studio-light.svg +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/config/theme_data.dart b/lib/config/theme_data.dart index b1e6db7..5bbab96 100644 --- a/lib/config/theme_data.dart +++ b/lib/config/theme_data.dart @@ -192,6 +192,7 @@ extension DidvanColorScheme on ColorScheme { Color get overlay => brightness == Brightness.dark ? const Color(0xFF0F1011) : const Color(0xFF292929); + Color get yellow => const Color(0XFFEAA92A); // Error and success colors Color get errorBack => brightness == Brightness.dark diff --git a/lib/constants/app_strings.dart b/lib/constants/app_strings.dart deleted file mode 100644 index e8f4321..0000000 --- a/lib/constants/app_strings.dart +++ /dev/null @@ -1 +0,0 @@ -class AppStrings {} diff --git a/lib/constants/assets.dart b/lib/constants/assets.dart index e19e386..f582dff 100644 --- a/lib/constants/assets.dart +++ b/lib/constants/assets.dart @@ -4,11 +4,12 @@ import 'package:flutter/cupertino.dart'; class Assets { static const String _basePath = 'lib/assets'; static const String _baseImagesPath = _basePath + '/images'; - static const String _baseThemesPath = _basePath + '/images/themes'; - static const String _baseEmptyStatesPath = _basePath + '/images/empty_states'; + static const String _baseCategoriesPath = _baseImagesPath + '/categories'; + static const String _baseThemesPath = _baseImagesPath + '/themes'; + static const String _baseEmptyStatesPath = _baseImagesPath + '/empty_states'; static const String _baseAnimationsPath = _basePath + '/animations'; - static const String _baseRecordsPath = _basePath + '/images/records'; - static const String _baseLogosPath = _basePath + '/images/logos'; + static const String _baseRecordsPath = _baseImagesPath + '/records'; + static const String _baseLogosPath = _baseImagesPath + '/logos'; static String get verticalLogoWithText => _baseLogosPath + '/logo-vertical-$_themeSuffix.svg'; @@ -20,17 +21,23 @@ class Assets { _baseAnimationsPath + '/indicator-$_themeSuffix.riv'; static String get businessCategoryIcon => - _baseImagesPath + '/categories/business-$_themeSuffix.svg'; + _baseCategoriesPath + '/business-$_themeSuffix.svg'; static String get economicCategoryIcon => - _baseImagesPath + '/categories/economic-$_themeSuffix.svg'; + _baseCategoriesPath + '/economic-$_themeSuffix.svg'; static String get enviromentalCategoryIcon => - _baseImagesPath + '/categories/enviromental-$_themeSuffix.svg'; + _baseCategoriesPath + '/enviromental-$_themeSuffix.svg'; static String get politicalCategoryIcon => - _baseImagesPath + '/categories/political-$_themeSuffix.svg'; + _baseCategoriesPath + '/political-$_themeSuffix.svg'; static String get socialCategoryIcon => - _baseImagesPath + '/categories/social-$_themeSuffix.svg'; + _baseCategoriesPath + '/social-$_themeSuffix.svg'; static String get techCategoryIcon => - _baseImagesPath + '/categories/tech-$_themeSuffix.svg'; + _baseCategoriesPath + '/tech-$_themeSuffix.svg'; + static String get steelCategoryIcon => + _baseCategoriesPath + '/steel-$_themeSuffix.svg'; + static String get stockCategoryIcon => + _baseCategoriesPath + '/stock-$_themeSuffix.svg'; + static String get globCategoryIcon => + _baseCategoriesPath + '/glob-$_themeSuffix.svg'; static String get emptyBookmark => _baseEmptyStatesPath + '/bookmark-$_themeSuffix.svg'; @@ -42,8 +49,6 @@ class Assets { _baseEmptyStatesPath + '/connection-$_themeSuffix.svg'; static String get emptyResult => _baseEmptyStatesPath + '/result-$_themeSuffix.svg'; - static String get emptyStudio => - _baseEmptyStatesPath + '/studio-$_themeSuffix.svg'; static const String lightTheme = _baseThemesPath + '/theme-light.svg'; static const String darkTheme = _baseThemesPath + '/theme-dark.svg'; diff --git a/lib/models/category.dart b/lib/models/category.dart index 3faf75a..552af8b 100644 --- a/lib/models/category.dart +++ b/lib/models/category.dart @@ -1,8 +1,9 @@ class CategoryData { final int id; final String label; + String? asset; - const CategoryData({required this.id, required this.label}); + CategoryData({required this.id, required this.label, this.asset}); factory CategoryData.fromJson(Map json) => CategoryData( id: json['id'], diff --git a/lib/models/statistic_data/data.dart b/lib/models/statistic_data/data.dart new file mode 100644 index 0000000..0e46084 --- /dev/null +++ b/lib/models/statistic_data/data.dart @@ -0,0 +1,64 @@ +class Data { + final String p; + final String h; + final String l; + final String d; + final double dp; + final String dt; + final String t; + final String? tEn; + final String tG; + final String ts; + + Data({ + required this.p, + required this.h, + required this.l, + required this.d, + required this.dp, + required this.dt, + required this.t, + required this.tEn, + required this.tG, + required this.ts, + }); + + factory Data.fromJson(Map json) => Data( + p: json['p'], + h: json['h'], + l: json['l'], + d: json['d'], + dp: double.parse(json['dp'].toString()), + dt: json['dt'], + t: json['t'], + tEn: json['t_en'], + tG: json['t-g'], + ts: json['ts'], + ); + + factory Data.fromList(List list) => Data( + p: list[0], + h: list[1], + l: list[2], + d: list[3], + dp: double.parse(list[4].toString().replaceAll('-', '0')), + dt: list[5], + t: list[6], + tEn: list[7], + tG: '', + ts: '', + ); + + Map toJson() => { + 'p': p, + 'h': h, + 'l': l, + 'd': d, + 'dp': dp, + 'dt': dt, + 't': t, + 't_en': tEn, + 't-g': tG, + 'ts': ts, + }; +} diff --git a/lib/models/statistic_data/statistic_data.dart b/lib/models/statistic_data/statistic_data.dart new file mode 100644 index 0000000..5d720e7 --- /dev/null +++ b/lib/models/statistic_data/statistic_data.dart @@ -0,0 +1,29 @@ +import 'data.dart'; + +class StatisticData { + final int id; + final String label; + final String title; + final Data data; + + StatisticData({ + required this.id, + required this.label, + required this.title, + required this.data, + }); + + factory StatisticData.fromJson(Map json) => StatisticData( + id: json['id'], + label: json['label'], + title: json['title'], + data: Data.fromJson(json['data']), + ); + + Map toJson() => { + 'id': id, + 'label': label, + 'title': title, + 'data': data.toJson(), + }; +} diff --git a/lib/models/view/radar_category.dart b/lib/models/view/radar_category.dart deleted file mode 100644 index 4312d46..0000000 --- a/lib/models/view/radar_category.dart +++ /dev/null @@ -1,7 +0,0 @@ -class RadarCategory { - final int id; - final String title; - final String asset; - - RadarCategory({required this.id, required this.title, required this.asset}); -} diff --git a/lib/providers/server_data.dart b/lib/providers/server_data.dart index 4ef71bb..4aac55f 100644 --- a/lib/providers/server_data.dart +++ b/lib/providers/server_data.dart @@ -9,12 +9,18 @@ class ServerDataProvider { await _getDirectTypes(); } - static int labelToTypeId(String label) => label.contains('پشتیبانی') - ? 7 - : directTypes + static int labelToTypeId(String label) { + if (label.contains('پشتیبانی اپلیکیشن')) { + return 8; + } else if (label.contains('پشتیبانی محتوا')) { + return 7; + } else { + return directTypes .firstWhereOrNull((element) => element.value.contains(label)) ?.key ?? 7; + } + } static Future _getDirectTypes() async { final service = RequestService(RequestHelper.directTypes); diff --git a/lib/providers/user.dart b/lib/providers/user.dart index d87e0ce..f0341e8 100644 --- a/lib/providers/user.dart +++ b/lib/providers/user.dart @@ -16,6 +16,7 @@ class UserProvider extends CoreProvier { static final List _radarMarkQueue = []; static final List _newsMarkQueue = []; static final List _studioMarkQueue = []; + static final List _statisticMarkQueue = []; Future setAndGetToken({String? newToken}) async { if (newToken == null) { @@ -184,4 +185,20 @@ class UserProvider extends CoreProvier { _newsMarkQueue.removeWhere((element) => element.key == id); }); } + + static Future changeStatisticMark(int id, bool value) async { + _statisticMarkQueue.add(MapEntry(id, value)); + Future.delayed(const Duration(milliseconds: 500), () async { + final MapEntry? lastChange = + _statisticMarkQueue.lastWhereOrNull((item) => item.key == id); + if (lastChange == null) return; + final service = RequestService(RequestHelper.mark(id, 'statistic')); + if (lastChange.value) { + await service.post(); + } else { + await service.delete(); + } + _statisticMarkQueue.removeWhere((element) => element.key == id); + }); + } } diff --git a/lib/routes/route_generator.dart b/lib/routes/route_generator.dart index 308bc61..95aa9f4 100644 --- a/lib/routes/route_generator.dart +++ b/lib/routes/route_generator.dart @@ -14,7 +14,6 @@ import 'package:didvan/views/home/news/news_state.dart'; import 'package:didvan/views/home/radar/radar_details/radar_details.dart'; import 'package:didvan/views/home/radar/radar_details/radar_details_state.dart'; import 'package:didvan/views/home/radar/radar_state.dart'; -import 'package:didvan/views/home/settings/about_us/about_us.dart'; import 'package:didvan/views/home/settings/bookmarks/bookmarks.dart'; import 'package:didvan/views/home/settings/bookmarks/bookmark_state.dart'; import 'package:didvan/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart'; @@ -24,6 +23,9 @@ import 'package:didvan/views/home/settings/direct_list/direct_list_state.dart'; import 'package:didvan/views/home/settings/general_settings/settings.dart'; import 'package:didvan/views/home/settings/general_settings/settings_state.dart'; import 'package:didvan/views/home/settings/profile/profile.dart'; +import 'package:didvan/views/home/statistic/statistic_details/statistic_details.dart'; +import 'package:didvan/views/home/statistic/statistic_details/statistic_details_state.dart'; +import 'package:didvan/views/home/statistic/statistic_state.dart'; import 'package:didvan/views/home/studio/studio_details/studio_details.mobile.dart' if (dart.library.io) 'package:didvan/views/home/studio/studio_details/studio_details.mobile.dart' if (dart.library.html) 'package:didvan/views/home/studio/studio_details/studio_details.web.dart'; @@ -64,6 +66,9 @@ class RouteGenerator { ChangeNotifierProvider( create: (context) => StudioState(), ), + ChangeNotifierProvider( + create: (context) => StatisticState(), + ), ], child: const Home(), ), @@ -72,10 +77,6 @@ class RouteGenerator { return _createRoute( const Profile(), ); - case Routes.aboutUs: - return _createRoute( - const AboutUs(), - ); case Routes.generalSettings: return _createRoute( ChangeNotifierProvider( @@ -107,6 +108,15 @@ class RouteGenerator { pageData: settings.arguments as Map, ), ); + case Routes.statisticDetails: + return _createRoute( + ChangeNotifierProvider( + create: (context) => StatisticDetailsState(), + child: StatisticDetails( + pageData: settings.arguments as Map, + ), + ), + ); case Routes.directList: return _createRoute( ChangeNotifierProvider( diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart index fe94d78..23657ab 100644 --- a/lib/routes/routes.dart +++ b/lib/routes/routes.dart @@ -9,6 +9,7 @@ class Routes { static const String radarDetails = '/radar-details'; static const String newsDetails = '/news-details'; static const String studioDetails = '/studio-details'; + static const String statisticDetails = '/statistic-details'; static const String directList = '/direct-list'; static const String direct = '/direct'; static const String comments = '/comments'; diff --git a/lib/services/media/media.dart b/lib/services/media/media.dart index a783e23..2342acd 100644 --- a/lib/services/media/media.dart +++ b/lib/services/media/media.dart @@ -14,7 +14,8 @@ class MediaService { static StudioDetailsData? currentPodcast; static StudioRequestArgs? podcastPlaylistArgs; - static Duration? get duration => audioPlayer.current.value?.audio.duration; + static Duration? get duration => + audioPlayer.current.valueOrNull?.audio.duration; static Future handleAudioPlayback({ required dynamic audioSource, diff --git a/lib/services/network/request_helper.dart b/lib/services/network/request_helper.dart index 525622e..56ec7da 100644 --- a/lib/services/network/request_helper.dart +++ b/lib/services/network/request_helper.dart @@ -8,6 +8,7 @@ class RequestHelper { static const String _baseRadarUrl = baseUrl + '/radar'; static const String _baseNewsUrl = baseUrl + '/news'; static const String _baseStudioUrl = baseUrl + '/studio'; + static const String _baseStatisticUrl = baseUrl + '/statistic'; static const String _baseDirectUrl = _baseUserUrl + '/direct'; static const String confirmUsername = _baseUserUrl + '/confirmUsername'; @@ -39,6 +40,8 @@ class RequestHelper { static String direct(int id) => _baseDirectUrl + '/$id'; static String sendDirectMessage(int id) => _baseDirectUrl + '/$id/sendMessage'; + static String deleteDirect(int id, int messageId) => + _baseDirectUrl + '/$id/message/$messageId'; static String tag({ required List ids, String? type, @@ -118,6 +121,21 @@ class RequestHelper { MapEntry('asc', args.asc), ]); + static String statisticOverviews(int? category) => + _baseStatisticUrl + + _urlConcatGenerator( + [MapEntry('category', category)], + ); + static String statisticDetails( + String label, + String period, + ) => + _baseStatisticUrl + + '/$label' + + _urlConcatGenerator([ + MapEntry('period', period), + ]); + static String mark(int id, String type) => baseUrl + '/$type/$id/mark'; static String tracking(int id, String type) => baseUrl + '/$type/$id/tracking'; @@ -127,6 +145,8 @@ class RequestHelper { baseUrl + '/$type/$id/comments/$commentId/feedback'; static String addComment(int id, String type) => baseUrl + '/$type/$id/comments/add'; + static String deleteComment(int id) => baseUrl + '/comment/$id'; + static String reportComment(int id) => baseUrl + '/comment/$id/report'; static String _urlConcatGenerator(List> additions) { String result = ''; diff --git a/lib/utils/date_time.dart b/lib/utils/date_time.dart index 41be0c6..3e50c93 100644 --- a/lib/utils/date_time.dart +++ b/lib/utils/date_time.dart @@ -47,6 +47,13 @@ class DateTimeUtils { initialDate: initialJalali, firstDate: firstDate, lastDate: lastDate, + builder: (context, child) => Theme( + data: Theme.of(context).copyWith( + textTheme: Theme.of(context) + .textTheme + .copyWith(headline4: Theme.of(context).textTheme.headline3)), + child: child!, + ), ); return result?.toDateTime().toString(); diff --git a/lib/views/authentication/screens/username.dart b/lib/views/authentication/screens/username.dart index 6c5cf8c..cd28681 100644 --- a/lib/views/authentication/screens/username.dart +++ b/lib/views/authentication/screens/username.dart @@ -65,14 +65,16 @@ class _UsernameInputState extends State { text: TextSpan( style: Theme.of(context).textTheme.caption, children: [ - const TextSpan(text: 'با و ورود به دیدوان،'), + const TextSpan(text: 'با ورود به دیدوان،'), TextSpan( text: ' شرایط ', style: Theme.of(context) .textTheme .caption! .copyWith(color: Theme.of(context).colorScheme.primary), - recognizer: TapGestureRecognizer()..onTap = _openTermsOfUse, + recognizer: TapGestureRecognizer() + ..onTap = () => + launch('https://didvan.app/termsOfUse.html#conditions'), ), const TextSpan(text: 'و\n'), TextSpan( @@ -81,7 +83,9 @@ class _UsernameInputState extends State { .textTheme .caption! .copyWith(color: Theme.of(context).colorScheme.primary), - recognizer: TapGestureRecognizer()..onTap = _openTermsOfUse, + recognizer: TapGestureRecognizer() + ..onTap = () => + launch('https://didvan.app/termsOfUse.html#privacy'), ), const TextSpan(text: 'را می‌پذیرم'), ], @@ -94,8 +98,4 @@ class _UsernameInputState extends State { ], ); } - - void _openTermsOfUse() { - launch('https://didvan.app/termsOfUse.html'); - } } diff --git a/lib/views/home/comments/comments.dart b/lib/views/home/comments/comments.dart index 6e03d73..7803230 100644 --- a/lib/views/home/comments/comments.dart +++ b/lib/views/home/comments/comments.dart @@ -4,7 +4,7 @@ import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/view/app_bar_data.dart'; import 'package:didvan/views/home/comments/comments_state.dart'; -import 'package:didvan/views/home/comments/widgets/comment_item.dart'; +import 'package:didvan/views/home/comments/widgets/comment.dart'; import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; diff --git a/lib/views/home/comments/comments_state.dart b/lib/views/home/comments/comments_state.dart index 4b02d00..03e04bc 100644 --- a/lib/views/home/comments/comments_state.dart +++ b/lib/views/home/comments/comments_state.dart @@ -4,10 +4,12 @@ import 'package:didvan/models/comment/feedback.dart'; import 'package:didvan/models/comment/reply.dart'; import 'package:didvan/models/comment/user.dart'; import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/view/alert_data.dart'; import 'package:didvan/providers/core.dart'; import 'package:didvan/providers/user.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; +import 'package:didvan/utils/action_sheet.dart'; import 'package:provider/provider.dart'; class CommentsState extends CoreProvier { @@ -158,4 +160,47 @@ class CommentsState extends CoreProvier { replyingTo = null; } } + + void reportComment(int id) { + final service = RequestService(RequestHelper.reportComment(id)); + service.post(); + ActionSheetUtils.showAlert( + AlertData( + message: 'گزارش شما با موفقیت ثبت شد و به زودی بررسی میگردد.', + aLertType: ALertType.success, + ), + ); + } + + void deleteComment(int id, int? rootId) { + final service = RequestService(RequestHelper.deleteComment(id)); + service.delete(); + if (rootId == null) { + final comment = comments.firstWhere((element) => element.id == id); + if (comment.replies.isNotEmpty) { + comments.insertAll( + comments.indexOf(comment), + comment.replies.map( + (rep) => CommentData( + id: rep.id, + text: rep.text, + createdAt: rep.createdAt, + liked: rep.liked, + disliked: rep.disliked, + feedback: rep.feedback, + user: rep.user, + replies: [], + ), + ), + ); + } + comments.remove(comment); + } else { + comments + .firstWhere((element) => element.id == rootId) + .replies + .removeWhere((element) => element.id == id); + } + notifyListeners(); + } } diff --git a/lib/views/home/comments/widgets/comment_item.dart b/lib/views/home/comments/widgets/comment.dart similarity index 83% rename from lib/views/home/comments/widgets/comment_item.dart rename to lib/views/home/comments/widgets/comment.dart index 4748031..cc50380 100644 --- a/lib/views/home/comments/widgets/comment_item.dart +++ b/lib/views/home/comments/widgets/comment.dart @@ -2,8 +2,13 @@ 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/comment/comment.dart'; +import 'package:didvan/models/comment/reply.dart'; +import 'package:didvan/models/view/action_sheet_data.dart'; +import 'package:didvan/providers/user.dart'; +import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/date_time.dart'; import 'package:didvan/views/home/comments/comments_state.dart'; +import 'package:didvan/views/home/widgets/menu_item.dart'; import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; @@ -84,17 +89,24 @@ class CommentState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ DidvanText( comment.user.fullName, style: Theme.of(context).textTheme.bodyText1, ), + const Spacer(), DidvanText( DateTimeUtils.momentGenerator(comment.createdAt), style: Theme.of(context).textTheme.caption, color: Theme.of(context).colorScheme.caption, ), + const SizedBox(width: 4), + DidvanIconButton( + size: 18, + gestureSize: 24, + icon: DidvanIcons.menu_light, + onPressed: () => _showCommentActions(comment), + ), ], ), const SizedBox(height: 8), @@ -171,6 +183,42 @@ class CommentState extends State { ], ), ); + + Future _showCommentActions(comment) async { + ActionSheetUtils.showBottomSheet( + data: ActionSheetData( + title: 'گزینه‌های نظر', + content: Column( + children: [ + if (comment.user.id != context.read().user.id) + MenuItem( + title: 'گزارش محتوای نامناسب', + onTap: () { + state.reportComment(comment.id); + ActionSheetUtils.pop(); + }, + icon: DidvanIcons.alert_regular, + ), + if (comment.user.id == context.read().user.id) + MenuItem( + title: 'حذف نظر', + color: Theme.of(context).colorScheme.secondary, + onTap: () { + state.deleteComment( + comment.id, + comment.runtimeType == Reply ? _comment.id : null, + ); + ActionSheetUtils.pop(); + }, + icon: DidvanIcons.trash_solid, + ), + ], + ), + hasConfirmButton: false, + hasDismissButton: false, + ), + ); + } } class _FeedbackButtons extends StatefulWidget { diff --git a/lib/views/home/direct/direct.dart b/lib/views/home/direct/direct.dart index d47be3c..2c0ba84 100644 --- a/lib/views/home/direct/direct.dart +++ b/lib/views/home/direct/direct.dart @@ -1,3 +1,4 @@ +import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/view/app_bar_data.dart'; @@ -6,7 +7,9 @@ import 'package:didvan/services/media/media.dart'; import 'package:didvan/views/home/direct/direct_state.dart'; import 'package:didvan/views/home/direct/widgets/message.dart'; import 'package:didvan/views/home/direct/widgets/message_box.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:flutter/material.dart'; @@ -52,11 +55,14 @@ class _DirectState extends State { left: 0, right: 0, child: DidvanScaffold( + padding: EdgeInsets.zero, reverse: true, backgroundColor: Theme.of(context).colorScheme.surface, appBarData: AppBarData( hasBack: true, - subtitle: 'ارتباط با سردبیر', + subtitle: widget.pageData['type'].contains('پشتیبانی') + ? null + : 'ارتباط با سردبیر', title: widget.pageData['type'] ?? 'پشتیبانی اپلیکیشن', ), slivers: [ @@ -103,6 +109,43 @@ class _DirectState extends State { left: 0, child: const MessageBox(), ), + if (state.deletionQueue.isNotEmpty) + Positioned( + left: 0, + right: 0, + top: d.padding.top, + child: Container( + height: 72, + color: Theme.of(context).colorScheme.surface, + child: Row( + children: [ + DidvanIconButton( + icon: DidvanIcons.close_solid, + size: 32, + gestureSize: 48, + color: Theme.of(context).colorScheme.secondary, + onPressed: () { + state.deletionQueue.clear(); + state.update(); + }, + ), + DidvanText( + '${state.deletionQueue.length} مورد انتخاب شد', + style: Theme.of(context).textTheme.subtitle1, + color: Theme.of(context).colorScheme.secondary, + ), + const Spacer(), + DidvanIconButton( + icon: DidvanIcons.trash_solid, + size: 32, + gestureSize: 48, + color: Theme.of(context).colorScheme.primary, + onPressed: state.delete, + ), + ], + ), + ), + ), ], ), ), diff --git a/lib/views/home/direct/direct_state.dart b/lib/views/home/direct/direct_state.dart index 20964d5..ba1e98c 100644 --- a/lib/views/home/direct/direct_state.dart +++ b/lib/views/home/direct/direct_state.dart @@ -16,6 +16,7 @@ class DirectState extends CoreProvier { final List messages = []; late final int typeId; final Map> dailyMessages = {}; + final List deletionQueue = []; String? text; RadarAttachment? replyRadar; @@ -77,6 +78,18 @@ class DirectState extends CoreProvier { } } + void delete() { + for (var i = 0; i < deletionQueue.length; i++) { + final service = RequestService( + RequestHelper.deleteDirect(typeId, deletionQueue[i]), + ); + service.delete(); + messages.removeWhere((element) => element.id == deletionQueue[i]); + } + deletionQueue.clear(); + notifyListeners(); + } + void _addToDailyGrouped(MessageData message) { String createdAt = message.createdAt.replaceAll('T', ' ').split(' ').first; if (!dailyMessages.containsKey(createdAt)) { diff --git a/lib/views/home/direct/widgets/message.dart b/lib/views/home/direct/widgets/message.dart index ff90eb1..d1c5e6a 100644 --- a/lib/views/home/direct/widgets/message.dart +++ b/lib/views/home/direct/widgets/message.dart @@ -18,85 +18,109 @@ class Message extends StatelessWidget { @override Widget build(BuildContext context) { - final firstMessageOfGroupId = context - .read() + final state = context.read(); + final firstMessageOfGroupId = state .dailyMessages[message.createdAt.replaceAll('T', ' ').split(' ').first]! .last; - return Column( - crossAxisAlignment: message.writedByAdmin - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - children: [ - if (message.id == firstMessageOfGroupId) - Center( - child: Container( - margin: const EdgeInsets.only(bottom: 12), - padding: const EdgeInsets.all(4), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.splash, - borderRadius: DesignConfig.lowBorderRadius, - ), - child: DidvanText( - DateTime.parse(message.createdAt).toPersianDateStr(), - style: Theme.of(context).textTheme.overline, - color: DesignConfig.isDark - ? Theme.of(context).colorScheme.white - : Theme.of(context).colorScheme.black, - ), - ), - ), - Padding( - padding: EdgeInsets.only( - right: message.writedByAdmin ? 20 : 0, - left: !message.writedByAdmin ? 20 : 0, - ), - child: Column( - crossAxisAlignment: message.writedByAdmin - ? CrossAxisAlignment.start - : CrossAxisAlignment.end, - children: [ - _MessageContainer( - writedByAdmin: message.writedByAdmin, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (message.text != null) DidvanText(message.text!), - if (message.audio != null || message.audioFile != null) - AudioWidget( - audioFile: message.audioFile, - audioUrl: message.audio, - id: message.id, - ), - if (message.radar != null) const DidvanDivider(), - if (message.radar != null) const SizedBox(height: 4), - if (message.radar != null) - _ReplyRadarOverview(message: message), - if (message.radar != null) const SizedBox(height: 4), - ], + return GestureDetector( + onLongPress: () { + if (state.deletionQueue.contains(message.id) || message.writedByAdmin) { + return; + } + state.deletionQueue.add(message.id); + state.update(); + }, + onTap: () { + if (state.deletionQueue.isEmpty || message.writedByAdmin) return; + if (!state.deletionQueue.contains(message.id)) { + state.deletionQueue.add(message.id); + } else { + state.deletionQueue.remove(message.id); + } + state.update(); + }, + child: Container( + color: state.deletionQueue.contains(message.id) + ? Theme.of(context).colorScheme.secondaryDisabled.withOpacity(0.5) + : null, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Column( + crossAxisAlignment: message.writedByAdmin + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + children: [ + if (message.id == firstMessageOfGroupId) + Center( + child: Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.splash, + borderRadius: DesignConfig.lowBorderRadius, + ), + child: DidvanText( + DateTime.parse(message.createdAt).toPersianDateStr(), + style: Theme.of(context).textTheme.overline, + color: DesignConfig.isDark + ? Theme.of(context).colorScheme.white + : Theme.of(context).colorScheme.black, + ), ), ), - const SizedBox(height: 4), - Row( - mainAxisSize: MainAxisSize.min, + Padding( + padding: EdgeInsets.only( + right: message.writedByAdmin ? 20 : 0, + left: !message.writedByAdmin ? 20 : 0, + ), + child: Column( + crossAxisAlignment: message.writedByAdmin + ? CrossAxisAlignment.start + : CrossAxisAlignment.end, children: [ - DidvanText( - DateTimeUtils.timeWithAmPm(message.createdAt), - style: Theme.of(context).textTheme.overline, - color: Theme.of(context).colorScheme.caption, + _MessageContainer( + writedByAdmin: message.writedByAdmin, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (message.text != null) DidvanText(message.text!), + if (message.audio != null || message.audioFile != null) + AudioWidget( + audioFile: message.audioFile, + audioUrl: message.audio, + id: message.id, + ), + if (message.radar != null) const DidvanDivider(), + if (message.radar != null) const SizedBox(height: 4), + if (message.radar != null) + _ReplyRadarOverview(message: message), + if (message.radar != null) const SizedBox(height: 4), + ], + ), + ), + const SizedBox(height: 4), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + DidvanText( + DateTimeUtils.timeWithAmPm(message.createdAt), + style: Theme.of(context).textTheme.overline, + color: Theme.of(context).colorScheme.caption, + ), + if (!message.writedByAdmin) + Icon( + message.readed + ? DidvanIcons.check_double_light + : DidvanIcons.check_light, + size: 16, + ) + ], ), - if (!message.writedByAdmin) - Icon( - message.readed - ? DidvanIcons.check_double_light - : DidvanIcons.check_light, - size: 16, - ) ], ), - ], - ), + ), + ], ), - ], + ), ); } } @@ -177,7 +201,10 @@ class _MessageContainer extends StatelessWidget { bottomLeft: writedByAdmin ? Radius.zero : null, bottomRight: !writedByAdmin ? Radius.zero : null, ), - color: writedByAdmin ? null : Theme.of(context).colorScheme.focused, + color: (writedByAdmin + ? Theme.of(context).colorScheme.surface + : Theme.of(context).colorScheme.focused) + .withOpacity(0.9), border: Border.all( color: Theme.of(context).colorScheme.border, width: 0.5, diff --git a/lib/views/home/home.dart b/lib/views/home/home.dart index b9c203e..aec1a56 100644 --- a/lib/views/home/home.dart +++ b/lib/views/home/home.dart @@ -3,7 +3,7 @@ import 'package:didvan/views/home/home_state.dart'; import 'package:didvan/views/home/news/news.dart'; import 'package:didvan/views/home/radar/radar.dart'; import 'package:didvan/views/home/settings/settings.dart'; -import 'package:didvan/views/home/statistics/statistics.dart'; +import 'package:didvan/views/home/statistic/statistic.dart'; import 'package:didvan/views/home/studio/studio.dart'; import 'package:didvan/views/widgets/didvan/bnb.dart'; import 'package:flutter/material.dart'; @@ -37,7 +37,7 @@ class _HomeState extends State with SingleTickerProviderStateMixin { controller: _tabController, children: const [ News(), - Statictics(), + Statistic(), Radar(), Studio(), Settings(), diff --git a/lib/views/home/news/news.dart b/lib/views/home/news/news.dart index 9387dae..afa9c9e 100644 --- a/lib/views/home/news/news.dart +++ b/lib/views/home/news/news.dart @@ -52,6 +52,7 @@ class _NewsState extends State { ), ), SliverStateHandler( + centerEmptyState: false, onRetry: () => state.getNews(page: state.page), state: state, builder: (context, state, index) { @@ -90,7 +91,7 @@ class _NewsState extends State { void _onChanged(String value) { final state = context.read(); - if (value.length < 4 && value.isNotEmpty || state.lastSearch == value) { + if (value.length < 3 && value.isNotEmpty || state.lastSearch == value) { return; } _timer?.cancel(); diff --git a/lib/views/home/radar/radar.dart b/lib/views/home/radar/radar.dart index 6384c96..42f3b08 100644 --- a/lib/views/home/radar/radar.dart +++ b/lib/views/home/radar/radar.dart @@ -1,17 +1,16 @@ -// ignore_for_file: prefer_const_constructors - import 'dart:async'; import 'dart:math'; 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/category.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/views/home/radar/radar_state.dart'; -import 'package:didvan/views/home/radar/widgets/categories_gird.dart'; -import 'package:didvan/views/home/radar/widgets/categories_list.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/date_picker_button.dart'; import 'package:didvan/views/home/widgets/logo_app_bar.dart'; import 'package:didvan/utils/action_sheet.dart'; @@ -162,20 +161,49 @@ class _RadarState extends State { ), ], ), - if (state.appState != AppState.failed) CategoriesRow1(), - if (state.appState != AppState.failed) CategoriesRow2(), + if (state.appState != AppState.failed) + CategoriesRow1( + topPadding: 300, + rightPadding: 124, + onSelected: _onCategorySelected, + categories: state.categories, + isColapsed: + state.isColapsed || state.searching || state.filtering, + ), + if (state.appState != AppState.failed) + CategoriesRow2( + categories: state.categories, + isColapsed: + state.isColapsed || state.searching || state.filtering, + onSelected: _onCategorySelected, + ), if (state.appState != AppState.failed && !state.searching && !state.filtering) - CategoriesList(), + CategoriesList( + categories: state.categories, + isColapsed: + state.isColapsed || state.searching || state.filtering, + onSelected: (_) => state.getRadars(page: 1), + selectedCats: state.selectedCats, + ), ], ), ); } + void _onCategorySelected(CategoryData category) { + final state = context.read(); + state.selectedCats.clear(); + if (category.id != 0) { + state.selectedCats.add(category); + } + state.getRadars(page: 1); + } + void _onChanged(String value) { final state = context.read(); - if (value.length < 4 && value.isNotEmpty || state.lastSearch == value) { + if (value.length < 3 && value.isNotEmpty || state.lastSearch == value) { return; } _timer?.cancel(); @@ -268,7 +296,7 @@ class _RadarState extends State { SizedBox( width: (MediaQuery.of(context).size.width - 40) / 2, child: DidvanCheckbox( - title: state.categories[i].title, + title: state.categories[i].label, value: state.selectedCats.contains(state.categories[i]), onChanged: (value) { if (value) { diff --git a/lib/views/home/radar/radar_state.dart b/lib/views/home/radar/radar_state.dart index fb293a2..2b57c88 100644 --- a/lib/views/home/radar/radar_state.dart +++ b/lib/views/home/radar/radar_state.dart @@ -1,8 +1,8 @@ import 'package:didvan/constants/assets.dart'; +import 'package:didvan/models/category.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/radar.dart'; -import 'package:didvan/models/view/radar_category.dart'; import 'package:didvan/providers/core.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; @@ -16,8 +16,8 @@ class RadarState extends CoreProvier { int lastPage = 1; bool isScrolled = false; bool shouldColapse = false; - final List selectedCats = []; - List categories = []; + final List selectedCats = []; + List categories = []; final List radars = []; bool get filtering => @@ -46,11 +46,9 @@ class RadarState extends CoreProvier { required int page, }) async { this.page = page; - if (this.page == page) { - radars.clear(); - } lastSearch = search; if (page == 1) { + radars.clear(); appState = AppState.busy; } final RequestService service = RequestService( @@ -99,34 +97,34 @@ class RadarState extends CoreProvier { getRadars(page: 1); }); categories = [ - RadarCategory( + CategoryData( id: 1, - title: 'اقتصادی', + label: 'اقتصادی', asset: Assets.economicCategoryIcon, ), - RadarCategory( + CategoryData( id: 2, - title: 'سیاسی', + label: 'سیاسی', asset: Assets.politicalCategoryIcon, ), - RadarCategory( + CategoryData( id: 3, - title: 'فناوری', + label: 'فناوری', asset: Assets.techCategoryIcon, ), - RadarCategory( + CategoryData( id: 4, - title: 'کسب و کار', + label: 'کسب و کار', asset: Assets.businessCategoryIcon, ), - RadarCategory( + CategoryData( id: 5, - title: 'زیست محیطی', + label: 'زیست محیطی', asset: Assets.enviromentalCategoryIcon, ), - RadarCategory( + CategoryData( id: 6, - title: 'اجتماعی', + label: 'اجتماعی', asset: Assets.socialCategoryIcon, ), ]; diff --git a/lib/views/home/radar/widgets/categories_list.dart b/lib/views/home/radar/widgets/categories_list.dart deleted file mode 100644 index bca0187..0000000 --- a/lib/views/home/radar/widgets/categories_list.dart +++ /dev/null @@ -1,130 +0,0 @@ -import 'package:didvan/config/design_config.dart'; -import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/models/view/radar_category.dart'; -import 'package:didvan/views/home/radar/radar_state.dart'; -import 'package:didvan/views/widgets/animated_visibility.dart'; -import 'package:didvan/views/widgets/didvan/text.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -class CategoriesList extends StatefulWidget { - const CategoriesList({Key? key}) : super(key: key); - - @override - State createState() => _CategoriesListState(); -} - -class _CategoriesListState extends State { - final _scrollController = ScrollController(); - - int _lastSelectedCategoryId = 0; - - @override - void didUpdateWidget(covariant CategoriesList oldWidget) { - final RadarState state = context.read(); - if (state.selectedCats.isNotEmpty && - _lastSelectedCategoryId != state.selectedCats.first.id) { - _lastSelectedCategoryId = state.selectedCats.first.id; - _scrollController.animateTo( - state.selectedCats.first.id * 100, - duration: DesignConfig.lowAnimationDuration, - curve: Curves.easeIn, - ); - } - super.didUpdateWidget(oldWidget); - } - - @override - Widget build(BuildContext context) { - final MediaQueryData d = MediaQuery.of(context); - final state = context.read(); - final isColapsed = state.isColapsed || state.searching || state.filtering; - return Positioned( - top: 0, - left: 0, - right: 0, - child: AnimatedCrossFade( - crossFadeState: - isColapsed ? CrossFadeState.showSecond : CrossFadeState.showFirst, - duration: DesignConfig.mediumAnimationDuration, - reverseDuration: DesignConfig.lowAnimationDuration, - sizeCurve: Curves.easeIn, - firstChild: const SizedBox(), - secondChild: Container( - height: 60 + d.padding.top, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - boxShadow: DesignConfig.defaultShadow, - ), - child: AnimatedVisibility( - isVisible: isColapsed, - duration: DesignConfig.mediumAnimationDuration, - child: SingleChildScrollView( - controller: _scrollController, - // physics: const BouncingScrollPhysics(), - scrollDirection: Axis.horizontal, - padding: EdgeInsets.only( - top: d.padding.top + 12, - bottom: 12, - right: 12, - ), - child: Row( - children: [ - _itemBuilder( - RadarCategory(title: 'همه', asset: '', id: 0), - context, - ), - for (var i = 0; i < state.categories.length; i++) - _itemBuilder(state.categories[i], context), - ], - ), - ), - ), - ), - ), - ); - } - - Widget _itemBuilder(RadarCategory category, BuildContext context) { - final state = context.read(); - return GestureDetector( - onTap: () async { - if (state.selectedCats.isNotEmpty && - state.selectedCats.first.id == category.id) return; - state.selectedCats.clear(); - if (category.id != 0) state.selectedCats.add(category); - await _scrollController.animateTo( - category.id * 100, - duration: DesignConfig.lowAnimationDuration, - curve: Curves.easeIn, - ); - state.getRadars(page: 1); - }, - child: Container( - margin: const EdgeInsets.only(left: 12), - width: 100, - padding: const EdgeInsets.all(4), - alignment: Alignment.center, - child: FittedBox( - fit: BoxFit.scaleDown, - child: DidvanText( - category.title, - fontWeight: FontWeight.w600, - color: Theme.of(context).colorScheme.focusedBorder, - ), - ), - decoration: BoxDecoration( - color: state.selectedCats.length == 1 && - state.selectedCats.contains(category) || - category.id == 0 && state.selectedCats.isEmpty - ? Theme.of(context).colorScheme.focused - : null, - border: Border.all( - color: Theme.of(context).colorScheme.focusedBorder, - ), - borderRadius: DesignConfig.lowBorderRadius, - ), - ), - ); - } -} diff --git a/lib/views/home/settings/about_us/about_us.dart b/lib/views/home/settings/about_us/about_us.dart deleted file mode 100644 index 6cc17f9..0000000 --- a/lib/views/home/settings/about_us/about_us.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:didvan/models/view/app_bar_data.dart'; -import 'package:didvan/views/widgets/didvan/scaffold.dart'; -import 'package:didvan/views/widgets/didvan/text.dart'; -import 'package:flutter/material.dart'; - -class AboutUs extends StatelessWidget { - const AboutUs({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return DidvanScaffold( - appBarData: AppBarData( - hasBack: true, - title: 'معرفی دیدوان', - ), - children: const [ - DidvanText( - '''برای سفر مطمئن در جغرافیای مکانی به ابزارهایی مانند نقشه، قطب نما، سیستم ناوبری، علائم کنار جاده و یا حتی علائم طبیعی نیاز داریم. اما برای سفر در جغرافیای زمانی چه؟ -همه ما آدمیان و همه‌ی سازمان‌ها و شرکت‌هایمان، لاجرم مسافران جغرافیای زمانی هستند. ما همواره در مسیری رو به مقصدی در آینده در حرکت هستیم. اما فراموش نکنیم که سفر زمانی یک تفاوت بنیادین با طی مسیر در جغرافیای مکانی دارد. جغرافیای زمانی هیچ‌گاه نمی‌ایستد و سفر به آینده یک سفر شگفت‌انگیز به جایی است که قسمت‌هایی از آن را قبلاً در گذشته دیده‌ایم، قسمت‌هایی از آن را طی مسیر به تدریج می‌بینیم و قسمت‌هایی از آن کاملاً بدیع هستند. -برای سفر مطمئن در جغرافیای زمانی ما به سیستم‌های پیش‌نگر نیاز داریم و یکی از ارکان این سیستم‌ها، سامانه‌های رصد راهبردی هستند. دیدوان با شعار "چشم همیشه باز مدیران" سامانه رصد راهبردی شرکت فولاد مبارکه است تا سفری مطمئن به آینده را برای این شرکت رقم بزند.\nدر پارسی کهن دیدوان به این شکل معنی شده است: شخصی را گویند که بر جای بلند مانند سر کوه و بالای کشتی نشیند و هرچه از دور بیند خبر دهد. دیدوان چشمی است که آینده را می‌بیند. ما در دیدوان تلاش داریم که با رویکردی آینده‌پژوهانه مسیر پیش‌روی صنایع را تحلیل کنیم و با تحویل آن به مدیران صنایع، راهنمایی باشیم برای اخذ تصمیمات درست.''', - ), - ], - ); - } -} diff --git a/lib/views/home/settings/bookmarks/bookmarks.dart b/lib/views/home/settings/bookmarks/bookmarks.dart index 81ddeab..01ae4fe 100644 --- a/lib/views/home/settings/bookmarks/bookmarks.dart +++ b/lib/views/home/settings/bookmarks/bookmarks.dart @@ -144,7 +144,7 @@ class _BookmarksState extends State { void _onChanged(String value) { final state = context.read(); - if (value.length < 4 && value.isNotEmpty || state.lastSearch == value) { + if (value.length < 3 && value.isNotEmpty || state.lastSearch == value) { return; } _timer?.cancel(); diff --git a/lib/views/home/settings/settings.dart b/lib/views/home/settings/settings.dart index e923da5..b205e5a 100644 --- a/lib/views/home/settings/settings.dart +++ b/lib/views/home/settings/settings.dart @@ -87,28 +87,32 @@ class Settings extends StatelessWidget { MenuItem( icon: DidvanIcons.didvan_solid, title: 'معرفی دیدوان', - onTap: () => launch('https://didvan.app/'), + onTap: () => launch('https://didvan.app/#info'), ), const DidvanDivider(), MenuItem( icon: DidvanIcons.support_regular, title: 'پیام به پشتیبانی', onTap: () { - launch('mailto:info@didvan.app'); + Navigator.of(context).pushNamed( + Routes.direct, + arguments: {'type': 'پشتیبانی اپلیکیشن'}, + ); }, ), const DidvanDivider(), MenuItem( icon: DidvanIcons.alert_regular, title: 'حریم خصوصی', - onTap: () => launch('https://didvan.app/'), + onTap: () => + launch('https://didvan.app/termsOfUse.html#privacy'), ), ], ), ), const SizedBox(height: 16), DidvanText( - 'نسخه نرم‌افزار: 1.5.0', + 'نسخه نرم‌افزار: 2.0.0', style: Theme.of(context).textTheme.caption, ), ], diff --git a/lib/views/home/statistic/statistic.dart b/lib/views/home/statistic/statistic.dart new file mode 100644 index 0000000..6496e79 --- /dev/null +++ b/lib/views/home/statistic/statistic.dart @@ -0,0 +1,205 @@ +import 'dart:math'; + +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/models/category.dart'; +import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/statistic_data/statistic_data.dart'; +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'; +import 'package:didvan/views/widgets/state_handlers/empty_list.dart'; +import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class Statistic extends StatefulWidget { + const Statistic({Key? key}) : super(key: key); + + @override + State createState() => _StatisticState(); +} + +class _StatisticState extends State { + final ScrollController _scrollController = ScrollController(); + + bool _isAnimating = false; + + @override + void initState() { + _scrollController.addListener(() { + _handleAnimations(); + }); + final state = context.read(); + state.addListener(() { + if (state.shouldColapse && mounted) { + _handleAnimations(true); + state.shouldColapse = false; + } + }); + state.init(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, state, child) => Stack( + children: [ + CustomScrollView( + physics: _isAnimating + ? const NeverScrollableScrollPhysics() + : const ClampingScrollPhysics(), + controller: _scrollController, + slivers: [ + const SliverToBoxAdapter(child: LogoAppBar()), + if (state.appState != AppState.failed) + const SliverToBoxAdapter( + child: SizedBox(height: 180), + ), + if (state.appState != AppState.failed && + state.markedStatistics.isNotEmpty) + SliverPadding( + padding: const EdgeInsets.only(right: 16, bottom: 20), + sliver: SliverToBoxAdapter( + child: Align( + alignment: Alignment.centerRight, + child: AnimatedVisibility( + isVisible: !state.isColapsed, + duration: DesignConfig.lowAnimationDuration, + child: DidvanText( + 'شاخص‌های منتخب', + style: Theme.of(context).textTheme.subtitle1, + color: Theme.of(context).colorScheme.title, + ), + ), + ), + ), + ), + SliverStateHandler( + onRetry: state.getStatistic, + state: state, + itemPadding: const EdgeInsets.only( + bottom: 20, + left: 16, + right: 16, + ), + emptyState: const EmptyList(), + enableEmptyState: _itemCount(state) == 0, + placeholder: StatisticOverview.placeHolder, + builder: (context, state, index) { + if (index == state.markedStatistics.length) { + return const DidvanDivider(verticalPadding: 8); + } + bool isMarked = false; + StatisticData statistic; + if (index < state.markedStatistics.length) { + isMarked = true; + statistic = state.markedStatistics[index]; + } else { + index--; + statistic = + state.statistics[index - state.markedStatistics.length]; + } + return StatisticOverview( + statistic: statistic, + isMarked: isMarked, + onMarkChanged: state.changeMark, + ); + }, + childCount: _itemCount(state) + 1, + ), + SliverToBoxAdapter( + child: SizedBox( + height: state.appState == AppState.busy + ? 300 + : _itemCount(state) == 0 + ? 150 + : max( + MediaQuery.of(context).size.height - + _itemCount(state) * 120, + 0), + ), + ), + ], + ), + if (state.appState != AppState.failed) + CategoriesRow1( + onSelected: _onCategorySelected, + categories: List.from(state.categories)..removeAt(0), + isColapsed: state.isColapsed, + topPadding: 144, + rightPadding: 300, + ), + if (state.appState != AppState.failed) + CategoriesList( + categories: state.categories, + isColapsed: state.isColapsed, + onSelected: (id) { + state.selectedCategoryId = id; + state.getStatistic(); + }, + selectedCats: state.selectedCategory == null + ? [] + : [state.selectedCategory!], + ), + ], + ), + ); + } + + int _itemCount(state) => + state.markedStatistics.length + + (state.selectedCategoryId == 1 ? 0 : state.statistics.length); + + void _onCategorySelected(CategoryData category) { + final state = context.read(); + state.selectedCategoryId = 0; + if (category.id != 0) { + state.selectedCategoryId = category.id; + } + state.getStatistic(); + } + + void _handleAnimations([bool forceAnimate = false]) async { + final state = context.read(); + if (_isAnimating) return; + final double position = _scrollController.offset; + if (position > 5 && !state.isColapsed || forceAnimate) { + state.isScrolled = true; + _isAnimating = true; + setState(() {}); + await _scrollController.animateTo( + 228, + duration: DesignConfig.mediumAnimationDuration, + curve: Curves.easeIn, + ); + _isAnimating = false; + setState(() {}); + } else if (position < + min(_scrollController.position.maxScrollExtent, 228) && + state.isColapsed) { + state.isScrolled = false; + _isAnimating = true; + setState(() {}); + await _scrollController.animateTo( + 0, + duration: DesignConfig.mediumAnimationDuration, + curve: Curves.easeIn, + ); + _isAnimating = false; + setState(() {}); + } + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } +} diff --git a/lib/views/home/statistic/statistic_details/statistic_details.dart b/lib/views/home/statistic/statistic_details/statistic_details.dart new file mode 100644 index 0000000..ae78195 --- /dev/null +++ b/lib/views/home/statistic/statistic_details/statistic_details.dart @@ -0,0 +1,298 @@ +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/view/app_bar_data.dart'; +import 'package:didvan/views/home/statistic/statistic_details/statistic_details_state.dart'; +import 'package:didvan/views/home/widgets/categories_list.dart'; +import 'package:didvan/views/home/widgets/overview/multitype.dart'; +import 'package:didvan/views/home/widgets/tag_item.dart'; +import 'package:didvan/views/widgets/didvan/card.dart'; +import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; +import 'package:didvan/views/widgets/didvan/scaffold.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:provider/provider.dart'; +import 'package:intl/intl.dart' as intl; + +class StatisticDetails extends StatefulWidget { + final Map pageData; + const StatisticDetails({Key? key, required this.pageData}) : super(key: key); + + @override + State createState() => _StatisticDetailsState(); +} + +class _StatisticDetailsState extends State { + @override + void initState() { + final state = context.read(); + state.label = widget.pageData['label']; + state.marked = widget.pageData['marked']; + state.currentDateRangeId = 0; + Future.delayed(Duration.zero, state.getStatisticDetails); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, state, child) => DidvanScaffold( + padding: EdgeInsets.zero, + appBarData: AppBarData( + title: widget.pageData['title'], + hasBack: true, + subtitle: 'رادار قیمت‌ها', + trailing: DidvanIconButton( + icon: state.marked ? Icons.star : Icons.star_border, + color: state.marked + ? Theme.of(context).colorScheme.yellow + : Theme.of(context).colorScheme.focusedBorder, + size: 32, + onPressed: () { + state.marked = !state.marked; + state.update(); + widget.pageData['onMarkChanged'](state.marked); + }, + ), + ), + children: [ + StateHandler( + topPadding: MediaQuery.of(context).size.height / 3, + builder: (context, state) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: DidvanText('نمودار تغییرات'), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: SizedBox( + width: double.infinity, + height: 120, + child: state.chartState == AppState.busy + ? SpinKitThreeBounce( + color: Theme.of(context).colorScheme.primary, + size: 24, + ) + : LineChart( + LineChartData( + lineTouchData: LineTouchData( + touchTooltipData: LineTouchTooltipData( + tooltipBgColor: + Theme.of(context).colorScheme.navigation, + getTooltipItems: (data) => [ + LineTooltipItem( + state.datas[data.first.spotIndex].tEn! + + '\n' + + intl.NumberFormat("###,000", "en_US") + .format( + data.first.bar + .spots[data.first.spotIndex].y, + ), + Theme.of(context) + .textTheme + .caption! + .copyWith( + color: Colors.white, + ), + ), + ], + ), + ), + minX: 0, + maxX: state.datas.length.toDouble() - 1, + maxY: state.maxValue * 1.02, + minY: state.minValue! * 0.98, + gridData: FlGridData(show: false), + borderData: FlBorderData(show: false), + titlesData: FlTitlesData(show: false), + lineBarsData: [ + LineChartBarData( + spots: [ + for (var i = 0; i < state.datas.length; i++) + FlSpot( + i.toDouble(), + _stringToDouble(state.datas[i].p), + ) + ], + barWidth: 2, + dotData: FlDotData( + getDotPainter: (p0, p1, p2, p3) => + FlDotCirclePainter( + color: Colors.transparent, + strokeWidth: 1, + strokeColor: + Theme.of(context).colorScheme.success, + ), + ), + color: Theme.of(context).colorScheme.success, + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + tileMode: TileMode.decal, + colors: [ + Theme.of(context) + .colorScheme + .background, + const Color(0XFFF5B763) + .withOpacity(0.2), + ], + ), + ), + ), + ], + ), + ), + ), + ), + const SizedBox(height: 20), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: DidvanText('بازه نمایش:'), + ), + const SizedBox(height: 20), + CategoriesList( + isColapsed: false, + isAppBar: false, + selectedCats: [state.currentDateRange], + categories: state.dateRanges, + onSelected: (id) { + state.currentDateRangeId = id; + state.getStatisticDetails(); + }, + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: DidvanCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildDataItem('قیمت لحظه‌ای', state.data!.p), + const DidvanDivider(verticalPadding: 8), + _buildDataItem('بالاترین قیمت روز', state.data!.h), + const SizedBox(height: 8), + _buildDataItem('پایین‌ترین قیمت روز', state.data!.l), + const SizedBox(height: 8), + _buildDataItem( + 'درصد تغییر نسبت به دیروز', + '${state.data!.dp}%', + icon: _diffIcon(state), + color: _diffColor(state), + ), + const SizedBox(height: 8), + _buildDataItem( + 'میزان تغییر نسبت به دیروز', + state.data!.d, + icon: _diffIcon(state), + color: _diffColor(state), + ), + const SizedBox(height: 16), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + for (var i = 0; i < state.tags.length; i++) + TagItem( + tag: state.tags[i], + onMarkChanged: (_, __) {}, + type: 'statistic', + ), + ], + ), + ], + ), + ), + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: DidvanCard( + child: Column( + children: [ + if (state.relatedContents.isEmpty) + for (var i = 0; i < 3; i++) ...[ + MultitypeOverview.placeholder, + if (i != 2) const SizedBox(height: 16) + ], + for (var i = 0; + i < state.relatedContents.length; + i++) ...[ + MultitypeOverview( + item: state.relatedContents[i], + onMarkChanged: (id, value) {}, + ), + if (i != state.relatedContents.length - 1) + const SizedBox(height: 16) + ] + ], + ), + ), + ), + const SizedBox(height: 16), + ], + ), + onRetry: state.getStatisticDetails, + state: state, + ) + ], + ), + ); + } + + double _stringToDouble(String value) => + double.parse(value.replaceAll(',', '')); + + Color? _diffColor(StatisticDetailsState state) { + if (state.data!.dp == 0) { + return null; + } + if (state.data!.dt == 'high') { + return Theme.of(context).colorScheme.success; + } else { + return Theme.of(context).colorScheme.error; + } + } + + IconData? _diffIcon(StatisticDetailsState state) { + if (state.data!.dp == 0) { + return null; + } + if (state.data!.dt == 'high') { + return DidvanIcons.angle_up_regular; + } else { + return DidvanIcons.angle_down_regular; + } + } + + Widget _buildDataItem( + String title, + String value, { + IconData? icon, + bool isBold = false, + Color? color, + }) { + return Row( + children: [ + DidvanText( + title, + style: isBold + ? Theme.of(context).textTheme.bodyText1 + : Theme.of(context).textTheme.bodyText2, + ), + const Spacer(), + if (icon != null) Icon(icon, color: color), + DidvanText(value, color: color), + ], + ); + } +} diff --git a/lib/views/home/statistic/statistic_details/statistic_details_state.dart b/lib/views/home/statistic/statistic_details/statistic_details_state.dart new file mode 100644 index 0000000..a87ef8c --- /dev/null +++ b/lib/views/home/statistic/statistic_details/statistic_details_state.dart @@ -0,0 +1,169 @@ +import 'package:didvan/models/category.dart'; +import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/overview_data.dart'; +import 'package:didvan/models/statistic_data/data.dart'; +import 'package:didvan/models/tag.dart'; +import 'package:didvan/providers/core.dart'; +import 'package:didvan/services/network/request.dart'; +import 'package:didvan/services/network/request_helper.dart'; +import 'package:collection/collection.dart'; + +class StatisticDetailsState extends CoreProvier { + late bool marked; + late String label; + String? startDate; + String? endDate; + int currentDateRangeId = 0; + final List datas = []; + final List relatedContents = []; + final List tags = []; + Data? data; + double maxValue = 0; + double? minValue; + + AppState chartState = AppState.idle; + + final dateRanges = [ + CategoryData( + id: 0, + label: 'هفتگی', + asset: 'weekly', + ), + CategoryData( + id: 1, + label: 'ماهانه', + asset: 'monthly', + ), + CategoryData( + id: 2, + label: 'شش ماهه', + asset: 'semiyearly', + ), + CategoryData( + id: 3, + label: 'سالانه', + asset: 'yearly', + ), + ]; + + CategoryData get currentDateRange => dateRanges.firstWhere( + (element) => element.id == currentDateRangeId, + ); + + Future getStatisticDetails() async { + if (data == null) { + final result = await getStatisticCurrentDetails(); + if (!result) { + appState = AppState.failed; + return; + } + } + minValue = null; + maxValue = 0; + if (datas.isEmpty) { + appState = AppState.busy; + } else { + chartState = AppState.busy; + notifyListeners(); + } + datas.clear(); + final service = RequestService( + RequestHelper.statisticDetails( + label, + dateRanges[currentDateRangeId].asset!, + ), + ); + await service.httpGet(); + if (service.isSuccess) { + final result = service.result['data']; + tags.clear(); + for (var i = 0; i < service.result['tags'].length; i++) { + tags.add(Tag.fromJson(service.result['tags'][i])); + } + for (var i = 0; i < result.length; i++) { + datas.add(Data.fromList(result[i])); + } + if (currentDateRangeId != 0 && currentDateRangeId != 1) { + final grouped = + datas.groupListsBy((element) => element.tEn!.split('/')[1]); + datas.clear(); + grouped.forEach((key, value) { + datas.add( + Data( + p: _average(value), + h: '', + l: '', + d: '', + dp: 0, + dt: '', + t: '', + tEn: value.first.tEn!.substring(0, 7), + tG: '', + ts: '', + ), + ); + }); + } + for (var i = 0; i < datas.length; i++) { + final current = _stringToDouble(datas[i].p); + if (maxValue < current) { + maxValue = current; + } + if (minValue == null || minValue! > current) { + minValue = current; + } + } + getRelatedContents(); + datas.replaceRange(0, datas.length, datas.reversed); + chartState = AppState.idle; + appState = AppState.idle; + return; + } + if (datas.isEmpty) { + appState = AppState.failed; + } else { + chartState = AppState.failed; + notifyListeners(); + } + } + + Future getStatisticCurrentDetails() async { + final service = RequestService( + RequestHelper.statisticDetails( + label, + 'current', + ), + ); + await service.httpGet(); + if (service.isSuccess) { + data = Data.fromJson(service.result['data']); + } + return service.isSuccess; + } + + String _average(List inputs) { + double sum = 0; + for (var i = 0; i < inputs.length; i++) { + sum += _stringToDouble(inputs[i].p); + } + return (sum / inputs.length).toString(); + } + + double _stringToDouble(String value) => + double.parse(value.replaceAll(',', '')); + + Future getRelatedContents() async { + if (relatedContents.isNotEmpty) return; + final service = RequestService(RequestHelper.tag( + ids: tags.map((tag) => tag.id).toList(), + )); + await service.httpGet(); + if (service.isSuccess) { + final relateds = service.result['contents']; + for (var i = 0; i < relateds.length; i++) { + relatedContents.add(OverviewData.fromJson(relateds[i])); + } + notifyListeners(); + } + } +} diff --git a/lib/views/home/statistic/statistic_state.dart b/lib/views/home/statistic/statistic_state.dart new file mode 100644 index 0000000..00f6c9f --- /dev/null +++ b/lib/views/home/statistic/statistic_state.dart @@ -0,0 +1,102 @@ +import 'package:didvan/constants/assets.dart'; +import 'package:collection/collection.dart'; +import 'package:didvan/models/category.dart'; +import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/statistic_data/statistic_data.dart'; +import 'package:didvan/providers/core.dart'; +import 'package:didvan/providers/user.dart'; +import 'package:didvan/services/network/request.dart'; +import 'package:didvan/services/network/request_helper.dart'; + +class StatisticState extends CoreProvier { + bool isScrolled = false; + bool shouldColapse = false; + int selectedCategoryId = -1; + List categories = []; + final List statistics = []; + final List markedStatistics = []; + + bool get isColapsed => (isCategorySelected && isScrolled) || isScrolled; + + CategoryData? get selectedCategory => categories.firstWhereOrNull( + (element) => element.id == selectedCategoryId, + ); + + bool get isCategorySelected => selectedCategoryId != 0; + + Future getStatistic() async { + statistics.clear(); + markedStatistics.clear(); + appState = AppState.busy; + final RequestService service = RequestService( + RequestHelper.statisticOverviews( + selectedCategoryId == 0 || selectedCategoryId == 1 + ? null + : selectedCategoryId - 1, + ), + ); + await service.httpGet(); + if (service.isSuccess) { + final others = service.result['others']; + for (var i = 0; i < others.length; i++) { + statistics.add(StatisticData.fromJson(others[i])); + } + final marked = service.result['marked']; + for (var i = 0; i < marked.length; i++) { + markedStatistics.add(StatisticData.fromJson(marked[i])); + } + if (isColapsed || isCategorySelected) { + shouldColapse = true; + } + appState = AppState.idle; + return; + } + appState = AppState.failed; + } + + Future changeMark(int id, bool value) async { + final item = statistics.firstWhereOrNull((element) => element.id == id) ?? + markedStatistics.firstWhere((element) => element.id == id); + if (value) { + markedStatistics.add(item); + statistics.remove(item); + } else { + markedStatistics.remove(item); + statistics.add(item); + } + UserProvider.changeStatisticMark(id, value); + notifyListeners(); + } + + void init() { + selectedCategoryId = 0; + isScrolled = false; + markedStatistics.clear(); + statistics.clear(); + Future.delayed(Duration.zero, () { + getStatistic(); + }); + categories = [ + CategoryData( + id: 1, + label: 'منتخب', + asset: Assets.economicCategoryIcon, + ), + CategoryData( + id: 2, + label: 'اقتصاد کلان', + asset: Assets.globCategoryIcon, + ), + CategoryData( + id: 3, + label: 'صنعت فولاد', + asset: Assets.steelCategoryIcon, + ), + CategoryData( + id: 4, + label: 'بازار سرمایه', + asset: Assets.stockCategoryIcon, + ), + ]; + } +} diff --git a/lib/views/home/statistic/widgets/statistic_overview.dart b/lib/views/home/statistic/widgets/statistic_overview.dart new file mode 100644 index 0000000..acb392b --- /dev/null +++ b/lib/views/home/statistic/widgets/statistic_overview.dart @@ -0,0 +1,164 @@ +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/models/statistic_data/statistic_data.dart'; +import 'package:didvan/routes/routes.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:flutter/material.dart'; + +class StatisticOverview extends StatelessWidget { + final StatisticData statistic; + final bool isMarked; + final void Function(int id, bool value) onMarkChanged; + const StatisticOverview({ + Key? key, + required this.statistic, + required this.isMarked, + required this.onMarkChanged, + }) : super(key: key); + + Color _diffColor(context) => statistic.data.dt == 'high' + ? Theme.of(context).colorScheme.success + : Theme.of(context).colorScheme.error; + + bool get _hasDiff => statistic.data.d != '0'; + + @override + Widget build(BuildContext context) { + return DidvanCard( + onTap: () => + Navigator.of(context).pushNamed(Routes.statisticDetails, arguments: { + 'onMarkChanged': (value) => onMarkChanged(statistic.id, value), + 'label': statistic.label, + 'title': statistic.title, + 'marked': isMarked, + }), + child: Column( + children: [ + Row( + children: [ + if (isMarked) + Icon( + Icons.star, + color: Theme.of(context).colorScheme.yellow, + size: 18, + ), + DidvanText( + statistic.title, + style: Theme.of(context).textTheme.bodyText1, + ), + const Spacer(), + if (_hasDiff) + DidvanText( + '(${statistic.data.d})', + color: _diffColor(context), + ), + if (_hasDiff) const SizedBox(width: 8), + DidvanText( + statistic.data.p, + style: Theme.of(context).textTheme.bodyText1, + ) + ], + ), + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.trending_down, + size: 18, + color: Theme.of(context).colorScheme.hint, + ), + DidvanText( + statistic.data.l, + style: Theme.of(context).textTheme.caption, + color: Theme.of(context).colorScheme.hint, + ), + const SizedBox(width: 8), + Icon( + Icons.trending_up, + size: 18, + color: Theme.of(context).colorScheme.hint, + ), + DidvanText( + statistic.data.h, + style: Theme.of(context).textTheme.caption, + color: Theme.of(context).colorScheme.hint, + ), + const Spacer(), + if (_hasDiff) + Icon( + statistic.data.dt == 'high' + ? DidvanIcons.angle_up_regular + : DidvanIcons.angle_down_regular, + size: 18, + color: _diffColor(context), + ), + if (_hasDiff) const SizedBox(width: 4), + if (_hasDiff) + DidvanText( + statistic.data.dp.toString() + '%', + style: Theme.of(context).textTheme.caption, + color: _diffColor(context), + ), + ], + ), + ], + ), + ); + } + + static Widget get placeHolder => Column( + children: [ + DidvanCard( + child: Column( + children: [ + const SizedBox(height: 4), + Row( + children: const [ + ShimmerPlaceholder(width: 80, height: 16), + Spacer(), + ShimmerPlaceholder(width: 50, height: 14), + SizedBox(width: 8), + ShimmerPlaceholder(width: 50, height: 16), + ], + ), + const SizedBox(height: 16), + Row( + children: const [ + ShimmerPlaceholder(width: 150, height: 12), + Spacer(), + ShimmerPlaceholder(width: 80, height: 12), + ], + ), + ], + ), + ), + const SizedBox(height: 20), + DidvanCard( + child: Column( + children: [ + const SizedBox(height: 4), + Row( + children: const [ + ShimmerPlaceholder(width: 80, height: 16), + Spacer(), + ShimmerPlaceholder(width: 50, height: 14), + SizedBox(width: 8), + ShimmerPlaceholder(width: 50, height: 16), + ], + ), + const SizedBox(height: 16), + Row( + children: const [ + ShimmerPlaceholder(width: 150, height: 12), + Spacer(), + ShimmerPlaceholder(width: 80, height: 12), + ], + ), + ], + ), + ), + ], + ); +} diff --git a/lib/views/home/statistics/statistics.dart b/lib/views/home/statistics/statistics.dart deleted file mode 100644 index eef8150..0000000 --- a/lib/views/home/statistics/statistics.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/constants/assets.dart'; -import 'package:didvan/views/home/widgets/logo_app_bar.dart'; -import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; -import 'package:flutter/material.dart'; - -class Statictics extends StatelessWidget { - const Statictics({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - const LogoAppBar(), - Expanded( - child: EmptyState( - asset: Assets.emptyChart, - title: 'قیمت‌ها و شاخص‌های اقتصادی', - subtitle: 'به زودی...', - titleColor: Theme.of(context).colorScheme.title, - ), - ), - ], - ); - } -} diff --git a/lib/views/home/studio/studio.dart b/lib/views/home/studio/studio.dart index 296a552..f437719 100644 --- a/lib/views/home/studio/studio.dart +++ b/lib/views/home/studio/studio.dart @@ -165,7 +165,7 @@ class _StudioState extends State { void _onChanged(String value) { final state = context.read(); - if (value.length < 4 && value.isNotEmpty || state.lastSearch == value) { + if (value.length < 3 && value.isNotEmpty || state.lastSearch == value) { return; } _timer?.cancel(); diff --git a/lib/views/home/studio/studio_details/studio_details.mobile.dart b/lib/views/home/studio/studio_details/studio_details.mobile.dart index 2c34596..b42266c 100644 --- a/lib/views/home/studio/studio_details/studio_details.mobile.dart +++ b/lib/views/home/studio/studio_details/studio_details.mobile.dart @@ -88,7 +88,6 @@ class _StudioDetailsState extends State { ), ), body: SingleChildScrollView( - physics: const NeverScrollableScrollPhysics(), child: SizedBox( height: d.size.height - d.padding.top - 56, child: Column( diff --git a/lib/views/home/studio/studio_details/studio_details.web.dart b/lib/views/home/studio/studio_details/studio_details.web.dart index b944905..95aa28f 100644 --- a/lib/views/home/studio/studio_details/studio_details.web.dart +++ b/lib/views/home/studio/studio_details/studio_details.web.dart @@ -87,9 +87,12 @@ class _StudioDetailsState extends State { height: d.size.height - d.padding.top - 56, child: Column( children: [ - const AspectRatio( + AspectRatio( aspectRatio: 16 / 9, - child: HtmlElementView(viewType: 'video'), + child: HtmlElementView( + viewType: 'video', + key: ValueKey(state.studio.id), + ), ), Expanded( child: StudioDetailsWidget( diff --git a/lib/views/home/radar/widgets/categories_gird.dart b/lib/views/home/widgets/categories_gird.dart similarity index 60% rename from lib/views/home/radar/widgets/categories_gird.dart rename to lib/views/home/widgets/categories_gird.dart index abcc0e2..564ad03 100644 --- a/lib/views/home/radar/widgets/categories_gird.dart +++ b/lib/views/home/widgets/categories_gird.dart @@ -1,27 +1,34 @@ import 'package:didvan/config/design_config.dart'; -import 'package:didvan/views/home/radar/radar_state.dart'; -import 'package:didvan/views/home/radar/widgets/category_item.dart'; +import 'package:didvan/models/category.dart'; +import 'package:didvan/views/home/widgets/category_item.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; class CategoriesRow1 extends StatelessWidget { - const CategoriesRow1({Key? key}) : super(key: key); + final List categories; + final bool isColapsed; + final double topPadding; + final double rightPadding; + final void Function(CategoryData data) onSelected; + const CategoriesRow1({ + Key? key, + required this.categories, + required this.isColapsed, + required this.onSelected, + required this.topPadding, + required this.rightPadding, + }) : super(key: key); @override Widget build(BuildContext context) { - final state = context.read(); - final isColapsed = state.isColapsed || state.searching || state.filtering; final MediaQueryData d = MediaQuery.of(context); return AnimatedPositioned( curve: Curves.easeIn, duration: DesignConfig.mediumAnimationDuration, - top: isColapsed ? -60 : 300 + d.padding.top, - left: isColapsed ? -80 : 0, - right: isColapsed ? 124 : 0, + top: isColapsed ? -60 : topPadding + d.padding.top, + left: isColapsed ? -rightPadding : 0, + right: isColapsed ? rightPadding : 0, child: Row( - children: context - .read() - .categories + children: categories .sublist(0, 3) .map( (category) => Expanded( @@ -30,6 +37,7 @@ class CategoriesRow1 extends StatelessWidget { child: CategoryItem( category: category, isColapsed: isColapsed, + onSelected: () => onSelected(category), ), ), ), @@ -41,14 +49,19 @@ class CategoriesRow1 extends StatelessWidget { } class CategoriesRow2 extends StatelessWidget { + final List categories; + final bool isColapsed; + final void Function(CategoryData data) onSelected; + const CategoriesRow2({ Key? key, + required this.categories, + required this.isColapsed, + required this.onSelected, }) : super(key: key); @override Widget build(BuildContext context) { - final state = context.read(); - final isColapsed = state.isColapsed || state.searching || state.filtering; final MediaQueryData d = MediaQuery.of(context); return AnimatedPositioned( curve: Curves.easeIn, @@ -57,14 +70,13 @@ class CategoriesRow2 extends StatelessWidget { left: isColapsed ? -d.size.width : 0, right: isColapsed ? d.size.width : 0, child: Row( - children: context - .read() - .categories + children: categories .sublist(3, 6) .map( (category) => Expanded( child: CategoryItem( category: category, + onSelected: () => onSelected(category), isColapsed: isColapsed, ), ), diff --git a/lib/views/home/widgets/categories_list.dart b/lib/views/home/widgets/categories_list.dart new file mode 100644 index 0000000..5a718be --- /dev/null +++ b/lib/views/home/widgets/categories_list.dart @@ -0,0 +1,146 @@ +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/models/category.dart'; +import 'package:didvan/views/widgets/animated_visibility.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:flutter/material.dart'; + +class CategoriesList extends StatefulWidget { + final bool isColapsed; + final bool isAppBar; + final List selectedCats; + final List categories; + final void Function(int id) onSelected; + const CategoriesList({ + Key? key, + required this.isColapsed, + required this.selectedCats, + required this.categories, + required this.onSelected, + this.isAppBar = true, + }) : super(key: key); + + @override + State createState() => _CategoriesListState(); +} + +class _CategoriesListState extends State { + final _scrollController = ScrollController(); + + int _lastSelectedCategoryId = 0; + + @override + void didUpdateWidget(covariant CategoriesList oldWidget) { + if (widget.selectedCats.isNotEmpty && + _lastSelectedCategoryId != widget.selectedCats.first.id) { + _lastSelectedCategoryId = widget.selectedCats.first.id; + _scrollController.animateTo( + widget.selectedCats.first.id * 100, + duration: DesignConfig.lowAnimationDuration, + curve: Curves.easeIn, + ); + } + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + final MediaQueryData d = MediaQuery.of(context); + final child = SingleChildScrollView( + controller: _scrollController, + scrollDirection: Axis.horizontal, + padding: widget.isAppBar + ? EdgeInsets.only( + top: d.padding.top + 12, + bottom: 12, + ) + : null, + child: Row( + children: [ + const SizedBox(width: 12), + if (widget.isAppBar) + _itemBuilder( + CategoryData( + label: 'همه', + id: 0, + ), + context, + ), + for (var i = 0; i < widget.categories.length; i++) + _itemBuilder(widget.categories[i], context), + ], + ), + ); + if (widget.isAppBar) { + return Positioned( + top: 0, + left: 0, + right: 0, + child: AnimatedCrossFade( + crossFadeState: widget.isColapsed + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, + duration: DesignConfig.mediumAnimationDuration, + reverseDuration: DesignConfig.lowAnimationDuration, + sizeCurve: Curves.easeIn, + firstChild: const SizedBox(), + secondChild: Container( + height: 60 + d.padding.top, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + boxShadow: DesignConfig.defaultShadow, + ), + child: AnimatedVisibility( + isVisible: widget.isColapsed, + duration: DesignConfig.mediumAnimationDuration, + child: child, + ), + ), + ), + ); + } + return child; + } + + Widget _itemBuilder(CategoryData category, BuildContext context) { + return GestureDetector( + onTap: () async { + if (widget.selectedCats.isNotEmpty && + widget.selectedCats.first.id == category.id) return; + widget.selectedCats.clear(); + if (category.id != 0) widget.selectedCats.add(category); + await _scrollController.animateTo( + category.id * 100, + duration: DesignConfig.lowAnimationDuration, + curve: Curves.easeIn, + ); + widget.onSelected(category.id); + }, + child: Container( + margin: const EdgeInsets.only(left: 12), + width: 100, + padding: const EdgeInsets.all(4), + alignment: Alignment.center, + child: FittedBox( + fit: BoxFit.scaleDown, + child: DidvanText( + category.label, + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.focusedBorder, + ), + ), + decoration: BoxDecoration( + color: widget.selectedCats.length == 1 && + widget.selectedCats.contains(category) || + category.id == 0 && widget.selectedCats.isEmpty + ? Theme.of(context).colorScheme.splash + : null, + border: Border.all( + color: Theme.of(context).colorScheme.focusedBorder, + ), + borderRadius: DesignConfig.lowBorderRadius, + ), + ), + ); + } +} diff --git a/lib/views/home/radar/widgets/category_item.dart b/lib/views/home/widgets/category_item.dart similarity index 83% rename from lib/views/home/radar/widgets/category_item.dart rename to lib/views/home/widgets/category_item.dart index faee630..eaf6cf6 100644 --- a/lib/views/home/radar/widgets/category_item.dart +++ b/lib/views/home/widgets/category_item.dart @@ -1,22 +1,22 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/models/view/radar_category.dart'; -import 'package:didvan/views/home/radar/radar_state.dart'; +import 'package:didvan/models/category.dart'; import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:provider/provider.dart'; class CategoryItem extends StatelessWidget { - final RadarCategory category; + final CategoryData category; final bool isColapsed; + final VoidCallback onSelected; const CategoryItem({ Key? key, required this.isColapsed, required this.category, + required this.onSelected, }) : super(key: key); double _width(context) { @@ -40,14 +40,7 @@ class CategoryItem extends StatelessWidget { final Size ds = MediaQuery.of(context).size; return Center( child: GestureDetector( - onTap: () { - final state = context.read(); - state.selectedCats.clear(); - if (category.id != 0) { - state.selectedCats.add(category); - } - state.getRadars(page: 1); - }, + onTap: onSelected, child: AnimatedContainer( duration: DesignConfig.mediumAnimationDuration, padding: isColapsed ? const EdgeInsets.all(4) : EdgeInsets.zero, @@ -77,14 +70,14 @@ class CategoryItem extends StatelessWidget { borderRadius: DesignConfig.mediumBorderRadius, ), padding: const EdgeInsets.all(8), - child: SvgPicture.asset(category.asset), + child: SvgPicture.asset(category.asset!), ), ), const SizedBox( height: 8, ), DidvanText( - category.title, + category.label, style: Theme.of(context).textTheme.subtitle2, color: Theme.of(context).colorScheme.title, ), diff --git a/lib/views/home/widgets/search_field.dart b/lib/views/home/widgets/search_field.dart index 9a979b7..d0fb92d 100644 --- a/lib/views/home/widgets/search_field.dart +++ b/lib/views/home/widgets/search_field.dart @@ -78,7 +78,7 @@ class _SearchFieldState extends State { right: 12, ), border: InputBorder.none, - hintText: 'جستجو مطلب در ${widget.title}', + hintText: 'جستجو در ${widget.title}', hintStyle: TextStyle( color: Theme.of(context).colorScheme.disabledText, ), diff --git a/lib/views/widgets/didvan/app_bar.dart b/lib/views/widgets/didvan/app_bar.dart index 81770c8..ab7a949 100644 --- a/lib/views/widgets/didvan/app_bar.dart +++ b/lib/views/widgets/didvan/app_bar.dart @@ -29,6 +29,7 @@ class DidvanAppBar extends StatelessWidget { ), ) : null, + color: backgroundColor ?? Theme.of(context).colorScheme.background, ), child: Row( children: [ diff --git a/lib/views/widgets/didvan/page_view.dart b/lib/views/widgets/didvan/page_view.dart index a7cb236..54384af 100644 --- a/lib/views/widgets/didvan/page_view.dart +++ b/lib/views/widgets/didvan/page_view.dart @@ -2,10 +2,13 @@ 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/routes/routes.dart'; +import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/date_time.dart'; import 'package:didvan/views/home/widgets/overview/multitype.dart'; import 'package:didvan/views/home/widgets/tag_item.dart'; import 'package:didvan/views/widgets/animated_visibility.dart'; +import 'package:didvan/views/widgets/didvan/button.dart'; import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; @@ -190,7 +193,35 @@ class _DidvanPageViewState extends State { if (content.text != null) { return Html( data: content.text, - onAnchorTap: (href, context, map, element) => launch(href!), + onAnchorTap: (href, context, map, element) { + if (href!.contains('navigate-')) { + Navigator.of(ActionSheetUtils.context) + .pushNamed(Routes.statisticDetails, arguments: { + 'onMarkChanged': (value) {}, + 'label': href.split('-')[1], + 'title': href.split('-').last, + 'marked': false, + }); + } else if (href.contains('popup-')) { + showDialog( + context: ActionSheetUtils.context, + builder: (context) => DidvanCard( + child: Column( + children: [ + DidvanText(href.split('-').last), + const DidvanDivider(), + const DidvanButton( + title: 'بستن', + onPressed: ActionSheetUtils.pop, + ), + ], + ), + ), + ); + } else { + launch(href); + } + }, style: { '*': Style( direction: TextDirection.rtl, diff --git a/lib/views/widgets/didvan/scaffold.dart b/lib/views/widgets/didvan/scaffold.dart index 38f9dde..879e240 100644 --- a/lib/views/widgets/didvan/scaffold.dart +++ b/lib/views/widgets/didvan/scaffold.dart @@ -59,11 +59,15 @@ class _DidvanScaffoldState extends State { SliverAppBar( toolbarHeight: (widget.appBarData!.isSmall ? 56 : 72) - statusBarHeight, - backgroundColor: widget.backgroundColor ?? - Theme.of(context).colorScheme.background, automaticallyImplyLeading: false, pinned: true, - flexibleSpace: DidvanAppBar(appBarData: widget.appBarData!), + backgroundColor: widget.backgroundColor ?? + Theme.of(context).colorScheme.background, + flexibleSpace: DidvanAppBar( + appBarData: widget.appBarData!, + backgroundColor: widget.backgroundColor ?? + Theme.of(context).colorScheme.background, + ), ), if (widget.children != null && !widget.showSliversFirst) SliverPadding( diff --git a/lib/views/widgets/state_handlers/sliver_state_handler.dart b/lib/views/widgets/state_handlers/sliver_state_handler.dart index 1e70ddb..8338a49 100644 --- a/lib/views/widgets/state_handlers/sliver_state_handler.dart +++ b/lib/views/widgets/state_handlers/sliver_state_handler.dart @@ -13,6 +13,7 @@ class SliverStateHandler extends SliverList { final Widget? placeholder; final EdgeInsets? itemPadding; final bool centerEmptyState; + final bool hasConstraints; SliverStateHandler({ Key? key, required this.state, @@ -24,14 +25,16 @@ class SliverStateHandler extends SliverList { this.emptyState, this.enableEmptyState = false, this.centerEmptyState = true, + this.hasConstraints = false, }) : super( key: key, delegate: SliverChildBuilderDelegate( (context, index) { + final deviceHight = MediaQuery.of(context).size.height; if (state.appState == AppState.failed) { return Padding( padding: EdgeInsets.only( - top: centerEmptyState ? 120 : 20, + top: centerEmptyState ? deviceHight / 4 : deviceHight / 8, bottom: 20, ), child: EmptyConnection(onRetry: onRetry), @@ -40,9 +43,7 @@ class SliverStateHandler extends SliverList { if (enableEmptyState && state.appState == AppState.idle) { return Padding( padding: EdgeInsets.only( - top: centerEmptyState - ? MediaQuery.of(context).size.height / 4 - : 20, + top: centerEmptyState ? deviceHight / 4 : deviceHight / 8, bottom: 20, ), child: emptyState, diff --git a/lib/views/widgets/state_handlers/state_handler.dart b/lib/views/widgets/state_handlers/state_handler.dart index 0addd30..c54c651 100644 --- a/lib/views/widgets/state_handlers/state_handler.dart +++ b/lib/views/widgets/state_handlers/state_handler.dart @@ -38,10 +38,13 @@ class StateHandler extends StatelessWidget { case AppState.idle: return builder(context, state); case AppState.busy: - return placeholder ?? - SpinKitSpinningLines( - color: Theme.of(context).colorScheme.primary, - ); + return Padding( + padding: EdgeInsets.only(top: topPadding), + child: placeholder ?? + SpinKitSpinningLines( + color: Theme.of(context).colorScheme.primary, + ), + ); case AppState.failed: return Center(child: EmptyConnection(onRetry: onRetry)); default: diff --git a/pubspec.lock b/pubspec.lock index f08bc28..3823c80 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -141,6 +141,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.5" + equatable: + dependency: transitive + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" expandable_bottom_sheet: dependency: "direct main" description: @@ -175,7 +182,7 @@ packages: name: firebase_core url: "https://pub.dartlang.org" source: hosted - version: "1.14.0" + version: "1.15.0" firebase_core_platform_interface: dependency: transitive description: @@ -189,28 +196,35 @@ packages: name: firebase_core_web url: "https://pub.dartlang.org" source: hosted - version: "1.6.1" + version: "1.6.2" firebase_messaging: dependency: "direct main" description: name: firebase_messaging url: "https://pub.dartlang.org" source: hosted - version: "11.2.12" + version: "11.2.14" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "3.2.2" + version: "3.3.0" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web url: "https://pub.dartlang.org" source: hosted - version: "2.2.10" + version: "2.2.12" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + url: "https://pub.dartlang.org" + source: hosted + version: "0.50.1" flutter: dependency: "direct main" description: flutter @@ -222,7 +236,7 @@ packages: name: flutter_blurhash url: "https://pub.dartlang.org" source: hosted - version: "0.6.4" + version: "0.6.8" flutter_cache_manager: dependency: transitive description: @@ -236,7 +250,7 @@ packages: name: flutter_html url: "https://pub.dartlang.org" source: hosted - version: "3.0.0-alpha.2" + version: "3.0.0-alpha.3" flutter_lints: dependency: "direct dev" description: @@ -384,6 +398,13 @@ packages: name: image_picker url: "https://pub.dartlang.org" source: hosted + version: "0.8.5" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + url: "https://pub.dartlang.org" + source: hosted version: "0.8.4+11" image_picker_for_web: dependency: transitive @@ -392,6 +413,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.6" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.4+11" image_picker_platform_interface: dependency: transitive description: @@ -601,7 +629,7 @@ packages: name: pin_code_fields url: "https://pub.dartlang.org" source: hosted - version: "7.3.0" + version: "7.4.0" platform: dependency: transitive description: @@ -690,14 +718,14 @@ packages: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.2+1" sqflite_common: dependency: transitive description: name: sqflite_common url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.2.1+1" stack_trace: dependency: transitive description: @@ -879,14 +907,14 @@ packages: name: webview_flutter url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" webview_flutter_android: dependency: transitive description: name: webview_flutter_android url: "https://pub.dartlang.org" source: hosted - version: "2.8.4" + version: "2.8.5" webview_flutter_platform_interface: dependency: transitive description: @@ -900,7 +928,7 @@ packages: name: webview_flutter_wkwebview url: "https://pub.dartlang.org" source: hosted - version: "2.7.1" + version: "2.7.2" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index dfc7dfa..31924b7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.5.0+10 +version: 2.0.0+11 environment: sdk: ">=2.12.0 <3.0.0" @@ -66,6 +66,7 @@ dependencies: permission_handler: ^9.2.0 better_player: ^0.0.81 assets_audio_player: ^3.0.4+1 + fl_chart: ^0.50.1 dev_dependencies: