From 7afc58888d96bcccd60ea9140ec972e80e457bfb Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Tue, 28 Oct 2025 18:15:29 +0300 Subject: [PATCH 01/46] feat: search init --- .../screen/search/search_screen.dart | 17 +++++++++++++++++ .../screen/search/widget/range_slider.dart | 15 +++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 lib/presentation/screen/search/widget/range_slider.dart diff --git a/lib/presentation/screen/search/search_screen.dart b/lib/presentation/screen/search/search_screen.dart index e69de29..9ab1c52 100644 --- a/lib/presentation/screen/search/search_screen.dart +++ b/lib/presentation/screen/search/search_screen.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +class SearchScreen extends StatefulWidget { + const SearchScreen({super.key}); + + @override + State createState() => _SearchScreenState(); +} + +class _SearchScreenState extends State { + @override + Widget build(BuildContext context) { + return Container( + + ); + } +} diff --git a/lib/presentation/screen/search/widget/range_slider.dart b/lib/presentation/screen/search/widget/range_slider.dart new file mode 100644 index 0000000..57b1338 --- /dev/null +++ b/lib/presentation/screen/search/widget/range_slider.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class RangeSlider extends StatefulWidget { + const RangeSlider({super.key}); + + @override + State createState() => _RangeSliderState(); +} + +class _RangeSliderState extends State { + @override + Widget build(BuildContext context) { + return Container(); + } +} From f5d5781f6ca369deeff4e94221f39554eb430567 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Wed, 29 Oct 2025 00:38:32 +0300 Subject: [PATCH 02/46] feat: slider base --- .../screen/search/widget/range_slider.dart | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/lib/presentation/screen/search/widget/range_slider.dart b/lib/presentation/screen/search/widget/range_slider.dart index 57b1338..e10f048 100644 --- a/lib/presentation/screen/search/widget/range_slider.dart +++ b/lib/presentation/screen/search/widget/range_slider.dart @@ -1,15 +1,55 @@ import 'package:flutter/material.dart'; -class RangeSlider extends StatefulWidget { - const RangeSlider({super.key}); +class RangeSliderWidget extends StatefulWidget { + final RangeValues maxRange; + RangeValues? currentRange; + final String currency; + final Function(RangeValues) onChange; + + RangeSliderWidget({ + super.key, + required this.maxRange, + required this.currency, + this.currentRange, + required this.onChange, + }); @override - State createState() => _RangeSliderState(); + State createState() => _RangeSliderWidgetState(); } -class _RangeSliderState extends State { +class _RangeSliderWidgetState extends State { @override Widget build(BuildContext context) { - return Container(); + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('${widget.maxRange.start.round()} ${widget.currency}'), + Text('${widget.maxRange.end.round()} ${widget.currency}'), + ], + ), + SliderTheme( + data: SliderTheme.of(context).copyWith( + activeTrackColor: Color(0xFFFF7C38), + thumbColor: Color(0xFFFF7C38), + overlayShape: RoundSliderOverlayShape(overlayRadius: 20), + overlappingShapeStrokeColor: Colors.black, + inactiveTrackColor: Color(0xFFF9FBFB).withValues(alpha: 0.8), + trackHeight: 8, + overlayColor: Colors.transparent, + padding: const EdgeInsets.symmetric(horizontal: 0), + + ), + child: RangeSlider( + values: widget.currentRange ?? widget.maxRange, + min: widget.maxRange.start, + max: widget.maxRange.end, + onChanged: widget.onChange, + ), + ), + ], + ); } } From f97207251da3769fe64ed25808c0d5f1e4fa3642 Mon Sep 17 00:00:00 2001 From: Tasneem AbdElhakeem Date: Wed, 29 Oct 2025 01:32:16 +0300 Subject: [PATCH 03/46] feat: add flutter_svg and icon assets --- asset/icons/ic_search.svg | 4 ++ pubspec.lock | 98 ++++++++++++++++++++++++++++++++++++++- pubspec.yaml | 6 +-- 3 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 asset/icons/ic_search.svg diff --git a/asset/icons/ic_search.svg b/asset/icons/ic_search.svg new file mode 100644 index 0000000..5afb8ec --- /dev/null +++ b/asset/icons/ic_search.svg @@ -0,0 +1,4 @@ + + + + diff --git a/pubspec.lock b/pubspec.lock index c1b51d3..950d42f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -70,11 +78,35 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_svg: + dependency: "direct dev" + description: + name: flutter_svg + sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678 + url: "https://pub.dev" + source: hosted + version: "2.2.1" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + url: "https://pub.dev" + source: hosted + version: "1.5.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" leak_tracker: dependency: transitive description: @@ -139,6 +171,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + url: "https://pub.dev" + source: hosted + version: "7.0.1" sky_engine: dependency: transitive description: flutter @@ -192,6 +240,38 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.6" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc + url: "https://pub.dev" + source: hosted + version: "1.1.19" vector_math: dependency: transitive description: @@ -208,6 +288,22 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" sdks: dart: ">=3.9.2 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index 86608dc..98c3e2c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,6 +45,7 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^5.0.0 + flutter_svg: ^2.2.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -58,9 +59,8 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - asset/icons/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images From 4f712b4900103c752a0aa19bdb3a023a38977d68 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Wed, 29 Oct 2025 02:18:14 +0300 Subject: [PATCH 04/46] fix: assets path --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 98c3e2c..9202065 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,7 +60,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - - asset/icons/ + - assets/icons/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images From 9f7cfdff4b0de4db7112c7ad8c574a68bb67425f Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Wed, 29 Oct 2025 02:18:31 +0300 Subject: [PATCH 05/46] feat: arrow icons --- assets/icons/arrow_down.svg | 4 ++++ assets/icons/arrow_up.svg | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 assets/icons/arrow_down.svg create mode 100644 assets/icons/arrow_up.svg diff --git a/assets/icons/arrow_down.svg b/assets/icons/arrow_down.svg new file mode 100644 index 0000000..07d7cb0 --- /dev/null +++ b/assets/icons/arrow_down.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/arrow_up.svg b/assets/icons/arrow_up.svg new file mode 100644 index 0000000..667369c --- /dev/null +++ b/assets/icons/arrow_up.svg @@ -0,0 +1,4 @@ + + + + From efa4f97b3cf3ff5236d01d814920fe93f29f1859 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Wed, 29 Oct 2025 02:38:49 +0300 Subject: [PATCH 06/46] feat: range chip --- .../screen/search/widget/range_chip.dart | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 lib/presentation/screen/search/widget/range_chip.dart diff --git a/lib/presentation/screen/search/widget/range_chip.dart b/lib/presentation/screen/search/widget/range_chip.dart new file mode 100644 index 0000000..f381fca --- /dev/null +++ b/lib/presentation/screen/search/widget/range_chip.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class RangeChip extends StatefulWidget { + final String label; + final int value; + final String image; + final String unit; + + const RangeChip({ + super.key, + required this.label, + required this.value, + required this.image, + required this.unit, + }); + + @override + State createState() => _RangeChipState(); +} + +class _RangeChipState extends State { + @override + Widget build(BuildContext context) { + return Chip( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(100.0)), + label: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SvgPicture.asset( + widget.image, + colorFilter: ColorFilter.mode(Colors.black, BlendMode.srcIn), + width: 24, + height: 24, + ), + + Expanded( + flex: 1, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.label, + style: TextStyle(fontSize: 12, color: Colors.grey), + ), + Text( + widget.value.toString(), + style: TextStyle(fontSize: 16, color: Colors.grey), + ), + ], + ), + ), + ), + Text(widget.unit, style: TextStyle(fontSize: 16, color: Colors.blue)), + ], + ), + ); + } +} From 7805448010e5747edd27bd857b7ae6b45c149c66 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Wed, 29 Oct 2025 02:39:42 +0300 Subject: [PATCH 07/46] feat: search screen price filter --- .../screen/search/search_screen.dart | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/lib/presentation/screen/search/search_screen.dart b/lib/presentation/screen/search/search_screen.dart index 9ab1c52..9aa88a1 100644 --- a/lib/presentation/screen/search/search_screen.dart +++ b/lib/presentation/screen/search/search_screen.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:share_space/presentation/screen/search/widget/range_chip.dart'; +import 'package:share_space/presentation/screen/search/widget/range_slider.dart'; + class SearchScreen extends StatefulWidget { const SearchScreen({super.key}); @@ -8,10 +11,44 @@ class SearchScreen extends StatefulWidget { } class _SearchScreenState extends State { + RangeValues _currentRange = const RangeValues(100, 500); + @override Widget build(BuildContext context) { - return Container( - + return Column( + children: [ + RangeSliderWidget( + maxRange: const RangeValues(0, 1000), + currentRange: _currentRange, + onChange: (newRange) { + setState(() { + _currentRange = newRange; + }); + }, + currency: "IQD", + ), + Row( + children: [ + Expanded( + child: RangeChip( + label: "Min", + value: _currentRange.start.toInt(), + image: "assets/icons/arrow_down.svg", + unit: "IQD", + ), + ), + const SizedBox(width: 16), + Expanded( + child: RangeChip( + label: "Max", + value: _currentRange.end.toInt(), + image: "assets/icons/arrow_down.svg", + unit: "IQD", + ), + ), + ], + ), + ], ); } } From fdc17b35ea143f93b376a57adf709abdc140413e Mon Sep 17 00:00:00 2001 From: Tasneem AbdElhakeem Date: Wed, 29 Oct 2025 15:53:08 +0300 Subject: [PATCH 08/46] refactor: remove duplicate assets package --- {asset => assets}/icons/ic_search.svg | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {asset => assets}/icons/ic_search.svg (100%) diff --git a/asset/icons/ic_search.svg b/assets/icons/ic_search.svg similarity index 100% rename from asset/icons/ic_search.svg rename to assets/icons/ic_search.svg From 34229e30285959107a03576f4d545ddf2d27577f Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Wed, 29 Oct 2025 16:54:13 +0300 Subject: [PATCH 09/46] feat: filter bottomsheet init --- .../screen/search/filter_bottomsheet.dart | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 lib/presentation/screen/search/filter_bottomsheet.dart diff --git a/lib/presentation/screen/search/filter_bottomsheet.dart b/lib/presentation/screen/search/filter_bottomsheet.dart new file mode 100644 index 0000000..a635308 --- /dev/null +++ b/lib/presentation/screen/search/filter_bottomsheet.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:share_space/presentation/screen/search/widget/range_chip.dart'; +import 'package:share_space/presentation/screen/search/widget/range_slider.dart'; + +class FilterBottomSheet extends StatefulWidget { + const FilterBottomSheet({super.key}); + + @override + State createState() => _FilterBottomSheetState(); +} + +class _FilterBottomSheetState extends State { + RangeValues _currentRange = const RangeValues(100, 500); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + RangeSliderWidget( + maxRange: const RangeValues(0, 1000), + currentRange: _currentRange, + onChange: (newRange) { + setState(() { + _currentRange = newRange; + }); + }, + currency: "IQD", + ), + Row( + children: [ + Expanded( + child: RangeChip( + label: "Min", + value: _currentRange.start.toInt(), + image: "assets/icons/arrow_down.svg", + unit: "IQD", + ), + ), + const SizedBox(width: 16), + Expanded( + child: RangeChip( + label: "Max", + value: _currentRange.end.toInt(), + image: "assets/icons/arrow_down.svg", + unit: "IQD", + ), + ), + ], + ), + ], + ); + } +} From f62d2724a2a2ea75a7a7b88f42107872ee3a3be4 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Thu, 30 Oct 2025 17:28:54 +0300 Subject: [PATCH 10/46] feat: custom chip for filter --- .../screen/search/widget/custom_chip.dart | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 lib/presentation/screen/search/widget/custom_chip.dart diff --git a/lib/presentation/screen/search/widget/custom_chip.dart b/lib/presentation/screen/search/widget/custom_chip.dart new file mode 100644 index 0000000..755aacf --- /dev/null +++ b/lib/presentation/screen/search/widget/custom_chip.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class CustomChip extends StatefulWidget { + final String label; + final String icon; + bool isSelected; + final VoidCallback? onSelect; + + + CustomChip({ + super.key, + required this.label, + this.isSelected = false, + this.icon = '', + this.onSelect, + }); + + @override + State createState() => _CustomChipState(); +} + +class _CustomChipState extends State { + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => setState(() { + widget.onSelect?.call(); + }), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: widget.isSelected + ? const Color(0xFF0A84FF).withValues(alpha: 0.1) + : const Color(0xFFF9FBFB), + border: Border.all( + color: widget.isSelected + ? const Color(0x3D84E2FE).withValues(alpha: 0.24) + : const Color(0x141F1F1F), + width: 1, + ), + borderRadius: BorderRadius.circular(20), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.icon.isNotEmpty) ...[ + SvgPicture.asset( + widget.icon, + colorFilter: ColorFilter.mode( + widget.isSelected ? Colors.blueAccent : Colors.orange, + BlendMode.srcIn, + ), + width: 16, + height: 16, + ), + const SizedBox(width: 8), + ], + Text( + widget.label, + style: TextStyle( + color: widget.isSelected ? Colors.blue : Colors.black, + fontSize: 14, + ), + ), + ], + ), + ), + ); + } +} From c71961c968ba8819c4869326b128d42a0c9c99c6 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Thu, 30 Oct 2025 17:29:38 +0300 Subject: [PATCH 11/46] feat: filter by rate --- assets/icons/star.svg | 4 ++ .../screen/search/widget/rating_filter.dart | 54 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 assets/icons/star.svg create mode 100644 lib/presentation/screen/search/widget/rating_filter.dart diff --git a/assets/icons/star.svg b/assets/icons/star.svg new file mode 100644 index 0000000..5700111 --- /dev/null +++ b/assets/icons/star.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/presentation/screen/search/widget/rating_filter.dart b/lib/presentation/screen/search/widget/rating_filter.dart new file mode 100644 index 0000000..830b3cf --- /dev/null +++ b/lib/presentation/screen/search/widget/rating_filter.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; + +import 'custom_chip.dart'; + +class RatingFilter extends StatefulWidget { + List options; + int selectedIndex; + final Function(int) onSelect; + + RatingFilter({ + super.key, + required this.options, + required this.selectedIndex, + required this.onSelect, + }); + + @override + State createState() => _RatingFilterState(); +} + +class _RatingFilterState extends State { + late List _isSelected; + + @override + void initState() { + super.initState(); + _isSelected = List.filled(widget.options.length, false); + } + + @override + Widget build(BuildContext context) { + return Row( + children: [ + for (var index = 0; index < widget.options.length; index++) + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: CustomChip( + label: widget.options[index], + icon: (index != 0) ? "assets/icons/star.svg" : "", + isSelected: _isSelected[index], + onSelect: () { + setState(() { + _isSelected[widget.selectedIndex] = false; + widget.selectedIndex = index; + _isSelected[widget.selectedIndex] = !_isSelected[index]; + }); + widget.onSelect(index); + }, + ), + ), + ], + ); + } +} From c900fa1188688efa47b13ff7c44507a9b4edc7e4 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Thu, 30 Oct 2025 17:30:39 +0300 Subject: [PATCH 12/46] feat: filter by rate to filter bottom sheet --- .../screen/search/filter_bottomsheet.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/presentation/screen/search/filter_bottomsheet.dart b/lib/presentation/screen/search/filter_bottomsheet.dart index a635308..818c9cd 100644 --- a/lib/presentation/screen/search/filter_bottomsheet.dart +++ b/lib/presentation/screen/search/filter_bottomsheet.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:share_space/presentation/screen/search/widget/range_chip.dart'; import 'package:share_space/presentation/screen/search/widget/range_slider.dart'; +import 'package:share_space/presentation/screen/search/widget/rating_filter.dart'; class FilterBottomSheet extends StatefulWidget { const FilterBottomSheet({super.key}); @@ -11,6 +12,8 @@ class FilterBottomSheet extends StatefulWidget { class _FilterBottomSheetState extends State { RangeValues _currentRange = const RangeValues(100, 500); + List rateOptions = ["All", "1", "2", "3", "4", "5"]; + int selectedRateIndex = 0; @override Widget build(BuildContext context) { @@ -47,6 +50,15 @@ class _FilterBottomSheetState extends State { ), ], ), + RatingFilter( + options: rateOptions, + selectedIndex: selectedRateIndex, + onSelect: (index) { + setState(() { + selectedRateIndex = index; + }); + }, + ), ], ); } From ccbb1b50e37170787ab0d60c48e22178caf74df5 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Thu, 30 Oct 2025 19:21:23 +0300 Subject: [PATCH 13/46] refactor: control filter state from the bottom sheet --- .../screen/search/filter_bottomsheet.dart | 14 ++++++++++++-- .../screen/search/widget/rating_filter.dart | 17 ++--------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/lib/presentation/screen/search/filter_bottomsheet.dart b/lib/presentation/screen/search/filter_bottomsheet.dart index 818c9cd..b53e2c8 100644 --- a/lib/presentation/screen/search/filter_bottomsheet.dart +++ b/lib/presentation/screen/search/filter_bottomsheet.dart @@ -13,7 +13,7 @@ class FilterBottomSheet extends StatefulWidget { class _FilterBottomSheetState extends State { RangeValues _currentRange = const RangeValues(100, 500); List rateOptions = ["All", "1", "2", "3", "4", "5"]; - int selectedRateIndex = 0; + List selectedRateIndex = [0]; @override Widget build(BuildContext context) { @@ -55,7 +55,17 @@ class _FilterBottomSheetState extends State { selectedIndex: selectedRateIndex, onSelect: (index) { setState(() { - selectedRateIndex = index; + if (selectedRateIndex.contains(index)) { + selectedRateIndex.remove(index); + if (selectedRateIndex.isEmpty) { + selectedRateIndex.add(0); + } + } else { + if (selectedRateIndex.length == 1 && selectedRateIndex.first == 0) { + selectedRateIndex.clear(); + } + selectedRateIndex.add(index); + } }); }, ), diff --git a/lib/presentation/screen/search/widget/rating_filter.dart b/lib/presentation/screen/search/widget/rating_filter.dart index 830b3cf..aae4685 100644 --- a/lib/presentation/screen/search/widget/rating_filter.dart +++ b/lib/presentation/screen/search/widget/rating_filter.dart @@ -4,7 +4,7 @@ import 'custom_chip.dart'; class RatingFilter extends StatefulWidget { List options; - int selectedIndex; + List selectedIndex; final Function(int) onSelect; RatingFilter({ @@ -19,14 +19,6 @@ class RatingFilter extends StatefulWidget { } class _RatingFilterState extends State { - late List _isSelected; - - @override - void initState() { - super.initState(); - _isSelected = List.filled(widget.options.length, false); - } - @override Widget build(BuildContext context) { return Row( @@ -37,13 +29,8 @@ class _RatingFilterState extends State { child: CustomChip( label: widget.options[index], icon: (index != 0) ? "assets/icons/star.svg" : "", - isSelected: _isSelected[index], + isSelected: widget.selectedIndex.contains(index), onSelect: () { - setState(() { - _isSelected[widget.selectedIndex] = false; - widget.selectedIndex = index; - _isSelected[widget.selectedIndex] = !_isSelected[index]; - }); widget.onSelect(index); }, ), From eabed4c16d78d9b60cdf75db1dd2e5e4109453d1 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Thu, 30 Oct 2025 20:06:15 +0300 Subject: [PATCH 14/46] refactor: rename variable list --- .../screen/search/filter_bottomsheet.dart | 20 ++++++++++--------- .../screen/search/widget/rating_filter.dart | 9 +++++---- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/presentation/screen/search/filter_bottomsheet.dart b/lib/presentation/screen/search/filter_bottomsheet.dart index b53e2c8..53bf09e 100644 --- a/lib/presentation/screen/search/filter_bottomsheet.dart +++ b/lib/presentation/screen/search/filter_bottomsheet.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:share_space/presentation/screen/search/widget/range_chip.dart'; import 'package:share_space/presentation/screen/search/widget/range_slider.dart'; import 'package:share_space/presentation/screen/search/widget/rating_filter.dart'; +import 'package:share_space/presentation/screen/search/widget/services_filter.dart'; class FilterBottomSheet extends StatefulWidget { const FilterBottomSheet({super.key}); @@ -13,7 +14,7 @@ class FilterBottomSheet extends StatefulWidget { class _FilterBottomSheetState extends State { RangeValues _currentRange = const RangeValues(100, 500); List rateOptions = ["All", "1", "2", "3", "4", "5"]; - List selectedRateIndex = [0]; + List selectedRateIndices = [0]; @override Widget build(BuildContext context) { @@ -52,23 +53,24 @@ class _FilterBottomSheetState extends State { ), RatingFilter( options: rateOptions, - selectedIndex: selectedRateIndex, + selectedIndices: selectedRateIndices, onSelect: (index) { setState(() { - if (selectedRateIndex.contains(index)) { - selectedRateIndex.remove(index); - if (selectedRateIndex.isEmpty) { - selectedRateIndex.add(0); + if (selectedRateIndices.contains(index)) { + selectedRateIndices.remove(index); + if (selectedRateIndices.isEmpty) { + selectedRateIndices.add(0); } } else { - if (selectedRateIndex.length == 1 && selectedRateIndex.first == 0) { - selectedRateIndex.clear(); + if (selectedRateIndices.length == 1 && selectedRateIndices.first == 0) { + selectedRateIndices.clear(); } - selectedRateIndex.add(index); + selectedRateIndices.add(index); } }); }, ), + // ServicesFilter(selectedIndex: selectedIndex, onSelect: onSelect) ], ); } diff --git a/lib/presentation/screen/search/widget/rating_filter.dart b/lib/presentation/screen/search/widget/rating_filter.dart index aae4685..312896a 100644 --- a/lib/presentation/screen/search/widget/rating_filter.dart +++ b/lib/presentation/screen/search/widget/rating_filter.dart @@ -4,13 +4,13 @@ import 'custom_chip.dart'; class RatingFilter extends StatefulWidget { List options; - List selectedIndex; + List selectedIndices; final Function(int) onSelect; RatingFilter({ super.key, required this.options, - required this.selectedIndex, + required this.selectedIndices, required this.onSelect, }); @@ -22,14 +22,15 @@ class _RatingFilterState extends State { @override Widget build(BuildContext context) { return Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ for (var index = 0; index < widget.options.length; index++) Padding( - padding: const EdgeInsets.only(right: 8.0), + padding: const EdgeInsets.symmetric(horizontal: 4.0), child: CustomChip( label: widget.options[index], icon: (index != 0) ? "assets/icons/star.svg" : "", - isSelected: widget.selectedIndex.contains(index), + isSelected: widget.selectedIndices.contains(index), onSelect: () { widget.onSelect(index); }, From a7ba24e45c2dcad57943a4edaf84d87520ce319d Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Thu, 30 Oct 2025 20:06:35 +0300 Subject: [PATCH 15/46] feat: add services icons --- assets/icons/canvas.svg | 5 +++++ assets/icons/plug.svg | 5 +++++ assets/icons/wifi.svg | 4 ++++ 3 files changed, 14 insertions(+) create mode 100644 assets/icons/canvas.svg create mode 100644 assets/icons/plug.svg create mode 100644 assets/icons/wifi.svg diff --git a/assets/icons/canvas.svg b/assets/icons/canvas.svg new file mode 100644 index 0000000..e65a06d --- /dev/null +++ b/assets/icons/canvas.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/plug.svg b/assets/icons/plug.svg new file mode 100644 index 0000000..f60cab0 --- /dev/null +++ b/assets/icons/plug.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/wifi.svg b/assets/icons/wifi.svg new file mode 100644 index 0000000..ac100cf --- /dev/null +++ b/assets/icons/wifi.svg @@ -0,0 +1,4 @@ + + + + From f0253827fa55a3366eaebbc53adffacb2a5fb5fd Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Thu, 30 Oct 2025 20:17:28 +0300 Subject: [PATCH 16/46] fix: select all remove other choices --- lib/presentation/screen/search/filter_bottomsheet.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/presentation/screen/search/filter_bottomsheet.dart b/lib/presentation/screen/search/filter_bottomsheet.dart index 53bf09e..e12970e 100644 --- a/lib/presentation/screen/search/filter_bottomsheet.dart +++ b/lib/presentation/screen/search/filter_bottomsheet.dart @@ -62,6 +62,9 @@ class _FilterBottomSheetState extends State { selectedRateIndices.add(0); } } else { + if (index == 0) { + selectedRateIndices.clear(); + } if (selectedRateIndices.length == 1 && selectedRateIndices.first == 0) { selectedRateIndices.clear(); } From ec9849a2184094e2c951c4cc57d2c52c05a04d3f Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Thu, 30 Oct 2025 23:55:33 +0300 Subject: [PATCH 17/46] fix: only color filter on select --- lib/presentation/screen/search/widget/custom_chip.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/presentation/screen/search/widget/custom_chip.dart b/lib/presentation/screen/search/widget/custom_chip.dart index 755aacf..a4f4ebe 100644 --- a/lib/presentation/screen/search/widget/custom_chip.dart +++ b/lib/presentation/screen/search/widget/custom_chip.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:share_space/presentation/design_system/theme/app_theme.dart'; class CustomChip extends StatefulWidget { final String label; @@ -7,7 +8,6 @@ class CustomChip extends StatefulWidget { bool isSelected; final VoidCallback? onSelect; - CustomChip({ super.key, required this.label, @@ -47,10 +47,9 @@ class _CustomChipState extends State { if (widget.icon.isNotEmpty) ...[ SvgPicture.asset( widget.icon, - colorFilter: ColorFilter.mode( - widget.isSelected ? Colors.blueAccent : Colors.orange, - BlendMode.srcIn, - ), + colorFilter: widget.isSelected + ? ColorFilter.mode(Colors.blueAccent, BlendMode.srcIn) + : null, width: 16, height: 16, ), From 2045d45baedbd5f88962c3fbfa445e0732d729e0 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Thu, 30 Oct 2025 23:56:00 +0300 Subject: [PATCH 18/46] feat: add services chips --- .../screen/search/filter_bottomsheet.dart | 35 ++++++++++- .../screen/search/widget/services_filter.dart | 59 +++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 lib/presentation/screen/search/widget/services_filter.dart diff --git a/lib/presentation/screen/search/filter_bottomsheet.dart b/lib/presentation/screen/search/filter_bottomsheet.dart index e12970e..0b1ffa7 100644 --- a/lib/presentation/screen/search/filter_bottomsheet.dart +++ b/lib/presentation/screen/search/filter_bottomsheet.dart @@ -15,6 +15,14 @@ class _FilterBottomSheetState extends State { RangeValues _currentRange = const RangeValues(100, 500); List rateOptions = ["All", "1", "2", "3", "4", "5"]; List selectedRateIndices = [0]; + List servicesOptions = [ + "All", + "Wifi", + "A/C", + "Whiteboard", + "Power Backup", + ]; + List selectedServicesIndices = [0]; @override Widget build(BuildContext context) { @@ -65,7 +73,8 @@ class _FilterBottomSheetState extends State { if (index == 0) { selectedRateIndices.clear(); } - if (selectedRateIndices.length == 1 && selectedRateIndices.first == 0) { + if (selectedRateIndices.length == 1 && + selectedRateIndices.first == 0) { selectedRateIndices.clear(); } selectedRateIndices.add(index); @@ -73,7 +82,29 @@ class _FilterBottomSheetState extends State { }); }, ), - // ServicesFilter(selectedIndex: selectedIndex, onSelect: onSelect) + ServicesFilter( + options: servicesOptions, + selectedIndices: selectedServicesIndices, + onSelect: (index) { + setState(() { + if (selectedServicesIndices.contains(index)) { + selectedServicesIndices.remove(index); + if (selectedServicesIndices.isEmpty) { + selectedServicesIndices.add(0); + } + } else { + if (index == 0) { + selectedServicesIndices.clear(); + } + if (selectedServicesIndices.length == 1 && + selectedServicesIndices.first == 0) { + selectedServicesIndices.clear(); + } + selectedServicesIndices.add(index); + } + }); + }, + ), ], ); } diff --git a/lib/presentation/screen/search/widget/services_filter.dart b/lib/presentation/screen/search/widget/services_filter.dart new file mode 100644 index 0000000..5fdb629 --- /dev/null +++ b/lib/presentation/screen/search/widget/services_filter.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; + +import 'custom_chip.dart'; + +class ServicesFilter extends StatefulWidget { + List options; + List selectedIndices; + final Function(int) onSelect; + + ServicesFilter({ + super.key, + required this.options, + required this.selectedIndices, + required this.onSelect, + }); + + @override + State createState() => _ServicesFilterState(); +} + +class _ServicesFilterState extends State { + @override + Widget build(BuildContext context) { + return Wrap( + alignment: WrapAlignment.start, + spacing: 8.0, + runSpacing: 8.0, + children: [ + for (var index = 0; index < widget.options.length; index++) + CustomChip( + label: widget.options[index], + icon: iconPathFor(widget.options[index]), + isSelected: widget.selectedIndices.contains(index), + onSelect: () { + widget.onSelect(index); + }, + ), + ], + ); + } + + + String iconPathFor(String label) { + final key = label.trim().toLowerCase(); + const icons = { + 'wifi': 'assets/icons/wifi.svg', + 'whiteboard': 'assets/icons/canvas.svg', + 'call': 'assets/icons/phone.svg', + 'power backup': 'assets/icons/plug.svg', + }; + return icons[key] ?? ''; + } + + +} + + + + From 8e8f894057329b960f72c9d5ddb6dbf2e2f0b6a3 Mon Sep 17 00:00:00 2001 From: Tasneem AbdElhakeem Date: Fri, 31 Oct 2025 00:14:34 +0200 Subject: [PATCH 19/46] feat: add filter button --- assets/icons/filter.svg | 7 +++ .../screen/search/widget/filter_button.dart | 49 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 assets/icons/filter.svg create mode 100644 lib/presentation/screen/search/widget/filter_button.dart diff --git a/assets/icons/filter.svg b/assets/icons/filter.svg new file mode 100644 index 0000000..0f119ac --- /dev/null +++ b/assets/icons/filter.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/presentation/screen/search/widget/filter_button.dart b/lib/presentation/screen/search/widget/filter_button.dart new file mode 100644 index 0000000..d968b6b --- /dev/null +++ b/lib/presentation/screen/search/widget/filter_button.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:share_space/presentation/design_system/theme/app_theme.dart'; + +class FilterButton extends StatelessWidget { + const FilterButton({super.key}); + + @override + Widget build(BuildContext context) { + final theme = AppTheme.of(context); + return ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(100)), + padding: const EdgeInsets.all(0), + minimumSize: const Size(48, 48), + elevation: 0, + ), + onPressed: () { + // TODO: Implement filter action + }, + child: Ink( + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFF50B5E7), Color(0xFF19C6F9)], + ), + borderRadius: BorderRadius.circular(100), + ), + child: Container( + padding: const EdgeInsets.all(14), + constraints: const BoxConstraints(minWidth: 48, minHeight: 48), + alignment: Alignment.center, + child: SvgPicture.asset( + 'assets/icons/filter.svg', + width: 24, + height: 24, + colorFilter: const ColorFilter.mode( + Colors.white, // TODO: use theme colors + BlendMode.srcIn, + ), + ), + ), + ), + ); + } +} From 6dd12ac4a51275cf670b59bed23f1a002335b6e5 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 31 Oct 2025 00:18:41 +0200 Subject: [PATCH 20/46] refactor: use theme in range chip --- .../screen/search/widget/range_chip.dart | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/presentation/screen/search/widget/range_chip.dart b/lib/presentation/screen/search/widget/range_chip.dart index f381fca..cc7a24d 100644 --- a/lib/presentation/screen/search/widget/range_chip.dart +++ b/lib/presentation/screen/search/widget/range_chip.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import '../../../design_system/theme/app_theme.dart'; class RangeChip extends StatefulWidget { final String label; @@ -22,6 +23,7 @@ class RangeChip extends StatefulWidget { class _RangeChipState extends State { @override Widget build(BuildContext context) { + final theme = AppTheme.of(context); return Chip( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(100.0)), label: Row( @@ -30,7 +32,6 @@ class _RangeChipState extends State { children: [ SvgPicture.asset( widget.image, - colorFilter: ColorFilter.mode(Colors.black, BlendMode.srcIn), width: 24, height: 24, ), @@ -44,17 +45,23 @@ class _RangeChipState extends State { children: [ Text( widget.label, - style: TextStyle(fontSize: 12, color: Colors.grey), + style: theme.typography.textTheme.labelSmall?.copyWith( + color: theme.colors.body, + ), ), Text( widget.value.toString(), - style: TextStyle(fontSize: 16, color: Colors.grey), + style: theme.typography.textTheme.labelLarge?.copyWith( + color: theme.colors.body, + ), ), ], ), ), ), - Text(widget.unit, style: TextStyle(fontSize: 16, color: Colors.blue)), + Text(widget.unit, style: theme.typography.textTheme.labelSmall?.copyWith( + color: theme.colors.primary, + )), ], ), ); From 5130c3c59ff521bd8c5dc10b45d47811dfc45f2f Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 31 Oct 2025 16:29:18 +0200 Subject: [PATCH 21/46] feat: use theme in custom chip --- .../screen/search/widget/custom_chip.dart | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/presentation/screen/search/widget/custom_chip.dart b/lib/presentation/screen/search/widget/custom_chip.dart index a4f4ebe..9e4ab72 100644 --- a/lib/presentation/screen/search/widget/custom_chip.dart +++ b/lib/presentation/screen/search/widget/custom_chip.dart @@ -4,6 +4,7 @@ import 'package:share_space/presentation/design_system/theme/app_theme.dart'; class CustomChip extends StatefulWidget { final String label; + Color labelColor; final String icon; bool isSelected; final VoidCallback? onSelect; @@ -11,8 +12,9 @@ class CustomChip extends StatefulWidget { CustomChip({ super.key, required this.label, - this.isSelected = false, + this.labelColor = const Color(0x991F1F1F), this.icon = '', + this.isSelected = false, this.onSelect, }); @@ -23,6 +25,7 @@ class CustomChip extends StatefulWidget { class _CustomChipState extends State { @override Widget build(BuildContext context) { + final theme = AppTheme.of(context); return GestureDetector( onTap: () => setState(() { widget.onSelect?.call(); @@ -31,13 +34,13 @@ class _CustomChipState extends State { padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: widget.isSelected - ? const Color(0xFF0A84FF).withValues(alpha: 0.1) - : const Color(0xFFF9FBFB), + ? theme.colors.blueVariant + : theme.colors.surface, border: Border.all( color: widget.isSelected - ? const Color(0x3D84E2FE).withValues(alpha: 0.24) - : const Color(0x141F1F1F), - width: 1, + ? theme.colors.primary + : theme.colors.stroke, + width: 0.5, ), borderRadius: BorderRadius.circular(20), ), @@ -48,7 +51,7 @@ class _CustomChipState extends State { SvgPicture.asset( widget.icon, colorFilter: widget.isSelected - ? ColorFilter.mode(Colors.blueAccent, BlendMode.srcIn) + ? ColorFilter.mode(theme.colors.primary, BlendMode.srcIn) : null, width: 16, height: 16, @@ -57,9 +60,8 @@ class _CustomChipState extends State { ], Text( widget.label, - style: TextStyle( - color: widget.isSelected ? Colors.blue : Colors.black, - fontSize: 14, + style: theme.typography.textTheme.labelSmall?.copyWith( + color: widget.isSelected ? theme.colors.primary : widget.labelColor, ), ), ], From d3f6abaea49228dcf8e7647143f872ea7b04ce93 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 31 Oct 2025 16:58:21 +0200 Subject: [PATCH 22/46] feat: primary button --- .../design_system/widget/primary_button.dart | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 lib/presentation/design_system/widget/primary_button.dart diff --git a/lib/presentation/design_system/widget/primary_button.dart b/lib/presentation/design_system/widget/primary_button.dart new file mode 100644 index 0000000..058501e --- /dev/null +++ b/lib/presentation/design_system/widget/primary_button.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import '../theme/app_theme.dart'; + +class PrimaryButton extends StatefulWidget { + final Function() onPressed; + final String label; + final bool isBig; + bool isDisabled; + + PrimaryButton({ + super.key, + required this.onPressed, + required this.label, + this.isBig = true, + this.isDisabled = false, + }); + + @override + State createState() => _PrimaryButtonState(); +} + +class _PrimaryButtonState extends State { + @override + Widget build(BuildContext context) { + final theme = AppTheme.of(context); + return ElevatedButton( + style: ButtonStyle( + padding: WidgetStateProperty.all(EdgeInsets.zero), + shape: WidgetStateProperty.all( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(50)), + ), + shadowColor: WidgetStateProperty.all(Colors.transparent), + ), + onPressed: widget.isDisabled ? null : widget.onPressed, + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + height: widget.isBig ? 52 : 32, + alignment: Alignment.center, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: widget.isDisabled + ? [theme.colors.disable, theme.colors.disable] + : [Color(0xFF50B5E7), Color(0xFF19C6F9)], + ), + borderRadius: BorderRadius.circular(100), + ), + child: Text( + widget.label, + style: theme.typography.textTheme.labelMedium?.copyWith( + color: theme.colors.onPrimary, + fontWeight: FontWeight.w500, + ), + ), + ), + ); + ; + } +} From fcdcec89ad47b3fae973074cdfba47d9917b27f1 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 31 Oct 2025 16:58:50 +0200 Subject: [PATCH 23/46] feat: secondary button --- .../widget/secondary_button.dart | 59 +++++++++++++++++++ .../screen/search/filter_bottomsheet.dart | 14 +++++ 2 files changed, 73 insertions(+) create mode 100644 lib/presentation/design_system/widget/secondary_button.dart diff --git a/lib/presentation/design_system/widget/secondary_button.dart b/lib/presentation/design_system/widget/secondary_button.dart new file mode 100644 index 0000000..7010bf9 --- /dev/null +++ b/lib/presentation/design_system/widget/secondary_button.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import '../theme/app_theme.dart'; + +class SecondaryButton extends StatefulWidget { + final Function() onPressed; + final String label; + final bool isBig; + bool isDisabled; + + SecondaryButton({ + super.key, + required this.onPressed, + required this.label, + this.isBig = true, + this.isDisabled = false, + }); + + @override + State createState() => _SecondaryButtonState(); +} + +class _SecondaryButtonState extends State { + @override + Widget build(BuildContext context) { + final theme = AppTheme.of(context); + return ElevatedButton( + style: ButtonStyle( + padding: WidgetStateProperty.all(EdgeInsets.zero), + shape: WidgetStateProperty.all( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(50)), + ), + shadowColor: WidgetStateProperty.all(Colors.transparent), + ), + onPressed: widget.isDisabled ? null : widget.onPressed, + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + height: widget.isBig ? 52 : 32, + alignment: Alignment.center, + decoration: BoxDecoration( + color: widget.isDisabled + ? theme.colors.blueVariant + : theme.colors.disable, + borderRadius: BorderRadius.circular(100), + border: Border.all(width: 0.5, color: theme.colors.stroke), + ), + child: Text( + widget.label, + style: theme.typography.textTheme.labelMedium?.copyWith( + color: widget.isDisabled + ? theme.colors.primary + : theme.colors.onPrimary, + fontWeight: FontWeight.w500, + ), + ), + ), + ); + ; + } +} diff --git a/lib/presentation/screen/search/filter_bottomsheet.dart b/lib/presentation/screen/search/filter_bottomsheet.dart index 0b1ffa7..d5e0098 100644 --- a/lib/presentation/screen/search/filter_bottomsheet.dart +++ b/lib/presentation/screen/search/filter_bottomsheet.dart @@ -4,6 +4,9 @@ import 'package:share_space/presentation/screen/search/widget/range_slider.dart' import 'package:share_space/presentation/screen/search/widget/rating_filter.dart'; import 'package:share_space/presentation/screen/search/widget/services_filter.dart'; +import '../../design_system/widget/primary_button.dart'; +import '../../design_system/widget/secondary_button.dart'; + class FilterBottomSheet extends StatefulWidget { const FilterBottomSheet({super.key}); @@ -105,6 +108,17 @@ class _FilterBottomSheetState extends State { }); }, ), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: SecondaryButton(onPressed: () {}, label: "Clear"), + ), + Expanded( + child: PrimaryButton(onPressed: () {}, label: "Apply Filters"), + ), + ], + ), ], ); } From ca5428fee952e0b8c08e803c8ab5f65919339aa5 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 31 Oct 2025 22:50:28 +0200 Subject: [PATCH 24/46] fix: use color theme --- .../screen/search/widget/range_slider.dart | 26 ++++++++++++++----- .../screen/search/widget/rating_filter.dart | 3 +++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/lib/presentation/screen/search/widget/range_slider.dart b/lib/presentation/screen/search/widget/range_slider.dart index e10f048..2476497 100644 --- a/lib/presentation/screen/search/widget/range_slider.dart +++ b/lib/presentation/screen/search/widget/range_slider.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import '../../../design_system/theme/app_theme.dart'; + class RangeSliderWidget extends StatefulWidget { final RangeValues maxRange; RangeValues? currentRange; @@ -21,26 +23,36 @@ class RangeSliderWidget extends StatefulWidget { class _RangeSliderWidgetState extends State { @override Widget build(BuildContext context) { + final theme = AppTheme.of(context); return Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('${widget.maxRange.start.round()} ${widget.currency}'), - Text('${widget.maxRange.end.round()} ${widget.currency}'), + Text( + '${widget.maxRange.start.round()} ${widget.currency}', + style: theme.typography.textTheme.labelSmall?.copyWith( + color: theme.colors.body, + ), + ), + Text( + '${widget.maxRange.end.round()} ${widget.currency}', + style: theme.typography.textTheme.labelSmall?.copyWith( + color: theme.colors.body, + ), + ), ], ), SliderTheme( data: SliderTheme.of(context).copyWith( - activeTrackColor: Color(0xFFFF7C38), - thumbColor: Color(0xFFFF7C38), + activeTrackColor: theme.colors.secondary, + thumbColor: theme.colors.secondary, overlayShape: RoundSliderOverlayShape(overlayRadius: 20), - overlappingShapeStrokeColor: Colors.black, - inactiveTrackColor: Color(0xFFF9FBFB).withValues(alpha: 0.8), + overlappingShapeStrokeColor: theme.colors.stroke, + inactiveTrackColor: theme.colors.surface, trackHeight: 8, overlayColor: Colors.transparent, padding: const EdgeInsets.symmetric(horizontal: 0), - ), child: RangeSlider( values: widget.currentRange ?? widget.maxRange, diff --git a/lib/presentation/screen/search/widget/rating_filter.dart b/lib/presentation/screen/search/widget/rating_filter.dart index 312896a..52d5b69 100644 --- a/lib/presentation/screen/search/widget/rating_filter.dart +++ b/lib/presentation/screen/search/widget/rating_filter.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import '../../../design_system/theme/app_theme.dart'; import 'custom_chip.dart'; class RatingFilter extends StatefulWidget { @@ -21,6 +22,7 @@ class RatingFilter extends StatefulWidget { class _RatingFilterState extends State { @override Widget build(BuildContext context) { + final theme = AppTheme.of(context); return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -30,6 +32,7 @@ class _RatingFilterState extends State { child: CustomChip( label: widget.options[index], icon: (index != 0) ? "assets/icons/star.svg" : "", + labelColor: theme.colors.yellow, isSelected: widget.selectedIndices.contains(index), onSelect: () { widget.onSelect(index); From 7d34cf93ac352fb4cc12424463198a36b501359e Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 31 Oct 2025 23:35:44 +0200 Subject: [PATCH 25/46] feat: add paddings --- .../screen/search/filter_bottomsheet.dart | 225 +++++++++++------- .../screen/search/widget/services_filter.dart | 40 ++-- 2 files changed, 161 insertions(+), 104 deletions(-) diff --git a/lib/presentation/screen/search/filter_bottomsheet.dart b/lib/presentation/screen/search/filter_bottomsheet.dart index d5e0098..b715ec5 100644 --- a/lib/presentation/screen/search/filter_bottomsheet.dart +++ b/lib/presentation/screen/search/filter_bottomsheet.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:share_space/presentation/design_system/theme/app_theme.dart'; import 'package:share_space/presentation/screen/search/widget/range_chip.dart'; import 'package:share_space/presentation/screen/search/widget/range_slider.dart'; import 'package:share_space/presentation/screen/search/widget/rating_filter.dart'; @@ -29,97 +30,157 @@ class _FilterBottomSheetState extends State { @override Widget build(BuildContext context) { - return Column( - children: [ - RangeSliderWidget( - maxRange: const RangeValues(0, 1000), - currentRange: _currentRange, - onChange: (newRange) { - setState(() { - _currentRange = newRange; - }); - }, - currency: "IQD", - ), - Row( + final theme = AppTheme.of(context); + return Container( + color: theme.colors.surfaceLow, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Column( children: [ - Expanded( - child: RangeChip( - label: "Min", - value: _currentRange.start.toInt(), - image: "assets/icons/arrow_down.svg", - unit: "IQD", + Align( + alignment: Alignment.topLeft, + child: Text( + "Rate", + style: theme.typography.textTheme.titleSmall?.copyWith( + color: theme.colors.title, + ), ), ), - const SizedBox(width: 16), - Expanded( - child: RangeChip( - label: "Max", - value: _currentRange.end.toInt(), - image: "assets/icons/arrow_down.svg", - unit: "IQD", + SizedBox(height: 12), + RatingFilter( + options: rateOptions, + selectedIndices: selectedRateIndices, + onSelect: (index) { + setState(() { + if (selectedRateIndices.contains(index)) { + selectedRateIndices.remove(index); + if (selectedRateIndices.isEmpty) { + selectedRateIndices.add(0); + } + } else { + if (index == 0) { + selectedRateIndices.clear(); + } + if (selectedRateIndices.length == 1 && + selectedRateIndices.first == 0) { + selectedRateIndices.clear(); + } + selectedRateIndices.add(index); + } + }); + }, + ), + SizedBox(height: 16), + Align( + alignment: Alignment.topLeft, + child: Text( + "Price Range", + style: theme.typography.textTheme.titleSmall?.copyWith( + color: theme.colors.title, + ), ), ), - ], - ), - RatingFilter( - options: rateOptions, - selectedIndices: selectedRateIndices, - onSelect: (index) { - setState(() { - if (selectedRateIndices.contains(index)) { - selectedRateIndices.remove(index); - if (selectedRateIndices.isEmpty) { - selectedRateIndices.add(0); - } - } else { - if (index == 0) { - selectedRateIndices.clear(); - } - if (selectedRateIndices.length == 1 && - selectedRateIndices.first == 0) { - selectedRateIndices.clear(); - } - selectedRateIndices.add(index); - } - }); - }, - ), - ServicesFilter( - options: servicesOptions, - selectedIndices: selectedServicesIndices, - onSelect: (index) { - setState(() { - if (selectedServicesIndices.contains(index)) { - selectedServicesIndices.remove(index); - if (selectedServicesIndices.isEmpty) { - selectedServicesIndices.add(0); - } - } else { - if (index == 0) { - selectedServicesIndices.clear(); - } - if (selectedServicesIndices.length == 1 && - selectedServicesIndices.first == 0) { - selectedServicesIndices.clear(); - } - selectedServicesIndices.add(index); - } - }); - }, - ), - Row( - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - child: SecondaryButton(onPressed: () {}, label: "Clear"), + SizedBox(height: 12), + RangeSliderWidget( + maxRange: const RangeValues(0, 1000), + currentRange: _currentRange, + onChange: (newRange) { + setState(() { + _currentRange = newRange; + }); + }, + currency: "IQD", + ), + SizedBox(height: 20), + Row( + children: [ + Expanded( + child: RangeChip( + label: "Min", + value: _currentRange.start.toInt(), + image: "assets/icons/arrow_down.svg", + unit: "IQD", + ), + ), + const SizedBox(width: 16), + Expanded( + child: RangeChip( + label: "Max", + value: _currentRange.end.toInt(), + image: "assets/icons/arrow_down.svg", + unit: "IQD", + ), + ), + ], + ), + SizedBox(height: 16), + Align( + alignment: Alignment.topLeft, + child: Text( + "Services", + style: theme.typography.textTheme.titleSmall?.copyWith( + color: theme.colors.title, + ), + ), + ), + SizedBox(height: 12), + ServicesFilter( + options: servicesOptions, + selectedIndices: selectedServicesIndices, + onSelect: (index) { + setState(() { + if (selectedServicesIndices.contains(index)) { + selectedServicesIndices.remove(index); + if (selectedServicesIndices.isEmpty) { + selectedServicesIndices.add(0); + } + } else { + if (index == 0) { + selectedServicesIndices.clear(); + } + if (selectedServicesIndices.length == 1 && + selectedServicesIndices.first == 0) { + selectedServicesIndices.clear(); + } + selectedServicesIndices.add(index); + } + }); + }, + ), + SizedBox(height: 16), + Align( + alignment: Alignment.topLeft, + child: Text( + "Location", + style: theme.typography.textTheme.titleSmall?.copyWith( + color: theme.colors.title, + ), + ), ), - Expanded( - child: PrimaryButton(onPressed: () {}, label: "Apply Filters"), + SizedBox(height: 12), + // TODO: Add Location Filter Widget here + SizedBox(height: 24), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: SecondaryButton( + onPressed: () {}, + label: "Clear", + isDisabled: true, + ), + ), + Expanded( + child: PrimaryButton( + onPressed: () {}, + label: "Apply Filters", + ), + ), + ], ), ], ), - ], + ), ); } } diff --git a/lib/presentation/screen/search/widget/services_filter.dart b/lib/presentation/screen/search/widget/services_filter.dart index 5fdb629..540bd27 100644 --- a/lib/presentation/screen/search/widget/services_filter.dart +++ b/lib/presentation/screen/search/widget/services_filter.dart @@ -21,25 +21,27 @@ class ServicesFilter extends StatefulWidget { class _ServicesFilterState extends State { @override Widget build(BuildContext context) { - return Wrap( - alignment: WrapAlignment.start, - spacing: 8.0, - runSpacing: 8.0, - children: [ - for (var index = 0; index < widget.options.length; index++) - CustomChip( - label: widget.options[index], - icon: iconPathFor(widget.options[index]), - isSelected: widget.selectedIndices.contains(index), - onSelect: () { - widget.onSelect(index); - }, - ), - ], + return Align( + alignment: Alignment.topLeft, + child: Wrap( + alignment: WrapAlignment.start, + spacing: 8.0, + runSpacing: 8.0, + children: [ + for (var index = 0; index < widget.options.length; index++) + CustomChip( + label: widget.options[index], + icon: iconPathFor(widget.options[index]), + isSelected: widget.selectedIndices.contains(index), + onSelect: () { + widget.onSelect(index); + }, + ), + ], + ), ); } - String iconPathFor(String label) { final key = label.trim().toLowerCase(); const icons = { @@ -50,10 +52,4 @@ class _ServicesFilterState extends State { }; return icons[key] ?? ''; } - - } - - - - From 129e49fda5a20ff1bea7305fc0bda0c8f3bafe79 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Sat, 1 Nov 2025 02:17:29 +0200 Subject: [PATCH 26/46] feat: base bottom sheet --- assets/icons/cancel_circle.svg | 5 ++ .../widget/base_bottom_sheet.dart | 61 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 assets/icons/cancel_circle.svg create mode 100644 lib/presentation/design_system/widget/base_bottom_sheet.dart diff --git a/assets/icons/cancel_circle.svg b/assets/icons/cancel_circle.svg new file mode 100644 index 0000000..f9cdf56 --- /dev/null +++ b/assets/icons/cancel_circle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/presentation/design_system/widget/base_bottom_sheet.dart b/lib/presentation/design_system/widget/base_bottom_sheet.dart new file mode 100644 index 0000000..6b95df4 --- /dev/null +++ b/lib/presentation/design_system/widget/base_bottom_sheet.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:share_space/presentation/design_system/theme/app_theme_provider.dart'; +import '../theme/app_theme.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class BaseBottomSheet extends StatefulWidget { + final Widget child; + final String label; + final Function() onClose; + + const BaseBottomSheet({ + super.key, + required this.child, + required this.label, + required this.onClose, + }); + + @override + State createState() => _BaseBottomSheetState(); +} + +class _BaseBottomSheetState extends State { + @override + Widget build(BuildContext context) { + return AppThemeProvider(child: Column( + children: [ + Padding( + padding: EdgeInsetsGeometry.only(left: 16, right: 16, top: 24), + child: Column( + children: [ + Row( + children: [ + Text( + widget.label, + style: AppTheme.of(context).typography.textTheme.titleSmall?.copyWith( + color: AppTheme.of(context).colors.title, + ), + ), + Expanded(child: SizedBox()), + SizedBox( + width: 20, + height: 20, + child: IconButton( + padding: EdgeInsets.zero, + alignment: Alignment.center, + icon: SvgPicture.asset('assets/icons/cancel_circle.svg'), + onPressed: widget.onClose, + ), + ), + ], + ), + SizedBox(height: 12), + Divider(color: AppTheme.of(context).colors.stroke, thickness: 1, height: 0), + ], + ), + ), + Expanded(child: widget.child), + ], + )); + } +} From 1b81582703e6c1c61c602d7c3bbf60b71331dd5b Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Sat, 1 Nov 2025 02:18:11 +0200 Subject: [PATCH 27/46] feat: wrap filter with base bottom sheet --- .../screen/search/filter_bottomsheet.dart | 274 +++++++++--------- 1 file changed, 140 insertions(+), 134 deletions(-) diff --git a/lib/presentation/screen/search/filter_bottomsheet.dart b/lib/presentation/screen/search/filter_bottomsheet.dart index b715ec5..fb7f925 100644 --- a/lib/presentation/screen/search/filter_bottomsheet.dart +++ b/lib/presentation/screen/search/filter_bottomsheet.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:share_space/presentation/design_system/theme/app_theme.dart'; +import 'package:share_space/presentation/design_system/widget/base_bottom_sheet.dart'; import 'package:share_space/presentation/screen/search/widget/range_chip.dart'; import 'package:share_space/presentation/screen/search/widget/range_slider.dart'; import 'package:share_space/presentation/screen/search/widget/rating_filter.dart'; @@ -31,154 +32,159 @@ class _FilterBottomSheetState extends State { @override Widget build(BuildContext context) { final theme = AppTheme.of(context); - return Container( - color: theme.colors.surfaceLow, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: Column( - children: [ - Align( - alignment: Alignment.topLeft, - child: Text( - "Rate", - style: theme.typography.textTheme.titleSmall?.copyWith( - color: theme.colors.title, + return BaseBottomSheet( + label: "Filter", + onClose: (){}, + child: Container( + color: theme.colors.surfaceLow, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Column( + children: [ + Align( + alignment: Alignment.topLeft, + child: Text( + "Rate", + style: theme.typography.textTheme.titleSmall?.copyWith( + color: theme.colors.title, + ), ), ), - ), - SizedBox(height: 12), - RatingFilter( - options: rateOptions, - selectedIndices: selectedRateIndices, - onSelect: (index) { - setState(() { - if (selectedRateIndices.contains(index)) { - selectedRateIndices.remove(index); - if (selectedRateIndices.isEmpty) { - selectedRateIndices.add(0); - } - } else { - if (index == 0) { - selectedRateIndices.clear(); - } - if (selectedRateIndices.length == 1 && - selectedRateIndices.first == 0) { - selectedRateIndices.clear(); + SizedBox(height: 12), + RatingFilter( + options: rateOptions, + selectedIndices: selectedRateIndices, + onSelect: (index) { + setState(() { + if (selectedRateIndices.contains(index)) { + selectedRateIndices.remove(index); + if (selectedRateIndices.isEmpty) { + selectedRateIndices.add(0); + } + } else { + if (index == 0) { + selectedRateIndices.clear(); + } + if (selectedRateIndices.length == 1 && + selectedRateIndices.first == 0) { + selectedRateIndices.clear(); + } + selectedRateIndices.add(index); } - selectedRateIndices.add(index); - } - }); - }, - ), - SizedBox(height: 16), - Align( - alignment: Alignment.topLeft, - child: Text( - "Price Range", - style: theme.typography.textTheme.titleSmall?.copyWith( - color: theme.colors.title, - ), + }); + }, ), - ), - SizedBox(height: 12), - RangeSliderWidget( - maxRange: const RangeValues(0, 1000), - currentRange: _currentRange, - onChange: (newRange) { - setState(() { - _currentRange = newRange; - }); - }, - currency: "IQD", - ), - SizedBox(height: 20), - Row( - children: [ - Expanded( - child: RangeChip( - label: "Min", - value: _currentRange.start.toInt(), - image: "assets/icons/arrow_down.svg", - unit: "IQD", + SizedBox(height: 16), + Align( + alignment: Alignment.topLeft, + child: Text( + "Price Range", + style: theme.typography.textTheme.titleSmall?.copyWith( + color: theme.colors.title, ), ), - const SizedBox(width: 16), - Expanded( - child: RangeChip( - label: "Max", - value: _currentRange.end.toInt(), - image: "assets/icons/arrow_down.svg", - unit: "IQD", + ), + SizedBox(height: 12), + RangeSliderWidget( + maxRange: const RangeValues(0, 1000), + currentRange: _currentRange, + onChange: (newRange) { + setState(() { + _currentRange = newRange; + }); + }, + currency: "IQD", + ), + SizedBox(height: 20), + Row( + children: [ + Expanded( + child: RangeChip( + label: "Min", + value: _currentRange.start.toInt(), + image: "assets/icons/arrow_down.svg", + unit: "IQD", + ), + ), + const SizedBox(width: 8), + Expanded( + child: RangeChip( + label: "Max", + value: _currentRange.end.toInt(), + image: "assets/icons/arrow_up.svg", + unit: "IQD", + ), + ), + ], + ), + SizedBox(height: 16), + Align( + alignment: Alignment.topLeft, + child: Text( + "Services", + style: theme.typography.textTheme.titleSmall?.copyWith( + color: theme.colors.title, ), - ), - ], - ), - SizedBox(height: 16), - Align( - alignment: Alignment.topLeft, - child: Text( - "Services", - style: theme.typography.textTheme.titleSmall?.copyWith( - color: theme.colors.title, ), ), - ), - SizedBox(height: 12), - ServicesFilter( - options: servicesOptions, - selectedIndices: selectedServicesIndices, - onSelect: (index) { - setState(() { - if (selectedServicesIndices.contains(index)) { - selectedServicesIndices.remove(index); - if (selectedServicesIndices.isEmpty) { - selectedServicesIndices.add(0); - } - } else { - if (index == 0) { - selectedServicesIndices.clear(); + SizedBox(height: 12), + ServicesFilter( + options: servicesOptions, + selectedIndices: selectedServicesIndices, + onSelect: (index) { + setState(() { + if (selectedServicesIndices.contains(index)) { + selectedServicesIndices.remove(index); + if (selectedServicesIndices.isEmpty) { + selectedServicesIndices.add(0); + } + } else { + if (index == 0) { + selectedServicesIndices.clear(); + } + if (selectedServicesIndices.length == 1 && + selectedServicesIndices.first == 0) { + selectedServicesIndices.clear(); + } + selectedServicesIndices.add(index); } - if (selectedServicesIndices.length == 1 && - selectedServicesIndices.first == 0) { - selectedServicesIndices.clear(); - } - selectedServicesIndices.add(index); - } - }); - }, - ), - SizedBox(height: 16), - Align( - alignment: Alignment.topLeft, - child: Text( - "Location", - style: theme.typography.textTheme.titleSmall?.copyWith( - color: theme.colors.title, - ), + }); + }, ), - ), - SizedBox(height: 12), - // TODO: Add Location Filter Widget here - SizedBox(height: 24), - Row( - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - child: SecondaryButton( - onPressed: () {}, - label: "Clear", - isDisabled: true, + SizedBox(height: 16), + Align( + alignment: Alignment.topLeft, + child: Text( + "Location", + style: theme.typography.textTheme.titleSmall?.copyWith( + color: theme.colors.title, ), ), - Expanded( - child: PrimaryButton( - onPressed: () {}, - label: "Apply Filters", + ), + SizedBox(height: 12), + // TODO: Add Location Filter Widget here + SizedBox(height: 24), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: SecondaryButton( + onPressed: () {}, + label: "Clear", + isDisabled: true, + ), ), - ), - ], - ), - ], + SizedBox(width: 8), + Expanded( + child: PrimaryButton( + onPressed: () {}, + label: "Apply Filters", + ), + ), + ], + ), + ], + ), ), ), ); From 3986ffd13e2fcdcc105d9d01ce407b30c5c8ba0f Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Sun, 2 Nov 2025 17:45:43 +0200 Subject: [PATCH 28/46] fix: background color --- .../widget/base_bottom_sheet.dart | 79 ++++++++++++------- .../screen/search/widget/range_chip.dart | 18 ++--- 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/lib/presentation/design_system/widget/base_bottom_sheet.dart b/lib/presentation/design_system/widget/base_bottom_sheet.dart index 6b95df4..2f2eeff 100644 --- a/lib/presentation/design_system/widget/base_bottom_sheet.dart +++ b/lib/presentation/design_system/widget/base_bottom_sheet.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:share_space/presentation/design_system/theme/app_theme_provider.dart'; + import '../theme/app_theme.dart'; -import 'package:flutter_svg/flutter_svg.dart'; class BaseBottomSheet extends StatefulWidget { final Widget child; @@ -22,40 +23,58 @@ class BaseBottomSheet extends StatefulWidget { class _BaseBottomSheetState extends State { @override Widget build(BuildContext context) { - return AppThemeProvider(child: Column( - children: [ - Padding( - padding: EdgeInsetsGeometry.only(left: 16, right: 16, top: 24), - child: Column( - children: [ - Row( + return AppThemeProvider( + child: Container( + color: AppTheme.of(context).colors.surface, + child: Column( + children: [ + Padding( + padding: EdgeInsetsGeometry.only(left: 16, right: 16, top: 24), + child: Column( children: [ - Text( - widget.label, - style: AppTheme.of(context).typography.textTheme.titleSmall?.copyWith( - color: AppTheme.of(context).colors.title, - ), + Row( + children: [ + Text( + widget.label, + style: AppTheme.of(context) + .typography + .textTheme + .titleSmall + ?.copyWith( + color: AppTheme.of(context).colors.title, + ), + ), + Expanded(child: SizedBox()), + SizedBox( + width: 20, + height: 20, + child: IconButton( + padding: EdgeInsets.zero, + alignment: Alignment.center, + icon: SvgPicture.asset( + 'assets/icons/cancel_circle.svg', + ), + onPressed: () { + widget.onClose(); + Navigator.pop(context); + }, + ), + ), + ], ), - Expanded(child: SizedBox()), - SizedBox( - width: 20, - height: 20, - child: IconButton( - padding: EdgeInsets.zero, - alignment: Alignment.center, - icon: SvgPicture.asset('assets/icons/cancel_circle.svg'), - onPressed: widget.onClose, - ), + SizedBox(height: 12), + Divider( + color: AppTheme.of(context).colors.stroke, + thickness: 1, + height: 0, ), ], ), - SizedBox(height: 12), - Divider(color: AppTheme.of(context).colors.stroke, thickness: 1, height: 0), - ], - ), + ), + widget.child, + ], ), - Expanded(child: widget.child), - ], - )); + ), + ); } } diff --git a/lib/presentation/screen/search/widget/range_chip.dart b/lib/presentation/screen/search/widget/range_chip.dart index cc7a24d..8a250b0 100644 --- a/lib/presentation/screen/search/widget/range_chip.dart +++ b/lib/presentation/screen/search/widget/range_chip.dart @@ -25,16 +25,13 @@ class _RangeChipState extends State { Widget build(BuildContext context) { final theme = AppTheme.of(context); return Chip( + backgroundColor: theme.colors.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(100.0)), label: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SvgPicture.asset( - widget.image, - width: 24, - height: 24, - ), + SvgPicture.asset(widget.image, width: 24, height: 24), Expanded( flex: 1, @@ -51,7 +48,7 @@ class _RangeChipState extends State { ), Text( widget.value.toString(), - style: theme.typography.textTheme.labelLarge?.copyWith( + style: theme.typography.textTheme.labelLarge?.copyWith( color: theme.colors.body, ), ), @@ -59,9 +56,12 @@ class _RangeChipState extends State { ), ), ), - Text(widget.unit, style: theme.typography.textTheme.labelSmall?.copyWith( - color: theme.colors.primary, - )), + Text( + widget.unit, + style: theme.typography.textTheme.labelSmall?.copyWith( + color: theme.colors.primary, + ), + ), ], ), ); From bfa988d36d6f89d5254009726de3e5720349c03e Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Sun, 2 Nov 2025 18:19:09 +0200 Subject: [PATCH 29/46] feat: show bottom sheet with function --- .../widget/base_bottom_sheet.dart | 29 +++++++++++++++++++ .../screen/search/filter_bottomsheet.dart | 12 ++------ .../screen/search/search_screen.dart | 13 ++++++++- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/lib/presentation/design_system/widget/base_bottom_sheet.dart b/lib/presentation/design_system/widget/base_bottom_sheet.dart index 2f2eeff..35d5f1f 100644 --- a/lib/presentation/design_system/widget/base_bottom_sheet.dart +++ b/lib/presentation/design_system/widget/base_bottom_sheet.dart @@ -78,3 +78,32 @@ class _BaseBottomSheetState extends State { ); } } + +Future showBaseBottomSheet({ + required BuildContext context, + required Widget child, + required String label, + required double height, + VoidCallback? onClose, + bool isScrollControlled = true, +}) { + return showModalBottomSheet( + isScrollControlled: isScrollControlled, + context: context, + backgroundColor: AppTheme.of(context).colors.surface, + builder: (BuildContext context) { + return AppThemeProvider( + child: SafeArea( + child: SizedBox( + height: MediaQuery.of(context).size.height * height, + child: BaseBottomSheet( + label: label, + onClose: onClose ?? () {}, + child: child, + ), + ), + ), + ); + }, + ); +} diff --git a/lib/presentation/screen/search/filter_bottomsheet.dart b/lib/presentation/screen/search/filter_bottomsheet.dart index fb7f925..3d3fd12 100644 --- a/lib/presentation/screen/search/filter_bottomsheet.dart +++ b/lib/presentation/screen/search/filter_bottomsheet.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:share_space/presentation/design_system/theme/app_theme.dart'; -import 'package:share_space/presentation/design_system/widget/base_bottom_sheet.dart'; import 'package:share_space/presentation/screen/search/widget/range_chip.dart'; import 'package:share_space/presentation/screen/search/widget/range_slider.dart'; import 'package:share_space/presentation/screen/search/widget/rating_filter.dart'; @@ -32,12 +31,7 @@ class _FilterBottomSheetState extends State { @override Widget build(BuildContext context) { final theme = AppTheme.of(context); - return BaseBottomSheet( - label: "Filter", - onClose: (){}, - child: Container( - color: theme.colors.surfaceLow, - child: Padding( + return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Column( children: [ @@ -185,8 +179,6 @@ class _FilterBottomSheetState extends State { ), ], ), - ), - ), - ); + ); } } diff --git a/lib/presentation/screen/search/search_screen.dart b/lib/presentation/screen/search/search_screen.dart index 9aa88a1..ff524de 100644 --- a/lib/presentation/screen/search/search_screen.dart +++ b/lib/presentation/screen/search/search_screen.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:share_space/presentation/screen/search/widget/range_chip.dart'; import 'package:share_space/presentation/screen/search/widget/range_slider.dart'; - class SearchScreen extends StatefulWidget { const SearchScreen({super.key}); @@ -52,3 +51,15 @@ class _SearchScreenState extends State { ); } } + +/* +* onPressed: () { + showBaseBottomSheet( + context: context, + child: FilterBottomSheet(), + label: "Filter", + onClose: () {}, + height: 0.65, + ); + }, +* */ From 98f41260f3051cb2aeb71632d09942345c58d503 Mon Sep 17 00:00:00 2001 From: Tasneem AbdElhakeem Date: Sun, 2 Nov 2025 20:53:36 +0200 Subject: [PATCH 30/46] feat: add search field widget --- assets/icons/arrow_up_right.svg | 4 + assets/icons/clock.svg | 5 ++ assets/icons/{ic_search.svg => search.svg} | 0 .../screen/search/widget/search_field.dart | 86 +++++++++++++++++++ 4 files changed, 95 insertions(+) create mode 100644 assets/icons/arrow_up_right.svg create mode 100644 assets/icons/clock.svg rename assets/icons/{ic_search.svg => search.svg} (100%) create mode 100644 lib/presentation/screen/search/widget/search_field.dart diff --git a/assets/icons/arrow_up_right.svg b/assets/icons/arrow_up_right.svg new file mode 100644 index 0000000..ba2fd0d --- /dev/null +++ b/assets/icons/arrow_up_right.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/clock.svg b/assets/icons/clock.svg new file mode 100644 index 0000000..65db125 --- /dev/null +++ b/assets/icons/clock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/ic_search.svg b/assets/icons/search.svg similarity index 100% rename from assets/icons/ic_search.svg rename to assets/icons/search.svg diff --git a/lib/presentation/screen/search/widget/search_field.dart b/lib/presentation/screen/search/widget/search_field.dart new file mode 100644 index 0000000..fbe9c85 --- /dev/null +++ b/lib/presentation/screen/search/widget/search_field.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:share_space/presentation/design_system/theme/app_theme.dart'; +import 'package:share_space/presentation/screen/search/widget/filter_button.dart'; + +class SearchField extends StatefulWidget { + const SearchField({super.key}); + + @override + State createState() => _SearchFieldState(); +} + +class _SearchFieldState extends State { + @override + Widget build(BuildContext context) { + final theme = AppTheme.of(context); + + return Row( + children: [ + Expanded( + flex: 1, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SearchAnchor( + viewConstraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.3, + ), + viewBackgroundColor: theme.colors.surfaceLow, + dividerColor: Colors.transparent, + isFullScreen: false, + builder: (BuildContext context, SearchController controller) { + return SearchBar( + hintText: "Search by room, location...", + hintStyle: WidgetStatePropertyAll( + theme.typography.textTheme.labelMedium?.copyWith( + color: theme.colors.body, + ), + ), + elevation: WidgetStatePropertyAll(0.0), + backgroundColor: WidgetStatePropertyAll(theme.colors.surfaceLow), + controller: controller, + shape: WidgetStatePropertyAll( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100), + side: BorderSide(color: theme.colors.stroke, width: 0.5), + ), + ), + padding: const WidgetStatePropertyAll( + EdgeInsets.symmetric(horizontal: 16.0), + ), + onTap: () { + controller.openView(); + }, + onChanged: (_) { + controller.openView(); + }, + leading: SvgPicture.asset( + 'assets/icons/search.svg', + width: 24, + height: 24, + colorFilter: ColorFilter.mode(theme.colors.body, BlendMode.srcIn), + ), + ); + }, + suggestionsBuilder: + (BuildContext context, SearchController controller) { + return List.generate(5, (int index) { + final String item = 'item $index'; + return ListTile( + title: Text(item), + onTap: () { + setState(() { + controller.closeView(item); + }); + }, + ); + }); + }, + ), + ), + ), + FilterButton() + ], + ); + } +} From d8896e3f91c4e53babfa4cf6c7837a9189f875e6 Mon Sep 17 00:00:00 2001 From: Tasneem AbdElhakeem Date: Sun, 2 Nov 2025 20:53:58 +0200 Subject: [PATCH 31/46] feat: implement search screen layout --- .../screen/search/search_screen.dart | 45 +++---------------- 1 file changed, 7 insertions(+), 38 deletions(-) diff --git a/lib/presentation/screen/search/search_screen.dart b/lib/presentation/screen/search/search_screen.dart index 9aa88a1..5549845 100644 --- a/lib/presentation/screen/search/search_screen.dart +++ b/lib/presentation/screen/search/search_screen.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:share_space/presentation/screen/search/widget/range_chip.dart'; -import 'package:share_space/presentation/screen/search/widget/range_slider.dart'; - +import 'package:share_space/presentation/screen/search/widget/search_field.dart'; class SearchScreen extends StatefulWidget { const SearchScreen({super.key}); @@ -11,43 +9,14 @@ class SearchScreen extends StatefulWidget { } class _SearchScreenState extends State { - RangeValues _currentRange = const RangeValues(100, 500); - @override Widget build(BuildContext context) { - return Column( - children: [ - RangeSliderWidget( - maxRange: const RangeValues(0, 1000), - currentRange: _currentRange, - onChange: (newRange) { - setState(() { - _currentRange = newRange; - }); - }, - currency: "IQD", - ), - Row( - children: [ - Expanded( - child: RangeChip( - label: "Min", - value: _currentRange.start.toInt(), - image: "assets/icons/arrow_down.svg", - unit: "IQD", - ), - ), - const SizedBox(width: 16), - Expanded( - child: RangeChip( - label: "Max", - value: _currentRange.end.toInt(), - image: "assets/icons/arrow_down.svg", - unit: "IQD", - ), - ), - ], - ), + return CustomScrollView( + slivers: [ + SliverToBoxAdapter(child: SearchField()), + SliverToBoxAdapter(child: Container(height: 500, color: Colors.red)), + SliverToBoxAdapter(child: Container(height: 500, color: Colors.black)), + SliverToBoxAdapter(child: Container(height: 500, color: Colors.blue)), ], ); } From d350cb530fcdbc65b7a0e7a1a8ef890c1a9e26da Mon Sep 17 00:00:00 2001 From: Tasneem AbdElhakeem Date: Sun, 2 Nov 2025 20:54:14 +0200 Subject: [PATCH 32/46] feat: use theme colors for filter icon --- lib/presentation/screen/search/widget/filter_button.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/screen/search/widget/filter_button.dart b/lib/presentation/screen/search/widget/filter_button.dart index d968b6b..1151cc7 100644 --- a/lib/presentation/screen/search/widget/filter_button.dart +++ b/lib/presentation/screen/search/widget/filter_button.dart @@ -37,8 +37,8 @@ class FilterButton extends StatelessWidget { 'assets/icons/filter.svg', width: 24, height: 24, - colorFilter: const ColorFilter.mode( - Colors.white, // TODO: use theme colors + colorFilter: ColorFilter.mode( + theme.colors.surfaceLow, BlendMode.srcIn, ), ), From 1da0171152f84848c0aa3a60b154b648e7119ec6 Mon Sep 17 00:00:00 2001 From: Tasneem AbdElhakeem Date: Sun, 2 Nov 2025 22:41:21 +0200 Subject: [PATCH 33/46] feat: add on press functionality to filter button --- lib/presentation/screen/search/search_screen.dart | 14 +------------- .../screen/search/widget/filter_button.dart | 11 ++++++++++- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/lib/presentation/screen/search/search_screen.dart b/lib/presentation/screen/search/search_screen.dart index 1c81c24..48ac94e 100644 --- a/lib/presentation/screen/search/search_screen.dart +++ b/lib/presentation/screen/search/search_screen.dart @@ -20,16 +20,4 @@ class _SearchScreenState extends State { ], ); } -} - -/* -* onPressed: () { - showBaseBottomSheet( - context: context, - child: FilterBottomSheet(), - label: "Filter", - onClose: () {}, - height: 0.65, - ); - }, -* */ +} \ No newline at end of file diff --git a/lib/presentation/screen/search/widget/filter_button.dart b/lib/presentation/screen/search/widget/filter_button.dart index 1151cc7..4f418dc 100644 --- a/lib/presentation/screen/search/widget/filter_button.dart +++ b/lib/presentation/screen/search/widget/filter_button.dart @@ -2,6 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:share_space/presentation/design_system/theme/app_theme.dart'; +import '../../../design_system/widget/base_bottom_sheet.dart'; +import '../filter_bottomsheet.dart'; + class FilterButton extends StatelessWidget { const FilterButton({super.key}); @@ -18,7 +21,13 @@ class FilterButton extends StatelessWidget { elevation: 0, ), onPressed: () { - // TODO: Implement filter action + showBaseBottomSheet( + context: context, + child: FilterBottomSheet(), + label: "Filter", + onClose: () {}, + height: 0.65, + ); }, child: Ink( decoration: BoxDecoration( From 3eb720bea9c091fefc12d2ea6f8e76640e35ad51 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Thu, 27 Nov 2025 23:45:32 +0200 Subject: [PATCH 34/46] feat: pass suggestions to search field --- .../screen/search/search_screen.dart | 22 +++++++++++++++++-- .../screen/search/widget/search_field.dart | 7 ++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/presentation/screen/search/search_screen.dart b/lib/presentation/screen/search/search_screen.dart index 48ac94e..88b33f2 100644 --- a/lib/presentation/screen/search/search_screen.dart +++ b/lib/presentation/screen/search/search_screen.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:share_space/presentation/screen/search/widget/search_field.dart'; +import '../../design_system/theme/app_theme.dart'; + class SearchScreen extends StatefulWidget { const SearchScreen({super.key}); @@ -11,13 +13,29 @@ class SearchScreen extends StatefulWidget { class _SearchScreenState extends State { @override Widget build(BuildContext context) { + final theme = AppTheme.of(context); return CustomScrollView( slivers: [ - SliverToBoxAdapter(child: SearchField()), + SliverAppBar( + leading: const SizedBox.shrink(), + leadingWidth: 0, + title: Text( + "Search", + style: theme.typography.textTheme.titleMedium?.copyWith( + color: theme.colors.title, + ), + ), + backgroundColor: theme.colors.surface, + surfaceTintColor: theme.colors.surface, + elevation: 0, + ), + SliverToBoxAdapter( + child: SearchField(suggestions: [], history: []), + ), SliverToBoxAdapter(child: Container(height: 500, color: Colors.red)), SliverToBoxAdapter(child: Container(height: 500, color: Colors.black)), SliverToBoxAdapter(child: Container(height: 500, color: Colors.blue)), ], ); } -} \ No newline at end of file +} diff --git a/lib/presentation/screen/search/widget/search_field.dart b/lib/presentation/screen/search/widget/search_field.dart index fbe9c85..2a0fb93 100644 --- a/lib/presentation/screen/search/widget/search_field.dart +++ b/lib/presentation/screen/search/widget/search_field.dart @@ -4,7 +4,10 @@ import 'package:share_space/presentation/design_system/theme/app_theme.dart'; import 'package:share_space/presentation/screen/search/widget/filter_button.dart'; class SearchField extends StatefulWidget { - const SearchField({super.key}); + final List suggestions; + final List history; + + const SearchField({super.key, required this.suggestions, required this.history}); @override State createState() => _SearchFieldState(); @@ -55,7 +58,7 @@ class _SearchFieldState extends State { controller.openView(); }, leading: SvgPicture.asset( - 'assets/icons/search.svg', + 'assets/icons/search_filled.svg', width: 24, height: 24, colorFilter: ColorFilter.mode(theme.colors.body, BlendMode.srcIn), From d7d066bec0b3006c01dcbe515f976279ad158ddb Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 28 Nov 2025 00:34:05 +0200 Subject: [PATCH 35/46] feat: search cubit init --- .../screen/search/state/search_cubit.dart | 57 +++++++++++++++++++ .../screen/search/state/search_state.dart | 35 ++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 lib/presentation/screen/search/state/search_cubit.dart create mode 100644 lib/presentation/screen/search/state/search_state.dart diff --git a/lib/presentation/screen/search/state/search_cubit.dart b/lib/presentation/screen/search/state/search_cubit.dart new file mode 100644 index 0000000..7406191 --- /dev/null +++ b/lib/presentation/screen/search/state/search_cubit.dart @@ -0,0 +1,57 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:share_space/presentation/screen/shared/ui_state/workspace_ui_state.dart'; + +import '../../../../domain/usecase/workspace/get_best.dart'; +import '../../shared/ui_state/mapper/workspace_ui_mapper.dart'; +import 'search_state.dart'; + +class SearchCubit extends Cubit { + final GetBestUseCase _getBestUseCase; + + SearchCubit(this._getBestUseCase) : super(SearchInitial()); + + Future fetchSearch() async { + } + + Future fetchLastViewed(String id) async { + final currentState = state; + if (currentState is SearchLoaded) { + try { + final lastViewed = await _getBestUseCase(); + emit( + SearchLoaded( + currentState.query, + currentState.isSearching, + currentState.selectedRate, + currentState.priceStart, + currentState.priceEnd, + currentState.services, + lastViewed.map((e) => mapWorkToUiState(e)).toList(), + currentState.searchResults, + ), + ); + } catch (e) { + emit(SearchError(e.toString())); + } + } else { + emit(SearchLoading()); + try { + final lastViewed = await _getBestUseCase(); + emit( + SearchLoaded( + '', + false, + 0, + 0, + 760, + [], + lastViewed.map((e) => mapWorkToUiState(e)).toList(), + [], + ), + ); + } catch (e) { + emit(SearchError(e.toString())); + } + } + } +} diff --git a/lib/presentation/screen/search/state/search_state.dart b/lib/presentation/screen/search/state/search_state.dart new file mode 100644 index 0000000..bb05424 --- /dev/null +++ b/lib/presentation/screen/search/state/search_state.dart @@ -0,0 +1,35 @@ +import 'package:share_space/presentation/screen/shared/ui_state/workspace_ui_state.dart'; + +abstract class SearchState {} + +class SearchInitial extends SearchState {} + +class SearchLoading extends SearchState {} + +class SearchLoaded extends SearchState { + final String query; + final bool isSearching; + final int selectedRate; + final int priceStart; + final int priceEnd; + final List services; + final List lastViewed; + final List searchResults; + + SearchLoaded( + this.query, + this.isSearching, + this.selectedRate, + this.priceStart, + this.priceEnd, + this.services, + this.lastViewed, + this.searchResults, + ); +} + +class SearchError extends SearchState { + final String message; + + SearchError(this.message); +} From 6525c0f83192b4f0c2734d83aefe7642b9ece6a2 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 28 Nov 2025 14:22:15 +0200 Subject: [PATCH 36/46] fix: pass room id navigate --- lib/presentation/screen/room_details/room_details_screen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/screen/room_details/room_details_screen.dart b/lib/presentation/screen/room_details/room_details_screen.dart index be577b4..37aa9fb 100644 --- a/lib/presentation/screen/room_details/room_details_screen.dart +++ b/lib/presentation/screen/room_details/room_details_screen.dart @@ -75,7 +75,7 @@ class RoomDetailsScreen extends StatelessWidget { state: state, roomId: resolvedRoomId, ), - bottomNavigationBar: RoomBookingBar(price: state.price), + bottomNavigationBar: RoomBookingBar(price: state.price,roomId: resolvedRoomId), ); } From d6e2b7d4e6b1e84ee3ef44271d3ff40153eedb1d Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 28 Nov 2025 15:14:00 +0200 Subject: [PATCH 37/46] feat: show cards in search --- .../screen/search/search_screen.dart | 104 ++++++++++++++---- .../screen/search/state/search_cubit.dart | 2 +- 2 files changed, 83 insertions(+), 23 deletions(-) diff --git a/lib/presentation/screen/search/search_screen.dart b/lib/presentation/screen/search/search_screen.dart index 88b33f2..474882f 100644 --- a/lib/presentation/screen/search/search_screen.dart +++ b/lib/presentation/screen/search/search_screen.dart @@ -1,7 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:share_space/presentation/screen/search/state/search_cubit.dart'; +import 'package:share_space/presentation/screen/search/state/search_state.dart'; import 'package:share_space/presentation/screen/search/widget/search_field.dart'; +import '../../../di/injection.dart'; import '../../design_system/theme/app_theme.dart'; +import '../../design_system/widget/error_screen.dart'; +import '../../design_system/widget/loading_screen.dart'; +import '../../design_system/widget/workspace_details_card.dart'; +import '../../routes/routes.dart'; +import '../../util/service_mapper.dart'; class SearchScreen extends StatefulWidget { const SearchScreen({super.key}); @@ -14,28 +23,79 @@ class _SearchScreenState extends State { @override Widget build(BuildContext context) { final theme = AppTheme.of(context); - return CustomScrollView( - slivers: [ - SliverAppBar( - leading: const SizedBox.shrink(), - leadingWidth: 0, - title: Text( - "Search", - style: theme.typography.textTheme.titleMedium?.copyWith( - color: theme.colors.title, - ), - ), - backgroundColor: theme.colors.surface, - surfaceTintColor: theme.colors.surface, - elevation: 0, - ), - SliverToBoxAdapter( - child: SearchField(suggestions: [], history: []), - ), - SliverToBoxAdapter(child: Container(height: 500, color: Colors.red)), - SliverToBoxAdapter(child: Container(height: 500, color: Colors.black)), - SliverToBoxAdapter(child: Container(height: 500, color: Colors.blue)), - ], + return BlocProvider( + create: (context) => getIt()..fetchLastViewed(), + child: BlocBuilder( + builder: (context, state) { + if (state is SearchLoading) { + return LoadingScreen(); + } else if (state is SearchLoaded) { + return CustomScrollView( + slivers: [ + SliverAppBar( + leading: const SizedBox.shrink(), + leadingWidth: 0, + title: Text( + "Search", + style: theme.typography.textTheme.titleMedium?.copyWith( + color: theme.colors.title, + ), + ), + backgroundColor: theme.colors.surface, + surfaceTintColor: theme.colors.surface, + elevation: 0, + ), + SliverToBoxAdapter( + child: SearchField(suggestions: [], history: []), + ), + SliverToBoxAdapter( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: state.lastViewed + .map( + (room) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + onTap: () { + Navigator.pushNamed( + context, + Routes.roomDetailsScreen, + arguments: room.id.toString(), + ); + }, + child: WorkspaceDetailsCard( + title: room.name, + imageUrl: room.imageUrls.isNotEmpty + ? room.imageUrls[0] + : '', + rating: room.rate, + price: + "${room.pricePerHour.toInt().toString()}/h", + location: room.locationName, + amenities: room.services + .map((e) => e.name) + .toList(), + ), + ), + const SizedBox(height: 12), + ], + ), + ), + ) + .toList(), + ), + ), + ], + ); + } else if (state is SearchError) { + return ErrorScreen(hasAppBar: false); + } + return const SizedBox.shrink(); + }, + ), ); } } diff --git a/lib/presentation/screen/search/state/search_cubit.dart b/lib/presentation/screen/search/state/search_cubit.dart index 7406191..472e3c7 100644 --- a/lib/presentation/screen/search/state/search_cubit.dart +++ b/lib/presentation/screen/search/state/search_cubit.dart @@ -13,7 +13,7 @@ class SearchCubit extends Cubit { Future fetchSearch() async { } - Future fetchLastViewed(String id) async { + Future fetchLastViewed() async { final currentState = state; if (currentState is SearchLoaded) { try { From 1b0c222f84bb080e38c156b127e1a5529e0345e0 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 28 Nov 2025 15:14:20 +0200 Subject: [PATCH 38/46] feat: add search cubit in get it --- lib/di/injection.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/di/injection.dart b/lib/di/injection.dart index f2ca056..998a0d5 100644 --- a/lib/di/injection.dart +++ b/lib/di/injection.dart @@ -34,6 +34,7 @@ import 'package:share_space/presentation/screen/login/state/login_cubit.dart'; import 'package:share_space/domain/usecase/workspace/save_workspace.dart'; import 'package:share_space/domain/usecase/workspace/remove_saved_workspace.dart'; +import 'package:share_space/presentation/screen/search/state/search_cubit.dart'; import '../data/remote/auth_api_service_impl.dart'; import '../data/remote/booking_api_service.dart'; @@ -140,6 +141,8 @@ void setupDependencies() { getIt.registerFactory(() => BookingCubit(getIt())); + getIt.registerFactory(() => SearchCubit(getIt())); + getIt.registerFactory( () => CreateAccountUseCase(getIt()), ); From ed1483d3e23aeadc63212cfc2169e7f889f71bf4 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 28 Nov 2025 15:18:07 +0200 Subject: [PATCH 39/46] fix: services labels --- lib/presentation/screen/search/search_screen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/screen/search/search_screen.dart b/lib/presentation/screen/search/search_screen.dart index 474882f..0318d77 100644 --- a/lib/presentation/screen/search/search_screen.dart +++ b/lib/presentation/screen/search/search_screen.dart @@ -76,7 +76,7 @@ class _SearchScreenState extends State { "${room.pricePerHour.toInt().toString()}/h", location: room.locationName, amenities: room.services - .map((e) => e.name) + .map((e) => e.toString()) .toList(), ), ), From aec3cdfe49114afab9c91c7f24c0eb80a1e51c2b Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 28 Nov 2025 15:44:56 +0200 Subject: [PATCH 40/46] feat: search titels --- lib/presentation/screen/search/search_screen.dart | 13 ++++++++++++- lib/resources/app_strings.dart | 3 ++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/presentation/screen/search/search_screen.dart b/lib/presentation/screen/search/search_screen.dart index 0318d77..08ae134 100644 --- a/lib/presentation/screen/search/search_screen.dart +++ b/lib/presentation/screen/search/search_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:share_space/presentation/screen/search/state/search_cubit.dart'; import 'package:share_space/presentation/screen/search/state/search_state.dart'; import 'package:share_space/presentation/screen/search/widget/search_field.dart'; +import 'package:share_space/resources/app_strings.dart'; import '../../../di/injection.dart'; import '../../design_system/theme/app_theme.dart'; @@ -36,7 +37,7 @@ class _SearchScreenState extends State { leading: const SizedBox.shrink(), leadingWidth: 0, title: Text( - "Search", + AppStrings.searchScreenTitle, style: theme.typography.textTheme.titleMedium?.copyWith( color: theme.colors.title, ), @@ -48,6 +49,16 @@ class _SearchScreenState extends State { SliverToBoxAdapter( child: SearchField(suggestions: [], history: []), ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Text( + AppStrings.mostSearched , + style: theme.typography.textTheme.titleSmall + ?.copyWith(color: theme.colors.title), + ), + ), + ), SliverToBoxAdapter( child: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/resources/app_strings.dart b/lib/resources/app_strings.dart index e0bed06..359eee2 100644 --- a/lib/resources/app_strings.dart +++ b/lib/resources/app_strings.dart @@ -90,7 +90,8 @@ class AppStrings { static const homeCategoriesTitle = 'Categories'; static const homePopularTitle = 'Popular workspaces'; - static const searchScreenTitle = 'Search Screen'; + static const searchScreenTitle = 'Search'; + static const mostSearched = 'Most Searched'; static const bookingScreenTitle = 'Booking Screen'; static const bookingHistoryScreenTitle = 'Booking History Screen'; static const chatScreenTitle = 'Chat Screen'; From 9eb78e23d60db881d76d829d788cb0dced10458a Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 28 Nov 2025 16:16:43 +0200 Subject: [PATCH 41/46] feat: use search use case in search cubit --- lib/di/injection.dart | 2 +- .../screen/search/search_screen.dart | 13 ++-- .../screen/search/state/search_cubit.dart | 60 +++++++++++++------ 3 files changed, 52 insertions(+), 23 deletions(-) diff --git a/lib/di/injection.dart b/lib/di/injection.dart index 2d6a4dc..1275733 100644 --- a/lib/di/injection.dart +++ b/lib/di/injection.dart @@ -140,7 +140,7 @@ void setupDependencies() { getIt.registerFactory(() => BookingCubit(getIt())); - getIt.registerFactory(() => SearchCubit(getIt())); + getIt.registerFactory(() => SearchCubit(getIt(), getIt())); getIt.registerFactory( () => CreateAccountUseCase(getIt()), diff --git a/lib/presentation/screen/search/search_screen.dart b/lib/presentation/screen/search/search_screen.dart index 08ae134..408366f 100644 --- a/lib/presentation/screen/search/search_screen.dart +++ b/lib/presentation/screen/search/search_screen.dart @@ -11,7 +11,6 @@ import '../../design_system/widget/error_screen.dart'; import '../../design_system/widget/loading_screen.dart'; import '../../design_system/widget/workspace_details_card.dart'; import '../../routes/routes.dart'; -import '../../util/service_mapper.dart'; class SearchScreen extends StatefulWidget { const SearchScreen({super.key}); @@ -51,11 +50,15 @@ class _SearchScreenState extends State { ), SliverToBoxAdapter( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), child: Text( - AppStrings.mostSearched , - style: theme.typography.textTheme.titleSmall - ?.copyWith(color: theme.colors.title), + AppStrings.mostSearched, + style: theme.typography.textTheme.titleSmall?.copyWith( + color: theme.colors.title, + ), ), ), ), diff --git a/lib/presentation/screen/search/state/search_cubit.dart b/lib/presentation/screen/search/state/search_cubit.dart index 472e3c7..9fd8d4a 100644 --- a/lib/presentation/screen/search/state/search_cubit.dart +++ b/lib/presentation/screen/search/state/search_cubit.dart @@ -1,23 +1,45 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:share_space/presentation/screen/shared/ui_state/workspace_ui_state.dart'; +import '../../../../domain/entity/search.dart'; +import '../../../../domain/usecase/search/search_workspaces.dart'; import '../../../../domain/usecase/workspace/get_best.dart'; import '../../shared/ui_state/mapper/workspace_ui_mapper.dart'; import 'search_state.dart'; class SearchCubit extends Cubit { final GetBestUseCase _getBestUseCase; + final SearchWorkspacesUseCase _searchWorkspacesUseCase; - SearchCubit(this._getBestUseCase) : super(SearchInitial()); + SearchCubit(this._getBestUseCase, this._searchWorkspacesUseCase) + : super(SearchInitial()); - Future fetchSearch() async { - } - - Future fetchLastViewed() async { + Future fetchSearch( + String? keyword, + double? minPrice, + double? maxPrice, + double? rating, + List? services, + double? latitude, + double? longitude, + String? location, + ) async { final currentState = state; if (currentState is SearchLoaded) { try { - final lastViewed = await _getBestUseCase(); + emit(SearchLoading()); + final searchResults = await _searchWorkspacesUseCase.execute( + SearchRequest( + keyword: keyword, + minPrice: minPrice, + maxPrice: maxPrice, + rating: rating, + services: services, + latitude: latitude, + longitude: longitude, + location: location, + ), + ); emit( SearchLoaded( currentState.query, @@ -26,27 +48,31 @@ class SearchCubit extends Cubit { currentState.priceStart, currentState.priceEnd, currentState.services, - lastViewed.map((e) => mapWorkToUiState(e)).toList(), - currentState.searchResults, + currentState.lastViewed, + searchResults.map((e) => mapWorkToUiState(e)).toList(), ), ); } catch (e) { emit(SearchError(e.toString())); } - } else { - emit(SearchLoading()); + } + } + + Future fetchLastViewed() async { + final currentState = state; + if (currentState is SearchLoaded) { try { final lastViewed = await _getBestUseCase(); emit( SearchLoaded( - '', - false, - 0, - 0, - 760, - [], + currentState.query, + currentState.isSearching, + currentState.selectedRate, + currentState.priceStart, + currentState.priceEnd, + currentState.services, lastViewed.map((e) => mapWorkToUiState(e)).toList(), - [], + currentState.searchResults, ), ); } catch (e) { From d12f310fb5317980b96ae239aa752a44d655e972 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 28 Nov 2025 16:51:58 +0200 Subject: [PATCH 42/46] fix: init the screen loading --- .../screen/search/state/search_cubit.dart | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/presentation/screen/search/state/search_cubit.dart b/lib/presentation/screen/search/state/search_cubit.dart index 9fd8d4a..f69d187 100644 --- a/lib/presentation/screen/search/state/search_cubit.dart +++ b/lib/presentation/screen/search/state/search_cubit.dart @@ -60,24 +60,29 @@ class SearchCubit extends Cubit { Future fetchLastViewed() async { final currentState = state; - if (currentState is SearchLoaded) { - try { - final lastViewed = await _getBestUseCase(); - emit( - SearchLoaded( - currentState.query, - currentState.isSearching, - currentState.selectedRate, - currentState.priceStart, - currentState.priceEnd, - currentState.services, - lastViewed.map((e) => mapWorkToUiState(e)).toList(), - currentState.searchResults, - ), - ); - } catch (e) { - emit(SearchError(e.toString())); + emit(SearchLoading()); + try { + final lastViewed = await _getBestUseCase(); + double priceEnd = 300; + if (lastViewed.isNotEmpty) { + priceEnd = lastViewed + .map((e) => (e.pricePerHour).toDouble()) + .reduce((a, b) => a > b ? a : b); } + emit( + SearchLoaded( + '', + false, + 0, + 0, + priceEnd.toInt(), + [], + lastViewed.map((e) => mapWorkToUiState(e)).toList(), + [], + ), + ); + } catch (e) { + emit(SearchError(e.toString())); } } } From 55a9c1e2d4af195eaecaa040d4a6e972dc604701 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 28 Nov 2025 16:54:33 +0200 Subject: [PATCH 43/46] feat: show either search results or most searched --- lib/presentation/screen/search/search_screen.dart | 12 +++++++++--- lib/resources/app_strings.dart | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/presentation/screen/search/search_screen.dart b/lib/presentation/screen/search/search_screen.dart index 408366f..7f71faa 100644 --- a/lib/presentation/screen/search/search_screen.dart +++ b/lib/presentation/screen/search/search_screen.dart @@ -30,6 +30,10 @@ class _SearchScreenState extends State { if (state is SearchLoading) { return LoadingScreen(); } else if (state is SearchLoaded) { + final rooms = (state.searchResults.isEmpty) + ? state.lastViewed + : state.searchResults; + return CustomScrollView( slivers: [ SliverAppBar( @@ -55,7 +59,9 @@ class _SearchScreenState extends State { vertical: 12, ), child: Text( - AppStrings.mostSearched, + (state.searchResults.isEmpty) + ? AppStrings.mostSearched + : AppStrings.resultsFound(state.searchResults.length), style: theme.typography.textTheme.titleSmall?.copyWith( color: theme.colors.title, ), @@ -65,7 +71,7 @@ class _SearchScreenState extends State { SliverToBoxAdapter( child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: state.lastViewed + children: rooms .map( (room) => Padding( padding: const EdgeInsets.symmetric(horizontal: 16), @@ -105,7 +111,7 @@ class _SearchScreenState extends State { ], ); } else if (state is SearchError) { - return ErrorScreen(hasAppBar: false); + return const ErrorScreen(hasAppBar: false); } return const SizedBox.shrink(); }, diff --git a/lib/resources/app_strings.dart b/lib/resources/app_strings.dart index 359eee2..d83e895 100644 --- a/lib/resources/app_strings.dart +++ b/lib/resources/app_strings.dart @@ -92,6 +92,7 @@ class AppStrings { static const searchScreenTitle = 'Search'; static const mostSearched = 'Most Searched'; + static String resultsFound(int count) => 'Results found ($count)'; static const bookingScreenTitle = 'Booking Screen'; static const bookingHistoryScreenTitle = 'Booking History Screen'; static const chatScreenTitle = 'Chat Screen'; From 0bb147d379f56c8103015bbf16f727c6cff12328 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 28 Nov 2025 17:04:30 +0200 Subject: [PATCH 44/46] fix: wrap with material --- .../screen/search/search_screen.dart | 173 +++++++++--------- 1 file changed, 90 insertions(+), 83 deletions(-) diff --git a/lib/presentation/screen/search/search_screen.dart b/lib/presentation/screen/search/search_screen.dart index 7f71faa..5f6f6eb 100644 --- a/lib/presentation/screen/search/search_screen.dart +++ b/lib/presentation/screen/search/search_screen.dart @@ -23,98 +23,105 @@ class _SearchScreenState extends State { @override Widget build(BuildContext context) { final theme = AppTheme.of(context); - return BlocProvider( - create: (context) => getIt()..fetchLastViewed(), - child: BlocBuilder( - builder: (context, state) { - if (state is SearchLoading) { - return LoadingScreen(); - } else if (state is SearchLoaded) { - final rooms = (state.searchResults.isEmpty) - ? state.lastViewed - : state.searchResults; + return Material( + color: theme.colors.surfaceLow, + child: BlocProvider( + create: (context) => getIt()..fetchLastViewed(), + child: BlocBuilder( + builder: (context, state) { + if (state is SearchLoading) { + return LoadingScreen(); + } else if (state is SearchLoaded) { + final rooms = (state.searchResults.isEmpty) + ? state.lastViewed + : state.searchResults; - return CustomScrollView( - slivers: [ - SliverAppBar( - leading: const SizedBox.shrink(), - leadingWidth: 0, - title: Text( - AppStrings.searchScreenTitle, - style: theme.typography.textTheme.titleMedium?.copyWith( - color: theme.colors.title, + return CustomScrollView( + slivers: [ + SliverAppBar( + leading: const SizedBox.shrink(), + leadingWidth: 0, + title: Text( + AppStrings.searchScreenTitle, + style: theme.typography.textTheme.titleMedium?.copyWith( + color: theme.colors.title, + ), ), + backgroundColor: theme.colors.surface, + surfaceTintColor: theme.colors.surface, + elevation: 0, ), - backgroundColor: theme.colors.surface, - surfaceTintColor: theme.colors.surface, - elevation: 0, - ), - SliverToBoxAdapter( - child: SearchField(suggestions: [], history: []), - ), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), - child: Text( - (state.searchResults.isEmpty) - ? AppStrings.mostSearched - : AppStrings.resultsFound(state.searchResults.length), - style: theme.typography.textTheme.titleSmall?.copyWith( - color: theme.colors.title, + SliverToBoxAdapter( + child: SearchField(suggestions: [], history: []), + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + child: Text( + (state.searchResults.isEmpty) + ? AppStrings.mostSearched + : AppStrings.resultsFound( + state.searchResults.length, + ), + style: theme.typography.textTheme.titleSmall?.copyWith( + color: theme.colors.title, + ), ), ), ), - ), - SliverToBoxAdapter( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: rooms - .map( - (room) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - InkWell( - onTap: () { - Navigator.pushNamed( - context, - Routes.roomDetailsScreen, - arguments: room.id.toString(), - ); - }, - child: WorkspaceDetailsCard( - title: room.name, - imageUrl: room.imageUrls.isNotEmpty - ? room.imageUrls[0] - : '', - rating: room.rate, - price: - "${room.pricePerHour.toInt().toString()}/h", - location: room.locationName, - amenities: room.services - .map((e) => e.toString()) - .toList(), + SliverToBoxAdapter( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: rooms + .map( + (room) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + onTap: () { + Navigator.pushNamed( + context, + Routes.roomDetailsScreen, + arguments: room.id.toString(), + ); + }, + child: WorkspaceDetailsCard( + title: room.name, + imageUrl: room.imageUrls.isNotEmpty + ? room.imageUrls[0] + : '', + rating: room.rate, + price: + "${room.pricePerHour.toInt().toString()}/h", + location: room.locationName, + amenities: room.services + .map((e) => e.toString()) + .toList(), + ), ), - ), - const SizedBox(height: 12), - ], + const SizedBox(height: 12), + ], + ), ), - ), - ) - .toList(), + ) + .toList(), + ), ), - ), - ], - ); - } else if (state is SearchError) { - return const ErrorScreen(hasAppBar: false); - } - return const SizedBox.shrink(); - }, + ], + ); + } else if (state is SearchError) { + return const ErrorScreen(hasAppBar: false); + } + return const SizedBox.shrink(); + }, + ), ), ); } From 3489327a7053d7e54642820f2c5b59767522ab00 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 28 Nov 2025 19:03:11 +0200 Subject: [PATCH 45/46] feat: search by name --- .../screen/search/filter_bottomsheet.dart | 107 +++++---- .../screen/search/search_screen.dart | 16 +- .../screen/search/state/search_cubit.dart | 220 ++++++++++++++++-- .../screen/search/state/search_state.dart | 8 + .../screen/search/widget/filter_button.dart | 29 ++- .../screen/search/widget/search_field.dart | 152 +++++++----- 6 files changed, 404 insertions(+), 128 deletions(-) diff --git a/lib/presentation/screen/search/filter_bottomsheet.dart b/lib/presentation/screen/search/filter_bottomsheet.dart index 3d3fd12..1a6d179 100644 --- a/lib/presentation/screen/search/filter_bottomsheet.dart +++ b/lib/presentation/screen/search/filter_bottomsheet.dart @@ -4,29 +4,36 @@ import 'package:share_space/presentation/screen/search/widget/range_chip.dart'; import 'package:share_space/presentation/screen/search/widget/range_slider.dart'; import 'package:share_space/presentation/screen/search/widget/rating_filter.dart'; import 'package:share_space/presentation/screen/search/widget/services_filter.dart'; +import 'package:share_space/presentation/screen/shared/ui_state/workspace_ui_state.dart'; import '../../design_system/widget/primary_button.dart'; import '../../design_system/widget/secondary_button.dart'; class FilterBottomSheet extends StatefulWidget { - const FilterBottomSheet({super.key}); + final RangeValues initialRange; + RangeValues currentRange; + final List rateOptions; + final int rateSelectedIndex; + List selectedRateIndices; + final List servicesOptions; + List selectedServicesIndices; + + FilterBottomSheet({ + super.key, + this.initialRange = const RangeValues(10, 500), + required this.currentRange, + required this.rateSelectedIndex, + this.rateOptions = const ["All", "1", "2", "3", "4", "5"], + required this.selectedRateIndices, + this.servicesOptions = ServicesUiState.values, + required this.selectedServicesIndices, + }); @override State createState() => _FilterBottomSheetState(); } class _FilterBottomSheetState extends State { - RangeValues _currentRange = const RangeValues(100, 500); - List rateOptions = ["All", "1", "2", "3", "4", "5"]; - List selectedRateIndices = [0]; - List servicesOptions = [ - "All", - "Wifi", - "A/C", - "Whiteboard", - "Power Backup", - ]; - List selectedServicesIndices = [0]; @override Widget build(BuildContext context) { @@ -46,24 +53,24 @@ class _FilterBottomSheetState extends State { ), SizedBox(height: 12), RatingFilter( - options: rateOptions, - selectedIndices: selectedRateIndices, + options: widget.rateOptions, + selectedIndices: widget.selectedRateIndices, onSelect: (index) { setState(() { - if (selectedRateIndices.contains(index)) { - selectedRateIndices.remove(index); - if (selectedRateIndices.isEmpty) { - selectedRateIndices.add(0); + if (widget.selectedRateIndices.contains(index)) { + widget.selectedRateIndices.remove(index); + if (widget.selectedRateIndices.isEmpty) { + widget.selectedRateIndices.add(0); } } else { if (index == 0) { - selectedRateIndices.clear(); + widget.selectedRateIndices.clear(); } - if (selectedRateIndices.length == 1 && - selectedRateIndices.first == 0) { - selectedRateIndices.clear(); + if (widget.selectedRateIndices.length == 1 && + widget.selectedRateIndices.first == 0) { + widget.selectedRateIndices.clear(); } - selectedRateIndices.add(index); + widget.selectedRateIndices.add(index); } }); }, @@ -80,14 +87,14 @@ class _FilterBottomSheetState extends State { ), SizedBox(height: 12), RangeSliderWidget( - maxRange: const RangeValues(0, 1000), - currentRange: _currentRange, + maxRange: widget.initialRange, + currentRange: widget.currentRange, onChange: (newRange) { setState(() { - _currentRange = newRange; + widget.currentRange = newRange; }); }, - currency: "IQD", + currency: "/hr", ), SizedBox(height: 20), Row( @@ -95,7 +102,7 @@ class _FilterBottomSheetState extends State { Expanded( child: RangeChip( label: "Min", - value: _currentRange.start.toInt(), + value: widget.currentRange.start.toInt(), image: "assets/icons/arrow_down.svg", unit: "IQD", ), @@ -104,7 +111,7 @@ class _FilterBottomSheetState extends State { Expanded( child: RangeChip( label: "Max", - value: _currentRange.end.toInt(), + value: widget.currentRange.end.toInt(), image: "assets/icons/arrow_up.svg", unit: "IQD", ), @@ -123,38 +130,38 @@ class _FilterBottomSheetState extends State { ), SizedBox(height: 12), ServicesFilter( - options: servicesOptions, - selectedIndices: selectedServicesIndices, + options: widget.servicesOptions.map((e) => e.toString()).toList(), + selectedIndices: widget.selectedServicesIndices, onSelect: (index) { setState(() { - if (selectedServicesIndices.contains(index)) { - selectedServicesIndices.remove(index); - if (selectedServicesIndices.isEmpty) { - selectedServicesIndices.add(0); + if (widget.selectedServicesIndices.contains(index)) { + widget.selectedServicesIndices.remove(index); + if (widget.selectedServicesIndices.isEmpty) { + widget.selectedServicesIndices.add(0); } } else { if (index == 0) { - selectedServicesIndices.clear(); + widget.selectedServicesIndices.clear(); } - if (selectedServicesIndices.length == 1 && - selectedServicesIndices.first == 0) { - selectedServicesIndices.clear(); + if (widget.selectedServicesIndices.length == 1 && + widget.selectedServicesIndices.first == 0) { + widget.selectedServicesIndices.clear(); } - selectedServicesIndices.add(index); + widget.selectedServicesIndices.add(index); } }); }, ), - SizedBox(height: 16), - Align( - alignment: Alignment.topLeft, - child: Text( - "Location", - style: theme.typography.textTheme.titleSmall?.copyWith( - color: theme.colors.title, - ), - ), - ), + // SizedBox(height: 16), + // Align( + // alignment: Alignment.topLeft, + // child: Text( + // "Location", + // style: theme.typography.textTheme.titleSmall?.copyWith( + // color: theme.colors.title, + // ), + // ), + // ), SizedBox(height: 12), // TODO: Add Location Filter Widget here SizedBox(height: 24), diff --git a/lib/presentation/screen/search/search_screen.dart b/lib/presentation/screen/search/search_screen.dart index 5f6f6eb..ee117b1 100644 --- a/lib/presentation/screen/search/search_screen.dart +++ b/lib/presentation/screen/search/search_screen.dart @@ -1,3 +1,5 @@ +import 'dart:ffi'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:share_space/presentation/screen/search/state/search_cubit.dart'; @@ -52,7 +54,19 @@ class _SearchScreenState extends State { elevation: 0, ), SliverToBoxAdapter( - child: SearchField(suggestions: [], history: []), + child: SearchField( + onQueryChanged: (query) { + context.read().updateQuery(query); + }, + suggestions: [], + history: [], + initialRange: RangeValues(state.priceStartRange.toDouble() , state.priceEndRange.toDouble()), + currentRange: RangeValues(state.priceStart.toDouble() , state.priceEnd.toDouble()), + rateOptions: const ["All", "1", "2", "3", "4", "5"], + rateSelectedIndex: state.selectedRate, + selectedRateIndices: [0], + selectedServicesIndices: [0], + ), ), SliverToBoxAdapter( child: Padding( diff --git a/lib/presentation/screen/search/state/search_cubit.dart b/lib/presentation/screen/search/state/search_cubit.dart index f69d187..18e1626 100644 --- a/lib/presentation/screen/search/state/search_cubit.dart +++ b/lib/presentation/screen/search/state/search_cubit.dart @@ -15,14 +15,7 @@ class SearchCubit extends Cubit { : super(SearchInitial()); Future fetchSearch( - String? keyword, - double? minPrice, - double? maxPrice, - double? rating, - List? services, - double? latitude, - double? longitude, - String? location, + String keyword, ) async { final currentState = state; if (currentState is SearchLoaded) { @@ -31,13 +24,13 @@ class SearchCubit extends Cubit { final searchResults = await _searchWorkspacesUseCase.execute( SearchRequest( keyword: keyword, - minPrice: minPrice, - maxPrice: maxPrice, - rating: rating, - services: services, - latitude: latitude, - longitude: longitude, - location: location, + minPrice: currentState.priceStart.toDouble(), + maxPrice: currentState.priceEnd.toDouble(), + rating: currentState.selectedRate.toDouble(), + services: currentState.selectedServicesIndices.map((e) => ServicesUiState.values[e].toString()).toList(), + // latitude: latitude, + // longitude: longitude, + // location: location, ), ); emit( @@ -47,6 +40,10 @@ class SearchCubit extends Cubit { currentState.selectedRate, currentState.priceStart, currentState.priceEnd, + currentState.priceStart, + currentState.priceEnd, + currentState.selectedRateIndices, + currentState.selectedServicesIndices, currentState.services, currentState.lastViewed, searchResults.map((e) => mapWorkToUiState(e)).toList(), @@ -63,20 +60,28 @@ class SearchCubit extends Cubit { emit(SearchLoading()); try { final lastViewed = await _getBestUseCase(); - double priceEnd = 300; + double priceStart = 20; + double priceEnd = 30; if (lastViewed.isNotEmpty) { - priceEnd = lastViewed + final prices = lastViewed .map((e) => (e.pricePerHour).toDouble()) - .reduce((a, b) => a > b ? a : b); + .toList(); + priceStart = prices.reduce((a, b) => a < b ? a : b); + priceEnd = prices.reduce((a, b) => a > b ? a : b); } + emit( SearchLoaded( '', false, 0, - 0, + (priceStart * 0.5).toInt(), + (priceEnd * 1.5).toInt(), + priceStart.toInt(), priceEnd.toInt(), [], + [], + [], lastViewed.map((e) => mapWorkToUiState(e)).toList(), [], ), @@ -85,4 +90,181 @@ class SearchCubit extends Cubit { emit(SearchError(e.toString())); } } + + void updateQuery(String query) { + final currentState = state; + if (currentState is SearchLoaded) { + emit( + SearchLoaded( + query, + currentState.isSearching, + currentState.selectedRate, + currentState.priceStartRange, + currentState.priceEndRange, + currentState.priceStart, + currentState.priceEnd, + currentState.selectedRateIndices, + currentState.selectedServicesIndices, + currentState.services, + currentState.lastViewed, + currentState.searchResults, + ), + ); + } + fetchSearch(query); + } + + void updateIsSearching(bool isSearching) { + final currentState = state; + if (currentState is SearchLoaded) { + emit( + SearchLoaded( + currentState.query, + isSearching, + currentState.selectedRate, + currentState.priceStartRange, + currentState.priceEndRange, + currentState.priceStart, + currentState.priceEnd, + currentState.selectedRateIndices, + currentState.selectedServicesIndices, + currentState.services, + currentState.lastViewed, + currentState.searchResults, + ), + ); + } + } + + void updateSelectedRate(double selectedRate) { + final currentState = state; + if (currentState is SearchLoaded) { + emit( + SearchLoaded( + currentState.query, + currentState.isSearching, + selectedRate.toInt(), + currentState.priceStartRange, + currentState.priceEndRange, + currentState.priceStart, + currentState.priceEnd, + currentState.selectedRateIndices, + currentState.selectedServicesIndices, + currentState.services, + currentState.lastViewed, + currentState.searchResults, + ), + ); + } + } + + void updatePriceStart(int priceStart) { + final currentState = state; + if (currentState is SearchLoaded) { + emit( + SearchLoaded( + currentState.query, + currentState.isSearching, + currentState.selectedRate, + currentState.priceStartRange, + currentState.priceEndRange, + priceStart, + currentState.priceEnd, + currentState.selectedRateIndices, + currentState.selectedServicesIndices, + currentState.services, + currentState.lastViewed, + currentState.searchResults, + ), + ); + } + } + + void updatePriceEnd(int priceEnd) { + final currentState = state; + if (currentState is SearchLoaded) { + emit( + SearchLoaded( + currentState.query, + currentState.isSearching, + currentState.selectedRate, + currentState.priceStartRange, + currentState.priceEndRange, + currentState.priceStart, + priceEnd, + currentState.selectedRateIndices, + currentState.selectedServicesIndices, + currentState.services, + currentState.lastViewed, + currentState.searchResults, + ), + ); + } + } + + void updateServices(List services) { + final currentState = state; + if (currentState is SearchLoaded) { + emit( + SearchLoaded( + currentState.query, + currentState.isSearching, + currentState.selectedRate, + currentState.priceStartRange, + currentState.priceEndRange, + currentState.priceStart, + currentState.priceEnd, + currentState.selectedRateIndices, + currentState.selectedServicesIndices, + services, + currentState.lastViewed, + currentState.searchResults, + ), + ); + } + } + + void updateSelectedServices(List services) { + final currentState = state; + if (currentState is SearchLoaded) { + emit( + SearchLoaded( + currentState.query, + currentState.isSearching, + currentState.selectedRate, + currentState.priceStartRange, + currentState.priceEndRange, + currentState.priceStart, + currentState.priceEnd, + currentState.selectedRateIndices, + services, + currentState.services, + currentState.lastViewed, + currentState.searchResults, + ), + ); + } + } + + void updateSelectedRates(List rates) { + final currentState = state; + if (currentState is SearchLoaded) { + emit( + SearchLoaded( + currentState.query, + currentState.isSearching, + currentState.selectedRate, + currentState.priceStartRange, + currentState.priceEndRange, + currentState.priceStart, + currentState.priceEnd, + rates, + currentState.selectedServicesIndices, + currentState.services, + currentState.lastViewed, + currentState.searchResults, + ), + ); + } + } } diff --git a/lib/presentation/screen/search/state/search_state.dart b/lib/presentation/screen/search/state/search_state.dart index bb05424..0d22310 100644 --- a/lib/presentation/screen/search/state/search_state.dart +++ b/lib/presentation/screen/search/state/search_state.dart @@ -10,8 +10,12 @@ class SearchLoaded extends SearchState { final String query; final bool isSearching; final int selectedRate; + final int priceStartRange; + final int priceEndRange; final int priceStart; final int priceEnd; + final List selectedRateIndices; + final List selectedServicesIndices; final List services; final List lastViewed; final List searchResults; @@ -20,8 +24,12 @@ class SearchLoaded extends SearchState { this.query, this.isSearching, this.selectedRate, + this.priceStartRange, + this.priceEndRange, this.priceStart, this.priceEnd, + this.selectedRateIndices, + this.selectedServicesIndices, this.services, this.lastViewed, this.searchResults, diff --git a/lib/presentation/screen/search/widget/filter_button.dart b/lib/presentation/screen/search/widget/filter_button.dart index 4f418dc..0ddd42a 100644 --- a/lib/presentation/screen/search/widget/filter_button.dart +++ b/lib/presentation/screen/search/widget/filter_button.dart @@ -3,10 +3,27 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:share_space/presentation/design_system/theme/app_theme.dart'; import '../../../design_system/widget/base_bottom_sheet.dart'; +import '../../shared/ui_state/workspace_ui_state.dart'; import '../filter_bottomsheet.dart'; class FilterButton extends StatelessWidget { - const FilterButton({super.key}); + final RangeValues initialRange; + RangeValues currentRange; + final List rateOptions; + final int rateSelectedIndex; + List selectedRateIndices; + final List servicesOptions; + List selectedServicesIndices; + + FilterButton({super.key, + this.initialRange = const RangeValues(10, 500), + required this.currentRange, + required this.rateSelectedIndex, + this.rateOptions = const ["All", "1", "2", "3", "4", "5"], + required this.selectedRateIndices, + this.servicesOptions = ServicesUiState.values, + required this.selectedServicesIndices, + }); @override Widget build(BuildContext context) { @@ -23,7 +40,15 @@ class FilterButton extends StatelessWidget { onPressed: () { showBaseBottomSheet( context: context, - child: FilterBottomSheet(), + child: FilterBottomSheet( + initialRange: initialRange, + currentRange: currentRange, + rateSelectedIndex: rateSelectedIndex, + rateOptions: rateOptions, + selectedRateIndices: selectedRateIndices, + servicesOptions: servicesOptions, + selectedServicesIndices: selectedServicesIndices, + ), label: "Filter", onClose: () {}, height: 0.65, diff --git a/lib/presentation/screen/search/widget/search_field.dart b/lib/presentation/screen/search/widget/search_field.dart index 2a0fb93..558e943 100644 --- a/lib/presentation/screen/search/widget/search_field.dart +++ b/lib/presentation/screen/search/widget/search_field.dart @@ -3,11 +3,33 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:share_space/presentation/design_system/theme/app_theme.dart'; import 'package:share_space/presentation/screen/search/widget/filter_button.dart'; +import '../../shared/ui_state/workspace_ui_state.dart'; + class SearchField extends StatefulWidget { + final ValueChanged onQueryChanged; final List suggestions; final List history; + final RangeValues initialRange; + RangeValues currentRange; + final List rateOptions; + final int rateSelectedIndex; + List selectedRateIndices; + final List servicesOptions; + List selectedServicesIndices; - const SearchField({super.key, required this.suggestions, required this.history}); + SearchField({ + super.key, + required this.onQueryChanged, + required this.suggestions, + required this.history, + this.initialRange = const RangeValues(10, 500), + required this.currentRange, + required this.rateSelectedIndex, + this.rateOptions = const ["All", "1", "2", "3", "4", "5"], + required this.selectedRateIndices, + this.servicesOptions = ServicesUiState.values, + required this.selectedServicesIndices, + }); @override State createState() => _SearchFieldState(); @@ -23,66 +45,84 @@ class _SearchFieldState extends State { Expanded( flex: 1, child: Padding( - padding: const EdgeInsets.all(8.0), - child: SearchAnchor( - viewConstraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.3, - ), - viewBackgroundColor: theme.colors.surfaceLow, - dividerColor: Colors.transparent, - isFullScreen: false, - builder: (BuildContext context, SearchController controller) { - return SearchBar( - hintText: "Search by room, location...", - hintStyle: WidgetStatePropertyAll( - theme.typography.textTheme.labelMedium?.copyWith( - color: theme.colors.body, - ), - ), - elevation: WidgetStatePropertyAll(0.0), - backgroundColor: WidgetStatePropertyAll(theme.colors.surfaceLow), - controller: controller, - shape: WidgetStatePropertyAll( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(100), - side: BorderSide(color: theme.colors.stroke, width: 0.5), - ), + padding: const EdgeInsets.all(8.0), + child: SearchAnchor( + viewConstraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.3, + ), + viewBackgroundColor: theme.colors.surfaceLow, + dividerColor: Colors.transparent, + isFullScreen: false, + builder: (BuildContext context, SearchController controller) { + return SearchBar( + hintText: "Search by room, location...", + hintStyle: WidgetStatePropertyAll( + theme.typography.textTheme.labelMedium?.copyWith( + color: theme.colors.body, ), - padding: const WidgetStatePropertyAll( - EdgeInsets.symmetric(horizontal: 16.0), + ), + elevation: WidgetStatePropertyAll(0.0), + backgroundColor: WidgetStatePropertyAll( + theme.colors.surfaceLow, + ), + controller: controller, + shape: WidgetStatePropertyAll( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100), + side: BorderSide(color: theme.colors.stroke, width: 0.5), ), - onTap: () { - controller.openView(); - }, - onChanged: (_) { - controller.openView(); - }, - leading: SvgPicture.asset( - 'assets/icons/search_filled.svg', - width: 24, - height: 24, - colorFilter: ColorFilter.mode(theme.colors.body, BlendMode.srcIn), + ), + padding: const WidgetStatePropertyAll( + EdgeInsets.symmetric(horizontal: 16.0), + ), + onTap: () { + // controller.openView(); + }, + onChanged: (value) { + // controller.openView(); + // widget.onQueryChanged(value); + }, + onSubmitted: (value) { + // controller.openView(); + widget.onQueryChanged(value); + }, + leading: SvgPicture.asset( + 'assets/icons/search_filled.svg', + width: 24, + height: 24, + colorFilter: ColorFilter.mode( + theme.colors.body, + BlendMode.srcIn, ), - ); - }, - suggestionsBuilder: - (BuildContext context, SearchController controller) { - return List.generate(5, (int index) { - final String item = 'item $index'; - return ListTile( - title: Text(item), - onTap: () { - setState(() { - controller.closeView(item); - }); - }, - ); - }); - }, - ), + ), + ); + }, + suggestionsBuilder: + (BuildContext context, SearchController controller) { + return List.generate(5, (int index) { + final String item = 'item $index'; + return ListTile( + title: Text(item), + onTap: () { + setState(() { + controller.closeView(item); + }); + }, + ); + }); + }, + ), ), ), - FilterButton() + FilterButton( + initialRange: widget.initialRange, + currentRange: widget.currentRange, + rateSelectedIndex: widget.rateSelectedIndex, + rateOptions: widget.rateOptions, + selectedRateIndices: widget.selectedRateIndices, + servicesOptions: widget.servicesOptions, + selectedServicesIndices: widget.selectedServicesIndices, + ), ], ); } From ec4ea37c359805455bcf049bf4b1bb9173be4b18 Mon Sep 17 00:00:00 2001 From: Ahmed Hussien Date: Fri, 28 Nov 2025 22:06:47 +0200 Subject: [PATCH 46/46] fix: booking history error screen --- .../screen/booking_history/booking_history_screen.dart | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/presentation/screen/booking_history/booking_history_screen.dart b/lib/presentation/screen/booking_history/booking_history_screen.dart index 8cabc78..124a9a5 100644 --- a/lib/presentation/screen/booking_history/booking_history_screen.dart +++ b/lib/presentation/screen/booking_history/booking_history_screen.dart @@ -13,6 +13,7 @@ import 'package:share_space/presentation/screen/shared/ui_state/workspace_ui_sta import '../../../resources/app_strings.dart'; import '../../design_system/widget/custom_top_snackbar.dart'; +import '../../design_system/widget/error_screen.dart'; class BookingHistoryScreen extends StatefulWidget { const BookingHistoryScreen({super.key}); @@ -142,12 +143,7 @@ class _BookingHistoryScreenState extends State { ), ); } else if (state is BookingHistoryError) { - return Center( - child: Text( - state.message, - style: theme.typography.textTheme.labelSmall, - ), - ); + return const ErrorScreen(hasAppBar: false); } return const SizedBox.shrink(); },