Files
trainhub-flutter/lib/presentation/plan_editor/widgets/plan_flow_graph.dart
Kazimierz Ciołek 6dd7213eb0
Some checks failed
Build Linux App / build (push) Failing after 1m18s
Przebudowanie aplikacji, usprawnione AI, dodanie combo buildera
2026-07-06 23:44:17 +02:00

122 lines
3.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:trainhub_flutter/core/theme/app_colors.dart';
import 'package:trainhub_flutter/domain/entities/training_plan.dart';
import 'package:trainhub_flutter/presentation/common/widgets/node_graph_canvas.dart';
/// Graph view of a training plan: one column per section, exercises chained
/// top-to-bottom, sections chained left-to-right — the session flow at a
/// glance. Nodes can be dragged to rearrange the picture; the plan structure
/// itself is edited in the list view.
class PlanFlowGraph extends StatefulWidget {
const PlanFlowGraph({super.key, required this.plan});
final TrainingPlanEntity plan;
@override
State<PlanFlowGraph> createState() => _PlanFlowGraphState();
}
class _PlanFlowGraphState extends State<PlanFlowGraph> {
/// User-dragged overrides on top of the auto layout, per node id.
final Map<String, Offset> _overrides = {};
static const double _colWidth = 250;
static const double _rowHeight = 84;
@override
Widget build(BuildContext context) {
final nodes = <GraphNodeData>[];
final edges = <GraphEdgeData>[];
Offset pos(String id, Offset fallback) => _overrides[id] ?? fallback;
nodes.add(
GraphNodeData(
id: 'start',
title: 'Start Session',
subtitle: widget.plan.name,
accent: AppColors.success,
position: pos('start', const Offset(40, 40)),
),
);
var previousTail = 'start';
for (var s = 0; s < widget.plan.sections.length; s++) {
final section = widget.plan.sections[s];
final x = 40.0 + (s + 1) * _colWidth;
final sectionId = 'sec-${section.id}';
nodes.add(
GraphNodeData(
id: sectionId,
title: section.name,
subtitle: '${section.exercises.length} drills',
accent: AppColors.accent,
position: pos(sectionId, Offset(x, 40)),
),
);
edges.add(GraphEdgeData(previousTail, sectionId));
var tail = sectionId;
for (var e = 0; e < section.exercises.length; e++) {
final exercise = section.exercises[e];
final id = 'ex-${exercise.instanceId}';
final detail = exercise.isTime
? '${exercise.sets} × ${exercise.value}s · rest ${exercise.rest}s'
: '${exercise.sets} × ${exercise.value} · rest ${exercise.rest}s';
nodes.add(
GraphNodeData(
id: id,
title: exercise.name,
subtitle: detail,
accent: AppColors.info,
position: pos(id, Offset(x + 24, 40 + (e + 1) * _rowHeight)),
),
);
edges.add(GraphEdgeData(tail, id));
tail = id;
}
previousTail = tail;
}
return Stack(
children: [
ClipRect(
child: NodeGraphCanvas(
nodes: nodes,
edges: edges,
canvasSize: Size(
(widget.plan.sections.length + 2) * _colWidth + 600,
2000,
),
onNodeMoved: (id, position) =>
setState(() => _overrides[id] = position),
),
),
Positioned(
left: 16,
bottom: 16,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: AppColors.surfaceContainer.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColors.border),
),
child: Text(
'SESSION FLOW · DRAG TO REARRANGE · EDIT IN LIST VIEW',
style: GoogleFonts.jetBrainsMono(
fontSize: 9,
letterSpacing: 0.8,
color: AppColors.textMuted,
),
),
),
),
],
);
}
}