diff --git a/lib/home/bloc/search_city_bloc.dart b/lib/home/bloc/search_city_bloc.dart new file mode 100644 index 0000000..0abcbd2 --- /dev/null +++ b/lib/home/bloc/search_city_bloc.dart @@ -0,0 +1,54 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +abstract class LoadCityEvent {} + +class LoadAllCity extends LoadCityEvent {} + +class SearchCity extends LoadCityEvent { + final String query; + + SearchCity(this.query); +} + +// ----- State ----- +class CityState { + final List> offers; + + const CityState(this.offers); +} + +// ----- Bloc ----- +class SearchCityBloc extends Bloc { + SearchCityBloc() : super(const CityState([])) { + on(_onLoadCity); + on(_onSearchCity); + } + + final List> _allOffers = [ + {"image": "assets/images/aa1.png", "title": "Sydney"}, + {"image": "assets/images/aa2.png", "title": "New York"}, + {"image": "assets/images/aa3.png", "title": "Abu Dhabi"}, + {"image": "assets/images/aa4.png", "title": "Dubai"}, + { + "image": "assets/images/card_banner.png", + "title": "Tokyo", + }, + {"image": "assets/images/city_germany.jpg", "title": "Ontario"}, + {"image": "assets/images/aa2.png", "title": "Mumbai"}, + {"image": "assets/images/aa3.png", "title": "Louisiana"}, + ]; + + void _onLoadCity(event, emit) { + emit(CityState(_allOffers)); + } + + void _onSearchCity(event, emit) { + final filtered = _allOffers + .where( + (offer) => + offer["title"]!.toLowerCase().contains(event.query.toLowerCase()), + ) + .toList(); + emit(CityState(filtered)); + } +} diff --git a/lib/home/views/first_time_user_home_page.dart b/lib/home/views/first_time_user_home_page.dart index db9e87e..881a310 100644 --- a/lib/home/views/first_time_user_home_page.dart +++ b/lib/home/views/first_time_user_home_page.dart @@ -1,3 +1,4 @@ +import 'package:citycards_customer/home/widgets/search_city_bottomsheet.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -120,7 +121,14 @@ class _FirstTimeUserHomePageState extends State { borderRadius: BorderRadius.circular(25.r), ), ), - onPressed: () {}, + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (_) => const CitySelectionBottomSheet(), + ); + }, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/lib/home/widgets/search_city_bottomsheet.dart b/lib/home/widgets/search_city_bottomsheet.dart new file mode 100644 index 0000000..e3f3d60 --- /dev/null +++ b/lib/home/widgets/search_city_bottomsheet.dart @@ -0,0 +1,183 @@ +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/home/bloc/search_city_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class CitySelectionBottomSheet extends StatelessWidget { + const CitySelectionBottomSheet({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => SearchCityBloc()..add(LoadAllCity()), + child: _CitySelectionView(), + ); + } +} + +class _CitySelectionView extends StatelessWidget { + _CitySelectionView(); + + final TextEditingController _controller = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Container( + height: 620.h, + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 20.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.r), + topRight: Radius.circular(12.r), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + InkWell( + onTap: () => Navigator.pop(context), + child: const Icon(Icons.arrow_back, size: 18), + ), + SizedBox(width: 4.w,), + CustomText(text: "Back", size: 12.sp,) + ], + ), + + Text( + "Select a City", + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + + SizedBox(width: 25.w,) + ], + ), + + SizedBox(height: 19.h), + + // Search Field + SizedBox( + height: 45.h, + child: TextField( + controller: _controller, + onChanged: (value) { + if (value.isEmpty) { + context.read().add(LoadAllCity()); + } else { + context.read().add(SearchCity(value)); + } + }, + decoration: InputDecoration( + hintText: "Search Cities", + hintStyle: TextStyle( + fontSize: 14.sp, + color: const Color(0xFF2B2B2B), + fontWeight: FontWeight.w300 + ), + filled: true, + fillColor: const Color(0xFFFFFFFF).withOpacity(.24), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.r), + borderSide: BorderSide( + color: Color(0xFFF95F62).withOpacity(.40), + width: 1, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.r), + borderSide: BorderSide( + color: Color(0xFFF95F62).withOpacity(.40), + width: 1, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.r), + borderSide: BorderSide( + color: Color(0xFFF95F62).withOpacity(.40), + width: 1.2, + ), + ), + ), + ), + ), + + SizedBox(height: 19.h), + + // City Grid + Expanded( + child: BlocBuilder( + builder: (context, state) { + if (state.offers.isEmpty) { + return const Center(child: Text("No cities found")); + } + return GridView.builder( + itemCount: state.offers.length, + physics: const BouncingScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 12.h, + crossAxisSpacing: 12.w, + childAspectRatio: 1.2, + ), + itemBuilder: (context, index) { + final city = state.offers[index]; + return _cityCard(city["image"]!, city["title"]!); + }, + ); + }, + ), + ), + ], + ), + ); + } + + Widget _cityCard(String image, String name) { + return ClipRRect( + borderRadius: BorderRadius.circular(12.r), + child: Stack( + fit: StackFit.expand, + children: [ + Image.asset(image, fit: BoxFit.cover,width: 170.w,height: 123.h,), + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.black.withOpacity(0.5), + Colors.black.withOpacity(0.5), + Colors.transparent, + ], + begin: Alignment.bottomCenter, + end: Alignment.center, + ), + ), + ), + Align( + alignment: Alignment.bottomLeft, + child: Padding( + padding: EdgeInsets.all(8.w), + child: Text( + name, + style: TextStyle( + color: Colors.white, + fontSize: 18.sp, + fontWeight: FontWeight.w400, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 40954f7..2f2b1c8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -29,7 +29,7 @@ class MyApp extends StatelessWidget { builder: (context, child) { return MaterialApp( onGenerateRoute: _appRouter.onGenerateRoute, - initialRoute: RouteConstants.offerPassDetail, + initialRoute: RouteConstants.home, debugShowCheckedModeBanner: false, title: 'City Cards', theme: ThemeData( diff --git a/lib/offer_pass_detail/offer_pass_detail_view.dart b/lib/offer_pass_detail/offer_pass_detail_view.dart index a963299..d6180ac 100644 --- a/lib/offer_pass_detail/offer_pass_detail_view.dart +++ b/lib/offer_pass_detail/offer_pass_detail_view.dart @@ -38,15 +38,10 @@ class OfferPassDetailView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ CommonAppBar( - isWhiteLogo: true, + isWhiteLogo: false, isProfilePage: false, ), - SizedBox(height: 10.h), - Divider( - color: Colors.white.withOpacity(0.6), - height: 1.h, - ), SizedBox(height: 8.h), Row(