From ba20b25ab071f8039fc49c2bb86f9e29ae2153cb Mon Sep 17 00:00:00 2001 From: mehrdad78 Date: Fri, 22 Mar 2024 12:31:13 +0330 Subject: [PATCH] Add Infography Screen --- android/gradle.properties | 2 +- lib/config/theme_data.dart | 28 +-- lib/models/infography/info_tag.dart | 29 +++ lib/models/infography/infography_content.dart | 53 +++++ lib/models/requests/infography.dart | 13 ++ lib/routes/route_generator.dart | 8 + lib/routes/routes.dart | 1 + lib/services/network/request.dart | 1 + lib/services/network/request_helper.dart | 14 ++ .../home/infography/infography_screen.dart | 184 ++++++++++++++++++ .../infography/infography_screen_state.dart | 127 ++++++++++++ lib/views/home/main/main_page.dart | 115 +++++------ lib/views/home/main/main_page_state.dart | 12 ++ .../home/main/widgets/infography_item.dart | 136 +++++++++++++ lib/views/home/main/widgets/main_content.dart | 10 - lib/views/widgets/infography_tag.dart | 85 ++++++++ lib/views/widgets/search_field.dart | 4 +- pubspec.lock | 48 ++--- pubspec.yaml | 1 + 19 files changed, 756 insertions(+), 115 deletions(-) create mode 100644 lib/models/infography/info_tag.dart create mode 100644 lib/models/infography/infography_content.dart create mode 100644 lib/models/requests/infography.dart create mode 100644 lib/views/home/infography/infography_screen.dart create mode 100644 lib/views/home/infography/infography_screen_state.dart create mode 100644 lib/views/home/main/widgets/infography_item.dart create mode 100644 lib/views/widgets/infography_tag.dart diff --git a/android/gradle.properties b/android/gradle.properties index 94adc3a..ed50858 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4608m android.useAndroidX=true android.enableJetifier=true diff --git a/lib/config/theme_data.dart b/lib/config/theme_data.dart index 6968c24..663b442 100644 --- a/lib/config/theme_data.dart +++ b/lib/config/theme_data.dart @@ -29,20 +29,20 @@ class LightThemeConfig { ); static const ColorScheme _colorScheme = ColorScheme( - primary: _primary, - primaryContainer: _white, - secondary: Color(0xFFB20436), - secondaryContainer: _white, - surface: _white, - background: _background, - error: Color(0xFFF00505), - onPrimary: _white, - onSecondary: _white, - onSurface: _black, - onBackground: _white, - onError: _white, - brightness: Brightness.light, - ); + primary: _primary, + primaryContainer: _white, + secondary: Color(0xFFB20436), + secondaryContainer: _white, + surface: _white, + background: _background, + error: Color(0xFFF00505), + onPrimary: _white, + onSecondary: _white, + onSurface: _black, + onBackground: _white, + onError: _white, + brightness: Brightness.light, + outline: Color(0xff007EA7)); } class DarkThemeConfig { diff --git a/lib/models/infography/info_tag.dart b/lib/models/infography/info_tag.dart new file mode 100644 index 0000000..0dd6164 --- /dev/null +++ b/lib/models/infography/info_tag.dart @@ -0,0 +1,29 @@ +class InfoTagModel { + final List tags; + + InfoTagModel({required this.tags}); + + factory InfoTagModel.fromJson(Map json) { + return InfoTagModel( + tags: List.from(json['tags'].map((x) => InTag.fromJson(x))), + ); + } +} + +class InTag { + final int id; + final String label; + + InTag({required this.id, required this.label}); + + factory InTag.fromJson(Map json) { + return InTag( + id: json['id'], + label: json['label'], + ); + } + @override + String toString() { + return label; + } +} diff --git a/lib/models/infography/infography_content.dart b/lib/models/infography/infography_content.dart new file mode 100644 index 0000000..beda55d --- /dev/null +++ b/lib/models/infography/infography_content.dart @@ -0,0 +1,53 @@ +class InfographyContent { + final List contents; + final int lastPage; + + InfographyContent({required this.contents, required this.lastPage}); + + factory InfographyContent.fromJson(Map json) { + return InfographyContent( + contents: + List.from(json['contents'].map((x) => Content.fromJson(x))), + lastPage: json['lastPage'], + ); + } +} + +class Content { + final int id; + final String title; + final String image; + final String category; + final List tags; + + Content( + {required this.id, + required this.title, + required this.image, + required this.category, + required this.tags}); + + factory Content.fromJson(Map json) { + return Content( + id: json['id'], + title: json['title'], + image: json['image'], + category: json['category'], + tags: List.from(json['tags'].map((x) => Tag.fromJson(x))), + ); + } +} + +class Tag { + final int id; + final String label; + + Tag({required this.id, required this.label}); + + factory Tag.fromJson(Map json) { + return Tag( + id: json['id'], + label: json['label'], + ); + } +} diff --git a/lib/models/requests/infography.dart b/lib/models/requests/infography.dart new file mode 100644 index 0000000..f11b2c4 --- /dev/null +++ b/lib/models/requests/infography.dart @@ -0,0 +1,13 @@ +class InfographyRequestArgs { + final int page; + final String? q; + final List? tag; + final List? categories; + + const InfographyRequestArgs({ + required this.page, + this.categories, + this.q, + this.tag, + }); +} diff --git a/lib/routes/route_generator.dart b/lib/routes/route_generator.dart index 94014bf..6b6f343 100644 --- a/lib/routes/route_generator.dart +++ b/lib/routes/route_generator.dart @@ -7,6 +7,8 @@ import 'package:didvan/views/direct/direct_state.dart'; import 'package:didvan/views/hashtag/hashtag.dart'; import 'package:didvan/views/hashtag/hashtag_state.dart'; import 'package:didvan/views/home/home.dart'; +import 'package:didvan/views/home/infography/infography_screen.dart'; +import 'package:didvan/views/home/infography/infography_screen_state.dart'; import 'package:didvan/views/home/main/main_page_state.dart'; import 'package:didvan/views/home/home_state.dart'; import 'package:didvan/views/news/news.dart'; @@ -111,6 +113,12 @@ class RouteGenerator { child: const News(), ), ); + case Routes.infography: + return _createRoute(ChangeNotifierProvider( + create: (context) => InfographyScreenState(), + child: const InfographyScreen(), + )); + case Routes.radarDetails: return _createRoute( ChangeNotifierProvider( diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart index 08f562f..906902c 100644 --- a/lib/routes/routes.dart +++ b/lib/routes/routes.dart @@ -3,6 +3,7 @@ class Routes { static const String home = '/home'; static const String radars = '/radars'; static const String news = '/news'; + static const String infography = '/infography'; static const String podcasts = '/podcasts'; static const String videocasts = '/videocasts'; static const String aboutUs = '/about-us'; diff --git a/lib/services/network/request.dart b/lib/services/network/request.dart index de6b12e..a218b24 100644 --- a/lib/services/network/request.dart +++ b/lib/services/network/request.dart @@ -178,6 +178,7 @@ class RequestService { void _handleResponse(http.Response? response) { statusCode = response?.statusCode; + //log("Mehradad->" + response!.body.toString()); if (_handleError(response)) { if (response!.body.isNotEmpty) { _body = json.decode(response.body); diff --git a/lib/services/network/request_helper.dart b/lib/services/network/request_helper.dart index b656f99..dc968f8 100644 --- a/lib/services/network/request_helper.dart +++ b/lib/services/network/request_helper.dart @@ -1,3 +1,4 @@ +import 'package:didvan/models/requests/infography.dart'; import 'package:didvan/models/requests/news.dart'; import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/models/requests/studio.dart'; @@ -7,6 +8,8 @@ class RequestHelper { static const String _baseUserUrl = '$baseUrl/user'; static const String _baseRadarUrl = '$baseUrl/radar'; static const String _baseNewsUrl = '$baseUrl/news'; + static const String _baseInfographyUrl = '$baseUrl/home/infography'; + static const String _baseInfoTagUrl = '$baseUrl/home/infography/tags'; static const String _baseStudioUrl = '$baseUrl/studio'; static const String _baseStatisticUrl = '$baseUrl/statistic'; static const String _baseDirectUrl = '$_baseUserUrl/direct'; @@ -106,6 +109,17 @@ class RequestHelper { MapEntry('search', args.search), ]); + static String infographyOverviews({required InfographyRequestArgs args}) => + _baseInfographyUrl + + _urlConcatGenerator([ + MapEntry('page', args.page), + MapEntry('category', _urlListConcatGenerator(args.categories)), + MapEntry('q', args.q), + MapEntry('tag', _urlListConcatGenerator(args.tag)), + ]); + + static String infographyTags() => _baseInfoTagUrl; + static String studioSlider(String type) => '$_baseStudioUrl/slider${_urlConcatGenerator([MapEntry('type', type)])}'; static String studioDetails(int id, StudioRequestArgs args) => diff --git a/lib/views/home/infography/infography_screen.dart b/lib/views/home/infography/infography_screen.dart new file mode 100644 index 0000000..9390032 --- /dev/null +++ b/lib/views/home/infography/infography_screen.dart @@ -0,0 +1,184 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/models/category.dart'; +import 'package:didvan/models/infography/info_tag.dart'; +import 'package:didvan/models/view/action_sheet_data.dart'; +import 'package:didvan/utils/action_sheet.dart'; +import 'package:didvan/views/home/infography/infography_screen_state.dart'; +import 'package:didvan/views/home/main/widgets/infography_item.dart'; +import 'package:didvan/views/widgets/didvan/checkbox.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/item_title.dart'; +import 'package:didvan/views/widgets/search_field.dart'; +import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:animated_custom_dropdown/custom_dropdown.dart'; + +class InfographyScreen extends StatefulWidget { + const InfographyScreen({super.key}); + + @override + State createState() => _InfographyScreenState(); +} + +class _InfographyScreenState extends State { + final ScrollController _scrollController = ScrollController(); + int pageNumber = 1; + Timer? _timer; + final _focusNode = FocusNode(); + + @override + void initState() { + context.read().init(); + _scrollController.addListener(_onScroll); + super.initState(); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _onScroll() { + if (_scrollController.position.pixels >= + _scrollController.position.maxScrollExtent) { + pageNumber++; + context + .read() + .getInfographyContent(page: pageNumber); + } + } + + void _onChanged(String value) { + final state = context.read(); + if (value.length < 3 && value.isNotEmpty || state.lastSearch == value) { + return; + } + _timer?.cancel(); + _timer = Timer(const Duration(seconds: 1), () { + state.search = value; + state.getInfographyContent(page: 1); + }); + } + + Future _showFilterBottomSheet() async { + final state = context.read(); + await ActionSheetUtils.showBottomSheet( + data: ActionSheetData( + title: 'فیلتر جستجو', + smallDismissButton: true, + titleIcon: DidvanIcons.filter_regular, + dismissTitle: 'حذف فیلتر', + confrimTitle: 'نمایش نتایج', + onDismissed: () => state.resetFilters(false), + onConfirmed: () => state.getInfographyContent(page: 1), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ItemTitle( + title: "جستجوی هشتگ", + icon: DidvanIcons.hashtag_regular, + style: Theme.of(context).textTheme.bodyMedium, + ), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + child: CustomDropdown.multiSelect( + closedHeaderPadding: EdgeInsets.all(12), + items: state.tags, + decoration: CustomDropdownDecoration( + closedBorder: Border.all(color: Colors.grey), + closedFillColor: Colors.grey.shade100.withOpacity(0.1), + ), + hintText: "انتخاب کنید", + onListChanged: (value) { + state.selectedTags.addAll(value); + log('changing value to: ${value.map((e) => e.label + e.id.toString())}'); + }, + ), + ), + ItemTitle( + title: 'دسته بندی', + icon: DidvanIcons.category_regular, + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: 12), + Wrap( + children: [ + for (var i = 0; i < state.categories.length; i++) + SizedBox( + width: (MediaQuery.of(context).size.width - 40) / 2, + child: DidvanCheckbox( + title: state.categories[i].label, + value: state.selectedCats.contains(state.categories[i]), + onChanged: (value) { + if (value) { + state.selectedCats.add(state.categories[i]); + return; + } + state.selectedCats.remove(state.categories[i]); + }, + ), + ), + ], + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return StateHandler( + onRetry: context.read().init, + state: context.watch(), + builder: (context, state) { + return Scaffold( + appBar: AppBar( + elevation: 0.0, + scrolledUnderElevation: 0.0, + title: DidvanText( + "اینفوگرافی", + style: Theme.of(context).textTheme.bodyLarge, + fontSize: 20, + ), + ), + body: Column( + children: [ + Container( + height: 80, + color: Colors.white, + padding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 16.0), + child: SearchField( + title: "اینفوگرافی", + onChanged: _onChanged, + focusNode: _focusNode, + onFilterButtonPressed: _showFilterBottomSheet, + isFiltered: state.filtering), + ), + Expanded( + child: ListView.builder( + controller: _scrollController, + //physics: const ClampingScrollPhysics(), + itemCount: state.contents.length, + itemBuilder: (context, index) => InfographyItem( + image: state.contents[index].image, + category: state.contents[index].category, + title: state.contents[index].title, + tag: state.contents[index].tags, + ), + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/views/home/infography/infography_screen_state.dart b/lib/views/home/infography/infography_screen_state.dart new file mode 100644 index 0000000..894f9fe --- /dev/null +++ b/lib/views/home/infography/infography_screen_state.dart @@ -0,0 +1,127 @@ +import 'package:didvan/constants/assets.dart'; +import 'package:didvan/models/category.dart'; +import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/infography/info_tag.dart'; +import 'package:didvan/models/infography/infography_content.dart'; +import 'package:didvan/models/requests/infography.dart'; +import 'package:didvan/providers/core.dart'; +import 'package:didvan/services/network/request.dart'; +import 'package:didvan/services/network/request_helper.dart'; +import 'dart:developer'; + +import 'package:didvan/views/widgets/infography_tag.dart'; + +class InfographyScreenState extends CoreProvier { + List contents = []; + String search = ''; + String lastSearch = ''; + int lastPage = 1; + int page = 1; + + final List selectedTags = []; + List tags = []; + final List selectedCats = []; + List categories = []; + + bool isScrolled = false; + bool shouldColapse = false; + + bool get filtering => selectedCats.length > 1 && selectedTags.length > 1; + + bool get searching => search.isNotEmpty; + + bool get isColapsed => + (selectedCats.length == 1 && + selectedTags.length == 1 && + !filtering && + isScrolled) || + isScrolled; + + bool get isCategorySelected => + selectedCats.length == 1 && selectedTags.length == 1 && !filtering; + + Future getInfographyContent({required int page}) async { + this.page = page; + lastSearch = search; + if (page == 1) { + contents.clear(); + tags.clear(); + appState = AppState.busy; + } + + final service = RequestService(RequestHelper.infographyOverviews( + args: InfographyRequestArgs( + page: page, + tag: selectedTags.map((e) => e.id).toList(), + q: search == '' ? null : search, + categories: selectedCats.map((e) => e.id).toList()))); + final service2 = RequestService(RequestHelper.infographyTags()); + await service.httpGet(); + await service2.httpGet(); + if (service.isSuccess && service2.isSuccess) { + lastPage = service.result['lastPage']; + + final content = InfographyContent.fromJson(service.result); + final content2 = InfoTagModel.fromJson(service2.result); + + contents.addAll(content.contents); + tags.addAll(content2.tags); + + appState = AppState.idle; + return; + } + appState = AppState.failed; + } + + void resetFilters(bool isInit) { + selectedCats.clear(); + selectedTags.clear(); + search = ''; + lastSearch = ''; + isScrolled = false; + if (!isInit) { + getInfographyContent(page: 1); + } + } + + void init() { + // search = ''; + // lastSearch = ''; + resetFilters(true); + Future.delayed(Duration.zero, () { + getInfographyContent(page: 1); + }); + categories = [ + CategoryData( + id: 1, + label: 'اقتصادی', + asset: Assets.economicCategoryIcon, + ), + CategoryData( + id: 2, + label: 'سیاسی', + asset: Assets.politicalCategoryIcon, + ), + CategoryData( + id: 3, + label: 'فناوری', + asset: Assets.techCategoryIcon, + ), + CategoryData( + id: 4, + label: 'کسب و کار', + asset: Assets.businessCategoryIcon, + ), + CategoryData( + id: 5, + label: 'زیست محیطی', + asset: Assets.enviromentalCategoryIcon, + ), + CategoryData( + id: 6, + label: 'اجتماعی', + asset: Assets.socialCategoryIcon, + ), + ]; + } +} diff --git a/lib/views/home/main/main_page.dart b/lib/views/home/main/main_page.dart index de8e6a1..ad24987 100644 --- a/lib/views/home/main/main_page.dart +++ b/lib/views/home/main/main_page.dart @@ -1,6 +1,8 @@ import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/home_page_content/home_page_list.dart'; +import 'package:didvan/routes/routes.dart'; +import 'package:didvan/views/home/infography/infography_screen.dart'; import 'package:didvan/views/home/main/main_page_state.dart'; import 'package:didvan/views/home/main/widgets/banner.dart'; import 'package:didvan/views/home/main/widgets/general_item.dart'; @@ -41,10 +43,54 @@ class _MainPageState extends State { } index--; if (index == 4) { - return const Padding( - padding: EdgeInsets.only(top: 32), - child: MainPageBanner( - isFirst: false, + return Padding( + padding: const EdgeInsets.only(top: 32), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + left: 16, + right: 16, + bottom: 16, + top: 28, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + const SizedBox(width: 4), + DidvanText( + "اینفوگرافی", + style: Theme.of(context).textTheme.titleMedium, + color: Theme.of(context).colorScheme.title, + ), + ], + ), + GestureDetector( + onTap: () => { + Navigator.of(context).pushNamed(Routes.infography) + }, + child: Row( + children: [ + DidvanText( + "همه", + color: Theme.of(context).colorScheme.primary, + ), + Icon( + DidvanIcons.angle_left_light, + color: Theme.of(context).colorScheme.primary, + ) + ], + ), + ) + ], + ), + ), + const MainPageBanner( + isFirst: false, + ), + ], ), ); } @@ -162,30 +208,6 @@ class _MainPageSection extends StatelessWidget { ), ), ), - // if (list.type == 'radar') - // DidvanSlider( - // height: 260, - // itemCount: list.contents.length, - // viewportFraction: 0.65, - // itemBuilder: (context, index, realIndex) => Padding( - // padding: const EdgeInsets.symmetric(horizontal: 4), - // child: MainPageGeneralItem( - // content: list.contents[index], - // ), - // ), - // ), - // if (list.type == 'video') - // DidvanSlider( - // height: 260, - // itemCount: list.contents.length, - // viewportFraction: 0.65, - // itemBuilder: (context, index, realIndex) => Padding( - // padding: const EdgeInsets.symmetric(horizontal: 4), - // child: MainPageGeneralItem( - // content: list.contents[index], - // ), - // ), - // ), if (list.type == 'podcast') Padding( padding: const EdgeInsets.only(top: 28), @@ -206,44 +228,7 @@ class _MainPageSection extends StatelessWidget { .toList(), ), ), - // if (list.type != 'news' && - // list.type != 'radar' && - // list.type != 'video' && - // list.type != 'podcast') - // DidvanSlider( - // itemBuilder: (context, index, realIndex) => Padding( - // padding: const EdgeInsets.symmetric(horizontal: 4), - // child: MainPageGeneralItem( - // content: list.contents[index], - // ), - // ), - // itemCount: list.contents.length, - // viewportFraction: 0.65, - // height: 260 + _maxSublistCount() * 20, - // ), - // if (!isLast) const _MainPageDivider(), ], ); } } - -// class _MainPageDivider extends StatelessWidget { -// const _MainPageDivider(); - -// @override -// Widget build(BuildContext context) { -// return Container( -// height: 2, -// margin: const EdgeInsets.only( -// top: 8, -// left: 20, -// right: 20, -// ), -// width: double.infinity, -// decoration: BoxDecoration( -// borderRadius: DesignConfig.highBorderRadius, -// color: Theme.of(context).colorScheme.border, -// ), -// ); -// } -// } diff --git a/lib/views/home/main/main_page_state.dart b/lib/views/home/main/main_page_state.dart index c1fe655..d6beb52 100644 --- a/lib/views/home/main/main_page_state.dart +++ b/lib/views/home/main/main_page_state.dart @@ -1,5 +1,6 @@ import 'package:didvan/models/enums.dart'; import 'package:didvan/models/home_page_content/home_page_content.dart'; +import 'package:didvan/models/requests/infography.dart'; import 'package:didvan/models/requests/news.dart'; import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/providers/core.dart'; @@ -49,6 +50,17 @@ class MainPageState extends CoreProvier { link = link ?? ''; dynamic args; switch (type) { + case 'infography': + { + link = Routes.infography; + args = { + 'onMarkChanged': (id, value) => markChangeHandler(type, id, value), + 'id': id, + 'args': const InfographyRequestArgs(page: 0), + 'hasUnmarkConfirmation': false, + }; + break; + } case 'news': { link = Routes.newsDetails; diff --git a/lib/views/home/main/widgets/infography_item.dart b/lib/views/home/main/widgets/infography_item.dart new file mode 100644 index 0000000..3941392 --- /dev/null +++ b/lib/views/home/main/widgets/infography_item.dart @@ -0,0 +1,136 @@ +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/infography/infography_content.dart'; +import 'package:didvan/views/widgets/animated_visibility.dart'; +import 'package:didvan/views/widgets/didvan/card.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/infography_tag.dart'; +import 'package:didvan/views/widgets/ink_wrapper.dart'; +import 'package:didvan/views/widgets/skeleton_image.dart'; +import 'package:flutter/material.dart'; + +class _BackButton extends StatefulWidget { + const _BackButton({Key? key}) : super(key: key); + + @override + __BackButtonState createState() => __BackButtonState(); +} + +class __BackButtonState extends State<_BackButton> { + @override + Widget build(BuildContext context) { + return AnimatedVisibility( + duration: DesignConfig.lowAnimationDuration, + isVisible: true, + child: InkWrapper( + borderRadius: DesignConfig.lowBorderRadius, + onPressed: Navigator.of(context).pop, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.splash, + border: Border.all(color: Theme.of(context).colorScheme.border), + borderRadius: DesignConfig.lowBorderRadius, + ), + child: const Icon( + DidvanIcons.back_regular, + size: 32, + ), + ), + ), + ); + } +} + +class InfographyItem extends StatelessWidget { + final String image; + final String title; + final String category; + final List tag; + + const InfographyItem( + {super.key, + required this.image, + required this.category, + required this.title, + required this.tag}); + + void _openInteractiveViewer(BuildContext context, String image) { + showDialog( + context: context, + builder: (context) => Stack( + children: [ + Positioned.fill( + child: InteractiveViewer( + child: Center( + child: SkeletonImage( + width: min(MediaQuery.of(context).size.width, + MediaQuery.of(context).size.height), + imageUrl: image, + ), + ), + ), + ), + const Positioned( + right: 24, + top: 24, + child: _BackButton(), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return DidvanCard( + margin: const EdgeInsets.all(12), + padding: const EdgeInsets.all(12), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Align( + alignment: Alignment.centerRight, + child: DidvanText( + title, + style: Theme.of(context).textTheme.bodyLarge, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: GestureDetector( + onTap: () => _openInteractiveViewer(context, image), + child: SkeletonImage( + imageUrl: image, + aspectRatio: 16 / 9, + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Wrap( + spacing: 4, + runSpacing: 4, + children: [ + for (var i = 0; i < tag.length; i++) + InfographyTag( + tag: tag[i], + ), + ], + ), + InfoCat(category: category) + ], + ) + ], + ), + ); + } +} diff --git a/lib/views/home/main/widgets/main_content.dart b/lib/views/home/main/widgets/main_content.dart index 8a28bf6..d03f999 100644 --- a/lib/views/home/main/widgets/main_content.dart +++ b/lib/views/home/main/widgets/main_content.dart @@ -15,16 +15,6 @@ class MainPageMainContent extends StatelessWidget { isFirst: true, ), const SizedBox(height: 28), - // Stack( - // children: [ - // Positioned( - // bottom: 13, - // child: Container( - // width: MediaQuery.of(context).size.width, - // height: 2, - // color: Theme.of(context).colorScheme.border, - // ), - // ), Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 12), diff --git a/lib/views/widgets/infography_tag.dart b/lib/views/widgets/infography_tag.dart new file mode 100644 index 0000000..765734a --- /dev/null +++ b/lib/views/widgets/infography_tag.dart @@ -0,0 +1,85 @@ +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/infography/infography_content.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/ink_wrapper.dart'; +import 'package:flutter/material.dart'; + +class InfographyTag extends StatelessWidget { + final Tag tag; + + const InfographyTag({ + Key? key, + required this.tag, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return InkWrapper( + borderRadius: DesignConfig.lowBorderRadius, + onPressed: () => {}, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 4, + horizontal: 4, + ), + decoration: BoxDecoration( + borderRadius: DesignConfig.lowBorderRadius, + border: Border.all( + color: Theme.of(context).colorScheme.focusedBorder, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + DidvanIcons.hashtag_regular, + color: Theme.of(context).colorScheme.focusedBorder, + size: 16, + ), + DidvanText( + tag.label, + color: Theme.of(context).colorScheme.focusedBorder, + style: Theme.of(context).textTheme.labelLarge, + ), + ], + ), + ), + ); + } +} + +class InfoCat extends StatelessWidget { + final String category; + + const InfoCat({ + Key? key, + required this.category, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return InkWrapper( + borderRadius: DesignConfig.lowBorderRadius, + onPressed: () => {}, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 4, + horizontal: 8, + ), + decoration: BoxDecoration( + borderRadius: DesignConfig.lowBorderRadius, + border: Border.all( + color: Theme.of(context).colorScheme.outline, + ), + ), + child: DidvanText( + category, + color: Theme.of(context).colorScheme.outline, + style: Theme.of(context).textTheme.labelLarge, + ), + ), + ); + } +} diff --git a/lib/views/widgets/search_field.dart b/lib/views/widgets/search_field.dart index 01859be..c9849f0 100644 --- a/lib/views/widgets/search_field.dart +++ b/lib/views/widgets/search_field.dart @@ -31,7 +31,9 @@ class _SearchFieldState extends State { @override void initState() { widget.focusNode.addListener(() { - setState(() {}); + if (mounted) { + setState(() {}); + } }); super.initState(); } diff --git a/pubspec.lock b/pubspec.lock index c0dc14f..61fb15c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.7" + animated_custom_dropdown: + dependency: "direct main" + description: + name: animated_custom_dropdown + sha256: "9e286defa42f2e774285015d7ff29523a24260888d53f79a3635328fad5bdad7" + url: "https://pub.dev" + source: hosted + version: "3.0.0" args: dependency: transitive description: @@ -134,10 +142,10 @@ packages: dependency: "direct main" description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" cross_file: dependency: transitive description: @@ -442,10 +450,10 @@ packages: dependency: transitive description: name: fwfh_text_style - sha256: f0883ccb64b7bb3f2a7a091542c2e834fc3e2a6aa54158f46b3c43b55675d8f7 + sha256: "5f8b587fd223a6bf14aad3d3da5e7ced0628becbd0768f8e7ae25ff6b9f3d2ec" url: "https://pub.dev" source: hosted - version: "2.22.8+3" + version: "2.23.8" graphs: dependency: transitive description: @@ -598,14 +606,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" - logging: - dependency: transitive - description: - name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" - url: "https://pub.dev" - source: hosted - version: "1.2.0" matcher: dependency: transitive description: @@ -626,10 +626,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" mime: dependency: transitive description: @@ -927,18 +927,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -967,10 +967,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" typed_data: dependency: transitive description: @@ -1151,10 +1151,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" webview_flutter: dependency: "direct main" description: @@ -1212,5 +1212,5 @@ packages: source: hosted version: "6.3.0" sdks: - dart: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + dart: ">=3.2.0-194.0.dev <4.0.0" + flutter: ">=3.14.0-0" diff --git a/pubspec.yaml b/pubspec.yaml index cf76f67..545726d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -69,6 +69,7 @@ dependencies: assets_audio_player: ^3.1.1 fl_chart: ^0.63.0 collection: ^1.17.2 + animated_custom_dropdown: ^3.0.0 dev_dependencies: flutter_test: