Przebudowanie aplikacji, usprawnione AI, dodanie combo buildera
Some checks failed
Build Linux App / build (push) Failing after 1m18s

This commit is contained in:
Kazimierz Ciołek
2026-07-06 23:44:17 +02:00
parent 9dcc4b87de
commit 6dd7213eb0
48 changed files with 3229 additions and 669 deletions

View File

@@ -1,4 +1,5 @@
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/core/constants/ui_constants.dart';
import 'package:trainhub_flutter/domain/entities/workout_activity.dart';
@@ -21,9 +22,7 @@ class ActivityCard extends StatelessWidget {
decoration: BoxDecoration(
color: AppColors.surfaceContainer.withValues(alpha: 0.6),
borderRadius: UIConstants.cardBorderRadius,
border: Border.all(
color: accentColor.withValues(alpha: 0.2),
),
border: Border.all(color: accentColor.withValues(alpha: 0.2)),
),
child: Column(
children: [
@@ -47,11 +46,12 @@ class ActivityCard extends StatelessWidget {
const SizedBox(height: UIConstants.spacing12),
// Activity name
Text(
activity.name,
style: const TextStyle(
activity.name.toUpperCase(),
style: GoogleFonts.chakraPetch(
color: AppColors.textPrimary,
fontSize: 22,
fontWeight: FontWeight.w600,
fontWeight: FontWeight.w700,
letterSpacing: 0.5,
),
textAlign: TextAlign.center,
),
@@ -59,10 +59,7 @@ class ActivityCard extends StatelessWidget {
const SizedBox(height: UIConstants.spacing8),
Text(
'${activity.sectionName ?? ''} \u00B7 Set ${activity.setIndex}/${activity.totalSets}',
style: const TextStyle(
color: AppColors.textMuted,
fontSize: 13,
),
style: const TextStyle(color: AppColors.textMuted, fontSize: 13),
),
if (activity.originalExercise != null) ...[
const SizedBox(height: UIConstants.spacing16),
@@ -86,10 +83,7 @@ class ActivityCard extends StatelessWidget {
padding: const EdgeInsets.only(top: UIConstants.spacing8),
child: Text(
'Take a break',
style: TextStyle(
color: AppColors.textMuted,
fontSize: 14,
),
style: TextStyle(color: AppColors.textMuted, fontSize: 14),
),
),
],
@@ -124,10 +118,7 @@ class _InfoChip extends StatelessWidget {
),
Text(
label,
style: const TextStyle(
color: AppColors.textMuted,
fontSize: 11,
),
style: const TextStyle(color: AppColors.textMuted, fontSize: 11),
),
],
),

View File

@@ -46,11 +46,7 @@ class SessionControls extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
if (isTimeBased) ...[
_ControlButton(
icon: Icons.replay_10,
onTap: onRewind,
size: 24,
),
_ControlButton(icon: Icons.replay_10, onTap: onRewind, size: 24),
const SizedBox(width: UIConstants.spacing8),
],
_ControlButton(

View File

@@ -101,9 +101,7 @@ class WorkoutSessionController extends _$WorkoutSessionController {
0,
maxDuration,
);
state = AsyncValue.data(
currentState.copyWith(timeRemaining: newRemaining),
);
state = AsyncValue.data(currentState.copyWith(timeRemaining: newRemaining));
}
void _tick(Timer timer) {

View File

@@ -2,6 +2,7 @@ import 'dart:math' as math;
import 'package:auto_route/auto_route.dart';
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/workout_activity.dart';
@@ -25,7 +26,7 @@ class WorkoutSessionPage extends ConsumerWidget {
final asyncState = ref.watch(workoutSessionControllerProvider(planId));
return Scaffold(
backgroundColor: AppColors.zinc950,
backgroundColor: AppColors.surface,
body: asyncState.when(
loading: () => const Center(
child: CircularProgressIndicator(
@@ -71,15 +72,10 @@ class WorkoutSessionPage extends ConsumerWidget {
);
if (state.isFinished) {
return _CompletionScreen(
totalTimeElapsed: state.totalTimeElapsed,
);
return _CompletionScreen(totalTimeElapsed: state.totalTimeElapsed);
}
return _ActiveSessionView(
state: state,
controller: controller,
);
return _ActiveSessionView(state: state, controller: controller);
},
),
);
@@ -90,10 +86,7 @@ class _ActiveSessionView extends StatefulWidget {
final WorkoutSessionState state;
final WorkoutSessionController controller;
const _ActiveSessionView({
required this.state,
required this.controller,
});
const _ActiveSessionView({required this.state, required this.controller});
@override
State<_ActiveSessionView> createState() => _ActiveSessionViewState();
@@ -110,8 +103,7 @@ class _ActiveSessionViewState extends State<_ActiveSessionView> {
final double timeProgress;
if (activity != null && activity.duration > 0) {
timeProgress =
1.0 - (widget.state.timeRemaining / activity.duration);
timeProgress = 1.0 - (widget.state.timeRemaining / activity.duration);
} else {
timeProgress = 0.0;
}
@@ -128,11 +120,7 @@ class _ActiveSessionViewState extends State<_ActiveSessionView> {
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
AppColors.zinc950,
accentTint,
AppColors.zinc950,
],
colors: [AppColors.surface, accentTint, AppColors.surface],
stops: const [0.0, 0.5, 1.0],
),
),
@@ -149,8 +137,7 @@ class _ActiveSessionViewState extends State<_ActiveSessionView> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (activity != null)
ActivityCard(activity: activity),
if (activity != null) ActivityCard(activity: activity),
const SizedBox(height: UIConstants.spacing32),
if (isTimeBased)
_CircularTimerDisplay(
@@ -249,26 +236,20 @@ class _ActiveSessionViewState extends State<_ActiveSessionView> {
decoration: BoxDecoration(
color: AppColors.zinc800.withValues(alpha: 0.6),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: AppColors.border.withValues(alpha: 0.5),
),
border: Border.all(color: AppColors.border.withValues(alpha: 0.5)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.timer_outlined,
size: 14,
color: AppColors.textMuted,
),
Icon(Icons.timer_outlined, size: 14, color: AppColors.textMuted),
const SizedBox(width: 4),
Text(
_formatDuration(widget.state.totalTimeElapsed),
style: const TextStyle(
style: GoogleFonts.jetBrainsMono(
color: AppColors.textSecondary,
fontSize: 13,
fontWeight: FontWeight.w500,
fontFeatures: [FontFeature.tabularFigures()],
fontFeatures: const [FontFeature.tabularFigures()],
),
),
],
@@ -352,11 +333,10 @@ class _RepsDisplay extends StatelessWidget {
children: [
Text(
'$reps',
style: TextStyle(
style: GoogleFonts.jetBrainsMono(
color: AppColors.textPrimary,
fontSize: 72,
fontWeight: FontWeight.w200,
letterSpacing: -2,
fontWeight: FontWeight.w700,
shadows: [
Shadow(
color: AppColors.accent.withValues(alpha: 0.3),
@@ -462,13 +442,11 @@ class _CircularTimerDisplay extends StatelessWidget {
children: [
Text(
_formatTime(timeRemaining),
style: TextStyle(
style: GoogleFonts.jetBrainsMono(
color: AppColors.textPrimary,
fontSize: 64,
fontWeight: FontWeight.w200,
letterSpacing: -1,
fontWeight: FontWeight.w700,
fontFeatures: const [FontFeature.tabularFigures()],
fontFamily: 'monospace',
shadows: [
Shadow(
color: ringColor.withValues(alpha: 0.3),
@@ -525,10 +503,7 @@ class TimerRingPainter extends CustomPainter {
final double progress;
final Color ringColor;
TimerRingPainter({
required this.progress,
required this.ringColor,
});
TimerRingPainter({required this.progress, required this.ringColor});
@override
void paint(Canvas canvas, Size size) {
@@ -551,10 +526,10 @@ class TimerRingPainter extends CustomPainter {
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth * 2
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 16);
final sweepAngle = 2 * math.pi * progress;
final startAngle = -math.pi / 2;
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
startAngle,
@@ -576,7 +551,9 @@ class TimerRingPainter extends CustomPainter {
);
final progressPaint = Paint()
..shader = gradient.createShader(Rect.fromCircle(center: center, radius: radius))
..shader = gradient.createShader(
Rect.fromCircle(center: center, radius: radius),
)
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round
..strokeWidth = strokeWidth;
@@ -593,7 +570,7 @@ class TimerRingPainter extends CustomPainter {
@override
bool shouldRepaint(TimerRingPainter oldDelegate) {
return oldDelegate.progress != progress ||
oldDelegate.ringColor != ringColor;
oldDelegate.ringColor != ringColor;
}
}
@@ -601,10 +578,7 @@ class _UpNextPill extends StatelessWidget {
final String nextActivityName;
final bool isNextRest;
const _UpNextPill({
required this.nextActivityName,
required this.isNextRest,
});
const _UpNextPill({required this.nextActivityName, required this.isNextRest});
@override
Widget build(BuildContext context) {
@@ -693,9 +667,7 @@ class _ActivitiesListPanel extends StatelessWidget {
width: 320,
decoration: const BoxDecoration(
color: AppColors.surfaceContainer,
border: Border(
left: BorderSide(color: AppColors.border),
),
border: Border(left: BorderSide(color: AppColors.border)),
),
child: Column(
children: [
@@ -763,8 +735,8 @@ class _ActivitiesListPanel extends StatelessWidget {
color: isCurrent
? AppColors.accent
: isRest
? AppColors.info
: AppColors.zinc600,
? AppColors.info
: AppColors.zinc600,
),
),
const SizedBox(width: UIConstants.spacing12),
@@ -785,7 +757,8 @@ class _ActivitiesListPanel extends StatelessWidget {
: FontWeight.normal,
),
),
if (!isRest && activity.setIndex != null)
if (!isRest &&
activity.setIndex != null)
Text(
'Set ${activity.setIndex}/${activity.totalSets} · ${activity.sectionName ?? ''}',
style: const TextStyle(
@@ -888,22 +861,19 @@ class _CompletionScreen extends StatelessWidget {
),
),
const SizedBox(height: UIConstants.spacing24),
const Text(
'Workout Complete',
style: TextStyle(
Text(
'WORKOUT COMPLETE',
style: GoogleFonts.chakraPetch(
color: AppColors.textPrimary,
fontSize: 28,
fontWeight: FontWeight.w600,
letterSpacing: -0.5,
fontWeight: FontWeight.w700,
letterSpacing: 1,
),
),
const SizedBox(height: UIConstants.spacing8),
Text(
'Great job! You crushed it.',
style: TextStyle(
color: AppColors.textSecondary,
fontSize: 15,
),
style: TextStyle(color: AppColors.textSecondary, fontSize: 15),
),
const SizedBox(height: UIConstants.spacing32),
Container(
@@ -932,12 +902,11 @@ class _CompletionScreen extends StatelessWidget {
const SizedBox(height: UIConstants.spacing4),
Text(
_formatDuration(totalTimeElapsed),
style: TextStyle(
style: GoogleFonts.jetBrainsMono(
color: AppColors.textPrimary,
fontSize: 36,
fontWeight: FontWeight.w300,
fontWeight: FontWeight.w600,
fontFeatures: const [FontFeature.tabularFigures()],
fontFamily: 'monospace',
),
),
],

View File

@@ -21,8 +21,8 @@ class WorkoutSessionState with _$WorkoutSessionState {
WorkoutActivityEntity? get nextActivity =>
currentIndex + 1 < activities.length
? activities[currentIndex + 1]
: null;
? activities[currentIndex + 1]
: null;
double get progress =>
activities.isEmpty ? 0.0 : currentIndex / activities.length;