kettlebell-tracker/lib/screens/training_screen.dart

448 lines
17 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:kettlebell_tracker/models/training_session.dart';
import 'package:kettlebell_tracker/providers/settings_provider.dart';
import 'package:kettlebell_tracker/providers/training_provider.dart';
import 'package:kettlebell_tracker/theme/app_theme.dart';
import 'package:kettlebell_tracker/widgets/custom_card.dart';
import 'package:kettlebell_tracker/services/api_service.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:intl/intl.dart';
class TrainingScreen extends ConsumerStatefulWidget {
const TrainingScreen({super.key});
@override
ConsumerState<TrainingScreen> createState() => _TrainingScreenState();
}
class _TrainingScreenState extends ConsumerState<TrainingScreen> {
Timer? _mainTimer;
Timer? _lastSetTimer;
final _audioPlayer = AudioPlayer();
@override
void dispose() {
_stopTimer();
_audioPlayer.dispose();
super.dispose();
}
void _startTraining() {
_audioPlayer.stop();
final settings = ref.read(settingsProvider);
ref.read(trainingProvider.notifier).startTraining(
settings.trainingTimeMinutes,
settings.goalSets,
);
_mainTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
final notifier = ref.read(trainingProvider.notifier);
if (notifier.state.remainingSeconds > 0) {
notifier.tick();
} else {
_stopTimer();
_autoFinishTraining();
}
});
_lastSetTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
ref.read(trainingProvider.notifier).tickLastSetTimer();
});
}
void _stopTimer() {
_mainTimer?.cancel();
_lastSetTimer?.cancel();
_mainTimer = null;
_lastSetTimer = null;
}
void _completeSet() {
ref.read(trainingProvider.notifier).completeSet();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Satz gespeichert!'),
backgroundColor: AppTheme.oneDarkGreen,
duration: Duration(seconds: 1),
),
);
}
void _finishTraining() {
_stopTimer();
_audioPlayer.stop();
final state = ref.read(trainingProvider);
final settings = ref.read(settingsProvider);
final session = TrainingSession(
date: DateTime.now(),
sets: state.setsDone,
weightLeft: settings.weightLeft,
weightRight: settings.weightRight,
repsPerSet: state.currentReps,
duration: state.initialDurationSeconds - state.remainingSeconds,
program: state.currentProgram,
blockDay: state.currentBlockDay,
);
ref.read(trainingProvider.notifier).finishTraining(session);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Training gespeichert!'),
backgroundColor: AppTheme.oneDarkPrimary,
),
);
}
Future<void> _autoFinishTraining() async {
await _audioPlayer.play(AssetSource('sound/notification.mp3'));
_finishTraining();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Zeit abgelaufen! Training automatisch beendet und gespeichert.'),
backgroundColor: AppTheme.oneDarkRed,
duration: Duration(seconds: 4),
),
);
}
String _formatDuration(int totalSeconds) {
final duration = Duration(seconds: totalSeconds);
final minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0');
final seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0');
return '$minutes:$seconds';
}
@override
Widget build(BuildContext context) {
final trainingState = ref.watch(trainingProvider);
final bool isTrainingRunning = trainingState.isTrainingRunning;
return Scaffold(
appBar: AppBar(title: const Text('Aktuelles Training')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Header-Bereich mit Programm-Infos
CustomCard(
child: Column(
children: [
Text(
'${trainingState.currentProgram.toUpperCase()}',
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: AppTheme.oneDarkPrimary,
),
),
Text(
'Block Tag: ${trainingState.currentBlockDay}',
style: const TextStyle(
fontSize: 20,
color: AppTheme.oneDarkAccent,
),
),
Text(
'Reps pro Satz: ${trainingState.currentReps}',
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.w500,
color: AppTheme.oneDarkYellow,
),
),
],
),
),
const SizedBox(height: 18),
// Timer und Fortschritt
CustomCard(
child: Column(
children: [
const Text(
'Verbleibende Zeit',
style: TextStyle(
fontSize: 16,
color: AppTheme.oneDarkTextWeak,
),
),
Text(
_formatDuration(trainingState.remainingSeconds),
style: const TextStyle(
fontSize: 40,
fontWeight: FontWeight.bold,
color: AppTheme.oneDarkPrimary,
),
),
const SizedBox(height: 10),
LinearProgressIndicator(
value: trainingState.progress,
minHeight: 12,
borderRadius: BorderRadius.circular(6),
color: AppTheme.oneDarkGreen,
backgroundColor: AppTheme.oneDarkTextWeak,
),
const SizedBox(height: 8),
Text(
'Fortschritt: ${(trainingState.progress * 100).toStringAsFixed(0)}%',
style: const TextStyle(
fontSize: 16,
color: AppTheme.oneDarkText,
),
),
],
),
),
const SizedBox(height: 18),
// Aktions-Buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton.icon(
onPressed: isTrainingRunning ? null : _startTraining,
icon: const Icon(Icons.play_arrow, size: 28),
label: const Text('Start', style: TextStyle(fontSize: 18)),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.oneDarkGreen,
foregroundColor: Colors.white,
minimumSize: const Size(110, 48),
),
),
ElevatedButton.icon(
onPressed: !isTrainingRunning ? null : _completeSet,
icon: const Icon(Icons.check_circle, size: 28),
label: const Text('Satz', style: TextStyle(fontSize: 18)),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.oneDarkPrimary,
foregroundColor: Colors.white,
minimumSize: const Size(110, 48),
),
),
ElevatedButton.icon(
onPressed: !isTrainingRunning ? null : _finishTraining,
icon: const Icon(Icons.stop, size: 28),
label: const Text('Beenden', style: TextStyle(fontSize: 18)),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.oneDarkRed,
foregroundColor: Colors.white,
minimumSize: const Size(110, 48),
),
),
],
),
const SizedBox(height: 22),
// Satz-Historie
Text(
"Satz-Historie",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: AppTheme.oneDarkAccent,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
CustomCard(
child: SizedBox(
height: 180,
child: trainingState.setTimes.isEmpty
? const Center(
child: Text(
"Noch keine Sätze in dieser Einheit.",
style: TextStyle(color: AppTheme.oneDarkTextWeak),
),
)
: ListView.separated(
itemCount: trainingState.setTimes.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) {
final setTime = trainingState.setTimes[index];
return ListTile(
leading: CircleAvatar(
backgroundColor: AppTheme.oneDarkGreen,
child: Text(
'${index + 1}',
style: const TextStyle(color: Colors.white),
),
),
title: Text(
'um ${DateFormat.Hms().format(setTime)}',
style: const TextStyle(fontSize: 16),
),
trailing: Text(
'${trainingState.currentReps} Reps',
style: const TextStyle(
fontSize: 16,
color: AppTheme.oneDarkGreen,
),
),
);
},
),
),
),
],
),
),
);
}
// @override
// Widget build(BuildContext context) {
// final trainingState = ref.watch(trainingProvider);
// final settings = ref.watch(settingsProvider);
// final bool isTrainingRunning = trainingState.isTrainingRunning;
//
// return Scaffold(
// appBar: AppBar(title: const Text('Aktuelles Training')),
// body: SingleChildScrollView(
// padding: const EdgeInsets.all(16.0),
// child: Column(
// children: [
// CustomCard(
// child: Column(
// children: [
// Text(
// 'Programm: ${trainingState.currentProgram.toUpperCase()}',
// style: const TextStyle(
// fontSize: 20,
// color: AppTheme.oneDarkYellow,
// ),
// ),
// Text(
// 'Block Tag: ${trainingState.currentBlockDay}',
// style: const TextStyle(
// fontSize: 18,
// color: AppTheme.oneDarkAccent,
// ),
// ),
// Text(
// 'Verbleibende Zeit: ${_formatDuration(trainingState.remainingSeconds)}',
// style: const TextStyle(
// fontSize: 26,
// fontWeight: FontWeight.bold,
// color: AppTheme.oneDarkPrimary,
// ),
// ),
// const SizedBox(height: 12),
// Text(
// 'Sätze: ${trainingState.setsDone}',
// style: const TextStyle(
// fontSize: 24,
// color: AppTheme.oneDarkYellow,
// ),
// ),
// const SizedBox(height: 8),
// Text(
// 'Reps pro Satz: ${trainingState.currentReps}',
// style: const TextStyle(
// fontSize: 20,
// color: AppTheme.oneDarkAccent,
// ),
// ),
// const SizedBox(height: 12),
// Text(
// 'Zeit seit letztem Satz: ${_formatDuration(trainingState.secondsSinceLastSet)}',
// style: const TextStyle(
// fontSize: 18,
// color: AppTheme.oneDarkTextWeak,
// ),
// ),
// const SizedBox(height: 20),
// LinearProgressIndicator(
// value: trainingState.progress,
// minHeight: 10,
// borderRadius: BorderRadius.circular(5),
// ),
// const SizedBox(height: 8),
// Text(
// 'Fortschritt: ${(trainingState.progress * 100).toStringAsFixed(0)}%',
// style: const TextStyle(
// fontSize: 16,
// color: AppTheme.oneDarkText,
// ),
// ),
// ],
// ),
// ),
// const SizedBox(height: 20),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// children: [
// ElevatedButton.icon(
// onPressed: isTrainingRunning ? null : _startTraining,
// icon: const Icon(Icons.play_arrow),
// label: const Text('Start'),
// style: ElevatedButton.styleFrom(
// backgroundColor: AppTheme.oneDarkGreen,
// foregroundColor: AppTheme.oneDarkTextWeak,
// ),
// ),
// ElevatedButton.icon(
// onPressed: !isTrainingRunning ? null : _completeSet,
// icon: const Icon(Icons.check_circle),
// label: const Text('Satz'),
// style: ElevatedButton.styleFrom(
// backgroundColor: AppTheme.oneDarkPrimary,
// foregroundColor: AppTheme.oneDarkTextWeak,
// ),
// ),
// ElevatedButton.icon(
// onPressed: !isTrainingRunning ? null : _finishTraining,
// icon: const Icon(Icons.stop),
// label: const Text('Beenden'),
// style: ElevatedButton.styleFrom(
// backgroundColor: AppTheme.oneDarkRed,
// foregroundColor: AppTheme.oneDarkTextWeak,
// ),
// ),
// ],
// ),
// const SizedBox(height: 20),
// const Divider(),
// Text(
// "Satz-Historie",
// style: Theme.of(
// context,
// ).textTheme.titleLarge?.copyWith(color: AppTheme.oneDarkAccent),
// ),
// const SizedBox(height: 10),
// CustomCard(
// child: SizedBox(
// height: 150,
// child: trainingState.setTimes.isEmpty
// ? const Center(
// child: Text(
// "Noch keine Sätze in dieser Einheit.",
// style: TextStyle(color: AppTheme.oneDarkTextWeak),
// ),
// )
// : ListView.builder(
// itemCount: trainingState.setTimes.length,
// itemBuilder: (context, index) {
// final setTime = trainingState.setTimes[index];
// return Center(
// child: Text(
// '#${index + 1} um ${DateFormat.Hms().format(setTime)} (${trainingState.currentReps} Reps)',
// style: const TextStyle(
// fontSize: 16,
// color: AppTheme.oneDarkGreen,
// ),
// ),
// );
// },
// ),
// ),
// ),
// ],
// ),
// ),
// );
}