Threads-style nav: move Search and Activity into bottom nav tabs, remove duplicate nav from NotificationsScreen

This commit is contained in:
Patrick Britton 2026-02-06 15:49:46 -06:00
parent a94c91da24
commit 1376802f76
3 changed files with 90 additions and 181 deletions

View file

@ -21,6 +21,7 @@ import '../screens/discover/discover_screen.dart';
import '../screens/secure_chat/secure_chat_full_screen.dart'; import '../screens/secure_chat/secure_chat_full_screen.dart';
import '../screens/secure_chat/secure_chat_loader_screen.dart'; import '../screens/secure_chat/secure_chat_loader_screen.dart';
import '../screens/post/threaded_conversation_screen.dart'; import '../screens/post/threaded_conversation_screen.dart';
import '../screens/notifications/notifications_screen.dart';
/// App routing config (GoRouter). /// App routing config (GoRouter).
class AppRoutes { class AppRoutes {
@ -88,6 +89,7 @@ class AppRoutes {
authenticatedChild: HomeShell(navigationShell: navigationShell), authenticatedChild: HomeShell(navigationShell: navigationShell),
), ),
branches: [ branches: [
// Tab 0: Home
StatefulShellBranch( StatefulShellBranch(
routes: [ routes: [
GoRoute( GoRoute(
@ -96,25 +98,25 @@ class AppRoutes {
), ),
], ],
), ),
// Tab 1: Search / Discover
StatefulShellBranch( StatefulShellBranch(
routes: [ routes: [
GoRoute( GoRoute(
path: quips, path: '/discover',
builder: (_, state) => QuipsFeedScreen( builder: (_, __) => const DiscoverScreen(),
initialPostId: state.uri.queryParameters['postId'],
), ),
),
], ],
), ),
// Tab 2: Activity / Notifications
StatefulShellBranch( StatefulShellBranch(
routes: [ routes: [
GoRoute( GoRoute(
path: '/beacon', path: '/activity',
builder: (_, __) => const BeaconScreen(), builder: (_, __) => const NotificationsScreen(),
), ),
], ],
), ),
// Tab 3: Profile
StatefulShellBranch( StatefulShellBranch(
routes: [ routes: [
GoRoute( GoRoute(

View file

@ -206,17 +206,14 @@ class _HomeShellState extends ConsumerState<HomeShell> with WidgetsBindingObserv
label: 'Home', label: 'Home',
), ),
_buildNavBarItem( _buildNavBarItem(
icon: Icons.play_circle_outline, icon: Icons.search_outlined,
activeIcon: Icons.play_circle, activeIcon: Icons.search,
index: 1, index: 1,
label: 'Quips', label: 'Search',
), ),
const SizedBox(width: 48), const SizedBox(width: 48),
_buildNavBarItem( _buildActivityNavItem(
icon: Icons.sensors_outlined,
activeIcon: Icons.sensors,
index: 2, index: 2,
label: 'Beacon',
), ),
_buildNavBarItem( _buildNavBarItem(
icon: Icons.person_outline, icon: Icons.person_outline,
@ -253,17 +250,6 @@ class _HomeShellState extends ConsumerState<HomeShell> with WidgetsBindingObserv
), ),
), ),
actions: [ actions: [
IconButton(
icon: Icon(Icons.search, color: AppTheme.navyBlue),
tooltip: 'Discover',
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const DiscoverScreen(),
),
);
},
),
IconButton( IconButton(
icon: Consumer( icon: Consumer(
builder: (context, ref, child) { builder: (context, ref, child) {
@ -286,27 +272,6 @@ class _HomeShellState extends ConsumerState<HomeShell> with WidgetsBindingObserv
); );
}, },
), ),
IconButton(
icon: Consumer(
builder: (context, ref, child) {
final badge = ref.watch(currentBadgeProvider);
return Badge(
label: Text(badge.notificationCount.toString()),
isLabelVisible: badge.notificationCount > 0,
backgroundColor: Colors.redAccent,
child: Icon(Icons.notifications_none, color: AppTheme.navyBlue),
);
},
),
tooltip: 'Notifications',
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const NotificationsScreen(),
),
);
},
),
const SizedBox(width: 4), const SizedBox(width: 4),
], ],
); );
@ -337,6 +302,37 @@ class _HomeShellState extends ConsumerState<HomeShell> with WidgetsBindingObserv
), ),
); );
} }
Widget _buildActivityNavItem({required int index}) {
final isActive = widget.navigationShell.currentIndex == index;
return Expanded(
child: InkWell(
onTap: () => widget.navigationShell.goBranch(
index,
initialLocation: index == widget.navigationShell.currentIndex,
),
child: Container(
height: double.infinity,
alignment: Alignment.center,
child: Consumer(
builder: (context, ref, child) {
final badge = ref.watch(currentBadgeProvider);
return Badge(
label: Text(badge.notificationCount.toString()),
isLabelVisible: badge.notificationCount > 0,
backgroundColor: Colors.redAccent,
child: Icon(
isActive ? Icons.favorite : Icons.favorite_border,
color: isActive ? AppTheme.navyBlue : Colors.grey,
size: 26,
),
);
},
),
),
),
);
}
} }
class _VerticalBorderProgressPainter extends CustomPainter { class _VerticalBorderProgressPainter extends CustomPainter {

View file

@ -6,13 +6,9 @@ import 'package:timeago/timeago.dart' as timeago;
import '../../models/notification.dart'; import '../../models/notification.dart';
import '../../providers/api_provider.dart'; import '../../providers/api_provider.dart';
import '../../theme/app_theme.dart'; import '../../theme/app_theme.dart';
import '../../widgets/app_scaffold.dart';
import '../../widgets/media/signed_media_image.dart'; import '../../widgets/media/signed_media_image.dart';
import '../profile/viewable_profile_screen.dart'; import '../profile/viewable_profile_screen.dart';
import '../post/post_detail_screen.dart'; import '../post/post_detail_screen.dart';
import '../search/search_screen.dart';
import '../discover/discover_screen.dart';
import '../secure_chat/secure_chat_full_screen.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../../services/notification_service.dart'; import '../../services/notification_service.dart';
@ -352,37 +348,25 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
} }
} }
void _navigateHome() {
Navigator.of(context).pop();
}
void _navigateSearch() {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const DiscoverScreen()),
);
}
void _navigateChat() {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => const SecureChatFullScreen(),
fullscreenDialog: true,
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final canArchiveAll = _activeTabIndex == 0 && _notifications.isNotEmpty; final canArchiveAll = _activeTabIndex == 0 && _notifications.isNotEmpty;
return DefaultTabController( return DefaultTabController(
length: 2, length: 2,
child: AppScaffold( child: Column(
title: 'Activity', children: [
leading: const SizedBox.shrink(), // Tab bar + Archive All button
actions: [ Material(
color: AppTheme.scaffoldBg,
child: Column(
children: [
if (canArchiveAll) if (canArchiveAll)
TextButton( Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.only(right: 8, top: 4),
child: TextButton(
onPressed: _archiveAllNotifications, onPressed: _archiveAllNotifications,
child: Text( child: Text(
'Archive All', 'Archive All',
@ -392,8 +376,9 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
), ),
), ),
), ),
], ),
bottom: TabBar( ),
TabBar(
onTap: (index) { onTap: (index) {
if (index != _activeTabIndex) { if (index != _activeTabIndex) {
setState(() { setState(() {
@ -410,8 +395,12 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
Tab(text: 'Archived'), Tab(text: 'Archived'),
], ],
), ),
bottomNavigationBar: _buildBottomNav(), ],
body: _error != null ),
),
// Content
Expanded(
child: _error != null
? _ErrorState( ? _ErrorState(
message: _error!, message: _error!,
onRetry: () => _loadNotifications(refresh: true), onRetry: () => _loadNotifications(refresh: true),
@ -486,86 +475,8 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
), ),
), ),
), ),
);
}
Widget _buildBottomNav() {
return Container(
decoration: BoxDecoration(
color: AppTheme.scaffoldBg,
border: Border(
top: BorderSide(
color: AppTheme.egyptianBlue.withOpacity(0.1),
width: 0.5,
),
),
),
child: SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildNavItem(
icon: Icons.home_outlined,
label: 'Home',
onTap: _navigateHome,
),
_buildNavItem(
icon: Icons.search,
label: 'Discover',
onTap: _navigateSearch,
),
_buildNavItem(
icon: Icons.notifications,
label: 'Activity',
isActive: true,
onTap: () {},
),
_buildNavItem(
icon: Icons.chat_bubble_outline,
label: 'Chat',
onTap: _navigateChat,
),
], ],
), ),
),
),
);
}
Widget _buildNavItem({
required IconData icon,
required String label,
required VoidCallback onTap,
bool isActive = false,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
color: isActive ? AppTheme.navyBlue : Colors.grey,
size: 26,
),
const SizedBox(height: 2),
Text(
label,
style: TextStyle(
fontSize: 10,
color: isActive ? AppTheme.navyBlue : Colors.grey,
fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
),
),
],
),
),
); );
} }
} }