Add tick boxes to task rows
Tasks now carry a tick box rather than being read-only rows. Ticking strikes the title through and leaves the item in place, so a day still reads as a record of what was asked rather than only what is left. New in designs/Checkbox.dart: - TickBox the square box itself, animating between empty and filled - TickRow box + struck-through title + caller-supplied detail and trailing - addTaskAffordance the centred "Add new task" control beneath a day's list Applied to the home plan, the overdue queue and the goal task list. Each screen holds a set of optimistically ticked ids so the tick lands immediately and rolls back if the call fails; the set is reconciled whenever fresh data arrives. A tick completes the item outright, whatever its proof requirement. The proof flow is still reachable from the Complete button in the overdue queue, so tasks that need an artefact have a route that asks for one — but the tick itself no longer enforces it. Late completes stay visually distinct: the row keeps a Late pill and the warning accent, and the user is told the window had already closed. flutter analyze lib/ reports no errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mfu2gQLSFN21YRBcU2NrTt
This commit is contained in:
@@ -6,4 +6,10 @@ abstract class ConnectGoalDetail {
|
||||
|
||||
/// The task is ready to run — hand off to the full-screen runner.
|
||||
void onTaskReady(Commitment task);
|
||||
|
||||
/// A tick landed. [late] is true when the window had already closed.
|
||||
void onTaskTicked(Commitment task, bool late);
|
||||
|
||||
/// The tick failed; the row goes back to unchecked.
|
||||
void onTickFailed(Commitment task);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ import 'package:stacked/stacked.dart';
|
||||
|
||||
import '../../about/external/data/Commitment.dart';
|
||||
import '../../about/external/data/Goal.dart';
|
||||
import '../../about/internal/application/CommitmentClass.dart';
|
||||
import '../../about/internal/application/CommitmentStatus.dart';
|
||||
import '../../about/internal/application/NotificationType.dart';
|
||||
import '../../about/internal/application/ProofType.dart';
|
||||
import '../../about/internal/application/TextType.dart';
|
||||
import '../../configs/Navigator.dart';
|
||||
import '../../designs/Checkbox.dart';
|
||||
import '../../designs/Component.dart';
|
||||
import '../../designs/Responsive.dart';
|
||||
import '../../designs/Shell.dart';
|
||||
@@ -33,6 +34,9 @@ class GoalDetailState extends State<GoalDetail>
|
||||
|
||||
bool _changed = false;
|
||||
|
||||
/// Ticked locally, awaiting the server.
|
||||
final Set<String> _ticked = <String>{};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ViewModelBuilder<ViewGoalDetail>.reactive(
|
||||
@@ -70,6 +74,20 @@ class GoalDetailState extends State<GoalDetail>
|
||||
_model?.startTask(task);
|
||||
}
|
||||
|
||||
/// Ticking completes the task without opening the runner — the runner is for
|
||||
/// work you actually want timed.
|
||||
void _onTick(Commitment task) {
|
||||
if (task.id == null || _ticked.contains(task.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_ticked.add(task.id!);
|
||||
});
|
||||
|
||||
_model?.tick(task);
|
||||
}
|
||||
|
||||
void _onAddTask() async {
|
||||
final result = await GroundedNavigation()
|
||||
.navigateToPageWithData(const NewCommitment(), context);
|
||||
@@ -180,51 +198,29 @@ class GoalDetailState extends State<GoalDetail>
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 3,
|
||||
height: 38,
|
||||
margin: const EdgeInsets.only(right: 14, top: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: accent,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
TickRow(
|
||||
checked: _ticked.contains(task.id),
|
||||
title: task.title,
|
||||
accent: accent,
|
||||
onTick: () => _onTick(task),
|
||||
detail: Row(
|
||||
children: [
|
||||
text(formatWindow(task), 11, TextType.Regular,
|
||||
color: colorGrey2),
|
||||
const SizedBox(width: 9),
|
||||
Container(
|
||||
width: 3,
|
||||
height: 3,
|
||||
decoration: BoxDecoration(
|
||||
color: colorGrey, shape: BoxShape.circle),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
text(task.title, 16, TextType.Medium,
|
||||
color: colorPrimaryDark,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis),
|
||||
const SizedBox(height: 7),
|
||||
Row(
|
||||
children: [
|
||||
text(formatWindow(task), 11, TextType.Regular,
|
||||
color: colorGrey2),
|
||||
const SizedBox(width: 9),
|
||||
Container(
|
||||
width: 3,
|
||||
height: 3,
|
||||
decoration: BoxDecoration(
|
||||
color: colorGrey, shape: BoxShape.circle),
|
||||
),
|
||||
const SizedBox(width: 9),
|
||||
text(formatMinutes(task.estMinutes), 11,
|
||||
TextType.Regular, color: colorGrey2),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
pill(proofLabel(task.proofType), colorGrey2, colorMuted,
|
||||
textSize: 9),
|
||||
],
|
||||
const SizedBox(width: 9),
|
||||
text(formatMinutes(task.estMinutes), 11, TextType.Regular,
|
||||
color: colorGrey2),
|
||||
],
|
||||
),
|
||||
trailing: pill(proofLabel(task.proofType), colorGrey2, colorMuted,
|
||||
textSize: 9),
|
||||
),
|
||||
if (late) ...[
|
||||
const SizedBox(height: 12),
|
||||
@@ -258,26 +254,15 @@ class GoalDetailState extends State<GoalDetail>
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
child: card(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 13),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
late
|
||||
? CupertinoIcons.checkmark_circle
|
||||
: CupertinoIcons.checkmark_circle_fill,
|
||||
size: 17,
|
||||
color: late ? colorStandingWarned : colorPositive,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: text(task.title, 13, TextType.Regular,
|
||||
color: colorGrey2,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
if (late)
|
||||
pill("Late", colorStandingWarned, colorStandingWarnedBg,
|
||||
textSize: 9),
|
||||
],
|
||||
child: TickRow(
|
||||
checked: true,
|
||||
locked: true,
|
||||
title: task.title,
|
||||
accent: late ? colorStandingWarned : colorPositive,
|
||||
trailing: late
|
||||
? pill("Late", colorStandingWarned, colorStandingWarnedBg,
|
||||
textSize: 9)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -288,11 +273,38 @@ class GoalDetailState extends State<GoalDetail>
|
||||
@override
|
||||
void onGoalLoaded(Goal goal, List<Commitment> tasks) {
|
||||
setState(() {
|
||||
_ticked.removeWhere((id) => tasks.every((task) => task.id != id));
|
||||
_goal = goal;
|
||||
_tasks = tasks;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onTaskTicked(Commitment task, bool late) {
|
||||
_changed = true;
|
||||
_model?.loadTasks(_goal);
|
||||
|
||||
if (!late) {
|
||||
return;
|
||||
}
|
||||
|
||||
_model?.showApplicationNotification(
|
||||
NotificationType.warning,
|
||||
"Late complete",
|
||||
"The window had already closed. This is recorded as a late complete — it reduces the debt but does not clear it.",
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTickFailed(Commitment task) {
|
||||
setState(() {
|
||||
_ticked.remove(task.id);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onTaskReady(Commitment task) async {
|
||||
// The runner takes over the whole screen — a task you are running is the
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'package:stacked/stacked.dart';
|
||||
import '../../about/external/data/Goal.dart';
|
||||
import '../../about/external/initial/GoalRequest.dart';
|
||||
import '../../about/internal/application/CommitmentClass.dart';
|
||||
import '../../about/internal/application/NavigatorType.dart';
|
||||
import '../../about/internal/application/TextType.dart';
|
||||
import '../../configs/Navigator.dart';
|
||||
import '../../designs/Component.dart';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import '../../about/external/data/Commitment.dart';
|
||||
import '../../about/external/data/Goal.dart';
|
||||
import '../../about/external/initial/CompletionRequest.dart';
|
||||
import '../../about/external/initial/IdRequest.dart';
|
||||
import '../../utils/ObjectConvertors.dart';
|
||||
import '../parent/ParentViewModel.dart';
|
||||
@@ -27,6 +28,23 @@ class ViewGoalDetail extends ParentViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Completes a task straight from the goal list.
|
||||
void tick(Commitment task) async {
|
||||
if (!await hasNetwork(() => tick(task))) return;
|
||||
|
||||
try {
|
||||
await getDataManager().completeCommitmentEntry(CompletionRequest(
|
||||
commitmentId: task.id ?? "",
|
||||
proofType: task.proofType.name,
|
||||
));
|
||||
|
||||
connection.onTaskTicked(task, task.wouldBeLate);
|
||||
} catch (e) {
|
||||
connection.onTickFailed(task);
|
||||
handleError(e, () => tick(task), () => dismissError(), "Retry");
|
||||
}
|
||||
}
|
||||
|
||||
/// Stashes the task as the active one before the runner opens, so the
|
||||
/// ongoing notification and any relaunch land back on the right thing.
|
||||
void startTask(Commitment task) async {
|
||||
|
||||
@@ -21,4 +21,11 @@ abstract class ConnectHome {
|
||||
|
||||
/// Creating a commitment is refused at this standing.
|
||||
void onCreationBlocked(String reason);
|
||||
|
||||
/// A tick landed. [late] is true when the window had already closed, so the
|
||||
/// row can show it was recorded as a late complete rather than a clean one.
|
||||
void onTicked(Commitment item, bool late);
|
||||
|
||||
/// The tick failed and the row has to go back to unchecked.
|
||||
void onTickFailed(Commitment item);
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ import '../../about/internal/application/NavigatorType.dart';
|
||||
import '../../about/internal/application/NotificationType.dart';
|
||||
import '../../about/internal/application/Standing.dart';
|
||||
import '../../about/internal/application/TextType.dart';
|
||||
import '../../about/internal/application/ToneLevel.dart';
|
||||
import '../../about/internal/application/UserDetails.dart';
|
||||
import '../../configs/Navigator.dart';
|
||||
import '../../designs/Checkbox.dart';
|
||||
import '../../designs/Component.dart';
|
||||
import '../../designs/Responsive.dart';
|
||||
import '../../designs/Shell.dart';
|
||||
@@ -20,7 +20,6 @@ import '../../designs/buttons/Buttons.dart';
|
||||
import '../../designs/text/Text.dart';
|
||||
import '../../utils/Colors.dart';
|
||||
import '../../utils/CommonUtils.dart';
|
||||
import '../../utils/DebtEngine.dart';
|
||||
import '../../utils/StandingEngine.dart';
|
||||
import '../../utils/Thresholds.dart';
|
||||
import '../../utils/ToneEngine.dart';
|
||||
@@ -52,6 +51,11 @@ class HomeState extends State<Home> implements ConnectHome {
|
||||
|
||||
bool _distressed = false;
|
||||
|
||||
/// Ids ticked locally but not yet confirmed by the server. The row reads as
|
||||
/// done immediately so the tick feels instant, and rolls back if the call
|
||||
/// fails.
|
||||
final Set<String> _ticked = <String>{};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ViewModelBuilder<ViewHome>.reactive(
|
||||
@@ -101,6 +105,18 @@ class HomeState extends State<Home> implements ConnectHome {
|
||||
}
|
||||
}
|
||||
|
||||
void _onTick(Commitment item) {
|
||||
if (item.id == null || _ticked.contains(item.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_ticked.add(item.id!);
|
||||
});
|
||||
|
||||
_model?.tick(item);
|
||||
}
|
||||
|
||||
void _onOpenReportCard() {
|
||||
GroundedNavigation().navigateToPage(
|
||||
NavigatorType.justOpen, const ReportCardScreen(), context);
|
||||
@@ -272,9 +288,14 @@ class HomeState extends State<Home> implements ConnectHome {
|
||||
"Nothing committed today",
|
||||
"An empty plan is a decision too. Add something you actually intend to do.",
|
||||
)
|
||||
else
|
||||
else ...[
|
||||
..._plan.map(_commitmentRow),
|
||||
const SizedBox(height: 28),
|
||||
addTaskAffordance(
|
||||
_onAddCommitment,
|
||||
enabled: StandingEngine.permitsNewCommitment(_standing),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
_quickLinks(),
|
||||
const SizedBox(height: 24),
|
||||
roundedCornerButton(
|
||||
@@ -499,83 +520,68 @@ class HomeState extends State<Home> implements ConnectHome {
|
||||
);
|
||||
}
|
||||
|
||||
/// A task line with its tick box. Completed items keep their place, struck
|
||||
/// through — the day stays a record of what was asked, not just what is left.
|
||||
Widget _commitmentRow(Commitment item) {
|
||||
final bool late = item.windowClosed &&
|
||||
item.status != CommitmentStatus.Completed &&
|
||||
item.status != CommitmentStatus.LateCompleted;
|
||||
final bool settled = item.status == CommitmentStatus.Completed ||
|
||||
item.status == CommitmentStatus.LateCompleted;
|
||||
|
||||
final bool ticked = settled || _ticked.contains(item.id);
|
||||
|
||||
final bool late = item.windowClosed && !settled;
|
||||
|
||||
final Color accent = classColor(item.commitmentClass);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
margin: const EdgeInsets.only(bottom: 6),
|
||||
child: card(
|
||||
padding: const EdgeInsets.fromLTRB(14, 14, 14, 14),
|
||||
onTap: _onOpenOverdue,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 3,
|
||||
height: 42,
|
||||
margin: const EdgeInsets.only(right: 14, top: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: accent,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
padding: const EdgeInsets.fromLTRB(12, 8, 14, 8),
|
||||
child: TickRow(
|
||||
checked: ticked,
|
||||
title: item.title,
|
||||
accent: item.status == CommitmentStatus.LateCompleted
|
||||
? colorStandingWarned
|
||||
: accent,
|
||||
onTick: settled ? null : () => _onTick(item),
|
||||
onTap: _onOpenOverdue,
|
||||
detail: Row(
|
||||
children: [
|
||||
text(formatWindow(item), 11, TextType.Regular,
|
||||
color: colorGrey2),
|
||||
const SizedBox(width: 10),
|
||||
Container(
|
||||
width: 3,
|
||||
height: 3,
|
||||
decoration:
|
||||
BoxDecoration(color: colorGrey, shape: BoxShape.circle),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
text(item.title, 15, TextType.Medium,
|
||||
color: colorPrimaryDark,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
text(formatWindow(item), 11, TextType.Regular,
|
||||
color: colorGrey2),
|
||||
const SizedBox(width: 10),
|
||||
Container(width: 3, height: 3, decoration: BoxDecoration(
|
||||
color: colorGrey, shape: BoxShape.circle)),
|
||||
const SizedBox(width: 10),
|
||||
text(formatMinutes(item.estMinutes), 11,
|
||||
TextType.Regular, color: colorGrey2),
|
||||
],
|
||||
),
|
||||
if (late) ...[
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
pill(
|
||||
overdueLabel(item),
|
||||
colorStandingGrounded,
|
||||
colorStandingGroundedBg,
|
||||
textSize: 9,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
pill(
|
||||
"−${formatDebt(DebtEngine.commitmentDebt(item))}",
|
||||
colorGrey2,
|
||||
colorMuted,
|
||||
textSize: 9,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
pill(
|
||||
classLabel(item.commitmentClass),
|
||||
accent,
|
||||
classBackground(item.commitmentClass),
|
||||
textSize: 9,
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 10),
|
||||
text(formatMinutes(item.estMinutes), 11, TextType.Regular,
|
||||
color: colorGrey2),
|
||||
if (late) ...[
|
||||
const SizedBox(width: 10),
|
||||
pill(
|
||||
overdueLabel(item),
|
||||
colorStandingGrounded,
|
||||
colorStandingGroundedBg,
|
||||
textSize: 9,
|
||||
),
|
||||
],
|
||||
if (item.status == CommitmentStatus.LateCompleted) ...[
|
||||
const SizedBox(width: 10),
|
||||
pill("Late", colorStandingWarned, colorStandingWarnedBg,
|
||||
textSize: 9),
|
||||
],
|
||||
],
|
||||
),
|
||||
trailing: ticked
|
||||
? null
|
||||
: pill(
|
||||
classLabel(item.commitmentClass),
|
||||
accent,
|
||||
classBackground(item.commitmentClass),
|
||||
textSize: 9,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -650,6 +656,9 @@ class HomeState extends State<Home> implements ConnectHome {
|
||||
void onPlanLoaded(List<Commitment> plan) {
|
||||
setState(() {
|
||||
_plan = plan;
|
||||
// The server is now authoritative; anything it reports as settled no
|
||||
// longer needs the local optimistic flag.
|
||||
_ticked.removeWhere((id) => plan.every((item) => item.id != id));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -682,6 +691,31 @@ class HomeState extends State<Home> implements ConnectHome {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onTicked(Commitment item, bool late) {
|
||||
if (!late) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A clean tick needs no comment. A late one does — otherwise the user
|
||||
// learns that the window never mattered.
|
||||
_model?.showApplicationNotification(
|
||||
NotificationType.warning,
|
||||
"Late complete",
|
||||
"The window had already closed, so this is recorded as a late complete. It reduces the debt but does not clear it.",
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTickFailed(Commitment item) {
|
||||
setState(() {
|
||||
_ticked.remove(item.id);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onCreationBlocked(String reason) {
|
||||
_model?.showApplicationNotification(
|
||||
|
||||
@@ -6,6 +6,7 @@ import '../../about/external/data/pages/request/PageAndSort.dart';
|
||||
import '../../about/external/data/pages/request/Pageable.dart';
|
||||
import '../../about/external/data/pages/request/Sort.dart';
|
||||
import '../../about/external/data/pages/response/CommitmentPage.dart';
|
||||
import '../../about/external/initial/CompletionRequest.dart';
|
||||
import '../../about/external/initial/ReportCardRequest.dart';
|
||||
import '../../about/internal/application/CommitmentClass.dart';
|
||||
import '../../about/internal/application/Standing.dart';
|
||||
@@ -141,6 +142,30 @@ class ViewHome extends ParentViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Ticking a row completes it. The window decides whether that lands as a
|
||||
/// clean complete or a late one — the client never claims which.
|
||||
void tick(Commitment item) async {
|
||||
if (!await hasNetwork(() => tick(item))) return;
|
||||
|
||||
final bool late = item.wouldBeLate;
|
||||
|
||||
try {
|
||||
await getDataManager().completeCommitmentEntry(CompletionRequest(
|
||||
commitmentId: item.id ?? "",
|
||||
proofType: item.proofType.name,
|
||||
));
|
||||
|
||||
connection.onTicked(item, late);
|
||||
|
||||
// Clearing an item moves the debt, so the standing has to be re-derived
|
||||
// rather than left showing the number from before the tick.
|
||||
loadPlan();
|
||||
} catch (e) {
|
||||
connection.onTickFailed(item);
|
||||
handleError(e, () => tick(item), () => dismissError(), "Retry");
|
||||
}
|
||||
}
|
||||
|
||||
/// The gate on creating anything new. Grounded blocks everything; Warned
|
||||
/// blocks electives only.
|
||||
void requestNewCommitment(Standing standing, CommitmentClass intended) async {
|
||||
|
||||
@@ -9,6 +9,7 @@ import '../../about/internal/application/NotificationType.dart';
|
||||
import '../../about/internal/application/ProofType.dart';
|
||||
import '../../about/internal/application/Standing.dart';
|
||||
import '../../about/internal/application/TextType.dart';
|
||||
import '../../designs/Checkbox.dart';
|
||||
import '../../designs/Component.dart';
|
||||
import '../../designs/Responsive.dart';
|
||||
import '../../designs/Shell.dart';
|
||||
@@ -36,6 +37,9 @@ class OverdueQueueState extends State<OverdueQueue>
|
||||
|
||||
bool _changed = false;
|
||||
|
||||
/// Ticked locally, awaiting the server. Rolls back on failure.
|
||||
final Set<String> _ticked = <String>{};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ViewModelBuilder<ViewOverdueQueue>.reactive(
|
||||
@@ -66,6 +70,27 @@ class OverdueQueueState extends State<OverdueQueue>
|
||||
Navigator.pop(context, _changed);
|
||||
}
|
||||
|
||||
/// The tick completes the item outright. The Complete button still runs the
|
||||
/// proof flow, so a task that needs an artefact has a route that asks for
|
||||
/// one — the tick is the quick path, not the only one.
|
||||
void _onTick(Commitment item) {
|
||||
if (item.id == null || _ticked.contains(item.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_ticked.add(item.id!);
|
||||
});
|
||||
|
||||
_model?.complete(
|
||||
item,
|
||||
CompletionRequest(
|
||||
commitmentId: item.id ?? "",
|
||||
proofType: item.proofType.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onComplete(Commitment item) {
|
||||
// Honour proof settles immediately; everything else has to produce its
|
||||
// artefact before the completion is accepted.
|
||||
@@ -526,31 +551,19 @@ class OverdueQueueState extends State<OverdueQueue>
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
text(item.title, 17, TextType.Medium,
|
||||
color: colorPrimaryDark, maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis),
|
||||
const SizedBox(height: 8),
|
||||
text(formatWindow(item), 11, TextType.Regular,
|
||||
color: colorGrey2),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
pill(
|
||||
classLabel(item.commitmentClass),
|
||||
accent,
|
||||
classBackground(item.commitmentClass),
|
||||
textSize: 9,
|
||||
),
|
||||
],
|
||||
TickRow(
|
||||
checked: _ticked.contains(item.id),
|
||||
title: item.title,
|
||||
accent: accent,
|
||||
onTick: () => _onTick(item),
|
||||
detail: text(formatWindow(item), 11, TextType.Regular,
|
||||
color: colorGrey2),
|
||||
trailing: pill(
|
||||
classLabel(item.commitmentClass),
|
||||
accent,
|
||||
classBackground(item.commitmentClass),
|
||||
textSize: 9,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
@@ -625,6 +638,7 @@ class OverdueQueueState extends State<OverdueQueue>
|
||||
@override
|
||||
void onQueueLoaded(List<Commitment> queue, Standing standing, double debt) {
|
||||
setState(() {
|
||||
_ticked.removeWhere((id) => queue.every((item) => item.id != id));
|
||||
_queue = queue;
|
||||
_standing = standing;
|
||||
_debt = debt;
|
||||
|
||||
Reference in New Issue
Block a user