diff --git a/assets/dummy/dummy_1.jpg b/assets/dummy/dummy_1.jpg new file mode 100644 index 0000000..841ccb4 Binary files /dev/null and b/assets/dummy/dummy_1.jpg differ diff --git a/assets/dummy/dummy_2.jpg b/assets/dummy/dummy_2.jpg new file mode 100644 index 0000000..c804e99 Binary files /dev/null and b/assets/dummy/dummy_2.jpg differ diff --git a/assets/dummy/dummy_3.jpg b/assets/dummy/dummy_3.jpg new file mode 100644 index 0000000..3e8c5de Binary files /dev/null and b/assets/dummy/dummy_3.jpg differ diff --git a/assets/dummy/dummy_4.jpg b/assets/dummy/dummy_4.jpg new file mode 100644 index 0000000..8c41c91 Binary files /dev/null and b/assets/dummy/dummy_4.jpg differ diff --git a/assets/dummy/dummy_5.jpg b/assets/dummy/dummy_5.jpg new file mode 100644 index 0000000..2eb52d2 Binary files /dev/null and b/assets/dummy/dummy_5.jpg differ diff --git a/assets/images/post_card_intro.png b/assets/images/post_card_intro.png new file mode 100644 index 0000000..70abe1b Binary files /dev/null and b/assets/images/post_card_intro.png differ diff --git a/lib/attractions/blocs/attractions_bloc.dart b/lib/attractions/blocs/attractions_bloc.dart new file mode 100644 index 0000000..7805d9f --- /dev/null +++ b/lib/attractions/blocs/attractions_bloc.dart @@ -0,0 +1,29 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../models/attraction_model.dart'; +import '../repository/attractions_repository.dart'; + +part 'attractions_event.dart'; +part 'attractions_state.dart'; + +class AttractionsBloc extends Bloc { + final AttractionsRepository repository; + + AttractionsBloc(this.repository) : super(AttractionsInitial()) { + on((event, emit) { + final attractions = repository.fetchAttractions(); + emit(AttractionsLoaded(attractions)); + }); + + on((event, emit) { + if (state is AttractionsLoaded) { + final currentState = state as AttractionsLoaded; + final filtered = currentState.attractions + .where((a) => + a.title.toLowerCase().contains(event.query.toLowerCase()) || + a.location.toLowerCase().contains(event.query.toLowerCase())) + .toList(); + emit(AttractionsLoaded(filtered)); + } + }); + } +} diff --git a/lib/attractions/blocs/attractions_event.dart b/lib/attractions/blocs/attractions_event.dart new file mode 100644 index 0000000..4db5651 --- /dev/null +++ b/lib/attractions/blocs/attractions_event.dart @@ -0,0 +1,10 @@ +part of 'attractions_bloc.dart'; + +abstract class AttractionsEvent {} + +class LoadAttractions extends AttractionsEvent {} + +class SearchAttractions extends AttractionsEvent { + final String query; + SearchAttractions(this.query); +} diff --git a/lib/attractions/blocs/attractions_state.dart b/lib/attractions/blocs/attractions_state.dart new file mode 100644 index 0000000..029fadd --- /dev/null +++ b/lib/attractions/blocs/attractions_state.dart @@ -0,0 +1,10 @@ +part of 'attractions_bloc.dart'; + +abstract class AttractionsState {} + +class AttractionsInitial extends AttractionsState {} + +class AttractionsLoaded extends AttractionsState { + final List attractions; + AttractionsLoaded(this.attractions); +} diff --git a/lib/attractions/models/attraction_model.dart b/lib/attractions/models/attraction_model.dart new file mode 100644 index 0000000..7fcc1dd --- /dev/null +++ b/lib/attractions/models/attraction_model.dart @@ -0,0 +1,15 @@ +class Attraction { + final String title; + final String location; + final String price; + final String image; + final List tags; + + Attraction({ + required this.title, + required this.location, + required this.price, + required this.image, + required this.tags, + }); +} diff --git a/lib/attractions/repository/attractions_repository.dart b/lib/attractions/repository/attractions_repository.dart new file mode 100644 index 0000000..55e0f7d --- /dev/null +++ b/lib/attractions/repository/attractions_repository.dart @@ -0,0 +1,43 @@ +import '../models/attraction_model.dart'; + +class AttractionsRepository { + List fetchAttractions() { + return [ + Attraction( + title: "Koh Rong Samloem", + location: "Krong Siem Reap", + price: "\$25", + image: "assets/dummy/dummy_1.jpg", + tags: ["Unlimited Card", "Flexi Card"], + ), + Attraction( + title: "Siem Reap", + location: "Krong Siem Reap", + price: "\$25", + image: "assets/dummy/dummy_2.jpg", + tags: ["Unlimited Card"], + ), + Attraction( + title: "Dart Palace", + location: "Krong Siem Reap", + price: "\$25", + image: "assets/dummy/dummy_3.jpg", + tags: ["Unlimited Card", "Flexi Card"], + ), + Attraction( + title: "Koh Rong Samloem", + location: "Krong Siem Reap", + price: "\$25", + image: "assets/dummy/dummy_4.jpg", + tags: ["Flexi Card"], + ), + Attraction( + title: "Dart Palace", + location: "Krong Siem Reap", + price: "\$25", + image: "assets/dummy/dummy_5.jpg", + tags: ["Unlimited Card", "Flexi Card"], + ), + ]; + } +} diff --git a/lib/attractions/views/attractions_page_view.dart b/lib/attractions/views/attractions_page_view.dart new file mode 100644 index 0000000..f927f5f --- /dev/null +++ b/lib/attractions/views/attractions_page_view.dart @@ -0,0 +1,122 @@ +import 'package:citycards_customer/common_packages/app_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import '../../common_packages/custom_search_field.dart'; +import '../blocs/attractions_bloc.dart'; +import '../repository/attractions_repository.dart'; +import '../widget/attraction_card.dart'; +import '../widget/filter_chip.dart'; + +class AttractionsPage extends StatelessWidget { + const AttractionsPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => AttractionsBloc(AttractionsRepository())..add(LoadAttractions()), + child: BlocBuilder( + builder: (context, state) { + final bloc = context.read(); + + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // App bar + CommonAppBar(isWhiteLogo: false, isProfilePage: false), + SizedBox(height: 12.h), + Divider(height: 1.h, color: const Color(0xFFD9D9D9)), + SizedBox(height: 22.h), + + // Back row + Row( + children: [ + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.arrow_back, size: 24.sp), + ), + SizedBox(width: 8.w), + Text( + "Your Attraction", + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w400, + color: Colors.black87, + ), + ), + ], + ), + const SizedBox(height: 20), + + // 🔍 Search field + CommonSearchField( + hint: "Search attractions...", + onChanged: (value) { + if (value.isEmpty) { + bloc.add(LoadAttractions()); + } else { + bloc.add(SearchAttractions(value)); + } + }, + ), + + const SizedBox(height: 16), + + // 🏝️ Category chips row + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + buildCategoryChip("Beach"), + buildCategoryChip("Hike"), + buildCategoryChip("Popular"), + buildCategoryChip("Best in Summer"), + ], + ), + ), + + const SizedBox(height: 10), + + // 🏙️ Attraction list + if (state is AttractionsLoaded) + state.attractions.isEmpty + ? Center( + child: Padding( + padding: const EdgeInsets.only(top: 60), + child: Text( + "No attractions found", + style: TextStyle( + color: Colors.grey[600], + fontSize: 14.sp, + ), + ), + ), + ) + : Column( + children: state.attractions + .map((attraction) => AttractionCard( + attraction: attraction)) + .toList(), + ) + else + const Center( + child: Padding( + padding: EdgeInsets.only(top: 60), + child: CircularProgressIndicator(), + ), + ), + ], + ), + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/attractions/widget/attraction_card.dart b/lib/attractions/widget/attraction_card.dart new file mode 100644 index 0000000..d6b2a9b --- /dev/null +++ b/lib/attractions/widget/attraction_card.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import '../models/attraction_model.dart'; + +class AttractionCard extends StatelessWidget { + final Attraction attraction; + const AttractionCard({super.key, required this.attraction}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 8), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all(color: const Color(0xffFDCDCE)), + borderRadius: BorderRadius.circular(15), + color: Color(0xffFFF5F5), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.asset( + attraction.image, + height: 94, + width: 94, + fit: BoxFit.cover, + ), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(attraction.title, + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.w500)), + const SizedBox(height: 6), + Text(attraction.location, + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Color(0xff464646))), + const SizedBox(height: 6), + Text.rich( + TextSpan( + children: [ + TextSpan( + text: "from ${attraction.price}", + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + const TextSpan( + text: "/person", + style: + TextStyle(fontSize: 10, color: Colors.black, fontWeight: FontWeight.w400,), + ), + ], + ), + ), + const SizedBox(height: 6), + Wrap( + spacing: 6, + children: attraction.tags + .map((tag) => Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: tag == "Flexi Card" + ? const Color(0xffF95FAF).withOpacity(0.1) + : const Color(0xffF95F62).withOpacity(0.1), + border: Border.all( + color: tag == "Flexi Card" + ? const Color(0xffF95FAF) + : const Color(0xffF95F62), + ), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + tag, + style: GoogleFonts.poppins( + fontSize: 11, + color: Color(0xff1A1A1A), + fontWeight: FontWeight.w400, + ), + ), + )) + .toList(), + ) +, + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/attractions/widget/filter_chip.dart b/lib/attractions/widget/filter_chip.dart new file mode 100644 index 0000000..5797d76 --- /dev/null +++ b/lib/attractions/widget/filter_chip.dart @@ -0,0 +1,20 @@ +import "package:flutter/material.dart"; + +Widget buildCategoryChip(String label) { + return Container( + margin: const EdgeInsets.only(right: 8), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: const Color(0xffF95F62), + borderRadius: BorderRadius.circular(40), + ), + child: Text( + label, + style: const TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.w500, + ), + ), + ); +} \ No newline at end of file diff --git a/lib/common_packages/app_bar.dart b/lib/common_packages/app_bar.dart index 3744d41..85c9106 100644 --- a/lib/common_packages/app_bar.dart +++ b/lib/common_packages/app_bar.dart @@ -34,7 +34,8 @@ class CommonAppBar extends StatelessWidget { if(!isProfilePage) GestureDetector( onTap: (){ - Navigator.pushNamed(context, RouteConstants.profile); + Navigator.of(context, rootNavigator: true) + .pushNamed(RouteConstants.profile); }, child: CircleAvatar( backgroundColor: Color(0xffFFDFDF), diff --git a/lib/common_packages/custom_search_field.dart b/lib/common_packages/custom_search_field.dart new file mode 100644 index 0000000..07fd398 --- /dev/null +++ b/lib/common_packages/custom_search_field.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class CommonSearchField extends StatelessWidget { + final ValueChanged onChanged; + final String hint; + + const CommonSearchField({ + super.key, + required this.onChanged, + this.hint = "Search attractions", + }); + + @override + Widget build(BuildContext context) { + return TextField( + onChanged: onChanged, + decoration: InputDecoration( + hintText: hint, + hintStyle: GoogleFonts.poppins(color: Colors.grey.shade500), + filled: true, + fillColor: Colors.white, + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Color(0xffF95F62).withOpacity(0.4)), + borderRadius: BorderRadius.circular(12), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Color(0xffF95F62).withOpacity(0.4)), + borderRadius: BorderRadius.circular(12), + ), + ), + ); + } +} diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index aca1f9b..89f1da1 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -13,6 +13,7 @@ import 'package:citycards_customer/privacy/privacy_view.dart'; import 'package:citycards_customer/terms_and_condition/terms_and_condition_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import '../attractions/views/attractions_page_view.dart'; import '../common_bloc/bottom_navigation_bloc.dart'; import '../home/views/home_page_view.dart'; import 'route_constants.dart'; @@ -30,7 +31,10 @@ class AppRouter { ); }, ); - + case RouteConstants.attractionsPage: + return MaterialPageRoute( + builder: (_) => const AttractionsPage(), + ); case RouteConstants.profile: return MaterialPageRoute( builder: (_) { diff --git a/lib/core/route_constants.dart b/lib/core/route_constants.dart index 0a095f8..935ee64 100644 --- a/lib/core/route_constants.dart +++ b/lib/core/route_constants.dart @@ -1,5 +1,8 @@ class RouteConstants { + + /****************************** HOME SECTION ************************************/ static const String home = '/home'; + static const String attractionsPage = "/attractions"; /* ****************************** Profile Section **************************/ diff --git a/lib/home/views/home_page_view.dart b/lib/home/views/home_page_view.dart index 5b3e397..f1ad21f 100644 --- a/lib/home/views/home_page_view.dart +++ b/lib/home/views/home_page_view.dart @@ -1,9 +1,10 @@ import 'package:citycards_customer/home/views/registered_user_home_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; - +import '../../attractions/views/attractions_page_view.dart'; import '../../common_bloc/bottom_navigation_bloc.dart'; import '../../common_packages/custom_bottom_navbar.dart'; +import '../../core/route_constants.dart'; import 'first_time_user_home_page.dart'; class HomePage extends StatefulWidget { @@ -14,30 +15,58 @@ class HomePage extends StatefulWidget { } class _HomePageState extends State { - + final _navigatorKeys = [ + GlobalKey(), // tab 0 + GlobalKey(), // tab 1 + ]; @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - Widget body; - switch (state.selectedIndex){ - case 0: - body = const FirstTimeUserHomePage(); - case 1: - body = const RegisteredUserHomePage(); - break; - default: - body = const FirstTimeUserHomePage(); - } + final currentIndex = state.selectedIndex; + return SafeArea( top: false, child: Scaffold( - body: body, + body: Stack( + children: [ + _buildOffstageNavigator(0, currentIndex, const FirstTimeUserHomePage()), + _buildOffstageNavigator(1, currentIndex, const RegisteredUserHomePage()), + ], + ), bottomNavigationBar: CustomBottomNavBar(), ), ); - } + }, ); } + + Widget _buildOffstageNavigator(int index, int currentIndex, Widget child) { + return Offstage( + offstage: currentIndex != index, + child: Navigator( + key: _navigatorKeys[index], + onGenerateRoute: (settings) { + switch (settings.name) { + case '/': + return MaterialPageRoute(builder: (_) => child); + + case RouteConstants.attractionsPage: + return MaterialPageRoute( + builder: (_) => const AttractionsPage(), + ); + + default: + return MaterialPageRoute( + builder: (_) => const Scaffold( + body: Center(child: Text('Page not found')), + ), + ); + } + }, + ), + ); + } + } diff --git a/lib/home/views/registered_user_home_page.dart b/lib/home/views/registered_user_home_page.dart index 005c82e..7861625 100644 --- a/lib/home/views/registered_user_home_page.dart +++ b/lib/home/views/registered_user_home_page.dart @@ -1,7 +1,10 @@ +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:google_fonts/google_fonts.dart'; import '../../common_packages/app_bar.dart'; +import '../../core/route_constants.dart'; import '../widgets/attractions_list.dart'; import '../widgets/get_your_pass_card.dart'; import '../widgets/gradient_container_bg.dart'; @@ -122,7 +125,9 @@ class _RegisteredUserHomePageState extends State { ), ), InkWell( - onTap: (){}, + onTap: (){ + Navigator.of(context).pushNamed(RouteConstants.attractionsPage); + }, child: Text("View all", style: TextStyle( fontSize: 12, @@ -146,6 +151,9 @@ class _RegisteredUserHomePageState extends State { ) ), const SizedBox(height: 10), + ESimOfferSection(), + HotelOffersSection(), + const SizedBox(height: 10), Padding( padding: const EdgeInsets.all(16.0), child: Column( @@ -166,9 +174,7 @@ class _RegisteredUserHomePageState extends State { ChooseYourPassSection(), const SizedBox(height: 20), - // ===== GET YOUR PASS SECTION ===== GetYourPassCard(), - const SizedBox(height: 40), ], ), ), diff --git a/lib/home/widgets/e_sim_offer_section.dart b/lib/home/widgets/e_sim_offer_section.dart new file mode 100644 index 0000000..7c28654 --- /dev/null +++ b/lib/home/widgets/e_sim_offer_section.dart @@ -0,0 +1,149 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class ESimOfferSection extends StatelessWidget { + const ESimOfferSection({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + gradient: const LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color(0xFF2A2B7B), // deep navy blue + Color(0xFF9E1E2E), // rich red tone + ], + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // ===== HEADER LINE ===== + RichText( + text: TextSpan( + children: [ + TextSpan( + text: "Explore ", + style: GoogleFonts.poppins( + color: const Color(0xFFF95F62), + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + TextSpan( + text: "More Cities.", + style: GoogleFonts.poppins( + color: Colors.white.withOpacity(0.9), + fontSize: 14, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + + const SizedBox(height: 10), + + // ===== MAIN HEADING ===== + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Stay Connected + Text.rich( + TextSpan( + children: [ + TextSpan( + text: "Stay ", + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 28, + fontWeight: FontWeight.w500, + ), + ), + TextSpan( + text: "Connected", + style: GoogleFonts.poppins( + color: const Color(0xFFF95F62), + fontSize: 28, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + const SizedBox(height: 2), + // Everywhere (centered) + Center( + child: Text( + "Everywhere", + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 28, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + + + const SizedBox(height: 20), + + // ===== SUB CARD ===== + Container( + width: double.infinity, + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: const Color(0xFFF95F62), + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Claim e-Sim offers with\nCityCard", + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Container( + decoration: const BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + padding: const EdgeInsets.all(12), + child: Image.asset( + "assets/icons/arrow_angle_up.png", + color: Color(0xFFF95F62), + scale: 4, + ), + ) + ], + ), + const SizedBox(height: 4), + Text( + "Lorem ipsum dolor sit amet...", + style: GoogleFonts.poppins( + color: Colors.white.withOpacity(0.85), + fontSize: 13, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/home/widgets/get_your_pass_card.dart b/lib/home/widgets/get_your_pass_card.dart index 8d2979d..10f0523 100644 --- a/lib/home/widgets/get_your_pass_card.dart +++ b/lib/home/widgets/get_your_pass_card.dart @@ -8,52 +8,63 @@ class GetYourPassCard extends StatelessWidget { Widget build(BuildContext context) { return Container( width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + margin: const EdgeInsets.symmetric(horizontal: 14, vertical: 0), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), decoration: BoxDecoration( color: const Color(0xFFFFF1F1), borderRadius: BorderRadius.circular(40), + border: Border.all(color: Color(0xffFDCDCE)) ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( + children: [ // ===== Left Section ===== Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - // Left texts and overlapping images - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Get your Pass", - style: GoogleFonts.poppins( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.black, - ), - ), - const SizedBox(height: 6), - Row( - children: [ - // Stacked circular attraction images - AttractionsAvatarStack(imagePath: 'assets/images/get_your_pass_bg.jpg',), - const SizedBox(width: 8), - Text( - "Attractions", - style: GoogleFonts.poppins( - fontSize: 13, - color: Colors.black87, - ), - ), - ], - ), - ], + Text( + "Get your Pass", + style: GoogleFonts.poppins( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + ), + Container( + padding: const EdgeInsets.all(8), + decoration: const BoxDecoration( + color: Color(0xffF95F62), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.arrow_forward, + color: Colors.black, + size: 18, + ), ), ], ), - // ===== Right Section ===== + const SizedBox( + height: 7, + ), Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Row( + children: [ + AttractionsAvatarStack(imagePath: 'assets/images/get_your_pass_bg.jpg',), + const SizedBox(width: 8), + Text( + "Attractions", + style: GoogleFonts.poppins( + fontSize: 13, + color: Colors.black, + fontWeight: FontWeight.w400 + ), + ), + ], + ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ @@ -87,20 +98,6 @@ class GetYourPassCard extends StatelessWidget { ), ], ), - const SizedBox(width: 12), - // Circular Arrow Button - Container( - padding: const EdgeInsets.all(10), - decoration: const BoxDecoration( - color: Color(0xffF95F62), - shape: BoxShape.circle, - ), - child: const Icon( - Icons.arrow_forward, - color: Colors.white, - size: 18, - ), - ), ], ), ], @@ -113,7 +110,7 @@ class AttractionsAvatarStack extends StatelessWidget { const AttractionsAvatarStack({ super.key, required this.imagePath, // from your assets/figma - this.size = 26, // circle diameter + this.size = 35, // circle diameter this.count = 4, // total circles including the last “16+” this.overlap = 8, // how much they overlap this.moreText = '16+', diff --git a/lib/home/widgets/hotel_offers_section.dart b/lib/home/widgets/hotel_offers_section.dart new file mode 100644 index 0000000..10c7a64 --- /dev/null +++ b/lib/home/widgets/hotel_offers_section.dart @@ -0,0 +1,173 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class HotelOffersSection extends StatelessWidget { + const HotelOffersSection({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color(0xFFFFCA8E).withOpacity(0.24), // deep navy blue + Color(0xFFDB00FF).withOpacity(0.24), // rich red tone + Color(0xFFF95F62).withOpacity(0.24), + ], + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // ===== HEADER LINE ===== + RichText( + text: TextSpan( + children: [ + TextSpan( + text: "MARRIOTT ", + style: GoogleFonts.poppins( + color: const Color(0xFFF95F62), + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + TextSpan( + text: "MOMENTS", + style: GoogleFonts.poppins( + color: Colors.black, + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ), + TextSpan( + text: ", ", + style: GoogleFonts.poppins( + color: const Color(0xFFF95F62), + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + TextSpan( + text: "CITYCARD ", + style: GoogleFonts.poppins( + color: const Color(0xFFF95F62), + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + TextSpan( + text: "PRICES", + style: GoogleFonts.poppins( + color: Colors.black, + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + + const SizedBox(height: 10), + + // ===== MAIN HEADING ===== + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Stay Connected + Text.rich( + TextSpan( + children: [ + TextSpan( + text: "Premium ", + style: GoogleFonts.poppins( + color: const Color(0xFFF95F62), + fontSize: 32.sp, + fontWeight: FontWeight.w600, + ), + ), + TextSpan( + text: "Stays", + style: GoogleFonts.poppins( + color: Colors.black, + fontSize: 32.sp, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + Center( + child: Text( + "CityCard Prices", + style: GoogleFonts.poppins( + color: Colors.black, + fontSize: 32.sp, + fontWeight: FontWeight.w400, + ), + ), + ), + ], + ), + + + const SizedBox(height: 20), + + // ===== SUB CARD ===== + Container( + width: double.infinity, + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: const Color(0xFFF95F62), + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Claim Hotel Discount offers\nwith CityCard", + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Container( + decoration: const BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + padding: const EdgeInsets.all(12), + child: Image.asset( + "assets/icons/arrow_angle_up.png", + color: Color(0xFFF95F62), + scale: 4, + ), + ) + ], + ), + const SizedBox(height: 4), + Text( + "Lorem ipsum dolor sit amet...", + style: GoogleFonts.poppins( + color: Colors.white.withOpacity(0.85), + fontSize: 13, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/home/widgets/pass_card_list.dart b/lib/home/widgets/pass_card_list.dart index 083c81f..5bc2d7f 100644 --- a/lib/home/widgets/pass_card_list.dart +++ b/lib/home/widgets/pass_card_list.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:google_fonts/google_fonts.dart'; class ChooseYourPassSection extends StatefulWidget { @@ -131,21 +132,35 @@ class _ChooseYourPassSectionState extends State { ), ), const SizedBox(height: 6), - Text( - "From ${item['price']}", - style: GoogleFonts.poppins( - fontSize: 16, - color: item['color'], - fontWeight: FontWeight.w600, + Text.rich( + TextSpan( + children: [ + TextSpan( + text: "From ", + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w400, + color: Color(0xff535353), + ), ), - ), + TextSpan( + text: item['price'], + style: TextStyle( + fontSize: 16.sp, + color: item['color'], + fontWeight: FontWeight.w600 + ), + ), + ], + ), + ), const SizedBox(height: 12), Text( "Dive into an extensive selection of thrilling destinations, " "thoughtfully categorized to help you find the perfect getaway.", style: GoogleFonts.poppins( fontSize: 12, - color: Colors.grey[800], + color: Color(0xff5B5F62), height: 1.4, ), ), @@ -156,7 +171,7 @@ class _ChooseYourPassSectionState extends State { "• Fusce tincidunt interdum ex, in tincidunt libero porttitor vel.", style: TextStyle( fontSize: 12, - color: Colors.black54, + color: Color(0xff5B5F62), height: 1.5, ), ), @@ -175,8 +190,8 @@ class _ChooseYourPassSectionState extends State { child: Text( "Get a Pass", style: GoogleFonts.poppins( - fontWeight: FontWeight.w600, - fontSize: 14, + fontWeight: FontWeight.w500, + fontSize: 14.sp, color: Colors.white, ), ), diff --git a/lib/postcard/views/postcard_initial_page_view.dart b/lib/postcard/views/postcard_initial_page_view.dart new file mode 100644 index 0000000..b9a9ff5 --- /dev/null +++ b/lib/postcard/views/postcard_initial_page_view.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + + +class PostcardPage extends StatelessWidget { + const PostcardPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // 🖼️ Postcard image + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Image.asset( + "assets/images/postcard_bg.png", // <-- your image + width: double.infinity, + height: 200.h, + fit: BoxFit.cover, + ), + ), + + SizedBox(height: 32.h), + + // 🌴📮💌 emojis + const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("🌴", style: TextStyle(fontSize: 24)), + SizedBox(width: 8), + Text("📮", style: TextStyle(fontSize: 24)), + SizedBox(width: 8), + Text("💌", style: TextStyle(fontSize: 24)), + ], + ), + + SizedBox(height: 24.h), + + // 📝 Title and subtitle + Text( + "Make the most of your trip", + style: GoogleFonts.poppins( + fontSize: 20.sp, + fontWeight: FontWeight.w600, + color: const Color(0xffF95F62), + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 8.h), + Text( + "Design your own unique postcards to\ncherish your unforgettable moments.", + style: GoogleFonts.poppins( + fontSize: 13.sp, + fontWeight: FontWeight.w400, + color: const Color(0xff464646), + height: 1.5, + ), + textAlign: TextAlign.center, + ), + + SizedBox(height: 36.h), + + // 🟥 CTA button + SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xffF95F62), + padding: EdgeInsets.symmetric(vertical: 16.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ), + ), + onPressed: () { + // Add navigation or bloc event here + }, + child: Text( + "Lets Create", + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 9b73622..b66a61a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -66,6 +66,7 @@ flutter: - assets/logo/ - assets/images/ - assets/icons/ + - assets/dummy/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images