f8c3422715
Extract _TaskCard from the list screen into ProgrammerTaskCard, a shared ConsumerWidget that reads programmerRunStatesProvider so the paused-task pill and Start/Resume/Complete buttons are consistent across any surface that embeds it. Move the _complete body verbatim to completeProgrammerTaskWithConfirm in programmer_task_actions.dart; the detail screen's _ActionBar now calls the shared function. Co-Authored-By: claude-flow <ruv@ruv.net>
144 lines
5.1 KiB
Dart
144 lines
5.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
import '../../../models/profile.model.dart';
|
|
import '../../../models/programmer_task.model.dart';
|
|
import '../../../providers/programmer_day_data_provider.dart';
|
|
import '../../../providers/programmer_projects_provider.dart';
|
|
import '../../../theme/app_status_colors.dart';
|
|
import '../../../utils/app_time.dart';
|
|
import '../../../widgets/m3_card.dart';
|
|
import '../../../widgets/status_pill.dart';
|
|
import '../../../widgets/tech_chip.dart';
|
|
import '../programmer_task_actions.dart';
|
|
import '../programmer_task_status_kind.dart';
|
|
|
|
/// Shared task card that reflects the current run state (running / paused /
|
|
/// not started) sourced from [programmerRunStatesProvider].
|
|
///
|
|
/// Used by the task list screen (and any future surface) so the paused-task
|
|
/// pill and action buttons are consistent everywhere.
|
|
class ProgrammerTaskCard extends ConsumerWidget {
|
|
const ProgrammerTaskCard({
|
|
super.key,
|
|
required this.task,
|
|
this.assignee,
|
|
required this.isMine,
|
|
this.readOnly = false,
|
|
});
|
|
|
|
final ProgrammerTask task;
|
|
final Profile? assignee;
|
|
final bool isMine;
|
|
final bool readOnly;
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final tt = Theme.of(context).textTheme;
|
|
final cs = Theme.of(context).colorScheme;
|
|
|
|
final runStates = ref.watch(programmerRunStatesProvider);
|
|
final runInfo = runStates[task.id];
|
|
final isPaused = runInfo?.state == ProgrammerRunState.paused;
|
|
final isRunning = runInfo?.state == ProgrammerRunState.running;
|
|
|
|
final canStart =
|
|
isMine && !readOnly && task.status == ProgrammerTaskStatus.queued;
|
|
final canResume = isMine && !readOnly && isPaused;
|
|
final canComplete = isMine && !readOnly && (isRunning || isPaused);
|
|
|
|
final project = task.projectId == null
|
|
? null
|
|
: ref.watch(programmerProjectByIdProvider(task.projectId!));
|
|
|
|
final pillKind =
|
|
isPaused ? StatusKind.warning : programmerTaskStatusKind(task.status);
|
|
final pillLabel =
|
|
isPaused ? 'Paused' : ProgrammerTaskStatus.label(task.status);
|
|
|
|
return M3Card.outlined(
|
|
onTap: () => context.go('/programmer-tasks/${task.id}'),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(14),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
task.title,
|
|
style:
|
|
tt.titleMedium?.copyWith(fontWeight: FontWeight.w600),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
StatusPill.kind(pillKind, label: pillLabel),
|
|
],
|
|
),
|
|
const SizedBox(height: 10),
|
|
Wrap(
|
|
spacing: 8,
|
|
runSpacing: 8,
|
|
crossAxisAlignment: WrapCrossAlignment.center,
|
|
children: [
|
|
TechChip(task.category,
|
|
icon: Icons.category_outlined, dense: true),
|
|
if (project != null)
|
|
TechChip(project.name,
|
|
icon: Icons.folder_outlined, dense: true),
|
|
if (assignee != null && !isMine)
|
|
Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.person_outline,
|
|
size: 14, color: cs.onSurfaceVariant),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
assignee!.fullName,
|
|
style: tt.bodySmall
|
|
?.copyWith(color: cs.onSurfaceVariant),
|
|
),
|
|
],
|
|
),
|
|
Text(
|
|
AppTime.relative(task.createdAt),
|
|
style: tt.bodySmall?.copyWith(color: cs.onSurfaceVariant),
|
|
),
|
|
],
|
|
),
|
|
if (canStart || canResume) ...[
|
|
const SizedBox(height: 12),
|
|
Align(
|
|
alignment: Alignment.centerRight,
|
|
child: FilledButton.tonalIcon(
|
|
onPressed: () =>
|
|
startProgrammerTaskWithPausePrompt(context, ref, task),
|
|
icon: const Icon(Icons.play_arrow_rounded, size: 18),
|
|
label: Text(canResume ? 'Resume' : 'Start'),
|
|
),
|
|
),
|
|
],
|
|
if (canComplete) ...[
|
|
const SizedBox(height: 8),
|
|
Align(
|
|
alignment: Alignment.centerRight,
|
|
child: OutlinedButton.icon(
|
|
onPressed: () =>
|
|
completeProgrammerTaskWithConfirm(context, ref, task),
|
|
icon: const Icon(Icons.check_rounded, size: 18),
|
|
label: const Text('Complete'),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|