fix(todo): the add button slides away while scrolling, and stays put behind quick add
Every tab's screen (TabScreen: Inbox, Today, Upcoming, a project's, filters' and labels') hands its add button to halkyon_ui's ScrollHidingFab, as Learn's screens do: it slides away while the tasks scroll on down and comes back as they scroll up. Its scaffold no longer resizes for the keyboard: opening quick add, the add button and anything at the screen's foot jumped up by the keyboard's height behind the bar, which floats above the keyboard by itself. No tab has a field of its own in its body (search, the editors and the pickers are screens, sheets or dialogs of their own). A task's sheet, where a sub-task is added, keeps still already: its foot is its scaffold's bar. Whats-New: The add button slides away while you scroll down, and the screen behind Add task no longer jumps with the keyboard. Whats-New-De: Der Hinzufügen-Knopf weicht beim Herunterscrollen aus, und der Bildschirm hinter Aufgabe hinzufügen springt nicht mehr mit der Tastatur. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
6c095fab47
commit
90f9415e72
2 changed files with 149 additions and 44 deletions
|
|
@ -48,50 +48,12 @@ class const TabScreen({
|
||||||
},
|
},
|
||||||
child: TopGradient(
|
child: TopGradient(
|
||||||
background: Theme.of(context).colorScheme.surface,
|
background: Theme.of(context).colorScheme.surface,
|
||||||
child: Scaffold(
|
// The add button slides away while the tasks scroll on down, and
|
||||||
backgroundColor: Colors.transparent,
|
// back as they scroll up, as Learn's (the developer's review, 7 Nov
|
||||||
appBar: AppBar(
|
// 2026).
|
||||||
backgroundColor: Colors.transparent,
|
child: ScrollHidingFab(
|
||||||
systemOverlayStyle: TopGradient.overlayStyle(context),
|
fab: switch (floatingActionButton) {
|
||||||
leading: choosing
|
// Clear of the navigation bar floating over the foot on a phone.
|
||||||
? IconButton(
|
|
||||||
tooltip: l10n.selectionEnd,
|
|
||||||
icon: const Icon(LucideIcons.arrowLeft),
|
|
||||||
onPressed: selection.end,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
title: switch (subtitle) {
|
|
||||||
final subtitle? when !choosing => Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(title),
|
|
||||||
Text(
|
|
||||||
subtitle,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
_ => Text(
|
|
||||||
choosing ? l10n.selectionCount(selection.ids.length) : title,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
actions: choosing
|
|
||||||
? const []
|
|
||||||
: [
|
|
||||||
if (search) const SearchButton(),
|
|
||||||
...actions,
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: body,
|
|
||||||
bottomNavigationBar: choosing
|
|
||||||
? TaskSelectionBar(selection: selection)
|
|
||||||
: null,
|
|
||||||
// Clear of the navigation bar floating over the foot on a phone.
|
|
||||||
floatingActionButton: switch (floatingActionButton) {
|
|
||||||
final button? when !choosing => Padding(
|
final button? when !choosing => Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
bottom: FloatingNavigationBar.coverOf(context),
|
bottom: FloatingNavigationBar.coverOf(context),
|
||||||
|
|
@ -100,6 +62,55 @@ class const TabScreen({
|
||||||
),
|
),
|
||||||
_ => null,
|
_ => null,
|
||||||
},
|
},
|
||||||
|
builder: (context, fab) => Scaffold(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
// Quick add's bar and the keyboard float over the tasks, which
|
||||||
|
// stay where they are (the developer's review, 7 Nov 2026: they
|
||||||
|
// jumped up by the keyboard's height behind the bar); a field of
|
||||||
|
// the screen's own is in a sheet or a screen of its own.
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
systemOverlayStyle: TopGradient.overlayStyle(context),
|
||||||
|
leading: choosing
|
||||||
|
? IconButton(
|
||||||
|
tooltip: l10n.selectionEnd,
|
||||||
|
icon: const Icon(LucideIcons.arrowLeft),
|
||||||
|
onPressed: selection.end,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
title: switch (subtitle) {
|
||||||
|
final subtitle? when !choosing => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(title),
|
||||||
|
Text(
|
||||||
|
subtitle,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
_ => Text(
|
||||||
|
choosing ? l10n.selectionCount(selection.ids.length) : title,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
actions: choosing
|
||||||
|
? const []
|
||||||
|
: [
|
||||||
|
if (search) const SearchButton(),
|
||||||
|
...actions,
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: body,
|
||||||
|
bottomNavigationBar: choosing
|
||||||
|
? TaskSelectionBar(selection: selection)
|
||||||
|
: null,
|
||||||
|
floatingActionButton: fab,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2749,6 +2749,100 @@ void main() {
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testWidgets('the add button slides away as the tasks scroll on down '
|
||||||
|
"and comes back as they scroll up, on every tab, as Learn's "
|
||||||
|
"(the developer's review, 7 Nov 2026)", (tester) async {
|
||||||
|
await start(tester, usedBefore: true);
|
||||||
|
await tester.runAsync(() async {
|
||||||
|
for (var index = 0; index < 30; index++) {
|
||||||
|
await tasks().add(
|
||||||
|
TaskContent(
|
||||||
|
title: 'Task $index',
|
||||||
|
projectId: inbox(),
|
||||||
|
position: index.toDouble(),
|
||||||
|
date: TaskDate(Day.of(DateTime.now())),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
taskRows.bringRemoteChanges();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
bool shown() =>
|
||||||
|
tester
|
||||||
|
.widget<AnimatedSlide>(
|
||||||
|
find.ancestor(
|
||||||
|
of: find.byType(AddTaskButton),
|
||||||
|
matching: find.byType(AnimatedSlide),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.offset ==
|
||||||
|
Offset.zero;
|
||||||
|
for (final tab in ['Inbox', 'Today', 'Upcoming']) {
|
||||||
|
await goTab(tester, tab);
|
||||||
|
expect(shown(), isTrue, reason: tab);
|
||||||
|
|
||||||
|
await tester.drag(find.text('Task 3'), const Offset(0, -300));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(shown(), isFalse, reason: tab);
|
||||||
|
|
||||||
|
await tester.drag(find.text('Task 8'), const Offset(0, 200));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(shown(), isTrue, reason: tab);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('behind the bar and the keyboard the tasks stay still, on '
|
||||||
|
"every tab and under a task's sheet (the developer's review, 7 "
|
||||||
|
"Nov 2026: they jumped up by the keyboard's height)", (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await start(tester, usedBefore: true);
|
||||||
|
await seed(
|
||||||
|
tester,
|
||||||
|
TaskContent(
|
||||||
|
title: 'Call mum',
|
||||||
|
projectId: inbox(),
|
||||||
|
date: TaskDate(Day.of(DateTime.now())),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
addTearDown(tester.view.resetViewInsets);
|
||||||
|
for (final tab in ['Inbox', 'Today', 'Upcoming']) {
|
||||||
|
await goTab(tester, tab);
|
||||||
|
final before = tester.getRect(find.text('Call mum'));
|
||||||
|
final button = find.byType(AddTaskButton);
|
||||||
|
final under = tester.getRect(button);
|
||||||
|
|
||||||
|
await tester.tap(find.byTooltip('Add task'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
tester.view.viewInsets = const FakeViewPadding(bottom: 300);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byType(QuickAddBar), findsOneWidget);
|
||||||
|
expect(tester.getRect(find.text('Call mum')), before, reason: tab);
|
||||||
|
expect(tester.getRect(button), under, reason: tab);
|
||||||
|
|
||||||
|
tester.view.resetViewInsets();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tapAt(const Offset(20, 20));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A sub-task added from the task's sheet: the sheet stays too.
|
||||||
|
await goTab(tester, 'Inbox');
|
||||||
|
await tester.tap(find.text('Call mum'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
final bar = tester.getRect(find.byKey(AddCommentBar.barKey));
|
||||||
|
await tester.ensureVisible(find.text('Add sub-task'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
final add = tester.getRect(find.text('Add sub-task'));
|
||||||
|
await tester.tap(find.text('Add sub-task'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
tester.view.viewInsets = const FakeViewPadding(bottom: 300);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byType(QuickAddBar), findsOneWidget);
|
||||||
|
expect(tester.getRect(find.byKey(AddCommentBar.barKey)), bar);
|
||||||
|
expect(tester.getRect(find.text('Add sub-task')), add);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets("a small bar docked just above the keyboard, as Todoist's, "
|
testWidgets("a small bar docked just above the keyboard, as Todoist's, "
|
||||||
'the list still in view above it', (tester) async {
|
'the list still in view above it', (tester) async {
|
||||||
await start(tester, usedBefore: true);
|
await start(tester, usedBefore: true);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue