diff --git a/assets/icons/explore.png b/assets/icons/explore.png new file mode 100644 index 0000000..9c77fbc Binary files /dev/null and b/assets/icons/explore.png differ diff --git a/assets/icons/magic.png b/assets/icons/magic.png new file mode 100644 index 0000000..2e0777d Binary files /dev/null and b/assets/icons/magic.png differ diff --git a/assets/icons/pass_icon.png b/assets/icons/pass_icon.png new file mode 100644 index 0000000..f604255 Binary files /dev/null and b/assets/icons/pass_icon.png differ diff --git a/assets/icons/postcard_icon.png b/assets/icons/postcard_icon.png new file mode 100644 index 0000000..eb81f63 Binary files /dev/null and b/assets/icons/postcard_icon.png differ diff --git a/lib/common_bloc/bottom_navigation_bloc.dart b/lib/common_bloc/bottom_navigation_bloc.dart new file mode 100644 index 0000000..b81456f --- /dev/null +++ b/lib/common_bloc/bottom_navigation_bloc.dart @@ -0,0 +1,21 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +abstract class NavigationEvent {} + +class NavigationTabChanged extends NavigationEvent { + final int index; + NavigationTabChanged(this.index); +} + +class NavigationState { + final int selectedIndex; + const NavigationState(this.selectedIndex); +} + +class NavigationBloc extends Bloc { + NavigationBloc() : super(const NavigationState(0)) { + on((event, emit) { + emit(NavigationState(event.index)); + }); + } +} diff --git a/lib/common_packages/custom_bottom_navbar.dart b/lib/common_packages/custom_bottom_navbar.dart new file mode 100644 index 0000000..6dc6f1d --- /dev/null +++ b/lib/common_packages/custom_bottom_navbar.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../common_bloc/bottom_navigation_bloc.dart'; + + +class CustomBottomNavBar extends StatelessWidget { + const CustomBottomNavBar({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Container( + decoration: BoxDecoration( + color: const Color(0xffFFF5F5), + border: Border.all(color: Color(0xffFDCDCE)), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 8, + offset: const Offset(0, -2), + ), + ], + ), + padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildNavItem( + context, + index: 0, + iconPath: 'assets/icons/explore.png', + label: 'Explore', + isActive: state.selectedIndex == 0, + ), + _buildNavItem( + context, + index: 1, + iconPath: 'assets/icons/magic.png', + label: 'Magic Itinerary', + isActive: state.selectedIndex == 1, + ), + _buildNavItem( + context, + index: 2, + iconPath: 'assets/icons/pass_icon.png', + label: 'My Passes', + isActive: state.selectedIndex == 2, + ), + _buildNavItem( + context, + index: 3, + iconPath: 'assets/icons/postcard_icon.png', + label: 'Postcard', + isActive: state.selectedIndex == 3, + ), + ], + ), + ); + }, + ); + } + + Widget _buildNavItem(BuildContext context, { + required int index, + required String iconPath, + required String label, + required bool isActive, + }) { + final color = isActive ? const Color(0xFFBB474A) : Color(0xFFBB474A).withOpacity(0.6); + + return GestureDetector( + onTap: () => + context.read().add(NavigationTabChanged(index)), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (isActive) + Container( + margin: const EdgeInsets.only(bottom: 4), + height: 4, + width: 50, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(2), + ), + ) + else + const SizedBox(height: 7), + + Image.asset(iconPath, scale: 4, color: color), + const SizedBox(height: 4), + Text( + label, + style: TextStyle( + color: color, + fontSize: 12, + fontWeight: isActive ? FontWeight.w500 : FontWeight.w500, + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 7b336ed..04bfcd0 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -6,12 +6,10 @@ 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 '../common_bloc/bottom_navigation_bloc.dart'; import '../home/views/home_page_view.dart'; import 'route_constants.dart'; -// Uncomment these when blocs are ready -// import '../login/blocs/login_bloc.dart'; -// import '../home/blocs/home_bloc.dart'; class AppRouter { Route onGenerateRoute(RouteSettings settings) { @@ -20,8 +18,7 @@ class AppRouter { case RouteConstants.home: return MaterialPageRoute( builder: (_) { - // return BlocProvider(create: (_) => HomeBloc(), child: const HomePage()); - return const HomePage(); + return BlocProvider(create: (_) => NavigationBloc(), child: const HomePage()); }, ); diff --git a/lib/home/views/first_time_user_home_page.dart b/lib/home/views/first_time_user_home_page.dart new file mode 100644 index 0000000..454e807 --- /dev/null +++ b/lib/home/views/first_time_user_home_page.dart @@ -0,0 +1,276 @@ +import 'package:flutter/material.dart'; + +import '../../common_packages/app_bar.dart'; +import 'explore_cities_card.dart'; + +class FirstTimeUserHomePage extends StatefulWidget { + const FirstTimeUserHomePage({super.key}); + + @override + State createState() => _FirstTimeUserHomePageState(); +} + +class _FirstTimeUserHomePageState extends State { + + final ScrollController _scrollController = ScrollController(); + + double _scrollProgress = 0.0; + + final List> featuredCities = [ + { + "name": "Melbourne", + "description": "Australia's cultural capital famous for vibrant...", + "individualTicket": "\$350+", + "cityCard": "\$199", + "savings": "Save \$151+", + "image": + "assets/images/city_sydney.png" + }, + { + "name": "Sydney", + "description": "Australia's cultural capital famous for vibrant...", + "individualTicket": "\$400+", + "cityCard": "\$249", + "savings": "Save \$151+", + "image": + "assets/images/city_sydney.png" + }, + { + "name": "Sydney", + "description": "Australia's cultural capital famous for vibrant...", + "individualTicket": "\$400+", + "cityCard": "\$249", + "savings": "Save \$151+", + "image": + "assets/images/city_sydney.png" + } + ]; + + final List> upcomingCities = [ + { + "image": "assets/images/city_turkey.jpg", + "name": "Turkey", + }, + { + "image":"assets/images/city_germany.jpg", + "name": "Germany" + }, + { + "image":"assets/images/city_switz.jpg", + "name" : "Switzerland" + }, + { + "image":"assets/images/city_maldives.jpg", + "name": "Maldives" + }, + { + "image" : "assets/images/city_turkey.jpg", + "name": "Turkey" + }, + { + "image":"assets/images/city_germany.jpg", + "name": "Germany" + }, + { + "image":"assets/images/city_switz.jpg", + "name" : "Switzerland" + }, + { + "image":"assets/images/city_maldives.jpg", + "name": "Maldives" + }, + ]; + + @override + void initState() { + super.initState(); + _scrollController.addListener(_updateScrollProgress); + } + + void _updateScrollProgress() { + if (!_scrollController.hasClients || + _scrollController.position.maxScrollExtent == 0) return; + setState(() { + _scrollProgress = (_scrollController.offset / + _scrollController.position.maxScrollExtent) + .clamp(0.0, 1.0); + }); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: SingleChildScrollView( + child: Stack( + children: [ + Image.asset("assets/images/home_bg.png"), + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CommonAppBar(isWhiteLogo: false , isProfilePage: false), + const SizedBox(height: 140), + const Text( + "CityCards.\nSee More,\nSpend Less.", + style: TextStyle( + fontSize: 44, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 8), + const Text( + "Instant QR access to 40+ attractions,\nexclusive perks, and savings up to 30%", + style: TextStyle(color: Colors.white), + ), + const SizedBox(height: 20), + ElevatedButton( + style: ElevatedButton.styleFrom( + fixedSize: const Size(200, 50), + padding: const EdgeInsets.all(15), + backgroundColor: const Color(0xffF95F62), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(25), + ), + ), + onPressed: () {}, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text("Get You'r CityCard", + style: TextStyle(color: Colors.white)), + const SizedBox(width: 10), + Image.asset("assets/icons/arrow.png", height: 13), + ], + ), + ), + const SizedBox(height: 80), + Text.rich( + TextSpan( + children: const [ + TextSpan( + text: "Explore ", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w500, + color: Color(0xffF95F62), + ), + ), + TextSpan( + text: "Cities", + style: + TextStyle(fontSize: 24, color: Colors.black, fontWeight: FontWeight.w500,), + ), + ], + ), + ), + const SizedBox(height: 8), + const Text( + "Explore your dream destination and experience various attractions.", + style: TextStyle(color: Color(0xff676D75)), + ), + const SizedBox(height: 16), + + // Horizontal cards + SizedBox( + height: 270, + child: ListView.builder( + controller: _scrollController, + scrollDirection: Axis.horizontal, + itemCount: featuredCities.length, + itemBuilder: (context, index) { + final city = featuredCities[index]; + return ExploreCitiesCard( + name: city['name']!, + description: city['description']!, + imageUrl: city['image']!, + individualPrice: city['individualTicket']!, + cityCardPrice: city['cityCard']!, + savingsText: city['savings']!, + ); + }, + ), + ), + + const SizedBox(height: 10), + const SizedBox(height: 10), + Align( + alignment: Alignment.center, + child: SizedBox( + width: 200, + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: LinearProgressIndicator( + value: _scrollProgress, + minHeight: 6, + backgroundColor: Color(0xffFEE7E7), + color: const Color(0xffF95F62), + ), + ), + ), + ), + + + const SizedBox(height: 40), + Text.rich( + TextSpan( + children: const [ + TextSpan( + text: "Upcoming ", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w500, + color: Color(0xffF95F62), + ), + ), + TextSpan( + text: "Cities", + style: + TextStyle(fontSize: 24, color: Colors.black, fontWeight: FontWeight.w500), + ), + ], + ), + ), + const SizedBox(height: 8), + Text( + "Explore your dream destination and experience various attractions.", + style: TextStyle(color: Colors.grey[600]), + ), + const SizedBox(height: 16), + SizedBox( + height: 80, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: upcomingCities.length, + separatorBuilder: (_, __) => const SizedBox(width: 16), + itemBuilder: (context, index) { + return Column( + children: [ + CircleAvatar( + radius: 28, + backgroundImage: AssetImage(upcomingCities[index]["image"] ?? ""), + ), + const SizedBox(height: 4), + Text(upcomingCities[index]["name"] ?? "", + style: const TextStyle(fontSize: 12)), + ], + ); + }, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/home/views/home_page_view.dart b/lib/home/views/home_page_view.dart index 24d65c4..e985766 100644 --- a/lib/home/views/home_page_view.dart +++ b/lib/home/views/home_page_view.dart @@ -1,7 +1,9 @@ -import 'package:citycards_customer/home/views/explore_cities_card.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../common_packages/app_bar.dart'; +import '../../common_bloc/bottom_navigation_bloc.dart'; +import '../../common_packages/custom_bottom_navbar.dart'; +import 'first_time_user_home_page.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -11,281 +13,28 @@ class HomePage extends StatefulWidget { } class _HomePageState extends State { - final ScrollController _scrollController = ScrollController(); - double _scrollProgress = 0.0; - - final List> featuredCities = [ - { - "name": "Melbourne", - "description": "Australia's cultural capital famous for vibrant...", - "individualTicket": "\$350+", - "cityCard": "\$199", - "savings": "Save \$151+", - "image": - "assets/images/city_sydney.png" - }, - { - "name": "Sydney", - "description": "Australia's cultural capital famous for vibrant...", - "individualTicket": "\$400+", - "cityCard": "\$249", - "savings": "Save \$151+", - "image": - "assets/images/city_sydney.png" - }, - { - "name": "Sydney", - "description": "Australia's cultural capital famous for vibrant...", - "individualTicket": "\$400+", - "cityCard": "\$249", - "savings": "Save \$151+", - "image": - "assets/images/city_sydney.png" - } - ]; - - final List> upcomingCities = [ - { - "image": "assets/images/city_turkey.jpg", - "name": "Turkey", - }, - { - "image":"assets/images/city_germany.jpg", - "name": "Germany" - }, - { - "image":"assets/images/city_switz.jpg", - "name" : "Switzerland" - }, - { - "image":"assets/images/city_maldives.jpg", - "name": "Maldives" - }, - { - "image" : "assets/images/city_turkey.jpg", - "name": "Turkey" - }, - { - "image":"assets/images/city_germany.jpg", - "name": "Germany" - }, - { - "image":"assets/images/city_switz.jpg", - "name" : "Switzerland" - }, - { - "image":"assets/images/city_maldives.jpg", - "name": "Maldives" - }, - ]; - - @override - void initState() { - super.initState(); - _scrollController.addListener(_updateScrollProgress); - } - - void _updateScrollProgress() { - if (!_scrollController.hasClients || - _scrollController.position.maxScrollExtent == 0) return; - setState(() { - _scrollProgress = (_scrollController.offset / - _scrollController.position.maxScrollExtent) - .clamp(0.0, 1.0); - }); - } - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } @override Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: SingleChildScrollView( - child: Stack( - children: [ - Image.asset("assets/images/home_bg.png"), - Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CommonAppBar(isWhiteLogo: false , isProfilePage: false), - const SizedBox(height: 140), - const Text( - "CityCards.\nSee More,\nSpend Less.", - style: TextStyle( - fontSize: 44, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - const SizedBox(height: 8), - const Text( - "Instant QR access to 40+ attractions,\nexclusive perks, and savings up to 30%", - style: TextStyle(color: Colors.white), - ), - const SizedBox(height: 20), - ElevatedButton( - style: ElevatedButton.styleFrom( - fixedSize: const Size(200, 50), - padding: const EdgeInsets.all(15), - backgroundColor: const Color(0xffF95F62), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(25), - ), - ), - onPressed: () {}, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text("Get You'r CityCard", - style: TextStyle(color: Colors.white)), - const SizedBox(width: 10), - Image.asset("assets/icons/arrow.png", height: 13), - ], - ), - ), - const SizedBox(height: 80), - Text.rich( - TextSpan( - children: const [ - TextSpan( - text: "Explore ", - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.w500, - color: Color(0xffF95F62), - ), - ), - TextSpan( - text: "Cities", - style: - TextStyle(fontSize: 24, color: Colors.black, fontWeight: FontWeight.w500,), - ), - ], - ), - ), - const SizedBox(height: 8), - const Text( - "Explore your dream destination and experience various attractions.", - style: TextStyle(color: Color(0xff676D75)), - ), - const SizedBox(height: 16), - - // Horizontal cards - SizedBox( - height: 270, - child: ListView.builder( - controller: _scrollController, - scrollDirection: Axis.horizontal, - itemCount: featuredCities.length, - itemBuilder: (context, index) { - final city = featuredCities[index]; - return ExploreCitiesCard( - name: city['name']!, - description: city['description']!, - imageUrl: city['image']!, - individualPrice: city['individualTicket']!, - cityCardPrice: city['cityCard']!, - savingsText: city['savings']!, - ); - }, - ), - ), - - const SizedBox(height: 10), - const SizedBox(height: 10), - Align( - alignment: Alignment.center, - child: SizedBox( - width: 200, - child: ClipRRect( - borderRadius: BorderRadius.circular(10), - child: LinearProgressIndicator( - value: _scrollProgress, - minHeight: 6, - backgroundColor: Color(0xffFEE7E7), - color: const Color(0xffF95F62), - ), - ), - ), - ), - - - const SizedBox(height: 40), - Text.rich( - TextSpan( - children: const [ - TextSpan( - text: "Upcoming ", - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.w500, - color: Color(0xffF95F62), - ), - ), - TextSpan( - text: "Cities", - style: - TextStyle(fontSize: 24, color: Colors.black, fontWeight: FontWeight.w500), - ), - ], - ), - ), - const SizedBox(height: 8), - Text( - "Explore your dream destination and experience various attractions.", - style: TextStyle(color: Colors.grey[600]), - ), - const SizedBox(height: 16), - SizedBox( - height: 80, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: upcomingCities.length, - separatorBuilder: (_, __) => const SizedBox(width: 16), - itemBuilder: (context, index) { - return Column( - children: [ - CircleAvatar( - radius: 28, - backgroundImage: AssetImage(upcomingCities[index]["image"] ?? ""), - ), - const SizedBox(height: 4), - Text(upcomingCities[index]["name"] ?? "", - style: const TextStyle(fontSize: 12)), - ], - ); - }, - ), - ), - ], - ), - ), - ], + return BlocBuilder( + builder: (context, state) { + Widget body; + switch (state.selectedIndex){ + case 0: + body = const FirstTimeUserHomePage(); + break; + default: + body = const FirstTimeUserHomePage(); + } + return SafeArea( + top: false, + child: Scaffold( + body: body, + bottomNavigationBar: CustomBottomNavBar(), ), - ), - ), - bottomNavigationBar: BottomNavigationBar( - currentIndex: 0, - selectedItemColor: Colors.red, - unselectedItemColor: Colors.grey, - items: const [ - BottomNavigationBarItem(icon: Icon(Icons.explore), label: "Explore"), - BottomNavigationBarItem( - icon: Icon(Icons.auto_fix_high), label: "Magic Itinerary"), - BottomNavigationBarItem( - icon: Icon(Icons.card_giftcard), label: "My Passes"), - BottomNavigationBarItem( - icon: Icon(Icons.post_add), label: "Postcard"), - ], - ), + ); + } ); } }