diff --git a/lib/application/artifacts/artifacts_bloc.dart b/lib/application/artifacts/artifacts_bloc.dart index b517a7e5b..db1276cb6 100644 --- a/lib/application/artifacts/artifacts_bloc.dart +++ b/lib/application/artifacts/artifacts_bloc.dart @@ -44,6 +44,7 @@ class ArtifactsBloc extends Bloc { tempSortDirectionType: currentState.sortDirectionType, ), collapseNotes: (e) => currentState.copyWith.call(collapseNotes: e.collapse), + resetFilters: (_) => _buildInitialState(excludeKeys: state.maybeMap(loaded: (state) => state.excludeKeys, orElse: () => [])), ); yield s; diff --git a/lib/application/artifacts/artifacts_event.dart b/lib/application/artifacts/artifacts_event.dart index 73b149045..7b596004e 100644 --- a/lib/application/artifacts/artifacts_event.dart +++ b/lib/application/artifacts/artifacts_event.dart @@ -19,4 +19,6 @@ class ArtifactsEvent with _$ArtifactsEvent { const factory ArtifactsEvent.applyFilterChanges() = _ApplyFilterChanges; const factory ArtifactsEvent.cancelChanges() = _CancelChanges; + + const factory ArtifactsEvent.resetFilters() = _ResetFilters; } diff --git a/lib/application/weapons/weapons_bloc.dart b/lib/application/weapons/weapons_bloc.dart index 47c3ebe35..1c45e05fe 100644 --- a/lib/application/weapons/weapons_bloc.dart +++ b/lib/application/weapons/weapons_bloc.dart @@ -68,6 +68,10 @@ class WeaponsBloc extends Bloc { tempWeaponLocationType: currentState.weaponLocationType, excludeKeys: currentState.excludeKeys, ), + resetFilters: (_) => _buildInitialState( + excludeKeys: state.maybeMap(loaded: (state) => state.excludeKeys, orElse: () => []), + weaponTypes: WeaponType.values, + ), ); yield s; diff --git a/lib/application/weapons/weapons_event.dart b/lib/application/weapons/weapons_event.dart index b903a9da5..c51c9148c 100644 --- a/lib/application/weapons/weapons_event.dart +++ b/lib/application/weapons/weapons_event.dart @@ -25,4 +25,6 @@ class WeaponsEvent with _$WeaponsEvent { const factory WeaponsEvent.weaponLocationTypeChanged(ItemLocationType locationType) = _WeaponLocationTypeChanged; const factory WeaponsEvent.cancelChanges() = _CancelChanges; + + const factory WeaponsEvent.resetFilters() = _ResetFilters; } diff --git a/lib/presentation/artifacts/widgets/artifact_bottom_sheet.dart b/lib/presentation/artifacts/widgets/artifact_bottom_sheet.dart index d61cfbc5f..f3f8f5baa 100644 --- a/lib/presentation/artifacts/widgets/artifact_bottom_sheet.dart +++ b/lib/presentation/artifacts/widgets/artifact_bottom_sheet.dart @@ -10,22 +10,18 @@ import 'package:genshindb/presentation/shared/item_popupmenu_filter.dart'; import 'package:genshindb/presentation/shared/loading.dart'; import 'package:genshindb/presentation/shared/rarity_rating.dart'; import 'package:genshindb/presentation/shared/sort_direction_popupmenu_filter.dart'; +import 'package:genshindb/presentation/shared/styles.dart'; class ArtifactBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { final s = S.of(context); + final theme = Theme.of(context); return CommonBottomSheet( titleIcon: GenshinDb.filter, title: s.filters, - onOk: () { - context.read().add(const ArtifactsEvent.applyFilterChanges()); - Navigator.pop(context); - }, - onCancel: () { - context.read().add(const ArtifactsEvent.cancelChanges()); - Navigator.pop(context); - }, + showCancelButton: false, + showOkButton: false, child: BlocBuilder( builder: (context, state) => state.map( loading: (_) => const Loading(), @@ -55,6 +51,32 @@ class ArtifactBottomSheet extends StatelessWidget { ) ], ), + ButtonBar( + buttonPadding: Styles.edgeInsetHorizontal10, + children: [ + OutlinedButton( + onPressed: () { + context.read().add(const ArtifactsEvent.cancelChanges()); + Navigator.pop(context); + }, + child: Text(s.cancel, style: TextStyle(color: theme.primaryColor)), + ), + OutlinedButton( + onPressed: () { + context.read().add(const ArtifactsEvent.resetFilters()); + Navigator.pop(context); + }, + child: Text(s.reset, style: TextStyle(color: theme.primaryColor)), + ), + ElevatedButton( + onPressed: () { + context.read().add(const ArtifactsEvent.applyFilterChanges()); + Navigator.pop(context); + }, + child: Text(s.ok), + ) + ], + ), ], ), ), diff --git a/lib/presentation/characters/widgets/character_bottom_sheet.dart b/lib/presentation/characters/widgets/character_bottom_sheet.dart index 705f88368..bf9e18e46 100644 --- a/lib/presentation/characters/widgets/character_bottom_sheet.dart +++ b/lib/presentation/characters/widgets/character_bottom_sheet.dart @@ -11,6 +11,7 @@ import 'package:genshindb/presentation/shared/item_popupmenu_filter.dart'; import 'package:genshindb/presentation/shared/loading.dart'; import 'package:genshindb/presentation/shared/rarity_rating.dart'; import 'package:genshindb/presentation/shared/sort_direction_popupmenu_filter.dart'; +import 'package:genshindb/presentation/shared/styles.dart'; import 'package:genshindb/presentation/shared/weapons_button_bar.dart'; class CharacterBottomSheet extends StatelessWidget { @@ -19,20 +20,15 @@ class CharacterBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { final s = S.of(context); + final theme = Theme.of(context); return CommonBottomSheet( titleIcon: GenshinDb.filter, title: s.filters, - onOk: () { - context.read().add(const CharactersEvent.applyFilterChanges()); - Navigator.pop(context); - }, - onCancel: () { - context.read().add(const CharactersEvent.cancelChanges()); - Navigator.pop(context); - }, + showCancelButton: false, + showOkButton: false, child: BlocBuilder( builder: (context, state) => state.map( - loading: (_) => const Loading(), + loading: (_) => const Loading(useScaffold: false), loaded: (state) => Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.spaceEvenly, @@ -56,6 +52,14 @@ class CharacterBottomSheet extends StatelessWidget { ButtonBar( alignment: MainAxisAlignment.spaceEvenly, children: [ + ItemPopupMenuFilter( + tooltipText: '${s.released} / ${s.brandNew} / ${s.comingSoon}', + values: ItemStatusType.values, + selectedValue: state.tempStatusType, + onSelected: (v) => context.read().add(CharactersEvent.itemStatusChanged(v)), + icon: const Icon(GenshinDb.sliders_h, size: 18), + itemText: (val) => s.translateReleasedUnreleasedType(val), + ), ItemPopupMenuFilter( tooltipText: s.role, values: CharacterRoleType.values.where((el) => el != CharacterRoleType.na).toList(), @@ -64,13 +68,14 @@ class CharacterBottomSheet extends StatelessWidget { itemText: (val) => s.translateCharacterType(val), icon: const Icon(GenshinDb.trefoil_lily, size: 18), ), - ItemPopupMenuFilter( - tooltipText: '${s.released} / ${s.brandNew} / ${s.comingSoon}', - values: ItemStatusType.values, - selectedValue: state.tempStatusType, - onSelected: (v) => context.read().add(CharactersEvent.itemStatusChanged(v)), - icon: const Icon(GenshinDb.sliders_h, size: 18), - itemText: (val) => s.translateReleasedUnreleasedType(val), + ItemPopupMenuFilterWithAllValue( + tooltipText: s.region, + values: RegionType.values.map((e) => e.index).toList(), + selectedValue: state.tempRegionType?.index, + onAllOrValueSelected: (v) => + context.read().add(CharactersEvent.regionTypeChanged(v == null ? null : RegionType.values[v])), + itemText: (val) => s.translateRegionType(RegionType.values[val]), + icon: const Icon(GenshinDb.reactor, size: 18), ), ItemPopupMenuFilter( tooltipText: s.sortBy, @@ -85,6 +90,32 @@ class CharacterBottomSheet extends StatelessWidget { ) ], ), + ButtonBar( + buttonPadding: Styles.edgeInsetHorizontal10, + children: [ + OutlinedButton( + onPressed: () { + context.read().add(const CharactersEvent.cancelChanges()); + Navigator.pop(context); + }, + child: Text(s.cancel, style: TextStyle(color: theme.primaryColor)), + ), + OutlinedButton( + onPressed: () { + context.read().add(const CharactersEvent.resetFilters()); + Navigator.pop(context); + }, + child: Text(s.reset, style: TextStyle(color: theme.primaryColor)), + ), + ElevatedButton( + onPressed: () { + context.read().add(const CharactersEvent.applyFilterChanges()); + Navigator.pop(context); + }, + child: Text(s.ok), + ) + ], + ), ], ), ), diff --git a/lib/presentation/shared/item_popupmenu_filter.dart b/lib/presentation/shared/item_popupmenu_filter.dart index ae3a18ba7..32a565e9a 100644 --- a/lib/presentation/shared/item_popupmenu_filter.dart +++ b/lib/presentation/shared/item_popupmenu_filter.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:genshindb/generated/l10n.dart'; import 'utils/enum_utils.dart'; @@ -7,7 +8,7 @@ typedef PopupMenuItemText = String Function(T value); class ItemPopupMenuFilter extends StatelessWidget { final String tooltipText; final TEnum selectedValue; - final Function(TEnum) onSelected; + final Function(TEnum)? onSelected; final List values; final List exclude; final Icon icon; @@ -18,7 +19,7 @@ class ItemPopupMenuFilter extends StatelessWidget { required this.tooltipText, required this.selectedValue, required this.values, - required this.onSelected, + this.onSelected, required this.itemText, this.exclude = const [], this.icon = const Icon(Icons.filter_list), @@ -26,23 +27,76 @@ class ItemPopupMenuFilter extends StatelessWidget { @override Widget build(BuildContext context) { - final translatedValues = EnumUtils.getTranslatedAndSortedEnum(values, itemText, exclude: exclude); + final translatedValues = getTranslatedValues(context); + final valuesToUse = getValuesToUse(translatedValues); + return PopupMenuButton( + padding: const EdgeInsets.all(0), + initialValue: selectedValue, + icon: icon, + onSelected: handleItemSelected, + itemBuilder: (context) => valuesToUse, + tooltip: tooltipText, + ); + } - final valuesToUse = translatedValues + List> getTranslatedValues(BuildContext context) { + return EnumUtils.getTranslatedAndSortedEnum(values, itemText, exclude: exclude); + } + + List> getValuesToUse(List> translatedValues) { + return translatedValues .map((e) => CheckedPopupMenuItem( checked: selectedValue == e.enumValue, value: e.enumValue, child: Text(e.translation), )) .toList(); + } - return PopupMenuButton( - padding: const EdgeInsets.all(0), - initialValue: selectedValue, - icon: icon, - onSelected: onSelected, - itemBuilder: (context) => valuesToUse, - tooltip: tooltipText, - ); + void handleItemSelected(TEnum value) { + if (onSelected != null) { + onSelected!(value); + } + } +} + +class ItemPopupMenuFilterWithAllValue extends ItemPopupMenuFilter { + static int allValue = -1; + + final Function(int?)? onAllOrValueSelected; + + ItemPopupMenuFilterWithAllValue({ + Key? key, + required String tooltipText, + int? selectedValue, + this.onAllOrValueSelected, + required List values, + required PopupMenuItemText itemText, + List exclude = const [], + Icon icon = const Icon(Icons.filter_list), + }) : super( + key: key, + tooltipText: tooltipText, + selectedValue: selectedValue ?? allValue, + itemText: itemText, + exclude: exclude, + icon: icon, + values: values..add(allValue), + ); + + @override + List> getTranslatedValues(BuildContext context) { + final s = S.of(context); + return EnumUtils.getTranslatedAndSortedEnumWithAllValue(allValue, s.all, values, itemText, exclude: exclude); + } + + @override + void handleItemSelected(int value) { + if (onAllOrValueSelected == null) { + return; + } + + final valueToUse = value == allValue ? null : value; + onAllOrValueSelected!(valueToUse); } } diff --git a/lib/presentation/shared/styles.dart b/lib/presentation/shared/styles.dart index 72b5bc8a5..1fc7ae7f3 100644 --- a/lib/presentation/shared/styles.dart +++ b/lib/presentation/shared/styles.dart @@ -28,6 +28,7 @@ class Styles { static const edgeInsetHorizontal5 = EdgeInsets.symmetric(horizontal: 5); static const edgeInsetVertical16 = EdgeInsets.symmetric(vertical: 16); static const edgeInsetVertical10 = EdgeInsets.symmetric(vertical: 10); + static const edgeInsetHorizontal10 = EdgeInsets.symmetric(horizontal: 10); static const modalBottomSheetShape = RoundedRectangleBorder( borderRadius: BorderRadius.only( diff --git a/lib/presentation/shared/utils/enum_utils.dart b/lib/presentation/shared/utils/enum_utils.dart index 813895029..52299c0b6 100644 --- a/lib/presentation/shared/utils/enum_utils.dart +++ b/lib/presentation/shared/utils/enum_utils.dart @@ -6,11 +6,29 @@ class TranslatedEnum { } class EnumUtils { - static List> getTranslatedAndSortedEnum(List values, String Function(TEnum) itemText, - {List exclude = const []}) { + static List> getTranslatedAndSortedEnum( + List values, + String Function(TEnum) itemText, { + List exclude = const [], + }) { final filterValues = exclude.isNotEmpty ? values.where((el) => !exclude.contains(el)) : values; final translatedValues = filterValues.map((filter) => TranslatedEnum(filter, itemText(filter))).toList() ..sort((x, y) => x.translation.compareTo(y.translation)); return translatedValues; } + + static List> getTranslatedAndSortedEnumWithAllValue( + TEnum allValue, + String allValueText, + List values, + String Function(TEnum) itemText, { + List exclude = const [], + }) { + final filterValues = exclude.isNotEmpty ? values.where((el) => !exclude.contains(el)) : values; + final translatedValues = filterValues + .map((filter) => TranslatedEnum(filter, filter == allValue ? allValueText : itemText(filter))) + .toList() + ..sort((x, y) => x.translation.compareTo(y.translation)); + return translatedValues; + } } diff --git a/lib/presentation/weapons/widgets/weapon_bottom_sheet.dart b/lib/presentation/weapons/widgets/weapon_bottom_sheet.dart index 44f9ff988..9efdd3ecf 100644 --- a/lib/presentation/weapons/widgets/weapon_bottom_sheet.dart +++ b/lib/presentation/weapons/widgets/weapon_bottom_sheet.dart @@ -10,6 +10,7 @@ import 'package:genshindb/presentation/shared/item_popupmenu_filter.dart'; import 'package:genshindb/presentation/shared/loading.dart'; import 'package:genshindb/presentation/shared/rarity_rating.dart'; import 'package:genshindb/presentation/shared/sort_direction_popupmenu_filter.dart'; +import 'package:genshindb/presentation/shared/styles.dart'; import 'package:genshindb/presentation/shared/weapons_button_bar.dart'; class WeaponBottomSheet extends StatelessWidget { @@ -31,18 +32,12 @@ class WeaponBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { final s = S.of(context); - + final theme = Theme.of(context); return CommonBottomSheet( titleIcon: GenshinDb.filter, title: s.filters, - onOk: () { - context.read().add(const WeaponsEvent.applyFilterChanges()); - Navigator.pop(context); - }, - onCancel: () { - context.read().add(const WeaponsEvent.cancelChanges()); - Navigator.pop(context); - }, + showOkButton: false, + showCancelButton: false, child: BlocBuilder( builder: (context, state) => state.map( loading: (_) => const Loading(), @@ -93,6 +88,32 @@ class WeaponBottomSheet extends StatelessWidget { ) ], ), + ButtonBar( + buttonPadding: Styles.edgeInsetHorizontal10, + children: [ + OutlinedButton( + onPressed: () { + context.read().add(const WeaponsEvent.cancelChanges()); + Navigator.pop(context); + }, + child: Text(s.cancel, style: TextStyle(color: theme.primaryColor)), + ), + OutlinedButton( + onPressed: () { + context.read().add(const WeaponsEvent.resetFilters()); + Navigator.pop(context); + }, + child: Text(s.reset, style: TextStyle(color: theme.primaryColor)), + ), + ElevatedButton( + onPressed: () { + context.read().add(const WeaponsEvent.applyFilterChanges()); + Navigator.pop(context); + }, + child: Text(s.ok), + ) + ], + ), ], ), ),