feat: add bottom navigation bar with icon-only tabs

Implement a compact bottom navigation system with 5 tabs: Home, Search, Create, Notifications, and Profile. The navigation bar uses icon-only design for a clean, modern look and matches the app's primary background color. Also configure Android system navigation bar to match the app theme.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

+11 -2
lib/main.dart
···
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
+
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'screens/landing_screen.dart';
import 'screens/auth/login_screen.dart';
-
import 'screens/home/feed_screen.dart';
+
import 'screens/home/main_shell_screen.dart';
import 'providers/auth_provider.dart';
import 'config/oauth_config.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
+
+
// Set system UI overlay style (Android navigation bar)
+
SystemChrome.setSystemUIOverlayStyle(
+
const SystemUiOverlayStyle(
+
systemNavigationBarColor: Color(0xFF0B0F14),
+
systemNavigationBarIconBrightness: Brightness.light,
+
),
+
);
// Initialize auth provider
final authProvider = AuthProvider();
···
),
GoRoute(
path: '/feed',
-
builder: (context, state) => const FeedScreen(),
+
builder: (context, state) => const MainShellScreen(),
),
],
refreshListenable: authProvider,
+51
lib/screens/home/create_post_screen.dart
···
+
import 'package:flutter/material.dart';
+
+
class CreatePostScreen extends StatelessWidget {
+
const CreatePostScreen({super.key});
+
+
@override
+
Widget build(BuildContext context) {
+
return Scaffold(
+
backgroundColor: const Color(0xFF0B0F14),
+
appBar: AppBar(
+
backgroundColor: const Color(0xFF0B0F14),
+
foregroundColor: Colors.white,
+
title: const Text('Create Post'),
+
automaticallyImplyLeading: false,
+
),
+
body: const Center(
+
child: Padding(
+
padding: EdgeInsets.all(24),
+
child: Column(
+
mainAxisAlignment: MainAxisAlignment.center,
+
children: [
+
Icon(
+
Icons.add_circle_outline,
+
size: 64,
+
color: Color(0xFFFF6B35),
+
),
+
SizedBox(height: 24),
+
Text(
+
'Create Post',
+
style: TextStyle(
+
fontSize: 28,
+
color: Colors.white,
+
fontWeight: FontWeight.bold,
+
),
+
),
+
SizedBox(height: 16),
+
Text(
+
'Share your thoughts with the community',
+
style: TextStyle(
+
fontSize: 16,
+
color: Color(0xFFB6C2D2),
+
),
+
textAlign: TextAlign.center,
+
),
+
],
+
),
+
),
+
),
+
);
+
}
+
}
+1 -45
lib/screens/home/feed_screen.dart
···
import 'package:flutter/material.dart';
-
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import '../../providers/auth_provider.dart';
-
import '../../widgets/primary_button.dart';
class FeedScreen extends StatelessWidget {
const FeedScreen({super.key});
···
backgroundColor: const Color(0xFF0B0F14),
foregroundColor: Colors.white,
title: Text(isAuthenticated ? 'Feed' : 'Explore'),
-
automaticallyImplyLeading: !isAuthenticated,
-
leading: !isAuthenticated
-
? IconButton(
-
icon: const Icon(Icons.arrow_back),
-
onPressed: () => context.go('/'),
-
)
-
: null,
-
actions: isAuthenticated
-
? [
-
IconButton(
-
icon: const Icon(Icons.person),
-
onPressed: () {
-
// TODO: Navigate to profile screen
-
},
-
),
-
]
-
: null,
+
automaticallyImplyLeading: false,
),
body: Center(
child: Padding(
···
),
textAlign: TextAlign.center,
),
-
const SizedBox(height: 48),
-
if (isAuthenticated) ...[
-
PrimaryButton(
-
title: 'Sign Out',
-
onPressed: () async {
-
await authProvider.signOut();
-
// Explicitly redirect to landing screen after sign out
-
if (context.mounted) {
-
context.go('/');
-
}
-
},
-
variant: ButtonVariant.outline,
-
),
-
] else ...[
-
PrimaryButton(
-
title: 'Sign in',
-
onPressed: () => context.go('/login'),
-
variant: ButtonVariant.solid,
-
),
-
const SizedBox(height: 12),
-
PrimaryButton(
-
title: 'Create account',
-
onPressed: () => context.go('/login'),
-
variant: ButtonVariant.outline,
-
),
-
],
],
),
),
+84
lib/screens/home/main_shell_screen.dart
···
+
import 'package:flutter/material.dart';
+
import 'feed_screen.dart';
+
import 'search_screen.dart';
+
import 'create_post_screen.dart';
+
import 'notifications_screen.dart';
+
import 'profile_screen.dart';
+
+
class MainShellScreen extends StatefulWidget {
+
const MainShellScreen({super.key});
+
+
@override
+
State<MainShellScreen> createState() => _MainShellScreenState();
+
}
+
+
class _MainShellScreenState extends State<MainShellScreen> {
+
int _selectedIndex = 0;
+
+
static const List<Widget> _screens = [
+
FeedScreen(),
+
SearchScreen(),
+
CreatePostScreen(),
+
NotificationsScreen(),
+
ProfileScreen(),
+
];
+
+
void _onItemTapped(int index) {
+
setState(() {
+
_selectedIndex = index;
+
});
+
}
+
+
@override
+
Widget build(BuildContext context) {
+
return Scaffold(
+
body: _screens[_selectedIndex],
+
bottomNavigationBar: Container(
+
decoration: const BoxDecoration(
+
color: Color(0xFF0B0F14),
+
border: Border(
+
top: BorderSide(
+
color: Color(0xFF0B0F14),
+
width: 0.5,
+
),
+
),
+
),
+
child: SafeArea(
+
child: SizedBox(
+
height: 48,
+
child: Row(
+
mainAxisAlignment: MainAxisAlignment.spaceAround,
+
children: [
+
_buildNavItem(0, Icons.home, 'Home'),
+
_buildNavItem(1, Icons.search, 'Search'),
+
_buildNavItem(2, Icons.add_box_outlined, 'Create'),
+
_buildNavItem(3, Icons.notifications_outlined, 'Notifications'),
+
_buildNavItem(4, Icons.person_outline, 'Me'),
+
],
+
),
+
),
+
),
+
),
+
);
+
}
+
+
Widget _buildNavItem(int index, IconData icon, String label) {
+
final isSelected = _selectedIndex == index;
+
final color = isSelected
+
? const Color(0xFFFF6B35)
+
: const Color(0xFFB6C2D2).withValues(alpha: 0.6);
+
+
return Expanded(
+
child: InkWell(
+
onTap: () => _onItemTapped(index),
+
splashColor: Colors.transparent,
+
highlightColor: Colors.transparent,
+
child: Icon(
+
icon,
+
size: 28,
+
color: color,
+
),
+
),
+
);
+
}
+
}
+51
lib/screens/home/notifications_screen.dart
···
+
import 'package:flutter/material.dart';
+
+
class NotificationsScreen extends StatelessWidget {
+
const NotificationsScreen({super.key});
+
+
@override
+
Widget build(BuildContext context) {
+
return Scaffold(
+
backgroundColor: const Color(0xFF0B0F14),
+
appBar: AppBar(
+
backgroundColor: const Color(0xFF0B0F14),
+
foregroundColor: Colors.white,
+
title: const Text('Notifications'),
+
automaticallyImplyLeading: false,
+
),
+
body: const Center(
+
child: Padding(
+
padding: EdgeInsets.all(24),
+
child: Column(
+
mainAxisAlignment: MainAxisAlignment.center,
+
children: [
+
Icon(
+
Icons.notifications_outlined,
+
size: 64,
+
color: Color(0xFFFF6B35),
+
),
+
SizedBox(height: 24),
+
Text(
+
'Notifications',
+
style: TextStyle(
+
fontSize: 28,
+
color: Colors.white,
+
fontWeight: FontWeight.bold,
+
),
+
),
+
SizedBox(height: 16),
+
Text(
+
'Stay updated with your activity',
+
style: TextStyle(
+
fontSize: 16,
+
color: Color(0xFFB6C2D2),
+
),
+
textAlign: TextAlign.center,
+
),
+
],
+
),
+
),
+
),
+
);
+
}
+
}
+95
lib/screens/home/profile_screen.dart
···
+
import 'package:flutter/material.dart';
+
import 'package:provider/provider.dart';
+
import 'package:go_router/go_router.dart';
+
import '../../providers/auth_provider.dart';
+
import '../../widgets/primary_button.dart';
+
+
class ProfileScreen extends StatelessWidget {
+
const ProfileScreen({super.key});
+
+
@override
+
Widget build(BuildContext context) {
+
final authProvider = Provider.of<AuthProvider>(context);
+
final isAuthenticated = authProvider.isAuthenticated;
+
+
return Scaffold(
+
backgroundColor: const Color(0xFF0B0F14),
+
appBar: AppBar(
+
backgroundColor: const Color(0xFF0B0F14),
+
foregroundColor: Colors.white,
+
title: const Text('Profile'),
+
automaticallyImplyLeading: false,
+
),
+
body: Center(
+
child: Padding(
+
padding: const EdgeInsets.all(24),
+
child: Column(
+
mainAxisAlignment: MainAxisAlignment.center,
+
children: [
+
const Icon(
+
Icons.person,
+
size: 64,
+
color: Color(0xFFFF6B35),
+
),
+
const SizedBox(height: 24),
+
Text(
+
isAuthenticated ? 'Your Profile' : 'Profile',
+
style: const TextStyle(
+
fontSize: 28,
+
color: Colors.white,
+
fontWeight: FontWeight.bold,
+
),
+
),
+
const SizedBox(height: 16),
+
if (isAuthenticated && authProvider.did != null) ...[
+
Text(
+
'Signed in as:',
+
style: TextStyle(
+
fontSize: 14,
+
color: Colors.white.withValues(alpha: 0.6),
+
),
+
),
+
const SizedBox(height: 4),
+
Text(
+
authProvider.did!,
+
style: const TextStyle(
+
fontSize: 16,
+
color: Color(0xFFB6C2D2),
+
fontFamily: 'monospace',
+
),
+
textAlign: TextAlign.center,
+
),
+
const SizedBox(height: 48),
+
PrimaryButton(
+
title: 'Sign Out',
+
onPressed: () async {
+
await authProvider.signOut();
+
if (context.mounted) {
+
context.go('/');
+
}
+
},
+
variant: ButtonVariant.outline,
+
),
+
] else ...[
+
const Text(
+
'Sign in to view your profile',
+
style: TextStyle(
+
fontSize: 16,
+
color: Color(0xFFB6C2D2),
+
),
+
textAlign: TextAlign.center,
+
),
+
const SizedBox(height: 48),
+
PrimaryButton(
+
title: 'Sign in',
+
onPressed: () => context.go('/login'),
+
variant: ButtonVariant.solid,
+
),
+
],
+
],
+
),
+
),
+
),
+
);
+
}
+
}
+51
lib/screens/home/search_screen.dart
···
+
import 'package:flutter/material.dart';
+
+
class SearchScreen extends StatelessWidget {
+
const SearchScreen({super.key});
+
+
@override
+
Widget build(BuildContext context) {
+
return Scaffold(
+
backgroundColor: const Color(0xFF0B0F14),
+
appBar: AppBar(
+
backgroundColor: const Color(0xFF0B0F14),
+
foregroundColor: Colors.white,
+
title: const Text('Search'),
+
automaticallyImplyLeading: false,
+
),
+
body: const Center(
+
child: Padding(
+
padding: EdgeInsets.all(24),
+
child: Column(
+
mainAxisAlignment: MainAxisAlignment.center,
+
children: [
+
Icon(
+
Icons.search,
+
size: 64,
+
color: Color(0xFFFF6B35),
+
),
+
SizedBox(height: 24),
+
Text(
+
'Search',
+
style: TextStyle(
+
fontSize: 28,
+
color: Colors.white,
+
fontWeight: FontWeight.bold,
+
),
+
),
+
SizedBox(height: 16),
+
Text(
+
'Search communities and conversations',
+
style: TextStyle(
+
fontSize: 16,
+
color: Color(0xFFB6C2D2),
+
),
+
textAlign: TextAlign.center,
+
),
+
],
+
),
+
),
+
),
+
);
+
}
+
}