This repository has been archived on 2026-06-02. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
FabledApp/docs/superpowers/plans/2026-04-06-android-nav-restructure.md
T

16 KiB
Raw Blame History

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 news and calendar to Routes

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 ~142162):

      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 _tabs from 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 _tabIndex to 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 _showMoreSheet method 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 _openTask method 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 onTap parameter 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 onTap callback in InkWell

Find inside _TaskRow.build:

      child: InkWell(
        onTap: () => context
            .push(Routes.taskEdit.replaceFirst(':id', '${task.id}')),

Replace with:

      child: InkWell(
        onTap: onTap,
  • Step 4: Pass onTap at both _TaskRow call 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!