feat: add complete multilanguage support for german and english
This commit is contained in:
parent
d4be30cf74
commit
e5c61447a3
25 changed files with 500 additions and 168 deletions
|
|
@ -33,6 +33,7 @@
|
|||
"registerHaveAccount": "Bereits registriert? ",
|
||||
"registerLoginButton": "LOGIN",
|
||||
|
||||
"hubCycleComplete": "Zyklus beendet! Beende ihn in den Stats.",
|
||||
"hubNoActiveCycle": "Kein aktiver Zyklus",
|
||||
"hubCreateCycle": "Neuen Zyklus starten",
|
||||
"hubCycleLabel": "Zyklus",
|
||||
|
|
@ -153,6 +154,26 @@
|
|||
"emomConfirm": "BESTÄTIGEN & BEENDEN",
|
||||
"emomRepsPerRound": "Wiederholungen pro Runde",
|
||||
|
||||
"questDailyBounties": "TÄGLICHE KOPFGELDER",
|
||||
"questViewAll": "ALLE ANZEIGEN >",
|
||||
"questClaim": "EINSAMMELN",
|
||||
"questRewardCollected": "Belohnung: {xp} XP erhalten!",
|
||||
"@questRewardCollected": {
|
||||
"placeholders": {
|
||||
"xp": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"guideNotFound": "Keine Schriftrolle für diese Technik gefunden.",
|
||||
"guideExecution": "AUSFÜHRUNG",
|
||||
"guideMistakes": "HÄUFIGE FEHLER",
|
||||
"guideAttributes": "BETROFFENE ATTRIBUTE",
|
||||
"guideDiffNovice": "ANFÄNGER",
|
||||
"guideDiffAdept": "FORTGESCHRITTEN",
|
||||
"guideDiffMaster": "MEISTER",
|
||||
|
||||
"questTabDailies": "TÄGLICH",
|
||||
"questTabJourney": "REISE",
|
||||
"questEmptyDailies": "Keine täglichen Quests.\nKomm morgen wieder!",
|
||||
|
|
@ -203,6 +224,32 @@
|
|||
"passwordsDoNotMatch": "Passwörter stimmen nicht überein",
|
||||
"confirmButton": "BESTÄTIGEN",
|
||||
|
||||
"battleTitle": "Kampf",
|
||||
"battleNoExercises": "Keine Übungen konfiguriert",
|
||||
"battleWeekDay": "Woche {week} - Tag {day}",
|
||||
"@battleWeekDay": {
|
||||
"placeholders": {
|
||||
"week": {"type": "int"},
|
||||
"day": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"bodyweight": "Körpergewicht",
|
||||
"battleBossDefeated": "BOSS BESIEGT!",
|
||||
"timerComplete": "Zeit abgelaufen!",
|
||||
"battleRepsPerRound": "{reps} Reps pro Runde",
|
||||
"@battleRepsPerRound": {
|
||||
"placeholders": {
|
||||
"reps": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"battleWeightKg": "GEWICHT: {weight} kg",
|
||||
"@battleWeightKg": {
|
||||
"placeholders": {
|
||||
"weight": {"type": "double", "format": "decimalPattern"}
|
||||
}
|
||||
},
|
||||
"commonConfirm": "BESTÄTIGEN",
|
||||
|
||||
"guidePullupTitle": "Weighted Pull-Up",
|
||||
"guidePullupLore": "Den Körper gegen die Schwerkraft zu ziehen, ist der ultimative Beweis für Oberkörperkraft.",
|
||||
"guidePullupSteps": "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",
|
||||
|
|
@ -384,6 +431,44 @@
|
|||
"lobbyStatusActive": "Raid startet...",
|
||||
"lobbyStatusEntering": "Betrete das Schlachtfeld...",
|
||||
|
||||
"unknownMember": "Unbekannt",
|
||||
"leaderboardTitle": "HALL OF FAME",
|
||||
"leaderboardHero": "Held #{id}",
|
||||
"@leaderboardHero": {
|
||||
"placeholders": {
|
||||
"id": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"leaderboardSubtitle": "Stufe {level} • {xp} XP",
|
||||
"@leaderboardSubtitle": {
|
||||
"placeholders": {
|
||||
"level": {"type": "int"},
|
||||
"xp": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"exportEmailSubject": "SLRPG Daten Export",
|
||||
"exportEmailBody": "Deine SLRPG Trainingsdaten (DSGVO Export)",
|
||||
"deleteConfirmationWord": "LÖSCHEN",
|
||||
|
||||
"timerPause": "PAUSE",
|
||||
"timerReset": "RESET",
|
||||
"timerSkip": "SKIP",
|
||||
"timerRestart": "NEUSTART",
|
||||
"genderMale": "Männlich",
|
||||
"genderFemale": "Weiblich",
|
||||
|
||||
"timerIgnite": "STARTEN",
|
||||
"timerResume": "FORTSETZEN",
|
||||
"timerReady": "BEREIT?",
|
||||
"timerPaused": "PAUSIERT",
|
||||
"timerRound": "RUNDE {current} / {total}",
|
||||
"@timerRound": {
|
||||
"placeholders": {
|
||||
"current": {"type": "int"},
|
||||
"total": {"type": "int"}
|
||||
}
|
||||
},
|
||||
|
||||
"connectivityError": "Keine Internetverbindung verfügbar.",
|
||||
"connectivityMultiplayerError": "Für Multiplayer wird eine Internetverbindung benötigt.",
|
||||
|
||||
|
|
@ -393,5 +478,60 @@
|
|||
"errorNotFound": "Daten konnten nicht gefunden werden.",
|
||||
"errorEntryNotUnique": "Dieser Eintrag ist bereits vergeben.",
|
||||
"errorAuthenticationFailed": "E-Mail oder Passwort falsch.",
|
||||
"errorIllegalRequest": "Ungültige Anfrage."
|
||||
"errorIllegalRequest": "Ungültige Anfrage.",
|
||||
|
||||
"commonLevel": "Stufe",
|
||||
"commonRequired": "Erforderlich",
|
||||
"commonUpdate": "AKTUALISIEREN",
|
||||
"commonEdit": "BEARBEITEN",
|
||||
"commonLogout": "ABMELDEN",
|
||||
"commonSave": "SPEICHERN",
|
||||
"commonCancel": "ABBRECHEN",
|
||||
"commonConfirm": "BESTÄTIGEN",
|
||||
|
||||
"profileEditTitle": "Profil bearbeiten",
|
||||
"profileEditAppearance": "Aussehen bearbeiten",
|
||||
"profileSelectScenery": "Hintergrund wählen",
|
||||
"profileBodyweightUpdated": "Körpergewicht aktualisiert",
|
||||
"profileChangePassword": "Passwort ändern",
|
||||
"profileOldPassword": "Altes Passwort",
|
||||
"profileNewPassword": "Neues Passwort",
|
||||
"profileConfirmNewPassword": "Bestätigen",
|
||||
"profilePassMismatch": "Stimmt nicht überein",
|
||||
"profilePassMinChars": "Min 8 Zeichen",
|
||||
"profilePassChangedSuccess": "Passwort erfolgreich geändert",
|
||||
"profilePhysicalStats": "Physische Werte",
|
||||
"profileCurrentBodyweight": "Aktuelles Gewicht",
|
||||
"profileTrainingFocus": "Trainingsfokus",
|
||||
"profileAccessoryTemplate": "Assistenz-Template",
|
||||
"profileAccountSecurity": "Kontosicherheit",
|
||||
"profileDangerZone": "Gefahrenzone",
|
||||
"profileResetProgress": "Fortschritt zurücksetzen",
|
||||
"profileResetProgressSubtitle": "Setzt Level, XP und Historie zurück",
|
||||
"profileResetConfirmTitle": "Fortschritt löschen?",
|
||||
"profileResetConfirmBody": "Dies löscht alle Workouts und setzt Level auf 1. Nicht widerruflich.",
|
||||
"profileDeleteAccount": "Account löschen",
|
||||
"profileDeleteAccountSubtitle": "Löscht Account und Daten dauerhaft",
|
||||
"profileDeleteConfirmTitle": "Account löschen?",
|
||||
"profileDeleteConfirmBody": "Bist du sicher? Alle Daten gehen verloren.",
|
||||
|
||||
"templateStrengthOnly": "Nur Stärke",
|
||||
"templateStrengthOnlyDesc": "Hauptübungen + FSL. Pur & Schnell.",
|
||||
"templateHypertrophy": "Hypertrophie Support",
|
||||
"templateHypertrophyDesc": "Bodybuilding-Assistenz für Muskelpanzer.",
|
||||
"templateConditioning": "Der Motor (Ausdauer)",
|
||||
"templateConditioningDesc": "15 min Kettlebell Intervalle.",
|
||||
"templateActiveJourneys": "AKTIVE REISEN",
|
||||
"templatePullupJourney": "Quest: Der erste Klimmzug",
|
||||
"templatePullupJourneyDesc": "Spezifische Progression für das eigene Körpergewicht.",
|
||||
|
||||
"setupFailed": "Setup fehlgeschlagen: {error}",
|
||||
"@setupFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "Object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"setupEmailExists": "E-Mail existiert bereits. Bitte einloggen."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
"registerHaveAccount": "Already have an account? ",
|
||||
"registerLoginButton": "LOGIN",
|
||||
|
||||
"hubCycleComplete": "Cycle complete! Finish it in stats.",
|
||||
"hubNoActiveCycle": "No active cycle",
|
||||
"hubCreateCycle": "Create New Cycle",
|
||||
"hubCycleLabel": "Cycle",
|
||||
|
|
@ -153,6 +154,26 @@
|
|||
"emomConfirm": "CONFIRM & FINISH",
|
||||
"emomRepsPerRound": "Reps per Round",
|
||||
|
||||
"questDailyBounties": "DAILY BOUNTIES",
|
||||
"questViewAll": "VIEW ALL >",
|
||||
"questClaim": "CLAIM",
|
||||
"questRewardCollected": "Reward collected: {xp} XP!",
|
||||
"@questRewardCollected": {
|
||||
"placeholders": {
|
||||
"xp": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"guideNotFound": "No ancient scroll found for this technique.",
|
||||
"guideExecution": "EXECUTION",
|
||||
"guideMistakes": "COMMON MISTAKES",
|
||||
"guideAttributes": "ATTRIBUTES AFFECTED",
|
||||
"guideDiffNovice": "NOVICE",
|
||||
"guideDiffAdept": "ADEPT",
|
||||
"guideDiffMaster": "MASTER",
|
||||
|
||||
"questTabDailies": "DAILIES",
|
||||
"questTabJourney": "JOURNEY",
|
||||
"questEmptyDailies": "No daily quests available.\nCome back tomorrow!",
|
||||
|
|
@ -203,6 +224,32 @@
|
|||
"passwordsDoNotMatch": "Passwords do not match",
|
||||
"confirmButton": "CONFIRM",
|
||||
|
||||
"battleTitle": "Battle",
|
||||
"battleNoExercises": "No exercises configured",
|
||||
"battleWeekDay": "Week {week} - Day {day}",
|
||||
"@battleWeekDay": {
|
||||
"placeholders": {
|
||||
"week": {"type": "int"},
|
||||
"day": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"bodyweight": "Bodyweight",
|
||||
"battleBossDefeated": "BOSS DEFEATED!",
|
||||
"timerComplete": "Time Complete!",
|
||||
"battleRepsPerRound": "{reps} Reps per Round",
|
||||
"@battleRepsPerRound": {
|
||||
"placeholders": {
|
||||
"reps": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"battleWeightKg": "WEIGHT: {weight} kg",
|
||||
"@battleWeightKg": {
|
||||
"placeholders": {
|
||||
"weight": {"type": "double", "format": "decimalPattern"}
|
||||
}
|
||||
},
|
||||
"commonConfirm": "CONFIRM",
|
||||
|
||||
"guidePullupTitle": "Weighted Pull-Up",
|
||||
"guidePullupLore": "Pulling your body against gravity is the ultimate proof of upper body strength.",
|
||||
"guidePullupSteps": "Grip the bar slightly wider than shoulder width (overhand)|Engage core and pull shoulder blades down/back|Pull yourself up until chin is over the bar|Lower yourself with control",
|
||||
|
|
@ -398,6 +445,44 @@
|
|||
"lobbyStatusActive": "Raid is starting...",
|
||||
"lobbyStatusEntering": "Entering Battle...",
|
||||
|
||||
"unknownMember": "Unknown",
|
||||
"leaderboardTitle": "HALL OF FAME",
|
||||
"leaderboardHero": "Hero #{id}",
|
||||
"@leaderboardHero": {
|
||||
"placeholders": {
|
||||
"id": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"leaderboardSubtitle": "Level {level} • {xp} XP",
|
||||
"@leaderboardSubtitle": {
|
||||
"placeholders": {
|
||||
"level": {"type": "int"},
|
||||
"xp": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"exportEmailSubject": "SLRPG Data Export",
|
||||
"exportEmailBody": "Your SLRPG training data (GDPR export)",
|
||||
"deleteConfirmationWord": "DELETE",
|
||||
|
||||
"timerPause": "PAUSE",
|
||||
"timerReset": "RESET",
|
||||
"timerSkip": "SKIP",
|
||||
"timerRestart": "RESTART",
|
||||
"genderMale": "Male",
|
||||
"genderFemale": "Female",
|
||||
|
||||
"timerIgnite": "IGNITE ENGINE",
|
||||
"timerResume": "RESUME",
|
||||
"timerReady": "READY?",
|
||||
"timerPaused": "PAUSED",
|
||||
"timerRound": "ROUND {current} / {total}",
|
||||
"@timerRound": {
|
||||
"placeholders": {
|
||||
"current": {"type": "int"},
|
||||
"total": {"type": "int"}
|
||||
}
|
||||
},
|
||||
|
||||
"connectivityError": "No internet connection available.",
|
||||
"connectivityMultiplayerError": "Active internet connection required for multiplayer.",
|
||||
|
||||
|
|
@ -407,5 +492,60 @@
|
|||
"errorNotFound": "Data not found.",
|
||||
"errorEntryNotUnique": "Entry already exists.",
|
||||
"errorAuthenticationFailed": "E-Mail or Passwort wrong.",
|
||||
"errorIllegalRequest": "Illegal Request."
|
||||
"errorIllegalRequest": "Illegal Request.",
|
||||
|
||||
"commonLevel": "Lvl",
|
||||
"commonRequired": "Required",
|
||||
"commonUpdate": "UPDATE",
|
||||
"commonEdit": "EDIT",
|
||||
"commonLogout": "LOGOUT",
|
||||
"commonSave": "SAVE",
|
||||
"commonCancel": "CANCEL",
|
||||
"commonConfirm": "CONFIRM",
|
||||
|
||||
"profileEditTitle": "Edit Profile",
|
||||
"profileEditAppearance": "Edit Appearance",
|
||||
"profileSelectScenery": "Select Scenery",
|
||||
"profileBodyweightUpdated": "Bodyweight updated",
|
||||
"profileChangePassword": "Change Password",
|
||||
"profileOldPassword": "Old Password",
|
||||
"profileNewPassword": "New Password",
|
||||
"profileConfirmNewPassword": "Confirm New",
|
||||
"profilePassMismatch": "Mismatch",
|
||||
"profilePassMinChars": "Min 8 chars",
|
||||
"profilePassChangedSuccess": "Password changed successfully",
|
||||
"profilePhysicalStats": "Physical Stats",
|
||||
"profileCurrentBodyweight": "Current Bodyweight",
|
||||
"profileTrainingFocus": "Training Focus",
|
||||
"profileAccessoryTemplate": "Accessory Template",
|
||||
"profileAccountSecurity": "Account Security",
|
||||
"profileDangerZone": "Danger Zone",
|
||||
"profileResetProgress": "Reset Progress",
|
||||
"profileResetProgressSubtitle": "Resets Level, XP and Training History",
|
||||
"profileResetConfirmTitle": "Reset Progress?",
|
||||
"profileResetConfirmBody": "This will delete all your workouts and reset your Level to 1. This cannot be undone.",
|
||||
"profileDeleteAccount": "Delete Account",
|
||||
"profileDeleteAccountSubtitle": "Permanently delete your account and data",
|
||||
"profileDeleteConfirmTitle": "Delete Account?",
|
||||
"profileDeleteConfirmBody": "Are you sure you want to delete your account? All data will be lost forever.",
|
||||
|
||||
"templateStrengthOnly": "Strength Only",
|
||||
"templateStrengthOnlyDesc": "Main Lifts + FSL. Pure & Fast.",
|
||||
"templateHypertrophy": "Hypertrophy Support",
|
||||
"templateHypertrophyDesc": "Bodybuilding accessories to build muscle armor.",
|
||||
"templateConditioning": "The Engine (Conditioning)",
|
||||
"templateConditioningDesc": "15 min Kettlebell intervals to boost stamina.",
|
||||
"templateActiveJourneys": "ACTIVE JOURNEYS",
|
||||
"templatePullupJourney": "Quest: The First Pull-Up",
|
||||
"templatePullupJourneyDesc": "Specific progression to master your bodyweight.",
|
||||
|
||||
"setupFailed": "Setup failed: {error}",
|
||||
"@setupFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "Object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"setupEmailExists": "Email already exists. Please login or use another email."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,26 +57,27 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
|||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_errorMessage = _parseErrorMessage(e.toString());
|
||||
_errorMessage = _parseErrorMessage(e.toString(), l10n);
|
||||
});
|
||||
ErrorHandler.showErrorSnackBar(context, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _parseErrorMessage(String error) {
|
||||
String _parseErrorMessage(String error, AppLocalizations l10n) {
|
||||
if (error.contains('400')) {
|
||||
return 'Invalid email or password';
|
||||
return l10n.loginErrorInvalid;
|
||||
} else if (error.contains('SocketException') ||
|
||||
error.contains('Connection refused') ||
|
||||
error.contains('Network is unreachable')) {
|
||||
return 'Could not connect to server.\nPlease check your internet connection.';
|
||||
return l10n.loginErrorConnection;
|
||||
} else if (error.contains('timeout')) {
|
||||
return 'Connection timeout.\nPlease try again.';
|
||||
return l10n.loginErrorTimeout;
|
||||
}
|
||||
return 'Login failed. Please try again.';
|
||||
return l10n.loginErrorGeneric;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
if (mounted) {
|
||||
setState(() => _hasChanges = false);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Bodyweight updated')),
|
||||
SnackBar(content: Text(AppLocalizations.of(context)!.profileBodyweightUpdated)),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
|
|
@ -66,6 +66,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
}
|
||||
|
||||
void _showChangePasswordDialog() {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final oldPassCtrl = TextEditingController();
|
||||
final newPassCtrl = TextEditingController();
|
||||
final confirmPassCtrl = TextEditingController();
|
||||
|
|
@ -74,7 +75,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Change Password'),
|
||||
title: Text(l10n.profileChangePassword),
|
||||
content: Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
|
|
@ -83,22 +84,22 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
TextFormField(
|
||||
controller: oldPassCtrl,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(labelText: 'Old Password'),
|
||||
validator: (v) => v?.isEmpty == true ? 'Required' : null,
|
||||
decoration: InputDecoration(labelText: l10n.profileOldPassword),
|
||||
validator: (v) => v?.isEmpty == true ? l10n.commonRequired : null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: newPassCtrl,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(labelText: 'New Password'),
|
||||
validator: (v) => (v?.length ?? 0) < 8 ? 'Min 8 chars' : null,
|
||||
decoration: InputDecoration(labelText: l10n.profileNewPassword),
|
||||
validator: (v) => (v?.length ?? 0) < 8 ? l10n.profilePassMinChars : null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: confirmPassCtrl,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(labelText: 'Confirm New'),
|
||||
validator: (v) => v != newPassCtrl.text ? 'Mismatch' : null,
|
||||
decoration: InputDecoration(labelText: l10n.profileConfirmNewPassword),
|
||||
validator: (v) => v != newPassCtrl.text ? l10n.profilePassMismatch : null,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -106,7 +107,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('CANCEL'),
|
||||
child: Text(l10n.commonCancel),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
|
|
@ -120,8 +121,8 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Password changed successfully')),
|
||||
SnackBar(
|
||||
content: Text(l10n.profilePassChangedSuccess)),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
@ -137,7 +138,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
}
|
||||
}
|
||||
},
|
||||
child: const Text('UPDATE'),
|
||||
child: Text(l10n.commonUpdate),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -149,6 +150,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
final currentConfig = _user?.avatarConfig != null
|
||||
? AvatarConfig.fromJson(_user!.avatarConfig!)
|
||||
: const AvatarConfig();
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
|
|
@ -158,7 +160,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text('Select Scenery',
|
||||
child: Text(l10n.profileSelectScenery,
|
||||
style: Theme.of(context).textTheme.titleLarge),
|
||||
),
|
||||
SizedBox(
|
||||
|
|
@ -225,7 +227,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
const Icon(Icons.lock, color: Colors.white54),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Lvl ${item.unlockLevel}',
|
||||
'${l10n.commonLevel} ${item.unlockLevel}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
@ -279,6 +281,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
|
||||
void _confirmDangerAction(
|
||||
String title, String content, VoidCallback onConfirm) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
|
|
@ -287,7 +290,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('CANCEL'),
|
||||
child: Text(l10n.commonCancel),
|
||||
),
|
||||
ElevatedButton(
|
||||
style:
|
||||
|
|
@ -296,7 +299,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
Navigator.pop(context);
|
||||
onConfirm();
|
||||
},
|
||||
child: const Text('CONFIRM'),
|
||||
child: Text(l10n.commonConfirm),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -307,6 +310,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
final currentConfig = _user?.avatarConfig != null
|
||||
? AvatarConfig.fromJson(_user!.avatarConfig!)
|
||||
: const AvatarConfig();
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
|
|
@ -315,7 +319,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
backgroundColor: AppTheme.backgroundColor,
|
||||
builder: (context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Edit Appearance'),
|
||||
title: Text(l10n.profileEditAppearance),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
|
|
@ -325,7 +329,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
onPressed: () {
|
||||
Navigator.pop(context, _tempAvatarConfig);
|
||||
},
|
||||
child: const Text('SAVE',
|
||||
child: Text(l10n.commonSave,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppTheme.primaryColor)),
|
||||
|
|
@ -409,7 +413,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Edit Profile'),
|
||||
title: Text(l10n.profileEditTitle),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => context.go('/hub'),
|
||||
|
|
@ -418,7 +422,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
if (_hasChanges)
|
||||
TextButton(
|
||||
onPressed: _isLoading ? null : _saveBodyweight,
|
||||
child: const Text('SAVE',
|
||||
child: Text(l10n.commonSave,
|
||||
style: TextStyle(
|
||||
color: AppTheme.primaryColor,
|
||||
fontWeight: FontWeight.bold)),
|
||||
|
|
@ -458,11 +462,11 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
child: OutlinedButton.icon(
|
||||
onPressed: _showBackgroundSelector,
|
||||
icon: const Icon(Icons.landscape),
|
||||
label: const Text('CHANGE SCENERY'),
|
||||
label: Text(l10n.profileSelectScenery.toUpperCase()),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Text('Physical Stats',
|
||||
Text(l10n.profilePhysicalStats,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
|
|
@ -474,7 +478,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Current Bodyweight',
|
||||
Text(l10n.profileCurrentBodyweight,
|
||||
style: Theme.of(context).textTheme.bodyMedium),
|
||||
Row(
|
||||
children: [
|
||||
|
|
@ -509,7 +513,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Text('Training Focus',
|
||||
Text(l10n.profileTrainingFocus,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
|
|
@ -521,7 +525,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Accessory Template',
|
||||
Text(l10n.profileAccessoryTemplate,
|
||||
style: Theme.of(context).textTheme.bodyMedium),
|
||||
const SizedBox(height: 12),
|
||||
_buildTemplateSelector(),
|
||||
|
|
@ -529,7 +533,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
),
|
||||
),
|
||||
),
|
||||
Text('Account Security',
|
||||
Text(l10n.profileAccountSecurity,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
|
|
@ -537,7 +541,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
const SizedBox(height: 8),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.lock_outline),
|
||||
title: const Text('Change Password'),
|
||||
title: Text(l10n.profileChangePassword),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: _showChangePasswordDialog,
|
||||
),
|
||||
|
|
@ -549,7 +553,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
),
|
||||
const Divider(),
|
||||
const SizedBox(height: 24),
|
||||
Text('Danger Zone',
|
||||
Text(l10n.profileDangerZone,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
|
|
@ -567,13 +571,13 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
ListTile(
|
||||
leading: const Icon(Icons.refresh,
|
||||
color: AppTheme.errorColor),
|
||||
title: const Text('Reset Progress',
|
||||
title: Text(l10n.profileResetProgress,
|
||||
style: TextStyle(color: AppTheme.errorColor)),
|
||||
subtitle: const Text(
|
||||
'Resets Level, XP and Training History'),
|
||||
subtitle: Text(
|
||||
l10n.profileResetProgressSubtitle),
|
||||
onTap: () => _confirmDangerAction(
|
||||
'Reset Progress?',
|
||||
'This will delete all your workouts and reset your Level to 1. This cannot be undone.',
|
||||
l10n.profileResetConfirmTitle,
|
||||
l10n.profileResetConfirmBody,
|
||||
() async {
|
||||
setState(() => _isLoading = true);
|
||||
await userRepo.resetProgress();
|
||||
|
|
@ -588,13 +592,13 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
ListTile(
|
||||
leading: const Icon(Icons.delete_forever,
|
||||
color: AppTheme.errorColor),
|
||||
title: const Text('Delete Account',
|
||||
title: Text(l10n.profileDeleteAccount,
|
||||
style: TextStyle(color: AppTheme.errorColor)),
|
||||
subtitle: const Text(
|
||||
'Permanently delete your account and data'),
|
||||
subtitle: Text(
|
||||
l10n.profileDeleteAccountSubtitle),
|
||||
onTap: () => _confirmDangerAction(
|
||||
'Delete Account?',
|
||||
'Are you sure you want to delete your account? All data will be lost forever.',
|
||||
l10n.profileDeleteConfirmTitle,
|
||||
l10n.profileDeleteConfirmBody,
|
||||
() async {
|
||||
setState(() => _isLoading = true);
|
||||
try {
|
||||
|
|
@ -621,7 +625,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
if (mounted) context.go('/login');
|
||||
},
|
||||
icon: const Icon(Icons.logout),
|
||||
label: const Text('LOGOUT'),
|
||||
label: Text(l10n.commonLogout),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
|
|
@ -637,6 +641,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
|
||||
Widget _buildTemplateSelector() {
|
||||
final current = _getTemplateFromSettings(_user?.inventorySettings ?? {});
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
|
@ -644,31 +649,31 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
_RadioTile<AccessoryTemplate>(
|
||||
value: AccessoryTemplate.none,
|
||||
groupValue: current,
|
||||
title: 'Strength Only',
|
||||
subtitle: 'Main Lifts + FSL. Pure & Fast.',
|
||||
title: l10n.templateStrengthOnly,
|
||||
subtitle: l10n.templateStrengthOnlyDesc,
|
||||
onChanged: (val) => _updateTemplate(val!),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
_RadioTile<AccessoryTemplate>(
|
||||
value: AccessoryTemplate.hypertrophy,
|
||||
groupValue: current,
|
||||
title: 'Hypertrophy Support',
|
||||
subtitle: 'Bodybuilding accessories to build muscle armor.',
|
||||
title: l10n.templateHypertrophy,
|
||||
subtitle: l10n.templateHypertrophyDesc,
|
||||
onChanged: (val) => _updateTemplate(val!),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
_RadioTile<AccessoryTemplate>(
|
||||
value: AccessoryTemplate.conditioning,
|
||||
groupValue: current,
|
||||
title: 'The Engine (Conditioning)',
|
||||
subtitle: '15 min Kettlebell intervals to boost stamina.',
|
||||
title: l10n.templateConditioning,
|
||||
subtitle: l10n.templateConditioningDesc,
|
||||
onChanged: (val) => _updateTemplate(val!),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.fromLTRB(16, 24, 16, 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 24, 16, 8),
|
||||
child: Text(
|
||||
'ACTIVE JOURNEYS',
|
||||
style: TextStyle(
|
||||
l10n.templateActiveJourneys,
|
||||
style: const TextStyle(
|
||||
color: Colors.grey,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
@ -678,8 +683,8 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
|
|||
_RadioTile<AccessoryTemplate>(
|
||||
value: AccessoryTemplate.journeyPullup,
|
||||
groupValue: current,
|
||||
title: 'Quest: The First Pull-Up',
|
||||
subtitle: 'Specific progression to master your bodyweight.',
|
||||
title: l10n.templatePullupJourney,
|
||||
subtitle: l10n.templatePullupJourneyDesc,
|
||||
onChanged: (val) => _updateTemplate(val!),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -108,8 +108,8 @@ class _HubScreenState extends ConsumerState<HubScreen> {
|
|||
if (!found) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Cycle complete! Finish it in stats.')),
|
||||
SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.hubCycleComplete)),
|
||||
);
|
||||
}
|
||||
return;
|
||||
|
|
@ -224,8 +224,8 @@ class _HubScreenState extends ConsumerState<HubScreen> {
|
|||
),
|
||||
const SizedBox(height: 8),
|
||||
if (sets >= 20)
|
||||
const Text('⚠️ HARDCORE MODE',
|
||||
style: TextStyle(
|
||||
Text(l10n.missionBriefingHardcore,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.errorColor,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold)),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:slrpg_app/l10n/app_localizations.dart';
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
|
||||
class StartRaidButton extends StatefulWidget {
|
||||
|
|
@ -82,7 +83,7 @@ class _StartRaidButtonState extends State<StartRaidButton>
|
|||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'START RAID',
|
||||
AppLocalizations.of(context)!.lobbyStartRaid,
|
||||
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
fontSize: 20,
|
||||
color: Colors.black,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class QuestLogScreen extends ConsumerWidget {
|
|||
length: 2,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Quest Log'),
|
||||
title: Text(l10n.historyTitle),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => context.go('/hub'),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:slrpg_app/l10n/app_localizations.dart';
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
import '../../domain/entities/avatar_config.dart';
|
||||
import 'avatar_renderer.dart';
|
||||
|
|
@ -38,6 +39,7 @@ class _AvatarEditorState extends State<AvatarEditor> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
|
|
@ -52,9 +54,9 @@ class _AvatarEditorState extends State<AvatarEditor> {
|
|||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: SegmentedButton<String>(
|
||||
segments: const [
|
||||
ButtonSegment(value: 'male', label: Text('Male')),
|
||||
ButtonSegment(value: 'female', label: Text('Female')),
|
||||
segments: [
|
||||
ButtonSegment(value: 'male', label: Text(l10n.genderMale)),
|
||||
ButtonSegment(value: 'female', label: Text(l10n.genderFemale)),
|
||||
],
|
||||
selected: {_gender},
|
||||
onSelectionChanged: (Set<String> newSelection) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:slrpg_app/l10n/app_localizations.dart';
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
import '../../../gamification/data/repositories/quest_repository.dart';
|
||||
import '../../../../shared/data/local/app_database.dart';
|
||||
|
|
@ -11,6 +12,7 @@ class QuestBoardWidget extends ConsumerWidget {
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final questRepo = ref.watch(questRepositoryProvider);
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return StreamBuilder<List<QuestCollection>>(
|
||||
stream: questRepo.watchQuests(),
|
||||
|
|
@ -39,7 +41,7 @@ class QuestBoardWidget extends ConsumerWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'DAILY BOUNTIES',
|
||||
l10n.questDailyBounties,
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: AppTheme.secondaryColor,
|
||||
letterSpacing: 1.5,
|
||||
|
|
@ -47,8 +49,9 @@ class QuestBoardWidget extends ConsumerWidget {
|
|||
),
|
||||
GestureDetector(
|
||||
onTap: () => context.go('/quests'),
|
||||
child: const Text('VIEW ALL >',
|
||||
style: TextStyle(fontSize: 10, color: Colors.grey)),
|
||||
child: Text(l10n.questViewAll,
|
||||
style:
|
||||
const TextStyle(fontSize: 10, color: Colors.grey)),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:slrpg_app/l10n/app_localizations.dart';
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
import '../../../../shared/data/local/app_database.dart';
|
||||
import '../../data/repositories/quest_repository.dart';
|
||||
|
|
@ -26,13 +27,14 @@ class _QuestItemState extends ConsumerState<QuestItem> {
|
|||
await questRepo.claimQuest(widget.quest.id);
|
||||
|
||||
if (mounted) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const Icon(Icons.check_circle, color: AppTheme.successColor),
|
||||
const SizedBox(width: 8),
|
||||
Text('Reward collected: ${widget.quest.rewardXP} XP!'),
|
||||
Text(l10n.questRewardCollected(widget.quest.rewardXP)),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.black87,
|
||||
|
|
@ -164,7 +166,8 @@ class _QuestItemState extends ConsumerState<QuestItem> {
|
|||
height: 12,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2, color: Colors.white))
|
||||
: const Text('CLAIM', style: TextStyle(fontSize: 12)),
|
||||
: Text(AppLocalizations.of(context)!.questClaim,
|
||||
style: const TextStyle(fontSize: 12)),
|
||||
)
|
||||
else if (widget.quest.rewardItem != null && !isClaimed)
|
||||
const Icon(Icons.inventory_2,
|
||||
|
|
|
|||
|
|
@ -214,8 +214,27 @@ class _ExerciseDetailRow extends StatelessWidget {
|
|||
|
||||
const _ExerciseDetailRow({required this.exercise});
|
||||
|
||||
String _getLocalizedName(BuildContext context, String rawName) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
switch (rawName) {
|
||||
case 'Back Squat':
|
||||
return l10n.exerciseSquat;
|
||||
case 'Weighted Pull-up':
|
||||
return l10n.exercisePullup;
|
||||
case 'Pendlay Row':
|
||||
return l10n.exerciseRow;
|
||||
case 'Weighted Dip':
|
||||
return l10n.exerciseDip;
|
||||
case 'Bench Press':
|
||||
return l10n.exerciseBench;
|
||||
default:
|
||||
return rawName;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Column(
|
||||
|
|
@ -230,7 +249,7 @@ class _ExerciseDetailRow extends StatelessWidget {
|
|||
margin: const EdgeInsets.only(right: 8),
|
||||
),
|
||||
Text(
|
||||
exercise.exerciseName,
|
||||
_getLocalizedName(context, exercise.exerciseName),
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold, color: AppTheme.textSecondary),
|
||||
),
|
||||
|
|
@ -256,7 +275,7 @@ class _ExerciseDetailRow extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
Text(
|
||||
'${set.targetWeightTotal} kg',
|
||||
'${set.targetWeightTotal} ${l10n.unitKg}',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Row(
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ class _InventoryScreenState extends ConsumerState<InventoryScreen> {
|
|||
setState(() => _isLoading = false);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Error saving: $e'),
|
||||
content: Text(l10n.inventorySaveError(e.toString())),
|
||||
backgroundColor: AppTheme.errorColor),
|
||||
);
|
||||
}
|
||||
|
|
@ -238,7 +238,7 @@ class _InventoryScreenState extends ConsumerState<InventoryScreen> {
|
|||
min: 10,
|
||||
max: 25,
|
||||
divisions: 6,
|
||||
label: '$_barWeight kg',
|
||||
label: '$_barWeight ${l10n.unitKg}',
|
||||
activeColor: AppTheme.primaryColor,
|
||||
onChanged: (val) => setState(() {
|
||||
_barWeight = val;
|
||||
|
|
@ -246,7 +246,7 @@ class _InventoryScreenState extends ConsumerState<InventoryScreen> {
|
|||
}),
|
||||
),
|
||||
),
|
||||
Text('${_barWeight.toStringAsFixed(1)} kg',
|
||||
Text('${_barWeight.toStringAsFixed(1)} ${l10n.unitKg}',
|
||||
style:
|
||||
const TextStyle(fontWeight: FontWeight.bold)),
|
||||
],
|
||||
|
|
@ -398,6 +398,7 @@ class _InventoryScreenState extends ConsumerState<InventoryScreen> {
|
|||
}
|
||||
|
||||
Widget _buildBandChip(String color, int resistance, bool isSelected) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
|
|
@ -430,7 +431,7 @@ class _InventoryScreenState extends ConsumerState<InventoryScreen> {
|
|||
if (isSelected) const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'$color (~${resistance}kg)',
|
||||
'$color (~$resistance${l10n.unitKg})',
|
||||
style: TextStyle(
|
||||
color: isSelected
|
||||
? _getBandColor(color)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:slrpg_app/l10n/app_localizations.dart';
|
||||
import 'package:slrpg_app/src/features/multiplayer/domain/entities/leaderboard_entry.dart';
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
import '../../../../shared/data/repositories/user_repository.dart';
|
||||
|
|
@ -20,10 +21,11 @@ class _LeaderboardScreenState extends ConsumerState<LeaderboardScreen> {
|
|||
Widget build(BuildContext context) {
|
||||
final leaderboardAsync = ref.watch(leaderboardProvider);
|
||||
final currentUserAsync = ref.watch(userRepositoryProvider).getLocalUser();
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('HALL OF FAME'),
|
||||
title: Text(l10n.leaderboardTitle),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => context.go('/hub'),
|
||||
|
|
@ -54,7 +56,7 @@ class _LeaderboardScreenState extends ConsumerState<LeaderboardScreen> {
|
|||
leading: _buildRankBadge(entry.rank),
|
||||
title: Text(
|
||||
entry.name.isEmpty
|
||||
? 'Hero #${entry.id.substring(0, 5)}'
|
||||
? l10n.leaderboardHero(entry.id.substring(0, 5))
|
||||
: entry.name,
|
||||
style: TextStyle(
|
||||
fontWeight:
|
||||
|
|
@ -62,7 +64,8 @@ class _LeaderboardScreenState extends ConsumerState<LeaderboardScreen> {
|
|||
color: isMe ? AppTheme.primaryColor : Colors.white,
|
||||
),
|
||||
),
|
||||
subtitle: Text('Level ${entry.level} • ${entry.xp} XP'),
|
||||
subtitle: Text(
|
||||
l10n.leaderboardSubtitle(entry.level, entry.xp)),
|
||||
trailing: _buildAvatarPreview(entry),
|
||||
),
|
||||
);
|
||||
|
|
@ -71,7 +74,8 @@ class _LeaderboardScreenState extends ConsumerState<LeaderboardScreen> {
|
|||
});
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (err, stack) => Center(child: Text('Error: $err')),
|
||||
error: (err, stack) =>
|
||||
Center(child: Text(l10n.setupFailed(err.toString()))),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class _LobbyScreenState extends ConsumerState<LobbyScreen> {
|
|||
extra: {'week': next['week'], 'day': next['day']});
|
||||
}
|
||||
});
|
||||
return const Center(child: Text('Entering Battle...'));
|
||||
return Center(child: Text(l10n.lobbyStatusEntering));
|
||||
}
|
||||
return Center(child: Text(l10n.lobbyStatusActive));
|
||||
},
|
||||
|
|
@ -243,6 +243,7 @@ class _MemberCard extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
child: ListTile(
|
||||
|
|
@ -253,7 +254,7 @@ class _MemberCard extends StatelessWidget {
|
|||
? AvatarRenderer(config: member.avatar!, size: 40)
|
||||
: const CircleAvatar(child: Icon(Icons.person)),
|
||||
),
|
||||
title: Text(member.username ?? 'Unknown'),
|
||||
title: Text(member.username ?? l10n.unknownMember),
|
||||
trailing: member.isReady
|
||||
? const Icon(Icons.check_circle, color: Colors.green)
|
||||
: const Icon(Icons.circle_outlined, color: Colors.grey),
|
||||
|
|
|
|||
|
|
@ -112,9 +112,10 @@ class _AvatarSetupScreenState extends ConsumerState<AvatarSetupScreen> {
|
|||
}
|
||||
} catch (e, stackTrace) {
|
||||
if (mounted) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Setup failed: $e'),
|
||||
content: Text(l10n.setupFailed(e.toString())),
|
||||
backgroundColor: AppTheme.errorColor,
|
||||
duration: const Duration(seconds: 5),
|
||||
),
|
||||
|
|
@ -196,7 +197,7 @@ class _AvatarSetupScreenState extends ConsumerState<AvatarSetupScreen> {
|
|||
prefixIcon: Icon(Icons.lock),
|
||||
),
|
||||
validator: (v) =>
|
||||
(v?.length ?? 0) < 8 ? 'Min 8 characters' : null,
|
||||
(v?.length ?? 0) < 8 ? l10n.profilePassMinChars : null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ class _BodyweightInputScreenState extends ConsumerState<BodyweightInputScreen> {
|
|||
),
|
||||
),
|
||||
Text(
|
||||
_useKg ? 'kg' : 'lbs',
|
||||
_useKg ? l10n.unitKg : l10n.unitLbs,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -179,10 +179,11 @@ class _InventorySetupScreenState extends ConsumerState<InventorySetupScreen> {
|
|||
log('Stack trace: $stackTrace');
|
||||
|
||||
if (mounted) {
|
||||
String message = 'Setup failed: ${e.toString()}';
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
String message = l10n.setupFailed(e.toString());
|
||||
if (e.toString().toLowerCase().contains('unique') ||
|
||||
e.toString().toLowerCase().contains('email')) {
|
||||
message = 'Email already exists. Please login or use another email.';
|
||||
message = l10n.setupEmailExists;
|
||||
}
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
|
@ -261,14 +262,14 @@ class _InventorySetupScreenState extends ConsumerState<InventorySetupScreen> {
|
|||
min: 10,
|
||||
max: 25,
|
||||
divisions: 3,
|
||||
label: '${_barWeight.toStringAsFixed(0)} kg',
|
||||
label: '${_barWeight.toStringAsFixed(0)} ${l10n.unitKg}',
|
||||
activeColor: AppTheme.primaryColor,
|
||||
onChanged: (value) {
|
||||
setState(() => _barWeight = value);
|
||||
},
|
||||
),
|
||||
Text(
|
||||
'${_barWeight.toStringAsFixed(0)} kg',
|
||||
'${_barWeight.toStringAsFixed(0)} ${l10n.unitKg}',
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||
color: AppTheme.primaryColor,
|
||||
),
|
||||
|
|
@ -459,7 +460,7 @@ class _InventorySetupScreenState extends ConsumerState<InventorySetupScreen> {
|
|||
if (isSelected) const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'$color (~${resistance}kg)',
|
||||
'$color (~$resistance${AppLocalizations.of(context)!.unitKg})',
|
||||
style: TextStyle(
|
||||
color: isSelected ? Colors.white : AppTheme.textSecondary,
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ class _StrengthTestScreenState extends ConsumerState<StrengthTestScreen> {
|
|||
const SizedBox(height: 32),
|
||||
_ExerciseCard(
|
||||
title: l10n.strengthLegs,
|
||||
exerciseName: 'Back Squat',
|
||||
exerciseName: l10n.exerciseSquat,
|
||||
icon: Icons.accessibility_new,
|
||||
weightController: _squatWeightController,
|
||||
repsController: _squatRepsController,
|
||||
|
|
@ -231,8 +231,8 @@ class _StrengthTestScreenState extends ConsumerState<StrengthTestScreen> {
|
|||
const SizedBox(height: 16),
|
||||
_AdaptiveExerciseCard(
|
||||
slotTitle: l10n.strengthPull,
|
||||
primaryName: 'Weighted Pull-up',
|
||||
secondaryName: 'Pendlay Row',
|
||||
primaryName: l10n.exercisePullup,
|
||||
secondaryName: l10n.exerciseRow,
|
||||
icon: Icons.north,
|
||||
isCapable: _canDoPullup,
|
||||
onToggleCapability: (val) {
|
||||
|
|
@ -254,12 +254,10 @@ class _StrengthTestScreenState extends ConsumerState<StrengthTestScreen> {
|
|||
repsController: _pullRepsController,
|
||||
weightLabel: _canDoPullup
|
||||
? (_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)',
|
||||
? l10n.bandAssistanceLabel
|
||||
: l10n.addWeightLabel)
|
||||
: l10n.rowWeightLabel,
|
||||
repsLabel: _canDoPullup ? l10n.repsLabel : l10n.reps5rmLabel,
|
||||
showResults: true,
|
||||
result1RM: _calculated1RMs['pullup'] ?? 0,
|
||||
resultTM: _calculatedTMs['pullup'] ?? 0,
|
||||
|
|
@ -268,8 +266,8 @@ class _StrengthTestScreenState extends ConsumerState<StrengthTestScreen> {
|
|||
const SizedBox(height: 16),
|
||||
_AdaptiveExerciseCard(
|
||||
slotTitle: l10n.strengthPush,
|
||||
primaryName: 'Weighted Dip',
|
||||
secondaryName: 'Bench Press',
|
||||
primaryName: l10n.exerciseDip,
|
||||
secondaryName: l10n.exerciseBench,
|
||||
icon: Icons.south,
|
||||
isCapable: _canDoDip,
|
||||
onToggleCapability: (val) {
|
||||
|
|
@ -292,11 +290,10 @@ class _StrengthTestScreenState extends ConsumerState<StrengthTestScreen> {
|
|||
repsController: _pushRepsController,
|
||||
weightLabel: _canDoDip
|
||||
? (_isAssistedDip
|
||||
? 'Band Assistance (kg)'
|
||||
: 'Added Weight (kg)')
|
||||
: 'Weight (kg)',
|
||||
// weightLabel: _canDoDip ? 'Add. Weight (kg)' : 'Weight (kg)',
|
||||
repsLabel: 'Reps',
|
||||
? l10n.bandAssistanceLabel
|
||||
: l10n.addWeightLabel)
|
||||
: l10n.weightLabel,
|
||||
repsLabel: l10n.repsLabel,
|
||||
showWeightInput: true,
|
||||
showResults: true,
|
||||
result1RM: _calculated1RMs['dip'] ?? 0,
|
||||
|
|
@ -416,7 +413,7 @@ class _ExerciseCard extends StatelessWidget {
|
|||
: l10n.weightLabel,
|
||||
isDense: true),
|
||||
onChanged: (_) => onChanged(),
|
||||
validator: (v) => v!.isEmpty ? 'Required' : null,
|
||||
validator: (v) => v!.isEmpty ? l10n.commonRequired : null,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
|
@ -428,7 +425,7 @@ class _ExerciseCard extends StatelessWidget {
|
|||
decoration: InputDecoration(
|
||||
labelText: l10n.repsLabel, isDense: true),
|
||||
onChanged: (_) => onChanged(),
|
||||
validator: (v) => v!.isEmpty ? 'Required' : null,
|
||||
validator: (v) => v!.isEmpty ? l10n.commonRequired : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -555,7 +552,7 @@ class _AdaptiveExerciseCard extends StatelessWidget {
|
|||
if (!isCapable) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Adjusted: ${"Wendler 5/3/1"}',
|
||||
l10n.adjustedWendler,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.secondaryColor,
|
||||
fontSize: 12,
|
||||
|
|
@ -578,7 +575,7 @@ class _AdaptiveExerciseCard extends StatelessWidget {
|
|||
decoration: InputDecoration(
|
||||
labelText: weightLabel, isDense: true),
|
||||
onChanged: (_) => onChanged(),
|
||||
validator: (v) => v!.isEmpty ? 'Required' : null,
|
||||
validator: (v) => v!.isEmpty ? l10n.commonRequired : null,
|
||||
),
|
||||
)
|
||||
else
|
||||
|
|
@ -592,7 +589,7 @@ class _AdaptiveExerciseCard extends StatelessWidget {
|
|||
decoration:
|
||||
InputDecoration(labelText: repsLabel, isDense: true),
|
||||
onChanged: (_) => onChanged(),
|
||||
validator: (v) => v!.isEmpty ? 'Required' : null,
|
||||
validator: (v) => v!.isEmpty ? l10n.commonRequired : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -626,7 +623,7 @@ class _ResultBox extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(l10n.est1rm),
|
||||
Text('${rm.toStringAsFixed(1)} kg',
|
||||
Text('${rm.toStringAsFixed(1)} ${l10n.unitKg}',
|
||||
style: Theme.of(context).textTheme.bodyLarge),
|
||||
],
|
||||
),
|
||||
|
|
@ -635,7 +632,7 @@ class _ResultBox extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(l10n.trainingMaxLabel),
|
||||
Text('${tm.toStringAsFixed(1)} kg',
|
||||
Text('${tm.toStringAsFixed(1)} ${l10n.unitKg}',
|
||||
style: const TextStyle(
|
||||
color: AppTheme.primaryColor,
|
||||
fontWeight: FontWeight.bold)),
|
||||
|
|
|
|||
|
|
@ -176,8 +176,8 @@ class _PrivacyPolicyScreenState extends ConsumerState<PrivacyPolicyScreen> {
|
|||
|
||||
await Share.shareXFiles(
|
||||
[XFile(file.path)],
|
||||
subject: 'SLRPG Data Export',
|
||||
text: 'Your SLRPG training data (GDPR export)',
|
||||
subject: l10n.exportEmailSubject,
|
||||
text: l10n.exportEmailBody,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
|
|
@ -207,7 +207,7 @@ class _PrivacyPolicyScreenState extends ConsumerState<PrivacyPolicyScreen> {
|
|||
Future<void> _showDeleteAccountDialog() async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final confirmationController = TextEditingController();
|
||||
final expectedText = l10n.localeName == 'de' ? 'LÖSCHEN' : 'DELETE';
|
||||
final expectedText = l10n.deleteConfirmationWord;
|
||||
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
|
|
|
|||
|
|
@ -208,15 +208,15 @@ class _StatsScreenState extends ConsumerState<StatsScreen> {
|
|||
String getLabel(String id) {
|
||||
switch (id) {
|
||||
case 'squat':
|
||||
return 'Squat';
|
||||
return l10n.exerciseSquat;
|
||||
case 'pullup':
|
||||
return 'Pull-up';
|
||||
return l10n.exercisePullup;
|
||||
case 'row':
|
||||
return 'Row';
|
||||
return l10n.exerciseRow;
|
||||
case 'dip':
|
||||
return 'Dip';
|
||||
return l10n.exerciseDip;
|
||||
case 'bench':
|
||||
return 'Bench';
|
||||
return l10n.exerciseBench;
|
||||
default:
|
||||
return id;
|
||||
}
|
||||
|
|
@ -358,13 +358,13 @@ class _CurrentCycleCard extends StatelessWidget {
|
|||
const SizedBox(height: 16),
|
||||
_StatRow(
|
||||
label: l10n.exerciseSquat,
|
||||
value: '${tms['squat'].toStringAsFixed(2)} kg'),
|
||||
value: '${tms['squat'].toStringAsFixed(2)} ${l10n.unitKg}'),
|
||||
_StatRow(
|
||||
label: getLabel(pullVariant),
|
||||
value: '${tms['pullup'].toStringAsFixed(2)} kg'),
|
||||
value: '${tms['pullup'].toStringAsFixed(2)} ${l10n.unitKg}'),
|
||||
_StatRow(
|
||||
label: getLabel(pushVariant),
|
||||
value: '${tms['dip'].toStringAsFixed(2)} kg'),
|
||||
value: '${tms['dip'].toStringAsFixed(2)} ${l10n.unitKg}'),
|
||||
const SizedBox(height: 32),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
|
|
@ -471,6 +471,7 @@ class _DiffRow extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final diff = newVal - oldVal;
|
||||
final isPositive = diff > 0;
|
||||
|
||||
|
|
@ -491,8 +492,9 @@ class _DiffRow extends StatelessWidget {
|
|||
style: const TextStyle(
|
||||
color: AppTheme.successColor, fontWeight: FontWeight.bold))
|
||||
else
|
||||
const Text('STALLED',
|
||||
style: TextStyle(color: AppTheme.secondaryColor, fontSize: 12)),
|
||||
Text(l10n.statsStalled,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.secondaryColor, fontSize: 12)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ class ExerciseGuideSheet extends StatelessWidget {
|
|||
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)),
|
||||
child: Text(l10n.guideNotFound,
|
||||
textAlign: TextAlign.center, style: const TextStyle(color: Colors.grey)),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ class ExerciseGuideSheet extends StatelessWidget {
|
|||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildDifficultyBadge(guide.difficulty),
|
||||
_buildDifficultyBadge(context, guide.difficulty),
|
||||
const SizedBox(height: 24),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
|
|
@ -80,18 +80,18 @@ class ExerciseGuideSheet extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
_buildSectionTitle(context, 'EXECUTION'),
|
||||
_buildSectionTitle(context, l10n.guideExecution),
|
||||
const SizedBox(height: 16),
|
||||
...guide.steps
|
||||
.asMap()
|
||||
.entries
|
||||
.map((entry) => _buildStep(entry.key + 1, entry.value)),
|
||||
const SizedBox(height: 32),
|
||||
_buildSectionTitle(context, 'COMMON MISTAKES'),
|
||||
_buildSectionTitle(context, l10n.guideMistakes),
|
||||
const SizedBox(height: 16),
|
||||
...guide.commonMistakes.map((m) => _buildMistake(m)),
|
||||
const SizedBox(height: 32),
|
||||
_buildSectionTitle(context, 'ATTRIBUTES AFFECTED'),
|
||||
_buildSectionTitle(context, l10n.guideAttributes),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
|
|
@ -118,17 +118,23 @@ class ExerciseGuideSheet extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildDifficultyBadge(String diff) {
|
||||
Widget _buildDifficultyBadge(BuildContext context, String diff) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
Color color;
|
||||
String text = diff;
|
||||
|
||||
switch (diff) {
|
||||
case 'Novice':
|
||||
color = Colors.green;
|
||||
text = l10n.guideDiffNovice;
|
||||
break;
|
||||
case 'Adept':
|
||||
color = Colors.orange;
|
||||
text = l10n.guideDiffAdept;
|
||||
break;
|
||||
case 'Master':
|
||||
color = AppTheme.errorColor;
|
||||
text = l10n.guideDiffMaster;
|
||||
break;
|
||||
default:
|
||||
color = Colors.grey;
|
||||
|
|
@ -143,7 +149,7 @@ class ExerciseGuideSheet extends StatelessWidget {
|
|||
border: Border.all(color: color.withValues(alpha: 0.5)),
|
||||
),
|
||||
child: Text(
|
||||
diff.toUpperCase(),
|
||||
text.toUpperCase(),
|
||||
style: TextStyle(
|
||||
color: color, fontSize: 12, fontWeight: FontWeight.bold),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -502,15 +502,15 @@ class _BattleScreenState extends ConsumerState<BattleScreen> {
|
|||
|
||||
if (state.error != null) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Battle')),
|
||||
body: Center(child: Text('Error: ${state.error}')),
|
||||
appBar: AppBar(title: Text(l10n.battleTitle)),
|
||||
body: Center(child: Text(l10n.setupFailed(state.error.toString()))),
|
||||
);
|
||||
}
|
||||
|
||||
if (state.exercises.isEmpty) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Battle')),
|
||||
body: const Center(child: Text('No exercises configured')),
|
||||
appBar: AppBar(title: Text(l10n.battleTitle)),
|
||||
body: Center(child: Text(l10n.battleNoExercises)),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -737,7 +737,7 @@ class _BattleScreenState extends ConsumerState<BattleScreen> {
|
|||
color: AppTheme.primaryColor),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'${currentSet.targetWeightTotal} kg',
|
||||
'${currentSet.targetWeightTotal} ${l10n.unitKg}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24,
|
||||
|
|
@ -842,16 +842,16 @@ class _BattleScreenState extends ConsumerState<BattleScreen> {
|
|||
size: 48, color: AppTheme.successColor),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Time Complete!',
|
||||
l10n.timerComplete,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'How many reps did you complete?',
|
||||
style: TextStyle(color: Colors.grey),
|
||||
Text(
|
||||
l10n.amrapResultBody,
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Row(
|
||||
|
|
@ -894,9 +894,9 @@ class _BattleScreenState extends ConsumerState<BattleScreen> {
|
|||
backgroundColor: AppTheme.successColor,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: const Text(
|
||||
'CONFIRM',
|
||||
style: TextStyle(
|
||||
child: Text(
|
||||
l10n.commonConfirm,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
|
|
@ -1165,7 +1165,7 @@ class _BattleScreenState extends ConsumerState<BattleScreen> {
|
|||
|
||||
AppBar _buildAppBar(AppLocalizations l10n) {
|
||||
return AppBar(
|
||||
title: Text('Week ${widget.week} - Day ${widget.day}'),
|
||||
title: Text(l10n.battleWeekDay(widget.week, widget.day)),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => _showAbandonDialog(l10n),
|
||||
|
|
@ -1322,7 +1322,7 @@ class _BattleScreenState extends ConsumerState<BattleScreen> {
|
|||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${nextSet.repsTarget} x ${nextSet.targetWeightTotal > 0 ? "${nextSet.targetWeightTotal} kg" : "Bodyweight"}',
|
||||
'${nextSet.repsTarget} x ${nextSet.targetWeightTotal > 0 ? "${nextSet.targetWeightTotal} ${l10n.unitKg}" : l10n.bodyweight}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24,
|
||||
|
|
@ -1394,6 +1394,7 @@ class _BattleScreenState extends ConsumerState<BattleScreen> {
|
|||
int completedHP,
|
||||
int totalHP,
|
||||
) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final state = ref.watch(battleControllerProvider);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
|
|
@ -1435,7 +1436,7 @@ class _BattleScreenState extends ConsumerState<BattleScreen> {
|
|||
),
|
||||
),
|
||||
Text(
|
||||
'${currentSet.repsTarget} Reps per Round',
|
||||
l10n.battleRepsPerRound(currentSet.repsTarget),
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
],
|
||||
|
|
@ -1505,7 +1506,7 @@ class _BattleScreenState extends ConsumerState<BattleScreen> {
|
|||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'WEIGHT: ${currentSet.targetWeightTotal} kg',
|
||||
l10n.battleWeightKg(currentSet.targetWeightTotal),
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: AppTheme.primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:slrpg_app/l10n/app_localizations.dart';
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
|
||||
|
|
@ -127,6 +128,7 @@ class _EmomTimerWidgetState extends State<EmomTimerWidget>
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final progress = 1.0 - (_secondsRemaining / widget.intervalSeconds);
|
||||
|
||||
return Column(
|
||||
|
|
@ -139,7 +141,7 @@ class _EmomTimerWidgetState extends State<EmomTimerWidget>
|
|||
border: Border.all(color: AppTheme.primaryColor),
|
||||
),
|
||||
child: Text(
|
||||
'ROUND ${widget.currentSet} / ${widget.totalSets}',
|
||||
l10n.timerRound(widget.currentSet, widget.totalSets),
|
||||
style: const TextStyle(
|
||||
color: AppTheme.primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
@ -192,7 +194,7 @@ class _EmomTimerWidgetState extends State<EmomTimerWidget>
|
|||
widget.currentSet == 1 &&
|
||||
_secondsRemaining == widget.intervalSeconds)
|
||||
Text(
|
||||
'READY?',
|
||||
l10n.timerReady,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
|
|
@ -200,7 +202,7 @@ class _EmomTimerWidgetState extends State<EmomTimerWidget>
|
|||
)
|
||||
else if (!_isRunning)
|
||||
Text(
|
||||
'PAUSED',
|
||||
l10n.timerPaused,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
|
|
@ -222,8 +224,8 @@ class _EmomTimerWidgetState extends State<EmomTimerWidget>
|
|||
icon: const Icon(Icons.play_arrow),
|
||||
label: Text(widget.currentSet == 1 &&
|
||||
_secondsRemaining == widget.intervalSeconds
|
||||
? 'IGNITE ENGINE'
|
||||
: 'RESUME'),
|
||||
? l10n.timerIgnite
|
||||
: l10n.timerResume),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.successColor,
|
||||
foregroundColor: Colors.white,
|
||||
|
|
@ -237,7 +239,7 @@ class _EmomTimerWidgetState extends State<EmomTimerWidget>
|
|||
child: OutlinedButton.icon(
|
||||
onPressed: _pauseTimer,
|
||||
icon: const Icon(Icons.pause),
|
||||
label: const Text('PAUSE'),
|
||||
label: Text(l10n.timerPause),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: AppTheme.errorColor,
|
||||
side: const BorderSide(color: AppTheme.errorColor),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
import 'package:slrpg_app/l10n/app_localizations.dart';
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
|
||||
class TimerWidget extends StatefulWidget {
|
||||
|
|
@ -96,6 +97,7 @@ class _TimerWidgetState extends State<TimerWidget> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final progress = widget.durationSeconds > 0
|
||||
? _secondsRemaining / widget.durationSeconds
|
||||
: 0.0;
|
||||
|
|
@ -129,11 +131,11 @@ class _TimerWidgetState extends State<TimerWidget> {
|
|||
),
|
||||
),
|
||||
if (_isCompleted)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
'COMPLETE!',
|
||||
style: TextStyle(
|
||||
l10n.timerComplete,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.successColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
@ -153,7 +155,7 @@ class _TimerWidgetState extends State<TimerWidget> {
|
|||
ElevatedButton.icon(
|
||||
onPressed: _isRunning ? _pause : _start,
|
||||
icon: Icon(_isRunning ? Icons.pause : Icons.play_arrow),
|
||||
label: Text(_isRunning ? 'PAUSE' : 'START'),
|
||||
label: Text(_isRunning ? l10n.timerPause : 'START'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
_isRunning ? Colors.orange : AppTheme.primaryColor,
|
||||
|
|
@ -168,7 +170,7 @@ class _TimerWidgetState extends State<TimerWidget> {
|
|||
OutlinedButton.icon(
|
||||
onPressed: _reset,
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('RESET'),
|
||||
label: Text(l10n.timerReset),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: AppTheme.primaryColor,
|
||||
side: const BorderSide(color: AppTheme.primaryColor),
|
||||
|
|
@ -182,7 +184,7 @@ class _TimerWidgetState extends State<TimerWidget> {
|
|||
OutlinedButton.icon(
|
||||
onPressed: _skip,
|
||||
icon: const Icon(Icons.skip_next),
|
||||
label: const Text('SKIP'),
|
||||
label: Text(l10n.timerSkip),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.grey,
|
||||
side: const BorderSide(color: Colors.grey),
|
||||
|
|
@ -196,7 +198,7 @@ class _TimerWidgetState extends State<TimerWidget> {
|
|||
ElevatedButton.icon(
|
||||
onPressed: _reset,
|
||||
icon: const Icon(Icons.replay),
|
||||
label: const Text('RESTART'),
|
||||
label: Text(l10n.timerRestart),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class WorkoutContent extends StatelessWidget {
|
|||
children: [
|
||||
_InfoBox(
|
||||
label: l10n.battleWeight,
|
||||
value: '${currentSet.targetWeightTotal} kg',
|
||||
value: '${currentSet.targetWeightTotal} ${l10n.unitKg}',
|
||||
),
|
||||
_InfoBox(
|
||||
label: l10n.battleReps,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue