Wire Backlog nav, Break up, Settings dialog + merge Backlog Board feature #20
13 changed files with 191 additions and 13 deletions
|
|
@ -103,7 +103,7 @@ boundary.
|
||||||
|
|
||||||
- Compact/Normal toggle.
|
- Compact/Normal toggle.
|
||||||
- Settings button.
|
- Settings button.
|
||||||
- Timeline card action icons.
|
- Timeline card Schedule and Protect time icons.
|
||||||
|
|
||||||
## Persistence Proof
|
## Persistence Proof
|
||||||
|
|
||||||
|
|
@ -170,6 +170,4 @@ Run these from `apps/focus_flow_flutter/`.
|
||||||
has no compact-mode concept yet; see Forgejo issue #11).
|
has no compact-mode concept yet; see Forgejo issue #11).
|
||||||
- Add a real Settings screen bound to existing `OwnerSettings`/
|
- Add a real Settings screen bound to existing `OwnerSettings`/
|
||||||
`BacklogStalenessSettings` persistence.
|
`BacklogStalenessSettings` persistence.
|
||||||
- Wire timeline card quick-action icons to the same commands the task modal
|
|
||||||
already uses.
|
|
||||||
- Add Shield/Recovery only in a later scope.
|
- Add Shield/Recovery only in a later scope.
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,7 @@ class _TodayScreenScaffoldState extends State<_TodayScreenScaffold> {
|
||||||
onPushToNext: widget.onPushToNext,
|
onPushToNext: widget.onPushToNext,
|
||||||
onPushToTomorrow: widget.onPushToTomorrow,
|
onPushToTomorrow: widget.onPushToTomorrow,
|
||||||
onPushToBacklog: widget.onPushToBacklog,
|
onPushToBacklog: widget.onPushToBacklog,
|
||||||
|
onBreakUpCard: widget.onBreakUpCard,
|
||||||
now: () =>
|
now: () =>
|
||||||
DateTime.now().toUtc().add(widget.data.timeZoneOffset),
|
DateTime.now().toUtc().add(widget.data.timeZoneOffset),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
// SPDX-FileCopyrightText: 2026 FocusFlow contributors
|
// SPDX-FileCopyrightText: 2026 FocusFlow contributors
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
part of '../task_selection_modal.dart';
|
/// Renders the shared Break Up dialog for the FocusFlow Flutter UI.
|
||||||
|
library;
|
||||||
|
|
||||||
/// Editable child-task row state owned by [_BreakUpDialog].
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:scheduler_core/scheduler_core.dart';
|
||||||
|
|
||||||
|
import '../../theme/focus_flow_tokens.dart';
|
||||||
|
|
||||||
|
/// Editable child-task row state owned by [BreakUpDialog].
|
||||||
class _BreakUpRow {
|
class _BreakUpRow {
|
||||||
/// Creates an empty break-up row with fresh text controllers.
|
/// Creates an empty break-up row with fresh text controllers.
|
||||||
_BreakUpRow()
|
_BreakUpRow()
|
||||||
|
|
@ -31,19 +37,22 @@ class _BreakUpRow {
|
||||||
|
|
||||||
/// Dialog that collects one or more child task drafts to break up a parent
|
/// Dialog that collects one or more child task drafts to break up a parent
|
||||||
/// task, per MVP-AC-11 (row-level priority, reward, and duration).
|
/// task, per MVP-AC-11 (row-level priority, reward, and duration).
|
||||||
class _BreakUpDialog extends StatefulWidget {
|
///
|
||||||
|
/// Shared by the task selection modal and timeline card quick actions so
|
||||||
|
/// both surfaces reuse the same break-up flow instead of duplicating it.
|
||||||
|
class BreakUpDialog extends StatefulWidget {
|
||||||
/// Creates a break-up dialog.
|
/// Creates a break-up dialog.
|
||||||
const _BreakUpDialog();
|
const BreakUpDialog({super.key});
|
||||||
|
|
||||||
/// Creates the mutable state object used by this stateful widget.
|
/// Creates the mutable state object used by this stateful widget.
|
||||||
/// Flutter calls this once for each mounted widget instance before lifecycle callbacks and builds begin.
|
/// Flutter calls this once for each mounted widget instance before lifecycle callbacks and builds begin.
|
||||||
@override
|
@override
|
||||||
State<_BreakUpDialog> createState() => _BreakUpDialogState();
|
State<BreakUpDialog> createState() => _BreakUpDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Private implementation type for `_BreakUpDialogState` in this library.
|
/// Private implementation type for `_BreakUpDialogState` in this library.
|
||||||
/// It owns the growable list of child-task rows shown by the dialog.
|
/// It owns the growable list of child-task rows shown by the dialog.
|
||||||
class _BreakUpDialogState extends State<_BreakUpDialog> {
|
class _BreakUpDialogState extends State<BreakUpDialog> {
|
||||||
/// Editable rows currently shown in the dialog.
|
/// Editable rows currently shown in the dialog.
|
||||||
final List<_BreakUpRow> _rows = [_BreakUpRow()];
|
final List<_BreakUpRow> _rows = [_BreakUpRow()];
|
||||||
|
|
||||||
|
|
@ -11,12 +11,12 @@ import 'package:scheduler_core/scheduler_core.dart';
|
||||||
|
|
||||||
import '../models/today_screen_models.dart';
|
import '../models/today_screen_models.dart';
|
||||||
import '../theme/focus_flow_tokens.dart';
|
import '../theme/focus_flow_tokens.dart';
|
||||||
|
import 'dialogs/break_up_dialog.dart';
|
||||||
import 'status_circle_button.dart';
|
import 'status_circle_button.dart';
|
||||||
import 'timeline/difficulty_bars.dart';
|
import 'timeline/difficulty_bars.dart';
|
||||||
import 'timeline/reward_icon.dart';
|
import 'timeline/reward_icon.dart';
|
||||||
|
|
||||||
part 'task_selection/action_button.dart';
|
part 'task_selection/action_button.dart';
|
||||||
part 'task_selection/break_up_dialog.dart';
|
|
||||||
part 'task_selection/header_metadata.dart';
|
part 'task_selection/header_metadata.dart';
|
||||||
part 'task_selection/info_tile.dart';
|
part 'task_selection/info_tile.dart';
|
||||||
part 'task_selection/modal_accent.dart';
|
part 'task_selection/modal_accent.dart';
|
||||||
|
|
@ -70,7 +70,7 @@ class TaskSelectionModal extends StatelessWidget {
|
||||||
Future<void> _handleBreakUp(BuildContext context) async {
|
Future<void> _handleBreakUp(BuildContext context) async {
|
||||||
final drafts = await showDialog<List<ApplicationChildTaskDraft>>(
|
final drafts = await showDialog<List<ApplicationChildTaskDraft>>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => const _BreakUpDialog(),
|
builder: (_) => const BreakUpDialog(),
|
||||||
);
|
);
|
||||||
if (drafts == null || drafts.isEmpty) {
|
if (drafts == null || drafts.isEmpty) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ class _CardMetrics {
|
||||||
|
|
||||||
/// Returns the derived `actionsWidth` value for this object.
|
/// Returns the derived `actionsWidth` value for this object.
|
||||||
/// Keeping this as an accessor gives callers a stable read surface while the owning type controls how the value is calculated.
|
/// Keeping this as an accessor gives callers a stable read surface while the owning type controls how the value is calculated.
|
||||||
double get actionsWidth => actionButtonSize * 3 + actionGap;
|
double get actionsWidth => actionButtonSize * 4 + actionGap;
|
||||||
|
|
||||||
/// Returns the derived `pushButtonHeight` value for this object.
|
/// Returns the derived `pushButtonHeight` value for this object.
|
||||||
/// Keeping this as an accessor gives callers a stable read surface while the owning type controls how the value is calculated.
|
/// Keeping this as an accessor gives callers a stable read surface while the owning type controls how the value is calculated.
|
||||||
|
|
|
||||||
|
|
@ -124,3 +124,55 @@ class _QuickPushIcon extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Private implementation type for `_QuickBreakUpIcon` in this library.
|
||||||
|
/// It exposes the same break-up dialog flow as the task selection modal from
|
||||||
|
/// the timeline card's compact hover actions.
|
||||||
|
class _QuickBreakUpIcon extends StatelessWidget {
|
||||||
|
/// Creates a `_QuickBreakUpIcon` instance with the values required by its invariants.
|
||||||
|
/// Callers should pass already-normalized scheduler data because validation and defaulting belong at the model or service boundary, not in arbitrary UI code.
|
||||||
|
const _QuickBreakUpIcon({
|
||||||
|
required this.card,
|
||||||
|
required this.color,
|
||||||
|
required this.metrics,
|
||||||
|
this.onBreakUp,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Stores the `card` value for this object.
|
||||||
|
/// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
|
||||||
|
final TimelineCardModel card;
|
||||||
|
|
||||||
|
/// Stores the `color` value for this object.
|
||||||
|
/// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
/// Stores the `metrics` value for this object.
|
||||||
|
/// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
|
||||||
|
final _CardMetrics metrics;
|
||||||
|
|
||||||
|
/// Callback invoked when Break up is selected.
|
||||||
|
final Future<void> Function()? onBreakUp;
|
||||||
|
|
||||||
|
/// Builds the widget subtree for this component.
|
||||||
|
/// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data.
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final enabled = onBreakUp != null;
|
||||||
|
return Tooltip(
|
||||||
|
message: 'Break up',
|
||||||
|
child: Semantics(
|
||||||
|
button: true,
|
||||||
|
enabled: enabled,
|
||||||
|
label: 'Break up ${card.title}',
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: enabled ? () => unawaited(onBreakUp!()) : null,
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
child: SizedBox.square(
|
||||||
|
dimension: metrics.actionButtonSize,
|
||||||
|
child: Icon(Icons.apps, color: color, size: metrics.actionIconSize),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ class _QuickActions extends StatelessWidget {
|
||||||
this.onPushToNext,
|
this.onPushToNext,
|
||||||
this.onPushToTomorrow,
|
this.onPushToTomorrow,
|
||||||
this.onPushToBacklog,
|
this.onPushToBacklog,
|
||||||
|
this.onBreakUp,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Stores the `card` value for this object.
|
/// Stores the `card` value for this object.
|
||||||
|
|
@ -53,6 +54,9 @@ class _QuickActions extends StatelessWidget {
|
||||||
/// Callback invoked when Push to backlog is selected.
|
/// Callback invoked when Push to backlog is selected.
|
||||||
final Future<void> Function()? onPushToBacklog;
|
final Future<void> Function()? onPushToBacklog;
|
||||||
|
|
||||||
|
/// Callback invoked when Break up is selected.
|
||||||
|
final Future<void> Function()? onBreakUp;
|
||||||
|
|
||||||
/// Builds the widget subtree for this component.
|
/// Builds the widget subtree for this component.
|
||||||
/// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data.
|
/// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data.
|
||||||
@override
|
@override
|
||||||
|
|
@ -90,6 +94,20 @@ class _QuickActions extends StatelessWidget {
|
||||||
onPushToTomorrow: onPushToTomorrow,
|
onPushToTomorrow: onPushToTomorrow,
|
||||||
onPushToBacklog: onPushToBacklog,
|
onPushToBacklog: onPushToBacklog,
|
||||||
),
|
),
|
||||||
|
if (onBreakUp != null)
|
||||||
|
_QuickBreakUpIcon(
|
||||||
|
card: card,
|
||||||
|
color: color,
|
||||||
|
metrics: metrics,
|
||||||
|
onBreakUp: onBreakUp,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
_NoOpIcon(
|
||||||
|
icon: Icons.apps,
|
||||||
|
tooltip: 'Break up',
|
||||||
|
color: color,
|
||||||
|
metrics: metrics,
|
||||||
|
),
|
||||||
_NoOpIcon(
|
_NoOpIcon(
|
||||||
icon: Icons.calendar_today,
|
icon: Icons.calendar_today,
|
||||||
tooltip: 'Schedule',
|
tooltip: 'Schedule',
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,9 @@ class _TaskTimelineCardState extends State<TaskTimelineCard> {
|
||||||
onPushToBacklog: widget.onPushToBacklog == null
|
onPushToBacklog: widget.onPushToBacklog == null
|
||||||
? null
|
? null
|
||||||
: () => _runPush(widget.onPushToBacklog!),
|
: () => _runPush(widget.onPushToBacklog!),
|
||||||
|
onBreakUp: widget.onBreakUpCard == null
|
||||||
|
? null
|
||||||
|
: () => _handleBreakUp(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -111,6 +114,25 @@ class _TaskTimelineCardState extends State<TaskTimelineCard> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens the break-up dialog and forwards drafted child tasks to
|
||||||
|
/// [TaskTimelineCard.onBreakUpCard] when the user confirms with at least
|
||||||
|
/// one task.
|
||||||
|
Future<void> _handleBreakUp(BuildContext context) async {
|
||||||
|
final drafts = await showDialog<List<ApplicationChildTaskDraft>>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => const BreakUpDialog(),
|
||||||
|
);
|
||||||
|
if (drafts == null || drafts.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug(
|
||||||
|
() =>
|
||||||
|
'Card break-up requested. cardId=${widget.card.id} '
|
||||||
|
'childCount=${drafts.length}',
|
||||||
|
);
|
||||||
|
await widget.onBreakUpCard!(widget.card, drafts);
|
||||||
|
}
|
||||||
|
|
||||||
/// Runs the `_runPush` operation and completes after its asynchronous work finishes.
|
/// Runs the `_runPush` operation and completes after its asynchronous work finishes.
|
||||||
/// Callers should await the returned future so repository writes, UI refreshes, and failure handling happen in a predictable order.
|
/// Callers should await the returned future so repository writes, UI refreshes, and failure handling happen in a predictable order.
|
||||||
Future<void> _runPush(
|
Future<void> _runPush(
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ class _TrailingControls extends StatelessWidget {
|
||||||
this.onPushToNext,
|
this.onPushToNext,
|
||||||
this.onPushToTomorrow,
|
this.onPushToTomorrow,
|
||||||
this.onPushToBacklog,
|
this.onPushToBacklog,
|
||||||
|
this.onBreakUp,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Stores the `card` value for this object.
|
/// Stores the `card` value for this object.
|
||||||
|
|
@ -48,6 +49,9 @@ class _TrailingControls extends StatelessWidget {
|
||||||
/// Callback invoked when Push to backlog is selected.
|
/// Callback invoked when Push to backlog is selected.
|
||||||
final Future<void> Function()? onPushToBacklog;
|
final Future<void> Function()? onPushToBacklog;
|
||||||
|
|
||||||
|
/// Callback invoked when Break up is selected.
|
||||||
|
final Future<void> Function()? onBreakUp;
|
||||||
|
|
||||||
/// Builds the widget subtree for this component.
|
/// Builds the widget subtree for this component.
|
||||||
/// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data.
|
/// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data.
|
||||||
@override
|
@override
|
||||||
|
|
@ -66,6 +70,7 @@ class _TrailingControls extends StatelessWidget {
|
||||||
onPushToNext: onPushToNext,
|
onPushToNext: onPushToNext,
|
||||||
onPushToTomorrow: onPushToTomorrow,
|
onPushToTomorrow: onPushToTomorrow,
|
||||||
onPushToBacklog: onPushToBacklog,
|
onPushToBacklog: onPushToBacklog,
|
||||||
|
onBreakUp: onBreakUp,
|
||||||
),
|
),
|
||||||
if (card.showPastTaskPushButton) ...[
|
if (card.showPastTaskPushButton) ...[
|
||||||
_PastTaskPushButton(
|
_PastTaskPushButton(
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,12 @@ import 'dart:async';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:scheduler_core/scheduler_core.dart' show logger;
|
import 'package:scheduler_core/scheduler_core.dart'
|
||||||
|
show ApplicationChildTaskDraft, logger;
|
||||||
|
|
||||||
import '../../models/today_screen_models.dart';
|
import '../../models/today_screen_models.dart';
|
||||||
import '../../theme/focus_flow_tokens.dart';
|
import '../../theme/focus_flow_tokens.dart';
|
||||||
|
import '../dialogs/break_up_dialog.dart';
|
||||||
import '../status_circle_button.dart';
|
import '../status_circle_button.dart';
|
||||||
import 'difficulty_bars.dart';
|
import 'difficulty_bars.dart';
|
||||||
import 'reward_icon.dart';
|
import 'reward_icon.dart';
|
||||||
|
|
@ -37,6 +39,7 @@ class TaskTimelineCard extends StatefulWidget {
|
||||||
this.onPushToNext,
|
this.onPushToNext,
|
||||||
this.onPushToTomorrow,
|
this.onPushToTomorrow,
|
||||||
this.onPushToBacklog,
|
this.onPushToBacklog,
|
||||||
|
this.onBreakUpCard,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -58,6 +61,13 @@ class TaskTimelineCard extends StatefulWidget {
|
||||||
/// Callback invoked when the card requests Push to backlog.
|
/// Callback invoked when the card requests Push to backlog.
|
||||||
final Future<void> Function(TimelineCardModel card)? onPushToBacklog;
|
final Future<void> Function(TimelineCardModel card)? onPushToBacklog;
|
||||||
|
|
||||||
|
/// Callback invoked with drafted child tasks after Break up is confirmed.
|
||||||
|
final Future<void> Function(
|
||||||
|
TimelineCardModel card,
|
||||||
|
List<ApplicationChildTaskDraft> children,
|
||||||
|
)?
|
||||||
|
onBreakUpCard;
|
||||||
|
|
||||||
/// Creates the mutable state object used by this stateful widget.
|
/// Creates the mutable state object used by this stateful widget.
|
||||||
/// Flutter calls this once for each mounted widget instance before lifecycle callbacks and builds begin.
|
/// Flutter calls this once for each mounted widget instance before lifecycle callbacks and builds begin.
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ library;
|
||||||
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:scheduler_core/scheduler_core.dart'
|
||||||
|
show ApplicationChildTaskDraft;
|
||||||
|
|
||||||
import '../../models/today_screen_models.dart';
|
import '../../models/today_screen_models.dart';
|
||||||
import '../../theme/focus_flow_tokens.dart';
|
import '../../theme/focus_flow_tokens.dart';
|
||||||
|
|
@ -29,6 +31,7 @@ class TimelineView extends StatefulWidget {
|
||||||
this.onPushToNext,
|
this.onPushToNext,
|
||||||
this.onPushToTomorrow,
|
this.onPushToTomorrow,
|
||||||
this.onPushToBacklog,
|
this.onPushToBacklog,
|
||||||
|
this.onBreakUpCard,
|
||||||
this.now,
|
this.now,
|
||||||
this.scrollTargetCardId,
|
this.scrollTargetCardId,
|
||||||
this.scrollRequest = 0,
|
this.scrollRequest = 0,
|
||||||
|
|
@ -60,6 +63,14 @@ class TimelineView extends StatefulWidget {
|
||||||
/// Callback invoked when a task card requests Push to backlog.
|
/// Callback invoked when a task card requests Push to backlog.
|
||||||
final Future<void> Function(TimelineCardModel card)? onPushToBacklog;
|
final Future<void> Function(TimelineCardModel card)? onPushToBacklog;
|
||||||
|
|
||||||
|
/// Callback invoked with drafted child tasks after a task card's Break up
|
||||||
|
/// action is confirmed.
|
||||||
|
final Future<void> Function(
|
||||||
|
TimelineCardModel card,
|
||||||
|
List<ApplicationChildTaskDraft> children,
|
||||||
|
)?
|
||||||
|
onBreakUpCard;
|
||||||
|
|
||||||
/// Clock used to center the initial scroll position.
|
/// Clock used to center the initial scroll position.
|
||||||
final DateTime Function()? now;
|
final DateTime Function()? now;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@ class _TimelineViewState extends State<TimelineView> {
|
||||||
onPushToNext: widget.onPushToNext,
|
onPushToNext: widget.onPushToNext,
|
||||||
onPushToTomorrow: widget.onPushToTomorrow,
|
onPushToTomorrow: widget.onPushToTomorrow,
|
||||||
onPushToBacklog: widget.onPushToBacklog,
|
onPushToBacklog: widget.onPushToBacklog,
|
||||||
|
onBreakUpCard: widget.onBreakUpCard,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -883,6 +883,51 @@ void main() {
|
||||||
await gesture.removePointer();
|
await gesture.removePointer();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'hover Break up icon opens the same break-up dialog as the task modal',
|
||||||
|
(tester) async {
|
||||||
|
TimelineCardModel? brokenUpCard;
|
||||||
|
List<ApplicationChildTaskDraft>? brokenUpChildren;
|
||||||
|
|
||||||
|
await _pumpTimelineCard(
|
||||||
|
tester,
|
||||||
|
card: _timelineCard(id: 'quick-break-up', title: 'Current task'),
|
||||||
|
size: const Size(420, 88),
|
||||||
|
onBreakUpCard: (card, children) async {
|
||||||
|
brokenUpCard = card;
|
||||||
|
brokenUpChildren = children;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||||
|
await gesture.addPointer(
|
||||||
|
location: tester.getCenter(
|
||||||
|
find.byKey(const ValueKey('quick-break-up')),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.byIcon(Icons.apps));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('Break up task'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.enterText(
|
||||||
|
find.byKey(const ValueKey('break-up-row-0-title')),
|
||||||
|
'Child task',
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.byKey(const ValueKey('break-up-confirm')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(brokenUpCard?.id, 'quick-break-up');
|
||||||
|
expect(brokenUpChildren, hasLength(1));
|
||||||
|
expect(brokenUpChildren!.single.title, 'Child task');
|
||||||
|
|
||||||
|
await gesture.removePointer();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
testWidgets('Push menu dismisses on outside click without callback', (
|
testWidgets('Push menu dismisses on outside click without callback', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
|
|
@ -1236,6 +1281,11 @@ Future<void> _pumpTimelineCard(
|
||||||
Future<void> Function(TimelineCardModel card)? onPushToNext,
|
Future<void> Function(TimelineCardModel card)? onPushToNext,
|
||||||
Future<void> Function(TimelineCardModel card)? onPushToTomorrow,
|
Future<void> Function(TimelineCardModel card)? onPushToTomorrow,
|
||||||
Future<void> Function(TimelineCardModel card)? onPushToBacklog,
|
Future<void> Function(TimelineCardModel card)? onPushToBacklog,
|
||||||
|
Future<void> Function(
|
||||||
|
TimelineCardModel card,
|
||||||
|
List<ApplicationChildTaskDraft> children,
|
||||||
|
)?
|
||||||
|
onBreakUpCard,
|
||||||
}) async {
|
}) async {
|
||||||
await tester.binding.setSurfaceSize(
|
await tester.binding.setSurfaceSize(
|
||||||
Size(size.width + 120, size.height + 220),
|
Size(size.width + 120, size.height + 220),
|
||||||
|
|
@ -1255,6 +1305,7 @@ Future<void> _pumpTimelineCard(
|
||||||
onPushToNext: onPushToNext,
|
onPushToNext: onPushToNext,
|
||||||
onPushToTomorrow: onPushToTomorrow,
|
onPushToTomorrow: onPushToTomorrow,
|
||||||
onPushToBacklog: onPushToBacklog,
|
onPushToBacklog: onPushToBacklog,
|
||||||
|
onBreakUpCard: onBreakUpCard,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue