Remove AppBar gap from notes and tasks list screens

Notes: no AppBar at all; a floating search icon (top-right corner)
triggers an inline search field above the list rather than an AppBar.

Tasks: no AppBar; TabBar sits directly at the top of the body, flush
with the quick-capture bar. The search icon lives to the right of the
tabs and expands a search field above them when tapped.

Both changes eliminate the ~56dp toolbar gap that was visible between
the quick-capture bar and the list content.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 11:49:46 -05:00
parent 2a35fe5532
commit 647b36837e
2 changed files with 221 additions and 169 deletions
+50 -21
View File
@@ -21,21 +21,27 @@ class _NotesListScreenState extends ConsumerState<NotesListScreen> {
final notesAsync = ref.watch(notesProvider); final notesAsync = ref.watch(notesProvider);
return Scaffold( return Scaffold(
appBar: AppBar( body: Stack(
automaticallyImplyLeading: false, children: [
title: _showSearch Column(
? TextField( children: [
if (_showSearch)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Row(
children: [
Expanded(
child: TextField(
autofocus: true, autofocus: true,
decoration: const InputDecoration( decoration: const InputDecoration(
hintText: 'Search notes…', hintText: 'Search notes…',
border: InputBorder.none, border: InputBorder.none,
prefixIcon: Icon(Icons.search),
), ),
onChanged: (v) => onChanged: (v) =>
setState(() => _search = v.trim().toLowerCase()), setState(() => _search = v.trim().toLowerCase()),
) ),
: null, ),
actions: [
if (_showSearch)
IconButton( IconButton(
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
tooltip: 'Close search', tooltip: 'Close search',
@@ -43,17 +49,14 @@ class _NotesListScreenState extends ConsumerState<NotesListScreen> {
_showSearch = false; _showSearch = false;
_search = ''; _search = '';
}), }),
)
else
IconButton(
icon: const Icon(Icons.search),
tooltip: 'Search',
onPressed: () => setState(() => _showSearch = true),
), ),
], ],
), ),
body: notesAsync.when( ),
loading: () => const Center(child: CircularProgressIndicator()), Expanded(
child: notesAsync.when(
loading: () =>
const Center(child: CircularProgressIndicator()),
error: (_, _) => Center( error: (_, _) => Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -97,22 +100,32 @@ class _NotesListScreenState extends ConsumerState<NotesListScreen> {
final note = filtered[i]; final note = filtered[i];
final preview = note.body final preview = note.body
.split('\n') .split('\n')
.firstWhere((l) => l.trim().isNotEmpty, orElse: () => '') .firstWhere((l) => l.trim().isNotEmpty,
orElse: () => '')
.trim(); .trim();
return ListTile( return ListTile(
title: Text(note.title), title: Text(note.title),
subtitle: Text( subtitle: Text(
preview.isNotEmpty preview.isNotEmpty
? preview ? preview
: note.updatedAt.toLocal().toString().substring(0, 16), : note.updatedAt
style: Theme.of(context).textTheme.bodySmall?.copyWith( .toLocal()
color: Theme.of(context).colorScheme.onSurfaceVariant, .toString()
.substring(0, 16),
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(
color: Theme.of(context)
.colorScheme
.onSurfaceVariant,
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
onTap: () => context.push( onTap: () => context.push(
Routes.noteDetail.replaceFirst(':id', '${note.id}'), Routes.noteDetail
.replaceFirst(':id', '${note.id}'),
), ),
); );
}, },
@@ -120,6 +133,22 @@ class _NotesListScreenState extends ConsumerState<NotesListScreen> {
); );
}, },
), ),
),
],
),
// Floating search button — only visible when search is closed
if (!_showSearch)
Positioned(
top: 4,
right: 4,
child: IconButton(
icon: const Icon(Icons.search),
tooltip: 'Search',
onPressed: () => setState(() => _showSearch = true),
),
),
],
),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
heroTag: 'notes_fab', heroTag: 'notes_fab',
onPressed: () => context.push(Routes.noteNew), onPressed: () => context.push(Routes.noteNew),
+46 -23
View File
@@ -36,21 +36,26 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen>
final tasksAsync = ref.watch(tasksProvider); final tasksAsync = ref.watch(tasksProvider);
return Scaffold( return Scaffold(
appBar: AppBar( body: Column(
automaticallyImplyLeading: false, children: [
title: _showSearch // Search field — appears above the tabs when active
? TextField( if (_showSearch)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Row(
children: [
Expanded(
child: TextField(
autofocus: true, autofocus: true,
decoration: const InputDecoration( decoration: const InputDecoration(
hintText: 'Search tasks…', hintText: 'Search tasks…',
border: InputBorder.none, border: InputBorder.none,
prefixIcon: Icon(Icons.search),
), ),
onChanged: (v) => onChanged: (v) =>
setState(() => _search = v.trim().toLowerCase()), setState(() => _search = v.trim().toLowerCase()),
) ),
: null, ),
actions: [
if (_showSearch)
IconButton( IconButton(
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
tooltip: 'Close search', tooltip: 'Close search',
@@ -58,15 +63,15 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen>
_showSearch = false; _showSearch = false;
_search = ''; _search = '';
}), }),
)
else
IconButton(
icon: const Icon(Icons.search),
tooltip: 'Search',
onPressed: () => setState(() => _showSearch = true),
), ),
], ],
bottom: TabBar( ),
),
// Tab bar row — search icon sits to the right of the tabs
Row(
children: [
Expanded(
child: TabBar(
controller: _tabs, controller: _tabs,
tabs: const [ tabs: const [
Tab(text: 'To Do'), Tab(text: 'To Do'),
@@ -75,7 +80,17 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen>
], ],
), ),
), ),
body: tasksAsync.when( if (!_showSearch)
IconButton(
icon: const Icon(Icons.search),
tooltip: 'Search',
onPressed: () => setState(() => _showSearch = true),
),
],
),
// Content
Expanded(
child: tasksAsync.when(
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: (_, _) => Center( error: (_, _) => Center(
child: Column( child: Column(
@@ -98,15 +113,20 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen>
: tasks : tasks
.where((t) => .where((t) =>
t.title.toLowerCase().contains(_search) || t.title.toLowerCase().contains(_search) ||
(t.description ?? '').toLowerCase().contains(_search)) (t.description ?? '')
.toLowerCase()
.contains(_search))
.toList(); .toList();
final todo = final todo = filtered
filtered.where((t) => t.status == TaskStatus.todo).toList(); .where((t) => t.status == TaskStatus.todo)
final inProgress = .toList();
filtered.where((t) => t.status == TaskStatus.inProgress).toList(); final inProgress = filtered
final done = .where((t) => t.status == TaskStatus.inProgress)
filtered.where((t) => t.status == TaskStatus.done).toList(); .toList();
final done = filtered
.where((t) => t.status == TaskStatus.done)
.toList();
return RefreshIndicator( return RefreshIndicator(
onRefresh: () => ref.refresh(tasksProvider.future), onRefresh: () => ref.refresh(tasksProvider.future),
@@ -121,6 +141,9 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen>
); );
}, },
), ),
),
],
),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
heroTag: 'tasks_fab', heroTag: 'tasks_fab',
onPressed: () => context.push(Routes.taskNew), onPressed: () => context.push(Routes.taskNew),