feat: add exercise informations and first l10n strings

This commit is contained in:
Patryk Hegenberg 2026-01-12 09:47:51 +01:00
parent 253f694424
commit bf9a54c8b1
8 changed files with 900 additions and 59 deletions

203
lib/l10n/app_de.arb Normal file
View file

@ -0,0 +1,203 @@
{
"enterTheArena": "BETRITT DIE ARENA",
"introText": "Die Eisengolems sind erwacht. Die Schwerkraft-Dämonen ziehen die Welt in den Abgrund.\n\nNur ein wahrer Streetlifter kann sie aufhalten. Bist du bereit, deinen Körper in eine Waffe zu schmieden?",
"featureArmorTitle": "Schmiede deine Rüstung",
"featureArmorDesc": "Progressive Overload basierend auf Wendler 5/3/1.",
"featureMonstersTitle": "Erschlage Monster",
"featureMonstersDesc": "Verwandle jede Wiederholung in Schaden gegen epische Feinde.",
"featureLootTitle": "Sammle Beute",
"featureLootDesc": "Verdiene XP, steige auf und schalte neue Ausrüstung frei.",
"beginJourney": "BEGINNE DEINE REISE",
"loginPrompt": "Schon ein Held? Hier einloggen",
"loginWelcomeBack": "WILLKOMMEN ZURÜCK",
"loginSubtitle": "Zeit für das nächste Level",
"loginErrorInvalid": "Ungültige E-Mail oder Passwort",
"loginErrorConnection": "Keine Verbindung zum Server.\nBitte prüfe deine Internetverbindung.",
"loginErrorTimeout": "Zeitüberschreitung.\nBitte versuche es erneut.",
"loginErrorGeneric": "Login fehlgeschlagen. Bitte erneut versuchen.",
"emailLabel": "E-Mail",
"emailEmptyError": "Bitte gib deine E-Mail ein",
"emailInvalidError": "Bitte gib eine gültige E-Mail ein",
"passwordLabel": "Passwort",
"passwordEmptyError": "Bitte gib dein Passwort ein",
"passwordLengthError": "Passwort muss mindestens 8 Zeichen haben",
"loginButton": "ANMELDEN",
"loginNoAccount": "Kein Account? ",
"loginRegisterButton": "REGISTRIEREN",
"registerTitle": "KONTO ERSTELLEN",
"registerSubtitle": "Beginne deine Reise",
"registerEmailHelper": "Wird für den Login verwendet",
"continueButton": "WEITER",
"registerHaveAccount": "Bereits registriert? ",
"registerLoginButton": "LOGIN",
"hubNoActiveCycle": "Kein aktiver Zyklus",
"hubCreateCycle": "Neuen Zyklus starten",
"hubCycleLabel": "Zyklus",
"hubActiveLabel": "Aktiv",
"hubActiveYes": "Ja",
"navHistory": "Historie",
"navInventory": "Inventar",
"navStats": "Statistik",
"navCodex": "Kodex",
"missionBriefingTitle": "MISSION BRIEFING",
"missionBriefingBody": "Der Feind flieht! Wir haben ein 20-Minuten-Fenster, um ihn abzufangen.",
"missionBriefingDensity": "Kampfdichte: {sets} Sätze",
"@missionBriefingDensity": {
"placeholders": {
"sets": {
"type": "int"
}
}
},
"missionBriefingInterval": "Intervall: Alle {seconds} Sekunden",
"@missionBriefingInterval": {
"placeholders": {
"seconds": {
"type": "String"
}
}
},
"missionBriefingHardcore": "⚠️ HARDCORE MODUS",
"abortButton": "ABBRECHEN",
"engageButton": "ANGREIFEN",
"inventoryTitle": "Ausrüstung verwalten",
"saveButton": "SPEICHERN",
"inventoryBarbellWeight": "Hantelstangengewicht",
"inventoryPresets": "Schnellwahl",
"inventoryPresetHome": "Home Gym",
"inventoryPresetCommercial": "Fitnessstudio",
"inventoryPresetMinimal": "Minimal",
"inventoryPlates": "Verfügbare Scheiben",
"inventoryBands": "Widerstandsbänder (Hilfe)",
"saveChangesButton": "ÄNDERUNGEN SPEICHERN",
"inventoryUpdatedSuccess": "Inventar erfolgreich aktualisiert",
"inventorySaveError": "Fehler beim Speichern: {error}",
"@inventorySaveError": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"statsTitle": "Statistik & Zyklen",
"statsProgressAnalysis": "Fortschrittsanalyse",
"statsCycleTitle": "ZYKLUS {number}",
"@statsCycleTitle": {
"placeholders": {
"number": {
"type": "int"
}
}
},
"statsCurrentTM": "Aktuelle Trainingsmaxima (TM)",
"statsFinishCycle": "ZYKLUS BEENDEN & LEVEL UP",
"statsCycleFinishedTitle": "Dungeon gesäubert!",
"statsCycleFinishedBody": "Du hast die Wächter dieses Zyklus besiegt. Doch tiefer im Dungeon warten stärkere Feinde...",
"statsTMIncreased": "Deine Trainingsmaxima wurden erhöht:",
"statsStalled": "STAGNIERT",
"statsEnterNextLevel": "NÄCHSTES LEVEL BETRETEN",
"historyTitle": "Quest Log",
"historyEmptyTitle": "Noch keine Quests abgeschlossen",
"historyEmptyBody": "Absolviere ein Training, um dein Journal zu füllen",
"historyUnknownWorkout": "Unbekanntes Training",
"battleWave": "WELLE {current} / {total}",
"@battleWave": {
"placeholders": {
"current": {"type": "int"},
"total": {"type": "int"}
}
},
"battleSet": "Satz {current} von {total}",
"@battleSet": {
"placeholders": {
"current": {"type": "int"},
"total": {"type": "int"}
}
},
"battleWeight": "GEWICHT",
"battleReps": "WH",
"battleAssistance": "UNTERSTÜTZUNG",
"battleCompleteSet": "SATZ ABSCHLIESSEN",
"battleRest": "PAUSE",
"battleSkipRest": "WEITER",
"battleUpNext": "NÄCHSTES: {exercise}",
"@battleUpNext": {
"placeholders": {
"exercise": {"type": "String"}
}
},
"battleRaidComplete": "RAID ERFOLGREICH!",
"battleBackToHub": "ZUR ZENTRALE",
"levelUpTitle": "LEVEL AUFSTIEG!",
"levelUpBody": "Du bist stärker geworden!",
"levelUpSubtitle": "Die Monster erzittern vor deiner neuen Macht.",
"battleAbandonTitle": "Raid abbrechen?",
"battleAbandonBody": "Dein Fortschritt wird nicht gespeichert.",
"cancelButton": "ABBRECHEN",
"abandonButton": "AUFGEBEN",
"amrapResultTitle": "🔥 AMRAP ERGEBNIS 🔥",
"amrapResultBody": "Alles gegeben! Wie viele waren es?",
"amrapConfirm": "ERGEBNIS BESTÄTIGEN",
"emomFinishedTitle": "MISSION ERFÜLLT",
"emomFinishedBody": "Die Zeit ist um. Hast du durchgehalten?",
"emomSetsCompleted": "SÄTZE ABGESCHLOSSEN",
"emomConfirm": "BESTÄTIGEN & BEENDEN",
"emomRepsPerRound": "Wiederholungen pro Runde",
"questTabDailies": "TÄGLICH",
"questTabJourney": "REISE",
"questEmptyDailies": "Keine täglichen Quests.\nKomm morgen wieder!",
"questEmptyJourney": "Deine Reise hat gerade erst begonnen.",
"setupProfileTitle": "Profil einrichten",
"bodyweightTitle": "Wie schwer bist du aktuell?",
"bodyweightSubtitle": "Wir benötigen dies zur Berechnung deiner Weighted Calisthenics Übungen",
"unitKg": "KG",
"unitLbs": "LBS",
"strengthTestTitle": "Stärke-Test",
"strengthTestSubtitle": "Kampf-Kalibrierung",
"strengthTestBody": "Wir müssen dein aktuelles Kraftlevel ermitteln, um die richtigen Monster zuzuweisen.",
"strengthLegs": "Beinkraft",
"strengthPull": "Zugkraft (Pull)",
"strengthPush": "Druckkraft (Push)",
"exerciseSquat": "Kniebeuge (Back Squat)",
"exercisePullup": "Weighted Pull-up",
"exerciseRow": "Pendlay Row",
"exerciseDip": "Weighted Dip",
"exerciseBench": "Bankdrücken",
"canDoOneRep": "Schaffst du 1 Rep?",
"isAssisted": "Mit Band-Hilfe?",
"addWeightLabel": "Zusatzgewicht (kg)",
"weightLabel": "Gewicht (kg)",
"bandAssistanceLabel": "Band-Hilfe (kg)",
"rowWeightLabel": "Ruder-Gewicht (kg)",
"repsLabel": "Wiederholungen",
"reps5rmLabel": "5RM Wiederholungen (meist 5)",
"est1rm": "Geschätztes 1RM",
"trainingMaxLabel": "Trainingsmaximum (90%)",
"adjustedWendler": "Angepasst: Wendler 5/3/1",
"tmExplanation": "Dein \"Trainingsmaximum\" (TM) ist deine Basis-Kampfkraft (90% vom 1RM). Bei Eigengewichtsübungen passen wir die Strategie an.",
"setupEquipmentTitle": "Ausrüstung Setup",
"setupInventoryTitle": "Ausrüstungsinventar",
"setupInventorySubtitle": "Sag uns, welche Ausrüstung du hast",
"setupBandsSubtitle": "Wähle Bänder für Pullup/Dip Unterstützung",
"nextStepButton": "NÄCHSTER SCHRITT",
"setupAvatarTitle": "Wähle deinen Helden",
"finishButton": "FERTIGSTELLEN",
"setupAvatarSubtitle": "So werden dich die Legenden in Erinnerung behalten.",
"secureAccountTitle": "Konto sichern",
"secureAccountBody": "Wähle ein starkes Passwort, um deinen Fortschritt zu schützen",
"confirmPasswordLabel": "Passwort bestätigen",
"passwordsDoNotMatch": "Passwörter stimmen nicht überein",
"confirmButton": "BESTÄTIGEN"
}

