diff --git a/assets/icons/ic_clock.svg b/assets/icons/ic_clock.svg new file mode 100644 index 0000000..fdc8a5b --- /dev/null +++ b/assets/icons/ic_clock.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/translations/app_en.arb b/assets/translations/app_en.arb index 90ec32a..baf37fd 100644 --- a/assets/translations/app_en.arb +++ b/assets/translations/app_en.arb @@ -77,7 +77,13 @@ "halal": "Halal", "allergyFriendly": "Allergy friendly", "atLeast":"At least", - "apply": "Apply" + "apply": "Apply", + "pickedForYouDefault": "Picked for you (default)", + "popular": "Most popular", + "topRated": "Top rated", + "fast": "Fast", + "delivery": "Delivery" + diff --git a/assets/translations/app_ru.arb b/assets/translations/app_ru.arb index e1ea54b..44e92aa 100644 --- a/assets/translations/app_ru.arb +++ b/assets/translations/app_ru.arb @@ -84,7 +84,13 @@ "halal": "Халяль", "allergyFriendly": "Без аллергенов", "atLeast":"Kак минимум", - "apply": "Применить" + "apply": "Применить", + "pickedForYouDefault": "Выбрано для вас (по умолчанию)", + "popular": "Самые популярные", + "topRated": "Высокий рейтинг", + "fast": "Быстро", + "delivery": "Доставка" + diff --git a/assets/translations/app_uz.arb b/assets/translations/app_uz.arb index 13d32ea..8b65ce5 100644 --- a/assets/translations/app_uz.arb +++ b/assets/translations/app_uz.arb @@ -84,7 +84,13 @@ "halal": "Halol", "allergyFriendly": "Allergiyaga mos", "atLeast":"Kаmida", - "apply": "Qo‘llash" + "apply": "Qo‘llash", + "pickedForYouDefault": "Siz uchun tanlangan (standart)", + "popular": "Eng ommabop", + "topRated": "Yuqori reytingli", + "fast": "Tezkor", + "delivery": "Yetkazish" + } diff --git a/lib/core/l10n/app_localizations.dart b/lib/core/l10n/app_localizations.dart index 50461c8..f0c723d 100644 --- a/lib/core/l10n/app_localizations.dart +++ b/lib/core/l10n/app_localizations.dart @@ -483,6 +483,30 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Apply'** String get apply; + + /// No description provided for @pickedForYouDefault. + /// + /// In en, this message translates to: + /// **'Picked for you (default)'** + String get pickedForYouDefault; + + /// No description provided for @popular. + /// + /// In en, this message translates to: + /// **'Most popular'** + String get popular; + + /// No description provided for @topRated. + /// + /// In en, this message translates to: + /// **'Top rated'** + String get topRated; + + /// No description provided for @fast. + /// + /// In en, this message translates to: + /// **'Fast'** + String get fast; } class _AppLocalizationsDelegate diff --git a/lib/core/l10n/app_localizations_en.dart b/lib/core/l10n/app_localizations_en.dart index c141639..87ec60a 100644 --- a/lib/core/l10n/app_localizations_en.dart +++ b/lib/core/l10n/app_localizations_en.dart @@ -204,4 +204,16 @@ class AppLocalizationsEn extends AppLocalizations { @override String get apply => 'Apply'; + + @override + String get pickedForYouDefault => 'Picked for you (default)'; + + @override + String get popular => 'Most popular'; + + @override + String get topRated => 'Top rated'; + + @override + String get fast => 'Fast'; } diff --git a/lib/core/l10n/app_localizations_ru.dart b/lib/core/l10n/app_localizations_ru.dart index 62a46fa..c97a2fa 100644 --- a/lib/core/l10n/app_localizations_ru.dart +++ b/lib/core/l10n/app_localizations_ru.dart @@ -205,4 +205,16 @@ class AppLocalizationsRu extends AppLocalizations { @override String get apply => 'Применить'; + + @override + String get pickedForYouDefault => 'Выбрано для вас (по умолчанию)'; + + @override + String get popular => 'Самые популярные'; + + @override + String get topRated => 'Высокий рейтинг'; + + @override + String get fast => 'Быстро'; } diff --git a/lib/core/l10n/app_localizations_uz.dart b/lib/core/l10n/app_localizations_uz.dart index 323e137..f5bf7ee 100644 --- a/lib/core/l10n/app_localizations_uz.dart +++ b/lib/core/l10n/app_localizations_uz.dart @@ -54,7 +54,7 @@ class AppLocalizationsUz extends AppLocalizations { } @override - String get delivery => 'Yetkazib berish'; + String get delivery => 'Yetkazish'; @override String get pickUp => 'Olib ketish'; @@ -205,4 +205,16 @@ class AppLocalizationsUz extends AppLocalizations { @override String get apply => 'Qo‘llash'; + + @override + String get pickedForYouDefault => 'Siz uchun tanlangan (standart)'; + + @override + String get popular => 'Eng ommabop'; + + @override + String get topRated => 'Yuqori reytingli'; + + @override + String get fast => 'Tezkor'; } diff --git a/lib/core/router/app_routes.dart b/lib/core/router/app_routes.dart index 27faa14..62e6fe4 100644 --- a/lib/core/router/app_routes.dart +++ b/lib/core/router/app_routes.dart @@ -1,6 +1,4 @@ import 'package:flutter/cupertino.dart'; -import 'package:food_delivery_client/feature/home/presentation/pages/categories_page/categories_page.dart'; -import 'package:food_delivery_client/feature/on_boarding/presentation/pages/splash_page/splash_page.dart'; import '../../food_delivery_client.dart'; @@ -22,6 +20,12 @@ class AppRoutes { path: Routes.categories, pageBuilder: (context, state) => CupertinoPage(child: CategoriesPage()), ), + GoRoute( + path: Routes.filters, + pageBuilder: (context, state) => CupertinoPage(child: FiltersPage( + homeBloc: state.extra as HomeBloc, + )), + ), ], ); } diff --git a/lib/core/router/routes_name.dart b/lib/core/router/routes_name.dart index 46c0f2d..e400c8a 100644 --- a/lib/core/router/routes_name.dart +++ b/lib/core/router/routes_name.dart @@ -4,5 +4,7 @@ abstract class Routes { static const String register = '/register'; static const String main = '/main'; static const String categories = '/categories'; + static const String filters= '/filters'; + } diff --git a/lib/core/theme/app_icons.dart b/lib/core/theme/app_icons.dart index deb75d5..5462d8e 100644 --- a/lib/core/theme/app_icons.dart +++ b/lib/core/theme/app_icons.dart @@ -42,5 +42,5 @@ abstract class AppIcons { static const String icVegen = "$baseUrl/ic_vegen.png"; static const String icGlutenFree = "$baseUrl/ic_gluten_free.png"; static const String icAllergyFriendly = "$baseUrl/ic_allergy_friendly.png"; - static const String icClock = "$baseUrl/ic_clock.png"; + static const String icClock = "$baseUrl/ic_clock.svg"; } diff --git a/lib/core/theme/app_textstyles.dart b/lib/core/theme/app_textstyles.dart index 99b8a00..33547af 100644 --- a/lib/core/theme/app_textstyles.dart +++ b/lib/core/theme/app_textstyles.dart @@ -28,6 +28,13 @@ abstract class AppTextStyles { fontWeight: FontWeight.w400, ); + static const TextStyle size16Regular = TextStyle( + color: _defaultColor, + fontSize: SizesCons.size_16, + fontFamily: _fontRegular, + fontWeight: FontWeight.w400, + ); + static const TextStyle size20Regular = TextStyle( color: _defaultColor, fontSize: SizesCons.size_20, diff --git a/lib/feature/common/presentation/widgets/app_list_tile.dart b/lib/feature/common/presentation/widgets/app_list_tile.dart new file mode 100644 index 0000000..27eec38 --- /dev/null +++ b/lib/feature/common/presentation/widgets/app_list_tile.dart @@ -0,0 +1,44 @@ +import '../../../../food_delivery_client.dart'; + +class AppListTile extends StatelessWidget { + const AppListTile({ + super.key, + required this.onPressed, + required this.isSelected, + required this.svgPath, + required this.title, + this.contentPadding, + this.leading, + this.titleWidget, + this.trailing, + this.titleTextStyle, + this.subTitle, + }); + + final VoidCallback onPressed; + final bool isSelected; + final String svgPath; + final String title; + final EdgeInsets? contentPadding; + final Widget? leading; + final Widget? titleWidget; + final Widget? trailing; + final TextStyle? titleTextStyle; + final Widget? subTitle; + + @override + Widget build(BuildContext context) { + return ListTile( + onTap: onPressed, + subtitle: subTitle, + contentPadding: + contentPadding ?? EdgeInsetsGeometry.only(left: 20, right: 27), + leading: leading ?? SvgPicture.asset(svgPath), + title: + titleWidget ?? + Text(title, style: titleTextStyle ?? AppTextStyles.size16Regular), + trailing: + trailing ?? (isSelected ? SvgPicture.asset(AppIcons.icCheck) : null), + ); + } +} diff --git a/lib/feature/common/presentation/widgets/widgets.dart b/lib/feature/common/presentation/widgets/widgets.dart index 6261541..d4e2f78 100644 --- a/lib/feature/common/presentation/widgets/widgets.dart +++ b/lib/feature/common/presentation/widgets/widgets.dart @@ -5,3 +5,4 @@ export 'w_see_all_raw.dart'; export 'w_stories_list_item.dart'; export 'w_custom_modal_bottom_sheet.dart'; export 'app_button.dart'; +export 'app_list_tile.dart'; diff --git a/lib/feature/home/home.dart b/lib/feature/home/home.dart index 31cbfa3..7fdc6e6 100644 --- a/lib/feature/home/home.dart +++ b/lib/feature/home/home.dart @@ -12,3 +12,11 @@ export 'package:food_delivery_client/feature/home/presentation/pages/home_page/w export 'package:flutter_svg/flutter_svg.dart'; export 'package:food_delivery_client/feature/home/presentation/pages/categories_page/categories_page.dart'; export 'package:food_delivery_client/feature/home/presentation/pages/home_page/widgets/w_rating_btm_sheet.dart'; +export 'package:food_delivery_client/feature/home/presentation/pages/filters_page/filters_page.dart'; +export 'presentation/mixins/filter_mixins.dart'; +export 'package:food_delivery_client/feature/home/presentation/pages/filters_page/widgets/w_filter_app_bar.dart'; +export 'package:food_delivery_client/feature/home/presentation/pages/filters_page/widgets/w_filters_body.dart'; +export 'package:food_delivery_client/feature/home/presentation/pages/filters_page/widgets/w_delivery_duration.dart'; +export 'package:food_delivery_client/feature/home/presentation/pages/filters_page/widgets/w_filter_dietary.dart'; +export 'package:food_delivery_client/feature/home/presentation/pages/filters_page/widgets/w_filters_deals.dart'; +export 'package:food_delivery_client/feature/home/presentation/pages/filters_page/widgets/w_filters_sort.dart'; \ No newline at end of file diff --git a/lib/feature/home/presentation/blocs/home_bloc/home_bloc.dart b/lib/feature/home/presentation/blocs/home_bloc/home_bloc.dart index efb321f..f67a90b 100644 --- a/lib/feature/home/presentation/blocs/home_bloc/home_bloc.dart +++ b/lib/feature/home/presentation/blocs/home_bloc/home_bloc.dart @@ -12,6 +12,7 @@ class HomeBloc extends Bloc { on<_Changed>(_onChanged); on<_Filtered>(_onFiltered); on<_Rated>(_onRated); + on<_DurationChanged>(_onDurationChanged); } void _onChanged(_Changed event, Emitter emit) { @@ -29,9 +30,11 @@ class HomeBloc extends Bloc { } } - - void _onRated(_Rated event, Emitter emit){ + void _onRated(_Rated event, Emitter emit) { emit(state.copyWith(rating: event.filter)); + } + void _onDurationChanged(_DurationChanged event, Emitter emit) { + emit(state.copyWith(deliveryDuration: event.filter)); } } diff --git a/lib/feature/home/presentation/blocs/home_bloc/home_bloc.freezed.dart b/lib/feature/home/presentation/blocs/home_bloc/home_bloc.freezed.dart index 718c3ca..df01d4c 100644 --- a/lib/feature/home/presentation/blocs/home_bloc/home_bloc.freezed.dart +++ b/lib/feature/home/presentation/blocs/home_bloc/home_bloc.freezed.dart @@ -55,12 +55,13 @@ extension HomeEventPatterns on HomeEvent { /// } /// ``` -@optionalTypeArgs TResult maybeMap({TResult Function( _Started value)? started,TResult Function( _Changed value)? changed,TResult Function( _Filtered value)? filtered,TResult Function( _Rated value)? rated,required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap({TResult Function( _Started value)? started,TResult Function( _Changed value)? changed,TResult Function( _DurationChanged value)? durationChanged,TResult Function( _Filtered value)? filtered,TResult Function( _Rated value)? rated,required TResult orElse(),}){ final _that = this; switch (_that) { case _Started() when started != null: return started(_that);case _Changed() when changed != null: -return changed(_that);case _Filtered() when filtered != null: +return changed(_that);case _DurationChanged() when durationChanged != null: +return durationChanged(_that);case _Filtered() when filtered != null: return filtered(_that);case _Rated() when rated != null: return rated(_that);case _: return orElse(); @@ -80,12 +81,13 @@ return rated(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map({required TResult Function( _Started value) started,required TResult Function( _Changed value) changed,required TResult Function( _Filtered value) filtered,required TResult Function( _Rated value) rated,}){ +@optionalTypeArgs TResult map({required TResult Function( _Started value) started,required TResult Function( _Changed value) changed,required TResult Function( _DurationChanged value) durationChanged,required TResult Function( _Filtered value) filtered,required TResult Function( _Rated value) rated,}){ final _that = this; switch (_that) { case _Started(): return started(_that);case _Changed(): -return changed(_that);case _Filtered(): +return changed(_that);case _DurationChanged(): +return durationChanged(_that);case _Filtered(): return filtered(_that);case _Rated(): return rated(_that);case _: throw StateError('Unexpected subclass'); @@ -104,12 +106,13 @@ return rated(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull({TResult? Function( _Started value)? started,TResult? Function( _Changed value)? changed,TResult? Function( _Filtered value)? filtered,TResult? Function( _Rated value)? rated,}){ +@optionalTypeArgs TResult? mapOrNull({TResult? Function( _Started value)? started,TResult? Function( _Changed value)? changed,TResult? Function( _DurationChanged value)? durationChanged,TResult? Function( _Filtered value)? filtered,TResult? Function( _Rated value)? rated,}){ final _that = this; switch (_that) { case _Started() when started != null: return started(_that);case _Changed() when changed != null: -return changed(_that);case _Filtered() when filtered != null: +return changed(_that);case _DurationChanged() when durationChanged != null: +return durationChanged(_that);case _Filtered() when filtered != null: return filtered(_that);case _Rated() when rated != null: return rated(_that);case _: return null; @@ -128,11 +131,12 @@ return rated(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen({TResult Function()? started,TResult Function( int index)? changed,TResult Function( String filter)? filtered,TResult Function( List filter)? rated,required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen({TResult Function()? started,TResult Function( int index)? changed,TResult Function( String filter)? durationChanged,TResult Function( String filter)? filtered,TResult Function( List filter)? rated,required TResult orElse(),}) {final _that = this; switch (_that) { case _Started() when started != null: return started();case _Changed() when changed != null: -return changed(_that.index);case _Filtered() when filtered != null: +return changed(_that.index);case _DurationChanged() when durationChanged != null: +return durationChanged(_that.filter);case _Filtered() when filtered != null: return filtered(_that.filter);case _Rated() when rated != null: return rated(_that.filter);case _: return orElse(); @@ -152,11 +156,12 @@ return rated(_that.filter);case _: /// } /// ``` -@optionalTypeArgs TResult when({required TResult Function() started,required TResult Function( int index) changed,required TResult Function( String filter) filtered,required TResult Function( List filter) rated,}) {final _that = this; +@optionalTypeArgs TResult when({required TResult Function() started,required TResult Function( int index) changed,required TResult Function( String filter) durationChanged,required TResult Function( String filter) filtered,required TResult Function( List filter) rated,}) {final _that = this; switch (_that) { case _Started(): return started();case _Changed(): -return changed(_that.index);case _Filtered(): +return changed(_that.index);case _DurationChanged(): +return durationChanged(_that.filter);case _Filtered(): return filtered(_that.filter);case _Rated(): return rated(_that.filter);case _: throw StateError('Unexpected subclass'); @@ -175,11 +180,12 @@ return rated(_that.filter);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull({TResult? Function()? started,TResult? Function( int index)? changed,TResult? Function( String filter)? filtered,TResult? Function( List filter)? rated,}) {final _that = this; +@optionalTypeArgs TResult? whenOrNull({TResult? Function()? started,TResult? Function( int index)? changed,TResult? Function( String filter)? durationChanged,TResult? Function( String filter)? filtered,TResult? Function( List filter)? rated,}) {final _that = this; switch (_that) { case _Started() when started != null: return started();case _Changed() when changed != null: -return changed(_that.index);case _Filtered() when filtered != null: +return changed(_that.index);case _DurationChanged() when durationChanged != null: +return durationChanged(_that.filter);case _Filtered() when filtered != null: return filtered(_that.filter);case _Rated() when rated != null: return rated(_that.filter);case _: return null; @@ -290,6 +296,72 @@ as int, /// @nodoc +class _DurationChanged implements HomeEvent { + const _DurationChanged(this.filter); + + + final String filter; + +/// Create a copy of HomeEvent +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$DurationChangedCopyWith<_DurationChanged> get copyWith => __$DurationChangedCopyWithImpl<_DurationChanged>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _DurationChanged&&(identical(other.filter, filter) || other.filter == filter)); +} + + +@override +int get hashCode => Object.hash(runtimeType,filter); + +@override +String toString() { + return 'HomeEvent.durationChanged(filter: $filter)'; +} + + +} + +/// @nodoc +abstract mixin class _$DurationChangedCopyWith<$Res> implements $HomeEventCopyWith<$Res> { + factory _$DurationChangedCopyWith(_DurationChanged value, $Res Function(_DurationChanged) _then) = __$DurationChangedCopyWithImpl; +@useResult +$Res call({ + String filter +}); + + + + +} +/// @nodoc +class __$DurationChangedCopyWithImpl<$Res> + implements _$DurationChangedCopyWith<$Res> { + __$DurationChangedCopyWithImpl(this._self, this._then); + + final _DurationChanged _self; + final $Res Function(_DurationChanged) _then; + +/// Create a copy of HomeEvent +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? filter = null,}) { + return _then(_DurationChanged( +null == filter ? _self.filter : filter // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +/// @nodoc + + class _Filtered implements HomeEvent { const _Filtered(this.filter); @@ -428,7 +500,7 @@ as List, /// @nodoc mixin _$HomeState { - int get currentIndex; List get filters; List get rating; + int get currentIndex; String get deliveryDuration; List get filters; List get rating; /// Create a copy of HomeState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -439,16 +511,16 @@ $HomeStateCopyWith get copyWith => _$HomeStateCopyWithImpl @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is HomeState&&(identical(other.currentIndex, currentIndex) || other.currentIndex == currentIndex)&&const DeepCollectionEquality().equals(other.filters, filters)&&const DeepCollectionEquality().equals(other.rating, rating)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is HomeState&&(identical(other.currentIndex, currentIndex) || other.currentIndex == currentIndex)&&(identical(other.deliveryDuration, deliveryDuration) || other.deliveryDuration == deliveryDuration)&&const DeepCollectionEquality().equals(other.filters, filters)&&const DeepCollectionEquality().equals(other.rating, rating)); } @override -int get hashCode => Object.hash(runtimeType,currentIndex,const DeepCollectionEquality().hash(filters),const DeepCollectionEquality().hash(rating)); +int get hashCode => Object.hash(runtimeType,currentIndex,deliveryDuration,const DeepCollectionEquality().hash(filters),const DeepCollectionEquality().hash(rating)); @override String toString() { - return 'HomeState(currentIndex: $currentIndex, filters: $filters, rating: $rating)'; + return 'HomeState(currentIndex: $currentIndex, deliveryDuration: $deliveryDuration, filters: $filters, rating: $rating)'; } @@ -459,7 +531,7 @@ abstract mixin class $HomeStateCopyWith<$Res> { factory $HomeStateCopyWith(HomeState value, $Res Function(HomeState) _then) = _$HomeStateCopyWithImpl; @useResult $Res call({ - int currentIndex, List filters, List rating + int currentIndex, String deliveryDuration, List filters, List rating }); @@ -476,10 +548,11 @@ class _$HomeStateCopyWithImpl<$Res> /// Create a copy of HomeState /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? currentIndex = null,Object? filters = null,Object? rating = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? currentIndex = null,Object? deliveryDuration = null,Object? filters = null,Object? rating = null,}) { return _then(_self.copyWith( currentIndex: null == currentIndex ? _self.currentIndex : currentIndex // ignore: cast_nullable_to_non_nullable -as int,filters: null == filters ? _self.filters : filters // ignore: cast_nullable_to_non_nullable +as int,deliveryDuration: null == deliveryDuration ? _self.deliveryDuration : deliveryDuration // ignore: cast_nullable_to_non_nullable +as String,filters: null == filters ? _self.filters : filters // ignore: cast_nullable_to_non_nullable as List,rating: null == rating ? _self.rating : rating // ignore: cast_nullable_to_non_nullable as List, )); @@ -566,10 +639,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( int currentIndex, List filters, List rating)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( int currentIndex, String deliveryDuration, List filters, List rating)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _HomeState() when $default != null: -return $default(_that.currentIndex,_that.filters,_that.rating);case _: +return $default(_that.currentIndex,_that.deliveryDuration,_that.filters,_that.rating);case _: return orElse(); } @@ -587,10 +660,10 @@ return $default(_that.currentIndex,_that.filters,_that.rating);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( int currentIndex, List filters, List rating) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( int currentIndex, String deliveryDuration, List filters, List rating) $default,) {final _that = this; switch (_that) { case _HomeState(): -return $default(_that.currentIndex,_that.filters,_that.rating);case _: +return $default(_that.currentIndex,_that.deliveryDuration,_that.filters,_that.rating);case _: throw StateError('Unexpected subclass'); } @@ -607,10 +680,10 @@ return $default(_that.currentIndex,_that.filters,_that.rating);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( int currentIndex, List filters, List rating)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( int currentIndex, String deliveryDuration, List filters, List rating)? $default,) {final _that = this; switch (_that) { case _HomeState() when $default != null: -return $default(_that.currentIndex,_that.filters,_that.rating);case _: +return $default(_that.currentIndex,_that.deliveryDuration,_that.filters,_that.rating);case _: return null; } @@ -622,10 +695,11 @@ return $default(_that.currentIndex,_that.filters,_that.rating);case _: class _HomeState implements HomeState { - const _HomeState({this.currentIndex = 0, final List filters = const [], final List rating = const []}): _filters = filters,_rating = rating; + const _HomeState({this.currentIndex = 0, this.deliveryDuration = "60+", final List filters = const [], final List rating = const []}): _filters = filters,_rating = rating; @override@JsonKey() final int currentIndex; +@override@JsonKey() final String deliveryDuration; final List _filters; @override@JsonKey() List get filters { if (_filters is EqualUnmodifiableListView) return _filters; @@ -651,16 +725,16 @@ _$HomeStateCopyWith<_HomeState> get copyWith => __$HomeStateCopyWithImpl<_HomeSt @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _HomeState&&(identical(other.currentIndex, currentIndex) || other.currentIndex == currentIndex)&&const DeepCollectionEquality().equals(other._filters, _filters)&&const DeepCollectionEquality().equals(other._rating, _rating)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _HomeState&&(identical(other.currentIndex, currentIndex) || other.currentIndex == currentIndex)&&(identical(other.deliveryDuration, deliveryDuration) || other.deliveryDuration == deliveryDuration)&&const DeepCollectionEquality().equals(other._filters, _filters)&&const DeepCollectionEquality().equals(other._rating, _rating)); } @override -int get hashCode => Object.hash(runtimeType,currentIndex,const DeepCollectionEquality().hash(_filters),const DeepCollectionEquality().hash(_rating)); +int get hashCode => Object.hash(runtimeType,currentIndex,deliveryDuration,const DeepCollectionEquality().hash(_filters),const DeepCollectionEquality().hash(_rating)); @override String toString() { - return 'HomeState(currentIndex: $currentIndex, filters: $filters, rating: $rating)'; + return 'HomeState(currentIndex: $currentIndex, deliveryDuration: $deliveryDuration, filters: $filters, rating: $rating)'; } @@ -671,7 +745,7 @@ abstract mixin class _$HomeStateCopyWith<$Res> implements $HomeStateCopyWith<$Re factory _$HomeStateCopyWith(_HomeState value, $Res Function(_HomeState) _then) = __$HomeStateCopyWithImpl; @override @useResult $Res call({ - int currentIndex, List filters, List rating + int currentIndex, String deliveryDuration, List filters, List rating }); @@ -688,10 +762,11 @@ class __$HomeStateCopyWithImpl<$Res> /// Create a copy of HomeState /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? currentIndex = null,Object? filters = null,Object? rating = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? currentIndex = null,Object? deliveryDuration = null,Object? filters = null,Object? rating = null,}) { return _then(_HomeState( currentIndex: null == currentIndex ? _self.currentIndex : currentIndex // ignore: cast_nullable_to_non_nullable -as int,filters: null == filters ? _self._filters : filters // ignore: cast_nullable_to_non_nullable +as int,deliveryDuration: null == deliveryDuration ? _self.deliveryDuration : deliveryDuration // ignore: cast_nullable_to_non_nullable +as String,filters: null == filters ? _self._filters : filters // ignore: cast_nullable_to_non_nullable as List,rating: null == rating ? _self._rating : rating // ignore: cast_nullable_to_non_nullable as List, )); diff --git a/lib/feature/home/presentation/blocs/home_bloc/home_event.dart b/lib/feature/home/presentation/blocs/home_bloc/home_event.dart index 1a4661f..7414fe1 100644 --- a/lib/feature/home/presentation/blocs/home_bloc/home_event.dart +++ b/lib/feature/home/presentation/blocs/home_bloc/home_event.dart @@ -4,6 +4,7 @@ part of 'home_bloc.dart'; class HomeEvent with _$HomeEvent { const factory HomeEvent.started() = _Started; const factory HomeEvent.changed(int index) = _Changed; + const factory HomeEvent.durationChanged(String filter) = _DurationChanged; const factory HomeEvent.filtered(String filter) = _Filtered; const factory HomeEvent.rated(List filter) = _Rated; diff --git a/lib/feature/home/presentation/blocs/home_bloc/home_state.dart b/lib/feature/home/presentation/blocs/home_bloc/home_state.dart index 280762b..d03eb3e 100644 --- a/lib/feature/home/presentation/blocs/home_bloc/home_state.dart +++ b/lib/feature/home/presentation/blocs/home_bloc/home_state.dart @@ -4,6 +4,7 @@ part of 'home_bloc.dart'; abstract class HomeState with _$HomeState { const factory HomeState({ @Default(0) int currentIndex, + @Default("60+") String deliveryDuration, @Default([]) List filters, @Default([]) List rating diff --git a/lib/feature/home/presentation/mixins/filter_mixins.dart b/lib/feature/home/presentation/mixins/filter_mixins.dart new file mode 100644 index 0000000..82465ce --- /dev/null +++ b/lib/feature/home/presentation/mixins/filter_mixins.dart @@ -0,0 +1,20 @@ +import '../../../../core/theme/app_icons.dart'; + +mixin FilterMixins { + final List deliveryDurations = ["30", "45", "60", "60+"]; + final List filterSortSvgs = [ + AppIcons.icPicked, + AppIcons.icMostPopular, + AppIcons.icStar, + AppIcons.icDeliveryTime, + ]; + + final List filterDietary = [ + AppIcons.icVegetarian, + AppIcons.icVegen, + AppIcons.icGlutenFree, + AppIcons.icAllergyFriendly, + ]; + + final List filterDeals = [AppIcons.icDeals, AppIcons.icBestOverall]; +} diff --git a/lib/feature/home/presentation/pages/filters_page/filters_page.dart b/lib/feature/home/presentation/pages/filters_page/filters_page.dart new file mode 100644 index 0000000..6602a9c --- /dev/null +++ b/lib/feature/home/presentation/pages/filters_page/filters_page.dart @@ -0,0 +1,26 @@ + +import '../../../../../food_delivery_client.dart'; + +class FiltersPage extends StatefulWidget { + const FiltersPage({super.key, required this.homeBloc}); + + final HomeBloc homeBloc; + + @override + State createState() => _FiltersPageState(); +} + +class _FiltersPageState extends State { + @override + Widget build(BuildContext context) { + return BlocProvider.value( + value: widget.homeBloc, + child: BlocBuilder( + builder: (context, state) { + return WFiltersBody(); + }, + ), + ); + } +} + diff --git a/lib/feature/home/presentation/pages/filters_page/widgets/w_delivery_duration.dart b/lib/feature/home/presentation/pages/filters_page/widgets/w_delivery_duration.dart new file mode 100644 index 0000000..762f619 --- /dev/null +++ b/lib/feature/home/presentation/pages/filters_page/widgets/w_delivery_duration.dart @@ -0,0 +1,84 @@ +import '../../../../../../food_delivery_client.dart'; + +class WDeliveryDuration extends StatelessWidget with FilterMixins { + WDeliveryDuration({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.loc.delivery, + style: AppTextStyles.size18Medium, + ).paddingSymmetric(horizontal: 20), + 15.verticalSpace, + SizedBox( + height: 60, + child: Stack( + alignment: AlignmentGeometry.center, + children: [ + SizedBox( + height: 40, + width: context.w, + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius8, + color: AppColors.cE6E6E6.newWithOpacity(.6), + ), + ), + ), + Material( + color: AppColors.cTransparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: List.generate(deliveryDurations.length, ( + index, + ) { + final isActive = + state.deliveryDuration == deliveryDurations[index]; + return InkWell( + onTap: () { + context.read().add( + HomeEvent.durationChanged( + deliveryDurations[index], + ), + ); + }, + borderRadius: AppUtils.kBorderRadius40, + child: Ink( + height: 60, + width: 60, + decoration: BoxDecoration( + color: isActive + ? AppColors.c34A853 + : AppColors.cTransparent, + borderRadius: AppUtils.kBorderRadius40, + ), + child: Center( + child: Text( + deliveryDurations[index], + style: AppTextStyles.size16Medium.copyWith( + color: isActive + ? AppColors.cFFFFFF + : AppColors.c000000, + ), + ), + ), + ), + ); + }), + ), + ), + ], + ), + ).paddingSymmetric(horizontal: 20), + 25.verticalSpace, + ], + ); + }, + ); + } +} diff --git a/lib/feature/home/presentation/pages/filters_page/widgets/w_filter_app_bar.dart b/lib/feature/home/presentation/pages/filters_page/widgets/w_filter_app_bar.dart new file mode 100644 index 0000000..ae80c23 --- /dev/null +++ b/lib/feature/home/presentation/pages/filters_page/widgets/w_filter_app_bar.dart @@ -0,0 +1,25 @@ +import '../../../../../../food_delivery_client.dart'; + +class WFiltersAppBar extends StatelessWidget { + const WFiltersAppBar({super.key}); + + @override + Widget build(BuildContext context) { + return AppBar( + centerTitle: true, + elevation: 30, + bottomOpacity: 1, + scrolledUnderElevation: 10, + backgroundColor: AppColors.cFFFFFF, + surfaceTintColor: AppColors.cTransparent, + useDefaultSemanticsOrder: true, + leading: IconButton( + onPressed: () { + context.pop(); + }, + icon: SvgPicture.asset(AppIcons.icClose), + ), + title: Text(context.loc.allCategories, style: AppTextStyles.size20Medium), + ); + } +} diff --git a/lib/feature/home/presentation/pages/filters_page/widgets/w_filter_dietary.dart b/lib/feature/home/presentation/pages/filters_page/widgets/w_filter_dietary.dart new file mode 100644 index 0000000..48cdffd --- /dev/null +++ b/lib/feature/home/presentation/pages/filters_page/widgets/w_filter_dietary.dart @@ -0,0 +1,38 @@ +import '../../../../../../food_delivery_client.dart'; + +class WFilterDietary extends StatelessWidget with FilterMixins { + WFilterDietary({super.key}); + + @override + Widget build(BuildContext context) { + final List dietaryTitles = [ + context.loc.vegetarian, + context.loc.vegan, + context.loc.glutenFree, + context.loc.allergyFriendly, + ]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 25.verticalSpace, + Text( + context.loc.dietary, + style: AppTextStyles.size18Medium, + ).paddingSymmetric(horizontal: 20), + 15.verticalSpace, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: List.generate(4, (index) { + return AppListTile( + onPressed: () {}, + isSelected: false, + leading: Image.asset(filterDietary[index], height: 30, width: 30), + svgPath: filterDietary[index], + title: dietaryTitles[index], + ); + }), + ), + ], + ); + } +} diff --git a/lib/feature/home/presentation/pages/filters_page/widgets/w_filters_body.dart b/lib/feature/home/presentation/pages/filters_page/widgets/w_filters_body.dart new file mode 100644 index 0000000..5c5f4c6 --- /dev/null +++ b/lib/feature/home/presentation/pages/filters_page/widgets/w_filters_body.dart @@ -0,0 +1,42 @@ +import '../../../../../../food_delivery_client.dart'; + +class WFiltersBody extends StatelessWidget { + const WFiltersBody({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return WLayout( + child: Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(56), + child: WFiltersAppBar(), + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + physics: const AlwaysScrollableScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 18.verticalSpace, + WDeliveryDuration(), + WFiltersSort(), + WFiltersDeals(), + WFilterDietary(), + ], + ), + ), + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/feature/home/presentation/pages/filters_page/widgets/w_filters_deals.dart b/lib/feature/home/presentation/pages/filters_page/widgets/w_filters_deals.dart new file mode 100644 index 0000000..74f3103 --- /dev/null +++ b/lib/feature/home/presentation/pages/filters_page/widgets/w_filters_deals.dart @@ -0,0 +1,59 @@ +import 'package:flutter/cupertino.dart'; + +import '../../../../../../food_delivery_client.dart'; + +class WFiltersDeals extends StatefulWidget { + const WFiltersDeals({super.key}); + + @override + State createState() => _WFiltersDealsState(); +} + +class _WFiltersDealsState extends State with FilterMixins { + List values = [false, false]; + + @override + Widget build(BuildContext context) { + final List dealsTitle = [ + context.loc.deals, + context.loc.bestOverall, + ]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 25.verticalSpace, + Text( + "From Uber eats", + style: AppTextStyles.size18Medium, + ).paddingSymmetric(horizontal: 20), + 15.verticalSpace, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: List.generate(2, (index) { + return AppListTile( + onPressed: () { + setState(() { + values[index] = !values[index]; + }); + }, + isSelected: false, + leading: index == 0 + ? SvgPicture.asset(filterDeals[index], height: 30, width: 30) + : Image.asset(filterDeals[index], height: 30, width: 30), + svgPath: filterDeals[index], + title: dealsTitle[index], + trailing: CupertinoSwitch( + value: values[index], + onChanged: (value) { + setState(() { + values[index] = !values[index]; + }); + }, + ), + ); + }), + ), + ], + ); + } +} diff --git a/lib/feature/home/presentation/pages/filters_page/widgets/w_filters_sort.dart b/lib/feature/home/presentation/pages/filters_page/widgets/w_filters_sort.dart new file mode 100644 index 0000000..604879d --- /dev/null +++ b/lib/feature/home/presentation/pages/filters_page/widgets/w_filters_sort.dart @@ -0,0 +1,60 @@ +import '../../../../../../food_delivery_client.dart'; + +class WFiltersSort extends StatelessWidget with FilterMixins { + WFiltersSort({super.key}); + + @override + Widget build(BuildContext context) { + final List filterSortTitles = [ + context.loc.pickedForYouDefault, + context.loc.mostPopular, + context.loc.topRated, + context.loc.fast, + ]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.loc.sort, + style: AppTextStyles.size18Medium, + ).paddingSymmetric(horizontal: 20), + 15.verticalSpace, + Column( + children: List.generate(filterSortTitles.length, (index) { + return AppListTile( + onPressed: () {}, + isSelected: false, + svgPath: filterSortSvgs[index], + title: filterSortTitles[index], + ); + }), + ), + + // AppListTile( + // onPressed: () {}, + // isSelected: true, + // svgPath: AppIcons.icPicked, + // title: context.loc.pickedForYouDefault, + // ), + // AppListTile( + // onPressed: () {}, + // isSelected: false, + // svgPath: AppIcons.icMostPopular, + // title: context.loc.popular, + // ), + // AppListTile( + // onPressed: () {}, + // isSelected: false, + // svgPath: AppIcons.icStar, + // title: context.loc.topRated, + // ), + // AppListTile( + // onPressed: () {}, + // isSelected: false, + // svgPath: AppIcons.icClock, + // title: context.loc.fast, + // ), + ], + ); + } +} diff --git a/lib/feature/home/presentation/pages/home_page/widgets/w_home_headers.dart b/lib/feature/home/presentation/pages/home_page/widgets/w_home_headers.dart index 06712db..4a4476b 100644 --- a/lib/feature/home/presentation/pages/home_page/widgets/w_home_headers.dart +++ b/lib/feature/home/presentation/pages/home_page/widgets/w_home_headers.dart @@ -9,8 +9,6 @@ class WHomeHeader extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - - return DecoratedBox( decoration: BoxDecoration(color: AppColors.cFFFFFF), child: Column( @@ -37,7 +35,12 @@ class WHomeHeader extends StatelessWidget { Align( alignment: AlignmentGeometry.topRight, child: IconButton( - onPressed: () {}, + onPressed: () { + context.push( + Routes.filters, + extra: context.read(), + ); + }, icon: SvgPicture.asset( AppIcons.icFilter, height: 36, diff --git a/lib/feature/home/presentation/pages/home_page/widgets/w_rating_btm_sheet.dart b/lib/feature/home/presentation/pages/home_page/widgets/w_rating_btm_sheet.dart index 30d29bf..da7586b 100644 --- a/lib/feature/home/presentation/pages/home_page/widgets/w_rating_btm_sheet.dart +++ b/lib/feature/home/presentation/pages/home_page/widgets/w_rating_btm_sheet.dart @@ -66,6 +66,7 @@ class _WRatingBottomSheetState extends State { 15.verticalSpace, Wrap( spacing: 10, + runSpacing: 15, children: List.generate(value.length, (index) { return WRatingButton( onTap: () { diff --git a/lib/food_delivery_client.dart b/lib/food_delivery_client.dart index 12574fe..29f8664 100644 --- a/lib/food_delivery_client.dart +++ b/lib/food_delivery_client.dart @@ -20,3 +20,4 @@ export 'package:shared_preferences/shared_preferences.dart'; export 'package:cached_network_image/cached_network_image.dart'; export 'package:carousel_slider/carousel_slider.dart'; export 'package:flutter_bounceable/flutter_bounceable.dart'; +export 'package:food_delivery_client/feature/on_boarding/presentation/pages/splash_page/splash_page.dart';