Compare commits
3 Commits
ede130224e
...
9dd76e1dac
| Author | SHA1 | Date | |
|---|---|---|---|
| 9dd76e1dac | |||
|
|
c771ce335c | ||
|
|
f93ba6dad2 |
54
lib/home/bloc/search_city_bloc.dart
Normal file
54
lib/home/bloc/search_city_bloc.dart
Normal file
@@ -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<Map<String, String>> offers;
|
||||
|
||||
const CityState(this.offers);
|
||||
}
|
||||
|
||||
// ----- Bloc -----
|
||||
class SearchCityBloc extends Bloc<LoadCityEvent, CityState> {
|
||||
SearchCityBloc() : super(const CityState([])) {
|
||||
on<LoadAllCity>(_onLoadCity);
|
||||
on<SearchCity>(_onSearchCity);
|
||||
}
|
||||
|
||||
final List<Map<String, String>> _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));
|
||||
}
|
||||
}
|
||||
@@ -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,9 @@ class _FirstTimeUserHomePageState extends State<FirstTimeUserHomePage> {
|
||||
borderRadius: BorderRadius.circular(25.r),
|
||||
),
|
||||
),
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
||||
183
lib/home/widgets/search_city_bottomsheet.dart
Normal file
183
lib/home/widgets/search_city_bottomsheet.dart
Normal file
@@ -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<SearchCityBloc>().add(LoadAllCity());
|
||||
} else {
|
||||
context.read<SearchCityBloc>().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<SearchCityBloc, CityState>(
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user