Refactoring
Some checks failed
Build Linux App / build (push) Failing after 1m33s

This commit is contained in:
2026-02-23 10:02:23 -05:00
parent 21f1387fa8
commit 0c9eb8878d
57 changed files with 8179 additions and 1114 deletions

View File

@@ -1,10 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:trainhub_flutter/core/constants/ui_constants.dart';
import 'package:trainhub_flutter/core/theme/app_colors.dart';
import 'package:trainhub_flutter/domain/entities/training_exercise.dart';
import 'package:trainhub_flutter/domain/entities/training_plan.dart';
import 'package:trainhub_flutter/presentation/plan_editor/models/exercise_drag_data.dart';
import 'package:trainhub_flutter/presentation/plan_editor/plan_editor_controller.dart';
class PlanExerciseTile extends ConsumerWidget {
class PlanExerciseTile extends ConsumerStatefulWidget {
final TrainingExerciseEntity exercise;
final int sectionIndex;
final int exerciseIndex;
@@ -19,100 +23,192 @@ class PlanExerciseTile extends ConsumerWidget {
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final controller = ref.read(planEditorControllerProvider(plan.id).notifier);
ConsumerState<PlanExerciseTile> createState() => _PlanExerciseTileState();
}
return Card(
margin: const EdgeInsets.symmetric(vertical: 4),
elevation: 0,
color: Theme.of(context).colorScheme.surfaceContainerLow,
shape: RoundedRectangleBorder(
side: BorderSide(color: Theme.of(context).colorScheme.outlineVariant),
class _PlanExerciseTileState extends ConsumerState<PlanExerciseTile> {
bool _isHovered = false;
@override
Widget build(BuildContext context) {
final controller = ref.read(
planEditorControllerProvider(widget.plan.id).notifier,
);
final dragData = ExerciseDragData(
fromSectionIndex: widget.sectionIndex,
exerciseIndex: widget.exerciseIndex,
exercise: widget.exercise,
);
return LongPressDraggable<ExerciseDragData>(
data: dragData,
feedback: Material(
elevation: 8,
borderRadius: BorderRadius.circular(8),
child: Container(
width: 220,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: AppColors.zinc800,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColors.accent.withValues(alpha: 0.5)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.drag_indicator,
size: 15,
color: AppColors.accent,
),
const SizedBox(width: 8),
Flexible(
child: Text(
widget.exercise.name,
style: GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
),
),
childWhenDragging: Opacity(
opacity: 0.35,
child: _buildContent(controller),
),
child: MouseRegion(
onEnter: (_) => setState(() => _isHovered = true),
onExit: (_) => setState(() => _isHovered = false),
child: _buildContent(controller),
),
);
}
Widget _buildContent(PlanEditorController controller) {
return AnimatedContainer(
duration: UIConstants.animationDuration,
margin: const EdgeInsets.symmetric(vertical: 3),
decoration: BoxDecoration(
color: _isHovered ? AppColors.zinc800 : AppColors.zinc900,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: _isHovered ? AppColors.zinc700 : AppColors.border,
),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
child: Column(
children: [
// --- Header: drag handle + name + delete ---
Row(
children: [
const Icon(
Icons.drag_handle,
size: 15,
color: AppColors.zinc600,
),
const SizedBox(width: 8),
Expanded(
child: Text(
exercise.name,
style: const TextStyle(fontWeight: FontWeight.bold),
widget.exercise.name,
style: GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w500,
color: AppColors.textPrimary,
),
overflow: TextOverflow.ellipsis,
),
),
IconButton(
icon: const Icon(Icons.close, size: 18),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
onPressed: () => controller.removeExerciseFromSection(
sectionIndex,
exerciseIndex,
AnimatedOpacity(
opacity: _isHovered ? 1.0 : 0.0,
duration: UIConstants.animationDuration,
child: GestureDetector(
onTap: () => controller.removeExerciseFromSection(
widget.sectionIndex,
widget.exerciseIndex,
),
child: const Padding(
padding: EdgeInsets.all(2),
child: Icon(
Icons.close,
size: 14,
color: AppColors.textMuted,
),
),
),
),
],
),
const SizedBox(height: 8),
// --- Fields row ---
Row(
children: [
_buildNumberInput(
context,
_FieldBox(
label: 'Sets',
value: exercise.sets,
value: widget.exercise.sets,
onChanged: (val) => controller.updateExerciseParams(
sectionIndex,
exerciseIndex,
widget.sectionIndex,
widget.exerciseIndex,
sets: val,
),
),
const SizedBox(width: 8),
_buildNumberInput(
context,
label: exercise.isTime ? 'Secs' : 'Reps',
value: exercise.value,
_FieldBox(
label: widget.exercise.isTime ? 'Secs' : 'Reps',
value: widget.exercise.value,
onChanged: (val) => controller.updateExerciseParams(
sectionIndex,
exerciseIndex,
widget.sectionIndex,
widget.exerciseIndex,
value: val,
),
),
const SizedBox(width: 8),
_buildNumberInput(
context,
_FieldBox(
label: 'Rest(s)',
value: exercise.rest,
value: widget.exercise.rest,
onChanged: (val) => controller.updateExerciseParams(
sectionIndex,
exerciseIndex,
widget.sectionIndex,
widget.exerciseIndex,
rest: val,
),
),
const SizedBox(width: 8),
// Toggle Time/Reps
InkWell(
// Time toggle pill
GestureDetector(
onTap: () => controller.updateExerciseParams(
sectionIndex,
exerciseIndex,
isTime: !exercise.isTime,
widget.sectionIndex,
widget.exerciseIndex,
isTime: !widget.exercise.isTime,
),
child: Container(
padding: const EdgeInsets.all(8),
child: AnimatedContainer(
duration: UIConstants.animationDuration,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 6,
),
decoration: BoxDecoration(
color: widget.exercise.isTime
? AppColors.accentMuted
: AppColors.zinc800,
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: Theme.of(context).colorScheme.outline,
color: widget.exercise.isTime
? AppColors.accent.withValues(alpha: 0.4)
: AppColors.border,
),
borderRadius: BorderRadius.circular(4),
color: exercise.isTime
? Theme.of(context).colorScheme.primaryContainer
: null,
),
child: Icon(
Icons.timer,
size: 16,
color: exercise.isTime
? Theme.of(context).colorScheme.onPrimaryContainer
: null,
Icons.timer_outlined,
size: 15,
color: widget.exercise.isTime
? AppColors.accent
: AppColors.textMuted,
),
),
),
@@ -123,38 +219,75 @@ class PlanExerciseTile extends ConsumerWidget {
),
);
}
}
Widget _buildNumberInput(
BuildContext context, {
required String label,
required int value,
required Function(int) onChanged,
}) {
// ---------------------------------------------------------------------------
// Compact field box (label + number input)
// ---------------------------------------------------------------------------
class _FieldBox extends StatelessWidget {
final String label;
final int value;
final Function(int) onChanged;
const _FieldBox({
required this.label,
required this.value,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: Theme.of(context).textTheme.labelSmall),
Text(
label,
style: GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.w500,
color: AppColors.textMuted,
letterSpacing: 0.3,
),
),
const SizedBox(height: 3),
SizedBox(
height: 40,
height: 32,
child: TextField(
controller: TextEditingController(text: value.toString())
..selection = TextSelection.fromPosition(
TextPosition(offset: value.toString().length),
),
keyboardType: TextInputType.number,
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(
horizontal: 8,
textAlign: TextAlign.center,
style: GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
),
decoration: InputDecoration(
filled: true,
fillColor: AppColors.zinc950,
contentPadding: const EdgeInsets.symmetric(
horizontal: 4,
vertical: 0,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(color: AppColors.border),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(color: AppColors.border),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(color: AppColors.zinc500),
),
),
onChanged: (val) {
final intVal = int.tryParse(val);
if (intVal != null) {
onChanged(intVal);
}
if (intVal != null) onChanged(intVal);
},
),
),