203
lib/l10n/app_en.arb Normal file
View file

@ -0,0 +1,203 @@
{
"enterTheArena": "ENTER THE ARENA",
"introText": "The Iron Golems have awakened. The Gravity Demons are pulling the world into the abyss.\n\nOnly a true Streetlifter can stop them. Are you ready to forge your body into a weapon?",
"featureArmorTitle": "Build Your Armor",
"featureArmorDesc": "Progressive overload based on Wendler 5/3/1.",
"featureMonstersTitle": "Slay Monsters",
"featureMonstersDesc": "Turn every rep into damage against epic foes.",
"featureLootTitle": "Gather Loot",
"featureLootDesc": "Earn XP, level up, and unlock new gear.",
"beginJourney": "BEGIN YOUR JOURNEY",
"loginPrompt": "Already a hero? Login here",
"loginWelcomeBack": "WELCOME BACK",
"loginSubtitle": "Time to level up your strength",
"loginErrorInvalid": "Invalid email or password",
"loginErrorConnection": "Could not connect to server.\nPlease check your internet connection.",
"loginErrorTimeout": "Connection timeout.\nPlease try again.",
"loginErrorGeneric": "Login failed. Please try again.",
"emailLabel": "Email",
"emailEmptyError": "Please enter your email",
"emailInvalidError": "Please enter a valid email",
"passwordLabel": "Password",
"passwordEmptyError": "Please enter your password",
"passwordLengthError": "Password must be at least 8 characters",
"loginButton": "LOGIN",
"loginNoAccount": "Don't have an account? ",
"loginRegisterButton": "REGISTER",
"registerTitle": "CREATE ACCOUNT",
"registerSubtitle": "Begin your strength journey",
"registerEmailHelper": "You will use this to login",
"continueButton": "CONTINUE",
"registerHaveAccount": "Already have an account? ",
"registerLoginButton": "LOGIN",
"hubNoActiveCycle": "No active cycle",
"hubCreateCycle": "Create New Cycle",
"hubCycleLabel": "Cycle",
"hubActiveLabel": "Active",
"hubActiveYes": "Yes",
"navHistory": "History",
"navInventory": "Inventory",
"navStats": "Stats",
"navCodex": "Codex",
"missionBriefingTitle": "MISSION BRIEFING",
"missionBriefingBody": "The enemy is fleeing! We have a 20-minute window to intercept.",
"missionBriefingDensity": "Combat Density: {sets} Sets",
"@missionBriefingDensity": {
"placeholders": {
"sets": {
"type": "int"
}
}
},
"missionBriefingInterval": "Interval: Every {seconds} seconds",
"@missionBriefingInterval": {
"placeholders": {
"seconds": {
"type": "String"
}
}
},
"missionBriefingHardcore": "⚠️ HARDCORE MODE",
"abortButton": "ABORT",
"engageButton": "ENGAGE",
"inventoryTitle": "Manage Equipment",
"saveButton": "SAVE",
"inventoryBarbellWeight": "Barbell Weight",
"inventoryPresets": "Quick Presets",
"inventoryPresetHome": "Home Gym",
"inventoryPresetCommercial": "Commercial",
"inventoryPresetMinimal": "Minimal",
"inventoryPlates": "Plates Available",
"inventoryBands": "Resistance Bands (Assistance)",
"saveChangesButton": "SAVE CHANGES",
"inventoryUpdatedSuccess": "Inventory updated successfully",
"inventorySaveError": "Error saving: {error}",
"@inventorySaveError": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"statsTitle": "Statistics & Cycles",
"statsProgressAnalysis": "Progress Analysis",
"statsCycleTitle": "CYCLE {number}",
"@statsCycleTitle": {
"placeholders": {
"number": {
"type": "int"
}
}
},
"statsCurrentTM": "Current Training Maxes (TM)",
"statsFinishCycle": "FINISH CYCLE & LEVEL UP",
"statsCycleFinishedTitle": "Dungeon Cleared!",
"statsCycleFinishedBody": "You have defeated the guardians of this cycle. But deeper in the dungeon, stronger foes await...",
"statsTMIncreased": "Your Training Maxes have increased:",
"statsStalled": "STALLED",
"statsEnterNextLevel": "ENTER NEXT LEVEL",
"historyTitle": "Quest Log",
"historyEmptyTitle": "No completed quests yet",
"historyEmptyBody": "Complete a workout to fill your journal",
"historyUnknownWorkout": "Unknown Workout",
"battleWave": "WAVE {current} / {total}",
"@battleWave": {
"placeholders": {
"current": {"type": "int"},
"total": {"type": "int"}
}
},
"battleSet": "Set {current} of {total}",
"@battleSet": {
"placeholders": {
"current": {"type": "int"},
"total": {"type": "int"}
}
},
"battleWeight": "WEIGHT",
"battleReps": "REPS",
"battleAssistance": "ASSISTANCE",
"battleCompleteSet": "COMPLETE SET",
"battleRest": "REST",
"battleSkipRest": "SKIP REST",
"battleUpNext": "UP NEXT: {exercise}",
"@battleUpNext": {
"placeholders": {
"exercise": {"type": "String"}
}
},
"battleRaidComplete": "RAID COMPLETE!",
"battleBackToHub": "BACK TO HUB",
"levelUpTitle": "LEVEL UP!",
"levelUpBody": "You have grown stronger!",
"levelUpSubtitle": "The monsters tremble at your new power.",
"battleAbandonTitle": "Abandon Raid?",
"battleAbandonBody": "Your progress will not be saved.",
"cancelButton": "CANCEL",
"abandonButton": "ABANDON",
"amrapResultTitle": "🔥 AMRAP RESULT 🔥",
"amrapResultBody": "Go all out! How many did you get?",
"amrapConfirm": "CONFIRM RESULT",
"emomFinishedTitle": "MISSION ACCOMPLISHED",
"emomFinishedBody": "Time is up. Did you push further?",
"emomSetsCompleted": "SETS COMPLETED",
"emomConfirm": "CONFIRM & FINISH",
"emomRepsPerRound": "Reps per Round",
"questTabDailies": "DAILIES",
"questTabJourney": "JOURNEY",
"questEmptyDailies": "No daily quests available.\nCome back tomorrow!",
"questEmptyJourney": "Your journey has just begun.",
"setupProfileTitle": "Setup Profile",
"bodyweightTitle": "What's your current bodyweight?",
"bodyweightSubtitle": "We need this to calculate your weighted calisthenics exercises",
"unitKg": "KG",
"unitLbs": "LBS",
"strengthTestTitle": "Strength Test",
"strengthTestSubtitle": "Combat Calibration",
"strengthTestBody": "We need to assess your current power level to assign the correct monsters.",
"strengthLegs": "Leg Strength",
"strengthPull": "Pull Strength",
"strengthPush": "Push Strength",
"exerciseSquat": "Back Squat",
"exercisePullup": "Weighted Pull-up",
"exerciseRow": "Pendlay Row",
"exerciseDip": "Weighted Dip",
"exerciseBench": "Bench Press",
"canDoOneRep": "Can do 1 rep?",
"isAssisted": "Assisted (Bands)?",
"addWeightLabel": "Add. Weight (kg)",
"weightLabel": "Weight (kg)",
"bandAssistanceLabel": "Band Assistance (kg)",
"rowWeightLabel": "Row Weight (kg)",
"repsLabel": "Reps",
"reps5rmLabel": "5RM Reps (usually 5)",
"est1rm": "Est. 1RM",
"trainingMaxLabel": "Training Max (90%)",
"adjustedWendler": "Adjusted: Wendler 5/3/1",
"tmExplanation": "Your \"Training Max\" (TM) is your base combat power (90% of 1RM). For bodyweight exercises, we adjust the strategy.",
"setupEquipmentTitle": "Equipment Setup",
"setupInventoryTitle": "Equipment Inventory",
"setupInventorySubtitle": "Tell us what equipment you have available",
"setupBandsSubtitle": "Select bands you have for pullup/dip assistance",
"nextStepButton": "NEXT STEP",
"setupAvatarTitle": "Choose Your Hero",
"finishButton": "FINISH",
"setupAvatarSubtitle": "This is how the legends will remember you.",
"secureAccountTitle": "Secure Your Account",
"secureAccountBody": "Choose a strong password to protect your progress",
"confirmPasswordLabel": "Confirm Password",
"passwordsDoNotMatch": "Passwords do not match",
"confirmButton": "CONFIRM"
}

View file

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:slrpg_app/l10n/app_localizations.dart';
import 'core/theme/app_theme.dart';
import 'core/routing/app_router.dart';
@ -17,7 +19,16 @@ class SLRPGApp extends ConsumerWidget {
debugShowCheckedModeBanner: false,
theme: AppTheme.darkTheme,
routerConfig: router,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en'),
Locale('de'),
],
);
}
}

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:slrpg_app/l10n/app_localizations.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../core/constants/asset_paths.dart';
@ -9,6 +10,8 @@ class WelcomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
body: Stack(
children: [
@ -48,7 +51,7 @@ class WelcomeScreen extends StatelessWidget {
),
const SizedBox(height: 32),
Text(
'ENTER THE ARENA',
l10n.enterTheArena,
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
color: Colors.white70,
letterSpacing: 2,
@ -67,9 +70,8 @@ class WelcomeScreen extends StatelessWidget {
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
const Text(
'The Iron Golems have awakened. The Gravity Demons are pulling the world into the abyss.\n\n'
'Only a true Streetlifter can stop them. Are you ready to forge your body into a weapon?',
Text(
l10n.introText,
style: TextStyle(
fontSize: 16, height: 1.5, color: Colors.white),
textAlign: TextAlign.center,
@ -77,21 +79,20 @@ class WelcomeScreen extends StatelessWidget {
const SizedBox(height: 48),
_FeatureItem(
icon: Icons.shield,
title: 'Build Your Armor',
description: 'Progressive overload based on Wendler 5/3/1.',
title: l10n.featureArmorTitle,
description: l10n.featureArmorDesc,
),
const SizedBox(height: 16),
_FeatureItem(
icon: Icons.videogame_asset,
title: 'Slay Monsters',
description:
'Turn every rep into damage against epic foes.',
title: l10n.featureMonstersTitle,
description: l10n.featureMonstersDesc,
),
const SizedBox(height: 16),
_FeatureItem(
icon: Icons.inventory_2,
title: 'Gather Loot',
description: 'Earn XP, level up, and unlock new gear.',
title: l10n.featureLootTitle,
description: l10n.featureLootDesc,
),
const Spacer(),
ElevatedButton(
@ -100,14 +101,14 @@ class WelcomeScreen extends StatelessWidget {
backgroundColor: AppTheme.primaryColor,
padding: const EdgeInsets.symmetric(vertical: 20),
),
child: const Text('BEGIN YOUR JOURNEY',
child: Text(l10n.beginJourney,
style: TextStyle(
fontWeight: FontWeight.bold, letterSpacing: 1)),
),
const SizedBox(height: 16),
TextButton(
onPressed: () => context.go('/login'),
child: const Text('Already a hero? Login here',
child: Text(l10n.loginPrompt,
style: TextStyle(color: Colors.white54)),
),
],

View file

@ -0,0 +1,210 @@
import 'package:flutter/material.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../shared/domain/models/exercise_guide.dart';
class ExerciseGuideSheet extends StatelessWidget {
final String exerciseId;
const ExerciseGuideSheet({super.key, required this.exerciseId});
@override
Widget build(BuildContext context) {
String lookupId = exerciseId;
if (exerciseId.contains('kb_snatch')) lookupId = 'kb_snatch';
// Weitere Mappings hier falls nötig...
final guide = exerciseLibrary[lookupId];
if (guide == null) {
return Container(
padding: const EdgeInsets.all(32),
child: const Text('No ancient scroll found for this technique.',
textAlign: TextAlign.center, style: TextStyle(color: Colors.grey)),
);
}
return DraggableScrollableSheet(
initialChildSize: 0.85,
minChildSize: 0.5,
maxChildSize: 0.95,
builder: (context, scrollController) {
return Container(
decoration: const BoxDecoration(
color: AppTheme.surfaceColor,
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
child: Column(
children: [
const SizedBox(height: 12),
Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey[700],
borderRadius: BorderRadius.circular(2),
),
),
Expanded(
child: ListView(
controller: scrollController,
padding: const EdgeInsets.all(24),
children: [
Text(
guide.title.toUpperCase(),
style:
Theme.of(context).textTheme.headlineMedium?.copyWith(
color: AppTheme.primaryColor,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
_buildDifficultyBadge(guide.difficulty),
const SizedBox(height: 24),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppTheme.backgroundColor,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white12),
),
child: Text(
'"${guide.rpgLore}"',
style: const TextStyle(
fontStyle: FontStyle.italic,
color: AppTheme.textSecondary,
),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 32),
_buildSectionTitle(context, 'EXECUTION'),
const SizedBox(height: 16),
...guide.steps
.asMap()
.entries
.map((entry) => _buildStep(entry.key + 1, entry.value)),
const SizedBox(height: 32),
_buildSectionTitle(context, 'COMMON MISTAKES'),
const SizedBox(height: 16),
...guide.commonMistakes.map((m) => _buildMistake(m)),
const SizedBox(height: 32),
_buildSectionTitle(context, 'ATTRIBUTES AFFECTED'),
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
children: guide.muscles
.map((m) => Chip(
label: Text(m),
backgroundColor: AppTheme.primaryColor
.withValues(alpha: 0.1),
labelStyle: const TextStyle(
color: AppTheme.primaryColor),
side: BorderSide.none,
))
.toList(),
),
const SizedBox(height: 40),
],
),
),
],
),
);
},
);
}
Widget _buildDifficultyBadge(String diff) {
Color color;
switch (diff) {
case 'Novice':
color = Colors.green;
break;
case 'Adept':
color = Colors.orange;
break;
case 'Master':
color = AppTheme.errorColor;
break;
default:
color = Colors.grey;
}
return Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: color.withValues(alpha: 0.5)),
),
child: Text(
diff.toUpperCase(),
style: TextStyle(
color: color, fontSize: 12, fontWeight: FontWeight.bold),
),
),
);
}
Widget _buildSectionTitle(BuildContext context, String title) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
letterSpacing: 1.2,
),
),
const Divider(
color: AppTheme.primaryColor, thickness: 2, endIndent: 250),
],
);
}
Widget _buildStep(int index, String text) {
return Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 24,
height: 24,
alignment: Alignment.center,
decoration: BoxDecoration(
color: AppTheme.primaryColor,
shape: BoxShape.circle,
),
child: Text('$index',
style: const TextStyle(
color: Colors.black, fontWeight: FontWeight.bold)),
),
const SizedBox(width: 16),
Expanded(
child: Text(text,
style: const TextStyle(color: Colors.white70, height: 1.4))),
],
),
);
}
Widget _buildMistake(String text) {
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Row(
children: [
const Icon(Icons.close, color: AppTheme.errorColor, size: 20),
const SizedBox(width: 12),
Expanded(
child: Text(text, style: const TextStyle(color: Colors.white70))),
],
),
);
}
}

