Added bloc navigation for bottom navbar and added bottom navbar to the home page

This commit is contained in:
2025-10-14 19:02:13 +05:30
parent ad5709e6bd
commit f6aaf121ca
9 changed files with 431 additions and 277 deletions

BIN
assets/icons/explore.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
assets/icons/magic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/icons/pass_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -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<NavigationEvent, NavigationState> {
NavigationBloc() : super(const NavigationState(0)) {
on<NavigationTabChanged>((event, emit) {
emit(NavigationState(event.index));
});
}
}

View File

@@ -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<NavigationBloc, NavigationState>(
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<NavigationBloc>().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,
),
),
],
),
);
}
}

View File

@@ -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());
},
);

View File

@@ -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<FirstTimeUserHomePage> createState() => _FirstTimeUserHomePageState();
}
class _FirstTimeUserHomePageState extends State<FirstTimeUserHomePage> {
final ScrollController _scrollController = ScrollController();
double _scrollProgress = 0.0;
final List<Map<String, String>> 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<Map<String, String>> 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)),
],
);
},
),
),
],
),
),
],
),
),
);
}
}

View File

@@ -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<HomePage> {
final ScrollController _scrollController = ScrollController();
double _scrollProgress = 0.0;
final List<Map<String, String>> 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<Map<String, String>> 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<NavigationBloc, NavigationState>(
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"),
],
),
);
}
);
}
}