feat: add assisted exercise an next set preview
This commit is contained in:
parent
44f5703de4
commit
79a7e1c50d
10 changed files with 534 additions and 270 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -43,3 +43,7 @@ app.*.map.json
|
||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
.env.development
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,11 @@ void main() async {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await dotenv.load(fileName: '.env');
|
await dotenv.load(fileName: '.env');
|
||||||
debugPrint('✅ Environment loaded: ${dotenv.env['ENVIRONMENT']}');
|
debugPrint('Environment loaded: ${dotenv.env['ENVIRONMENT']}');
|
||||||
debugPrint('✅ API URL: ${dotenv.env['API_BASE_URL']}');
|
debugPrint('API URL: ${dotenv.env['API_BASE_URL']}');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('⚠️ Could not load .env file: $e');
|
debugPrint('Could not load .env file: $e');
|
||||||
debugPrint('⚠️ Using default production values');
|
debugPrint('Using default production values');
|
||||||
}
|
}
|
||||||
|
|
||||||
await SystemChrome.setPreferredOrientations([
|
await SystemChrome.setPreferredOrientations([
|
||||||
|
|
|
||||||
|
|
@ -425,199 +425,204 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
||||||
),
|
),
|
||||||
body: _isLoading
|
body: _isLoading
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: ListView(
|
: SafeArea(
|
||||||
padding: const EdgeInsets.all(16),
|
child: ListView(
|
||||||
children: [
|
padding: const EdgeInsets.all(16),
|
||||||
Center(
|
children: [
|
||||||
child: Stack(
|
Center(
|
||||||
children: [
|
child: Stack(
|
||||||
AvatarRenderer(
|
children: [
|
||||||
config: avatarConfig,
|
AvatarRenderer(
|
||||||
size: 120,
|
config: avatarConfig,
|
||||||
),
|
size: 120,
|
||||||
Positioned(
|
),
|
||||||
bottom: 0,
|
Positioned(
|
||||||
right: 0,
|
bottom: 0,
|
||||||
child: CircleAvatar(
|
right: 0,
|
||||||
backgroundColor: AppTheme.surfaceColor,
|
child: CircleAvatar(
|
||||||
radius: 18,
|
backgroundColor: AppTheme.surfaceColor,
|
||||||
child: IconButton(
|
radius: 18,
|
||||||
icon: const Icon(Icons.edit, size: 16),
|
child: IconButton(
|
||||||
onPressed: _showAvatarEditor,
|
icon: const Icon(Icons.edit, size: 16),
|
||||||
|
onPressed: _showAvatarEditor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 32),
|
||||||
const SizedBox(height: 32),
|
Center(
|
||||||
Center(
|
child: OutlinedButton.icon(
|
||||||
child: OutlinedButton.icon(
|
onPressed: _showBackgroundSelector,
|
||||||
onPressed: _showBackgroundSelector,
|
icon: const Icon(Icons.landscape),
|
||||||
icon: const Icon(Icons.landscape),
|
label: const Text('CHANGE SCENERY'),
|
||||||
label: const Text('CHANGE SCENERY'),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 32),
|
||||||
const SizedBox(height: 32),
|
Text('Physical Stats',
|
||||||
Text('Physical Stats',
|
style: Theme.of(context)
|
||||||
style: Theme.of(context)
|
.textTheme
|
||||||
.textTheme
|
.titleLarge
|
||||||
.titleLarge
|
?.copyWith(color: AppTheme.textPrimary)),
|
||||||
?.copyWith(color: AppTheme.textPrimary)),
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 16),
|
Card(
|
||||||
Card(
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.all(16),
|
||||||
padding: const EdgeInsets.all(16),
|
child: Column(
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Text('Current Bodyweight',
|
||||||
Text('Current Bodyweight',
|
style: Theme.of(context).textTheme.bodyMedium),
|
||||||
style: Theme.of(context).textTheme.bodyMedium),
|
Row(
|
||||||
Row(
|
children: [
|
||||||
children: [
|
Expanded(
|
||||||
Expanded(
|
child: Slider(
|
||||||
child: Slider(
|
value: _currentBodyweight,
|
||||||
value: _currentBodyweight,
|
min: 40,
|
||||||
min: 40,
|
max: 150,
|
||||||
max: 150,
|
divisions: 220,
|
||||||
divisions: 220,
|
label: _currentBodyweight.toStringAsFixed(1),
|
||||||
label: _currentBodyweight.toStringAsFixed(1),
|
activeColor: AppTheme.primaryColor,
|
||||||
activeColor: AppTheme.primaryColor,
|
onChanged: (val) => setState(() {
|
||||||
onChanged: (val) => setState(() {
|
_currentBodyweight = val;
|
||||||
_currentBodyweight = val;
|
_hasChanges = true;
|
||||||
_hasChanges = true;
|
}),
|
||||||
}),
|
),
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
Text(
|
'${_currentBodyweight.toStringAsFixed(1)} kg',
|
||||||
'${_currentBodyweight.toStringAsFixed(1)} kg',
|
style: Theme.of(context)
|
||||||
style: Theme.of(context)
|
.textTheme
|
||||||
.textTheme
|
.titleMedium
|
||||||
.titleMedium
|
?.copyWith(
|
||||||
?.copyWith(
|
fontWeight: FontWeight.bold,
|
||||||
fontWeight: FontWeight.bold,
|
color: AppTheme.primaryColor,
|
||||||
color: AppTheme.primaryColor,
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
Text('Training Focus',
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.titleLarge
|
|
||||||
?.copyWith(color: AppTheme.textPrimary)),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('Accessory Template',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
_buildTemplateSelector(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text('Account Security',
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.titleLarge
|
|
||||||
?.copyWith(color: AppTheme.textPrimary)),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.lock_outline),
|
|
||||||
title: const Text('Change Password'),
|
|
||||||
trailing: const Icon(Icons.chevron_right),
|
|
||||||
onTap: _showChangePasswordDialog,
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
Text('Danger Zone',
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.titleLarge
|
|
||||||
?.copyWith(color: AppTheme.errorColor)),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(
|
|
||||||
color: AppTheme.errorColor.withValues(alpha: 0.5)),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
color: AppTheme.errorColor.withValues(alpha: 0.05),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.refresh,
|
|
||||||
color: AppTheme.errorColor),
|
|
||||||
title: const Text('Reset Progress',
|
|
||||||
style: TextStyle(color: AppTheme.errorColor)),
|
|
||||||
subtitle:
|
|
||||||
const Text('Resets Level, XP and Training History'),
|
|
||||||
onTap: () => _confirmDangerAction(
|
|
||||||
'Reset Progress?',
|
|
||||||
'This will delete all your workouts and reset your Level to 1. This cannot be undone.',
|
|
||||||
() async {
|
|
||||||
setState(() => _isLoading = true);
|
|
||||||
await userRepo.resetProgress();
|
|
||||||
if (mounted) {
|
|
||||||
setState(() => _isLoading = false);
|
|
||||||
context.go('/hub');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
),
|
||||||
ListTile(
|
),
|
||||||
leading: const Icon(Icons.delete_forever,
|
const SizedBox(height: 32),
|
||||||
color: AppTheme.errorColor),
|
Text('Training Focus',
|
||||||
title: const Text('Delete Account',
|
style: Theme.of(context)
|
||||||
style: TextStyle(color: AppTheme.errorColor)),
|
.textTheme
|
||||||
subtitle: const Text(
|
.titleLarge
|
||||||
'Permanently delete your account and data'),
|
?.copyWith(color: AppTheme.textPrimary)),
|
||||||
onTap: () => _confirmDangerAction(
|
const SizedBox(height: 16),
|
||||||
'Delete Account?',
|
Card(
|
||||||
'Are you sure you want to delete your account? All data will be lost forever.',
|
child: Padding(
|
||||||
() async {
|
padding: const EdgeInsets.all(16),
|
||||||
setState(() => _isLoading = true);
|
child: Column(
|
||||||
try {
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
await userRepo.deleteAccount();
|
children: [
|
||||||
if (mounted) context.go('/login');
|
Text('Accessory Template',
|
||||||
} catch (e) {
|
style: Theme.of(context).textTheme.bodyMedium),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildTemplateSelector(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text('Account Security',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleLarge
|
||||||
|
?.copyWith(color: AppTheme.textPrimary)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.lock_outline),
|
||||||
|
title: const Text('Change Password'),
|
||||||
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
onTap: _showChangePasswordDialog,
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Text('Danger Zone',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleLarge
|
||||||
|
?.copyWith(color: AppTheme.errorColor)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: AppTheme.errorColor.withValues(alpha: 0.5)),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: AppTheme.errorColor.withValues(alpha: 0.05),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.refresh,
|
||||||
|
color: AppTheme.errorColor),
|
||||||
|
title: const Text('Reset Progress',
|
||||||
|
style: TextStyle(color: AppTheme.errorColor)),
|
||||||
|
subtitle: const Text(
|
||||||
|
'Resets Level, XP and Training History'),
|
||||||
|
onTap: () => _confirmDangerAction(
|
||||||
|
'Reset Progress?',
|
||||||
|
'This will delete all your workouts and reset your Level to 1. This cannot be undone.',
|
||||||
|
() async {
|
||||||
|
setState(() => _isLoading = true);
|
||||||
|
await userRepo.resetProgress();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _isLoading = false);
|
setState(() => _isLoading = false);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
context.go('/hub');
|
||||||
SnackBar(content: Text('Error: $e')),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
),
|
const Divider(height: 1),
|
||||||
],
|
ListTile(
|
||||||
|
leading: const Icon(Icons.delete_forever,
|
||||||
|
color: AppTheme.errorColor),
|
||||||
|
title: const Text('Delete Account',
|
||||||
|
style: TextStyle(color: AppTheme.errorColor)),
|
||||||
|
subtitle: const Text(
|
||||||
|
'Permanently delete your account and data'),
|
||||||
|
onTap: () => _confirmDangerAction(
|
||||||
|
'Delete Account?',
|
||||||
|
'Are you sure you want to delete your account? All data will be lost forever.',
|
||||||
|
() async {
|
||||||
|
setState(() => _isLoading = true);
|
||||||
|
try {
|
||||||
|
await userRepo.deleteAccount();
|
||||||
|
if (mounted) context.go('/login');
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _isLoading = false);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Error: $e')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 32),
|
||||||
const SizedBox(height: 32),
|
OutlinedButton.icon(
|
||||||
OutlinedButton.icon(
|
onPressed: () async {
|
||||||
onPressed: () async {
|
await userRepo.logout();
|
||||||
await userRepo.logout();
|
if (mounted) context.go('/login');
|
||||||
if (mounted) context.go('/login');
|
},
|
||||||
},
|
icon: const Icon(Icons.logout),
|
||||||
icon: const Icon(Icons.logout),
|
label: const Text('LOGOUT'),
|
||||||
label: const Text('LOGOUT'),
|
style: OutlinedButton.styleFrom(
|
||||||
style: OutlinedButton.styleFrom(
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(
|
||||||
],
|
height: 50,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ class _StrengthTestScreenState extends ConsumerState<StrengthTestScreen> {
|
||||||
Map<String, double> _calculated1RMs = {};
|
Map<String, double> _calculated1RMs = {};
|
||||||
Map<String, double> _calculatedTMs = {};
|
Map<String, double> _calculatedTMs = {};
|
||||||
|
|
||||||
|
bool _isAssistedPull = false;
|
||||||
|
bool _isAssistedDip = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
@ -52,16 +55,27 @@ class _StrengthTestScreenState extends ConsumerState<StrengthTestScreen> {
|
||||||
void _calculateAll() {
|
void _calculateAll() {
|
||||||
final bodyweight = ref.read(onboardingDataProvider)['bodyweight'] ?? 80.0;
|
final bodyweight = ref.read(onboardingDataProvider)['bodyweight'] ?? 80.0;
|
||||||
|
|
||||||
|
// Squat bleibt gleich...
|
||||||
final squatWeight = double.tryParse(_squatWeightController.text) ?? 0;
|
final squatWeight = double.tryParse(_squatWeightController.text) ?? 0;
|
||||||
final squatReps = int.tryParse(_squatRepsController.text) ?? 1;
|
final squatReps = int.tryParse(_squatRepsController.text) ?? 1;
|
||||||
final squat1RM = WendlerCalculator.calculate1RM(squatWeight, squatReps);
|
final squat1RM = WendlerCalculator.calculate1RM(squatWeight, squatReps);
|
||||||
final squatTM = WendlerCalculator.calculateTrainingMax(squat1RM);
|
final squatTM = WendlerCalculator.calculateTrainingMax(squat1RM);
|
||||||
|
|
||||||
|
// PULL CALCULATION (Angepasst)
|
||||||
double pull1RM = 0.0;
|
double pull1RM = 0.0;
|
||||||
if (_canDoPullup) {
|
if (_canDoPullup) {
|
||||||
final added = double.tryParse(_pullWeightController.text) ?? 0;
|
final inputWeight = double.tryParse(_pullWeightController.text) ?? 0;
|
||||||
final reps = int.tryParse(_pullRepsController.text) ?? 1;
|
final reps = int.tryParse(_pullRepsController.text) ?? 1;
|
||||||
pull1RM = WendlerCalculator.calculate1RM(bodyweight + added, reps);
|
|
||||||
|
// LOGIK: Assisted vs Weighted
|
||||||
|
double totalLoad;
|
||||||
|
if (_isAssistedPull) {
|
||||||
|
totalLoad = (bodyweight - inputWeight).clamp(0.0, double.infinity);
|
||||||
|
} else {
|
||||||
|
totalLoad = bodyweight + inputWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
pull1RM = WendlerCalculator.calculate1RM(totalLoad, reps);
|
||||||
} else {
|
} else {
|
||||||
final weight = double.tryParse(_pullWeightController.text) ?? 0;
|
final weight = double.tryParse(_pullWeightController.text) ?? 0;
|
||||||
final reps = int.tryParse(_pullRepsController.text) ?? 1;
|
final reps = int.tryParse(_pullRepsController.text) ?? 1;
|
||||||
|
|
@ -69,11 +83,21 @@ class _StrengthTestScreenState extends ConsumerState<StrengthTestScreen> {
|
||||||
}
|
}
|
||||||
final pullTM = WendlerCalculator.calculateTrainingMax(pull1RM);
|
final pullTM = WendlerCalculator.calculateTrainingMax(pull1RM);
|
||||||
|
|
||||||
|
// PUSH CALCULATION (Angepasst)
|
||||||
double push1RM = 0.0;
|
double push1RM = 0.0;
|
||||||
if (_canDoDip) {
|
if (_canDoDip) {
|
||||||
final added = double.tryParse(_dipWeightController.text) ?? 0;
|
final inputWeight = double.tryParse(_dipWeightController.text) ?? 0;
|
||||||
final reps = int.tryParse(_pushRepsController.text) ?? 1;
|
final reps = int.tryParse(_pushRepsController.text) ?? 1;
|
||||||
push1RM = WendlerCalculator.calculate1RM(bodyweight + added, reps);
|
|
||||||
|
// LOGIK: Assisted vs Weighted
|
||||||
|
double totalLoad;
|
||||||
|
if (_isAssistedDip) {
|
||||||
|
totalLoad = (bodyweight - inputWeight).clamp(0.0, double.infinity);
|
||||||
|
} else {
|
||||||
|
totalLoad = bodyweight + inputWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
push1RM = WendlerCalculator.calculate1RM(totalLoad, reps);
|
||||||
} else {
|
} else {
|
||||||
final weight = double.tryParse(_benchWeightController.text) ?? 0;
|
final weight = double.tryParse(_benchWeightController.text) ?? 0;
|
||||||
final reps = int.tryParse(_pushRepsController.text) ?? 1;
|
final reps = int.tryParse(_pushRepsController.text) ?? 1;
|
||||||
|
|
@ -95,6 +119,52 @@ class _StrengthTestScreenState extends ConsumerState<StrengthTestScreen> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// void _calculateAll() {
|
||||||
|
// final bodyweight = ref.read(onboardingDataProvider)['bodyweight'] ?? 80.0;
|
||||||
|
|
||||||
|
// final squatWeight = double.tryParse(_squatWeightController.text) ?? 0;
|
||||||
|
// final squatReps = int.tryParse(_squatRepsController.text) ?? 1;
|
||||||
|
// final squat1RM = WendlerCalculator.calculate1RM(squatWeight, squatReps);
|
||||||
|
// final squatTM = WendlerCalculator.calculateTrainingMax(squat1RM);
|
||||||
|
|
||||||
|
// double pull1RM = 0.0;
|
||||||
|
// if (_canDoPullup) {
|
||||||
|
// final added = double.tryParse(_pullWeightController.text) ?? 0;
|
||||||
|
// final reps = int.tryParse(_pullRepsController.text) ?? 1;
|
||||||
|
// pull1RM = WendlerCalculator.calculate1RM(bodyweight + added, reps);
|
||||||
|
// } else {
|
||||||
|
// final weight = double.tryParse(_pullWeightController.text) ?? 0;
|
||||||
|
// final reps = int.tryParse(_pullRepsController.text) ?? 1;
|
||||||
|
// pull1RM = WendlerCalculator.calculate1RM(weight, reps);
|
||||||
|
// }
|
||||||
|
// final pullTM = WendlerCalculator.calculateTrainingMax(pull1RM);
|
||||||
|
|
||||||
|
// double push1RM = 0.0;
|
||||||
|
// if (_canDoDip) {
|
||||||
|
// final added = double.tryParse(_dipWeightController.text) ?? 0;
|
||||||
|
// final reps = int.tryParse(_pushRepsController.text) ?? 1;
|
||||||
|
// push1RM = WendlerCalculator.calculate1RM(bodyweight + added, reps);
|
||||||
|
// } else {
|
||||||
|
// final weight = double.tryParse(_benchWeightController.text) ?? 0;
|
||||||
|
// final reps = int.tryParse(_pushRepsController.text) ?? 1;
|
||||||
|
// push1RM = WendlerCalculator.calculate1RM(weight, reps);
|
||||||
|
// }
|
||||||
|
// final pushTM = WendlerCalculator.calculateTrainingMax(push1RM);
|
||||||
|
|
||||||
|
// setState(() {
|
||||||
|
// _calculated1RMs = {
|
||||||
|
// 'squat': squat1RM,
|
||||||
|
// 'pullup': pull1RM,
|
||||||
|
// 'dip': push1RM,
|
||||||
|
// };
|
||||||
|
// _calculatedTMs = {
|
||||||
|
// 'squat': squatTM,
|
||||||
|
// 'pullup': pullTM,
|
||||||
|
// 'dip': pushTM,
|
||||||
|
// };
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
void _handleContinue() {
|
void _handleContinue() {
|
||||||
if (!_formKey.currentState!.validate()) return;
|
if (!_formKey.currentState!.validate()) return;
|
||||||
|
|
||||||
|
|
@ -171,12 +241,24 @@ class _StrengthTestScreenState extends ConsumerState<StrengthTestScreen> {
|
||||||
_calculateAll();
|
_calculateAll();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
isAssisted: _isAssistedPull,
|
||||||
|
onToggleAssisted: (val) {
|
||||||
|
setState(() {
|
||||||
|
_isAssistedPull = val;
|
||||||
|
_calculateAll();
|
||||||
|
});
|
||||||
|
},
|
||||||
weightController: _pullWeightController,
|
weightController: _pullWeightController,
|
||||||
repsController: _pullRepsController,
|
repsController: _pullRepsController,
|
||||||
weightLabel:
|
weightLabel: _canDoPullup
|
||||||
_canDoPullup ? 'Add. Weight (kg)' : 'Row Weight (kg)',
|
? (_isAssistedPull
|
||||||
|
? 'Band Assistance (kg)'
|
||||||
|
: 'Added Weight (kg)')
|
||||||
|
: 'Row Weight (kg)',
|
||||||
|
// weightLabel:
|
||||||
|
// _canDoPullup ? 'Add. Weight (kg)' : 'Row Weight (kg)',
|
||||||
repsLabel: _canDoPullup ? 'Reps' : '5RM Reps (usually 5)',
|
repsLabel: _canDoPullup ? 'Reps' : '5RM Reps (usually 5)',
|
||||||
showResults: _canDoPullup || true,
|
showResults: true,
|
||||||
result1RM: _calculated1RMs['pullup'] ?? 0,
|
result1RM: _calculated1RMs['pullup'] ?? 0,
|
||||||
resultTM: _calculatedTMs['pullup'] ?? 0,
|
resultTM: _calculatedTMs['pullup'] ?? 0,
|
||||||
onChanged: _calculateAll,
|
onChanged: _calculateAll,
|
||||||
|
|
@ -196,10 +278,22 @@ class _StrengthTestScreenState extends ConsumerState<StrengthTestScreen> {
|
||||||
_calculateAll();
|
_calculateAll();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
isAssisted: _isAssistedDip,
|
||||||
|
onToggleAssisted: (val) {
|
||||||
|
setState(() {
|
||||||
|
_isAssistedDip = val;
|
||||||
|
_calculateAll();
|
||||||
|
});
|
||||||
|
},
|
||||||
weightController:
|
weightController:
|
||||||
_canDoDip ? _dipWeightController : _benchWeightController,
|
_canDoDip ? _dipWeightController : _benchWeightController,
|
||||||
repsController: _pushRepsController,
|
repsController: _pushRepsController,
|
||||||
weightLabel: _canDoDip ? 'Add. Weight (kg)' : 'Weight (kg)',
|
weightLabel: _canDoDip
|
||||||
|
? (_isAssistedDip
|
||||||
|
? 'Band Assistance (kg)'
|
||||||
|
: 'Added Weight (kg)')
|
||||||
|
: 'Weight (kg)',
|
||||||
|
// weightLabel: _canDoDip ? 'Add. Weight (kg)' : 'Weight (kg)',
|
||||||
repsLabel: 'Reps',
|
repsLabel: 'Reps',
|
||||||
showWeightInput: true,
|
showWeightInput: true,
|
||||||
showResults: true,
|
showResults: true,
|
||||||
|
|
@ -295,7 +389,10 @@ class _ExerciseCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Text(exerciseName,
|
Text(exerciseName,
|
||||||
style: Theme.of(context).textTheme.titleLarge),
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleLarge
|
||||||
|
?.copyWith(color: AppTheme.textPrimary)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
@ -357,6 +454,8 @@ class _AdaptiveExerciseCard extends StatelessWidget {
|
||||||
final double result1RM;
|
final double result1RM;
|
||||||
final double resultTM;
|
final double resultTM;
|
||||||
final VoidCallback onChanged;
|
final VoidCallback onChanged;
|
||||||
|
final bool isAssisted;
|
||||||
|
final ValueChanged<bool>? onToggleAssisted;
|
||||||
|
|
||||||
const _AdaptiveExerciseCard({
|
const _AdaptiveExerciseCard({
|
||||||
required this.slotTitle,
|
required this.slotTitle,
|
||||||
|
|
@ -374,6 +473,8 @@ class _AdaptiveExerciseCard extends StatelessWidget {
|
||||||
required this.result1RM,
|
required this.result1RM,
|
||||||
required this.resultTM,
|
required this.resultTM,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
|
this.isAssisted = false,
|
||||||
|
this.onToggleAssisted,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -384,14 +485,15 @@ class _AdaptiveExerciseCard extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||||
|
Text(slotTitle.toUpperCase(),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: AppTheme.textSecondary,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold)),
|
||||||
|
]),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
children: [
|
||||||
Text(slotTitle.toUpperCase(),
|
|
||||||
style: const TextStyle(
|
|
||||||
color: AppTheme.textSecondary,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold)),
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text('Can do 1 rep?',
|
Text('Can do 1 rep?',
|
||||||
|
|
@ -407,6 +509,24 @@ class _AdaptiveExerciseCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
if (isCapable && onToggleAssisted != null)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text('Assisted (Bands)?',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: isAssisted
|
||||||
|
? AppTheme.primaryColor
|
||||||
|
: Colors.grey)),
|
||||||
|
Switch(
|
||||||
|
value: isAssisted,
|
||||||
|
activeThumbColor: AppTheme.primaryColor,
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
onChanged: onToggleAssisted,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
|
|
@ -421,13 +541,16 @@ class _AdaptiveExerciseCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Text(isCapable ? primaryName : secondaryName,
|
Text(isCapable ? primaryName : secondaryName,
|
||||||
style: Theme.of(context).textTheme.titleLarge),
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleLarge
|
||||||
|
?.copyWith(color: AppTheme.textPrimary)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (!isCapable) ...[
|
if (!isCapable) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Adjusted Strategy: ${isCapable ? "Wendler 5/3/1" : "Linear Progression (3x5)"}',
|
'Adjusted: ${"Wendler 5/3/1"}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: AppTheme.secondaryColor,
|
color: AppTheme.secondaryColor,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
|
|
||||||
|
|
@ -352,10 +352,14 @@ class _CurrentCycleCard extends StatelessWidget {
|
||||||
Text('Current Training Maxes (TM)',
|
Text('Current Training Maxes (TM)',
|
||||||
style: Theme.of(context).textTheme.labelLarge),
|
style: Theme.of(context).textTheme.labelLarge),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_StatRow(label: 'Squat', value: '${tms['squat']} kg'),
|
|
||||||
_StatRow(
|
_StatRow(
|
||||||
label: getLabel(pullVariant), value: '${tms['pullup']} kg'),
|
label: 'Squat', value: '${tms['squat'].toStringAsFixed(2)} kg'),
|
||||||
_StatRow(label: getLabel(pushVariant), value: '${tms['dip']} kg'),
|
_StatRow(
|
||||||
|
label: getLabel(pullVariant),
|
||||||
|
value: '${tms['pullup'].toStringAsFixed(2)} kg'),
|
||||||
|
_StatRow(
|
||||||
|
label: getLabel(pushVariant),
|
||||||
|
value: '${tms['dip'].toStringAsFixed(2)} kg'),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
|
|
@ -468,15 +472,15 @@ class _DiffRow extends StatelessWidget {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: Text(name)),
|
Expanded(child: Text(name)),
|
||||||
Text('${oldVal.toStringAsFixed(1)} → ',
|
Text('${oldVal.toStringAsFixed(2)} → ',
|
||||||
style: const TextStyle(color: Colors.grey)),
|
style: const TextStyle(color: Colors.grey)),
|
||||||
Text(
|
Text(
|
||||||
newVal.toStringAsFixed(1),
|
newVal.toStringAsFixed(2),
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
if (isPositive)
|
if (isPositive)
|
||||||
Text('+${diff.toStringAsFixed(1)}',
|
Text('+${diff.toStringAsFixed(2)}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: AppTheme.successColor, fontWeight: FontWeight.bold))
|
color: AppTheme.successColor, fontWeight: FontWeight.bold))
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -67,22 +67,14 @@ class WorkoutGeneratorService {
|
||||||
List<WorkoutSet> sets;
|
List<WorkoutSet> sets;
|
||||||
|
|
||||||
if (isMain) {
|
if (isMain) {
|
||||||
if (type == ExerciseType.row || type == ExerciseType.bench) {
|
sets = WendlerCalculator.generateSets(
|
||||||
sets = WendlerCalculator.generateLinearSets(
|
week: week,
|
||||||
trainingMax: tm,
|
trainingMax: tm,
|
||||||
exerciseType: type,
|
exerciseType: type,
|
||||||
currentBodyweight: user.currentBodyweight);
|
currentBodyweight: user.currentBodyweight,
|
||||||
} else {
|
);
|
||||||
sets = WendlerCalculator.generateSets(
|
|
||||||
week: week,
|
|
||||||
trainingMax: tm,
|
|
||||||
exerciseType: type,
|
|
||||||
currentBodyweight: user.currentBodyweight,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (week == 4) return;
|
if (week == 4) return;
|
||||||
if (type == ExerciseType.row || type == ExerciseType.bench) return;
|
|
||||||
|
|
||||||
sets = WendlerCalculator.generateFSLSets(
|
sets = WendlerCalculator.generateFSLSets(
|
||||||
trainingMax: tm,
|
trainingMax: tm,
|
||||||
|
|
@ -156,8 +148,8 @@ class WorkoutGeneratorService {
|
||||||
weight: calculateWeight(squatTm, 0.4)));
|
weight: calculateWeight(squatTm, 0.4)));
|
||||||
|
|
||||||
accessories.add(_createIntervalExercise(
|
accessories.add(_createIntervalExercise(
|
||||||
id: 'kb_swing',
|
id: 'kb_snatch_acc',
|
||||||
name: '2H KB Swing',
|
name: 'KB Snatch',
|
||||||
sets: 10,
|
sets: 10,
|
||||||
intervalSeconds: 60,
|
intervalSeconds: 60,
|
||||||
repsPerSet: 10));
|
repsPerSet: 10));
|
||||||
|
|
@ -178,8 +170,8 @@ class WorkoutGeneratorService {
|
||||||
weight: calculateWeight(pullupTm, 0.2)));
|
weight: calculateWeight(pullupTm, 0.2)));
|
||||||
|
|
||||||
accessories.add(_createIntervalExercise(
|
accessories.add(_createIntervalExercise(
|
||||||
id: 'kb_snatch_acc',
|
id: 'kb_swing',
|
||||||
name: 'KB Snatch',
|
name: '2H KB Swing',
|
||||||
sets: 10,
|
sets: 10,
|
||||||
intervalSeconds: 60,
|
intervalSeconds: 60,
|
||||||
repsPerSet: 5));
|
repsPerSet: 5));
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import '../widgets/plate_visualizer.dart';
|
||||||
import '../widgets/enemy_hp_bar.dart';
|
import '../widgets/enemy_hp_bar.dart';
|
||||||
import '../../../gamification/application/quest_service.dart';
|
import '../../../gamification/application/quest_service.dart';
|
||||||
import '../widgets/emom_timer_widget.dart';
|
import '../widgets/emom_timer_widget.dart';
|
||||||
|
import '../widgets/timer_widget.dart';
|
||||||
|
|
||||||
class BattleScreen extends ConsumerStatefulWidget {
|
class BattleScreen extends ConsumerStatefulWidget {
|
||||||
final int week;
|
final int week;
|
||||||
|
|
@ -615,7 +616,7 @@ class _BattleScreenState extends ConsumerState<BattleScreen> {
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: _isResting
|
child: _isResting
|
||||||
? _buildRestScreen()
|
? _buildRestScreen(inventory)
|
||||||
: _buildWorkoutScreen(currentExercise, currentSet,
|
: _buildWorkoutScreen(currentExercise, currentSet,
|
||||||
plateResult, completedHP, totalHP),
|
plateResult, completedHP, totalHP),
|
||||||
),
|
),
|
||||||
|
|
@ -626,7 +627,20 @@ class _BattleScreenState extends ConsumerState<BattleScreen> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRestScreen() {
|
Widget _buildRestScreen(Map<String, dynamic> inventory) {
|
||||||
|
WorkoutSet? nextSet;
|
||||||
|
Exercise? nextExerciseInfo;
|
||||||
|
|
||||||
|
if (_currentSetIndex + 1 < _exercises[_currentExerciseIndex].sets.length) {
|
||||||
|
nextExerciseInfo = _exercises[_currentExerciseIndex];
|
||||||
|
nextSet = nextExerciseInfo.sets[_currentSetIndex + 1];
|
||||||
|
} else if (_currentExerciseIndex + 1 < _exercises.length) {
|
||||||
|
nextExerciseInfo = _exercises[_currentExerciseIndex + 1];
|
||||||
|
if (nextExerciseInfo.sets.isNotEmpty) {
|
||||||
|
nextSet = nextExerciseInfo.sets.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
|
|
@ -639,51 +653,151 @@ class _BattleScreenState extends ConsumerState<BattleScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: SingleChildScrollView(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
padding: const EdgeInsets.all(16),
|
||||||
children: [
|
child: Column(
|
||||||
Text(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
'REST',
|
mainAxisSize: MainAxisSize.min,
|
||||||
style: Theme.of(context).textTheme.displayLarge,
|
children: [
|
||||||
),
|
Text(
|
||||||
const SizedBox(height: 32),
|
'REST',
|
||||||
SizedBox(
|
style: Theme.of(context).textTheme.displayLarge,
|
||||||
width: 200,
|
|
||||||
height: 200,
|
|
||||||
child: Stack(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 200,
|
|
||||||
height: 200,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
value: _restSeconds / 180,
|
|
||||||
strokeWidth: 12,
|
|
||||||
backgroundColor: AppTheme.xpBarBackground,
|
|
||||||
color: AppTheme.primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
_formatTime(_restSeconds),
|
|
||||||
style: Theme.of(context).textTheme.displayLarge?.copyWith(
|
|
||||||
fontSize: 48,
|
|
||||||
color: AppTheme.primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 20),
|
||||||
const SizedBox(height: 48),
|
SizedBox(
|
||||||
ElevatedButton(
|
width: 200,
|
||||||
onPressed: _skipRest,
|
height: 200,
|
||||||
child: const Text('SKIP REST'),
|
child: Stack(
|
||||||
),
|
alignment: Alignment.center,
|
||||||
],
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 200,
|
||||||
|
height: 200,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
value: _restSeconds / 180,
|
||||||
|
strokeWidth: 12,
|
||||||
|
backgroundColor: AppTheme.xpBarBackground,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
_formatTime(_restSeconds),
|
||||||
|
style: Theme.of(context).textTheme.displayLarge?.copyWith(
|
||||||
|
fontSize: 32,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _skipRest,
|
||||||
|
child: const Text('SKIP REST'),
|
||||||
|
),
|
||||||
|
if (nextSet != null && nextExerciseInfo != null) ...[
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
const Divider(color: Colors.white10, endIndent: 32, indent: 32),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
'UP NEXT: ${nextExerciseInfo.exerciseName.toUpperCase()}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: 1.2),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'${nextSet.repsTarget} x ${nextSet.targetWeightTotal > 0 ? "${nextSet.targetWeightTotal} kg" : "Bodyweight"}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
if (nextSet.targetWeightTotal > 0)
|
||||||
|
_buildNextSetPlates(nextExerciseInfo, nextSet, inventory),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildNextSetPlates(
|
||||||
|
Exercise exercise, WorkoutSet set, Map<String, dynamic> inventory) {
|
||||||
|
final isTwoSided = exercise.exerciseId == 'squat' ||
|
||||||
|
exercise.exerciseId == 'row' ||
|
||||||
|
exercise.exerciseId == 'bench' ||
|
||||||
|
exercise.exerciseId == 'rdl' ||
|
||||||
|
exercise.exerciseId == 'ohp' ||
|
||||||
|
exercise.exerciseId == 'curl';
|
||||||
|
|
||||||
|
if (!isTwoSided) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
final barWeight = (inventory['bar_weight'] as num?)?.toDouble() ?? 20.0;
|
||||||
|
final platesList = (inventory['plates'] as List?)
|
||||||
|
?.map((e) => (e as num).toDouble())
|
||||||
|
.toList() ??
|
||||||
|
[];
|
||||||
|
|
||||||
|
final plateResult = PlateCalculator.calculate(
|
||||||
|
targetWeight: set.targetWeightTotal,
|
||||||
|
barWeight: barWeight,
|
||||||
|
availablePlates: platesList,
|
||||||
|
availableBands: {},
|
||||||
|
isTwoSided: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 12.0),
|
||||||
|
child: PlateVisualizer(
|
||||||
|
plateConfiguration: plateResult.plateConfiguration,
|
||||||
|
isTwoSided: true,
|
||||||
|
exerciseName: '',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widget _buildNextSetPlates(
|
||||||
|
// Exercise exercise, WorkoutSet set, Map<String, dynamic> inventory) {
|
||||||
|
// final isTwoSided = exercise.exerciseId == 'squat' ||
|
||||||
|
// exercise.exerciseId == 'row' ||
|
||||||
|
// exercise.exerciseId == 'bench' ||
|
||||||
|
// exercise.exerciseId == 'rdl' ||
|
||||||
|
// exercise.exerciseId == 'ohp' ||
|
||||||
|
// exercise.exerciseId == 'curl';
|
||||||
|
|
||||||
|
// if (!isTwoSided) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
// final barWeight = (inventory['bar_weight'] as num?)?.toDouble() ?? 20.0;
|
||||||
|
// final platesList = (inventory['plates'] as List?)
|
||||||
|
// ?.map((e) => (e as num).toDouble())
|
||||||
|
// .toList() ??
|
||||||
|
// [];
|
||||||
|
|
||||||
|
// final plateResult = PlateCalculator.calculate(
|
||||||
|
// targetWeight: set.targetWeightTotal,
|
||||||
|
// barWeight: barWeight,
|
||||||
|
// availablePlates: platesList,
|
||||||
|
// availableBands: {},
|
||||||
|
// isTwoSided: true,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// return Padding(
|
||||||
|
// padding: const EdgeInsets.only(top: 12.0),
|
||||||
|
// child: SizedBox(
|
||||||
|
// height: 50,
|
||||||
|
// child: PlateVisualizer(
|
||||||
|
// plateConfiguration: plateResult.plateConfiguration,
|
||||||
|
// isTwoSided: true,
|
||||||
|
// exerciseName: '',
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
Widget _buildWorkoutScreen(
|
Widget _buildWorkoutScreen(
|
||||||
Exercise currentExercise,
|
Exercise currentExercise,
|
||||||
WorkoutSet currentSet,
|
WorkoutSet currentSet,
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,24 @@ class _EmomTimerWidgetState extends State<EmomTimerWidget>
|
||||||
_secondsRemaining = widget.intervalSeconds;
|
_secondsRemaining = widget.intervalSeconds;
|
||||||
_audioPlayer = AudioPlayer();
|
_audioPlayer = AudioPlayer();
|
||||||
|
|
||||||
|
_audioPlayer.setAudioContext(
|
||||||
|
AudioContext(
|
||||||
|
android: AudioContextAndroid(
|
||||||
|
isSpeakerphoneOn: false,
|
||||||
|
stayAwake: false,
|
||||||
|
contentType: AndroidContentType.sonification,
|
||||||
|
usageType: AndroidUsageType.notificationEvent,
|
||||||
|
audioFocus: AndroidAudioFocus.none,
|
||||||
|
),
|
||||||
|
// iOS: AudioContextIOS(
|
||||||
|
// category: AVAudioSessionCategory.ambient,
|
||||||
|
// options: [
|
||||||
|
// AVAudioSessionOptions.mixWithOthers,
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
_pulseController = AnimationController(
|
_pulseController = AnimationController(
|
||||||
vsync: this,
|
vsync: this,
|
||||||
duration: const Duration(milliseconds: 500),
|
duration: const Duration(milliseconds: 500),
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,7 @@ class PlateVisualizer extends StatelessWidget {
|
||||||
if (isTwoSided) _buildBarbellView() else _buildBeltView(),
|
if (isTwoSided) _buildBarbellView() else _buildBeltView(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'Total: ${plateConfiguration.fold<double>(0, (sum, p) => sum + p).toStringAsFixed(1)} kg ${isTwoSided ? 'per side' : ''}',
|
'Total: ${plateConfiguration.fold<double>(0, (sum, p) => sum + p).toStringAsFixed(2)} kg ${isTwoSided ? 'per side' : ''}',
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
color: AppTheme.primaryColor,
|
color: AppTheme.primaryColor,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,9 @@ class WendlerCalculator {
|
||||||
final rounded = _roundWeight(targetTotal, exerciseType);
|
final rounded = _roundWeight(targetTotal, exerciseType);
|
||||||
|
|
||||||
double plateWeight = 0;
|
double plateWeight = 0;
|
||||||
if (exerciseType != ExerciseType.squat) {
|
if (exerciseType != ExerciseType.squat ||
|
||||||
|
exerciseType != ExerciseType.row ||
|
||||||
|
exerciseType != ExerciseType.bench) {
|
||||||
plateWeight = max(0, rounded - currentBodyweight);
|
plateWeight = max(0, rounded - currentBodyweight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,7 +146,9 @@ class WendlerCalculator {
|
||||||
final rounded = _roundWeight(targetTotal, exerciseType);
|
final rounded = _roundWeight(targetTotal, exerciseType);
|
||||||
|
|
||||||
double plateWeight = 0;
|
double plateWeight = 0;
|
||||||
if (exerciseType != ExerciseType.squat) {
|
if (exerciseType != ExerciseType.squat ||
|
||||||
|
exerciseType != ExerciseType.row ||
|
||||||
|
exerciseType != ExerciseType.bench) {
|
||||||
plateWeight = max(0, rounded - currentBodyweight);
|
plateWeight = max(0, rounded - currentBodyweight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue