feat(todo): the list scrolls to each task as quick add adds it, just above its bar
As Todoist's (the developer's review, 10 Nov 2026): the row of the task added brings itself into view, just above quick add's bar on a phone, within its list beside the dialog on the desktop; while quick add is open, a screen's lists leave room at their end for the bar and the keyboard, so that even the last task can rise above them, nothing in view moving for it. A long list builds the rows near its view alone, so the screen scrolls on down until the task's row is built. Whats-New: Each task you add now scrolls into view, just above the add bar. Whats-New-De: Jede neue Aufgabe rückt jetzt ins Bild, direkt über die Leiste zum Hinzufügen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
607ac71233
commit
7f643aa00e
7 changed files with 348 additions and 7 deletions
File diff suppressed because one or more lines are too long
|
|
@ -7,6 +7,7 @@ import 'package:halkyon_todo_flutter/src/app/todo_languages.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/app/todo_router.dart';
|
import 'package:halkyon_todo_flutter/src/app/todo_router.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/reminder/widget/reminder_scheduler.dart';
|
import 'package:halkyon_todo_flutter/src/feature/reminder/widget/reminder_scheduler.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/task/bloc/todo_cubit.dart';
|
import 'package:halkyon_todo_flutter/src/feature/task/bloc/todo_cubit.dart';
|
||||||
|
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_reveal.dart';
|
||||||
import 'package:halkyon_ui/halkyon_ui.dart';
|
import 'package:halkyon_ui/halkyon_ui.dart';
|
||||||
import 'package:material_ui/material_ui.dart';
|
import 'package:material_ui/material_ui.dart';
|
||||||
|
|
||||||
|
|
@ -70,9 +71,13 @@ class const TodoPersonApp({
|
||||||
class _TodoPersonAppState() extends State<TodoPersonApp> {
|
class _TodoPersonAppState() extends State<TodoPersonApp> {
|
||||||
final _router = buildTodoRouter();
|
final _router = buildTodoRouter();
|
||||||
|
|
||||||
|
/// The task quick add just added, for the lists to bring into view.
|
||||||
|
final _reveal = TaskReveal();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_router.dispose();
|
_router.dispose();
|
||||||
|
_reveal.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,10 +125,13 @@ class _TodoPersonAppState() extends State<TodoPersonApp> {
|
||||||
SyncStatusBloc(PersonScope.of<TodoPerson>(context).sync),
|
SyncStatusBloc(PersonScope.of<TodoPerson>(context).sync),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: ScreenColor(
|
child: TaskRevealScope(
|
||||||
color: PaletteColor.strawberry.background,
|
notifier: _reveal,
|
||||||
// Their tasks' reminders on this device (ADR 0033, D5).
|
child: ScreenColor(
|
||||||
child: ReminderScheduler(child: child!),
|
color: PaletteColor.strawberry.background,
|
||||||
|
// Their tasks' reminders on this device (ADR 0033, D5).
|
||||||
|
child: ReminderScheduler(child: child!),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import 'package:halkyon_todo_flutter/src/feature/task/model/task.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/task/widget/date_picker.dart';
|
import 'package:halkyon_todo_flutter/src/feature/task/widget/date_picker.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_drag.dart';
|
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_drag.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_pickers.dart';
|
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_pickers.dart';
|
||||||
|
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_reveal.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_texts.dart';
|
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_texts.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/task/widget/title_tokens.dart';
|
import 'package:halkyon_todo_flutter/src/feature/task/widget/title_tokens.dart';
|
||||||
import 'package:halkyon_ui/halkyon_ui.dart';
|
import 'package:halkyon_ui/halkyon_ui.dart';
|
||||||
|
|
@ -68,6 +69,11 @@ Future<void> showQuickAdd(
|
||||||
},
|
},
|
||||||
spot: spot,
|
spot: spot,
|
||||||
);
|
);
|
||||||
|
final reveal = TaskRevealScope.read(context);
|
||||||
|
return _shown(context, bar).whenComplete(() => reveal?.closed());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _shown(BuildContext context, Widget bar) {
|
||||||
if (sheetShowsAsDialog(context)) {
|
if (sheetShowsAsDialog(context)) {
|
||||||
return showDialog<void>(
|
return showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -339,6 +345,22 @@ class _QuickAddState() extends State<_QuickAdd> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Has the lists bring the task [id] into view, above the bar where it
|
||||||
|
/// is a sheet (the developer's review, 10 Nov 2026).
|
||||||
|
void _revealAdded(String id) {
|
||||||
|
final reveal = TaskRevealScope.read(context);
|
||||||
|
final box = context.findRenderObject();
|
||||||
|
if (reveal == null || box is! RenderBox) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final dialog = sheetShowsAsDialog(context);
|
||||||
|
reveal.added(
|
||||||
|
id,
|
||||||
|
barTop: dialog ? null : box.localToGlobal(Offset.zero).dy,
|
||||||
|
height: MediaQuery.sizeOf(context).height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _add() async {
|
Future<void> _add() async {
|
||||||
final l10n = AppLocalizations.of(context);
|
final l10n = AppLocalizations.of(context);
|
||||||
final parsed = _parse();
|
final parsed = _parse();
|
||||||
|
|
@ -380,6 +402,7 @@ class _QuickAddState() extends State<_QuickAdd> {
|
||||||
: siblings.isEmpty
|
: siblings.isEmpty
|
||||||
? 0.0
|
? 0.0
|
||||||
: last + 1;
|
: last + 1;
|
||||||
|
final String id;
|
||||||
try {
|
try {
|
||||||
final made = <String>[
|
final made = <String>[
|
||||||
for (final (index, name) in parsed.newLabels.indexed)
|
for (final (index, name) in parsed.newLabels.indexed)
|
||||||
|
|
@ -387,7 +410,7 @@ class _QuickAddState() extends State<_QuickAdd> {
|
||||||
LabelContent(name: name, position: lastLabel + 1 + index),
|
LabelContent(name: name, position: lastLabel + 1 + index),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
await todo.tasks.add(
|
id = await todo.tasks.add(
|
||||||
TaskContent(
|
TaskContent(
|
||||||
title: title,
|
title: title,
|
||||||
projectId: projectId,
|
projectId: projectId,
|
||||||
|
|
@ -418,6 +441,7 @@ class _QuickAddState() extends State<_QuickAdd> {
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_revealAdded(id);
|
||||||
// Ready for the next one, in the same place and on the same day.
|
// Ready for the next one, in the same place and on the same day.
|
||||||
await _dictation.stop();
|
await _dictation.stop();
|
||||||
_title.clear();
|
_title.clear();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:material_ui/material_ui.dart';
|
||||||
|
|
||||||
|
/// The task quick add has just added, brought into view where a list shows
|
||||||
|
/// it, just above quick add's bar while that is open, as Todoist's (the
|
||||||
|
/// developer's review, 10 Nov 2026).
|
||||||
|
class TaskReveal() extends ChangeNotifier {
|
||||||
|
String? _pending;
|
||||||
|
|
||||||
|
/// The task to be brought into view, until a row of it has been.
|
||||||
|
String? get pending => _pending;
|
||||||
|
|
||||||
|
/// How much of the window's foot quick add's bar and the keyboard cover,
|
||||||
|
/// from the first task it added on until it closes: the lists' ends
|
||||||
|
/// leave room for it, so that their last task can stand above the bar.
|
||||||
|
double get cover => _cover;
|
||||||
|
double _cover = 0;
|
||||||
|
|
||||||
|
/// The top of quick add's bar in the window as it added the task; none
|
||||||
|
/// for a dialog, which stands beside the list.
|
||||||
|
double? get barTop => _barTop;
|
||||||
|
double? _barTop;
|
||||||
|
|
||||||
|
/// Quick add added the task [id], its bar's top at [barTop] in a window
|
||||||
|
/// [height] high where it is a sheet.
|
||||||
|
void added(String id, {double? barTop, double height = 0}) {
|
||||||
|
_pending = id;
|
||||||
|
_barTop = barTop;
|
||||||
|
_cover = barTop == null ? 0 : max(_cover, height - barTop);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes the task [id] to bring into view, once: true where it was the
|
||||||
|
/// one pending.
|
||||||
|
bool claim(String id) {
|
||||||
|
if (_pending != id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_pending = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Quick add closed: the lists' ends as they were.
|
||||||
|
void closed() {
|
||||||
|
if (_pending == null && _cover == 0 && _barTop == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_pending = null;
|
||||||
|
_cover = 0;
|
||||||
|
_barTop = null;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Holds the person's [TaskReveal] for their lists and quick add, above the
|
||||||
|
/// navigators, as quick add opens on the root one.
|
||||||
|
class const TaskRevealScope({
|
||||||
|
required TaskReveal super.notifier,
|
||||||
|
required super.child,
|
||||||
|
super.key,
|
||||||
|
}) extends InheritedNotifier<TaskReveal> {
|
||||||
|
/// The [TaskReveal] above [context], which rebuilds as it changes; none
|
||||||
|
/// outside a person's app.
|
||||||
|
static TaskReveal? maybeOf(BuildContext context) =>
|
||||||
|
context.dependOnInheritedWidgetOfExactType<TaskRevealScope>()?.notifier;
|
||||||
|
|
||||||
|
/// The [TaskReveal] above [context], without depending on it.
|
||||||
|
static TaskReveal? read(BuildContext context) =>
|
||||||
|
context.getInheritedWidgetOfExactType<TaskRevealScope>()?.notifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Brings [context], a row of the task just added, into view in its list:
|
||||||
|
/// just above quick add's bar, or within the list where it is a dialog.
|
||||||
|
void revealRow(BuildContext context, TaskReveal reveal) {
|
||||||
|
final row = context.findRenderObject();
|
||||||
|
final scrollable = Scrollable.maybeOf(context, axis: Axis.vertical);
|
||||||
|
final view = scrollable?.context.findRenderObject();
|
||||||
|
if (row is! RenderBox ||
|
||||||
|
!row.hasSize ||
|
||||||
|
scrollable == null ||
|
||||||
|
view is! RenderBox) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final rect = row.localToGlobal(Offset.zero) & row.size;
|
||||||
|
final shown = view.localToGlobal(Offset.zero) & view.size;
|
||||||
|
final foot = min(
|
||||||
|
shown.bottom - MediaQuery.paddingOf(scrollable.context).bottom,
|
||||||
|
reveal.barTop ?? double.infinity,
|
||||||
|
);
|
||||||
|
const gap = 8.0;
|
||||||
|
final position = scrollable.position;
|
||||||
|
final by = rect.bottom > foot - gap
|
||||||
|
? rect.bottom - (foot - gap)
|
||||||
|
: rect.top < shown.top
|
||||||
|
? rect.top - shown.top
|
||||||
|
: 0.0;
|
||||||
|
if (by == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final to = (position.pixels + by).clamp(
|
||||||
|
position.minScrollExtent,
|
||||||
|
position.maxScrollExtent,
|
||||||
|
);
|
||||||
|
if (to != position.pixels) {
|
||||||
|
unawaited(
|
||||||
|
position.animateTo(
|
||||||
|
to,
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
curve: Curves.easeOutCubic,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [child], a screen's tasks, leaving room at their end for quick add's
|
||||||
|
/// bar while it is open, and scrolling on down, a screenful at a time,
|
||||||
|
/// while the task just added has no row built yet to bring itself into
|
||||||
|
/// view: a long list builds those near its view alone.
|
||||||
|
class const TaskRevealRoom({required final Widget child, super.key})
|
||||||
|
extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
State<TaskRevealRoom> createState() => _TaskRevealRoomState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TaskRevealRoomState() extends State<TaskRevealRoom> {
|
||||||
|
TaskReveal? _reveal;
|
||||||
|
String? _seeking;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
final reveal = TaskRevealScope.maybeOf(context);
|
||||||
|
_reveal = reveal;
|
||||||
|
final pending = reveal?.pending;
|
||||||
|
if (pending != null && pending != _seeking) {
|
||||||
|
_seeking = pending;
|
||||||
|
_seek(pending, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Looks for the task [id] after a frame, [steps] screenfuls down so far.
|
||||||
|
void _seek(String id, int steps) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
final reveal = _reveal;
|
||||||
|
if (!mounted ||
|
||||||
|
reveal == null ||
|
||||||
|
reveal.pending != id ||
|
||||||
|
!TickerMode.valuesOf(context).enabled ||
|
||||||
|
steps > 40) {
|
||||||
|
_seeking = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final position = _listOf(context)?.position;
|
||||||
|
if (position == null || position.pixels >= position.maxScrollExtent) {
|
||||||
|
_seeking = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
position.jumpTo(
|
||||||
|
min(
|
||||||
|
position.pixels + position.viewportDimension * 0.8,
|
||||||
|
position.maxScrollExtent,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_seek(id, steps + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The first list going down in [context].
|
||||||
|
static ScrollableState? _listOf(BuildContext context) {
|
||||||
|
ScrollableState? found;
|
||||||
|
void visit(Element element) {
|
||||||
|
if (found != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (element
|
||||||
|
case StatefulElement(
|
||||||
|
:final ScrollableState state,
|
||||||
|
)
|
||||||
|
when state.position.axis == Axis.vertical) {
|
||||||
|
found = state;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
element.visitChildren(visit);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.visitChildElements(visit);
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cover = _reveal?.cover ?? 0;
|
||||||
|
if (cover == 0) {
|
||||||
|
return widget.child;
|
||||||
|
}
|
||||||
|
final media = MediaQuery.of(context);
|
||||||
|
return MediaQuery(
|
||||||
|
data: media.copyWith(
|
||||||
|
padding: media.padding.copyWith(
|
||||||
|
bottom: max(media.padding.bottom, cover),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ import 'package:halkyon_todo_flutter/src/feature/task/bloc/todo_cubit.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/task/model/task.dart';
|
import 'package:halkyon_todo_flutter/src/feature/task/model/task.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/task/model/task_views.dart';
|
import 'package:halkyon_todo_flutter/src/feature/task/model/task_views.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/task/widget/date_picker.dart';
|
import 'package:halkyon_todo_flutter/src/feature/task/widget/date_picker.dart';
|
||||||
|
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_reveal.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_selection.dart';
|
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_selection.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_sheet.dart';
|
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_sheet.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_texts.dart';
|
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_texts.dart';
|
||||||
|
|
@ -60,6 +61,20 @@ class const TaskTile({
|
||||||
final l10n = AppLocalizations.of(context);
|
final l10n = AppLocalizations.of(context);
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final state = context.watch<TodoCubit>().state;
|
final state = context.watch<TodoCubit>().state;
|
||||||
|
// The task just added brings itself into view (the developer's
|
||||||
|
// review, 10 Nov 2026); a row on a screen out of sight leaves it to
|
||||||
|
// the one in view.
|
||||||
|
final reveal = TaskRevealScope.maybeOf(context);
|
||||||
|
if (reveal != null &&
|
||||||
|
reveal.pending == task.id &&
|
||||||
|
TickerMode.valuesOf(context).enabled &&
|
||||||
|
reveal.claim(task.id)) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (context.mounted) {
|
||||||
|
revealRow(context, reveal);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
final content = task.content;
|
final content = task.content;
|
||||||
final today = Day.of(clock.now());
|
final today = Day.of(clock.now());
|
||||||
final muted = theme.colorScheme.onSurfaceVariant;
|
final muted = theme.colorScheme.onSurfaceVariant;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import 'package:halkyon_todo_flutter/src/app/todo_dependencies.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/core/widget/menu_entry.dart';
|
import 'package:halkyon_todo_flutter/src/core/widget/menu_entry.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/task/model/task_views.dart';
|
import 'package:halkyon_todo_flutter/src/feature/task/model/task_views.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/task/widget/search_screen.dart';
|
import 'package:halkyon_todo_flutter/src/feature/task/widget/search_screen.dart';
|
||||||
|
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_reveal.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_selection.dart';
|
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_selection.dart';
|
||||||
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_writes.dart';
|
import 'package:halkyon_todo_flutter/src/feature/task/widget/task_writes.dart';
|
||||||
import 'package:halkyon_ui/halkyon_ui.dart';
|
import 'package:halkyon_ui/halkyon_ui.dart';
|
||||||
|
|
@ -108,7 +109,9 @@ class const TabScreen({
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: body,
|
// Room at the tasks' end for quick add's bar, and the task it
|
||||||
|
// added brought into view (the developer's review, 10 Nov 2026).
|
||||||
|
body: TaskRevealRoom(child: body),
|
||||||
// On a phone the button stands over the fade above the
|
// On a phone the button stands over the fade above the
|
||||||
// navigation bar, which the screen paints beneath it, the tasks
|
// navigation bar, which the screen paints beneath it, the tasks
|
||||||
// running on beneath both (the developer's review, 9 Nov 2026);
|
// running on beneath both (the developer's review, 9 Nov 2026);
|
||||||
|
|
|
||||||
|
|
@ -1025,6 +1025,84 @@ void main() {
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
group('the list scrolls to a task as quick add adds it, as '
|
||||||
|
"Todoist's (the developer's review, 10 Nov 2026)", () {
|
||||||
|
/// The Inbox holding [count] tasks.
|
||||||
|
Future<void> inboxOf(WidgetTester tester, int count) async {
|
||||||
|
await start(tester, usedBefore: true);
|
||||||
|
for (var index = 0; index < count; index++) {
|
||||||
|
await seed(
|
||||||
|
tester,
|
||||||
|
TaskContent(
|
||||||
|
title: 'Task $index',
|
||||||
|
projectId: inbox(),
|
||||||
|
position: index.toDouble(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await goTab(tester, 'Inbox');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds [title] through the quick add bar open.
|
||||||
|
Future<void> add(WidgetTester tester, String title) async {
|
||||||
|
await tester.enterText(find.byKey(quickAddTitleKey), title);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.tap(find.byTooltip('Add'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
double barTop(WidgetTester tester) =>
|
||||||
|
tester.getTopLeft(find.byType(QuickAddBar)).dy;
|
||||||
|
|
||||||
|
testWidgets('on a phone, each one just above the bar, however long '
|
||||||
|
'the list', (tester) async {
|
||||||
|
await inboxOf(tester, 25);
|
||||||
|
await tester.tap(find.byTooltip('Add task'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
for (final title in ['Newest', 'Newer still']) {
|
||||||
|
await add(tester, title);
|
||||||
|
final row = tester.getRect(find.text(title));
|
||||||
|
expect(row.bottom, lessThanOrEqualTo(barTop(tester)));
|
||||||
|
expect(row.bottom, greaterThan(barTop(tester) - 120));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('a list that has room for it stays where it is', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await inboxOf(tester, 2);
|
||||||
|
final top = tester.getTopLeft(find.text('Task 0'));
|
||||||
|
await tester.tap(find.byTooltip('Add task'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await add(tester, 'Newest');
|
||||||
|
expect(tester.getTopLeft(find.text('Task 0')), top);
|
||||||
|
expect(find.text('Newest'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('on the desktop, beside the dialog, into view', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
debugDefaultTargetPlatformOverride = TargetPlatform.linux;
|
||||||
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
||||||
|
await inboxOf(tester, 25);
|
||||||
|
await tester.tap(find.byTooltip('Add task'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await add(tester, 'Newest');
|
||||||
|
final row = tester.getRect(find.text('Newest'));
|
||||||
|
expect(
|
||||||
|
row.bottom,
|
||||||
|
lessThanOrEqualTo(
|
||||||
|
tester.view.physicalSize.height / tester.view.devicePixelRatio,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(row.top, greaterThan(0));
|
||||||
|
debugDefaultTargetPlatformOverride = null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('a task added in the Inbox shows there, sealed on its way', (
|
testWidgets('a task added in the Inbox shows there, sealed on its way', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
|
|
@ -4857,6 +4935,11 @@ void main() {
|
||||||
await tester.tapAt(const Offset(20, 20));
|
await tester.tapAt(const Offset(20, 20));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(find.text('Sub-tasks 0/1'), findsOneWidget);
|
expect(find.text('Sub-tasks 0/1'), findsOneWidget);
|
||||||
|
// The sub-task added was brought into view above quick add's bar.
|
||||||
|
await tester.ensureVisible(
|
||||||
|
find.byTooltip('Task options', skipOffstage: false),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.tap(find.byTooltip('Task options'));
|
await tester.tap(find.byTooltip('Task options'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue