From e037b805e46bbae2d224da1c4d1edf11257efcd7 Mon Sep 17 00:00:00 2001 From: Alesia <104492428+alesiaradkevich@users.noreply.github.com> Date: Mon, 31 Jul 2023 20:15:23 +0300 Subject: [PATCH] Implement sorting (#50) --- assets/arrow_down_icon.svg | 3 + lib/src/presentation/l10n/app_en.arb | 5 +- lib/src/presentation/l10n/app_ru.arb | 5 +- lib/src/presentation/l10n/app_uk.arb | 5 +- .../l10n/suggestions_localizations.dart | 19 +++- .../l10n/suggestions_localizations_en.dart | 15 ++- .../l10n/suggestions_localizations_ru.dart | 15 ++- .../l10n/suggestions_localizations_uk.dart | 9 ++ .../pages/suggestions/suggestions_cubit.dart | 48 +++++++-- .../pages/suggestions/suggestions_page.dart | 26 ++++- .../pages/suggestions/suggestions_state.dart | 96 +++++++++++++++-- .../suggestions/widgets/list_description.dart | 58 ++++++++-- .../suggestions/widgets/suggestion_list.dart | 8 +- .../bottom_sheets/label_bottom_sheet.dart | 27 +---- .../bottom_sheets/sorting_bottom_sheet.dart | 102 ++++++++++++++++++ .../widgets/suggestions_radio_button.dart | 42 ++++++++ .../presentation/utils/assets_strings.dart | 4 +- .../cubits/suggestions_cubit_test.dart | 28 +++-- 18 files changed, 448 insertions(+), 67 deletions(-) create mode 100644 assets/arrow_down_icon.svg create mode 100644 lib/src/presentation/pages/widgets/bottom_sheets/sorting_bottom_sheet.dart create mode 100644 lib/src/presentation/pages/widgets/suggestions_radio_button.dart diff --git a/assets/arrow_down_icon.svg b/assets/arrow_down_icon.svg new file mode 100644 index 0000000..dde5b84 --- /dev/null +++ b/assets/arrow_down_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/src/presentation/l10n/app_en.arb b/lib/src/presentation/l10n/app_en.arb index 5a5605b..6938dc3 100644 --- a/lib/src/presentation/l10n/app_en.arb +++ b/lib/src/presentation/l10n/app_en.arb @@ -48,5 +48,8 @@ "commentHint": "Your comment…", "publish": "Publish", "add": "Add", - "eventPhotosRestriction": "You can attach up to 10 photos 🖼️" + "eventPhotosRestriction": "You can attach up to 10 photos 🖼️", + "sortBy": "Sort by", + "numberOfLikes": "Number of likes", + "creationDate": "Creation date" } \ No newline at end of file diff --git a/lib/src/presentation/l10n/app_ru.arb b/lib/src/presentation/l10n/app_ru.arb index cee3027..5797073 100644 --- a/lib/src/presentation/l10n/app_ru.arb +++ b/lib/src/presentation/l10n/app_ru.arb @@ -48,5 +48,8 @@ "commentHint": "Ваш комментарий…", "publish": "Опубликовать", "add": "Добавить", - "eventPhotosRestriction": "Вы можете прикрепить до 10 фото 🖼️" + "eventPhotosRestriction": "Вы можете прикрепить до 10 фото 🖼️", + "sortBy": "Сортировать по", + "numberOfLikes": "Количеству лайков", + "creationDate": "Дате создания" } \ No newline at end of file diff --git a/lib/src/presentation/l10n/app_uk.arb b/lib/src/presentation/l10n/app_uk.arb index 7d42465..381a599 100644 --- a/lib/src/presentation/l10n/app_uk.arb +++ b/lib/src/presentation/l10n/app_uk.arb @@ -48,5 +48,8 @@ "commentHint": "Ваш коментар…", "publish": "Опублікувати", "add": "Додати", - "eventPhotosRestriction": "Ви можете прикріпити до 10 фото 🖼️" + "eventPhotosRestriction": "Ви можете прикріпити до 10 фото 🖼️", + "sortBy": "Сортувати за", + "numberOfLikes": "Кількістю лайків", + "creationDate": "Датою створення" } \ No newline at end of file diff --git a/lib/src/presentation/l10n/suggestions_localizations.dart b/lib/src/presentation/l10n/suggestions_localizations.dart index 6262267..cc082e5 100644 --- a/lib/src/presentation/l10n/suggestions_localizations.dart +++ b/lib/src/presentation/l10n/suggestions_localizations.dart @@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:intl/intl.dart' as intl; - import 'package:suggest_a_feature/src/presentation/l10n/suggestions_localizations_en.dart'; import 'package:suggest_a_feature/src/presentation/l10n/suggestions_localizations_ru.dart'; import 'package:suggest_a_feature/src/presentation/l10n/suggestions_localizations_uk.dart'; @@ -402,6 +401,24 @@ abstract class SuggestionsLocalizations { /// In en, this message translates to: /// **'You can attach up to 10 photos 🖼️'** String get eventPhotosRestriction; + + /// No description provided for @sortBy. + /// + /// In en, this message translates to: + /// **'Sort by'** + String get sortBy; + + /// No description provided for @numberOfLikes. + /// + /// In en, this message translates to: + /// **'Number of likes'** + String get numberOfLikes; + + /// No description provided for @creationDate. + /// + /// In en, this message translates to: + /// **'Creation date'** + String get creationDate; } class _SuggestionsLocalizationsDelegate diff --git a/lib/src/presentation/l10n/suggestions_localizations_en.dart b/lib/src/presentation/l10n/suggestions_localizations_en.dart index b85b2eb..eda0273 100644 --- a/lib/src/presentation/l10n/suggestions_localizations_en.dart +++ b/lib/src/presentation/l10n/suggestions_localizations_en.dart @@ -46,10 +46,12 @@ class SuggestionsLocalizationsEn extends SuggestionsLocalizations { String get delete => 'Delete suggestion'; @override - String get deletionQuestion => 'Are you sure you want to delete the suggestion?'; + String get deletionQuestion => + 'Are you sure you want to delete the suggestion?'; @override - String get deletionPhotoQuestion => 'Are you sure you want to delete this photo?'; + String get deletionPhotoQuestion => + 'Are you sure you want to delete this photo?'; @override String get title => 'Briefly describe your suggestion'; @@ -155,4 +157,13 @@ class SuggestionsLocalizationsEn extends SuggestionsLocalizations { @override String get eventPhotosRestriction => 'You can attach up to 10 photos 🖼️'; + + @override + String get sortBy => 'Sort by'; + + @override + String get numberOfLikes => 'Number of likes'; + + @override + String get creationDate => 'Creation date'; } diff --git a/lib/src/presentation/l10n/suggestions_localizations_ru.dart b/lib/src/presentation/l10n/suggestions_localizations_ru.dart index 3105dc8..5444acd 100644 --- a/lib/src/presentation/l10n/suggestions_localizations_ru.dart +++ b/lib/src/presentation/l10n/suggestions_localizations_ru.dart @@ -46,10 +46,12 @@ class SuggestionsLocalizationsRu extends SuggestionsLocalizations { String get delete => 'Удалить предложение'; @override - String get deletionQuestion => 'Вы действительно хотите удалить это предложение?'; + String get deletionQuestion => + 'Вы действительно хотите удалить это предложение?'; @override - String get deletionPhotoQuestion => 'Вы действительно хотите удалить это фото?'; + String get deletionPhotoQuestion => + 'Вы действительно хотите удалить это фото?'; @override String get title => 'Кратко опишите ваше предложение'; @@ -155,4 +157,13 @@ class SuggestionsLocalizationsRu extends SuggestionsLocalizations { @override String get eventPhotosRestriction => 'Вы можете прикрепить до 10 фото 🖼️'; + + @override + String get sortBy => 'Сортировать по'; + + @override + String get numberOfLikes => 'Количеству лайков'; + + @override + String get creationDate => 'Дате создания'; } diff --git a/lib/src/presentation/l10n/suggestions_localizations_uk.dart b/lib/src/presentation/l10n/suggestions_localizations_uk.dart index 1dc502c..0d859e2 100644 --- a/lib/src/presentation/l10n/suggestions_localizations_uk.dart +++ b/lib/src/presentation/l10n/suggestions_localizations_uk.dart @@ -157,4 +157,13 @@ class SuggestionsLocalizationsUk extends SuggestionsLocalizations { @override String get eventPhotosRestriction => 'Ви можете прикріпити до 10 фото 🖼️'; + + @override + String get sortBy => 'Сортувати за'; + + @override + String get numberOfLikes => 'Кількістю лайків'; + + @override + String get creationDate => 'Датою створення'; } diff --git a/lib/src/presentation/pages/suggestions/suggestions_cubit.dart b/lib/src/presentation/pages/suggestions/suggestions_cubit.dart index ea85bac..8764f48 100644 --- a/lib/src/presentation/pages/suggestions/suggestions_cubit.dart +++ b/lib/src/presentation/pages/suggestions/suggestions_cubit.dart @@ -1,4 +1,5 @@ import 'dart:async'; + import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:suggest_a_feature/src/domain/data_interfaces/suggestion_repository.dart'; import 'package:suggest_a_feature/src/domain/entities/suggestion.dart'; @@ -18,7 +19,7 @@ class SuggestionsCubit extends Cubit { completed: [], declined: [], duplicated: [], - isCreateBottomSheetOpened: false, + sortType: SortType.likes, ), ) { _init(); @@ -40,7 +41,7 @@ class SuggestionsCubit extends Cubit { } Future _onNewSuggestions(List suggestions) async { - suggestions.sort((a, b) => b.upvotesCount.compareTo(a.upvotesCount)); + suggestions.sort(state.sortType.sortFunction); emit( state.newState( @@ -87,12 +88,47 @@ class SuggestionsCubit extends Cubit { : _suggestionRepository.upvote(suggestion.id); } - void openCreateBottomSheet() => - emit(state.newState(isCreateBottomSheetOpened: true)); + void openCreateBottomSheet() => emit( + CreateState( + requests: state.requests, + inProgress: state.inProgress, + completed: state.completed, + declined: state.declined, + duplicated: state.duplicated, + sortType: state.sortType, + activeTab: state.activeTab, + ), + ); - void closeCreateBottomSheet() => - emit(state.newState(isCreateBottomSheetOpened: false)); + void closeBottomSheet() => emit( + SuggestionsState( + requests: state.requests, + inProgress: state.inProgress, + completed: state.completed, + declined: state.declined, + duplicated: state.duplicated, + sortType: state.sortType, + ), + ); void changeActiveTab(SuggestionStatus activeTab) => emit(state.newState(activeTab: activeTab)); + + void openSortingBottomSheet() => emit( + SortingState( + requests: state.requests, + inProgress: state.inProgress, + completed: state.completed, + declined: state.declined, + duplicated: state.duplicated, + sortType: state.sortType, + ), + ); + + void onSortTypeChanged(SortType sortType) { + if (sortType != state.sortType) { + emit(state.newState(sortType: sortType)); + _onNewSuggestions(_suggestionRepository.suggestions); + } + } } diff --git a/lib/src/presentation/pages/suggestions/suggestions_page.dart b/lib/src/presentation/pages/suggestions/suggestions_page.dart index 499eef8..5ee5262 100644 --- a/lib/src/presentation/pages/suggestions/suggestions_page.dart +++ b/lib/src/presentation/pages/suggestions/suggestions_page.dart @@ -12,6 +12,7 @@ import 'package:suggest_a_feature/src/presentation/pages/suggestions/widgets/sug import 'package:suggest_a_feature/src/presentation/pages/suggestions/widgets/suggestions_tab_bar.dart'; import 'package:suggest_a_feature/src/presentation/pages/theme/suggestions_theme.dart'; import 'package:suggest_a_feature/src/presentation/pages/widgets/appbar_widget.dart'; +import 'package:suggest_a_feature/src/presentation/pages/widgets/bottom_sheets/sorting_bottom_sheet.dart'; import 'package:suggest_a_feature/src/presentation/pages/widgets/fab.dart'; import 'package:suggest_a_feature/src/presentation/utils/assets_strings.dart'; import 'package:suggest_a_feature/src/presentation/utils/context_utils.dart'; @@ -89,9 +90,9 @@ class _SuggestionsPageState extends State { return SuggestionsCubitScope( child: BlocBuilder( buildWhen: (previous, current) => - previous.isCreateBottomSheetOpened != - current.isCreateBottomSheetOpened || - previous.activeTab != current.activeTab, + previous.type != current.type || + previous.activeTab != current.activeTab || + previous.sortType != current.sortType, builder: (context, state) { final cubit = context.read(); return Stack( @@ -123,12 +124,18 @@ class _SuggestionsPageState extends State { ], ), ), - if (state.isCreateBottomSheetOpened) + if (state is CreateState) _BottomSheet( onSaveToGallery: widget.onSaveToGallery, onUploadMultiplePhotos: widget.onUploadMultiplePhotos, - onCloseBottomSheet: cubit.closeCreateBottomSheet, + onCloseBottomSheet: cubit.closeBottomSheet, ), + if (state is SortingState) + SortingBottomSheet( + closeBottomSheet: cubit.closeBottomSheet, + value: state.sortType, + onChanged: cubit.onSortTypeChanged, + ) ], ); }, @@ -194,6 +201,8 @@ class _MainContentState extends State<_MainContent> userId: widget.userId, onVote: context.read().vote, tabController: _tabController, + openSortingBottomSheet: + context.read().openSortingBottomSheet, ), ], ), @@ -242,12 +251,14 @@ class _TabBarView extends StatelessWidget { final OnUploadMultiplePhotosCallback? onUploadMultiplePhotos; final void Function(SuggestionStatus status, int i) onVote; final String userId; + final VoidCallback openSortingBottomSheet; const _TabBarView({ required this.tabController, required this.onGetUserById, required this.userId, required this.onVote, + required this.openSortingBottomSheet, this.onSaveToGallery, this.onUploadMultiplePhotos, }); @@ -275,6 +286,7 @@ class _TabBarView extends StatelessWidget { onUploadMultiplePhotos: onUploadMultiplePhotos, userId: userId, vote: (i) => onVote(SuggestionStatus.requests, i), + openSortingBottomSheet: openSortingBottomSheet, ), SuggestionList( status: SuggestionStatus.inProgress, @@ -285,6 +297,7 @@ class _TabBarView extends StatelessWidget { onUploadMultiplePhotos: onUploadMultiplePhotos, userId: userId, vote: (i) => onVote(SuggestionStatus.inProgress, i), + openSortingBottomSheet: openSortingBottomSheet, ), SuggestionList( status: SuggestionStatus.completed, @@ -295,6 +308,7 @@ class _TabBarView extends StatelessWidget { onUploadMultiplePhotos: onUploadMultiplePhotos, userId: userId, vote: (i) => onVote(SuggestionStatus.completed, i), + openSortingBottomSheet: openSortingBottomSheet, ), SuggestionList( status: SuggestionStatus.declined, @@ -305,6 +319,7 @@ class _TabBarView extends StatelessWidget { onUploadMultiplePhotos: onUploadMultiplePhotos, userId: userId, vote: (i) => onVote(SuggestionStatus.declined, i), + openSortingBottomSheet: openSortingBottomSheet, ), SuggestionList( status: SuggestionStatus.duplicated, @@ -315,6 +330,7 @@ class _TabBarView extends StatelessWidget { onUploadMultiplePhotos: onUploadMultiplePhotos, userId: userId, vote: (i) => onVote(SuggestionStatus.duplicated, i), + openSortingBottomSheet: openSortingBottomSheet, ), ], ), diff --git a/lib/src/presentation/pages/suggestions/suggestions_state.dart b/lib/src/presentation/pages/suggestions/suggestions_state.dart index 0d4c2d2..62c6905 100644 --- a/lib/src/presentation/pages/suggestions/suggestions_state.dart +++ b/lib/src/presentation/pages/suggestions/suggestions_state.dart @@ -8,7 +8,7 @@ class SuggestionsState extends Equatable { final List declined; final List duplicated; final SuggestionStatus activeTab; - final bool isCreateBottomSheetOpened; + final SortType sortType; const SuggestionsState({ required this.requests, @@ -16,7 +16,7 @@ class SuggestionsState extends Equatable { required this.completed, required this.declined, required this.duplicated, - required this.isCreateBottomSheetOpened, + required this.sortType, this.activeTab = SuggestionStatus.requests, }); @@ -27,7 +27,7 @@ class SuggestionsState extends Equatable { List? declined, List? duplicated, SuggestionStatus? activeTab, - bool? isCreateBottomSheetOpened, + SortType? sortType, }) { return SuggestionsState( requests: requests ?? this.requests, @@ -36,8 +36,7 @@ class SuggestionsState extends Equatable { declined: declined ?? this.declined, duplicated: duplicated ?? this.duplicated, activeTab: activeTab ?? this.activeTab, - isCreateBottomSheetOpened: - isCreateBottomSheetOpened ?? this.isCreateBottomSheetOpened, + sortType: sortType ?? this.sortType, ); } @@ -49,6 +48,91 @@ class SuggestionsState extends Equatable { declined, duplicated, activeTab, - isCreateBottomSheetOpened, + sortType, ]; } + +class CreateState extends SuggestionsState { + const CreateState({ + required super.requests, + required super.inProgress, + required super.completed, + required super.declined, + required super.duplicated, + required super.sortType, + super.activeTab, + }); + + @override + CreateState newState({ + List? requests, + List? inProgress, + List? completed, + List? declined, + List? duplicated, + SuggestionStatus? activeTab, + SortType? sortType, + }) { + return CreateState( + requests: requests ?? this.requests, + inProgress: inProgress ?? this.inProgress, + completed: completed ?? this.completed, + declined: declined ?? this.declined, + duplicated: duplicated ?? this.duplicated, + activeTab: activeTab ?? this.activeTab, + sortType: sortType ?? this.sortType, + ); + } +} + +class SortingState extends SuggestionsState { + const SortingState({ + required super.requests, + required super.inProgress, + required super.completed, + required super.declined, + required super.duplicated, + required super.sortType, + super.activeTab, + }); + + @override + SortingState newState({ + List? requests, + List? inProgress, + List? completed, + List? declined, + List? duplicated, + SuggestionStatus? activeTab, + SortType? sortType, + }) { + return SortingState( + requests: requests ?? this.requests, + inProgress: inProgress ?? this.inProgress, + completed: completed ?? this.completed, + declined: declined ?? this.declined, + duplicated: duplicated ?? this.duplicated, + activeTab: activeTab ?? this.activeTab, + sortType: sortType ?? this.sortType, + ); + } +} + +enum SortType { likes, date } + +extension SortTypeExtension on SortType { + Comparator get sortFunction { + return switch (this) { + SortType.likes => (a, b) => b.upvotesCount.compareTo(a.upvotesCount), + SortType.date => (a, b) => b.creationTime.compareTo(a.creationTime), + }; + } +} + +extension SuggestionsStateType on SuggestionsState { + Type get type { + if (this is SortingState) return SortingState; + if (this is CreateState) return CreateState; + return SuggestionsState; + } +} diff --git a/lib/src/presentation/pages/suggestions/widgets/list_description.dart b/lib/src/presentation/pages/suggestions/widgets/list_description.dart index 5c0b6c8..9811319 100644 --- a/lib/src/presentation/pages/suggestions/widgets/list_description.dart +++ b/lib/src/presentation/pages/suggestions/widgets/list_description.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:suggest_a_feature/src/presentation/utils/assets_strings.dart'; import 'package:suggest_a_feature/src/presentation/utils/context_utils.dart'; import 'package:suggest_a_feature/src/presentation/utils/dimensions.dart'; import 'package:suggest_a_feature/suggest_a_feature.dart'; @@ -6,10 +8,12 @@ import 'package:suggest_a_feature/suggest_a_feature.dart'; class ListDescription extends StatelessWidget { final SuggestionStatus status; final int length; + final VoidCallback openSortingBottomSheet; const ListDescription({ required this.status, required this.length, + required this.openSortingBottomSheet, super.key, }); @@ -58,14 +62,52 @@ class ListDescription extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: Dimensions.margin2x), child: Column( children: [ - RichText( - textAlign: TextAlign.center, - text: TextSpan( - children: [ - TextSpan(text: header, style: theme.textMediumBold), - TextSpan(text: ' ($length)', style: theme.textSmallPlus), - ], - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + RichText( + textAlign: TextAlign.center, + text: TextSpan( + children: [ + TextSpan(text: header, style: theme.textMediumBold), + TextSpan(text: ' ($length)', style: theme.textSmallPlus), + ], + ), + ), + GestureDetector( + onTap: openSortingBottomSheet, + behavior: HitTestBehavior.translucent, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 6, + horizontal: Dimensions.marginSmall, + ), + decoration: BoxDecoration( + color: theme.secondaryBackgroundColor, + borderRadius: const BorderRadius.all( + Radius.circular(Dimensions.middleCircularRadius), + ), + ), + child: Row( + children: [ + Text( + context.localization.sortBy, + style: theme.textSmallPlus + .copyWith(color: theme.enabledTextColor), + ), + SvgPicture.asset( + AssetStrings.arrowDownIcon, + package: AssetStrings.packageName, + colorFilter: ColorFilter.mode( + theme.barIndicatorColor, + BlendMode.srcIn, + ), + ), + ], + ), + ), + ), + ], ), const SizedBox(height: Dimensions.marginSmall), Text( diff --git a/lib/src/presentation/pages/suggestions/widgets/suggestion_list.dart b/lib/src/presentation/pages/suggestions/widgets/suggestion_list.dart index 6e1b680..4aee662 100644 --- a/lib/src/presentation/pages/suggestions/widgets/suggestion_list.dart +++ b/lib/src/presentation/pages/suggestions/widgets/suggestion_list.dart @@ -14,6 +14,7 @@ class SuggestionList extends StatelessWidget { final OnGetUserById onGetUserById; final String userId; final ValueChanged vote; + final VoidCallback openSortingBottomSheet; const SuggestionList({ required this.status, @@ -22,6 +23,7 @@ class SuggestionList extends StatelessWidget { required this.onGetUserById, required this.userId, required this.vote, + required this.openSortingBottomSheet, this.onUploadMultiplePhotos, this.onSaveToGallery, super.key, @@ -38,7 +40,11 @@ class SuggestionList extends StatelessWidget { ), itemBuilder: (_, index) { return index == 0 - ? ListDescription(status: status, length: suggestions.length) + ? ListDescription( + status: status, + length: suggestions.length, + openSortingBottomSheet: openSortingBottomSheet, + ) : _ListItem( index: index, suggestions: suggestions, diff --git a/lib/src/presentation/pages/widgets/bottom_sheets/label_bottom_sheet.dart b/lib/src/presentation/pages/widgets/bottom_sheets/label_bottom_sheet.dart index 8218e84..57b7ab4 100644 --- a/lib/src/presentation/pages/widgets/bottom_sheets/label_bottom_sheet.dart +++ b/lib/src/presentation/pages/widgets/bottom_sheets/label_bottom_sheet.dart @@ -4,6 +4,7 @@ import 'package:suggest_a_feature/src/presentation/pages/theme/suggestions_theme import 'package:suggest_a_feature/src/presentation/pages/widgets/bottom_sheets/base_bottom_sheet.dart'; import 'package:suggest_a_feature/src/presentation/pages/widgets/bottom_sheets/bottom_sheet_actions.dart'; import 'package:suggest_a_feature/src/presentation/pages/widgets/suggestions_labels.dart'; +import 'package:suggest_a_feature/src/presentation/pages/widgets/suggestions_radio_button.dart'; import 'package:suggest_a_feature/src/presentation/utils/context_utils.dart'; import 'package:suggest_a_feature/src/presentation/utils/dimensions.dart'; import 'package:wtf_sliding_sheet/wtf_sliding_sheet.dart'; @@ -160,31 +161,9 @@ class _LabelItem extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SuggestionLabels(labels: [label]), - GestureDetector( + SuggestionsRadioButton( + selected: selectedLabels.contains(label), onTap: () => onTap(label), - child: SizedBox( - height: Dimensions.defaultSize, - width: Dimensions.defaultSize, - child: DecoratedBox( - decoration: BoxDecoration( - border: Border.all( - color: theme.primaryIconColor, - width: 0.5, - ), - color: selectedLabels.contains(label) - ? theme.primaryIconColor - : theme.thirdBackgroundColor, - shape: BoxShape.circle, - ), - child: selectedLabels.contains(label) - ? Icon( - Icons.check, - size: Dimensions.smallSize, - color: theme.primaryBackgroundColor, - ) - : null, - ), - ), ), ], ); diff --git a/lib/src/presentation/pages/widgets/bottom_sheets/sorting_bottom_sheet.dart b/lib/src/presentation/pages/widgets/bottom_sheets/sorting_bottom_sheet.dart new file mode 100644 index 0000000..c7819c1 --- /dev/null +++ b/lib/src/presentation/pages/widgets/bottom_sheets/sorting_bottom_sheet.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:suggest_a_feature/src/presentation/pages/suggestions/suggestions_state.dart'; +import 'package:suggest_a_feature/src/presentation/pages/widgets/bottom_sheets/base_bottom_sheet.dart'; +import 'package:suggest_a_feature/src/presentation/pages/widgets/suggestions_radio_button.dart'; +import 'package:suggest_a_feature/src/presentation/utils/context_utils.dart'; +import 'package:suggest_a_feature/src/presentation/utils/dimensions.dart'; +import 'package:suggest_a_feature/src/presentation/utils/font_sizes.dart'; +import 'package:suggest_a_feature/suggest_a_feature.dart'; +import 'package:wtf_sliding_sheet/wtf_sliding_sheet.dart'; + +class SortingBottomSheet extends StatefulWidget { + final VoidCallback closeBottomSheet; + final ValueChanged onChanged; + final SortType value; + + const SortingBottomSheet({ + required this.closeBottomSheet, + required this.value, + required this.onChanged, + super.key, + }); + + @override + State createState() => _SortingBottomSheetState(); +} + +class _SortingBottomSheetState extends State { + final SheetController _controller = SheetController(); + + @override + Widget build(BuildContext context) { + return BaseBottomSheet( + controller: _controller, + onClose: ([_]) => _onClose(), + backgroundColor: theme.bottomSheetBackgroundColor, + previousNavBarColor: theme.primaryBackgroundColor, + previousStatusBarColor: theme.primaryBackgroundColor, + title: context.localization.sortBy, + contentBuilder: (context, _) { + return Column( + children: [ + _SortRow( + title: context.localization.numberOfLikes, + value: SortType.likes, + selected: widget.value == SortType.likes, + onChanged: widget.onChanged, + ), + const SizedBox(height: Dimensions.marginSmall), + _SortRow( + title: context.localization.creationDate, + value: SortType.date, + onChanged: widget.onChanged, + selected: widget.value == SortType.date, + ), + ], + ); + }, + ); + } + + Future _onClose() async { + await _controller.collapse(); + widget.closeBottomSheet(); + } +} + +class _SortRow extends StatelessWidget { + final String title; + final SortType value; + final ValueChanged onChanged; + final bool selected; + + const _SortRow({ + required this.title, + required this.value, + required this.onChanged, + required this.selected, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + vertical: Dimensions.marginSmall, + horizontal: Dimensions.marginDefault, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: theme.textMedium.copyWith(fontWeight: FontSizes.weightBold), + ), + SuggestionsRadioButton( + selected: selected, + onTap: () => onChanged(value), + ), + ], + ), + ); + } +} diff --git a/lib/src/presentation/pages/widgets/suggestions_radio_button.dart b/lib/src/presentation/pages/widgets/suggestions_radio_button.dart new file mode 100644 index 0000000..65e63f0 --- /dev/null +++ b/lib/src/presentation/pages/widgets/suggestions_radio_button.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:suggest_a_feature/src/presentation/utils/dimensions.dart'; +import 'package:suggest_a_feature/suggest_a_feature.dart'; + +class SuggestionsRadioButton extends StatelessWidget { + final bool selected; + final VoidCallback onTap; + const SuggestionsRadioButton({ + required this.selected, + required this.onTap, + super.key, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: SizedBox( + height: Dimensions.defaultSize, + width: Dimensions.defaultSize, + child: DecoratedBox( + decoration: BoxDecoration( + border: Border.all( + color: theme.primaryIconColor, + width: 0.5, + ), + color: + selected ? theme.primaryIconColor : theme.thirdBackgroundColor, + shape: BoxShape.circle, + ), + child: selected + ? Icon( + Icons.check, + size: Dimensions.smallSize, + color: theme.primaryBackgroundColor, + ) + : null, + ), + ), + ); + } +} diff --git a/lib/src/presentation/utils/assets_strings.dart b/lib/src/presentation/utils/assets_strings.dart index e71ca9a..bea6f50 100644 --- a/lib/src/presentation/utils/assets_strings.dart +++ b/lib/src/presentation/utils/assets_strings.dart @@ -1,6 +1,7 @@ class AssetStrings { static const String packageName = 'suggest_a_feature'; + static const String arrowDownIcon = 'assets/arrow_down_icon.svg'; static const String addPhotoButton = 'assets/add_photo_icon.svg'; static const String backIconImage = 'assets/arrow_left_icon.svg'; static const String checkIconImage = 'assets/check_icon.svg'; @@ -18,6 +19,7 @@ class AssetStrings { 'assets/suggestions_in_progress.svg'; static const String suggestionsCompleted = 'assets/suggestions_completed.svg'; static const String suggestionsDeclined = 'assets/suggestions_declined.svg'; - static const String suggestionsDuplicated = 'assets/suggestions_duplicated.svg'; + static const String suggestionsDuplicated = + 'assets/suggestions_duplicated.svg'; static const String suggestionsUpvoteArrow = 'assets/arrow_up_suggestion.svg'; } diff --git a/test/presentation/cubits/suggestions_cubit_test.dart b/test/presentation/cubits/suggestions_cubit_test.dart index 454a67b..d4186a6 100644 --- a/test/presentation/cubits/suggestions_cubit_test.dart +++ b/test/presentation/cubits/suggestions_cubit_test.dart @@ -23,7 +23,7 @@ void main() { completed: [mockedCompletedSuggestion, mockedCompletedSuggestion2], declined: const [], duplicated: const [], - isCreateBottomSheetOpened: false, + sortType: SortType.likes, ); final mockedSuggestions = [ mockedRequestSuggestion, @@ -58,8 +58,14 @@ void main() { seed: () => emptySuggestionsState, act: (cubit) => cubit.openCreateBottomSheet(), expect: () => [ - emptySuggestionsState.newState( - isCreateBottomSheetOpened: true, + CreateState( + requests: emptySuggestionsState.requests, + inProgress: emptySuggestionsState.inProgress, + completed: emptySuggestionsState.completed, + declined: emptySuggestionsState.declined, + duplicated: emptySuggestionsState.duplicated, + sortType: emptySuggestionsState.sortType, + activeTab: emptySuggestionsState.activeTab, ), ], ); @@ -74,10 +80,16 @@ void main() { mockSuggestionRepository, ); }, - seed: () => emptySuggestionsState.newState( - isCreateBottomSheetOpened: true, + seed: () => CreateState( + requests: emptySuggestionsState.requests, + inProgress: emptySuggestionsState.inProgress, + completed: emptySuggestionsState.completed, + declined: emptySuggestionsState.declined, + duplicated: emptySuggestionsState.duplicated, + sortType: emptySuggestionsState.sortType, + activeTab: emptySuggestionsState.activeTab, ), - act: (cubit) => cubit.closeCreateBottomSheet(), + act: (cubit) => cubit.closeBottomSheet(), expect: () => [ emptySuggestionsState, ], @@ -132,7 +144,7 @@ void main() { completed: const [], declined: const [], duplicated: const [], - isCreateBottomSheetOpened: false, + sortType: SortType.likes, ), act: (cubit) { cubit.vote(SuggestionStatus.requests, 1); @@ -144,7 +156,7 @@ void main() { completed: const [], declined: const [], duplicated: const [], - isCreateBottomSheetOpened: false, + sortType: SortType.likes, ), ], );