Files
tasq/lib/screens/programmer_tasks/dashboard/now_working_card.dart
T
redz1029 9a40ffec81 fix(programmer): move clock ticker watch to dashboard parent
NowWorkingCard was watching clockTickerProvider but received pre-computed
Duration values from ProgrammerDashboard, which never rebuilt on ticks.
Move ref.watch(clockTickerProvider) to ProgrammerDashboard.build() so
the parent rebuilds every 30 s and passes fresh durations to the card.
Remove the now-unused clockTickerProvider watch (and its import) from
NowWorkingCard.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-09-27 21:59:01 +08:00

153 lines
4.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../models/programmer_task.model.dart';
import '../../../providers/programmer_tasks_provider.dart';
import '../../../utils/programmer_task_ledger.dart';
import '../../../widgets/m3_card.dart';
import '../../../widgets/tech_chip.dart';
import '../programmer_task_actions.dart';
/// Hero card at the top of the programmer dashboard showing the currently
/// running task (or an idle state when nothing is running).
class NowWorkingCard extends ConsumerWidget {
const NowWorkingCard({
super.key,
required this.running,
required this.nextUp,
required this.todayOnTask,
required this.totalOnTask,
required this.readOnly,
});
/// The currently running task, or null when nothing is running.
final ProgrammerTask? running;
/// First queued (not-started) task, shown as a start target when idle.
final ProgrammerTask? nextUp;
/// How long the user has worked on [running] today.
final Duration todayOnTask;
/// Total worked time on [running] across all days.
final Duration totalOnTask;
final bool readOnly;
@override
Widget build(BuildContext context, WidgetRef ref) {
return M3Card.elevated(
child: Padding(
padding: const EdgeInsets.all(16),
child: running != null
? _RunningView(
task: running!,
todayOnTask: todayOnTask,
totalOnTask: totalOnTask,
readOnly: readOnly,
)
: _IdleView(nextUp: nextUp, readOnly: readOnly),
),
);
}
}
class _RunningView extends ConsumerWidget {
const _RunningView({
required this.task,
required this.todayOnTask,
required this.totalOnTask,
required this.readOnly,
});
final ProgrammerTask task;
final Duration todayOnTask;
final Duration totalOnTask;
final bool readOnly;
@override
Widget build(BuildContext context, WidgetRef ref) {
final tt = Theme.of(context).textTheme;
final cs = Theme.of(context).colorScheme;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
task.title,
style: tt.titleMedium?.copyWith(fontWeight: FontWeight.w600),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 4,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
TechChip(task.category, icon: Icons.category_outlined, dense: true),
Text(
'Today ${formatDurationHm(todayOnTask)} · '
'Total ${formatDurationHm(totalOnTask)}',
style: tt.bodySmall?.copyWith(color: cs.onSurfaceVariant),
),
],
),
if (!readOnly) ...[
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
OutlinedButton.icon(
onPressed: () => ref
.read(programmerTasksControllerProvider)
.pauseTask(taskId: task.id),
icon: const Icon(Icons.pause_rounded, size: 18),
label: const Text('Pause'),
),
const SizedBox(width: 8),
FilledButton.icon(
onPressed: () =>
completeProgrammerTaskWithConfirm(context, ref, task),
icon: const Icon(Icons.check_rounded, size: 18),
label: const Text('Complete'),
),
],
),
],
],
);
}
}
class _IdleView extends ConsumerWidget {
const _IdleView({required this.nextUp, required this.readOnly});
final ProgrammerTask? nextUp;
final bool readOnly;
@override
Widget build(BuildContext context, WidgetRef ref) {
final tt = Theme.of(context).textTheme;
final cs = Theme.of(context).colorScheme;
return Row(
children: [
Expanded(
child: Text(
'Nothing running',
style: tt.bodyLarge?.copyWith(color: cs.onSurfaceVariant),
),
),
if (nextUp != null && !readOnly)
FilledButton.icon(
onPressed: () =>
startProgrammerTaskWithPausePrompt(context, ref, nextUp!),
icon: const Icon(Icons.play_arrow_rounded, size: 18),
label: const Text('Start'),
),
],
);
}
}