16 KiB
Android Nav Restructure & Projects Staleness Fix Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Replace the Projects bottom-nav tab with a "More" bottom sheet giving access to Projects, News, and Calendar; fix stale task data when returning from task edit.
Architecture: The shell's _tabs list shrinks from 4 to 3 entries; the 4th nav destination ("More") is intercepted in onDestinationSelected to show a showModalBottomSheet instead of navigating. Projects moves from a ShellRoute to a top-level push route. News and Calendar are added as stub push-routes. _tabIndex maps the three overflow paths to index 3 so "More" highlights correctly. The staleness fix moves the task-tap callback out of the stateless _TaskRow widget into the parent ConsumerState where ref is available, using .then() to invalidate after pop.
Tech Stack: Flutter, GoRouter, Riverpod, Material 3
Files
| File | Action | What changes |
|---|---|---|
lib/core/constants.dart |
Modify | Add news and calendar route constants |
lib/app.dart |
Modify | Remove Projects from ShellRoute, add 3 push routes, shrink _tabs, add _showMoreSheet, update _tabIndex, update both nav widgets |
lib/screens/library/project_tasks_screen.dart |
Modify | Add onTap: VoidCallback to _TaskRow; add _openTask method to state; pass callback at both call sites |
Task 1: Add route constants
Files:
-
Modify:
lib/core/constants.dart -
Step 1: Add
newsandcalendartoRoutes
Open lib/core/constants.dart. The file currently ends with:
abstract class Routes {
static const splash = '/';
static const setup = '/setup';
static const login = '/login';
static const notes = '/notes';
static const noteDetail = '/notes/:id';
static const noteEdit = '/notes/:id/edit';
static const noteNew = '/notes/new';
static const tasks = '/tasks';
static const taskNew = '/tasks/new';
static const taskEdit = '/tasks/:id/edit';
static const knowledge = '/knowledge';
static const projects = '/projects';
static const projectEdit = '/projects/:id/edit';
static const conversations = '/chat';
static const chat = '/chat/:id';
static const quickCapture = '/quick-capture';
static const settings = '/settings';
static const briefing = '/briefing';
static const projectTasks = '/projects/:id/tasks';
}
Add two constants after briefing:
abstract class Routes {
static const splash = '/';
static const setup = '/setup';
static const login = '/login';
static const notes = '/notes';
static const noteDetail = '/notes/:id';
static const noteEdit = '/notes/:id/edit';
static const noteNew = '/notes/new';
static const tasks = '/tasks';
static const taskNew = '/tasks/new';
static const taskEdit = '/tasks/:id/edit';
static const knowledge = '/knowledge';
static const projects = '/projects';
static const projectEdit = '/projects/:id/edit';
static const conversations = '/chat';
static const chat = '/chat/:id';
static const quickCapture = '/quick-capture';
static const settings = '/settings';
static const briefing = '/briefing';
static const news = '/news';
static const calendar = '/calendar';
static const projectTasks = '/projects/:id/tasks';
}
- Step 2: Verify no analysis errors
cd /home/bvandeusen/Nextcloud/Projects/fabled_app
flutter analyze lib/core/constants.dart
Expected: No issues found!
Task 2: Restructure shell and add routes in app.dart
Files:
- Modify:
lib/app.dart
This task has several sub-steps. Make them all before running analyze.
- Step 1: Move Projects out of ShellRoute, add three push routes
Find the ShellRoute block (currently lines ~142–162):
ShellRoute(
builder: (context, state, child) => _Shell(child: child),
routes: [
GoRoute(
path: Routes.briefing,
builder: (_, _) => const BriefingScreen(),
),
GoRoute(
path: Routes.knowledge,
builder: (_, _) => const KnowledgeScreen(),
),
GoRoute(
path: Routes.conversations,
builder: (_, _) => const ConversationsTabScreen(),
),
GoRoute(
path: Routes.projects,
builder: (_, _) => const ProjectsScreen(),
),
],
),
Replace with (Projects removed from shell; three new top-level routes added after the closing ], of the ShellRoute):
ShellRoute(
builder: (context, state, child) => _Shell(child: child),
routes: [
GoRoute(
path: Routes.briefing,
builder: (_, _) => const BriefingScreen(),
),
GoRoute(
path: Routes.knowledge,
builder: (_, _) => const KnowledgeScreen(),
),
GoRoute(
path: Routes.conversations,
builder: (_, _) => const ConversationsTabScreen(),
),
],
),
GoRoute(
path: Routes.projects,
builder: (_, _) => const ProjectsScreen(),
),
GoRoute(
path: Routes.news,
builder: (_, _) => Scaffold(
appBar: AppBar(title: const Text('News')),
body: const Center(child: Text('News — coming soon')),
),
),
GoRoute(
path: Routes.calendar,
builder: (_, _) => Scaffold(
appBar: AppBar(title: const Text('Calendar')),
body: const Center(child: Text('Calendar — coming soon')),
),
),
- Step 2: Shrink
_tabsfrom 4 to 3 entries
Find in _ShellState:
static const _tabs = [
Routes.briefing,
Routes.knowledge,
Routes.conversations,
Routes.projects,
];
Replace with:
static const _tabs = [
Routes.briefing,
Routes.knowledge,
Routes.conversations,
];
- Step 3: Update
_tabIndexto map overflow routes to index 3
Find:
int _tabIndex(String location) {
for (var i = 0; i < _tabs.length; i++) {
if (location.startsWith(_tabs[i])) return i;
}
return 0;
}
Replace with:
int _tabIndex(String location) {
for (var i = 0; i < _tabs.length; i++) {
if (location.startsWith(_tabs[i])) return i;
}
if (location.startsWith(Routes.projects) ||
location.startsWith(Routes.news) ||
location.startsWith(Routes.calendar)) return 3;
return 0;
}
- Step 4: Add
_showMoreSheetmethod to_ShellState
Add this method anywhere in _ShellState, e.g. just before build:
void _showMoreSheet(BuildContext context) {
showModalBottomSheet<void>(
context: context,
builder: (_) => SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.folder_outlined),
title: const Text('Projects'),
onTap: () {
Navigator.pop(context);
context.push(Routes.projects);
},
),
ListTile(
leading: const Icon(Icons.newspaper_outlined),
title: const Text('News'),
onTap: () {
Navigator.pop(context);
context.push(Routes.news);
},
),
ListTile(
leading: const Icon(Icons.calendar_month_outlined),
title: const Text('Calendar'),
onTap: () {
Navigator.pop(context);
context.push(Routes.calendar);
},
),
],
),
),
);
}
- Step 5: Update
NavigationRail— intercept index 3 and swap destination
Find in the wide-layout branch:
NavigationRail(
selectedIndex: index,
onDestinationSelected: (i) => context.go(_tabs[i]),
labelType: NavigationRailLabelType.all,
destinations: const [
NavigationRailDestination(
icon: Icon(Icons.wb_sunny_outlined),
selectedIcon: Icon(Icons.wb_sunny),
label: Text('Briefing'),
),
NavigationRailDestination(
icon: Icon(Icons.menu_book_outlined),
selectedIcon: Icon(Icons.menu_book),
label: Text('Knowledge'),
),
NavigationRailDestination(
icon: Icon(Icons.chat_bubble_outline),
selectedIcon: Icon(Icons.chat_bubble),
label: Text('Chat'),
),
NavigationRailDestination(
icon: Icon(Icons.folder_outlined),
selectedIcon: Icon(Icons.folder),
label: Text('Projects'),
),
],
),
Replace with:
NavigationRail(
selectedIndex: index,
onDestinationSelected: (i) {
if (i == 3) {
_showMoreSheet(context);
} else {
context.go(_tabs[i]);
}
},
labelType: NavigationRailLabelType.all,
destinations: const [
NavigationRailDestination(
icon: Icon(Icons.wb_sunny_outlined),
selectedIcon: Icon(Icons.wb_sunny),
label: Text('Briefing'),
),
NavigationRailDestination(
icon: Icon(Icons.menu_book_outlined),
selectedIcon: Icon(Icons.menu_book),
label: Text('Knowledge'),
),
NavigationRailDestination(
icon: Icon(Icons.chat_bubble_outline),
selectedIcon: Icon(Icons.chat_bubble),
label: Text('Chat'),
),
NavigationRailDestination(
icon: Icon(Icons.more_horiz_outlined),
selectedIcon: Icon(Icons.more_horiz),
label: Text('More'),
),
],
),
- Step 6: Update
NavigationBar— intercept index 3 and swap destination
Find in the narrow-layout branch:
bottomNavigationBar: NavigationBar(
selectedIndex: index,
onDestinationSelected: (i) => context.go(_tabs[i]),
destinations: const [
NavigationDestination(
icon: Icon(Icons.wb_sunny_outlined),
selectedIcon: Icon(Icons.wb_sunny),
label: 'Briefing',
),
NavigationDestination(
icon: Icon(Icons.menu_book_outlined),
selectedIcon: Icon(Icons.menu_book),
label: 'Knowledge',
),
NavigationDestination(
icon: Icon(Icons.chat_bubble_outline),
selectedIcon: Icon(Icons.chat_bubble),
label: 'Chat',
),
NavigationDestination(
icon: Icon(Icons.folder_outlined),
selectedIcon: Icon(Icons.folder),
label: 'Projects',
),
],
),
Replace with:
bottomNavigationBar: NavigationBar(
selectedIndex: index,
onDestinationSelected: (i) {
if (i == 3) {
_showMoreSheet(context);
} else {
context.go(_tabs[i]);
}
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.wb_sunny_outlined),
selectedIcon: Icon(Icons.wb_sunny),
label: 'Briefing',
),
NavigationDestination(
icon: Icon(Icons.menu_book_outlined),
selectedIcon: Icon(Icons.menu_book),
label: 'Knowledge',
),
NavigationDestination(
icon: Icon(Icons.chat_bubble_outline),
selectedIcon: Icon(Icons.chat_bubble),
label: 'Chat',
),
NavigationDestination(
icon: Icon(Icons.more_horiz_outlined),
selectedIcon: Icon(Icons.more_horiz),
label: 'More',
),
],
),
- Step 7: Verify no analysis errors
cd /home/bvandeusen/Nextcloud/Projects/fabled_app
flutter analyze lib/app.dart lib/core/constants.dart
Expected: No issues found!
- Step 8: Commit
git add lib/core/constants.dart lib/app.dart
git commit -m "feat: replace Projects tab with More bottom sheet (news/calendar stubs)"
Task 3: Fix stale tasks after returning from task edit
Files:
-
Modify:
lib/screens/library/project_tasks_screen.dart -
Step 1: Add
_openTaskmethod to_ProjectTasksScreenState
_ProjectTasksScreenState is a ConsumerState — it has ref and context. Find the class body (look for _cycleStatus method as a landmark) and add _openTask as a sibling method:
void _openTask(int taskId) {
context
.push(Routes.taskEdit.replaceFirst(':id', '$taskId'))
.then((_) => ref.invalidate(projectTasksProvider(widget.projectId)));
}
- Step 2: Add
onTapparameter to_TaskRow
Find the _TaskRow class definition:
class _TaskRow extends StatelessWidget {
final Task task;
final TaskStatus effectiveStatus;
final VoidCallback onStatusTap;
const _TaskRow({
required this.task,
required this.effectiveStatus,
required this.onStatusTap,
});
Replace with:
class _TaskRow extends StatelessWidget {
final Task task;
final TaskStatus effectiveStatus;
final VoidCallback onStatusTap;
final VoidCallback onTap;
const _TaskRow({
required this.task,
required this.effectiveStatus,
required this.onStatusTap,
required this.onTap,
});
- Step 3: Use the
onTapcallback inInkWell
Find inside _TaskRow.build:
child: InkWell(
onTap: () => context
.push(Routes.taskEdit.replaceFirst(':id', '${task.id}')),
Replace with:
child: InkWell(
onTap: onTap,
- Step 4: Pass
onTapat both_TaskRowcall sites
There are two places in _buildBody where _TaskRow is instantiated. Update both:
First call site (milestone tasks, around line 170):
return _TaskRow(
task: task,
effectiveStatus: _effectiveStatus(task),
onStatusTap: () => _cycleStatus(task),
onTap: () => _openTask(task.id),
);
Second call site (unassigned tasks, around line 197):
return _TaskRow(
task: task,
effectiveStatus: _effectiveStatus(task),
onStatusTap: () => _cycleStatus(task),
onTap: () => _openTask(task.id),
);
- Step 5: Verify no analysis errors
cd /home/bvandeusen/Nextcloud/Projects/fabled_app
flutter analyze lib/screens/library/project_tasks_screen.dart
Expected: No issues found!
- Step 6: Commit
git add lib/screens/library/project_tasks_screen.dart
git commit -m "fix: invalidate project tasks on return from task edit screen"
Task 4: Final check
- Step 1: Full analyze
cd /home/bvandeusen/Nextcloud/Projects/fabled_app
flutter analyze
Expected: No issues found!