View file

@ -20,6 +20,7 @@ import '../widgets/enemy_hp_bar.dart';
import '../../../gamification/application/quest_service.dart';
import '../widgets/emom_timer_widget.dart';
import '../widgets/timer_widget.dart';
import '../../../wiki/presentation/widgets/exercise_guide_sheet.dart';
class BattleScreen extends ConsumerStatefulWidget {
final int week;
@ -105,6 +106,15 @@ class _BattleScreenState extends ConsumerState<BattleScreen> {
}
}
void _showExerciseGuide(String exerciseId) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => ExerciseGuideSheet(exerciseId: exerciseId),
);
}
List<Map<String, dynamic>> _getExerciseConfig(int day, UserCollection user) {
final variants = user.exerciseVariants ?? {};
@ -875,6 +885,13 @@ class _BattleScreenState extends ConsumerState<BattleScreen> {
),
textAlign: TextAlign.center,
),
const SizedBox(width: 8),
IconButton(
icon: const Icon(Icons.info_outline,
color: Colors.white54),
onPressed: () =>
_showExerciseGuide(currentExercise.exerciseId),
),
Text(
'Set ${_currentSetIndex + 1} of ${currentExercise.sets.length}',
style: Theme.of(context)
@ -1108,6 +1125,14 @@ class _BattleScreenState extends ConsumerState<BattleScreen> {
fontSize: 16,
color: Colors.white),
),
IconButton(
icon: const Icon(Icons.info_outline,
size: 20, color: AppTheme.primaryColor),
onPressed: () =>
_showExerciseGuide(currentExercise.exerciseId),
padding: const EdgeInsets.all(4),
constraints: const BoxConstraints(),
),
Text(
'${currentSet.repsTarget} Reps per Round',
style: const TextStyle(color: Colors.grey),

View file

@ -0,0 +1,233 @@
import '../logic/wendler_calculator.dart';
class ExerciseGuide {
final String title;
final String difficulty; // z.B. "Novice", "Adept", "Master"
final String rpgLore; // Ein kleiner Flavor-Text
final List<String> steps;
final List<String> muscles;
final List<String> commonMistakes;
const ExerciseGuide({
required this.title,
required this.difficulty,
required this.rpgLore,
required this.steps,
required this.muscles,
required this.commonMistakes,
});
}
final Map<String, ExerciseGuide> exerciseLibrary = {
'pullup': const ExerciseGuide(
title: 'Weighted Pull-Up',
difficulty: 'Adept',
rpgLore:
'Den Körper gegen die Schwerkraft zu ziehen, ist der ultimative Beweis für Oberkörperkraft.',
steps: [
'Greife die Stange etwas weiter als schulterbreit (Obergriff).',
'Aktiviere den Core und ziehe die Schulterblätter nach unten/hinten.',
'Ziehe dich hoch, bis das Kinn über der Stange ist.',
'Lasse dich kontrolliert wieder ab.',
],
muscles: ['Latissimus', 'Bizeps', 'Unterarme'],
commonMistakes: [
'Schwingen (Kipping)',
'Halbe Wiederholungen',
'Schultern hochgezogen lassen'
],
),
'dip': const ExerciseGuide(
title: 'Weighted Dip',
difficulty: 'Adept',
rpgLore: 'Ein fundamentaler Druck-Move, um Mauern zu überwinden.',
steps: [
'Stütze dich auf die Barren, Arme gestreckt.',
'Lehne dich leicht nach vorne für mehr Brust-Fokus.',
'Senke den Körper ab, bis die Schultern unter den Ellbogen sind.',
'Drücke dich explosiv zurück in die Ausgangsposition.',
],
muscles: ['Brust', 'Trizeps', 'Vordere Schulter'],
commonMistakes: ['Zu wenig Tiefe', 'Ellbogen wandern zu weit nach außen'],
),
'squat': const ExerciseGuide(
title: 'Low Bar Back Squat',
difficulty: 'Master',
rpgLore:
'Die Mutter aller Schlachten. Trainiert den gesamten Körperpanzer.',
steps: [
'Lege die Hantel auf dem hinteren Deltamuskel ab (nicht im Nacken).',
'Füße schulterbreit, Zehen leicht nach außen.',
'Atme tief ein (Bracing) und schiebe die Hüfte nach hinten.',
'Gehe in die Hocke (Hüfte unter Kniehöhe).',
'Drücke dich aus der Ferse/Mittelfuß wieder hoch.',
],
muscles: ['Quadrizeps', 'Gesäß', 'Core', 'Rückenstrecker'],
commonMistakes: [
'Knie fallen nach innen',
'Rücken rundet ein',
'Zu wenig Tiefe'
],
),
'bench': const ExerciseGuide(
title: 'Bench Press',
difficulty: 'Novice',
rpgLore: 'Der Standard-Test für reine Druckkraft.',
steps: [
'Lege dich auf die Bank, Augen unter der Stange.',
'Füße fest am Boden, leichter Bogen im Rücken (Brücke).',
'Senke die Hantel kontrolliert zur unteren Brust.',
'Drücke die Hantel explosiv nach oben.',
],
muscles: ['Brust', 'Trizeps', 'Vordere Schulter'],
commonMistakes: [
'Ellbogen 90° abgespreizt (Verletzungsgefahr)',
'Hintern hebt ab'
],
),
'ohp': const ExerciseGuide(
title: 'Overhead Press',
difficulty: 'Adept',
rpgLore: 'Ein Objekt über den Kopf zu stemmen erfordert pure Stabilität.',
steps: [
'Stange liegt auf dem vorderen Schultermuskel.',
'Fester Stand, Gesäß und Bauch maximal anspannen.',
'Kopf leicht zurücknehmen, Stange vertikal nach oben drücken.',
'Oben den Kopf "durch das Fenster" der Arme schieben.',
],
muscles: ['Schultern', 'Trizeps', 'Core'],
commonMistakes: ['Hohlkreuz (Rücklage)', 'Beine helfen mit (Push Press)'],
),
'rdl': const ExerciseGuide(
title: 'Romanian Deadlift',
difficulty: 'Adept',
rpgLore: 'Baut die hintere Kette auf, essenziell für Stabilität.',
steps: [
'Startposition stehend mit Hantel.',
'Schiebe die Hüfte weit nach hinten, Beine bleiben fast gestreckt.',
'Senke die Hantel am Bein entlang bis knapp unter das Knie.',
'Spüre den Zug im Beinbeuger und richte dich wieder auf.',
],
muscles: ['Beinbeuger (Hamstrings)', 'Gesäß', 'Unterer Rücken'],
commonMistakes: ['Rücken rundet ein', 'Hantel zu weit vom Körper weg'],
),
'row': const ExerciseGuide(
title: 'Pendlay Row',
difficulty: 'Adept',
rpgLore: 'Explosive Zugkraft vom Boden. Für einen starken Rücken.',
steps: [
'Oberkörper parallel zum Boden, Rücken gerade.',
'Hantel liegt bei jeder Wiederholung tot auf dem Boden.',
'Ziehe die Hantel explosiv zum unteren Brustbein.',
'Kontrolliert ablegen, Spannung kurz lösen, neu ansetzen.',
],
muscles: ['Latissimus', 'Trapez', 'Hintere Schulter'],
commonMistakes: ['Oberkörper richtet sich auf', 'Reißen mit Schwung'],
),
'curl': const ExerciseGuide(
title: 'Barbell Curl',
difficulty: 'Novice',
rpgLore: 'Isolierte Kraft für den finalen Schlag.',
steps: [
'Stehender Stand, Hantel im Untergriff.',
'Ellbogen bleiben fixiert am Körper.',
'Hantel zur Brust curlen, oben kurz halten.',
'Langsam ablassen.',
],
muscles: ['Bizeps'],
commonMistakes: ['Schwingen aus der Hüfte', 'Ellbogen wandern nach vorne'],
),
'kb_swing': const ExerciseGuide(
title: 'Kettlebell Swing',
difficulty: 'Adept',
rpgLore: 'Ballistische Kraft und Ausdauer. Die Hüfte ist der Motor.',
steps: [
'Hüftbreiter Stand, KB vor dir am Boden.',
'Hike-Pass: Ziehe die KB durch die Beine nach hinten.',
'Hüfte explosiv strecken (Snap!), KB fliegt durch Hüftkraft auf Brusthöhe.',
'KB kontrolliert zurückschwingen lassen.',
],
muscles: ['Gesäß', 'Beinbeuger', 'Core', 'Ausdauer'],
commonMistakes: ['Kniebeuge statt Hüftbeuge', 'Arme heben das Gewicht'],
),
'kb_snatch': const ExerciseGuide(
title: 'Kettlebell Snatch',
difficulty: 'Master',
rpgLore: 'Der Zar der Kettlebell-Übungen. Totale Körperkontrolle.',
steps: [
'Starte wie beim Swing (Einarmig).',
'Hüftkraft beschleunigt die Kugel nach oben.',
'Bei Kopfhöhe: Durchstoßen der Hand ("Punch through").',
'Sanftes Auffangen im Lockout über Kopf.',
],
muscles: ['Gesamter Körper', 'Schultern', 'Griffkraft'],
commonMistakes: ['Kugel knallt auf den Unterarm', 'Zu wenig Hüftkraft'],
),
'kb_thruster': const ExerciseGuide(
title: 'Kettlebell Thruster',
difficulty: 'Master',
rpgLore: 'Eine brutale Kombination aus Squat und Press.',
steps: [
'KB in der Rack-Position (vor der Brust).',
'Tiefe Kniebeuge.',
'Beim Aufstehen den Schwung nutzen, um KB über Kopf zu drücken.',
'Zurück in die Rack-Position beim Absenken in den nächsten Squat.',
],
muscles: ['Beine', 'Schultern', 'Lunge (Cardio)'],
commonMistakes: [
'Pause zwischen Squat und Press',
'Rücken rundet im Squat'
],
),
'kb_clean_press': const ExerciseGuide(
title: 'KB Clean & Press',
difficulty: 'Adept',
rpgLore: 'Zwei Bewegungen in Harmonie.',
steps: [
'Clean: Ziehe die KB vom Boden in die Rack-Position vor der Brust.',
'Press: Drücke sie strikt über den Kopf.',
'Senke sie in Rack, dann zum Boden (oder Swing).',
],
muscles: ['Schultern', 'Rücken', 'Beine'],
commonMistakes: ['Clean knallt auf Arm', 'Hohlkreuz beim Press'],
),
'face_pull': const ExerciseGuide(
title: 'Band Face Pull',
difficulty: 'Novice',
rpgLore: 'Schützt die Schultern vor dem Verschleiß des Kampfes.',
steps: [
'Band auf Kopfhöhe befestigen.',
'Ziehe das Band zum Gesicht (Richtung Stirn/Augen).',
'Ellbogen hoch und weit nach außen ziehen.',
'Schulterblätter hinten zusammenkneifen.',
],
muscles: ['Hintere Schulter', 'Rotatorenmanschette'],
commonMistakes: ['Ellbogen zu tief', 'Kopf nach vorne schieben'],
),
'ab_wheel': const ExerciseGuide(
title: 'Ab Wheel Rollout',
difficulty: 'Adept',
rpgLore: 'Ein Stahlkern, der jeden Treffer absorbiert.',
steps: [
'Knie am Boden, Rad vor den Knien.',
'Rolle nach vorne, halte den Rücken rund/stabil (Hollow Body).',
'Gehe nur so weit, wie du den Rücken stabil halten kannst.',
'Ziehe dich aus dem Bauchmuskel zurück.',
],
muscles: ['Core (Anti-Extension)'],
commonMistakes: ['Hohlkreuz (Gefährlich!)', 'Ziehen aus den Armen'],
),
'plank': const ExerciseGuide(
title: 'Plank',
difficulty: 'Novice',
rpgLore: 'Unbeweglich wie ein Fels in der Brandung.',
steps: [
'Unterarmstütz, Körper bildet eine Linie.',
'Gesäß und Bauch fest anspannen.',
'Schulterblätter auseinanderdrücken.',
'Atmen nicht vergessen!',
],
muscles: ['Core'],
commonMistakes: ['Hüfte hängt durch', 'Gesäß zu hoch'],
),
};