512 lines
20 KiB
Dart
512 lines
20 KiB
Dart
import 'package:cached_network_image/cached_network_image.dart';
|
|
import 'package:citycards_customer/home/widgets/e_sim_offer_section.dart';
|
|
import 'package:citycards_customer/home/widgets/hotel_offers_section.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import '../../common_bloc/bottom_navigation_bloc.dart';
|
|
import '../../common_packages/app_bar.dart';
|
|
import '../../common_packages/custom_filled_button.dart';
|
|
import '../../common_packages/custom_text.dart';
|
|
import '../../core/route_constants.dart';
|
|
import '../../localPreference/local_preference.dart';
|
|
import '../../networkApiServices/api_urls.dart';
|
|
import '../../postcard/blocs/myPostCards/my_postcard_bloc.dart';
|
|
import '../../postcard/blocs/myPostCards/my_postcard_event.dart';
|
|
import '../../profile/bloc/profile/profile_bloc.dart';
|
|
import '../../profile/bloc/profile/profile_event.dart';
|
|
import '../bloc/registeredHome/home_bloc.dart';
|
|
import '../bloc/registeredHome/home_event.dart';
|
|
import '../bloc/registeredHome/home_state.dart';
|
|
import '../widgets/attractions_list.dart';
|
|
import '../widgets/get_your_pass_card.dart';
|
|
import '../widgets/gradient_container_bg.dart';
|
|
import '../widgets/itineary_animation.dart';
|
|
import '../widgets/pass_card_list.dart';
|
|
import '../widgets/search_city_bottomsheet.dart';
|
|
|
|
class RegisteredUserHomePage extends StatefulWidget {
|
|
const RegisteredUserHomePage({super.key});
|
|
|
|
@override
|
|
State<RegisteredUserHomePage> createState() => _RegisteredUserHomePageState();
|
|
}
|
|
|
|
class _RegisteredUserHomePageState extends State<RegisteredUserHomePage> {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// _loadMyPostCards();
|
|
_checkAndShowCitySelection();
|
|
_loadProfileIfLoggedIn();
|
|
}
|
|
|
|
Future<void> _loadProfileIfLoggedIn() async {
|
|
final userId = await LocalPreference.getUserId();
|
|
|
|
if (userId != null && mounted) {
|
|
context.read<ProfileBloc>().add(
|
|
FetchProfileEvent(userId: userId),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _loadMyPostCards() async {
|
|
final userId = await LocalPreference.getUserId();
|
|
|
|
if (userId != null && mounted) {
|
|
context.read<MyPostCardBloc>().add(FetchDraftPostCards());
|
|
context.read<MyPostCardBloc>().add(RefreshDraftPostCards());
|
|
context.read<MyPostCardBloc>().add(RefreshOrderPostCards());
|
|
context.read<MyPostCardBloc>().add(FetchOrderPostCards());
|
|
}
|
|
}
|
|
|
|
Future<void> _checkAndShowCitySelection() async {
|
|
final int cityId = await LocalPreference.getSelectedCityId();
|
|
|
|
if (cityId == 0) {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_showCitySelectionBottomSheet();
|
|
});
|
|
} else {
|
|
if (mounted) {
|
|
context.read<HomeBloc>().add(FetchHomeData());
|
|
}
|
|
}
|
|
}
|
|
|
|
void _showCitySelectionBottomSheet() {
|
|
showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
backgroundColor: Colors.transparent,
|
|
isDismissible: false,
|
|
enableDrag: false,
|
|
builder: (_) => const CitySelectionBottomSheet(),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: Colors.white,
|
|
body: SafeArea(
|
|
child: RefreshIndicator(
|
|
color: Color(0xffF95F62),
|
|
onRefresh: () async {
|
|
await _checkAndShowCitySelection();
|
|
},
|
|
child: BlocBuilder<HomeBloc, HomeState>(
|
|
builder: (context, state) {
|
|
if (state is HomeLoading) {
|
|
return const Center(child: CircularProgressIndicator(color: Color(0xffF95F62)));
|
|
}
|
|
|
|
if (state is HomeError) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
SizedBox(height: 40.h),
|
|
Icon(
|
|
Icons.error_outline,
|
|
size: 120.sp,
|
|
color: Colors.red.withOpacity(0.3),
|
|
),
|
|
SizedBox(height: 32.h),
|
|
|
|
CustomText(
|
|
text: "Oops! Something went wrong",
|
|
size: 18.sp,
|
|
weight: FontWeight.w600,
|
|
textAlign: TextAlign.center,
|
|
),
|
|
|
|
SizedBox(height: 12.h),
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 24.w),
|
|
child: CustomText(
|
|
text: state.message,
|
|
size: 14.sp,
|
|
color: Color(0xFF656565),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
|
|
SizedBox(height: 32.h),
|
|
CustomFilledButton(
|
|
onTap:() {
|
|
context.read<HomeBloc>().add(FetchHomeData());
|
|
},
|
|
label: "Try Again",
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
if (state is HomeLoaded) {
|
|
final city = state.homeModel.city;
|
|
final attractions = state.homeModel.attraction ?? [];
|
|
final String? cityIconUrl =
|
|
city?.cityIconPath != null && city!.cityIconPath!.isNotEmpty
|
|
? "${ApiUrls.baseUrl}${city.cityIconPath}"
|
|
: null;
|
|
final bannerImageUrl = city?.cityBanners?.isNotEmpty == true
|
|
? city!.cityBanners!
|
|
.firstWhere(
|
|
(banner) =>
|
|
banner.isActive == true &&
|
|
banner.imageFilePath != null,
|
|
orElse: () => city.cityBanners!.first,
|
|
)
|
|
.imageFilePath
|
|
: null;
|
|
|
|
return SingleChildScrollView(
|
|
child: Stack(
|
|
children: [
|
|
// Background image - use city banner if available
|
|
_buildBannerImage(bannerImageUrl),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: EdgeInsets.all(10.r),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
CommonAppBar(
|
|
isWhiteLogo: false,
|
|
isProfilePage: false,
|
|
showDivider: false,
|
|
// imageUrl: cityIconUrl,
|
|
isSelectCity: true,
|
|
),
|
|
SizedBox(height: 130.h),
|
|
|
|
// City name from API
|
|
Text(
|
|
city?.cityName ?? "City Name",
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 44.sp,
|
|
),
|
|
),
|
|
SizedBox(height: 4.h),
|
|
|
|
// City description from API
|
|
Text(
|
|
city?.description ?? "City description",
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(0.9),
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.w400,
|
|
),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
SizedBox(height: 12.h),
|
|
|
|
// Category tags
|
|
SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: Row(
|
|
children: () {
|
|
final tags = (city?.cityHighlights ?? [])
|
|
.where((highlight) => highlight.isActive == true)
|
|
.map((highlight) => Padding(
|
|
padding: EdgeInsets.only(right: 8.w),
|
|
child: _buildTag(highlight.title ?? ""),
|
|
))
|
|
.toList();
|
|
return tags.isEmpty ? [_buildTag("No Highlights Available")] : tags;
|
|
}(),
|
|
),
|
|
),
|
|
SizedBox(height: 40.h),
|
|
|
|
Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text.rich(
|
|
TextSpan(
|
|
children: [
|
|
TextSpan(
|
|
text: "Popular ",
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w500,
|
|
color: Color(0xffF95F62),
|
|
),
|
|
),
|
|
TextSpan(
|
|
text: "Attractions",
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
InkWell(
|
|
onTap: () {
|
|
Navigator.of(context).pushNamed(
|
|
RouteConstants.attractionsPage,
|
|
arguments: "home",
|
|
);
|
|
},
|
|
child: Text(
|
|
"View all",
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.w500,
|
|
color: Color(0xffF95F62),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
|
|
// Pass attractions from API
|
|
AttractionsListView(attractions: attractions),
|
|
],
|
|
),
|
|
),
|
|
|
|
InwardCurvedContainer(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
SizedBox(height: 40.h),
|
|
const ItineraryVideo(),
|
|
SizedBox(height: 20.h),
|
|
|
|
// Button section
|
|
Container(
|
|
margin: EdgeInsets.symmetric(horizontal: 16.w),
|
|
child: SizedBox(
|
|
width: 240.w,
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
context.read<NavigationBloc>().add(
|
|
NavigationTabChanged(1),
|
|
);
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xffF95F62),
|
|
padding: EdgeInsets.symmetric(
|
|
vertical: 14.h,
|
|
),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(
|
|
30.r,
|
|
),
|
|
),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.center,
|
|
children: [
|
|
Text(
|
|
"Create My Magic Itinerary",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 14.sp,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
SizedBox(width: 4.w),
|
|
const Icon(
|
|
Icons.arrow_forward,
|
|
color: Colors.white,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
ESimOfferSection(),
|
|
HotelOffersSection(),
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 16.w),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Column(
|
|
children: [
|
|
InkWell(
|
|
onTap: () {
|
|
Navigator.of(context).pushNamed(
|
|
RouteConstants.searchOffer,
|
|
);
|
|
},
|
|
child: _buildFeatureCard(
|
|
image:
|
|
"assets/images/claim_offers_bg.jpg",
|
|
title: "Claim offers with your City Cards",
|
|
subtitle: "Lorem ipsum dolor sit amet...",
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
SizedBox(height: 24.h),
|
|
ChooseYourPassSection(
|
|
cards: state.homeModel.city?.cards ?? [],
|
|
),
|
|
SizedBox(height: 20.h),
|
|
GetYourPassCard(),
|
|
SizedBox(height: 20.h),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}// Initial state
|
|
return const Center(child: CircularProgressIndicator(color: Color(0xffF95F62),));
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTag(String label) {
|
|
return Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xffFFFFFF).withOpacity(0.29),
|
|
borderRadius: BorderRadius.circular(20.r),
|
|
),
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 12.sp,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildFeatureCard({
|
|
required String image,
|
|
required String title,
|
|
required String subtitle,
|
|
}) {
|
|
return Stack(
|
|
children: [
|
|
ClipRRect(
|
|
borderRadius: BorderRadius.circular(16.r),
|
|
child: Image.asset(
|
|
image,
|
|
height: 220.h,
|
|
width: double.infinity,
|
|
fit: BoxFit.cover,
|
|
),
|
|
),
|
|
Positioned(
|
|
left: 16.w,
|
|
right: 16.w,
|
|
bottom: 16.h,
|
|
child: Container(
|
|
padding: EdgeInsets.all(12.r),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.9),
|
|
borderRadius: BorderRadius.circular(16.r),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 18.sp,
|
|
),
|
|
),
|
|
Text(
|
|
subtitle,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 14.sp,
|
|
color: Colors.black.withOpacity(0.6),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
Container(
|
|
decoration: const BoxDecoration(
|
|
color: Color(0xffFDCDCE),
|
|
shape: BoxShape.circle,
|
|
),
|
|
padding: EdgeInsets.all(12.r),
|
|
child: Image.asset(
|
|
"assets/icons/arrow_angle_up.png",
|
|
scale: 4,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildBannerImage(String? imageUrl) {
|
|
return SizedBox(
|
|
height: 350.h,
|
|
width: double.infinity,
|
|
child: (imageUrl == null || imageUrl.isEmpty)
|
|
? Image.asset(
|
|
"assets/images/chicago.png",
|
|
fit: BoxFit.cover,
|
|
)
|
|
: CachedNetworkImage(
|
|
imageUrl: imageUrl,
|
|
fit: BoxFit.cover,
|
|
|
|
// 🔄 Loader (same as your loadingBuilder)
|
|
placeholder: (context, url) => Container(
|
|
color: Colors.grey[300],
|
|
child: const Center(
|
|
child: CircularProgressIndicator(
|
|
color: Color(0xffF95F62),
|
|
),
|
|
),
|
|
),
|
|
|
|
// ❌ Error fallback (same as errorBuilder)
|
|
errorWidget: (context, url, error) => Image.asset(
|
|
"assets/images/chicago.png",
|
|
fit: BoxFit.cover,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
} |