diff --git a/.gitignore b/.gitignore index 3820a95..84f4bd6 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,7 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +.env +.env.production +.env.development diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4428126..610d9a0 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,49 +1,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - \ No newline at end of file + diff --git a/assets/audio/beep_long.ogg b/assets/audio/beep_long.ogg new file mode 100644 index 0000000..1fe6edf Binary files /dev/null and b/assets/audio/beep_long.ogg differ diff --git a/assets/audio/beep_short.ogg b/assets/audio/beep_short.ogg new file mode 100644 index 0000000..73ef94a Binary files /dev/null and b/assets/audio/beep_short.ogg differ diff --git a/assets/images/backgrounds/commercial_gym.png b/assets/images/backgrounds/commercial_gym.png new file mode 100644 index 0000000..baad337 Binary files /dev/null and b/assets/images/backgrounds/commercial_gym.png differ diff --git a/assets/images/backgrounds/olympic_gym.png b/assets/images/backgrounds/olympic_gym.png new file mode 100644 index 0000000..eae984f Binary files /dev/null and b/assets/images/backgrounds/olympic_gym.png differ diff --git a/assets/images/backgrounds/street_park_night.png b/assets/images/backgrounds/street_park_night.png new file mode 100644 index 0000000..f6b4d5e Binary files /dev/null and b/assets/images/backgrounds/street_park_night.png differ diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 0000000..15338f2 --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,3 @@ +arb-dir: lib/l10n +template-arb-file: app_en.arb +output-localization-file: app_localizations.dart diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb new file mode 100644 index 0000000..1318757 --- /dev/null +++ b/lib/l10n/app_de.arb @@ -0,0 +1,310 @@ +{ + "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", + + "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", + "guidePullupMuscles": "Latissimus|Bizeps|Unterarme", + "guidePullupMistakes": "Schwingen (Kipping)|Halbe Wiederholungen|Schultern hochgezogen lassen", + + "guideDipTitle": "Weighted Dip", + "guideDipLore": "Ein fundamentaler Druck-Move, um Mauern zu überwinden.", + "guideDipSteps": "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", + "guideDipMuscles": "Brust|Trizeps|Vordere Schulter", + "guideDipMistakes": "Zu wenig Tiefe|Ellbogen wandern zu weit nach außen", + + "guideSquatTitle": "Low Bar Back Squat", + "guideSquatLore": "Die Mutter aller Schlachten. Trainiert den gesamten Körperpanzer.", + "guideSquatSteps": "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", + "guideSquatMuscles": "Quadrizeps|Gesäß|Core|Rückenstrecker", + "guideSquatMistakes": "Knie fallen nach innen|Rücken rundet ein|Zu wenig Tiefe", + + "guideBenchTitle": "Bench Press", + "guideBenchLore": "Der Standard-Test für reine Druckkraft.", + "guideBenchSteps": "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", + "guideBenchMuscles": "Brust|Trizeps|Vordere Schulter", + "guideBenchMistakes": "Ellbogen 90° abgespreizt (Verletzungsgefahr)|Hintern hebt ab", + + "guideOhpTitle": "Overhead Press", + "guideOhpLore": "Ein Objekt über den Kopf zu stemmen erfordert pure Stabilität.", + "guideOhpSteps": "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", + "guideOhpMuscles": "Schultern|Trizeps|Core", + "guideOhpMistakes": "Hohlkreuz (Rücklage)|Beine helfen mit (Push Press)", + + "guideRdlTitle": "Romanian Deadlift", + "guideRdlLore": "Baut die hintere Kette auf, essenziell für Stabilität.", + "guideRdlSteps": "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", + "guideRdlMuscles": "Beinbeuger (Hamstrings)|Gesäß|Unterer Rücken", + "guideRdlMistakes": "Rücken rundet ein|Hantel zu weit vom Körper weg", + + "guideRowTitle": "Pendlay Row", + "guideRowLore": "Explosive Zugkraft vom Boden. Für einen starken Rücken.", + "guideRowSteps": "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", + "guideRowMuscles": "Latissimus|Trapez|Hintere Schulter", + "guideRowMistakes": "Oberkörper richtet sich auf|Reißen mit Schwung", + + "guideCurlTitle": "Barbell Curl", + "guideCurlLore": "Isolierte Kraft für den finalen Schlag.", + "guideCurlSteps": "Stehender Stand, Hantel im Untergriff|Ellbogen bleiben fixiert am Körper|Hantel zur Brust curlen, oben kurz halten|Langsam ablassen", + "guideCurlMuscles": "Bizeps", + "guideCurlMistakes": "Schwingen aus der Hüfte|Ellbogen wandern nach vorne", + + "guideKbSwingTitle": "Kettlebell Swing", + "guideKbSwingLore": "Ballistische Kraft und Ausdauer. Die Hüfte ist der Motor.", + "guideKbSwingSteps": "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", + "guideKbSwingMuscles": "Gesäß|Beinbeuger|Core|Ausdauer", + "guideKbSwingMistakes": "Kniebeuge statt Hüftbeuge|Arme heben das Gewicht", + + "guideKbSnatchTitle": "Kettlebell Snatch", + "guideKbSnatchLore": "Der Zar der Kettlebell-Übungen. Totale Körperkontrolle.", + "guideKbSnatchSteps": "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", + "guideKbSnatchMuscles": "Gesamter Körper|Schultern|Griffkraft", + "guideKbSnatchMistakes": "Kugel knallt auf den Unterarm|Zu wenig Hüftkraft", + + "guideKbThrusterTitle": "Kettlebell Thruster", + "guideKbThrusterLore": "Eine brutale Kombination aus Squat und Press.", + "guideKbThrusterSteps": "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", + "guideKbThrusterMuscles": "Beine|Schultern|Lunge (Cardio)", + "guideKbThrusterMistakes": "Pause zwischen Squat und Press|Rücken rundet im Squat", + + "guideKbCleanPressTitle": "KB Clean & Press", + "guideKbCleanPressLore": "Zwei Bewegungen in Harmonie.", + "guideKbCleanPressSteps": "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)", + "guideKbCleanPressMuscles": "Schultern|Rücken|Beine", + "guideKbCleanPressMistakes": "Clean knallt auf Arm|Hohlkreuz beim Press", + + "guideFacePullTitle": "Band Face Pull", + "guideFacePullLore": "Schützt die Schultern vor dem Verschleiß des Kampfes.", + "guideFacePullSteps": "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", + "guideFacePullMuscles": "Hintere Schulter|Rotatorenmanschette", + "guideFacePullMistakes": "Ellbogen zu tief|Kopf nach vorne schieben", + + "guideAbWheelTitle": "Ab Wheel Rollout", + "guideAbWheelLore": "Ein Stahlkern, der jeden Treffer absorbiert.", + "guideAbWheelSteps": "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", + "guideAbWheelMuscles": "Core (Anti-Extension)", + "guideAbWheelMistakes": "Hohlkreuz (Gefährlich!)|Ziehen aus den Armen", + + "guidePlankTitle": "Plank", + "guidePlankLore": "Unbeweglich wie ein Fels in der Brandung.", + "guidePlankSteps": "Unterarmstütz, Körper bildet eine Linie|Gesäß und Bauch fest anspannen|Schulterblätter auseinanderdrücken|Atmen nicht vergessen!", + "guidePlankMuscles": "Core", + "guidePlankMistakes": "Hüfte hängt durch|Gesäß zu hoch", + + "codexTitle": "Kreaturen-Kodex", + + "enemyIronGolemName": "Eisengolem", + "enemyIronGolemTitle": "Das Gewicht der Erde", + "enemyIronGolemDesc": "Geschmiedet aus den tektonischen Platten der tiefen Erde, existiert der Eisengolem nur, um die Schwachen zu zermalmen. Er verkörpert die unerbittliche Schwerkraft, die auf eine schwere Last wirkt.\n\nEr respektiert nur eines: Die rohe Kraft der BEINE, die seinem erdrückenden Gewicht standhalten können.", + "enemyIronGolemNemesis": "Kniebeugen-Nemesis", + + "enemyGravityDemonName": "Schwerkraft-Dämon", + "enemyGravityDemonTitle": "Der Sog des Abgrunds", + "enemyGravityDemonDesc": "Ein Geist reiner Abwärtskraft, der sich an den Rücken von Abenteurern klammert. Er flüstert Lügen der Schwäche in dein Ohr, während er dich in den Abgrund zieht.\n\nNur wer einen Rücken aus Stahl und den Willen hat, sich hochzuziehen, kann seinem Griff entkommen.", + "enemyGravityDemonNemesis": "Klimmzug-Nemesis", + + "enemyPressurePhantomName": "Druck-Phantom", + "enemyPressurePhantomTitle": "Der unsichtbare Zermalmer", + "enemyPressurePhantomDesc": "Eine ätherische Entität, die die Luft um dich herum komprimiert. Es versucht, Brust und Schultern derer kollabieren zu lassen, die es wagen, dagegen zu drücken.\n\nBesiege es, indem du mit explosiver Dip-Kraft durch den Schmerz drückst.", + "enemyPressurePhantomNemesis": "Dip-Nemesis" +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb new file mode 100644 index 0000000..63dc211 --- /dev/null +++ b/lib/l10n/app_en.arb @@ -0,0 +1,310 @@ +{ + "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", + + "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", + "guidePullupMuscles": "Latissimus|Biceps|Forearms", + "guidePullupMistakes": "Swinging (Kipping)|Half reps|Shoulders shrugged up", + + "guideDipTitle": "Weighted Dip", + "guideDipLore": "A fundamental pushing move to overcome walls.", + "guideDipSteps": "Support yourself on the bars, arms straight|Lean forward slightly for more chest focus|Lower body until shoulders are below elbows|Push explosively back to start position", + "guideDipMuscles": "Chest|Triceps|Front Delt", + "guideDipMistakes": "Not enough depth|Elbows flaring out too much", + + "guideSquatTitle": "Low Bar Back Squat", + "guideSquatLore": "The mother of all battles. Trains the entire body armor.", + "guideSquatSteps": "Place bar on rear delts (not neck)|Feet shoulder width, toes slightly out|Inhale deeply (Bracing) and push hips back|Squat down (hip crease below knee)|Push up through heels/midfoot", + "guideSquatMuscles": "Quadriceps|Glutes|Core|Spinal Erectors", + "guideSquatMistakes": "Knees caving in|Back rounding|Not enough depth", + + "guideBenchTitle": "Bench Press", + "guideBenchLore": "The standard test for pure pushing power.", + "guideBenchSteps": "Lie on bench, eyes under bar|Feet planted firmly, slight arch in back|Lower bar controlled to lower chest|Press bar explosively up", + "guideBenchMuscles": "Chest|Triceps|Front Delt", + "guideBenchMistakes": "Elbows flared 90° (Injury risk)|Butt lifting off bench", + + "guideOhpTitle": "Overhead Press", + "guideOhpLore": "Pressing an object overhead requires pure stability.", + "guideOhpSteps": "Bar rests on front delts|Firm stance, squeeze glutes and abs|Move head back slightly, press bar vertically|At top, push head 'through the window' of arms", + "guideOhpMuscles": "Shoulders|Triceps|Core", + "guideOhpMistakes": "Excessive arching (Leaning back)|Using legs (Push Press)", + + "guideRdlTitle": "Romanian Deadlift", + "guideRdlLore": "Builds the posterior chain, essential for stability.", + "guideRdlSteps": "Start standing with bar|Push hips far back, legs stay nearly straight|Lower bar along legs until just below knees|Feel stretch in hamstrings and stand back up", + "guideRdlMuscles": "Hamstrings|Glutes|Lower Back", + "guideRdlMistakes": "Back rounding|Bar drifting away from body", + + "guideRowTitle": "Pendlay Row", + "guideRowLore": "Explosive pulling power from the floor. For a strong back.", + "guideRowSteps": "Torso parallel to floor, back straight|Bar rests dead on floor each rep|Pull bar explosively to lower sternum|Lower controlled, reset tension", + "guideRowMuscles": "Latissimus|Traps|Rear Delt", + "guideRowMistakes": "Torso raising up|Jerking with momentum", + + "guideCurlTitle": "Barbell Curl", + "guideCurlLore": "Isolated power for the finishing strike.", + "guideCurlSteps": "Standing stance, underhand grip|Elbows fixed at sides|Curl bar to chest, squeeze at top|Lower slowly", + "guideCurlMuscles": "Biceps", + "guideCurlMistakes": "Swinging from hips|Elbows drifting forward", + + "guideKbSwingTitle": "Kettlebell Swing", + "guideKbSwingLore": "Ballistic power and endurance. The hips are the engine.", + "guideKbSwingSteps": "Hip-width stance, KB on floor in front|Hike-Pass: Pull KB back between legs|Extend hips explosively (Snap!), KB flies to chest height|Let KB swing back controllably", + "guideKbSwingMuscles": "Glutes|Hamstrings|Core|Cardio", + "guideKbSwingMistakes": "Squatting instead of hinging|Arms lifting the weight", + + "guideKbSnatchTitle": "Kettlebell Snatch", + "guideKbSnatchLore": "The Tsar of Kettlebell exercises. Total body control.", + "guideKbSnatchSteps": "Start like Swing (One arm)|Hip power accelerates ball upwards|At head height: Punch through handle|Soft catch in lockout overhead", + "guideKbSnatchMuscles": "Full Body|Shoulders|Grip", + "guideKbSnatchMistakes": "Bell slamming on forearm|Not enough hip power", + + "guideKbThrusterTitle": "Kettlebell Thruster", + "guideKbThrusterLore": "A brutal combination of squat and press.", + "guideKbThrusterSteps": "KB in rack position (chest)|Deep squat|Use momentum from standing up to press KB overhead|Return to rack for next squat", + "guideKbThrusterMuscles": "Legs|Shoulders|Cardio", + "guideKbThrusterMistakes": "Pausing between squat and press|Back rounding in squat", + + "guideKbCleanPressTitle": "KB Clean & Press", + "guideKbCleanPressLore": "Two movements in harmony.", + "guideKbCleanPressSteps": "Clean: Pull KB from floor to rack position|Press: Press strictly overhead|Lower to rack, then floor (or swing)", + "guideKbCleanPressMuscles": "Shoulders|Back|Legs", + "guideKbCleanPressMistakes": "Clean slams arm|Arching back during press", + + "guideFacePullTitle": "Band Face Pull", + "guideFacePullLore": "Protects shoulders from the wear of battle.", + "guideFacePullSteps": "Attach band at head height|Pull band towards face (forehead/eyes)|Pull elbows high and wide|Squeeze shoulder blades together", + "guideFacePullMuscles": "Rear Delt|Rotator Cuff", + "guideFacePullMistakes": "Elbows too low|Head pushing forward", + + "guideAbWheelTitle": "Ab Wheel Rollout", + "guideAbWheelLore": "A core of steel to absorb any hit.", + "guideAbWheelSteps": "Knees on floor, wheel in front|Roll forward, keep back rounded/stable (Hollow Body)|Go only as far as you can maintain stability|Pull back using abs", + "guideAbWheelMuscles": "Core (Anti-Extension)", + "guideAbWheelMistakes": "Arching back (Dangerous!)|Pulling with arms", + + "guidePlankTitle": "Plank", + "guidePlankLore": "Immovable as a rock in the surf.", + "guidePlankSteps": "Forearm support, body forms a line|Squeeze glutes and abs hard|Push shoulder blades apart|Don't forget to breathe!", + "guidePlankMuscles": "Core", + "guidePlankMistakes": "Hips sagging|Butt too high", + + "codexTitle": "Creature Codex", + + "enemyIronGolemName": "Iron Golem", + "enemyIronGolemTitle": "The Weight of the Earth", + "enemyIronGolemDesc": "Forged from the tectonic plates of the Deep Earth, the Iron Golem exists only to crush the weak. It embodies the unrelenting force of gravity acting on a heavy load.\n\nIt respects only one thing: The raw power of the LEGS that can stand up against its crushing weight.", + "enemyIronGolemNemesis": "Squat Nemesis", + + "enemyGravityDemonName": "Gravity Demon", + "enemyGravityDemonTitle": "The Abyssal Pull", + "enemyGravityDemonDesc": "A spirit of pure downward force that clings to the back of adventurers. It whispers lies of weakness into your ear while dragging you towards the abyss.\n\nOnly those with a back of steel and the will to pull themselves up can escape its grasp.", + "enemyGravityDemonNemesis": "Pull-up Nemesis", + + "enemyPressurePhantomName": "Pressure Phantom", + "enemyPressurePhantomTitle": "The Invisible Crusher", + "enemyPressurePhantomDesc": "An ethereal entity that compresses the very air around you. It seeks to collapse the chest and shoulders of any who dare to push against it.\n\nDefeat it by pushing through the pain with explosive dipping power.", + "enemyPressurePhantomNemesis": "Dip Nemesis" +} diff --git a/lib/main.dart b/lib/main.dart index 2790be2..df186ee 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,35 +1,36 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:isar/isar.dart'; -import 'package:path_provider/path_provider.dart'; - import 'src/app.dart'; -import 'src/shared/data/local/collections/user_collection.dart'; -import 'src/shared/data/local/collections/cycle_collection.dart'; -import 'src/shared/data/local/collections/workout_collection.dart'; +import 'src/shared/data/local/app_database.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); + try { + await dotenv.load(fileName: '.env'); + debugPrint('Environment loaded: ${dotenv.env['ENVIRONMENT']}'); + debugPrint('API URL: ${dotenv.env['API_BASE_URL']}'); + } catch (e) { + debugPrint('Could not load .env file: $e'); + debugPrint('Using default production values'); + } + await SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, ]); - final dir = await getApplicationDocumentsDirectory(); - final isar = await Isar.open( - [UserCollectionSchema, CycleCollectionSchema, WorkoutCollectionSchema], - directory: dir.path, - name: 'slrpg_db', - ); + final database = AppDatabase(); runApp( ProviderScope( - overrides: [isarProvider.overrideWithValue(isar)], + overrides: [appDatabaseProvider.overrideWithValue(database)], child: const SLRPGApp(), ), ); } -final isarProvider = Provider((ref) => throw UnimplementedError()); +final appDatabaseProvider = + Provider((ref) => throw UnimplementedError()); diff --git a/lib/src/app.dart b/lib/src/app.dart index 4e18f19..f40f8b8 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -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'), + ], ); } } - diff --git a/lib/src/core/constants/app_constants.dart b/lib/src/core/constants/app_constants.dart index 42f7109..977d621 100644 --- a/lib/src/core/constants/app_constants.dart +++ b/lib/src/core/constants/app_constants.dart @@ -1,9 +1,36 @@ import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; class AppConstants { + // ✅ API Configuration aus Environment + static String get apiBaseUrl => + dotenv.env['API_BASE_URL'] ?? 'https://slift.patanix.de'; + + static String get apiVersion => dotenv.env['API_VERSION'] ?? 'v1'; + + static String get environment => dotenv.env['ENVIRONMENT'] ?? 'production'; + + static bool get isDebugMode => + dotenv.env['DEBUG_MODE']?.toLowerCase() == 'true'; + + // ✅ Helper Getter + static bool get isDevelopment => environment == 'development'; + static bool get isProduction => environment == 'production'; + + // Debug Info + static void printConfig() { + debugPrint('═══════════════════════════════════'); + debugPrint('🔧 APP CONFIGURATION'); + debugPrint('Environment: $environment'); + debugPrint('API Base URL: $apiBaseUrl'); + debugPrint('API Version: $apiVersion'); + debugPrint('Debug Mode: $isDebugMode'); + debugPrint('═══════════════════════════════════'); + } // API Configuration - static const String apiBaseUrl = 'http://10.0.2.2:8090'; // Android emulator - static const String apiVersion = 'v1'; + // static const String apiBaseUrl = 'http://10.0.2.2:8090'; // Android emulator + // static const String apiBaseUrl = 'https://slift.patanix.de'; + // static const String apiVersion = 'v1'; // Wendler 5/3/1 Constants static const double trainingMaxPercentage = 0.9; @@ -12,14 +39,14 @@ class AppConstants { // XP System static const int baseXP = 1000; - static const double xpMultiplier = 1.15; + static const double xpMultiplier = 1.25; static const int maxLevel = 100; // XP Rewards static const int workoutCompleteXP = 100; - static const double volumeXPRate = 0.1; // XP per kg + static const double volumeXPRate = 0.01; // XP per kg static const int amrapBonusXPPerRep = 25; - static const int prBonusXP = 500; + static const int prBonusXP = 200; static const int cycleCompleteXP = 500; // Rounding Steps diff --git a/lib/src/core/constants/asset_paths.dart b/lib/src/core/constants/asset_paths.dart index a86cabb..c34b4e6 100644 --- a/lib/src/core/constants/asset_paths.dart +++ b/lib/src/core/constants/asset_paths.dart @@ -43,6 +43,9 @@ class AssetPaths { static String getAvatarPath(String gender, int variant) { return 'assets/images/avatars/$gender/$variant.png'; } + + static const String audioBeepShort = 'audio/beep_short.ogg'; + static const String audioBeepLong = 'audio/beep_long.ogg'; } class PlateColors { diff --git a/lib/src/core/debug/debug_config_screen.dart b/lib/src/core/debug/debug_config_screen.dart new file mode 100644 index 0000000..c938977 --- /dev/null +++ b/lib/src/core/debug/debug_config_screen.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +import '../constants/app_constants.dart'; +import '../theme/app_theme.dart'; + +class DebugConfigScreen extends StatelessWidget { + const DebugConfigScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('🔧 Configuration'), + ), + body: ListView( + padding: const EdgeInsets.all(16), + children: [ + _buildSection( + 'Environment', + [ + _buildRow('Environment', AppConstants.environment), + _buildRow('Debug Mode', AppConstants.isDebugMode.toString()), + _buildRow( + 'Is Development', AppConstants.isDevelopment.toString()), + _buildRow('Is Production', AppConstants.isProduction.toString()), + ], + ), + const SizedBox(height: 16), + _buildSection( + 'API Configuration', + [ + _buildRow('Base URL', AppConstants.apiBaseUrl), + _buildRow('API Version', AppConstants.apiVersion), + ], + ), + const SizedBox(height: 16), + _buildSection( + 'All Environment Variables', + dotenv.env.entries.map((e) => _buildRow(e.key, e.value)).toList(), + ), + ], + ), + ); + } + + Widget _buildSection(String title, List children) { + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppTheme.primaryColor, + ), + ), + const Divider(), + ...children, + ], + ), + ), + ); + } + + Widget _buildRow(String key, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 120, + child: Text( + key, + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.grey, + ), + ), + ), + Expanded( + child: Text( + value, + style: const TextStyle(color: Colors.white), + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/core/routing/app_router.dart b/lib/src/core/routing/app_router.dart index 7a12beb..8deb418 100644 --- a/lib/src/core/routing/app_router.dart +++ b/lib/src/core/routing/app_router.dart @@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart'; import '../../features/authentication/presentation/screens/login_screen.dart'; import '../../features/authentication/presentation/screens/profile_screen.dart'; import '../../features/authentication/presentation/screens/register_screen.dart'; +import '../../features/gamification/presentation/screens/quest_log.dart'; import '../../features/onboarding/presentation/screens/avatar_setup_screen.dart'; import '../../features/onboarding/presentation/screens/welcome_screen.dart'; import '../../features/onboarding/presentation/screens/bodyweight_input_screen.dart'; @@ -24,6 +25,22 @@ final routerProvider = Provider((ref) { return GoRouter( initialLocation: '/splash', + redirect: (context, state) async { + final user = await userRepo.getLocalUser(); + final isAuthenticated = user != null; + + final isOnAuthPage = state.matchedLocation == '/login' || + state.matchedLocation == '/register' || + state.matchedLocation.startsWith('/onboarding'); + + if (!isAuthenticated && + !isOnAuthPage && + state.matchedLocation != '/splash') { + return '/login'; + } + + return null; + }, routes: [ // Splash / Initial Route GoRoute( @@ -113,6 +130,11 @@ final routerProvider = Provider((ref) { name: 'codex', builder: (context, state) => const CodexScreen(), ), + GoRoute( + path: '/quests', + name: 'quests', + builder: (context, state) => const QuestLogScreen(), + ), ], ); }); @@ -159,7 +181,7 @@ class _SplashScreenState extends ConsumerState { ), Positioned.fill( child: Container( - color: Colors.black.withOpacity(0.5), + color: Colors.black.withValues(alpha: 0.5), ), ), Center( @@ -170,11 +192,11 @@ class _SplashScreenState extends ConsumerState { width: 120, height: 120, decoration: BoxDecoration( - color: const Color(0xFF00E5FF).withOpacity(0.9), + color: const Color(0xFF00E5FF).withValues(alpha: 0.9), borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( - color: const Color(0xFF00E5FF).withOpacity(0.6), + color: const Color(0xFF00E5FF).withValues(alpha: 0.6), blurRadius: 20, spreadRadius: 5, ), diff --git a/lib/src/core/theme/app_theme.dart b/lib/src/core/theme/app_theme.dart index 104f8e0..a916685 100644 --- a/lib/src/core/theme/app_theme.dart +++ b/lib/src/core/theme/app_theme.dart @@ -87,7 +87,7 @@ class AppTheme { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide( - color: primaryColor.withOpacity(0.3), + color: primaryColor.withValues(alpha: 0.3), width: 1, ), ), @@ -97,11 +97,11 @@ class AppTheme { fillColor: surfaceColor, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: primaryColor.withOpacity(0.5)), + borderSide: BorderSide(color: primaryColor.withValues(alpha: 0.5)), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: primaryColor.withOpacity(0.3)), + borderSide: BorderSide(color: primaryColor.withValues(alpha: 0.3)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), diff --git a/lib/src/features/authentication/presentation/screens/login_screen.dart b/lib/src/features/authentication/presentation/screens/login_screen.dart index cc4d1a2..5e8b772 100644 --- a/lib/src/features/authentication/presentation/screens/login_screen.dart +++ b/lib/src/features/authentication/presentation/screens/login_screen.dart @@ -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 '../../../../shared/data/repositories/user_repository.dart'; import '../../../../core/theme/app_theme.dart'; @@ -16,18 +17,30 @@ class _LoginScreenState extends ConsumerState { final _formKey = GlobalKey(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); + final _emailFocusNode = FocusNode(); + final _passwordFocusNode = FocusNode(); + bool _isLoading = false; bool _obscurePassword = true; + String? _errorMessage; @override void dispose() { _emailController.dispose(); _passwordController.dispose(); + _emailFocusNode.dispose(); + _passwordFocusNode.dispose(); super.dispose(); } Future _handleLogin() async { - if (!_formKey.currentState!.validate()) return; + FocusScope.of(context).unfocus(); + + setState(() => _errorMessage = null); + + if (!_formKey.currentState!.validate()) { + return; + } setState(() => _isLoading = true); @@ -43,141 +56,236 @@ class _LoginScreenState extends ConsumerState { } } catch (e) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Login failed: ${e.toString()}'), - backgroundColor: AppTheme.errorColor, - ), - ); - } - } finally { - if (mounted) { - setState(() => _isLoading = false); + setState(() { + _isLoading = false; + _errorMessage = _parseErrorMessage(e.toString()); + }); } } } + String _parseErrorMessage(String error) { + if (error.contains('400')) { + return 'Invalid email or password'; + } 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.'; + } else if (error.contains('timeout')) { + return 'Connection timeout.\nPlease try again.'; + } + return 'Login failed. Please try again.'; + } + @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; return Scaffold( - body: SafeArea( - child: Center( - child: SingleChildScrollView( - padding: const EdgeInsets.all(24), - child: Form( - key: _formKey, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // Logo - Container( - width: 100, - height: 100, - decoration: BoxDecoration( - color: AppTheme.primaryColor, - borderRadius: BorderRadius.circular(20), - ), - child: const Icon( - Icons.fitness_center, - size: 56, - color: Colors.black, - ), + body: GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + physics: const ClampingScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, ), - const SizedBox(height: 24), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(24), + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Spacer(), - Text( - 'WELCOME BACK', - style: Theme.of(context).textTheme.displayMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - 'Time to level up your strength', - style: Theme.of(context).textTheme.bodyMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 48), - - TextFormField( - controller: _emailController, - keyboardType: TextInputType.emailAddress, - decoration: const InputDecoration( - labelText: 'Email', - prefixIcon: Icon(Icons.email_outlined), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter your email'; - } - if (!value.contains('@')) { - return 'Please enter a valid email'; - } - return null; - }, - ), - const SizedBox(height: 16), - - TextFormField( - controller: _passwordController, - obscureText: _obscurePassword, - decoration: InputDecoration( - labelText: 'Password', - prefixIcon: const Icon(Icons.lock_outline), - suffixIcon: IconButton( - icon: Icon( - _obscurePassword - ? Icons.visibility_outlined - : Icons.visibility_off_outlined, - ), - onPressed: () { - setState(() => _obscurePassword = !_obscurePassword); - }, - ), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter your password'; - } - if (value.length < 8) { - return 'Password must be at least 8 characters'; - } - return null; - }, - ), - const SizedBox(height: 32), - - ElevatedButton( - onPressed: _isLoading ? null : _handleLogin, - child: _isLoading - ? const SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Colors.black, + Container( + width: 100, + height: 100, + decoration: BoxDecoration( + color: AppTheme.primaryColor, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: AppTheme.primaryColor + .withValues(alpha: 0.4), + blurRadius: 20, + spreadRadius: 2, + ), + ], + ), + child: const Icon( + Icons.fitness_center, + size: 56, + color: Colors.black, + ), ), - ) - : const Text('LOGIN'), - ), - const SizedBox(height: 16), + const SizedBox(height: 32), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Don't have an account? ", - style: Theme.of(context).textTheme.bodyMedium, + Text( + l10n.loginWelcomeBack, + style: Theme.of(context).textTheme.displayMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + l10n.loginSubtitle, + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 48), + + if (_errorMessage != null) + Container( + padding: const EdgeInsets.all(16), + margin: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: AppTheme.errorColor + .withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: AppTheme.errorColor, + width: 1, + ), + ), + child: Row( + children: [ + const Icon( + Icons.error_outline, + color: AppTheme.errorColor, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + _errorMessage!, + style: const TextStyle( + color: AppTheme.errorColor, + fontSize: 14, + ), + ), + ), + ], + ), + ), + + TextFormField( + controller: _emailController, + focusNode: _emailFocusNode, + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.next, + enabled: !_isLoading, + decoration: InputDecoration( + labelText: l10n.emailLabel, + prefixIcon: Icon(Icons.email_outlined), + ), + onFieldSubmitted: (_) { + _passwordFocusNode.requestFocus(); + }, + validator: (value) { + if (value == null || value.isEmpty) { + return l10n.emailEmptyError; + } + if (!value.contains('@')) { + return l10n.emailInvalidError; + } + return null; + }, + ), + const SizedBox(height: 16), + + // Password Field + TextFormField( + controller: _passwordController, + focusNode: _passwordFocusNode, + obscureText: _obscurePassword, + textInputAction: TextInputAction.done, + enabled: !_isLoading, + decoration: InputDecoration( + labelText: l10n.passwordLabel, + prefixIcon: const Icon(Icons.lock_outline), + suffixIcon: IconButton( + icon: Icon( + _obscurePassword + ? Icons.visibility_outlined + : Icons.visibility_off_outlined, + ), + onPressed: () { + setState(() { + _obscurePassword = !_obscurePassword; + }); + }, + ), + ), + onFieldSubmitted: (_) => _handleLogin(), + validator: (value) { + if (value == null || value.isEmpty) { + return l10n.passwordEmptyError; + } + if (value.length < 8) { + return l10n.passwordLengthError; + } + return null; + }, + ), + const SizedBox(height: 32), + + ElevatedButton( + onPressed: _isLoading ? null : _handleLogin, + style: ElevatedButton.styleFrom( + padding: + const EdgeInsets.symmetric(vertical: 16), + disabledBackgroundColor: AppTheme.primaryColor + .withValues(alpha: 0.5), + ), + child: _isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.black, + ), + ) + : Text( + l10n.loginButton, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + letterSpacing: 1.2, + ), + ), + ), + const SizedBox(height: 16), + + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + l10n.loginNoAccount, + style: Theme.of(context).textTheme.bodyMedium, + ), + TextButton( + onPressed: _isLoading + ? null + : () => context.go('/register'), + child: Text(l10n.loginRegisterButton), + ), + ], + ), + + const Spacer(), + ], + ), ), - TextButton( - onPressed: () => context.go('/register'), - child: const Text('REGISTER'), - ), - ], + ), ), - ], - ), - ), + ), + ); + }, ), ), ), diff --git a/lib/src/features/authentication/presentation/screens/profile_screen.dart b/lib/src/features/authentication/presentation/screens/profile_screen.dart index daf80ea..a4ca385 100644 --- a/lib/src/features/authentication/presentation/screens/profile_screen.dart +++ b/lib/src/features/authentication/presentation/screens/profile_screen.dart @@ -1,14 +1,16 @@ +import 'package:drift/drift.dart' hide Column; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../../core/theme/app_theme.dart'; import '../../../../shared/data/repositories/user_repository.dart'; -import '../../../../shared/data/local/collections/user_collection.dart'; +import '../../../../shared/data/local/app_database.dart'; import '../../../gamification/domain/entities/avatar_config.dart'; import '../../../gamification/presentation/widgets/avatar_editor.dart'; import '../../../gamification/presentation/widgets/avatar_renderer.dart'; -import 'dart:convert'; +import '../../../gamification/domain/entities/item_catalog.dart'; +import '../../../../shared/domain/logic/wendler_calculator.dart'; class ProfileScreen extends ConsumerStatefulWidget { const ProfileScreen({super.key}); @@ -46,6 +48,11 @@ class _ProfileScreenState extends ConsumerState { await ref .read(userRepositoryProvider) .updateBodyweight(_currentBodyweight); + + if (_user != null) { + _user = _user!.copyWith(currentBodyweight: _currentBodyweight); + } + if (mounted) { setState(() => _hasChanges = false); ScaffoldMessenger.of(context).showSnackBar( @@ -136,6 +143,139 @@ class _ProfileScreenState extends ConsumerState { ); } + void _showBackgroundSelector() { + final currentLevel = _user?.level ?? 1; + final currentConfig = _user?.avatarConfig != null + ? AvatarConfig.fromJson(_user!.avatarConfig!) + : const AvatarConfig(); + + showModalBottomSheet( + context: context, + backgroundColor: AppTheme.surfaceColor, + builder: (context) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Text('Select Scenery', + style: Theme.of(context).textTheme.titleLarge), + ), + SizedBox( + height: 200, + child: ListView.builder( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 16), + itemCount: ItemCatalog.backgrounds.length, + itemBuilder: (context, index) { + final item = ItemCatalog.backgrounds[index]; + final isUnlocked = currentLevel >= item.unlockLevel; + final isSelected = currentConfig.selectedBackground == item.id; + + return GestureDetector( + onTap: isUnlocked + ? () async { + Navigator.pop(context); + setState(() => _isLoading = true); + + final newConfig = AvatarConfig( + gender: currentConfig.gender, + variant: currentConfig.variant, + selectedBackground: item.id, + ); + + final updatedUser = _user!.copyWith( + avatarConfig: Value(newConfig.toJson()), + isDirty: true, + ); + _user = updatedUser; + + await ref + .read(userRepositoryProvider) + .saveLocalUser(_user!); + setState(() => _isLoading = false); + } + : null, + child: Container( + width: 140, + margin: const EdgeInsets.only(right: 12, bottom: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: + isSelected ? AppTheme.primaryColor : Colors.white12, + width: isSelected ? 3 : 1, + ), + image: DecorationImage( + image: AssetImage(item.assetPath), + fit: BoxFit.cover, + colorFilter: isUnlocked + ? null + : const ColorFilter.mode( + Colors.black87, BlendMode.darken), + ), + ), + child: Stack( + children: [ + if (!isUnlocked) + Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.lock, color: Colors.white54), + const SizedBox(height: 4), + Text( + 'Lvl ${item.unlockLevel}', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + shadows: [ + Shadow( + blurRadius: 4, color: Colors.black) + ]), + ), + ], + ), + ), + if (isSelected) + const Positioned( + top: 8, + right: 8, + child: Icon(Icons.check_circle, + color: AppTheme.primaryColor), + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.7), + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(10)), + ), + child: Text( + item.name, + style: const TextStyle( + color: Colors.white, + fontSize: 10, + overflow: TextOverflow.ellipsis), + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + ), + ); + }, + ), + ), + ], + ), + ); + } + void _confirmDangerAction( String title, String content, VoidCallback onConfirm) { showDialog( @@ -163,8 +303,8 @@ class _ProfileScreenState extends ConsumerState { } void _showAvatarEditor() { - final currentConfig = _user?.avatarConfigJson != null - ? AvatarConfig.fromJson(jsonDecode(_user!.avatarConfigJson!)) + final currentConfig = _user?.avatarConfig != null + ? AvatarConfig.fromJson(_user!.avatarConfig!) : const AvatarConfig(); showModalBottomSheet( @@ -199,19 +339,70 @@ class _ProfileScreenState extends ConsumerState { ).then((result) async { if (result is AvatarConfig) { setState(() => _isLoading = true); - _user!.avatarConfigJson = jsonEncode(result.toJson()); - _user!.isDirty = true; + + final updatedUser = _user!.copyWith( + avatarConfig: Value(result.toJson()), + isDirty: true, + ); + _user = updatedUser; + await ref.read(userRepositoryProvider).saveLocalUser(_user!); setState(() => _isLoading = false); } }); } + AccessoryTemplate _getTemplateFromSettings(Map settings) { + final key = settings['accessory_template'] as String?; + if (key == 'hypertrophy') return AccessoryTemplate.hypertrophy; + if (key == 'conditioning') return AccessoryTemplate.conditioning; + if (key == 'journey_pullup') return AccessoryTemplate.journey_pullup; + return AccessoryTemplate.none; + } + + Future _updateTemplate(AccessoryTemplate newTemplate) async { + setState(() => _isLoading = true); + + String templateKey = 'none'; + if (newTemplate == AccessoryTemplate.hypertrophy) { + templateKey = 'hypertrophy'; + } + if (newTemplate == AccessoryTemplate.conditioning) { + templateKey = 'conditioning'; + } + if (newTemplate == AccessoryTemplate.journey_pullup) { + templateKey = 'journey_pullup'; + } + + final currentSettings = + Map.from(_user!.inventorySettings ?? {}); + currentSettings['accessory_template'] = templateKey; + + try { + final updatedUser = _user!.copyWith( + inventorySettings: Value(currentSettings), + isDirty: true, + ); + + await ref.read(userRepositoryProvider).saveLocalUser(updatedUser); + + ref.read(userRepositoryProvider).updateInventory(currentSettings); + + setState(() { + _user = updatedUser; + _isLoading = false; + }); + } catch (e) { + setState(() => _isLoading = false); + // Error handling... + } + } + @override Widget build(BuildContext context) { final userRepo = ref.watch(userRepositoryProvider); - final avatarConfig = _user?.avatarConfigJson != null - ? AvatarConfig.fromJson(jsonDecode(_user!.avatarConfigJson!)) + final avatarConfig = _user?.avatarConfig != null + ? AvatarConfig.fromJson(_user!.avatarConfig!) : const AvatarConfig(); return Scaffold( @@ -234,172 +425,292 @@ class _ProfileScreenState extends ConsumerState { ), body: _isLoading ? const Center(child: CircularProgressIndicator()) - : ListView( - padding: const EdgeInsets.all(16), - children: [ - Center( - child: Stack( - children: [ - AvatarRenderer( - config: avatarConfig, - size: 120, - ), - Positioned( - bottom: 0, - right: 0, - child: CircleAvatar( - backgroundColor: AppTheme.surfaceColor, - radius: 18, - child: IconButton( - icon: const Icon(Icons.edit, size: 16), - onPressed: _showAvatarEditor, - ), - ), - ), - ], - ), - ), - const SizedBox(height: 32), - Text('Physical Stats', - style: Theme.of(context) - .textTheme - .titleLarge - ?.copyWith(color: AppTheme.textPrimary)), - const SizedBox(height: 16), - Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + : SafeArea( + child: ListView( + padding: const EdgeInsets.all(16), + children: [ + Center( + child: Stack( children: [ - Text('Current Bodyweight', - style: Theme.of(context).textTheme.bodyMedium), - Row( - children: [ - Expanded( - child: Slider( - value: _currentBodyweight, - min: 40, - max: 150, - divisions: 220, // 0.5 steps - label: _currentBodyweight.toStringAsFixed(1), - activeColor: AppTheme.primaryColor, - onChanged: (val) => setState(() { - _currentBodyweight = val; - _hasChanges = true; - }), - ), + AvatarRenderer( + config: avatarConfig, + size: 120, + ), + Positioned( + bottom: 0, + right: 0, + child: CircleAvatar( + backgroundColor: AppTheme.surfaceColor, + radius: 18, + child: IconButton( + icon: const Icon(Icons.edit, size: 16), + onPressed: _showAvatarEditor, ), - Text( - '${_currentBodyweight.toStringAsFixed(1)} kg', - style: Theme.of(context) - .textTheme - .titleMedium - ?.copyWith( - fontWeight: FontWeight.bold, - color: AppTheme.primaryColor, - ), - ), - ], + ), ), ], ), ), - ), - const SizedBox(height: 32), - Text('Account Security', - style: Theme.of(context) - .textTheme - .titleLarge - ?.copyWith(color: AppTheme.textPrimary)), - const SizedBox(height: 8), - ListTile( - leading: const Icon(Icons.lock_outline), - title: const Text('Change Password'), - trailing: const Icon(Icons.chevron_right), - onTap: _showChangePasswordDialog, - ), - const Divider(), - const SizedBox(height: 24), - Text('Danger Zone', - style: Theme.of(context) - .textTheme - .titleLarge - ?.copyWith(color: AppTheme.errorColor)), - const SizedBox(height: 8), - Container( - decoration: BoxDecoration( - border: - Border.all(color: AppTheme.errorColor.withOpacity(0.5)), - borderRadius: BorderRadius.circular(12), - color: AppTheme.errorColor.withOpacity(0.05), + const SizedBox(height: 32), + Center( + child: OutlinedButton.icon( + onPressed: _showBackgroundSelector, + icon: const Icon(Icons.landscape), + label: const Text('CHANGE SCENERY'), + ), ), - child: Column( - children: [ - ListTile( - leading: const Icon(Icons.refresh, - color: AppTheme.errorColor), - title: const Text('Reset Progress', - style: TextStyle(color: AppTheme.errorColor)), - subtitle: - const Text('Resets Level, XP and Training History'), - onTap: () => _confirmDangerAction( - 'Reset Progress?', - 'This will delete all your workouts and reset your Level to 1. This cannot be undone.', - () async { - setState(() => _isLoading = true); - await userRepo.resetProgress(); - if (mounted) { - setState(() => _isLoading = false); - context.go('/hub'); - } - }, - ), + const SizedBox(height: 32), + Text('Physical Stats', + style: Theme.of(context) + .textTheme + .titleLarge + ?.copyWith(color: AppTheme.textPrimary)), + const SizedBox(height: 16), + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Current Bodyweight', + style: Theme.of(context).textTheme.bodyMedium), + Row( + children: [ + Expanded( + child: Slider( + value: _currentBodyweight, + min: 40, + max: 150, + divisions: 220, + label: _currentBodyweight.toStringAsFixed(1), + activeColor: AppTheme.primaryColor, + onChanged: (val) => setState(() { + _currentBodyweight = val; + _hasChanges = true; + }), + ), + ), + Text( + '${_currentBodyweight.toStringAsFixed(1)} kg', + style: Theme.of(context) + .textTheme + .titleMedium + ?.copyWith( + fontWeight: FontWeight.bold, + color: AppTheme.primaryColor, + ), + ), + ], + ), + ], ), - const Divider(height: 1), - ListTile( - leading: const Icon(Icons.delete_forever, - color: AppTheme.errorColor), - title: const Text('Delete Account', - style: TextStyle(color: AppTheme.errorColor)), - subtitle: const Text( - 'Permanently delete your account and data'), - onTap: () => _confirmDangerAction( - 'Delete Account?', - 'Are you sure you want to delete your account? All data will be lost forever.', - () async { - setState(() => _isLoading = true); - try { - await userRepo.deleteAccount(); - if (mounted) context.go('/login'); - } catch (e) { + ), + ), + const SizedBox(height: 32), + Text('Training Focus', + style: Theme.of(context) + .textTheme + .titleLarge + ?.copyWith(color: AppTheme.textPrimary)), + const SizedBox(height: 16), + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Accessory Template', + style: Theme.of(context).textTheme.bodyMedium), + const SizedBox(height: 12), + _buildTemplateSelector(), + ], + ), + ), + ), + Text('Account Security', + style: Theme.of(context) + .textTheme + .titleLarge + ?.copyWith(color: AppTheme.textPrimary)), + const SizedBox(height: 8), + ListTile( + leading: const Icon(Icons.lock_outline), + title: const Text('Change Password'), + trailing: const Icon(Icons.chevron_right), + onTap: _showChangePasswordDialog, + ), + const Divider(), + const SizedBox(height: 24), + Text('Danger Zone', + style: Theme.of(context) + .textTheme + .titleLarge + ?.copyWith(color: AppTheme.errorColor)), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + border: Border.all( + color: AppTheme.errorColor.withValues(alpha: 0.5)), + borderRadius: BorderRadius.circular(12), + color: AppTheme.errorColor.withValues(alpha: 0.05), + ), + child: Column( + children: [ + ListTile( + leading: const Icon(Icons.refresh, + color: AppTheme.errorColor), + title: const Text('Reset Progress', + style: TextStyle(color: AppTheme.errorColor)), + subtitle: const Text( + 'Resets Level, XP and Training History'), + onTap: () => _confirmDangerAction( + 'Reset Progress?', + 'This will delete all your workouts and reset your Level to 1. This cannot be undone.', + () async { + setState(() => _isLoading = true); + await userRepo.resetProgress(); if (mounted) { setState(() => _isLoading = false); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Error: $e')), - ); + context.go('/hub'); } - } - }, + }, + ), ), - ), - ], + const Divider(height: 1), + ListTile( + leading: const Icon(Icons.delete_forever, + color: AppTheme.errorColor), + title: const Text('Delete Account', + style: TextStyle(color: AppTheme.errorColor)), + subtitle: const Text( + 'Permanently delete your account and data'), + onTap: () => _confirmDangerAction( + 'Delete Account?', + 'Are you sure you want to delete your account? All data will be lost forever.', + () async { + setState(() => _isLoading = true); + try { + await userRepo.deleteAccount(); + if (mounted) context.go('/login'); + } catch (e) { + if (mounted) { + setState(() => _isLoading = false); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: $e')), + ); + } + } + }, + ), + ), + ], + ), ), - ), - const SizedBox(height: 32), - OutlinedButton.icon( - onPressed: () async { - await userRepo.logout(); - if (mounted) context.go('/login'); - }, - icon: const Icon(Icons.logout), - label: const Text('LOGOUT'), - style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), + const SizedBox(height: 32), + OutlinedButton.icon( + onPressed: () async { + await userRepo.logout(); + if (mounted) context.go('/login'); + }, + icon: const Icon(Icons.logout), + label: const Text('LOGOUT'), + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + ), ), - ), - ], + const SizedBox( + height: 50, + ) + ], + ), ), ); } + + Widget _buildTemplateSelector() { + final current = _getTemplateFromSettings(_user?.inventorySettings ?? {}); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _RadioTile( + value: AccessoryTemplate.none, + groupValue: current, + title: 'Strength Only', + subtitle: 'Main Lifts + FSL. Pure & Fast.', + onChanged: (val) => _updateTemplate(val!), + ), + const Divider(height: 1), + _RadioTile( + value: AccessoryTemplate.hypertrophy, + groupValue: current, + title: 'Hypertrophy Support', + subtitle: 'Bodybuilding accessories to build muscle armor.', + onChanged: (val) => _updateTemplate(val!), + ), + const Divider(height: 1), + _RadioTile( + value: AccessoryTemplate.conditioning, + groupValue: current, + title: 'The Engine (Conditioning)', + subtitle: '15 min Kettlebell intervals to boost stamina.', + onChanged: (val) => _updateTemplate(val!), + ), + const Padding( + padding: EdgeInsets.fromLTRB(16, 24, 16, 8), + child: Text( + 'ACTIVE JOURNEYS', + style: TextStyle( + color: Colors.grey, + fontSize: 12, + fontWeight: FontWeight.bold, + letterSpacing: 1.5), + ), + ), + _RadioTile( + value: AccessoryTemplate.journey_pullup, + groupValue: current, + title: 'Quest: The First Pull-Up', + subtitle: 'Specific progression to master your bodyweight.', + onChanged: (val) => _updateTemplate(val!), + ), + ], + ); + } +} + +class _RadioTile extends StatelessWidget { + final T value; + final T groupValue; + final String title; + final String subtitle; + final ValueChanged onChanged; + + const _RadioTile({ + required this.value, + required this.groupValue, + required this.title, + required this.subtitle, + required this.onChanged, + }); + + @override + Widget build(BuildContext context) { + final isSelected = value == groupValue; + return RadioListTile( + value: value, + groupValue: groupValue, + onChanged: onChanged, + activeColor: AppTheme.primaryColor, + title: Text( + title, + style: TextStyle( + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + color: isSelected ? AppTheme.primaryColor : Colors.white, + ), + ), + subtitle: Text(subtitle, style: const TextStyle(fontSize: 12)), + contentPadding: EdgeInsets.zero, + ); + } } diff --git a/lib/src/features/authentication/presentation/screens/register_screen.dart b/lib/src/features/authentication/presentation/screens/register_screen.dart index 13fe485..c846d8f 100644 --- a/lib/src/features/authentication/presentation/screens/register_screen.dart +++ b/lib/src/features/authentication/presentation/screens/register_screen.dart @@ -1,9 +1,9 @@ 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 '../../../../core/constants/app_constants.dart'; import '../../../onboarding/presentation/screens/bodyweight_input_screen.dart'; class RegisterScreen extends ConsumerStatefulWidget { @@ -16,32 +16,32 @@ class RegisterScreen extends ConsumerStatefulWidget { class _RegisterScreenState extends ConsumerState { final _formKey = GlobalKey(); final _emailController = TextEditingController(); - final _passwordController = TextEditingController(); - final _confirmPasswordController = TextEditingController(); - bool _obscurePassword = true; - bool _obscureConfirmPassword = true; + final _emailFocusNode = FocusNode(); @override void dispose() { _emailController.dispose(); - _passwordController.dispose(); - _confirmPasswordController.dispose(); + _emailFocusNode.dispose(); super.dispose(); } void _handleRegister() { - if (!_formKey.currentState!.validate()) return; + FocusScope.of(context).unfocus(); - ref.read(onboardingDataProvider.notifier).update((state) => { - 'email': _emailController.text.trim(), - 'password': _passwordController.text, - }); + if (!_formKey.currentState!.validate()) { + return; + } + + ref.read(onboardingDataProvider.notifier).updateData({ + 'email': _emailController.text.trim(), + }); context.go('/onboarding/welcome'); } @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; return Scaffold( appBar: AppBar( leading: IconButton( @@ -49,120 +49,98 @@ class _RegisterScreenState extends ConsumerState { onPressed: () => context.go('/login'), ), ), - body: SafeArea( - child: Center( - child: SingleChildScrollView( - padding: const EdgeInsets.all(24), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - 'CREATE ACCOUNT', - style: Theme.of(context).textTheme.displayMedium, - textAlign: TextAlign.center, + body: GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + physics: const ClampingScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, ), - const SizedBox(height: 8), - Text( - 'Begin your strength journey', - style: Theme.of(context).textTheme.bodyMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 48), - TextFormField( - controller: _emailController, - keyboardType: TextInputType.emailAddress, - decoration: const InputDecoration( - labelText: 'Email', - prefixIcon: Icon(Icons.email_outlined), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter your email'; - } - if (!value.contains('@')) { - return 'Please enter a valid email'; - } - return null; - }, - ), - const SizedBox(height: 16), - TextFormField( - controller: _passwordController, - obscureText: _obscurePassword, - decoration: InputDecoration( - labelText: 'Password', - prefixIcon: const Icon(Icons.lock_outline), - suffixIcon: IconButton( - icon: Icon( - _obscurePassword - ? Icons.visibility_outlined - : Icons.visibility_off_outlined, + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(24), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Spacer(), + Text( + l10n.registerTitle, + style: Theme.of(context).textTheme.displayMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + l10n.registerSubtitle, + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 48), + TextFormField( + controller: _emailController, + focusNode: _emailFocusNode, + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.next, + decoration: InputDecoration( + labelText: l10n.emailLabel, + prefixIcon: Icon(Icons.email_outlined), + helperText: l10n.registerEmailHelper, + ), + onFieldSubmitted: (_) {}, + validator: (value) { + if (value == null || value.isEmpty) { + return l10n.emailEmptyError; + } + if (!value.contains('@')) { + return l10n.emailInvalidError; + } + return null; + }, + ), + const SizedBox(height: 32), + ElevatedButton( + onPressed: _handleRegister, + style: ElevatedButton.styleFrom( + padding: + const EdgeInsets.symmetric(vertical: 16), + ), + child: Text( + l10n.continueButton, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + letterSpacing: 1.2, + ), + ), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + l10n.registerHaveAccount, + style: Theme.of(context).textTheme.bodyMedium, + ), + TextButton( + onPressed: () => context.go('/login'), + child: Text(l10n.registerLoginButton), + ), + ], + ), + const Spacer(), + ], ), - onPressed: () { - setState(() => _obscurePassword = !_obscurePassword); - }, ), ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a password'; - } - if (value.length < 8) { - return 'Password must be at least 8 characters'; - } - return null; - }, ), - const SizedBox(height: 16), - TextFormField( - controller: _confirmPasswordController, - obscureText: _obscureConfirmPassword, - decoration: InputDecoration( - labelText: 'Confirm Password', - prefixIcon: const Icon(Icons.lock_outline), - suffixIcon: IconButton( - icon: Icon( - _obscureConfirmPassword - ? Icons.visibility_outlined - : Icons.visibility_off_outlined, - ), - onPressed: () { - setState(() => _obscureConfirmPassword = - !_obscureConfirmPassword); - }, - ), - ), - validator: (value) { - if (value != _passwordController.text) { - return 'Passwords do not match'; - } - return null; - }, - ), - const SizedBox(height: 32), - ElevatedButton( - onPressed: _handleRegister, - child: const Text('CONTINUE'), - ), - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Already have an account? ', - style: Theme.of(context).textTheme.bodyMedium, - ), - TextButton( - onPressed: () => context.go('/login'), - child: const Text('LOGIN'), - ), - ], - ), - ], - ), - ), + ), + ); + }, ), ), ), diff --git a/lib/src/features/dashboard/presentation/screens/hub_screen.dart b/lib/src/features/dashboard/presentation/screens/hub_screen.dart index 8fc4725..8b73e84 100644 --- a/lib/src/features/dashboard/presentation/screens/hub_screen.dart +++ b/lib/src/features/dashboard/presentation/screens/hub_screen.dart @@ -1,26 +1,29 @@ -import 'dart:convert'; - 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/constants/asset_paths.dart'; +import '../../../../core/constants/app_constants.dart'; +import '../../../../core/debug/debug_config_screen.dart'; import '../../../../core/theme/app_theme.dart'; +import '../../../../shared/data/local/app_database.dart'; import '../../../../shared/data/repositories/user_repository.dart'; import '../../../../shared/data/repositories/cycle_repository.dart'; +import '../../../../shared/data/repositories/workout_repository.dart'; +import '../../../../shared/data/remote/sync_service.dart'; import '../../../../shared/domain/logic/xp_calculator.dart'; +import '../../../../shared/domain/logic/wendler_calculator.dart'; +import '../../../../shared/domain/entities/exercise.dart'; +import '../../../../shared/domain/entities/workout_set.dart'; +import '../../../gamification/domain/entities/avatar_config.dart'; +import '../../../gamification/domain/entities/item_catalog.dart'; +import '../../../gamification/presentation/widgets/avatar_renderer.dart'; +import '../../../gamification/presentation/widgets/quest_board.dart'; import '../widgets/xp_bar_widget.dart'; import '../widgets/level_display.dart'; import '../widgets/start_raid_button.dart'; -import '../../../../shared/data/local/collections/user_collection.dart'; -import '../../../../shared/data/local/collections/cycle_collection.dart'; -import '../../../../shared/data/repositories/workout_repository.dart'; -import '../../../../shared/domain/entities/exercise.dart'; -import '../../../../shared/domain/logic/wendler_calculator.dart'; -import '../../../../shared/data/remote/sync_service.dart'; -import '../../../../shared/domain/entities/workout_set.dart'; -import '../../../gamification/domain/entities/avatar_config.dart'; -import '../../../gamification/presentation/widgets/avatar_renderer.dart'; +import '../../../gamification/application/quest_service.dart'; +import '../../../workout_runner/application/workout_generator_service.dart'; class HubScreen extends ConsumerStatefulWidget { const HubScreen({super.key}); @@ -41,70 +44,21 @@ class _HubScreenState extends ConsumerState { @override void initState() { super.initState(); - - WidgetsBinding.instance.addPostFrameCallback((_) { - _runSync(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + await _runSync(); + await ref.read(questServiceProvider).checkAndGenerateQuests(); }); } - List _generateExercises({ - required int week, - required int day, - required Map trainingMaxes, - required double bodyweight, - }) { - final exercises = []; - - void addExercise(String id, String name, ExerciseType type, bool isMain) { - final tm = trainingMaxes[id] ?? 0.0; - List sets; - - if (isMain) { - sets = WendlerCalculator.generateSets( - week: week, - trainingMax: tm, - exerciseType: type, - currentBodyweight: bodyweight, - ); - } else { - if (week == 4) return; - - sets = WendlerCalculator.generateFSLSets( - trainingMax: tm, - exerciseType: type, - currentBodyweight: bodyweight, - ); - } - - if (sets.isNotEmpty) { - exercises.add(Exercise( - exerciseId: id, - exerciseName: isMain ? name : '$name (FSL)', - bodyweightAtSession: bodyweight, - sets: sets, - )); - } - } - - if (day == 1) { - addExercise('squat', 'Back Squat', ExerciseType.squat, true); - addExercise('pullup', 'Weighted Pull-up', ExerciseType.pullup, false); - } else if (day == 2) { - addExercise('dip', 'Weighted Dip', ExerciseType.dip, true); - addExercise('squat', 'Back Squat', ExerciseType.squat, false); - } else if (day == 3) { - addExercise('pullup', 'Weighted Pull-up', ExerciseType.pullup, true); - addExercise('dip', 'Weighted Dip', ExerciseType.dip, false); - } - - return exercises; - } - Future _startNextWorkout( CycleCollection cycle, UserCollection user) async { try { final workoutRepo = ref.read(workoutRepositoryProvider); - final cycleRepo = ref.read(cycleRepositoryProvider); + final workoutGenerator = ref.read(workoutGeneratorServiceProvider); + + final tmsDynamic = cycle.trainingMaxes; + final trainingMaxes = Map.from(tmsDynamic + .map((key, value) => MapEntry(key, (value as num).toDouble()))); int targetWeek = 1; int targetDay = 1; @@ -137,9 +91,6 @@ class _HubScreenState extends ConsumerState { } return; } - - final trainingMaxes = cycleRepo.getCurrentTrainingMaxes(); - var workout = await workoutRepo.getWorkoutByWeekDay( cycleId: cycleRefId, localCycleId: localCycleId, @@ -148,11 +99,21 @@ class _HubScreenState extends ConsumerState { ); if (workout == null) { - final exercises = _generateExercises( + final activeTemplate = _getTemplateFromUser(user); + int? conditioningSets; + + if (activeTemplate == AccessoryTemplate.conditioning) { + conditioningSets = await _showConditioningDialog(); + if (conditioningSets == null) return; + } + + final exercises = workoutGenerator.generateWorkout( week: targetWeek, day: targetDay, trainingMaxes: trainingMaxes, - bodyweight: user.currentBodyweight, + user: user, + template: activeTemplate, + conditioningSets: conditioningSets, ); final userId = user.serverId ?? user.id.toString(); @@ -162,7 +123,7 @@ class _HubScreenState extends ConsumerState { cycleId: cycleRefId, week: targetWeek, day: targetDay, - exercisesJson: jsonEncode(exercises.map((e) => e.toJson()).toList()), + exercises: exercises.map((e) => e.toJson()).toList(), ); } @@ -183,10 +144,94 @@ class _HubScreenState extends ConsumerState { } } + AccessoryTemplate _getTemplateFromUser(UserCollection user) { + final settings = user.inventorySettings ?? {}; + final key = settings['accessory_template'] as String?; + if (key == 'hypertrophy') return AccessoryTemplate.hypertrophy; + if (key == 'conditioning') return AccessoryTemplate.conditioning; + return AccessoryTemplate.none; + } + + Future _showConditioningDialog() async { + int sets = 20; + final l10n = AppLocalizations.of(context)!; + + return await showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return StatefulBuilder( + builder: (context, setDialogState) { + final interval = (20 * 60) / sets; + + return AlertDialog( + title: Text( + l10n.missionBriefingTitle, + style: TextStyle( + color: AppTheme.textSecondary, + fontWeight: FontWeight.bold, + ), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + l10n.missionBriefingBody, + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + Text( + l10n.missionBriefingDensity(sets), + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: AppTheme.primaryColor, + fontWeight: FontWeight.bold), + ), + Text( + l10n.missionBriefingInterval(interval.toStringAsFixed(0)), + style: const TextStyle(color: Colors.grey), + ), + const SizedBox(height: 16), + Slider( + value: sets.toDouble(), + min: 10, + max: 30, + divisions: 20, + activeColor: AppTheme.primaryColor, + onChanged: (val) { + setDialogState(() => sets = val.toInt()); + }, + ), + const SizedBox(height: 8), + if (sets >= 20) + const Text('⚠️ HARDCORE MODE', + style: TextStyle( + color: AppTheme.errorColor, + fontSize: 10, + fontWeight: FontWeight.bold)), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, null), + child: Text(l10n.abortButton), + ), + ElevatedButton( + onPressed: () => Navigator.pop(context, sets), + child: Text(l10n.engageButton), + ), + ], + ); + }, + ); + }, + ); + } + @override Widget build(BuildContext context) { final userRepo = ref.watch(userRepositoryProvider); final cycleRepo = ref.watch(cycleRepositoryProvider); + final l10n = AppLocalizations.of(context)!; return Scaffold( body: SafeArea( @@ -202,9 +247,12 @@ class _HubScreenState extends ConsumerState { final user = snapshot.data![0] as UserCollection?; final cycle = snapshot.data![1] as CycleCollection?; - final avatarConfig = user?.avatarConfigJson != null - ? AvatarConfig.fromJson(jsonDecode(user!.avatarConfigJson!)) + + final avatarConfig = user?.avatarConfig != null + ? AvatarConfig.fromJson(user!.avatarConfig!) : const AvatarConfig(); + final bgItem = + ItemCatalog.getBackground(avatarConfig.selectedBackground); if (user == null) { WidgetsBinding.instance.addPostFrameCallback((_) { @@ -223,8 +271,9 @@ class _HubScreenState extends ConsumerState { children: [ Positioned.fill( child: Image.asset( - AssetPaths.bgStreetParkDay, + bgItem.assetPath, fit: BoxFit.cover, + key: ValueKey(bgItem.assetPath), ), ), Positioned.fill( @@ -234,8 +283,8 @@ class _HubScreenState extends ConsumerState { begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ - Colors.black.withOpacity(0.6), - Colors.black.withOpacity(0.85), + Colors.black.withValues(alpha: 0.6), + Colors.black.withValues(alpha: 0.85), ], ), ), @@ -248,6 +297,17 @@ class _HubScreenState extends ConsumerState { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + if (AppConstants.isDevelopment) + IconButton( + icon: const Icon(Icons.settings), + onPressed: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + const DebugConfigScreen(), + ), + ), + ), IconButton( icon: const Icon(Icons.person_outline), onPressed: () => context.go('/profile'), @@ -289,6 +349,8 @@ class _HubScreenState extends ConsumerState { nextLevelXP: nextLevelXP, ), ), + const SizedBox(height: 16), + const QuestBoardWidget(), const Spacer(flex: 2), if (cycle != null) Padding( @@ -303,7 +365,7 @@ class _HubScreenState extends ConsumerState { child: Column( children: [ Text( - 'No active cycle', + l10n.hubNoActiveCycle, style: Theme.of(context).textTheme.bodyLarge, ), const SizedBox(height: 16), @@ -311,7 +373,7 @@ class _HubScreenState extends ConsumerState { onPressed: () { context.push('/onboarding/strength-test'); }, - child: const Text('Create New Cycle'), + child: Text(l10n.hubCreateCycle), ), ], ), @@ -324,8 +386,11 @@ class _HubScreenState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _StatBox( - label: 'Cycle', value: '#${cycle.cycleNumber}'), - _StatBox(label: 'Active', value: 'Yes'), + label: l10n.hubCycleLabel, + value: '#${cycle.cycleNumber}'), + _StatBox( + label: l10n.hubActiveLabel, + value: l10n.hubActiveYes), ], ), ), @@ -339,7 +404,7 @@ class _HubScreenState extends ConsumerState { top: Radius.circular(24)), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.2), + color: Colors.black.withValues(alpha: 0.2), blurRadius: 10, offset: const Offset(0, -5), ), @@ -350,24 +415,24 @@ class _HubScreenState extends ConsumerState { children: [ _NavButton( icon: Icons.history, - label: 'History', + label: l10n.navHistory, onTap: () => context.go('/history'), ), _NavButton( icon: Icons.inventory_2_outlined, - label: 'Inventory', + label: l10n.navInventory, onTap: () => context.go('/inventory'), ), _NavButton( icon: Icons.bar_chart, - label: 'Stats', + label: l10n.navStats, onTap: () { context.go('/stats'); }, ), _NavButton( icon: Icons.auto_stories, - label: 'Codex', + label: l10n.navCodex, onTap: () => context.go('/codex'), ), ], @@ -401,7 +466,7 @@ class _StatBox extends StatelessWidget { color: AppTheme.surfaceColor, borderRadius: BorderRadius.circular(12), border: Border.all( - color: AppTheme.primaryColor.withOpacity(0.3), + color: AppTheme.primaryColor.withValues(alpha: 0.3), ), ), child: Column( diff --git a/lib/src/features/dashboard/presentation/widgets/level_display.dart b/lib/src/features/dashboard/presentation/widgets/level_display.dart index 73a651c..9eb8cab 100644 --- a/lib/src/features/dashboard/presentation/widgets/level_display.dart +++ b/lib/src/features/dashboard/presentation/widgets/level_display.dart @@ -23,7 +23,7 @@ class LevelDisplay extends StatelessWidget { borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( - color: AppTheme.primaryColor.withOpacity(0.5), + color: AppTheme.primaryColor.withValues(alpha: 0.5), blurRadius: 12, spreadRadius: 2, ), @@ -57,4 +57,3 @@ class LevelDisplay extends StatelessWidget { ); } } - diff --git a/lib/src/features/dashboard/presentation/widgets/start_raid_button.dart b/lib/src/features/dashboard/presentation/widgets/start_raid_button.dart index e46d41e..ecb179f 100644 --- a/lib/src/features/dashboard/presentation/widgets/start_raid_button.dart +++ b/lib/src/features/dashboard/presentation/widgets/start_raid_button.dart @@ -54,7 +54,8 @@ class _StartRaidButtonState extends State borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: AppTheme.primaryColor.withOpacity(_glowAnimation.value), + color: AppTheme.primaryColor + .withValues(alpha: _glowAnimation.value), blurRadius: 20, spreadRadius: 5, ), @@ -96,4 +97,3 @@ class _StartRaidButtonState extends State ); } } - diff --git a/lib/src/features/dashboard/presentation/widgets/xp_bar_widget.dart b/lib/src/features/dashboard/presentation/widgets/xp_bar_widget.dart index 3b5e5fb..57e5245 100644 --- a/lib/src/features/dashboard/presentation/widgets/xp_bar_widget.dart +++ b/lib/src/features/dashboard/presentation/widgets/xp_bar_widget.dart @@ -49,7 +49,7 @@ class XPBarWidget extends StatelessWidget { color: AppTheme.xpBarBackground, borderRadius: BorderRadius.circular(16), border: Border.all( - color: AppTheme.primaryColor.withOpacity(0.3), + color: AppTheme.primaryColor.withValues(alpha: 0.3), width: 2, ), ), @@ -64,13 +64,13 @@ class XPBarWidget extends StatelessWidget { gradient: LinearGradient( colors: [ AppTheme.primaryColor, - AppTheme.primaryColor.withOpacity(0.7), + AppTheme.primaryColor.withValues(alpha: 0.7), ], ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: AppTheme.primaryColor.withOpacity(0.5), + color: AppTheme.primaryColor.withValues(alpha: 0.5), blurRadius: 8, spreadRadius: 1, ), @@ -86,15 +86,15 @@ class XPBarWidget extends StatelessWidget { child: Text( '${(progress * 100).toStringAsFixed(0)}%', style: Theme.of(context).textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.bold, - color: Colors.white, - shadows: [ - const Shadow( - color: Colors.black, - blurRadius: 4, - ), - ], + fontWeight: FontWeight.bold, + color: Colors.white, + shadows: [ + const Shadow( + color: Colors.black, + blurRadius: 4, ), + ], + ), ), ), ], @@ -103,4 +103,3 @@ class XPBarWidget extends StatelessWidget { ); } } - diff --git a/lib/src/features/gamification/application/quest_service.dart b/lib/src/features/gamification/application/quest_service.dart new file mode 100644 index 0000000..670d51b --- /dev/null +++ b/lib/src/features/gamification/application/quest_service.dart @@ -0,0 +1,179 @@ +import 'dart:math'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:drift/drift.dart'; + +import '../../../shared/data/local/app_database.dart'; +import '../../../shared/data/repositories/user_repository.dart'; +import '../../../shared/data/repositories/workout_repository.dart'; +import '../data/repositories/quest_repository.dart'; +import '../../../core/constants/app_constants.dart'; + +enum QuestTrigger { + workoutComplete, + volume, + repCount, + inventoryChange, +} + +final questServiceProvider = Provider((ref) { + final questRepo = ref.watch(questRepositoryProvider); + final userRepo = ref.watch(userRepositoryProvider); + return QuestService(ref: ref, questRepo: questRepo, userRepo: userRepo); +}); + +class QuestService { + final Ref ref; + final QuestRepository questRepo; + final UserRepository userRepo; + + QuestService({ + required this.ref, + required this.questRepo, + required this.userRepo, + }); + + Future checkAndGenerateQuests() async { + await _cleanupExpired(); + await _generateDailiesIfNeeded(); + await _ensureStoryQuests(); + } + + Future _cleanupExpired() async { + await questRepo.cleanupExpiredQuests(); + } + + Future _generateDailiesIfNeeded() async { + final activeDailies = await questRepo.getActiveQuests(); + final hasDailies = activeDailies.any((q) => q.type == 'daily'); + + if (!hasDailies) { + debugPrint('🎲 Generating new Daily Quests...'); + final random = Random(); + final newQuests = []; + + final pool = List<_QuestTemplate>.from(_dailyQuestPool)..shuffle(); + final selected = pool.take(3); + + final now = DateTime.now(); + final endOfDay = DateTime(now.year, now.month, now.day, 23, 59, 59); + + for (var template in selected) { + newQuests.add(QuestCollection( + id: 'daily_${now.millisecondsSinceEpoch}_${random.nextInt(1000)}', + type: 'daily', + title: template.title, + description: template.description, + targetValue: template.target, + currentValue: 0, + rewardXP: template.xp, + rewardItem: template.itemId, + isCompleted: false, + isClaimed: false, + expiresAt: endOfDay, + createdAt: now, + )); + } + + for (var q in newQuests) { + await questRepo.createQuest(q); + } + } + } + + Future _ensureStoryQuests() async { + final activeQuests = await questRepo.getActiveQuests(); + + // Helper: Prüft ob Quest-ID schon existiert (aktiv oder erledigt) + // Hinweis: getActiveQuests liefert aktuell alle nicht-abgelaufenen. + // Für Story Quests (die nie ablaufen) reicht das. + bool hasQuest(String id) => activeQuests.any((q) => q.id == id); + + if (!hasQuest('story_initiate')) { + await questRepo.createQuest(QuestCollection( + id: 'story_initiate', + type: 'story', + title: 'The Awakening', + description: 'Complete your first workout to prove your worth.', + targetValue: 1, + currentValue: 0, + rewardXP: 100, + isCompleted: false, + isClaimed: false, + createdAt: DateTime.now(), + )); + } + + if (!hasQuest('story_inventory')) { + await questRepo.createQuest(QuestCollection( + id: 'story_inventory', + type: 'story', + title: 'Armory Master', + description: 'Setup your equipment inventory.', + targetValue: 1, + currentValue: 0, + rewardXP: 50, + isCompleted: false, + isClaimed: false, + createdAt: DateTime.now(), + )); + final inventory = await userRepo.getInventorySettingsAsync(); + if ((inventory['plates'] as List).isNotEmpty) { + await questRepo.updateProgress('story_inventory', 1); + } + } + } + + Future reportEvent(QuestTrigger trigger, {dynamic data}) async { + final activeQuests = await questRepo.getActiveQuests(); + + for (var quest in activeQuests) { + if (quest.isCompleted) continue; + + if (quest.id == 'story_initiate' && + trigger == QuestTrigger.workoutComplete) { + await questRepo.updateProgress(quest.id, 1); + } + + if (quest.title == 'Volume Eater' && trigger == QuestTrigger.volume) { + final volume = data as int; // kg + await questRepo.updateProgress(quest.id, volume); + } + + if (quest.title == 'Workout Warrior' && + trigger == QuestTrigger.workoutComplete) { + await questRepo.updateProgress(quest.id, 1); + } + + if (quest.title == 'Rep Collector' && trigger == QuestTrigger.repCount) { + final reps = data as int; + await questRepo.updateProgress(quest.id, reps); + } + } + } +} + +// --- QUEST DEFINITIONS --- + +class _QuestTemplate { + final String title; + final String description; + final int target; + final int xp; + final String? itemId; + + const _QuestTemplate(this.title, this.description, this.target, this.xp, + [this.itemId]); +} + +const List<_QuestTemplate> _dailyQuestPool = [ + _QuestTemplate( + 'Volume Eater', 'Move a total of 500kg in a single day.', 500, 100), + _QuestTemplate('Workout Warrior', 'Complete 1 Workout today.', 1, 50), + _QuestTemplate('Rep Collector', + 'Perform 50 total repetitions across all exercises.', 50, 75), + _QuestTemplate('Early Bird', 'Start a workout before noon.', 1, + 50), // Logik müsste Zeit prüfen + _QuestTemplate( + 'Iron Discipline', 'Log your bodyweight in the profile.', 1, 25), +]; diff --git a/lib/src/features/gamification/data/repositories/quest_repository.dart b/lib/src/features/gamification/data/repositories/quest_repository.dart new file mode 100644 index 0000000..d60f6b3 --- /dev/null +++ b/lib/src/features/gamification/data/repositories/quest_repository.dart @@ -0,0 +1,83 @@ +import 'package:drift/drift.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../../shared/data/local/app_database.dart'; +import '../../../../../main.dart'; + +final questRepositoryProvider = Provider((ref) { + final db = ref.watch(appDatabaseProvider); + return QuestRepository(db: db); +}); + +class QuestRepository { + final AppDatabase db; + + QuestRepository({required this.db}); + + Future> getActiveQuests() async { + final now = DateTime.now(); + return await (db.select(db.quests) + ..where((q) { + final notExpired = + q.expiresAt.isNull() | q.expiresAt.isBiggerThanValue(now); + return notExpired; + }) + ..orderBy([ + (q) => + OrderingTerm(expression: q.isCompleted, mode: OrderingMode.asc) + ])) // Offene zuerst + .get(); + } + + Future createQuest(QuestCollection quest) async { + await db.into(db.quests).insertOnConflictUpdate(QuestsCompanion( + id: Value(quest.id), + type: Value(quest.type), + title: Value(quest.title), + description: Value(quest.description), + targetValue: Value(quest.targetValue), + currentValue: Value(quest.currentValue), + rewardXP: Value(quest.rewardXP), + rewardItem: Value(quest.rewardItem), + expiresAt: Value(quest.expiresAt), + createdAt: Value(DateTime.now()), + )); + } + + Future updateProgress(String questId, int addValue) async { + final quest = await (db.select(db.quests) + ..where((q) => q.id.equals(questId))) + .getSingleOrNull(); + if (quest != null && !quest.isCompleted) { + final newValue = quest.currentValue + addValue; + final isComplete = newValue >= quest.targetValue; + + await (db.update(db.quests)..where((q) => q.id.equals(questId))) + .write(QuestsCompanion( + currentValue: Value(newValue), + isCompleted: Value(isComplete), + )); + } + } + + Future claimQuest(String questId) async { + await (db.update(db.quests)..where((q) => q.id.equals(questId))).write( + const QuestsCompanion(isClaimed: Value(true)), + ); + } + + Future cleanupExpiredQuests() async { + final now = DateTime.now(); + await (db.delete(db.quests) + ..where((q) => q.expiresAt.isSmallerThanValue(now))) + .go(); + } + + Stream> watchQuests() { + return (db.select(db.quests) + ..orderBy([ + (q) => + OrderingTerm(expression: q.isCompleted, mode: OrderingMode.desc) + ])) + .watch(); + } +} diff --git a/lib/src/features/gamification/domain/entities/avatar_config.dart b/lib/src/features/gamification/domain/entities/avatar_config.dart index f731cf2..09a5f37 100644 --- a/lib/src/features/gamification/domain/entities/avatar_config.dart +++ b/lib/src/features/gamification/domain/entities/avatar_config.dart @@ -1,18 +1,50 @@ +// import '../../../../core/constants/asset_paths.dart'; + +// class AvatarConfig { +// final String gender; // 'male' or 'female' +// final int variant; // 1 to 8 +// final String selectedBackground; + +// const AvatarConfig({ +// this.gender = 'male', +// this.variant = 1, +// this.selectedBackground = 'bg_street_day', +// }); + +// factory AvatarConfig.fromJson(Map json) { +// return AvatarConfig( +// gender: json['gender'] ?? 'male', +// variant: json['variant'] ?? 1, +// ); +// } + +// Map toJson() { +// return { +// 'gender': gender, +// 'variant': variant, +// }; +// } + +// String get assetPath => AssetPaths.getAvatarPath(gender, variant); +// } import '../../../../core/constants/asset_paths.dart'; class AvatarConfig { - final String gender; // 'male' or 'female' - final int variant; // 1 to 8 + final String gender; + final int variant; + final String selectedBackground; // NEU const AvatarConfig({ this.gender = 'male', this.variant = 1, + this.selectedBackground = 'bg_street_day', // Default }); factory AvatarConfig.fromJson(Map json) { return AvatarConfig( gender: json['gender'] ?? 'male', variant: json['variant'] ?? 1, + selectedBackground: json['selected_background'] ?? 'bg_street_day', // NEU ); } @@ -20,6 +52,7 @@ class AvatarConfig { return { 'gender': gender, 'variant': variant, + 'selected_background': selectedBackground, // NEU }; } diff --git a/lib/src/features/gamification/domain/entities/item_catalog.dart b/lib/src/features/gamification/domain/entities/item_catalog.dart new file mode 100644 index 0000000..63370fb --- /dev/null +++ b/lib/src/features/gamification/domain/entities/item_catalog.dart @@ -0,0 +1,68 @@ +import '../../../../core/constants/asset_paths.dart'; + +enum ItemCategory { background, badge } + +class UnlockableItem { + final String id; + final String name; + final String description; + final String assetPath; + final ItemCategory category; + final int unlockLevel; // 0 = Start + final String? unlockQuestId; // Optional für später + + const UnlockableItem({ + required this.id, + required this.name, + required this.description, + required this.assetPath, + required this.category, + this.unlockLevel = 0, + this.unlockQuestId, + }); +} + +class ItemCatalog { + // Alle verfügbaren Hintergründe + static const List backgrounds = [ + UnlockableItem( + id: 'bg_street_day', + name: 'Street Park (Day)', + description: 'Where every journey begins.', + assetPath: AssetPaths.bgStreetParkDay, + category: ItemCategory.background, + unlockLevel: 0, // Start + ), + UnlockableItem( + id: 'bg_street_night', + name: 'Street Park (Night)', + description: 'For those who grind while others sleep.', + assetPath: AssetPaths.bgStreetParkNight, + category: ItemCategory.background, + unlockLevel: 5, + ), + UnlockableItem( + id: 'bg_commercial', + name: 'Commercial Gym', + description: 'Clean equipment, AC, and mirrors everywhere.', + assetPath: AssetPaths.bgCommercialGym, + category: ItemCategory.background, + unlockLevel: 10, + ), + UnlockableItem( + id: 'bg_underground', + name: 'Underground Dojo', + description: 'No rules. Just heavy iron and concrete.', + assetPath: AssetPaths.bgUndergroundGym, + category: ItemCategory.background, + unlockLevel: 20, + ), + ]; + + static UnlockableItem getBackground(String id) { + return backgrounds.firstWhere( + (b) => b.id == id, + orElse: () => backgrounds.first, // Fallback auf Start + ); + } +} diff --git a/lib/src/features/gamification/presentation/screens/codex_screen.dart b/lib/src/features/gamification/presentation/screens/codex_screen.dart index 5b7b478..8806ff1 100644 --- a/lib/src/features/gamification/presentation/screens/codex_screen.dart +++ b/lib/src/features/gamification/presentation/screens/codex_screen.dart @@ -1,5 +1,7 @@ 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'; @@ -8,9 +10,11 @@ class CodexScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + return Scaffold( appBar: AppBar( - title: const Text('Creature Codex'), + title: Text(l10n.codexTitle), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => context.go('/hub'), @@ -18,40 +22,31 @@ class CodexScreen extends StatelessWidget { ), body: ListView( padding: const EdgeInsets.all(16), - children: const [ + children: [ _LoreCard( - name: 'Iron Golem', - title: 'The Weight of the Earth', - description: - 'Forged from the tectonic plates of the Deep Earth, the Iron Golem exists only to crush the weak. ' - 'It embodies the unrelenting force of gravity acting on a heavy load.\n\n' - 'It respects only one thing: The raw power of the LEGS that can stand up against its crushing weight.', + name: l10n.enemyIronGolemName, + title: l10n.enemyIronGolemTitle, + description: l10n.enemyIronGolemDesc, assetPath: AssetPaths.enemyIronGolem, - exercise: 'Squat Nemesis', + exercise: l10n.enemyIronGolemNemesis, color: Colors.redAccent, ), - SizedBox(height: 24), + const SizedBox(height: 24), _LoreCard( - name: 'Gravity Demon', - title: 'The Abyssal Pull', - description: - 'A spirit of pure downward force that clings to the back of adventurers. ' - 'It whispers lies of weakness into your ear while dragging you towards the abyss.\n\n' - 'Only those with a back of steel and the will to pull themselves up can escape its grasp.', + name: l10n.enemyGravityDemonName, + title: l10n.enemyGravityDemonTitle, + description: l10n.enemyGravityDemonDesc, assetPath: AssetPaths.enemyGravityDemon, - exercise: 'Pull-up Nemesis', + exercise: l10n.enemyGravityDemonNemesis, color: Colors.purpleAccent, ), - SizedBox(height: 24), + const SizedBox(height: 24), _LoreCard( - name: 'Pressure Phantom', - title: 'The Invisible Crusher', - description: - 'An ethereal entity that compresses the very air around you. ' - 'It seeks to collapse the chest and shoulders of any who dare to push against it.\n\n' - 'Defeat it by pushing through the pain with explosive dipping power.', + name: l10n.enemyPressurePhantomName, + title: l10n.enemyPressurePhantomTitle, + description: l10n.enemyPressurePhantomDesc, assetPath: AssetPaths.enemyPressurePhantom, - exercise: 'Dip Nemesis', + exercise: l10n.enemyPressurePhantomNemesis, color: Colors.cyanAccent, ), ], @@ -83,14 +78,14 @@ class _LoreCard extends StatelessWidget { clipBehavior: Clip.antiAlias, child: Container( decoration: BoxDecoration( - border: Border.all(color: color.withOpacity(0.5), width: 1), + border: Border.all(color: color.withValues(alpha: 0.5), width: 1), borderRadius: BorderRadius.circular(16), gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ AppTheme.surfaceColor, - color.withOpacity(0.1), + color.withValues(alpha: 0.1), ], ), ), @@ -112,7 +107,7 @@ class _LoreCard extends StatelessWidget { child: Image.asset( assetPath, fit: BoxFit.contain, - color: Colors.white.withOpacity(0.9), + color: Colors.white.withValues(alpha: 0.9), colorBlendMode: BlendMode.modulate, ), ), diff --git a/lib/src/features/gamification/presentation/screens/quest_log.dart b/lib/src/features/gamification/presentation/screens/quest_log.dart new file mode 100644 index 0000000..28c54c3 --- /dev/null +++ b/lib/src/features/gamification/presentation/screens/quest_log.dart @@ -0,0 +1,92 @@ +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 '../../data/repositories/quest_repository.dart'; +import '../../../../shared/data/local/app_database.dart'; // Für QuestCollection Typ +import '../widgets/quest_item.dart'; + +class QuestLogScreen extends ConsumerWidget { + const QuestLogScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final questRepo = ref.watch(questRepositoryProvider); + final l10n = AppLocalizations.of(context)!; + + return DefaultTabController( + length: 2, + child: Scaffold( + appBar: AppBar( + title: const Text('Quest Log'), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => context.go('/hub'), + ), + bottom: TabBar( + indicatorColor: AppTheme.primaryColor, + labelColor: AppTheme.primaryColor, + unselectedLabelColor: Colors.grey, + tabs: [ + Tab(text: l10n.questTabDailies), + Tab(text: l10n.questTabJourney), + ], + ), + ), + body: StreamBuilder>( + stream: questRepo.watchQuests(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } + + final allQuests = snapshot.data ?? []; + + final dailies = allQuests.where((q) => q.type == 'daily').toList(); + final story = allQuests.where((q) => q.type != 'daily').toList(); + + return TabBarView( + children: [ + _QuestList( + quests: dailies, emptyMessage: l10n.questEmptyDailies), + _QuestList(quests: story, emptyMessage: l10n.questEmptyJourney), + ], + ); + }, + ), + ), + ); + } +} + +class _QuestList extends StatelessWidget { + final List quests; + final String emptyMessage; + + const _QuestList({required this.quests, required this.emptyMessage}); + + @override + Widget build(BuildContext context) { + if (quests.isEmpty) { + return Center( + child: Text( + emptyMessage, + textAlign: TextAlign.center, + style: TextStyle(color: AppTheme.textSecondary), + ), + ); + } + + return ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: quests.length, + itemBuilder: (context, index) { + return QuestItem(quest: quests[index]); + }, + ); + } +} diff --git a/lib/src/features/gamification/presentation/widgets/avatar_renderer.dart b/lib/src/features/gamification/presentation/widgets/avatar_renderer.dart index 7bd9aa8..faf9b32 100644 --- a/lib/src/features/gamification/presentation/widgets/avatar_renderer.dart +++ b/lib/src/features/gamification/presentation/widgets/avatar_renderer.dart @@ -20,7 +20,7 @@ class AvatarRenderer extends StatelessWidget { shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.3), + color: Colors.black.withValues(alpha: 0.3), blurRadius: 10, spreadRadius: 2, ), diff --git a/lib/src/features/gamification/presentation/widgets/quest_board.dart b/lib/src/features/gamification/presentation/widgets/quest_board.dart new file mode 100644 index 0000000..0cc6fcd --- /dev/null +++ b/lib/src/features/gamification/presentation/widgets/quest_board.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import '../../../../core/theme/app_theme.dart'; +import '../../../gamification/data/repositories/quest_repository.dart'; +import '../../../../shared/data/local/app_database.dart'; + +class QuestBoardWidget extends ConsumerWidget { + const QuestBoardWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final questRepo = ref.watch(questRepositoryProvider); + + return StreamBuilder>( + stream: questRepo.watchQuests(), + builder: (context, snapshot) { + final allQuests = snapshot.data ?? []; + final activeQuests = allQuests + .where( + (q) => !q.isClaimed && (q.type == 'daily' || q.type == 'story')) + .take(3) + .toList(); + + if (activeQuests.isEmpty) return const SizedBox.shrink(); + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppTheme.surfaceColor, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.white10), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'DAILY BOUNTIES', + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: AppTheme.secondaryColor, + letterSpacing: 1.5, + fontWeight: FontWeight.bold), + ), + GestureDetector( + onTap: () => context.go('/quests'), + child: const Text('VIEW ALL >', + style: TextStyle(fontSize: 10, color: Colors.grey)), + ), + ], + ), + const SizedBox(height: 12), + ...activeQuests.map((q) => _MiniQuestRow(quest: q)).toList(), + ], + ), + ); + }, + ); + } +} + +class _MiniQuestRow extends StatelessWidget { + final QuestCollection quest; + const _MiniQuestRow({required this.quest}); + + @override + Widget build(BuildContext context) { + final progress = (quest.currentValue / quest.targetValue).clamp(0.0, 1.0); + final isComplete = quest.isCompleted; + + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + children: [ + Icon( + isComplete ? Icons.check_circle : Icons.circle_outlined, + size: 16, + color: isComplete ? AppTheme.successColor : Colors.grey, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + quest.title, + style: TextStyle( + fontSize: 12, + color: isComplete ? Colors.grey : Colors.white, + decoration: isComplete ? TextDecoration.lineThrough : null, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + if (!isComplete) + SizedBox( + width: 40, + height: 4, + child: LinearProgressIndicator( + value: progress, + backgroundColor: Colors.white10, + color: AppTheme.secondaryColor, + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/features/gamification/presentation/widgets/quest_item.dart b/lib/src/features/gamification/presentation/widgets/quest_item.dart new file mode 100644 index 0000000..6704ac5 --- /dev/null +++ b/lib/src/features/gamification/presentation/widgets/quest_item.dart @@ -0,0 +1,193 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../../core/theme/app_theme.dart'; +import '../../../../shared/data/local/app_database.dart'; +import '../../data/repositories/quest_repository.dart'; +import '../../domain/entities/item_catalog.dart'; + +class QuestItem extends ConsumerStatefulWidget { + final QuestCollection quest; + + const QuestItem({ + super.key, + required this.quest, + }); + + @override + ConsumerState createState() => _QuestItemState(); +} + +class _QuestItemState extends ConsumerState { + bool _isClaiming = false; + + Future _handleClaim() async { + setState(() => _isClaiming = true); + try { + final questRepo = ref.read(questRepositoryProvider); + await questRepo.claimQuest(widget.quest.id); + + if (mounted) { + 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!'), + ], + ), + backgroundColor: Colors.black87, + ), + ); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text('Error: $e'))); + } + } finally { + if (mounted) setState(() => _isClaiming = false); + } + } + + @override + Widget build(BuildContext context) { + final progress = + (widget.quest.currentValue / widget.quest.targetValue).clamp(0.0, 1.0); + final isComplete = widget.quest.isCompleted; + final isClaimed = widget.quest.isClaimed; + + return Card( + margin: const EdgeInsets.only(bottom: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: isComplete && !isClaimed + ? const BorderSide(color: AppTheme.successColor, width: 1) + : BorderSide.none, + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + Row( + children: [ + Icon( + _getIconForType(widget.quest.type), + color: isComplete + ? AppTheme.successColor + : AppTheme.primaryColor, + size: 20, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + widget.quest.title, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + color: isClaimed ? Colors.grey : Colors.white, + decoration: + isClaimed ? TextDecoration.lineThrough : null, + ), + ), + ), + if (isClaimed) + const Icon(Icons.check, color: Colors.grey, size: 20) + else if (widget.quest.rewardXP > 0) + Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: AppTheme.xpBarFill.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + '+${widget.quest.rewardXP} XP', + style: const TextStyle( + color: AppTheme.primaryColor, + fontSize: 12, + fontWeight: FontWeight.bold), + ), + ), + ], + ), + + const SizedBox(height: 8), + Text( + widget.quest.description, + style: TextStyle( + color: isClaimed ? Colors.grey : AppTheme.textSecondary, + fontSize: 12), + ), + const SizedBox(height: 16), + + // Progress Bar & Action + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: LinearProgressIndicator( + value: progress, + backgroundColor: Colors.grey[800], + color: isComplete + ? AppTheme.successColor + : AppTheme.primaryColor, + minHeight: 8, + ), + ), + const SizedBox(height: 4), + Text( + '${widget.quest.currentValue} / ${widget.quest.targetValue}', + style: + const TextStyle(color: Colors.grey, fontSize: 10), + ), + ], + ), + ), + const SizedBox(width: 16), + if (isComplete && !isClaimed) + ElevatedButton( + onPressed: _isClaiming ? null : _handleClaim, + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.successColor, + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 0), + minimumSize: const Size(0, 32), + ), + child: _isClaiming + ? const SizedBox( + width: 12, + height: 12, + child: CircularProgressIndicator( + strokeWidth: 2, color: Colors.white)) + : const Text('CLAIM', style: TextStyle(fontSize: 12)), + ) + else if (widget.quest.rewardItem != null && !isClaimed) + const Icon(Icons.inventory_2, + color: AppTheme.secondaryColor, size: 20), + ], + ), + ], + ), + ), + ); + } + + IconData _getIconForType(String type) { + switch (type) { + case 'daily': + return Icons.today; + case 'story': + return Icons.auto_stories; + case 'milestone': + return Icons.emoji_events; + default: + return Icons.task_alt; + } + } +} diff --git a/lib/src/features/history/presentation/screens/history_screen.dart b/lib/src/features/history/presentation/screens/history_screen.dart index 47326e8..85c8917 100644 --- a/lib/src/features/history/presentation/screens/history_screen.dart +++ b/lib/src/features/history/presentation/screens/history_screen.dart @@ -1,15 +1,14 @@ -import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; +import 'package:slrpg_app/l10n/app_localizations.dart'; import '../../../../core/theme/app_theme.dart'; +import '../../../../shared/data/local/app_database.dart'; import '../../../../shared/data/repositories/user_repository.dart'; import '../../../../shared/data/repositories/workout_repository.dart'; -import '../../../../shared/data/local/collections/workout_collection.dart'; import '../../../../shared/domain/entities/exercise.dart'; -import '../../../../shared/domain/entities/workout_set.dart'; class HistoryScreen extends ConsumerStatefulWidget { const HistoryScreen({super.key}); @@ -27,16 +26,15 @@ class _HistoryScreenState extends ConsumerState { if (user == null) return []; final userId = user.serverId ?? user.id.toString(); - return workoutRepo.getCompletedWorkouts(userId); // ID übergeben + return workoutRepo.getCompletedWorkouts(userId); } @override Widget build(BuildContext context) { - final workoutRepo = ref.watch(workoutRepositoryProvider); - + final l10n = AppLocalizations.of(context)!; return Scaffold( appBar: AppBar( - title: const Text('Quest Log'), + title: Text(l10n.historyTitle), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => context.go('/hub'), @@ -57,16 +55,16 @@ class _HistoryScreenState extends ConsumerState { Icon( Icons.history_edu, size: 80, - color: AppTheme.primaryColor.withOpacity(0.5), + color: AppTheme.primaryColor.withValues(alpha: 0.5), ), const SizedBox(height: 16), Text( - 'No completed quests yet', + l10n.historyEmptyTitle, style: Theme.of(context).textTheme.headlineMedium, ), const SizedBox(height: 8), Text( - 'Complete a workout to fill your journal', + l10n.historyEmptyBody, style: Theme.of(context).textTheme.bodyMedium, ), ], @@ -74,7 +72,7 @@ class _HistoryScreenState extends ConsumerState { ); } - final workouts = snapshot.data! + final workouts = List.from(snapshot.data!) ..sort((a, b) => b.completedAt!.compareTo(a.completedAt!)); return ListView.builder( @@ -98,8 +96,10 @@ class _WorkoutHistoryCard extends StatelessWidget { List _parseExercises() { try { - final List jsonList = jsonDecode(workout.exercisesJson); - return jsonList.map((json) => Exercise.fromJson(json)).toList(); + final List list = workout.exercises; + return list + .map((json) => Exercise.fromJson(json as Map)) + .toList(); } catch (e) { debugPrint('Error parsing workout history: $e'); return []; @@ -111,6 +111,7 @@ class _WorkoutHistoryCard extends StatelessWidget { final dateStr = DateFormat.yMMMd().format(workout.completedAt!); final timeStr = DateFormat.jm().format(workout.completedAt!); final exercises = _parseExercises(); + final l10n = AppLocalizations.of(context)!; final summary = exercises .map((e) => @@ -124,7 +125,7 @@ class _WorkoutHistoryCard extends StatelessWidget { tilePadding: const EdgeInsets.all(16), leading: _buildDateBadge(context, workout), title: Text( - summary.isEmpty ? 'Unknown Workout' : summary, + summary.isEmpty ? l10n.historyUnknownWorkout : summary, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, color: AppTheme.primaryColor, @@ -182,9 +183,9 @@ class _WorkoutHistoryCard extends StatelessWidget { width: 50, height: 50, decoration: BoxDecoration( - color: AppTheme.primaryColor.withOpacity(0.1), + color: AppTheme.primaryColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), - border: Border.all(color: AppTheme.primaryColor.withOpacity(0.3)), + border: Border.all(color: AppTheme.primaryColor.withValues(alpha: 0.3)), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -237,10 +238,10 @@ class _ExerciseDetailRow extends StatelessWidget { Table( defaultVerticalAlignment: TableCellVerticalAlignment.middle, columnWidths: const { - 0: FlexColumnWidth(1), // Set - 1: FlexColumnWidth(2), // Weight - 2: FlexColumnWidth(2), // Reps - 3: FlexColumnWidth(1), // Type (AMRAP/FSL) + 0: FlexColumnWidth(1), + 1: FlexColumnWidth(2), + 2: FlexColumnWidth(2), + 3: FlexColumnWidth(1), }, children: exercise.sets.where((s) => s.completed).map((set) { return TableRow( diff --git a/lib/src/features/inventory/presentation/screens/inventory_screen.dart b/lib/src/features/inventory/presentation/screens/inventory_screen.dart index 1372aad..72555bd 100644 --- a/lib/src/features/inventory/presentation/screens/inventory_screen.dart +++ b/lib/src/features/inventory/presentation/screens/inventory_screen.dart @@ -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 '../../../../core/constants/app_constants.dart'; @@ -29,7 +30,7 @@ class _InventoryScreenState extends ConsumerState { Future _loadCurrentInventory() async { final userRepo = ref.read(userRepositoryProvider); - final inventory = userRepo.getInventorySettings(); + final inventory = await userRepo.getInventorySettingsAsync(); final barWeight = (inventory['bar_weight'] as num?)?.toDouble() ?? 20.0; @@ -62,7 +63,8 @@ class _InventoryScreenState extends ConsumerState { }; for (var b in bandsList) { - final color = b['color'] as String; + final band = b as Map; + final color = band['color'] as String; if (bandMap.containsKey(color)) { bandMap[color] = true; } @@ -120,13 +122,16 @@ class _InventoryScreenState extends ConsumerState { } Future _saveChanges() async { + final l10n = AppLocalizations.of(context)!; setState(() => _isLoading = true); try { final userRepo = ref.read(userRepositoryProvider); final platesList = []; _plateInventory.forEach((weight, count) { - for (int i = 0; i < count; i++) platesList.add(weight); + for (int i = 0; i < count; i++) { + platesList.add(weight); + } }); final bandsList = >[]; @@ -150,7 +155,7 @@ class _InventoryScreenState extends ConsumerState { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Inventory updated successfully')), + SnackBar(content: Text(l10n.inventoryUpdatedSuccess)), ); setState(() { _hasChanges = false; @@ -186,13 +191,14 @@ class _InventoryScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; if (_isLoading && !_hasChanges) { return const Scaffold(body: Center(child: CircularProgressIndicator())); } return Scaffold( appBar: AppBar( - title: const Text('Manage Equipment'), + title: Text(l10n.inventoryTitle), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => context.go('/hub'), @@ -201,7 +207,7 @@ class _InventoryScreenState extends ConsumerState { if (_hasChanges) TextButton( onPressed: _saveChanges, - child: const Text('SAVE', + child: Text(l10n.saveButton, style: TextStyle( color: AppTheme.primaryColor, fontWeight: FontWeight.bold)), @@ -219,7 +225,7 @@ class _InventoryScreenState extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Barbell Weight', + Text(l10n.inventoryBarbellWeight, style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 16), Row( @@ -250,32 +256,52 @@ class _InventoryScreenState extends ConsumerState { ), ), const SizedBox(height: 24), - Text('Quick Presets', + Text(l10n.inventoryPresets, style: Theme.of(context) .textTheme .titleMedium ?.copyWith(color: AppTheme.textSecondary)), const SizedBox(height: 8), - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - ActionChip( - label: const Text('Home Gym'), - onPressed: () => _applyPreset('home')), - const SizedBox(width: 8), - ActionChip( - label: const Text('Commercial'), - onPressed: () => _applyPreset('commercial')), - const SizedBox(width: 8), - ActionChip( - label: const Text('Minimal'), - onPressed: () => _applyPreset('minimal')), - ], - ), + LayoutBuilder( + builder: (context, constraints) { + final screenWidth = constraints.maxWidth; + final chipWidth = 130.0; + final spacing = 8.0; + final totalWidth = (chipWidth * 3) + (spacing * 2); + + if (screenWidth < totalWidth) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildPresetChip(l10n.inventoryPresetHome, 'home'), + const SizedBox(height: 8), + _buildPresetChip( + l10n.inventoryPresetCommercial, 'commercial'), + const SizedBox(height: 8), + _buildPresetChip(l10n.inventoryPresetMinimal, 'minimal'), + ], + ); + } else { + return Row( + children: [ + Expanded( + child: _buildPresetChip( + l10n.inventoryPresetHome, 'home')), + const SizedBox(width: 8), + Expanded( + child: _buildPresetChip( + l10n.inventoryPresetCommercial, 'commercial')), + const SizedBox(width: 8), + Expanded( + child: _buildPresetChip( + l10n.inventoryPresetMinimal, 'minimal')), + ], + ); + } + }, ), const SizedBox(height: 24), - Text('Plates Available', + Text(l10n.inventoryPlates, style: Theme.of(context) .textTheme .titleMedium @@ -294,43 +320,130 @@ class _InventoryScreenState extends ConsumerState { ); }), const SizedBox(height: 24), - Text('Resistance Bands (Assistance)', + Text(l10n.inventoryBands, style: Theme.of(context) .textTheme .titleMedium ?.copyWith(color: AppTheme.textSecondary)), const SizedBox(height: 8), - Wrap( - spacing: 8, - runSpacing: 8, - children: _bandInventory.entries.map((entry) { - final resistance = AppConstants.defaultBands[entry.key] ?? 0; - return FilterChip( - label: Text('${entry.key} (~${resistance.toInt()}kg)'), - selected: entry.value, - onSelected: (bool selected) { - setState(() { - _bandInventory[entry.key] = selected; - _hasChanges = true; - }); + LayoutBuilder( + builder: (context, constraints) { + final screenWidth = constraints.maxWidth; + + int crossAxisCount = 2; + if (screenWidth > 600) { + crossAxisCount = 4; + } else if (screenWidth > 400) { + crossAxisCount = 2; + } + + final bandEntries = _bandInventory.entries.toList(); + + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + childAspectRatio: 2.5, + ), + itemCount: bandEntries.length, + itemBuilder: (context, index) { + final entry = bandEntries[index]; + final resistance = + AppConstants.defaultBands[entry.key] ?? 0; + + return _buildBandChip( + entry.key, + resistance.toInt(), + entry.value, + ); }, - selectedColor: _getBandColor(entry.key).withOpacity(0.3), - checkmarkColor: _getBandColor(entry.key), - side: BorderSide(color: _getBandColor(entry.key)), ); - }).toList(), + }, ), const SizedBox(height: 40), if (_hasChanges) ElevatedButton( onPressed: _isLoading ? null : _saveChanges, child: _isLoading - ? const CircularProgressIndicator(color: Colors.black) - : const Text('SAVE CHANGES'), + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: Colors.black, + strokeWidth: 2, + ), + ) + : Text(l10n.saveChangesButton), ), ], ), ), ); } + + Widget _buildPresetChip(String label, String preset) { + return OutlinedButton( + onPressed: () => _applyPreset(preset), + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12), + side: BorderSide( + color: AppTheme.primaryColor.withValues(alpha: 0.5), + ), + ), + child: Text(label), + ); + } + + Widget _buildBandChip(String color, int resistance, bool isSelected) { + return InkWell( + onTap: () { + setState(() { + _bandInventory[color] = !isSelected; + _hasChanges = true; + }); + }, + borderRadius: BorderRadius.circular(8), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: isSelected + ? _getBandColor(color).withValues(alpha: 0.2) + : AppTheme.surfaceColor, + border: Border.all( + color: _getBandColor(color), + width: isSelected ? 2 : 1, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (isSelected) + Icon( + Icons.check_circle, + color: _getBandColor(color), + size: 18, + ), + if (isSelected) const SizedBox(width: 4), + Expanded( + child: Text( + '$color (~${resistance}kg)', + style: TextStyle( + color: isSelected + ? _getBandColor(color) + : AppTheme.textSecondary, + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + fontSize: 12, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ); + } } diff --git a/lib/src/features/inventory/presentation/widgets/plate_counter.dart b/lib/src/features/inventory/presentation/widgets/plate_counter.dart index 63e44c3..fd780af 100644 --- a/lib/src/features/inventory/presentation/widgets/plate_counter.dart +++ b/lib/src/features/inventory/presentation/widgets/plate_counter.dart @@ -19,6 +19,16 @@ class PlateCounter extends StatelessWidget { return colorValue != null ? Color(colorValue) : Colors.grey; } + Color _getTextColor(double weight) { + if (weight == 5.0) { + return Colors.black; + } + if (weight <= 2.5) { + return Colors.white70; + } + return Colors.white; + } + @override Widget build(BuildContext context) { return Card( @@ -43,8 +53,8 @@ class PlateCounter extends StatelessWidget { weight == weight.toInt() ? '${weight.toInt()}' : weight.toStringAsFixed(2), - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: _getTextColor(weight), fontWeight: FontWeight.bold, fontSize: 12, ), @@ -68,10 +78,10 @@ class PlateCounter extends StatelessWidget { height: 40, alignment: Alignment.center, decoration: BoxDecoration( - color: AppTheme.primaryColor.withOpacity(0.1), + color: AppTheme.primaryColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), - border: - Border.all(color: AppTheme.primaryColor.withOpacity(0.3)), + border: Border.all( + color: AppTheme.primaryColor.withValues(alpha: 0.3)), ), child: Text( count.toString(), diff --git a/lib/src/features/onboarding/presentation/screens/avatar_setup_screen.dart b/lib/src/features/onboarding/presentation/screens/avatar_setup_screen.dart index 306701a..9be13ce 100644 --- a/lib/src/features/onboarding/presentation/screens/avatar_setup_screen.dart +++ b/lib/src/features/onboarding/presentation/screens/avatar_setup_screen.dart @@ -1,12 +1,15 @@ -import 'dart:convert'; +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:drift/drift.dart' hide Column; +import 'package:slrpg_app/l10n/app_localizations.dart'; import '../../../../core/theme/app_theme.dart'; -import '../../../../core/constants/app_constants.dart'; import '../../../../shared/data/repositories/user_repository.dart'; import '../../../../shared/data/repositories/cycle_repository.dart'; +import '../../../../shared/data/local/app_database.dart'; import '../../../gamification/domain/entities/avatar_config.dart'; import '../../../gamification/presentation/widgets/avatar_editor.dart'; import 'bodyweight_input_screen.dart'; @@ -23,64 +26,96 @@ class _AvatarSetupScreenState extends ConsumerState { bool _isLoading = false; Future _handleFinish() async { + final password = await _showPasswordDialog(); + if (password == null) return; + setState(() => _isLoading = true); try { final onboardingData = ref.read(onboardingDataProvider); final userRepo = ref.read(userRepositoryProvider); final inventorySettings = - onboardingData['inventory_settings'] as Map; + (onboardingData['inventory_settings'] as Map?) ?? {}; + final exerciseVariants = + onboardingData['exercise_variants'] as Map?; var user = await userRepo.getLocalUser(); if (user == null) { - user = await userRepo.register( - email: onboardingData['email'] ?? '', - password: onboardingData['password'] ?? '', - bodyweight: onboardingData['bodyweight'] ?? 80.0, - inventorySettings: inventorySettings, - ); - } else { - user.currentBodyweight = - onboardingData['bodyweight'] ?? user.currentBodyweight; - user.inventorySettingsJson = jsonEncode(inventorySettings); - user.isDirty = true; - await userRepo.saveLocalUser(user); + final email = onboardingData['email'] as String? ?? ''; + final bodyweight = + (onboardingData['bodyweight'] as num?)?.toDouble() ?? 80.0; - try { - await userRepo.updateBodyweight(user.currentBodyweight); - await userRepo.updateInventory(inventorySettings); - } catch (e) { - // Sync macht das später + if (email.isEmpty || password.isEmpty) { + throw Exception('Email or password is missing!'); } + + user = await userRepo.register( + email: email, + password: password, + bodyweight: bodyweight, + inventorySettings: inventorySettings, + exerciseVariants: exerciseVariants, + ); + await Future.delayed(const Duration(milliseconds: 100)); + user = await userRepo.getLocalUser(); + + if (user == null) { + throw Exception( + 'User registration succeeded but user not found in DB'); + } + } else { + user = user.copyWith( + currentBodyweight: + (onboardingData['bodyweight'] as num?)?.toDouble() ?? + user.currentBodyweight, + inventorySettings: Value(inventorySettings), + isDirty: true, + ); + await userRepo.saveLocalUser(user); } - user!.avatarConfigJson = jsonEncode(_config.toJson()); - user.isDirty = true; - await userRepo.saveLocalUser(user); + final avatarJson = _config.toJson(); - final trainingMaxes = - onboardingData['training_maxes'] as Map?; - if (trainingMaxes != null) { - final cycleRepo = ref.read(cycleRepositoryProvider); - final tmMap = { - 'squat': (trainingMaxes['squat'] as num?)?.toDouble() ?? 100.0, - 'pullup': (trainingMaxes['pullup'] as num?)?.toDouble() ?? 80.0, - 'dip': (trainingMaxes['dip'] as num?)?.toDouble() ?? 90.0, - }; - await cycleRepo.createCycle(tmMap); + user = user.copyWith( + avatarConfig: Value(avatarJson), + isDirty: true, + ); + await userRepo.saveLocalUser(user); + try { + final trainingMaxes = + onboardingData['training_maxes'] as Map?; + + if (trainingMaxes != null && trainingMaxes.isNotEmpty) { + final cycleRepo = ref.read(cycleRepositoryProvider); + + final tmMap = { + 'squat': (trainingMaxes['squat'] as num?)?.toDouble() ?? 100.0, + 'pullup': (trainingMaxes['pullup'] as num?)?.toDouble() ?? 80.0, + 'dip': (trainingMaxes['dip'] as num?)?.toDouble() ?? 90.0, + }; + + final cycle = await cycleRepo.createCycle(tmMap); + } + } catch (e, stackTrace) { + log('❌ CYCLE ERROR (non-critical): $e'); + log(' Error type: ${e.runtimeType}'); + log(' Stack:\n$stackTrace'); } if (mounted) { - ref.read(onboardingDataProvider.notifier).state = {}; + ref.read(onboardingDataProvider.notifier).clear(); + context.go('/hub'); } - } catch (e) { + } catch (e, stackTrace) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Setup failed: $e'), - backgroundColor: AppTheme.errorColor), + content: Text('Setup failed: $e'), + backgroundColor: AppTheme.errorColor, + duration: const Duration(seconds: 5), + ), ); } } finally { @@ -90,9 +125,10 @@ class _AvatarSetupScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; return Scaffold( appBar: AppBar( - title: const Text('Choose Your Hero'), + title: Text(l10n.setupAvatarTitle), actions: [ TextButton( onPressed: _isLoading ? null : _handleFinish, @@ -101,7 +137,7 @@ class _AvatarSetupScreenState extends ConsumerState { width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2)) - : const Text('FINISH', + : Text(l10n.finishButton, style: TextStyle( fontWeight: FontWeight.bold, color: AppTheme.primaryColor)), @@ -114,8 +150,8 @@ class _AvatarSetupScreenState extends ConsumerState { padding: const EdgeInsets.all(16), color: AppTheme.surfaceColor, width: double.infinity, - child: const Text( - 'This is how the legends will remember you.', + child: Text( + l10n.setupAvatarSubtitle, textAlign: TextAlign.center, style: TextStyle(fontStyle: FontStyle.italic, color: Colors.grey), ), @@ -130,4 +166,66 @@ class _AvatarSetupScreenState extends ConsumerState { ), ); } + + Future _showPasswordDialog() async { + final passwordController = TextEditingController(); + final confirmController = TextEditingController(); + final formKey = GlobalKey(); + final l10n = AppLocalizations.of(context)!; + + return showDialog( + context: context, + barrierDismissible: false, + builder: (context) => AlertDialog( + title: Text(l10n.secureAccountTitle), + content: Form( + key: formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(l10n.secureAccountBody), + const SizedBox(height: 16), + TextFormField( + controller: passwordController, + obscureText: true, + autofocus: true, + decoration: InputDecoration( + labelText: l10n.passwordLabel, + prefixIcon: Icon(Icons.lock), + ), + validator: (v) => + (v?.length ?? 0) < 8 ? 'Min 8 characters' : null, + ), + const SizedBox(height: 16), + TextFormField( + controller: confirmController, + obscureText: true, + decoration: InputDecoration( + labelText: l10n.confirmPasswordLabel, + prefixIcon: Icon(Icons.lock_outline), + ), + validator: (v) => v != passwordController.text + ? l10n.passwordsDoNotMatch + : null, + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(l10n.cancelButton), + ), + ElevatedButton( + onPressed: () { + if (formKey.currentState!.validate()) { + Navigator.pop(context, passwordController.text); + } + }, + child: Text(l10n.confirmButton), + ), + ], + ), + ); + } } diff --git a/lib/src/features/onboarding/presentation/screens/bodyweight_input_screen.dart b/lib/src/features/onboarding/presentation/screens/bodyweight_input_screen.dart index f5569d4..f60f71b 100644 --- a/lib/src/features/onboarding/presentation/screens/bodyweight_input_screen.dart +++ b/lib/src/features/onboarding/presentation/screens/bodyweight_input_screen.dart @@ -1,13 +1,39 @@ 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 '../../../../core/constants/app_constants.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'bodyweight_input_screen.g.dart'; // Provider to store onboarding data -final onboardingDataProvider = - StateProvider>((ref) => {}); +@Riverpod(keepAlive: true) +class OnboardingData extends _$OnboardingData { + @override + Map build() => {}; + + void update(Map Function(Map state) cb) { + final newData = cb(state); + state = {...state, ...newData}; + } + + void updateData(Map newValue) { + state = {...state, ...newValue}; + } + + void clear() { + state = {}; + } + + void removeKey(String key) { + final newState = Map.from(state); + newState.remove(key); + state = newState; + } +} class BodyweightInputScreen extends ConsumerStatefulWidget { const BodyweightInputScreen({super.key}); @@ -17,26 +43,26 @@ class BodyweightInputScreen extends ConsumerStatefulWidget { _BodyweightInputScreenState(); } -class _BodyweightInputScreenState - extends ConsumerState { +class _BodyweightInputScreenState extends ConsumerState { double _bodyweight = 80.0; bool _useKg = true; void _handleContinue() { // Store bodyweight - ref.read(onboardingDataProvider.notifier).update((state) => { - ...state, - 'bodyweight': _bodyweight, - }); + ref.read(onboardingDataProvider.notifier).updateData({ + 'bodyweight': _bodyweight, + }); context.go('/onboarding/strength-test'); } @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; return Scaffold( appBar: AppBar( - title: const Text('Setup Profile'), + title: Text(l10n.setupProfileTitle), + // title: const Text('Setup Profile'), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => context.go('/onboarding/welcome'), @@ -58,12 +84,14 @@ class _BodyweightInputScreenState // Title Text( - 'What\'s your current bodyweight?', + l10n.bodyweightTitle, + // 'What\'s your current bodyweight?', style: Theme.of(context).textTheme.displayMedium, ), const SizedBox(height: 16), Text( - 'We need this to calculate your weighted calisthenics exercises', + l10n.bodyweightSubtitle, + // 'We need this to calculate your weighted calisthenics exercises', style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 48), @@ -73,9 +101,9 @@ class _BodyweightInputScreenState mainAxisAlignment: MainAxisAlignment.center, children: [ SegmentedButton( - segments: const [ - ButtonSegment(value: true, label: Text('KG')), - ButtonSegment(value: false, label: Text('LBS')), + segments: [ + ButtonSegment(value: true, label: Text(l10n.unitKg)), + ButtonSegment(value: false, label: Text(l10n.unitLbs)), ], selected: {_useKg}, onSelectionChanged: (Set newSelection) { @@ -134,7 +162,7 @@ class _BodyweightInputScreenState // Continue Button ElevatedButton( onPressed: _handleContinue, - child: const Text('CONTINUE'), + child: Text(l10n.continueButton), ), ], ), @@ -143,4 +171,3 @@ class _BodyweightInputScreenState ); } } - diff --git a/lib/src/features/onboarding/presentation/screens/bodyweight_input_screen.g.dart b/lib/src/features/onboarding/presentation/screens/bodyweight_input_screen.g.dart new file mode 100644 index 0000000..f907159 --- /dev/null +++ b/lib/src/features/onboarding/presentation/screens/bodyweight_input_screen.g.dart @@ -0,0 +1,59 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'bodyweight_input_screen.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(OnboardingData) +final onboardingDataProvider = OnboardingDataProvider._(); + +final class OnboardingDataProvider + extends $NotifierProvider> { + OnboardingDataProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'onboardingDataProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$onboardingDataHash(); + + @$internal + @override + OnboardingData create() => OnboardingData(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(Map value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider>(value), + ); + } +} + +String _$onboardingDataHash() => r'639bee078cad2141ddbcb7e802af999a609dee01'; + +abstract class _$OnboardingData extends $Notifier> { + Map build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, Map>; + final element = ref.element as $ClassProviderElement< + AnyNotifier, Map>, + Map, + Object?, + Object?>; + element.handleCreate(ref, build); + } +} diff --git a/lib/src/features/onboarding/presentation/screens/inventory_setup_screen.dart b/lib/src/features/onboarding/presentation/screens/inventory_setup_screen.dart index 964dc1d..abb9ae9 100644 --- a/lib/src/features/onboarding/presentation/screens/inventory_setup_screen.dart +++ b/lib/src/features/onboarding/presentation/screens/inventory_setup_screen.dart @@ -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 '../../../../core/constants/app_constants.dart'; @@ -82,7 +83,9 @@ class _InventorySetupScreenState extends ConsumerState { void _handleNext() { final platesList = []; _plateInventory.forEach((weight, count) { - for (int i = 0; i < count; i++) platesList.add(weight); + for (int i = 0; i < count; i++) { + platesList.add(weight); + } }); final bandsList = >[]; @@ -102,10 +105,9 @@ class _InventorySetupScreenState extends ConsumerState { 'bands': bandsList, }; - ref.read(onboardingDataProvider.notifier).update((state) => { - ...state, - 'inventory_settings': inventorySettings, - }); + ref.read(onboardingDataProvider.notifier).updateData({ + 'inventory_settings': inventorySettings, + }); context.push('/onboarding/avatar'); } @@ -167,8 +169,7 @@ class _InventorySetupScreenState extends ConsumerState { } if (mounted) { - ref.read(onboardingDataProvider.notifier).state = {}; - + ref.read(onboardingDataProvider.notifier).clear(); context.go('/hub'); } } catch (e, stackTrace) { @@ -214,9 +215,10 @@ class _InventorySetupScreenState extends ConsumerState { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; return Scaffold( appBar: AppBar( - title: const Text('Equipment Setup'), + title: Text(l10n.setupEquipmentTitle), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => context.go('/onboarding/strength-test'), @@ -235,17 +237,17 @@ class _InventorySetupScreenState extends ConsumerState { ), const SizedBox(height: 32), Text( - 'Equipment Inventory', + l10n.setupInventoryTitle, style: Theme.of(context).textTheme.displayMedium, ), const SizedBox(height: 8), Text( - 'Tell us what equipment you have available', + l10n.setupInventorySubtitle, style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 32), Text( - 'Barbell Weight', + l10n.inventoryBarbellWeight, style: Theme.of(context) .textTheme .titleLarge @@ -272,40 +274,55 @@ class _InventorySetupScreenState extends ConsumerState { ), const SizedBox(height: 32), Text( - 'Quick Presets', + l10n.inventoryPresets, style: Theme.of(context) .textTheme .titleLarge ?.copyWith(color: AppTheme.textPrimary), ), const SizedBox(height: 12), - Row( - children: [ - Expanded( - child: OutlinedButton( - onPressed: () => _applyPreset('home'), - child: const Text('Home Gym'), - ), - ), - const SizedBox(width: 8), - Expanded( - child: OutlinedButton( - onPressed: () => _applyPreset('commercial'), - child: const Text('Commercial'), - ), - ), - const SizedBox(width: 8), - Expanded( - child: OutlinedButton( - onPressed: () => _applyPreset('minimal'), - child: const Text('Minimal'), - ), - ), - ], + LayoutBuilder( + builder: (context, constraints) { + final screenWidth = constraints.maxWidth; + final chipWidth = 130.0; + final spacing = 8.0; + final totalWidth = (chipWidth * 3) + (spacing * 2); + + if (screenWidth < totalWidth) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildPresetButton(l10n.inventoryPresetHome, 'home'), + const SizedBox(height: 8), + _buildPresetButton( + l10n.inventoryPresetCommercial, 'commercial'), + const SizedBox(height: 8), + _buildPresetButton( + l10n.inventoryPresetMinimal, 'minimal'), + ], + ); + } else { + return Row( + children: [ + Expanded( + child: _buildPresetButton( + l10n.inventoryPresetHome, 'home')), + const SizedBox(width: 8), + Expanded( + child: _buildPresetButton( + l10n.inventoryPresetCommercial, 'commercial')), + const SizedBox(width: 8), + Expanded( + child: _buildPresetButton( + l10n.inventoryPresetMinimal, 'minimal')), + ], + ); + } + }, ), const SizedBox(height: 32), Text( - 'Available Plates', + l10n.inventoryPlates, style: Theme.of(context) .textTheme .titleLarge @@ -325,7 +342,7 @@ class _InventorySetupScreenState extends ConsumerState { }).toList(), const SizedBox(height: 32), Text( - 'Resistance Bands (Assistance)', + l10n.inventoryBands, style: Theme.of(context) .textTheme .titleLarge @@ -333,41 +350,63 @@ class _InventorySetupScreenState extends ConsumerState { ), const SizedBox(height: 8), Text( - 'Select bands you have for pullup/dip assistance', + l10n.setupBandsSubtitle, style: Theme.of(context) .textTheme .bodySmall ?.copyWith(color: AppTheme.textSecondary), ), const SizedBox(height: 12), - Wrap( - spacing: 8, - runSpacing: 8, - children: _bandInventory.entries.map((entry) { - final resistance = AppConstants.defaultBands[entry.key] ?? 0; - return FilterChip( - label: Text('${entry.key} (~${resistance.toInt()}kg)'), - selected: entry.value, - onSelected: (bool selected) { - setState(() { - _bandInventory[entry.key] = selected; - }); + LayoutBuilder( + builder: (context, constraints) { + final screenWidth = constraints.maxWidth; + + int crossAxisCount = 2; + if (screenWidth > 600) { + crossAxisCount = 4; + } else if (screenWidth > 400) { + crossAxisCount = 2; + } + + final bandEntries = _bandInventory.entries.toList(); + + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + childAspectRatio: 2.5, + ), + itemCount: bandEntries.length, + itemBuilder: (context, index) { + final entry = bandEntries[index]; + final resistance = + AppConstants.defaultBands[entry.key] ?? 0; + + return _buildBandChip( + entry.key, + resistance.toInt(), + entry.value, + ); }, - selectedColor: _getBandColor(entry.key).withOpacity(0.3), - checkmarkColor: _getBandColor(entry.key), - labelStyle: TextStyle( - color: entry.value ? Colors.white : Colors.grey, - ), - side: BorderSide( - color: _getBandColor(entry.key), - ), ); - }).toList(), + }, ), const SizedBox(height: 32), ElevatedButton( - onPressed: _handleNext, - child: const Text('NEXT STEP'), + onPressed: _isLoading ? null : _handleNext, + child: _isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: Colors.black, + strokeWidth: 2, + ), + ) + : Text(l10n.nextStepButton), ), ], ), @@ -375,4 +414,61 @@ class _InventorySetupScreenState extends ConsumerState { ), ); } + + Widget _buildPresetButton(String label, String preset) { + return OutlinedButton( + onPressed: () => _applyPreset(preset), + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12), + ), + child: Text(label), + ); + } + + Widget _buildBandChip(String color, int resistance, bool isSelected) { + return InkWell( + onTap: () { + setState(() { + _bandInventory[color] = !isSelected; + }); + }, + borderRadius: BorderRadius.circular(8), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: isSelected + ? _getBandColor(color).withValues(alpha: 0.2) + : AppTheme.surfaceColor, + border: Border.all( + color: _getBandColor(color), + width: isSelected ? 2 : 1, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (isSelected) + Icon( + Icons.check_circle, + color: _getBandColor(color), + size: 18, + ), + if (isSelected) const SizedBox(width: 4), + Expanded( + child: Text( + '$color (~${resistance}kg)', + style: TextStyle( + color: isSelected ? Colors.white : AppTheme.textSecondary, + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + fontSize: 12, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ); + } } diff --git a/lib/src/features/onboarding/presentation/screens/strength_test_screen.dart b/lib/src/features/onboarding/presentation/screens/strength_test_screen.dart index cf1c47e..a13f486 100644 --- a/lib/src/features/onboarding/presentation/screens/strength_test_screen.dart +++ b/lib/src/features/onboarding/presentation/screens/strength_test_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.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 '../../../../shared/domain/logic/wendler_calculator.dart'; @@ -17,16 +18,24 @@ class StrengthTestScreen extends ConsumerStatefulWidget { class _StrengthTestScreenState extends ConsumerState { final _formKey = GlobalKey(); - final _squatWeightController = TextEditingController(text: '100'); + final _squatWeightController = TextEditingController(text: '60'); final _squatRepsController = TextEditingController(text: '5'); - final _pullupWeightController = TextEditingController(text: '0'); - final _pullupRepsController = TextEditingController(text: '8'); + + bool _canDoPullup = true; + final _pullWeightController = TextEditingController(text: '0'); + final _pullRepsController = TextEditingController(text: '5'); + + bool _canDoDip = true; final _dipWeightController = TextEditingController(text: '0'); - final _dipRepsController = TextEditingController(text: '10'); + final _benchWeightController = TextEditingController(text: '40'); + final _pushRepsController = TextEditingController(text: '5'); Map _calculated1RMs = {}; Map _calculatedTMs = {}; + bool _isAssistedPull = false; + bool _isAssistedDip = false; + @override void initState() { super.initState(); @@ -37,65 +46,148 @@ class _StrengthTestScreenState extends ConsumerState { void dispose() { _squatWeightController.dispose(); _squatRepsController.dispose(); - _pullupWeightController.dispose(); - _pullupRepsController.dispose(); + _pullWeightController.dispose(); + _pullRepsController.dispose(); _dipWeightController.dispose(); - _dipRepsController.dispose(); + _pushRepsController.dispose(); super.dispose(); } void _calculateAll() { final bodyweight = ref.read(onboardingDataProvider)['bodyweight'] ?? 80.0; + // Squat bleibt gleich... final squatWeight = double.tryParse(_squatWeightController.text) ?? 0; final squatReps = int.tryParse(_squatRepsController.text) ?? 1; final squat1RM = WendlerCalculator.calculate1RM(squatWeight, squatReps); final squatTM = WendlerCalculator.calculateTrainingMax(squat1RM); - final pullupAdditional = double.tryParse(_pullupWeightController.text) ?? 0; - final pullupReps = int.tryParse(_pullupRepsController.text) ?? 1; - final pullupTotal = bodyweight + pullupAdditional; - final pullup1RM = WendlerCalculator.calculate1RM(pullupTotal, pullupReps); - final pullupTM = WendlerCalculator.calculateTrainingMax(pullup1RM); + // PULL CALCULATION (Angepasst) + double pull1RM = 0.0; + if (_canDoPullup) { + final inputWeight = double.tryParse(_pullWeightController.text) ?? 0; + final reps = int.tryParse(_pullRepsController.text) ?? 1; - final dipAdditional = double.tryParse(_dipWeightController.text) ?? 0; - final dipReps = int.tryParse(_dipRepsController.text) ?? 1; - final dipTotal = bodyweight + dipAdditional; - final dip1RM = WendlerCalculator.calculate1RM(dipTotal, dipReps); - final dipTM = WendlerCalculator.calculateTrainingMax(dip1RM); + // LOGIK: Assisted vs Weighted + double totalLoad; + if (_isAssistedPull) { + totalLoad = (bodyweight - inputWeight).clamp(0.0, double.infinity); + } else { + totalLoad = bodyweight + inputWeight; + } + + pull1RM = WendlerCalculator.calculate1RM(totalLoad, reps); + } else { + final weight = double.tryParse(_pullWeightController.text) ?? 0; + final reps = int.tryParse(_pullRepsController.text) ?? 1; + pull1RM = WendlerCalculator.calculate1RM(weight, reps); + } + final pullTM = WendlerCalculator.calculateTrainingMax(pull1RM); + + // PUSH CALCULATION (Angepasst) + double push1RM = 0.0; + if (_canDoDip) { + final inputWeight = double.tryParse(_dipWeightController.text) ?? 0; + final reps = int.tryParse(_pushRepsController.text) ?? 1; + + // LOGIK: Assisted vs Weighted + double totalLoad; + if (_isAssistedDip) { + totalLoad = (bodyweight - inputWeight).clamp(0.0, double.infinity); + } else { + totalLoad = bodyweight + inputWeight; + } + + push1RM = WendlerCalculator.calculate1RM(totalLoad, reps); + } else { + final weight = double.tryParse(_benchWeightController.text) ?? 0; + final reps = int.tryParse(_pushRepsController.text) ?? 1; + push1RM = WendlerCalculator.calculate1RM(weight, reps); + } + final pushTM = WendlerCalculator.calculateTrainingMax(push1RM); setState(() { _calculated1RMs = { 'squat': squat1RM, - 'pullup': pullup1RM, - 'dip': dip1RM, + 'pullup': pull1RM, + 'dip': push1RM, }; _calculatedTMs = { 'squat': squatTM, - 'pullup': pullupTM, - 'dip': dipTM, + 'pullup': pullTM, + 'dip': pushTM, }; }); } + // void _calculateAll() { + // final bodyweight = ref.read(onboardingDataProvider)['bodyweight'] ?? 80.0; + + // final squatWeight = double.tryParse(_squatWeightController.text) ?? 0; + // final squatReps = int.tryParse(_squatRepsController.text) ?? 1; + // final squat1RM = WendlerCalculator.calculate1RM(squatWeight, squatReps); + // final squatTM = WendlerCalculator.calculateTrainingMax(squat1RM); + + // double pull1RM = 0.0; + // if (_canDoPullup) { + // final added = double.tryParse(_pullWeightController.text) ?? 0; + // final reps = int.tryParse(_pullRepsController.text) ?? 1; + // pull1RM = WendlerCalculator.calculate1RM(bodyweight + added, reps); + // } else { + // final weight = double.tryParse(_pullWeightController.text) ?? 0; + // final reps = int.tryParse(_pullRepsController.text) ?? 1; + // pull1RM = WendlerCalculator.calculate1RM(weight, reps); + // } + // final pullTM = WendlerCalculator.calculateTrainingMax(pull1RM); + + // double push1RM = 0.0; + // if (_canDoDip) { + // final added = double.tryParse(_dipWeightController.text) ?? 0; + // final reps = int.tryParse(_pushRepsController.text) ?? 1; + // push1RM = WendlerCalculator.calculate1RM(bodyweight + added, reps); + // } else { + // final weight = double.tryParse(_benchWeightController.text) ?? 0; + // final reps = int.tryParse(_pushRepsController.text) ?? 1; + // push1RM = WendlerCalculator.calculate1RM(weight, reps); + // } + // final pushTM = WendlerCalculator.calculateTrainingMax(push1RM); + + // setState(() { + // _calculated1RMs = { + // 'squat': squat1RM, + // 'pullup': pull1RM, + // 'dip': push1RM, + // }; + // _calculatedTMs = { + // 'squat': squatTM, + // 'pullup': pullTM, + // 'dip': pushTM, + // }; + // }); + // } + void _handleContinue() { if (!_formKey.currentState!.validate()) return; - ref.read(onboardingDataProvider.notifier).update((state) => { - ...state, - 'training_maxes': _calculatedTMs, - }); + final variants = { + 'pull': _canDoPullup ? 'pullup' : 'row', + 'push': _canDoDip ? 'dip' : 'bench', + }; + + ref.read(onboardingDataProvider.notifier).updateData({ + 'training_maxes': _calculatedTMs, + 'exercise_variants': variants, + }); context.go('/onboarding/inventory'); } @override Widget build(BuildContext context) { - final bodyweight = ref.watch(onboardingDataProvider)['bodyweight'] ?? 80.0; - + final l10n = AppLocalizations.of(context)!; return Scaffold( appBar: AppBar( - title: const Text('Strength Test'), + title: Text(l10n.strengthTestTitle), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => context.go('/onboarding/bodyweight'), @@ -116,74 +208,118 @@ class _StrengthTestScreenState extends ConsumerState { ), const SizedBox(height: 32), Text( - 'Combat Calibration', + l10n.strengthTestSubtitle, style: Theme.of(context).textTheme.displayMedium, ), const SizedBox(height: 8), Text( - 'We need to assess your current power level to assign the correct monsters.', // Flavor - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 8), - Text( - 'Enter your recent best performance for each exercise', + l10n.strengthTestBody, style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 32), _ExerciseCard( + title: l10n.strengthLegs, exerciseName: 'Back Squat', icon: Icons.accessibility_new, weightController: _squatWeightController, repsController: _squatRepsController, isBodyweight: false, - calculated1RM: _calculated1RMs['squat'] ?? 0, - calculatedTM: _calculatedTMs['squat'] ?? 0, onChanged: _calculateAll, + result1RM: _calculated1RMs['squat'] ?? 0, + resultTM: _calculatedTMs['squat'] ?? 0, ), const SizedBox(height: 16), - _ExerciseCard( - exerciseName: 'Weighted Pull-up', + _AdaptiveExerciseCard( + slotTitle: l10n.strengthPull, + primaryName: 'Weighted Pull-up', + secondaryName: 'Pendlay Row', icon: Icons.north, - weightController: _pullupWeightController, - repsController: _pullupRepsController, - isBodyweight: true, - bodyweight: bodyweight, - calculated1RM: _calculated1RMs['pullup'] ?? 0, - calculatedTM: _calculatedTMs['pullup'] ?? 0, + isCapable: _canDoPullup, + onToggleCapability: (val) { + setState(() { + _canDoPullup = val; + _pullWeightController.text = '0'; + _pullRepsController.text = '5'; + _calculateAll(); + }); + }, + isAssisted: _isAssistedPull, + onToggleAssisted: (val) { + setState(() { + _isAssistedPull = val; + _calculateAll(); + }); + }, + weightController: _pullWeightController, + 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)', + showResults: true, + result1RM: _calculated1RMs['pullup'] ?? 0, + resultTM: _calculatedTMs['pullup'] ?? 0, onChanged: _calculateAll, ), const SizedBox(height: 16), - _ExerciseCard( - exerciseName: 'Weighted Dip', + _AdaptiveExerciseCard( + slotTitle: l10n.strengthPush, + primaryName: 'Weighted Dip', + secondaryName: 'Bench Press', icon: Icons.south, - weightController: _dipWeightController, - repsController: _dipRepsController, - isBodyweight: true, - bodyweight: bodyweight, - calculated1RM: _calculated1RMs['dip'] ?? 0, - calculatedTM: _calculatedTMs['dip'] ?? 0, + isCapable: _canDoDip, + onToggleCapability: (val) { + setState(() { + _canDoDip = val; + _dipWeightController.text = '0'; + _pushRepsController.text = '5'; + _calculateAll(); + }); + }, + isAssisted: _isAssistedDip, + onToggleAssisted: (val) { + setState(() { + _isAssistedDip = val; + _calculateAll(); + }); + }, + weightController: + _canDoDip ? _dipWeightController : _benchWeightController, + repsController: _pushRepsController, + weightLabel: _canDoDip + ? (_isAssistedDip + ? 'Band Assistance (kg)' + : 'Added Weight (kg)') + : 'Weight (kg)', + // weightLabel: _canDoDip ? 'Add. Weight (kg)' : 'Weight (kg)', + repsLabel: 'Reps', + showWeightInput: true, + showResults: true, + result1RM: _calculated1RMs['dip'] ?? 0, + resultTM: _calculatedTMs['dip'] ?? 0, onChanged: _calculateAll, ), const SizedBox(height: 32), Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: AppTheme.primaryColor.withOpacity(0.1), + color: AppTheme.primaryColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), border: Border.all( - color: AppTheme.primaryColor.withOpacity(0.3), - ), + color: AppTheme.primaryColor.withValues(alpha: 0.3)), ), child: Row( children: [ - const Icon( - Icons.info_outline, - color: AppTheme.primaryColor, - ), + const Icon(Icons.info_outline, + color: AppTheme.primaryColor), const SizedBox(width: 12), Expanded( child: Text( - 'Your "Training Max" (TM) is your base combat power. We calculate it as 90% of your max potential to ensure long-term survival.', // Flavor + l10n.tmExplanation, style: Theme.of(context) .textTheme .bodySmall @@ -196,7 +332,7 @@ class _StrengthTestScreenState extends ConsumerState { const SizedBox(height: 32), ElevatedButton( onPressed: _handleContinue, - child: const Text('CONTINUE'), + child: Text(l10n.continueButton), ), ], ), @@ -208,57 +344,61 @@ class _StrengthTestScreenState extends ConsumerState { } class _ExerciseCard extends StatelessWidget { + final String title; final String exerciseName; final IconData icon; final TextEditingController weightController; final TextEditingController repsController; final bool isBodyweight; - final double bodyweight; - final double calculated1RM; - final double calculatedTM; + final double result1RM; + final double resultTM; final VoidCallback onChanged; const _ExerciseCard({ + required this.title, required this.exerciseName, required this.icon, required this.weightController, required this.repsController, - this.isBodyweight = false, - this.bodyweight = 0, - required this.calculated1RM, - required this.calculatedTM, + required this.isBodyweight, + required this.result1RM, + required this.resultTM, required this.onChanged, }); @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Header + Text(title.toUpperCase(), + style: const TextStyle( + color: AppTheme.textSecondary, + fontSize: 12, + fontWeight: FontWeight.bold)), + const SizedBox(height: 8), Row( children: [ Container( - width: 40, - height: 40, + padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: AppTheme.primaryColor.withOpacity(0.2), - borderRadius: BorderRadius.circular(8), - ), + color: AppTheme.primaryColor.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(8)), child: Icon(icon, color: AppTheme.primaryColor), ), const SizedBox(width: 12), - Text( - exerciseName, - style: Theme.of(context).textTheme.titleLarge, - ), + Text(exerciseName, + style: Theme.of(context) + .textTheme + .titleLarge + ?.copyWith(color: AppTheme.textPrimary)), ], ), const SizedBox(height: 16), - Row( children: [ Expanded( @@ -268,21 +408,15 @@ class _ExerciseCard extends StatelessWidget { keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.allow( - RegExp(r'^\d+\.?\d{0,2}')), + RegExp(r'^\d+\.?\d{0,2}')) ], decoration: InputDecoration( - labelText: isBodyweight - ? 'Additional Weight (kg)' - : 'Weight (kg)', - isDense: true, - ), + labelText: isBodyweight + ? l10n.addWeightLabel + : l10n.weightLabel, + isDense: true), onChanged: (_) => onChanged(), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Required'; - } - return null; - }, + validator: (v) => v!.isEmpty ? 'Required' : null, ), ), const SizedBox(width: 12), @@ -290,56 +424,17 @@ class _ExerciseCard extends StatelessWidget { child: TextFormField( controller: repsController, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], - decoration: const InputDecoration( - labelText: 'Reps', - isDense: true, - ), + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + decoration: InputDecoration( + labelText: l10n.repsLabel, isDense: true), onChanged: (_) => onChanged(), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Required'; - } - final reps = int.tryParse(value); - if (reps == null || reps < 1 || reps > 20) { - return '1-20'; - } - return null; - }, + validator: (v) => v!.isEmpty ? 'Required' : null, ), ), ], ), const SizedBox(height: 16), - - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: AppTheme.surfaceColor, - borderRadius: BorderRadius.circular(8), - ), - child: Column( - children: [ - if (isBodyweight) - _ResultRow( - label: 'Total Weight', - value: - '${(bodyweight + (double.tryParse(weightController.text) ?? 0)).toStringAsFixed(1)} kg', - ), - _ResultRow( - label: 'Estimated 1RM', - value: '${calculated1RM.toStringAsFixed(1)} kg', - ), - _ResultRow( - label: 'Training Max (90%)', - value: '${calculatedTM.toStringAsFixed(1)} kg', - highlight: true, - ), - ], - ), - ), + _ResultBox(rm: result1RM, tm: resultTM), ], ), ), @@ -347,36 +442,204 @@ class _ExerciseCard extends StatelessWidget { } } -class _ResultRow extends StatelessWidget { - final String label; - final String value; - final bool highlight; +class _AdaptiveExerciseCard extends StatelessWidget { + final String slotTitle; + final String primaryName; + final String secondaryName; + final IconData icon; + final bool isCapable; + final ValueChanged onToggleCapability; + final TextEditingController weightController; + final TextEditingController repsController; + final String weightLabel; + final String repsLabel; + final bool showWeightInput; + final bool showResults; + final double result1RM; + final double resultTM; + final VoidCallback onChanged; + final bool isAssisted; + final ValueChanged? onToggleAssisted; - const _ResultRow({ - required this.label, - required this.value, - this.highlight = false, + const _AdaptiveExerciseCard({ + required this.slotTitle, + required this.primaryName, + required this.secondaryName, + required this.icon, + required this.isCapable, + required this.onToggleCapability, + required this.weightController, + required this.repsController, + required this.weightLabel, + required this.repsLabel, + this.showWeightInput = true, + this.showResults = true, + required this.result1RM, + required this.resultTM, + required this.onChanged, + this.isAssisted = false, + this.onToggleAssisted, }); @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + final l10n = AppLocalizations.of(context)!; + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Text(slotTitle.toUpperCase(), + style: const TextStyle( + color: AppTheme.textSecondary, + fontSize: 12, + fontWeight: FontWeight.bold)), + ]), + Row( + children: [ + Row( + children: [ + Text(l10n.canDoOneRep, + style: TextStyle( + fontSize: 12, + color: isCapable + ? AppTheme.successColor + : Colors.grey)), + Switch( + value: isCapable, + activeThumbColor: AppTheme.successColor, + onChanged: onToggleCapability, + ), + ], + ), + if (isCapable && onToggleAssisted != null) + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text(l10n.isAssisted, + style: TextStyle( + fontSize: 12, + color: isAssisted + ? AppTheme.primaryColor + : Colors.grey)), + Switch( + value: isAssisted, + activeThumbColor: AppTheme.primaryColor, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + onChanged: onToggleAssisted, + ), + ], + ), + ], + ), + const SizedBox(height: 4), + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: AppTheme.primaryColor.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(8)), + child: Icon(icon, color: AppTheme.primaryColor), + ), + const SizedBox(width: 12), + Text(isCapable ? primaryName : secondaryName, + style: Theme.of(context) + .textTheme + .titleLarge + ?.copyWith(color: AppTheme.textPrimary)), + ], + ), + if (!isCapable) ...[ + const SizedBox(height: 8), + Text( + 'Adjusted: ${"Wendler 5/3/1"}', + style: const TextStyle( + color: AppTheme.secondaryColor, + fontSize: 12, + fontStyle: FontStyle.italic), + ), + ], + const SizedBox(height: 16), + Row( + children: [ + if (showWeightInput) + Expanded( + flex: 2, + child: TextFormField( + controller: weightController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp(r'^\d+\.?\d{0,2}')) + ], + decoration: InputDecoration( + labelText: weightLabel, isDense: true), + onChanged: (_) => onChanged(), + validator: (v) => v!.isEmpty ? 'Required' : null, + ), + ) + else + const Spacer(flex: 2), + if (showWeightInput) const SizedBox(width: 12), + Expanded( + child: TextFormField( + controller: repsController, + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + decoration: + InputDecoration(labelText: repsLabel, isDense: true), + onChanged: (_) => onChanged(), + validator: (v) => v!.isEmpty ? 'Required' : null, + ), + ), + ], + ), + if (showResults) ...[ + const SizedBox(height: 16), + _ResultBox(rm: result1RM, tm: resultTM), + ], + ], + ), + ), + ); + } +} + +class _ResultBox extends StatelessWidget { + final double rm; + final double tm; + const _ResultBox({required this.rm, required this.tm}); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppTheme.surfaceColor, borderRadius: BorderRadius.circular(8)), + child: Column( children: [ - Text( - label, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - fontWeight: highlight ? FontWeight.bold : null, - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(l10n.est1rm), + Text('${rm.toStringAsFixed(1)} kg', + style: Theme.of(context).textTheme.bodyLarge), + ], ), - Text( - value, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: highlight ? AppTheme.primaryColor : null, - fontWeight: highlight ? FontWeight.bold : null, - ), + const SizedBox(height: 4), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(l10n.trainingMaxLabel), + Text('${tm.toStringAsFixed(1)} kg', + style: const TextStyle( + color: AppTheme.primaryColor, + fontWeight: FontWeight.bold)), + ], ), ], ), diff --git a/lib/src/features/onboarding/presentation/screens/welcome_screen.dart b/lib/src/features/onboarding/presentation/screens/welcome_screen.dart index e248da8..a6372a4 100644 --- a/lib/src/features/onboarding/presentation/screens/welcome_screen.dart +++ b/lib/src/features/onboarding/presentation/screens/welcome_screen.dart @@ -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: [ @@ -20,7 +23,7 @@ class WelcomeScreen extends StatelessWidget { ), Positioned.fill( child: Container( - color: Colors.black.withOpacity(0.7), + color: Colors.black.withValues(alpha: 0.7), ), ), SafeArea( @@ -35,11 +38,11 @@ class WelcomeScreen extends StatelessWidget { width: 100, height: 100, decoration: BoxDecoration( - color: AppTheme.primaryColor.withOpacity(0.9), + color: AppTheme.primaryColor.withValues(alpha: 0.9), shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: AppTheme.primaryColor.withOpacity(0.5), + color: AppTheme.primaryColor.withValues(alpha: 0.5), blurRadius: 20) ], ), @@ -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)), ), ], @@ -139,7 +140,7 @@ class _FeatureItem extends StatelessWidget { width: 48, height: 48, decoration: BoxDecoration( - color: AppTheme.primaryColor.withOpacity(0.2), + color: AppTheme.primaryColor.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(12), ), child: Icon( diff --git a/lib/src/features/stats/presentation/screens/stats_screen.dart b/lib/src/features/stats/presentation/screens/stats_screen.dart index 9fa6924..3e85acc 100644 --- a/lib/src/features/stats/presentation/screens/stats_screen.dart +++ b/lib/src/features/stats/presentation/screens/stats_screen.dart @@ -1,12 +1,12 @@ -import 'dart:convert'; 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/shared/data/local/tables.dart'; import '../../../../core/theme/app_theme.dart'; +import '../../../../shared/data/local/app_database.dart'; import '../../../../shared/data/repositories/cycle_repository.dart'; -import '../../../../shared/data/local/collections/cycle_collection.dart'; -import '../../../../shared/data/remote/api_client.dart'; // Zugriff auf API import '../../../../shared/data/repositories/user_repository.dart'; import '../../../../shared/data/repositories/workout_repository.dart'; import '../../../../shared/domain/entities/exercise.dart'; @@ -24,7 +24,7 @@ class StatsScreen extends ConsumerStatefulWidget { class _StatsScreenState extends ConsumerState { bool _isLoading = false; - String _selectedExercise = 'squat'; // squat, pullup, dip + String _selectedExercise = 'squat'; String _selectedRange = '3m'; // 1m, 3m, 1y, all List _chartData = []; bool _isChartLoading = true; @@ -55,19 +55,15 @@ class _StatsScreenState extends ConsumerState { for (var workout in allWorkouts) { if (workout.completedAt == null) continue; - List exercisesJson = []; - try { - exercisesJson = jsonDecode(workout.exercisesJson); - } catch (e) { - continue; - } + final exercisesList = workout.exercises; double max1RM = 0.0; double sessionVolume = 0.0; bool foundExercise = false; double trainingMax = 0.0; - for (var exJson in exercisesJson) { + for (var exDynamic in exercisesList) { + final exJson = exDynamic as Map; final exercise = Exercise.fromJson(exJson); if (exercise.exerciseId == _selectedExercise) { @@ -147,12 +143,10 @@ class _StatsScreenState extends ConsumerState { try { final cycleRepo = ref.read(cycleRepositoryProvider); - final oldTMs = - jsonDecode(currentCycle.trainingMaxesJson) as Map; + final oldTMs = currentCycle.trainingMaxes; final newCycle = await cycleRepo.finishCycle(); - final newTMs = - jsonDecode(newCycle.trainingMaxesJson) as Map; + final newTMs = newCycle.trainingMaxes; if (mounted) { await showDialog( @@ -183,23 +177,49 @@ class _StatsScreenState extends ConsumerState { @override Widget build(BuildContext context) { final cycleRepo = ref.watch(cycleRepositoryProvider); + final userRepo = ref.watch(userRepositoryProvider); + final l10n = AppLocalizations.of(context)!; return Scaffold( appBar: AppBar( - title: const Text('Statistics & Cycles'), + title: Text(l10n.statsTitle), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => context.go('/hub'), ), ), - body: FutureBuilder( - future: cycleRepo.getCurrentCycle(), + body: FutureBuilder>( + future: Future.wait([ + cycleRepo.getCurrentCycle(), + userRepo.getLocalUser(), + ]), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } - final currentCycle = snapshot.data; + final currentCycle = snapshot.data?[0] as CycleCollection?; + final user = snapshot.data?[1] as UserCollection; + final variants = user.exerciseVariants ?? {}; + final pullVariant = variants['pull'] ?? 'pullup'; + final pushVariant = variants['push'] ?? 'dip'; + + String getLabel(String id) { + switch (id) { + case 'squat': + return 'Squat'; + case 'pullup': + return 'Pull-up'; + case 'row': + return 'Row'; + case 'dip': + return 'Dip'; + case 'bench': + return 'Bench'; + default: + return id; + } + } return SingleChildScrollView( padding: const EdgeInsets.all(16), @@ -209,55 +229,53 @@ class _StatsScreenState extends ConsumerState { if (currentCycle != null) ...[ _CurrentCycleCard( cycle: currentCycle, + user: user, onFinish: _isLoading ? null : () => _handleFinishCycle(currentCycle), ), const SizedBox(height: 24), ], - Text( - 'Progress Analysis', + l10n.statsProgressAnalysis, style: Theme.of(context) .textTheme .titleLarge ?.copyWith(color: AppTheme.textPrimary), ), const SizedBox(height: 16), - SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _FilterChip( - label: 'Squat', + label: l10n.exerciseSquat, isSelected: _selectedExercise == 'squat', onTap: () => _onFilterChanged('squat', _selectedRange), ), const SizedBox(width: 8), _FilterChip( - label: 'Pull-up', - isSelected: _selectedExercise == 'pullup', - onTap: () => _onFilterChanged('pullup', _selectedRange), + label: getLabel(pullVariant), + isSelected: _selectedExercise == pullVariant, + onTap: () => + _onFilterChanged(pullVariant, _selectedRange), ), const SizedBox(width: 8), _FilterChip( - label: 'Dip', - isSelected: _selectedExercise == 'dip', - onTap: () => _onFilterChanged('dip', _selectedRange), + label: getLabel(pushVariant), + isSelected: _selectedExercise == pushVariant, + onTap: () => + _onFilterChanged(pushVariant, _selectedRange), ), ], ), ), const SizedBox(height: 16), - _isChartLoading ? const SizedBox( height: 250, child: Center(child: CircularProgressIndicator())) : ProgressChart(data: _chartData), - - // (Optional: Range Selector unten drunter '1M', '3M', '1Y'...) ], ), ); @@ -269,13 +287,36 @@ class _StatsScreenState extends ConsumerState { class _CurrentCycleCard extends StatelessWidget { final CycleCollection cycle; + final UserCollection user; final VoidCallback? onFinish; - const _CurrentCycleCard({required this.cycle, required this.onFinish}); + const _CurrentCycleCard( + {required this.cycle, required this.user, required this.onFinish}); @override Widget build(BuildContext context) { - final tms = jsonDecode(cycle.trainingMaxesJson) as Map; + final tms = cycle.trainingMaxes; + final variants = user.exerciseVariants ?? {}; + final pullVariant = variants['pull'] ?? 'pullup'; + final pushVariant = variants['push'] ?? 'dip'; + final l10n = AppLocalizations.of(context)!; + + String getLabel(String id) { + switch (id) { + case 'squat': + return l10n.exerciseSquat; + case 'pullup': + return l10n.exercisePullup; + case 'row': + return l10n.exerciseRow; + case 'dip': + return l10n.exerciseDip; + case 'bench': + return l10n.exerciseBench; + default: + return id; + } + } return Card( child: Padding( @@ -287,7 +328,7 @@ class _CurrentCycleCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'CYCLE ${cycle.cycleNumber}', + l10n.statsCycleTitle(cycle.cycleNumber), style: Theme.of(context).textTheme.headlineMedium?.copyWith( color: AppTheme.primaryColor, fontSize: 24, @@ -297,11 +338,11 @@ class _CurrentCycleCard extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: AppTheme.successColor.withOpacity(0.2), + color: AppTheme.successColor.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(4), ), - child: const Text( - 'ACTIVE', + child: Text( + l10n.hubActiveYes, style: TextStyle( color: AppTheme.successColor, fontWeight: FontWeight.bold, @@ -311,19 +352,25 @@ class _CurrentCycleCard extends StatelessWidget { ], ), const Divider(height: 32), - Text('Current Training Maxes (TM)', + Text(l10n.statsCurrentTM, style: Theme.of(context).textTheme.labelLarge), const SizedBox(height: 16), - _StatRow(label: 'Squat', value: '${tms['squat']} kg'), - _StatRow(label: 'Pull-up', value: '${tms['pullup']} kg'), - _StatRow(label: 'Dip', value: '${tms['dip']} kg'), + _StatRow( + label: l10n.exerciseSquat, + value: '${tms['squat'].toStringAsFixed(2)} kg'), + _StatRow( + label: getLabel(pullVariant), + value: '${tms['pullup'].toStringAsFixed(2)} kg'), + _StatRow( + label: getLabel(pushVariant), + value: '${tms['dip'].toStringAsFixed(2)} kg'), const SizedBox(height: 32), SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: onFinish, icon: const Icon(Icons.upgrade), - label: const Text('FINISH CYCLE & LEVEL UP'), + label: Text(l10n.statsFinishCycle), style: ElevatedButton.styleFrom( backgroundColor: AppTheme.secondaryColor, foregroundColor: Colors.white, @@ -373,33 +420,40 @@ class _CycleFinishDialog extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; return AlertDialog( - title: const Text('Dungeon Cleared!'), + title: Text(l10n.statsCycleFinishedTitle), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'You have defeated the guardians of this cycle. But deeper in the dungeon, stronger foes await...'), // Story + Text( + l10n.statsCycleFinishedBody, + ), const SizedBox(height: 16), const Divider(), const SizedBox(height: 8), - const Text('Your Training Maxes have increased:', + Text(l10n.statsTMIncreased, style: TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 16), _DiffRow( - name: 'Squat', oldVal: oldTMs['squat'], newVal: newTMs['squat']), + name: l10n.exerciseSquat, + oldVal: (oldTMs['squat'] as num).toDouble(), + newVal: (newTMs['squat'] as num).toDouble()), _DiffRow( - name: 'Pull-up', - oldVal: oldTMs['pullup'], - newVal: newTMs['pullup']), - _DiffRow(name: 'Dip', oldVal: oldTMs['dip'], newVal: newTMs['dip']), + name: l10n.exercisePullup, + oldVal: (oldTMs['pullup'] as num).toDouble(), + newVal: (newTMs['pullup'] as num).toDouble()), + _DiffRow( + name: l10n.exerciseDip, + oldVal: (oldTMs['dip'] as num).toDouble(), + newVal: (newTMs['dip'] as num).toDouble()), ], ), actions: [ ElevatedButton( onPressed: () => Navigator.of(context).pop(), - child: const Text('ENTER NEXT LEVEL'), + child: Text(l10n.statsEnterNextLevel), ), ], ); @@ -424,15 +478,15 @@ class _DiffRow extends StatelessWidget { child: Row( children: [ Expanded(child: Text(name)), - Text('${oldVal.toStringAsFixed(1)} → ', + Text('${oldVal.toStringAsFixed(2)} → ', style: const TextStyle(color: Colors.grey)), Text( - newVal.toStringAsFixed(1), + newVal.toStringAsFixed(2), style: const TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(width: 8), if (isPositive) - Text('+${diff.toStringAsFixed(1)}', + Text('+${diff.toStringAsFixed(2)}', style: const TextStyle( color: AppTheme.successColor, fontWeight: FontWeight.bold)) else @@ -458,14 +512,15 @@ class _FilterChip extends StatelessWidget { label: Text(label), selected: isSelected, onSelected: (_) => onTap(), - selectedColor: AppTheme.primaryColor.withOpacity(0.2), + selectedColor: AppTheme.primaryColor.withValues(alpha: 0.2), labelStyle: TextStyle( color: isSelected ? AppTheme.primaryColor : Colors.grey, fontWeight: FontWeight.bold, ), side: BorderSide( - color: - isSelected ? AppTheme.primaryColor : Colors.grey.withOpacity(0.3), + color: isSelected + ? AppTheme.primaryColor + : Colors.grey.withValues(alpha: 0.3), ), ); } diff --git a/lib/src/features/stats/presentation/widgets/progress_chart.dart b/lib/src/features/stats/presentation/widgets/progress_chart.dart index 223124d..4f1d300 100644 --- a/lib/src/features/stats/presentation/widgets/progress_chart.dart +++ b/lib/src/features/stats/presentation/widgets/progress_chart.dart @@ -51,7 +51,7 @@ class ProgressChart extends StatelessWidget { decoration: BoxDecoration( color: AppTheme.surfaceColor, borderRadius: BorderRadius.circular(16), - border: Border.all(color: AppTheme.primaryColor.withOpacity(0.1)), + border: Border.all(color: AppTheme.primaryColor.withValues(alpha: 0.1)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -148,14 +148,12 @@ class ProgressChart extends StatelessWidget { ), belowBarData: BarAreaData( show: true, - color: AppTheme.primaryColor.withOpacity(0.1), + color: AppTheme.primaryColor.withValues(alpha: 0.1), ), ), ], lineTouchData: LineTouchData( touchTooltipData: LineTouchTooltipData( - // FIX 2: Alte API nutzen (tooltipBgColor statt getTooltipColor) - // tooltipBgColor: AppTheme.surfaceColor, getTooltipColor: (touchedSpot) => AppTheme.surfaceColor, getTooltipItems: (touchedSpots) { return touchedSpots.map((spot) { diff --git a/lib/src/features/wiki/presentation/widgets/exercise_guide_sheet.dart b/lib/src/features/wiki/presentation/widgets/exercise_guide_sheet.dart new file mode 100644 index 0000000..52afa14 --- /dev/null +++ b/lib/src/features/wiki/presentation/widgets/exercise_guide_sheet.dart @@ -0,0 +1,212 @@ +import 'package:flutter/material.dart'; +import 'package:slrpg_app/l10n/app_localizations.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'; + + final l10n = AppLocalizations.of(context)!; + final library = ExerciseGuide.getLibrary(l10n); + final guide = library[exerciseId]; + + 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))), + ], + ), + ); + } +} diff --git a/lib/src/features/workout_runner/application/workout_generator_service.dart b/lib/src/features/workout_runner/application/workout_generator_service.dart new file mode 100644 index 0000000..c2ae329 --- /dev/null +++ b/lib/src/features/workout_runner/application/workout_generator_service.dart @@ -0,0 +1,312 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../shared/data/local/app_database.dart'; +import '../../../shared/domain/entities/exercise.dart'; +import '../../../shared/domain/entities/workout_set.dart'; +import '../../../shared/domain/logic/wendler_calculator.dart'; + +final workoutGeneratorServiceProvider = + Provider((ref) { + return WorkoutGeneratorService(); +}); + +class WorkoutGeneratorService { + List generateWorkout({ + required int week, + required int day, + required Map trainingMaxes, + required UserCollection user, + required AccessoryTemplate template, + int? conditioningSets, + }) { + final exercises = []; + + exercises.addAll(_generateMainLifts(week, day, trainingMaxes, user)); + + if (template == AccessoryTemplate.hypertrophy) { + exercises + .addAll(_generateHypertrophyAccessories(day, trainingMaxes, user)); + } else if (template == AccessoryTemplate.conditioning) { + final sets = (conditioningSets != null && conditioningSets > 0) + ? conditioningSets + : 15; + exercises.addAll(_generateConditioning(day, sets)); + } else if (template == AccessoryTemplate.journey_pullup) { + exercises.addAll(_generatePullUpJourney(day, trainingMaxes)); + } + + return exercises; + } + + List _generateMainLifts(int week, int day, + Map trainingMaxes, UserCollection user) { + final exercises = []; + final variants = user.exerciseVariants ?? {}; + + (String, String, ExerciseType) resolveVariant(String slot, String defaultId, + String defaultName, ExerciseType defaultType) { + final variant = variants[slot]; + if (slot == 'pull') { + if (variant == 'row') return ('row', 'Pendlay Row', ExerciseType.row); + return ('pullup', 'Weighted Pull-up', ExerciseType.pullup); + } + if (slot == 'push') { + if (variant == 'bench') { + return ('bench', 'Bench Press', ExerciseType.bench); + } + return ('dip', 'Weighted Dip', ExerciseType.dip); + } + return (defaultId, defaultName, defaultType); + } + + void addExercise(String slot, String defaultId, String defaultName, + ExerciseType defaultType, bool isMain) { + final (id, name, type) = + resolveVariant(slot, defaultId, defaultName, defaultType); + + final tm = trainingMaxes[defaultId] ?? 0.0; + List sets; + + if (isMain) { + sets = WendlerCalculator.generateSets( + week: week, + trainingMax: tm, + exerciseType: type, + currentBodyweight: user.currentBodyweight, + ); + } else { + if (week == 4) return; + + sets = WendlerCalculator.generateFSLSets( + trainingMax: tm, + exerciseType: type, + currentBodyweight: user.currentBodyweight, + ); + } + + if (sets.isNotEmpty) { + exercises.add(Exercise( + exerciseId: id, + exerciseName: isMain ? name : '$name (FSL)', + bodyweightAtSession: user.currentBodyweight, + sets: sets, + )); + } + } + + if (day == 1) { + addExercise('legs', 'squat', 'Back Squat', ExerciseType.squat, true); + addExercise( + 'pull', 'pullup', 'Weighted Pull-up', ExerciseType.pullup, false); + } else if (day == 2) { + addExercise('push', 'dip', 'Weighted Dip', ExerciseType.dip, true); + addExercise('legs', 'squat', 'Back Squat', ExerciseType.squat, false); + } else if (day == 3) { + addExercise( + 'pull', 'pullup', 'Weighted Pull-up', ExerciseType.pullup, true); + addExercise('push', 'dip', 'Weighted Dip', ExerciseType.dip, false); + } + + return exercises; + } + + List _generateHypertrophyAccessories( + int day, Map trainingMaxes, UserCollection user) { + final accessories = []; + + double calculateWeight(double referenceTm, double percentage) { + final raw = referenceTm * percentage; + return (raw / 2.5).round() * 2.5; + } + + Exercise createSimple(String id, String name, int sets, int reps, + {double weight = 0.0}) { + return Exercise( + exerciseId: id, + exerciseName: name, + bodyweightAtSession: 0, + sets: List.generate( + sets, + (i) => WorkoutSet( + setNumber: i + 1, + repsTarget: reps, + targetWeightTotal: weight, + repsActual: 0, + isAmrap: false, + completed: false, + )), + ); + } + + final squatTm = trainingMaxes['squat'] ?? 0.0; + final dipTm = trainingMaxes['dip'] ?? 0.0; + final pullupTm = trainingMaxes['pullup'] ?? 0.0; + + switch (day) { + case 1: // Squat Tag + // RDL: ~40% vom Squat TM + accessories.add(createSimple('rdl', 'Romanian Deadlift', 3, 10, + weight: calculateWeight(squatTm, 0.4))); + + accessories.add(_createIntervalExercise( + id: 'kb_snatch_acc', + name: 'KB Snatch', + sets: 10, + intervalSeconds: 60, + repsPerSet: 10)); + break; + + case 2: // Dip Tag (Push) + // OHP: ~30% vom System-Dip-TM (konservativ für 3x10) + accessories.add(createSimple('ohp', 'Overhead Press', 3, 10, + weight: calculateWeight(dipTm, 0.3))); + + accessories.add(createSimple('face_pull', 'Band Face Pull', 3, 10)); + accessories.add(createSimple('ab_roll', 'Ab Wheel Rollout', 3, 10)); + break; + + case 3: // Pullup Tag (Pull) + // Curls: ~20% vom System-Pullup-TM + accessories.add(createSimple('curl', 'Barbell Curl', 3, 10, + weight: calculateWeight(pullupTm, 0.2))); + + accessories.add(_createIntervalExercise( + id: 'kb_swing', + name: '2H KB Swing', + sets: 10, + intervalSeconds: 60, + repsPerSet: 5)); + accessories.add(createSimple('plank', 'Plank (30s)', 3, 1)); + break; + } + return accessories; + } + + List _generatePullUpJourney( + int day, Map trainingMaxes) { + final exercises = []; + + Exercise createAccessory( + String id, String name, ExerciseType type, int sets, int reps, + {double weight = 0.0}) { + return Exercise( + exerciseId: id, + exerciseName: name, + bodyweightAtSession: 0, + sets: List.generate( + sets, + (i) => WorkoutSet( + setNumber: i + 1, + repsTarget: reps, + targetWeightTotal: weight, + repsActual: 0, + isAmrap: false, + completed: false, + )), + ); + } + + double calculateWeight(double referenceTm, double percentage) { + final raw = referenceTm * percentage; + return (raw / 2.5).round() * 2.5; + } + + switch (day) { + case 1: + exercises.add(createAccessory('scap_pull', 'Scapular Pull-Ups', + ExerciseType.scapular_pull, 3, 10)); + + exercises.add(createAccessory( + 'plank', 'Core Plank (45s)', ExerciseType.plank, 3, 1)); + break; + + case 2: + exercises.add(createAccessory( + 'inv_row', 'Australian Pull-Ups', ExerciseType.inverted_row, 4, 8)); + + exercises.add(createAccessory( + 'face_pull', 'Band Face Pull', ExerciseType.face_pull, 3, 15)); + break; + + case 3: + exercises.add(createAccessory('neg_pull', 'Negative Pull-Ups (5s slow)', + ExerciseType.negative_pullup, 3, 4)); + + final rowTm = trainingMaxes['row'] ?? 0.0; + final curlWeight = rowTm > 0 ? calculateWeight(rowTm, 0.3) : 0.0; + + exercises.add(createAccessory( + 'curl', 'Barbell Curl', ExerciseType.curl_barbell, 3, 10, + weight: curlWeight)); + break; + } + + return exercises; + } + + List _generateConditioning(int day, int targetSets) { + final accessories = []; + + const totalTimeSeconds = 20 * 60; + final intervalSeconds = (totalTimeSeconds / targetSets).floor(); + + String id; + String name; + + switch (day) { + case 1: + id = 'kb_clean_press'; + name = 'KB Clean & Press'; + break; + case 2: + id = 'kb_snatch_cond'; + name = 'KB Snatch'; + break; + case 3: + id = 'kb_thruster'; + name = 'KB Thruster'; + break; + default: + return []; + } + + accessories.add(_createIntervalExercise( + id: id, + name: name, + sets: targetSets, + intervalSeconds: intervalSeconds, + repsPerSet: 5, + )); + + return accessories; + } + + Exercise _createIntervalExercise({ + required String id, + required String name, + required int sets, + required int intervalSeconds, + required int repsPerSet, + }) { + return Exercise( + exerciseId: id, + exerciseName: '$name (${_formatIntervalName(intervalSeconds)})', + bodyweightAtSession: 0, + intervalSeconds: intervalSeconds, + sets: List.generate( + sets, + (i) => WorkoutSet( + setNumber: i + 1, + repsTarget: repsPerSet, + targetWeightTotal: 0, + repsActual: 0, + isAmrap: false, + completed: false, + )), + ); + } + + String _formatIntervalName(int seconds) { + if (seconds == 60) return 'EMOM'; + return 'E${seconds}S'; + } +} diff --git a/lib/src/features/workout_runner/presentation/screens/battle_screen.dart b/lib/src/features/workout_runner/presentation/screens/battle_screen.dart index 6709f59..407f11f 100644 --- a/lib/src/features/workout_runner/presentation/screens/battle_screen.dart +++ b/lib/src/features/workout_runner/presentation/screens/battle_screen.dart @@ -1,11 +1,12 @@ 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 'dart:async'; -import 'dart:convert'; import '../../../../core/constants/asset_paths.dart'; import '../../../../core/theme/app_theme.dart'; +import '../../../../shared/data/local/app_database.dart'; import '../../../../shared/domain/entities/exercise.dart'; import '../../../../shared/domain/entities/workout_set.dart'; import '../../../../shared/domain/logic/wendler_calculator.dart'; @@ -16,8 +17,11 @@ import '../../../../shared/data/repositories/cycle_repository.dart'; import '../../../../shared/data/repositories/workout_repository.dart'; import '../../../../shared/data/remote/sync_service.dart'; import '../widgets/plate_visualizer.dart'; -import '../widgets/timer_widget.dart'; 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; @@ -62,17 +66,92 @@ class _BattleScreenState extends ConsumerState { case 'squat': return AssetPaths.enemyIronGolem; case 'pullup': + case 'row': return AssetPaths.enemyGravityDemon; case 'dip': + case 'bench': return AssetPaths.enemyPressurePhantom; default: return AssetPaths.enemyIronGolem; } } - List> _getExerciseConfig(int day) { + void _handleEmomSetComplete() { + final currentExercise = _exercises[_currentExerciseIndex]; + final currentSet = currentExercise.sets[_currentSetIndex]; + + final updatedSet = currentSet.copyWith( + repsActual: currentSet.repsTarget, + completed: true, + ); + + final updatedSets = List.from(currentExercise.sets); + updatedSets[_currentSetIndex] = updatedSet; + + final updatedExercise = currentExercise.copyWith(sets: updatedSets); + final updatedExercises = List.from(_exercises); + updatedExercises[_currentExerciseIndex] = updatedExercise; + + if (_currentSetIndex < currentExercise.sets.length - 1) { + setState(() { + _exercises = updatedExercises; + _currentSetIndex++; + + _repsCompleted = currentExercise.sets[_currentSetIndex].repsTarget; + }); + } else { + setState(() { + _exercises = updatedExercises; + }); + _showEmomFinishDialog(); + } + } + + void _showExerciseGuide(String exerciseId) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) => ExerciseGuideSheet(exerciseId: exerciseId), + ); + } + + List> _getExerciseConfig(int day, UserCollection user) { + final variants = user.exerciseVariants ?? {}; + + Map getVariant(String slot, String defaultId, + String defaultName, ExerciseType defaultType) { + final variant = variants[slot]; + + if (slot == 'pull') { + if (variant == 'row') { + return {'id': 'row', 'name': 'Pendlay Row', 'type': ExerciseType.row}; + } + return { + 'id': 'pullup', + 'name': 'Weighted Pull-up', + 'type': ExerciseType.pullup + }; + } + + if (slot == 'push') { + if (variant == 'bench') { + return { + 'id': 'bench', + 'name': 'Bench Press', + 'type': ExerciseType.bench + }; + } + return {'id': 'dip', 'name': 'Weighted Dip', 'type': ExerciseType.dip}; + } + + return {'id': defaultId, 'name': defaultName, 'type': defaultType}; + } + switch (day) { case 1: + final pull = getVariant( + 'pull', 'pullup', 'Weighted Pull-up', ExerciseType.pullup); return [ { 'id': 'squat', @@ -80,21 +159,13 @@ class _BattleScreenState extends ConsumerState { 'type': ExerciseType.squat, 'isMain': true }, - { - 'id': 'pullup', - 'name': 'Weighted Pull-up', - 'type': ExerciseType.pullup, - 'isMain': false - }, + {...pull, 'isMain': false}, ]; case 2: + final push = + getVariant('push', 'dip', 'Weighted Dip', ExerciseType.dip); return [ - { - 'id': 'dip', - 'name': 'Weighted Dip', - 'type': ExerciseType.dip, - 'isMain': true - }, + {...push, 'isMain': true}, { 'id': 'squat', 'name': 'Back Squat', @@ -103,19 +174,13 @@ class _BattleScreenState extends ConsumerState { }, ]; case 3: + final pull = getVariant( + 'pull', 'pullup', 'Weighted Pull-up', ExerciseType.pullup); + final push = + getVariant('push', 'dip', 'Weighted Dip', ExerciseType.dip); return [ - { - 'id': 'pullup', - 'name': 'Weighted Pull-up', - 'type': ExerciseType.pullup, - 'isMain': true - }, - { - 'id': 'dip', - 'name': 'Weighted Dip', - 'type': ExerciseType.dip, - 'isMain': false - }, + {...pull, 'isMain': true}, + {...push, 'isMain': false}, ]; default: return []; @@ -124,65 +189,90 @@ class _BattleScreenState extends ConsumerState { Future _loadWorkout() async { final userRepo = ref.read(userRepositoryProvider); + final workoutRepo = ref.read(workoutRepositoryProvider); final cycleRepo = ref.read(cycleRepositoryProvider); final user = await userRepo.getLocalUser(); - final cycle = await cycleRepo.getCurrentCycle(); - if (user == null || cycle == null) { + if (user == null) { if (mounted) context.go('/hub'); return; } - final trainingMaxes = cycleRepo.getCurrentTrainingMaxes(); - final exercises = []; + List exercises = []; - final exerciseConfigs = _getExerciseConfig(widget.day); + if (widget.workoutId != null) { + try { + final allWorkouts = await workoutRepo.getAllWorkouts(); - for (final config in exerciseConfigs) { - final id = config['id'] as String; - final name = config['name'] as String; - final type = config['type'] as ExerciseType; - final isMain = config['isMain'] as bool; + final loadedWorkout = + allWorkouts.where((w) => w.id == widget.workoutId).firstOrNull; - final tm = trainingMaxes[id] ?? 0.0; - List sets = []; + if (loadedWorkout != null && loadedWorkout.exercises.isNotEmpty) { + exercises = loadedWorkout.exercises.map((e) { + return Exercise.fromJson(e as Map); + }).toList(); + } + } catch (e) { + debugPrint('⚠️ Fehler beim Laden des gespeicherten Workouts: $e'); + } + } - if (isMain) { - sets = WendlerCalculator.generateSets( - week: widget.week, - trainingMax: tm, - exerciseType: type, - currentBodyweight: user.currentBodyweight, - ); - } else { - if (widget.week != 4) { - sets = WendlerCalculator.generateFSLSets( + if (exercises.isEmpty) { + final trainingMaxesMap = await cycleRepo.getCurrentTrainingMaxesAsync(); + final exerciseConfigs = _getExerciseConfig(widget.day, user); + + for (final config in exerciseConfigs) { + final id = config['id'] as String; + final name = config['name'] as String; + final type = config['type'] as ExerciseType; + final isMain = config['isMain'] as bool; + + String tmKey = id; + if (id == 'bench') tmKey = 'dip'; + if (id == 'row') tmKey = 'pullup'; + + final tm = trainingMaxesMap[tmKey] ?? 0.0; + List sets = []; + + if (isMain) { + sets = WendlerCalculator.generateSets( + week: widget.week, trainingMax: tm, exerciseType: type, currentBodyweight: user.currentBodyweight, ); + } else { + if (widget.week != 4) { + sets = WendlerCalculator.generateFSLSets( + trainingMax: tm, + exerciseType: type, + currentBodyweight: user.currentBodyweight, + ); + } } - } - if (sets.isNotEmpty) { - exercises.add(Exercise( - exerciseId: id, - exerciseName: isMain ? name : '$name (FSL)', - bodyweightAtSession: user.currentBodyweight, - sets: sets, - )); + if (sets.isNotEmpty) { + exercises.add(Exercise( + exerciseId: id, + exerciseName: isMain ? name : '$name (FSL)', + bodyweightAtSession: user.currentBodyweight, + sets: sets, + )); + } } } - setState(() { - _exercises = exercises; - _isLoading = false; + if (mounted) { + setState(() { + _exercises = exercises; + _isLoading = false; - if (exercises.isNotEmpty && exercises.first.sets.isNotEmpty) { - _repsCompleted = exercises.first.sets.first.repsTarget; - } - }); + if (exercises.isNotEmpty && exercises.first.sets.isNotEmpty) { + _repsCompleted = exercises.first.sets.first.repsTarget; + } + }); + } } void _completeSet() { @@ -275,6 +365,28 @@ class _BattleScreenState extends ConsumerState { } } + final questService = ref.read(questServiceProvider); + + await questService.reportEvent(QuestTrigger.workoutComplete); + + int totalVolume = 0; + int totalReps = 0; + for (var ex in _exercises) { + for (var set in ex.sets) { + if (set.completed) { + totalVolume += (set.targetWeightTotal * set.repsActual).round(); + totalReps += set.repsActual; + } + } + } + + if (totalVolume > 0) { + await questService.reportEvent(QuestTrigger.volume, data: totalVolume); + } + if (totalReps > 0) { + await questService.reportEvent(QuestTrigger.repCount, data: totalReps); + } + if (widget.workoutId != null) { final workoutRepo = ref.read(workoutRepositoryProvider); final cycleRepo = ref.read(cycleRepositoryProvider); @@ -286,20 +398,22 @@ class _BattleScreenState extends ConsumerState { cycleId: cycleIdRef, week: widget.week, day: widget.day); if (workout != null) { - workout.exercisesJson = - jsonEncode(_exercises.map((e) => e.toJson()).toList()); - await workoutRepo.completeWorkout(workout, xpEarned: xpEarned); + final updatedExercises = _exercises.map((e) => e.toJson()).toList(); + final updatedWorkout = workout.copyWith(exercises: updatedExercises); + + await workoutRepo.completeWorkout(updatedWorkout, xpEarned: xpEarned); ref.read(syncServiceProvider).sync(); } } + final l10n = AppLocalizations.of(context)!; if (mounted) { showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( - title: const Text('RAID COMPLETE!'), + title: Text(l10n.battleRaidComplete), content: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -323,7 +437,7 @@ class _BattleScreenState extends ConsumerState { Navigator.of(context).pop(); context.go('/hub'); }, - child: const Text('BACK TO HUB'), + child: Text(l10n.battleBackToHub), ), ], ), @@ -332,12 +446,13 @@ class _BattleScreenState extends ConsumerState { } void _showLevelUpDialog(int oldLevel, int newLevel) { + final l10n = AppLocalizations.of(context)!; showDialog( context: context, builder: (context) => AlertDialog( backgroundColor: AppTheme.primaryColor, - title: const Text( - 'LEVEL UP!', + title: Text( + l10n.levelUpTitle, style: TextStyle(color: Colors.black), ), content: Column( @@ -346,7 +461,7 @@ class _BattleScreenState extends ConsumerState { const Icon(Icons.military_tech, size: 80, color: Colors.black), const SizedBox(height: 16), Text( - 'You have grown stronger!', + l10n.levelUpBody, style: Theme.of(context) .textTheme .bodyMedium @@ -355,7 +470,7 @@ class _BattleScreenState extends ConsumerState { ), const SizedBox(height: 8), Text( - 'The monsters tremble at your new power.', // Story Flavor + l10n.levelUpSubtitle, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Colors.black54, fontStyle: FontStyle.italic), textAlign: TextAlign.center, @@ -373,16 +488,28 @@ class _BattleScreenState extends ConsumerState { actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), - child: - const Text('CONTINUE', style: TextStyle(color: Colors.black)), + child: Text(l10n.continueButton, + style: TextStyle(color: Colors.black)), ), ], ), ); } + void _handleCompletePress(WorkoutSet currentSet) { + if (currentSet.isAmrap) { + _showAmrapDialog(currentSet); + } else { + setState(() { + _repsCompleted = currentSet.repsTarget; + }); + _completeSet(); + } + } + @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; if (_isLoading) { return const Scaffold(body: Center(child: CircularProgressIndicator())); } @@ -396,104 +523,131 @@ class _BattleScreenState extends ConsumerState { final currentExercise = _exercises[_currentExerciseIndex]; final currentSet = currentExercise.sets[_currentSetIndex]; - final userRepo = ref.watch(userRepositoryProvider); - final totalHP = _exercises.fold( - 0, - (sum, ex) => sum + ex.sets.fold(0, (s, set) => s + set.repsTarget), - ); + return FutureBuilder>( + future: ref.read(userRepositoryProvider).getInventorySettingsAsync(), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const Scaffold( + body: Center(child: CircularProgressIndicator())); + } - final completedHP = _exercises.take(_currentExerciseIndex).fold( - 0, - (sum, ex) => - sum + ex.sets.fold(0, (s, set) => s + set.repsActual), - ) + - currentExercise.sets - .take(_currentSetIndex) - .fold(0, (sum, set) => sum + set.repsActual); + final inventory = snapshot.data!; - final isBodyweight = currentExercise.exerciseId != 'squat'; - final barWeight = isBodyweight - ? currentExercise.bodyweightAtSession - : userRepo.getBarWeight(); - final availablePlates = userRepo.getAvailablePlates(); - final inventory = userRepo.getInventorySettings(); - final bandsList = - (inventory['bands'] as List?)?.cast>() ?? []; + final totalHP = _exercises.fold( + 0, + (sum, ex) => + sum + ex.sets.fold(0, (s, set) => s + set.repsTarget), + ); - final Map availableBands = {}; - for (var band in bandsList) { - final color = band['color'] as String; - final resistance = (band['resistance_kg'] as num).toDouble(); - if (band['count'] as int > 0) { - availableBands[color] = resistance; - } - } + final completedHP = _exercises.take(_currentExerciseIndex).fold( + 0, + (sum, ex) => + sum + + ex.sets.fold(0, (s, set) => s + set.repsActual), + ) + + currentExercise.sets + .take(_currentSetIndex) + .fold(0, (sum, set) => sum + set.repsActual); - final plateResult = PlateCalculator.calculate( - targetWeight: currentSet.targetWeightTotal, - barWeight: barWeight, - availablePlates: availablePlates, - availableBands: availableBands, - isTwoSided: !isBodyweight, - ); + final isTwoSided = currentExercise.exerciseId == 'squat' || + currentExercise.exerciseId == 'row' || + currentExercise.exerciseId == 'bench' || + currentExercise.exerciseId == 'rdl' || + currentExercise.exerciseId == 'ohp' || + currentExercise.exerciseId == 'curl'; + final isBodyweight = !isTwoSided; + final barWeight = isBodyweight + ? currentExercise.bodyweightAtSession + : (inventory['bar_weight'] as num?)?.toDouble() ?? 20.0; - return Scaffold( - appBar: AppBar( - title: Text('Week ${widget.week} - Day ${widget.day}'), - leading: IconButton( - icon: const Icon(Icons.close), - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Abandon Raid?'), - content: const Text('Your progress will not be saved.'), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('CANCEL'), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop(); - context.go('/hub'); - }, - style: TextButton.styleFrom( - foregroundColor: AppTheme.errorColor), - child: const Text('ABANDON'), - ), - ], + final platesList = (inventory['plates'] as List?) + ?.map((e) => (e as num).toDouble()) + .toList() ?? + []; + + final bandsList = + (inventory['bands'] as List?)?.cast>() ?? []; + final Map availableBands = {}; + for (var band in bandsList) { + final color = band['color'] as String; + final resistance = (band['resistance_kg'] as num).toDouble(); + if (band['count'] as int > 0) { + availableBands[color] = resistance; + } + } + + final plateResult = PlateCalculator.calculate( + targetWeight: currentSet.targetWeightTotal, + barWeight: barWeight, + availablePlates: platesList, + availableBands: availableBands, + isTwoSided: isTwoSided, + ); + + return Scaffold( + appBar: AppBar( + title: Text('Week ${widget.week} - Day ${widget.day}'), + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(l10n.battleAbandonTitle), + content: Text(l10n.battleAbandonBody), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(l10n.cancelButton), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(); + context.go('/hub'); + }, + style: TextButton.styleFrom( + foregroundColor: AppTheme.errorColor), + child: Text(l10n.abandonButton), + ), + ], + ), + ); + }, ), - ); - }, - ), - ), - body: Stack( - children: [ - Positioned.fill( - child: Image.asset( - AssetPaths.bgUndergroundGym, - fit: BoxFit.cover, ), - ), - Positioned.fill( - child: Container( - color: Colors.black.withOpacity(0.7), + body: Stack( + children: [ + Positioned.fill( + child: Image.asset( + AssetPaths.bgUndergroundGym, + fit: BoxFit.cover, + ), + ), + Positioned.fill( + child: Container( + color: Colors.black.withValues(alpha: 0.7), + ), + ), + Positioned.fill( + child: SafeArea( + child: _isResting + ? _buildRestScreen(inventory) + : _buildWorkoutScreen(currentExercise, currentSet, + plateResult, completedHP, totalHP), + ), + ), + ], ), - ), - SafeArea( - child: _isResting - ? _buildRestScreen() - : _buildWorkoutScreen(currentExercise, currentSet, plateResult, - completedHP, totalHP), - ), - ], - ), - ); + ); + }); } - Widget _buildRestScreen() { + Widget _buildRestScreen(Map inventory) { + final nextExerciseInfo = _exercises[_currentExerciseIndex]; + final nextSet = nextExerciseInfo.sets[_currentSetIndex]; + final l10n = AppLocalizations.of(context)!; + return Container( decoration: const BoxDecoration( gradient: LinearGradient( @@ -506,51 +660,114 @@ class _BattleScreenState extends ConsumerState { ), ), child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'REST', - style: Theme.of(context).textTheme.displayLarge, - ), - const SizedBox(height: 32), - SizedBox( - width: 200, - height: 200, - child: Stack( - alignment: Alignment.center, - children: [ - SizedBox( - width: 200, - height: 200, - child: CircularProgressIndicator( - value: _restSeconds / 180, - strokeWidth: 12, - backgroundColor: AppTheme.xpBarBackground, - color: AppTheme.primaryColor, - ), - ), - Text( - _formatTime(_restSeconds), - style: Theme.of(context).textTheme.displayLarge?.copyWith( - fontSize: 48, - color: AppTheme.primaryColor, - ), - ), - ], + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + l10n.battleRest, + style: Theme.of(context).textTheme.displayLarge, ), - ), - const SizedBox(height: 48), - ElevatedButton( - onPressed: _skipRest, - child: const Text('SKIP REST'), - ), - ], + const SizedBox(height: 20), + SizedBox( + width: 200, + height: 200, + child: Stack( + alignment: Alignment.center, + children: [ + SizedBox( + width: 200, + height: 200, + child: CircularProgressIndicator( + value: _restSeconds / 180, + strokeWidth: 12, + backgroundColor: AppTheme.xpBarBackground, + color: AppTheme.primaryColor, + ), + ), + Text( + _formatTime(_restSeconds), + style: Theme.of(context).textTheme.displayLarge?.copyWith( + fontSize: 32, + color: AppTheme.primaryColor, + ), + ), + ], + ), + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: _skipRest, + child: Text(l10n.battleSkipRest), + ), + if (nextSet != null && nextExerciseInfo != null) ...[ + const SizedBox(height: 24), + const Divider(color: Colors.white10, endIndent: 32, indent: 32), + const SizedBox(height: 12), + Text( + l10n.battleUpNext( + nextExerciseInfo.exerciseName.toUpperCase()), + style: const TextStyle( + color: Colors.grey, + fontSize: 11, + fontWeight: FontWeight.bold, + letterSpacing: 1.2), + ), + const SizedBox(height: 4), + Text( + '${nextSet.repsTarget} x ${nextSet.targetWeightTotal > 0 ? "${nextSet.targetWeightTotal} kg" : "Bodyweight"}', + style: const TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold), + ), + if (nextSet.targetWeightTotal > 0) + _buildNextSetPlates(nextExerciseInfo, nextSet, inventory), + ], + ], + ), ), ), ); } + Widget _buildNextSetPlates( + Exercise exercise, WorkoutSet set, Map inventory) { + final isTwoSided = exercise.exerciseId == 'squat' || + exercise.exerciseId == 'row' || + exercise.exerciseId == 'bench' || + exercise.exerciseId == 'rdl' || + exercise.exerciseId == 'ohp' || + exercise.exerciseId == 'curl'; + + if (!isTwoSided) return const SizedBox.shrink(); + + final barWeight = (inventory['bar_weight'] as num?)?.toDouble() ?? 20.0; + final platesList = (inventory['plates'] as List?) + ?.map((e) => (e as num).toDouble()) + .toList() ?? + []; + + final plateResult = PlateCalculator.calculate( + targetWeight: set.targetWeightTotal, + barWeight: barWeight, + availablePlates: platesList, + availableBands: {}, + isTwoSided: true, + ); + + return Padding( + padding: const EdgeInsets.only(top: 12.0), + child: PlateVisualizer( + plateConfiguration: plateResult.plateConfiguration, + isTwoSided: true, + exerciseName: '', + ), + ); + } + Widget _buildWorkoutScreen( Exercise currentExercise, WorkoutSet currentSet, @@ -558,6 +775,10 @@ class _BattleScreenState extends ConsumerState { int completedHP, int totalHP, ) { + if (currentExercise.intervalSeconds != null && + currentExercise.intervalSeconds! > 0) { + return _buildEmomView(currentExercise, currentSet, completedHP, totalHP); + } final readableStyle = Theme.of(context).textTheme.bodyLarge?.copyWith( color: Colors.white, shadows: [ @@ -572,6 +793,7 @@ class _BattleScreenState extends ConsumerState { const Shadow(color: Colors.black, blurRadius: 8, offset: Offset(0, 2)) ], ); + final l10n = AppLocalizations.of(context)!; return Column( children: [ @@ -585,7 +807,7 @@ class _BattleScreenState extends ConsumerState { child: Image.asset( _getEnemyAsset(currentExercise.exerciseId), fit: BoxFit.contain, - color: Colors.white.withOpacity(0.9), + color: Colors.white.withValues(alpha: 0.9), colorBlendMode: BlendMode.modulate, ), ), @@ -601,7 +823,8 @@ class _BattleScreenState extends ConsumerState { border: Border.all(color: Colors.white24), ), child: Text( - 'WAVE ${_currentExerciseIndex + 1} / ${_exercises.length}', + l10n.battleWave( + _currentExerciseIndex + 1, _exercises.length), style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, @@ -632,12 +855,12 @@ class _BattleScreenState extends ConsumerState { flex: 6, child: Container( decoration: BoxDecoration( - color: AppTheme.surfaceColor.withOpacity(0.95), + color: AppTheme.surfaceColor.withValues(alpha: 0.95), borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.5), + color: Colors.black.withValues(alpha: 0.5), blurRadius: 20, offset: const Offset(0, -5)) ], @@ -660,8 +883,16 @@ class _BattleScreenState extends ConsumerState { ), 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}', + l10n.battleSet(_currentSetIndex + 1, + currentExercise.sets.length), style: Theme.of(context) .textTheme .titleMedium @@ -672,10 +903,10 @@ class _BattleScreenState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _InfoBox( - label: 'WEIGHT', + label: l10n.battleWeight, value: '${currentSet.targetWeightTotal} kg'), _InfoBox( - label: 'REPS', + label: l10n.battleReps, value: '${currentSet.repsTarget}${currentSet.isAmrap ? "+" : ""}'), ], @@ -685,7 +916,8 @@ class _BattleScreenState extends ConsumerState { Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: AppTheme.primaryColor.withOpacity(0.1), + color: + AppTheme.primaryColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), border: Border.all(color: AppTheme.primaryColor), ), @@ -699,7 +931,7 @@ class _BattleScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('ASSISTANCE', + Text(l10n.battleAssistance, style: TextStyle( color: AppTheme.primaryColor, fontSize: 12, @@ -718,7 +950,9 @@ class _BattleScreenState extends ConsumerState { else PlateVisualizer( plateConfiguration: plateResult.plateConfiguration, - isTwoSided: currentExercise.exerciseId == 'squat', + isTwoSided: currentExercise.exerciseId == 'squat' || + currentExercise.exerciseId == 'row' || + currentExercise.exerciseId == 'bench', exerciseName: currentExercise.exerciseName, ), const SizedBox(height: 32), @@ -746,7 +980,7 @@ class _BattleScreenState extends ConsumerState { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), ), - child: const Text('COMPLETE SET', + child: Text(l10n.battleCompleteSet, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -759,17 +993,6 @@ class _BattleScreenState extends ConsumerState { ); } - void _handleCompletePress(WorkoutSet currentSet) { - if (currentSet.isAmrap) { - _showAmrapDialog(currentSet); - } else { - setState(() { - _repsCompleted = currentSet.repsTarget; - }); - _completeSet(); - } - } - String _formatTime(int seconds) { final minutes = seconds ~/ 60; final secs = seconds % 60; @@ -778,6 +1001,7 @@ class _BattleScreenState extends ConsumerState { void _showAmrapDialog(WorkoutSet set) { int tempReps = set.repsTarget; + final l10n = AppLocalizations.of(context)!; showModalBottomSheet( context: context, @@ -793,16 +1017,16 @@ class _BattleScreenState extends ConsumerState { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Text( - '🔥 AMRAP RESULT 🔥', + Text( + l10n.amrapResultTitle, style: TextStyle( color: AppTheme.secondaryColor, fontWeight: FontWeight.bold, fontSize: 20), ), const SizedBox(height: 8), - const Text( - 'Go all out! How many did you get?', + Text( + l10n.amrapResultBody, style: TextStyle(color: Colors.grey), ), const SizedBox(height: 32), @@ -846,7 +1070,7 @@ class _BattleScreenState extends ConsumerState { backgroundColor: AppTheme.secondaryColor, foregroundColor: Colors.white, ), - child: const Text('CONFIRM RESULT', + child: Text(l10n.amrapConfirm, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold)), ), @@ -859,6 +1083,272 @@ class _BattleScreenState extends ConsumerState { }, ); } + + Widget _buildEmomView( + Exercise currentExercise, + WorkoutSet currentSet, + int completedHP, + int totalHP, + ) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.all(16), + margin: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppTheme.surfaceColor.withValues(alpha: 0.9), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.white10), + ), + child: Row( + children: [ + SizedBox( + height: 60, + width: 60, + child: Image.asset( + _getEnemyAsset(currentExercise.exerciseId), + fit: BoxFit.contain, + errorBuilder: (c, o, s) => const Icon(Icons.fitness_center, + size: 40, color: Colors.white), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + currentExercise.exerciseName, + style: const TextStyle( + fontWeight: FontWeight.bold, + 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), + ), + ], + ), + ), + SizedBox( + width: 80, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '${totalHP - completedHP}/$totalHP HP', + style: const TextStyle( + color: AppTheme.errorColor, + fontWeight: FontWeight.bold, + fontSize: 10), + textAlign: TextAlign.center, + ), + const SizedBox(height: 4), + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: LinearProgressIndicator( + value: totalHP > 0 + ? (totalHP - completedHP) / totalHP + : 0.0, + backgroundColor: Colors.red[900], + color: AppTheme.errorColor, + minHeight: 6, + ), + ), + ], + ), + ), + ], + ), + ), + Expanded( + child: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + EmomTimerWidget( + key: ValueKey( + '${currentExercise.exerciseId}_$_currentExerciseIndex'), + intervalSeconds: currentExercise.intervalSeconds!, + totalSets: currentExercise.sets.length, + currentSet: _currentSetIndex + 1, + onSetComplete: _handleEmomSetComplete, + onWorkoutComplete: _handleEmomSetComplete, + ), + const SizedBox(height: 32), + if (currentSet.targetWeightTotal > 0) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 8), + decoration: BoxDecoration( + border: Border.all(color: AppTheme.primaryColor), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + 'WEIGHT: ${currentSet.targetWeightTotal} kg', + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: AppTheme.primaryColor, + fontWeight: FontWeight.bold), + ), + ), + ], + ), + ), + ), + ), + ], + ); + } + + void _adjustEmomSets(int newTotalSets) { + final currentEx = _exercises[_currentExerciseIndex]; + + if (newTotalSets == currentEx.sets.length) return; + + List currentSets = List.from(currentEx.sets); + + if (newTotalSets > currentSets.length) { + final templateSet = currentSets.last; + + for (int i = currentSets.length; i < newTotalSets; i++) { + currentSets.add(templateSet.copyWith( + setNumber: i + 1, + completed: true, + repsActual: templateSet.repsTarget, + )); + } + } else { + currentSets = currentSets.sublist(0, newTotalSets); + } + + final updatedEx = currentEx.copyWith(sets: currentSets); + final updatedExercises = List.from(_exercises); + updatedExercises[_currentExerciseIndex] = updatedEx; + + setState(() { + _exercises = updatedExercises; + + _currentSetIndex = newTotalSets - 1; + + _repsCompleted = updatedEx.sets.last.repsTarget; + }); + } + + void _showEmomFinishDialog() { + final currentEx = _exercises[_currentExerciseIndex]; + int setsCount = currentEx.sets.length; + final l10n = AppLocalizations.of(context)!; + + showModalBottomSheet( + context: context, + backgroundColor: AppTheme.surfaceColor, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + ), + builder: (context) { + return StatefulBuilder( + builder: (context, setModalState) { + return Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.timer_off, + size: 48, color: AppTheme.primaryColor), + const SizedBox(height: 16), + Text( + l10n.emomFinishedTitle, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Colors.white, + fontWeight: FontWeight.bold, + letterSpacing: 1.5, + ), + ), + const SizedBox(height: 8), + Text( + l10n.emomFinishedBody, + style: TextStyle(color: Colors.grey), + ), + const SizedBox(height: 32), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _CounterButton( + icon: Icons.remove, + onTap: setsCount > 1 + ? () => setModalState(() => setsCount--) + : null, + ), + Container( + width: 140, + alignment: Alignment.center, + child: Column( + children: [ + Text( + '$setsCount', + style: const TextStyle( + fontSize: 64, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + Text(l10n.emomSetsCompleted, + style: TextStyle( + color: AppTheme.primaryColor, + fontSize: 10, + fontWeight: FontWeight.bold)), + ], + ), + ), + _CounterButton( + icon: Icons.add, + onTap: () => setModalState(() => setsCount++), + ), + ], + ), + const SizedBox(height: 32), + SizedBox( + width: double.infinity, + height: 56, + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + + _adjustEmomSets(setsCount); + + _completeSet(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.successColor, + foregroundColor: Colors.white, + ), + child: Text(l10n.emomConfirm, + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold)), + ), + ), + const SizedBox(height: 16), + ], + ), + ); + }, + ); + }, + ); + } } class _InfoBox extends StatelessWidget { @@ -902,7 +1392,7 @@ class _CounterButton extends StatelessWidget { decoration: BoxDecoration( color: onTap != null ? AppTheme.primaryColor - : Colors.grey.withOpacity(0.1), + : Colors.grey.withValues(alpha: 0.1), shape: BoxShape.circle, ), child: Icon(icon, diff --git a/lib/src/features/workout_runner/presentation/screens/battle_screen_back b/lib/src/features/workout_runner/presentation/screens/battle_screen_back deleted file mode 100644 index 25abe80..0000000 --- a/lib/src/features/workout_runner/presentation/screens/battle_screen_back +++ /dev/null @@ -1,1011 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; -import 'dart:async'; -import 'dart:convert'; - -import '../../../../core/constants/asset_paths.dart'; -import '../../../../core/theme/app_theme.dart'; -import '../../../../shared/domain/entities/exercise.dart'; -import '../../../../shared/domain/entities/workout_set.dart'; -import '../../../../shared/domain/logic/wendler_calculator.dart'; -import '../../../../shared/domain/logic/plate_calculator.dart'; -import '../../../../shared/domain/logic/xp_calculator.dart'; -import '../../../../shared/data/repositories/user_repository.dart'; -import '../../../../shared/data/repositories/cycle_repository.dart'; -import '../../../../shared/data/repositories/workout_repository.dart'; -import '../../../../shared/data/remote/sync_service.dart'; -import '../widgets/plate_visualizer.dart'; -import '../widgets/timer_widget.dart'; -import '../widgets/enemy_hp_bar.dart'; - -class BattleScreen extends ConsumerStatefulWidget { - final int week; - final int day; - final int? workoutId; - - const BattleScreen({ - super.key, - required this.week, - required this.day, - this.workoutId, - }); - - @override - ConsumerState createState() => _BattleScreenState(); -} - -class _BattleScreenState extends ConsumerState { - List _exercises = []; - int _currentExerciseIndex = 0; - int _currentSetIndex = 0; - int _repsCompleted = 0; - bool _isLoading = true; - Timer? _restTimer; - int _restSeconds = 0; - bool _isResting = false; - - @override - void initState() { - super.initState(); - _loadWorkout(); - } - - @override - void dispose() { - _restTimer?.cancel(); - super.dispose(); - } - - String _getEnemyAsset(String exerciseId) { - // Mapping basierend auf Übungs-ID - switch (exerciseId) { - case 'squat': - return AssetPaths.enemyIronGolem; - case 'pullup': - return AssetPaths.enemyGravityDemon; - case 'dip': - return AssetPaths.enemyPressurePhantom; - default: - return AssetPaths.enemyIronGolem; // Fallback - } - } - - List> _getExerciseConfig(int day) { - switch (day) { - case 1: - return [ - { - 'id': 'squat', - 'name': 'Back Squat', - 'type': ExerciseType.squat, - 'isMain': true - }, - { - 'id': 'pullup', - 'name': 'Weighted Pull-up', - 'type': ExerciseType.pullup, - 'isMain': false - }, - ]; - case 2: - return [ - { - 'id': 'dip', - 'name': 'Weighted Dip', - 'type': ExerciseType.dip, - 'isMain': true - }, - { - 'id': 'squat', - 'name': 'Back Squat', - 'type': ExerciseType.squat, - 'isMain': false - }, - ]; - case 3: - return [ - { - 'id': 'pullup', - 'name': 'Weighted Pull-up', - 'type': ExerciseType.pullup, - 'isMain': true - }, - { - 'id': 'dip', - 'name': 'Weighted Dip', - 'type': ExerciseType.dip, - 'isMain': false - }, - ]; - default: - return []; - } - } - - Future _loadWorkout() async { - final userRepo = ref.read(userRepositoryProvider); - final cycleRepo = ref.read(cycleRepositoryProvider); - - final user = await userRepo.getLocalUser(); - final cycle = await cycleRepo.getCurrentCycle(); - - if (user == null || cycle == null) { - if (mounted) context.go('/hub'); - return; - } - - final trainingMaxes = cycleRepo.getCurrentTrainingMaxes(); - final exercises = []; - - final exerciseConfigs = _getExerciseConfig(widget.day); - - for (final config in exerciseConfigs) { - final id = config['id'] as String; - final name = config['name'] as String; - final type = config['type'] as ExerciseType; - final isMain = config['isMain'] as bool; - - final tm = trainingMaxes[id] ?? 0.0; - List sets = []; - - if (isMain) { - sets = WendlerCalculator.generateSets( - week: widget.week, - trainingMax: tm, - exerciseType: type, - currentBodyweight: user.currentBodyweight, - ); - } else { - if (widget.week != 4) { - sets = WendlerCalculator.generateFSLSets( - trainingMax: tm, - exerciseType: type, - currentBodyweight: user.currentBodyweight, - ); - } - } - - if (sets.isNotEmpty) { - exercises.add(Exercise( - exerciseId: id, - exerciseName: isMain ? name : '$name (FSL)', - bodyweightAtSession: user.currentBodyweight, - sets: sets, - )); - } - } - - setState(() { - _exercises = exercises; - _isLoading = false; - - if (exercises.isNotEmpty && exercises.first.sets.isNotEmpty) { - _repsCompleted = exercises.first.sets.first.repsTarget; - } - }); - } - - void _completeSet() { - final currentExercise = _exercises[_currentExerciseIndex]; - final currentSet = currentExercise.sets[_currentSetIndex]; - - final updatedSet = currentSet.copyWith( - repsActual: _repsCompleted, - completed: true, - ); - - final updatedSets = List.from(currentExercise.sets); - updatedSets[_currentSetIndex] = updatedSet; - - final updatedExercise = currentExercise.copyWith(sets: updatedSets); - final updatedExercises = List.from(_exercises); - updatedExercises[_currentExerciseIndex] = updatedExercise; - - int nextRepsTarget = 0; - - if (_currentSetIndex < currentExercise.sets.length - 1) { - nextRepsTarget = currentExercise.sets[_currentSetIndex + 1].repsTarget; - - setState(() { - _exercises = updatedExercises; - _currentSetIndex++; - _repsCompleted = nextRepsTarget; - }); - _startRestTimer(90); - } else if (_currentExerciseIndex < _exercises.length - 1) { - final nextExercise = _exercises[_currentExerciseIndex + 1]; - if (nextExercise.sets.isNotEmpty) { - nextRepsTarget = nextExercise.sets.first.repsTarget; - } - - setState(() { - _exercises = updatedExercises; - _currentExerciseIndex++; - _currentSetIndex = 0; - _repsCompleted = nextRepsTarget; - }); - _startRestTimer(180); - } else { - setState(() { - _exercises = updatedExercises; - }); - _completeWorkout(); - } - } - - void _startRestTimer(int seconds) { - setState(() { - _isResting = true; - _restSeconds = seconds; - }); - - _restTimer?.cancel(); - _restTimer = Timer.periodic(const Duration(seconds: 1), (timer) { - if (_restSeconds > 0) { - setState(() => _restSeconds--); - } else { - timer.cancel(); - setState(() => _isResting = false); - } - }); - } - - void _skipRest() { - _restTimer?.cancel(); - setState(() { - _isResting = false; - _restSeconds = 0; - }); - } - - Future _completeWorkout() async { - final xpEarned = XPCalculator.calculateWorkoutXP(_exercises); - - final userRepo = ref.read(userRepositoryProvider); - await userRepo.updateXP(xpEarned); - - final user = await userRepo.getLocalUser(); - if (user != null) { - final newLevel = XPCalculator.calculateLevelFromXP(user.xp); - if (newLevel > user.level) { - await userRepo.updateLevel(newLevel); - if (mounted) { - _showLevelUpDialog(user.level, newLevel); - } - } - } - - if (widget.workoutId != null) { - final workoutRepo = ref.read(workoutRepositoryProvider); - final cycleRepo = ref.read(cycleRepositoryProvider); - final cycle = await cycleRepo.getCurrentCycle(); - - final cycleIdRef = cycle?.serverId ?? cycle?.id.toString() ?? ''; - - var workout = await workoutRepo.getWorkoutByWeekDay( - cycleId: cycleIdRef, week: widget.week, day: widget.day); - - if (workout != null) { - workout.exercisesJson = - jsonEncode(_exercises.map((e) => e.toJson()).toList()); - await workoutRepo.completeWorkout(workout, xpEarned: xpEarned); - - ref.read(syncServiceProvider).sync(); - } - } - - if (mounted) { - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => AlertDialog( - title: const Text('RAID COMPLETE!'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - Icons.emoji_events, - size: 64, - color: AppTheme.primaryColor, - ), - const SizedBox(height: 16), - Text( - '+$xpEarned XP', - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - color: AppTheme.primaryColor, - ), - ), - ], - ), - actions: [ - ElevatedButton( - onPressed: () { - Navigator.of(context).pop(); - context.go('/hub'); - }, - child: const Text('BACK TO HUB'), - ), - ], - ), - ); - } - } - - void _showLevelUpDialog(int oldLevel, int newLevel) { - showDialog( - context: context, - builder: (context) => AlertDialog( - backgroundColor: AppTheme.primaryColor, - title: const Text( - 'LEVEL UP!', - style: TextStyle(color: Colors.black), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.military_tech, size: 80, color: Colors.black), - const SizedBox(height: 16), - Text( - '$oldLevel → $newLevel', - style: const TextStyle( - fontSize: 32, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), - ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: - const Text('CONTINUE', style: TextStyle(color: Colors.black)), - ), - ], - ), - ); - } - - @override - Widget build(BuildContext context) { - if (_isLoading) { - return const Scaffold(body: Center(child: CircularProgressIndicator())); - } - - if (_exercises.isEmpty) { - return Scaffold( - appBar: AppBar(title: const Text('Battle')), - body: const Center(child: Text('No exercises configured')), - ); - } - - final currentExercise = _exercises[_currentExerciseIndex]; - final currentSet = currentExercise.sets[_currentSetIndex]; - final userRepo = ref.watch(userRepositoryProvider); - - final totalHP = _exercises.fold( - 0, - (sum, ex) => sum + ex.sets.fold(0, (s, set) => s + set.repsTarget), - ); - - final completedHP = _exercises.take(_currentExerciseIndex).fold( - 0, - (sum, ex) => - sum + ex.sets.fold(0, (s, set) => s + set.repsActual), - ) + - currentExercise.sets - .take(_currentSetIndex) - .fold(0, (sum, set) => sum + set.repsActual); - - final isBodyweight = currentExercise.exerciseId != 'squat'; - final barWeight = isBodyweight - ? currentExercise.bodyweightAtSession - : userRepo.getBarWeight(); - final availablePlates = userRepo.getAvailablePlates(); - final inventory = userRepo.getInventorySettings(); - final bandsList = - (inventory['bands'] as List?)?.cast>() ?? []; - - final Map availableBands = {}; - for (var band in bandsList) { - final color = band['color'] as String; - final resistance = (band['resistance_kg'] as num).toDouble(); - if (band['count'] as int > 0) { - availableBands[color] = resistance; - } - } - - final plateResult = PlateCalculator.calculate( - targetWeight: currentSet.targetWeightTotal, - barWeight: barWeight, - availablePlates: availablePlates, - availableBands: availableBands, - isTwoSided: !isBodyweight, - ); - - return Scaffold( - appBar: AppBar( - title: Text('Week ${widget.week} - Day ${widget.day}'), - leading: IconButton( - icon: const Icon(Icons.close), - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Abandon Raid?'), - content: const Text('Your progress will not be saved.'), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('CANCEL'), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop(); - context.go('/hub'); - }, - style: TextButton.styleFrom( - foregroundColor: AppTheme.errorColor), - child: const Text('ABANDON'), - ), - ], - ), - ); - }, - ), - ), - body: Stack( - children: [ - // 1. HINTERGRUND (Underground Gym) - Positioned.fill( - child: Image.asset( - AssetPaths.bgUndergroundGym, - fit: BoxFit.cover, - ), - ), - - // 2. Overlay (Atmosphäre & Lesbarkeit) - Positioned.fill( - child: Container( - color: Colors.black.withOpacity(0.7), // Dunkler Schleier - ), - ), - - // 3. INHALT - SafeArea( - child: _isResting - ? _buildRestScreen() // Rest Screen überdeckt das Gym (oder man macht ihn auch transparent) - : _buildWorkoutScreen(currentExercise, currentSet, plateResult, - completedHP, totalHP), - ), - ], - ), - // body: _isResting - // ? _buildRestScreen() - // : _buildWorkoutScreen( - // currentExercise, currentSet, plateResult, completedHP, totalHP), - ); - } - - Widget _buildRestScreen() { - return Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - AppTheme.backgroundColor, - AppTheme.surfaceColor, - ], - ), - ), - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'REST', - style: Theme.of(context).textTheme.displayLarge, - ), - const SizedBox(height: 32), - SizedBox( - width: 200, - height: 200, - child: Stack( - alignment: Alignment.center, - children: [ - SizedBox( - width: 200, - height: 200, - child: CircularProgressIndicator( - value: _restSeconds / 180, - strokeWidth: 12, - backgroundColor: AppTheme.xpBarBackground, - color: AppTheme.primaryColor, - ), - ), - Text( - _formatTime(_restSeconds), - style: Theme.of(context).textTheme.displayLarge?.copyWith( - fontSize: 48, - color: AppTheme.primaryColor, - ), - ), - ], - ), - ), - const SizedBox(height: 48), - ElevatedButton( - onPressed: _skipRest, - child: const Text('SKIP REST'), - ), - ], - ), - ), - ); - } - - Widget _buildWorkoutScreen( - Exercise currentExercise, - WorkoutSet currentSet, - PlateLoadResult plateResult, - int completedHP, - int totalHP, - ) { - // Gemeinsamer Text-Style für bessere Lesbarkeit auf Hintergründen - final readableStyle = Theme.of(context).textTheme.bodyLarge?.copyWith( - color: Colors.white, - shadows: [ - const Shadow(color: Colors.black, blurRadius: 4, offset: Offset(0, 1)) - ], - ); - - final titleStyle = Theme.of(context).textTheme.titleLarge?.copyWith( - color: Colors.white, - fontWeight: FontWeight.bold, - shadows: [ - const Shadow(color: Colors.black, blurRadius: 8, offset: Offset(0, 2)) - ], - ); - - return Column( - children: [ - // Info Header (HP & Wave) - Container( - padding: const EdgeInsets.all(16), - margin: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppTheme.surfaceColor.withOpacity(0.9), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.white10), - ), - child: Column( - children: [ - // --- NEU: Enemy Image --- - SizedBox( - height: 120, // Gute Größe für den Header - child: Image.asset( - _getEnemyAsset( - currentExercise.exerciseId), // Wählt das richtige Bild - fit: BoxFit.contain, - // Ein leichter Schatten, damit der Gegner nicht "schwebt" - color: Colors.black.withOpacity(0.2), - colorBlendMode: BlendMode.dstOver, - // Fallback Icon, falls Asset fehlt - errorBuilder: (c, o, s) => const Icon( - Icons.warning_amber_rounded, - size: 48, - color: Colors.white24), - ), - ), - const SizedBox(height: 16), // Abstand zum Text - // ------------------------ - Text( - 'Wave ${_currentExerciseIndex + 1}/${_exercises.length}', - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 8), - EnemyHPBar( - current: totalHP - completedHP, - max: totalHP, - ), - ], - ), - ), - - // Scrollbarer Inhalt (nimmt den verfügbaren Platz ein) - Expanded( - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - currentExercise.exerciseName, - style: Theme.of(context).textTheme.displayMedium?.copyWith( - shadows: [ - const Shadow(color: Colors.black, blurRadius: 10) - ], - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - 'Set ${_currentSetIndex + 1}/${currentExercise.sets.length}', - style: titleStyle, // Neuer Style - textAlign: TextAlign.center, - ), - const SizedBox(height: 24), - - // Target Card (etwas transparenter für Look) - Card( - color: AppTheme.surfaceColor.withOpacity(0.95), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - Text('TARGET', - style: Theme.of(context) - .textTheme - .labelLarge - ?.copyWith(color: AppTheme.textSecondary)), - const SizedBox(height: 8), - Text( - '${currentSet.targetWeightTotal.toStringAsFixed(1)} kg', - style: Theme.of(context) - .textTheme - .displayMedium - ?.copyWith(color: AppTheme.primaryColor), - ), - Text( - '${currentSet.targetPercentage}% TM × ${currentSet.repsTarget} reps${currentSet.isAmrap ? '+' : ''}', - style: readableStyle, // Neuer Style - ), - ], - ), - ), - ), - const SizedBox(height: 24), - - // Visualizer oder Band Info - // Wir geben dem Container eine feste Mindesthöhe, damit das Layout weniger springt, - // aber da der Button jetzt fixiert ist, ist das Springen weniger kritisch. - if (plateResult.bandAssistance != null) - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppTheme.primaryColor - .withOpacity(0.2), // Heller für Kontrast - borderRadius: BorderRadius.circular(16), - border: Border.all(color: AppTheme.primaryColor), - ), - child: Column( - children: [ - const Icon(Icons.help_outline, - size: 48, color: AppTheme.primaryColor), - const SizedBox(height: 8), - Text( - 'ASSISTANCE NEEDED', - style: titleStyle?.copyWith( - color: AppTheme.primaryColor), - ), - const SizedBox(height: 8), - Text( - 'Use ${plateResult.bandAssistance} Band', - style: Theme.of(context) - .textTheme - .headlineSmall - ?.copyWith( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - Text( - '(approx. -${(plateResult.totalAchieved - currentSet.targetWeightTotal).abs().toStringAsFixed(1)} kg)', - style: readableStyle, // Neuer Style - ), - ], - ), - ) - else - PlateVisualizer( - plateConfiguration: plateResult.plateConfiguration, - isTwoSided: currentExercise.exerciseId == 'squat', - exerciseName: currentExercise.exerciseName, - ), - - const SizedBox(height: 32), - - Text( - 'REPS COMPLETED', - style: titleStyle, - textAlign: TextAlign.center, - ), - const SizedBox(height: 16), - - // Counter (Hintergrund hinzufügen für Lesbarkeit) - Container( - decoration: BoxDecoration( - color: Colors.black54, - borderRadius: BorderRadius.circular(30), - ), - padding: - const EdgeInsets.symmetric(vertical: 8, horizontal: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon(Icons.remove_circle), - iconSize: 48, - color: AppTheme.primaryColor, - onPressed: _repsCompleted > 0 - ? () => setState(() => _repsCompleted--) - : null, - ), - const SizedBox(width: 24), - SizedBox( - width: 80, - child: Text( - _repsCompleted.toString(), - style: Theme.of(context) - .textTheme - .displayLarge - ?.copyWith(fontSize: 56, color: Colors.white), - textAlign: TextAlign.center, - ), - ), - const SizedBox(width: 24), - IconButton( - icon: const Icon(Icons.add_circle), - iconSize: 48, - color: AppTheme.primaryColor, - onPressed: () => setState(() => _repsCompleted++), - ), - ], - ), - ), - - if (currentSet.isAmrap) - Padding( - padding: const EdgeInsets.only(top: 12), - child: Text( - '🔥 AMRAP - Go for max reps! 🔥', - style: readableStyle?.copyWith( - color: AppTheme.secondaryColor, - fontWeight: FontWeight.bold, - fontSize: 18), - textAlign: TextAlign.center, - ), - ), - - // Platzhalter am Ende, damit man nicht "hinter" den fixierten Button scrollen muss - const SizedBox(height: 100), - ], - ), - ), - ), - - // --- FIXIERTER BUTTON BEREICH --- - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppTheme.surfaceColor, // Solider Hintergrund - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.5), - blurRadius: 10, - offset: const Offset(0, -5), - ), - ], - ), - child: SafeArea( - top: false, - child: SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: _repsCompleted >= currentSet.repsTarget - ? _completeSet - : null, - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), - backgroundColor: AppTheme.primaryColor, - foregroundColor: Colors.black, - ), - child: const Text( - 'COMPLETE SET', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - letterSpacing: 1.5), - ), - ), - ), - ), - ), - ], - ); - } - // Widget _buildWorkoutScreen( - // Exercise currentExercise, - // WorkoutSet currentSet, - // PlateLoadResult plateResult, - // int completedHP, - // int totalHP, - // ) { - // return Column( - // children: [ - // Container( - // padding: const EdgeInsets.all(16), - // color: AppTheme.surfaceColor, - // child: Column( - // children: [ - // Text( - // 'Wave ${_currentExerciseIndex + 1}/${_exercises.length}', - // style: Theme.of(context).textTheme.titleMedium, - // ), - // const SizedBox(height: 8), - // EnemyHPBar( - // current: totalHP - completedHP, - // max: totalHP, - // ), - // ], - // ), - // ), - // Expanded( - // child: SingleChildScrollView( - // padding: const EdgeInsets.all(24), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.stretch, - // children: [ - // Text( - // currentExercise.exerciseName, - // style: Theme.of(context).textTheme.displayMedium, - // textAlign: TextAlign.center, - // ), - // const SizedBox(height: 8), - // Text( - // 'Set ${_currentSetIndex + 1}/${currentExercise.sets.length}', - // style: Theme.of(context).textTheme.titleLarge, - // textAlign: TextAlign.center, - // ), - // const SizedBox(height: 24), - // Card( - // child: Padding( - // padding: const EdgeInsets.all(16), - // child: Column( - // children: [ - // Text('Target', - // style: Theme.of(context).textTheme.bodyMedium), - // const SizedBox(height: 8), - // Text( - // '${currentSet.targetWeightTotal.toStringAsFixed(1)} kg', - // style: Theme.of(context) - // .textTheme - // .displayMedium - // ?.copyWith(color: AppTheme.primaryColor), - // ), - // Text( - // '${currentSet.targetPercentage}% TM × ${currentSet.repsTarget} reps${currentSet.isAmrap ? '+' : ''}', - // style: Theme.of(context).textTheme.bodyMedium, - // ), - // ], - // ), - // ), - // ), - // const SizedBox(height: 24), - // if (plateResult.bandAssistance != null) - // Container( - // padding: const EdgeInsets.all(16), - // decoration: BoxDecoration( - // color: AppTheme.primaryColor.withOpacity(0.1), - // borderRadius: BorderRadius.circular(16), - // border: Border.all(color: AppTheme.primaryColor), - // ), - // child: Column( - // children: [ - // const Icon(Icons.help_outline, - // size: 48, color: AppTheme.primaryColor), - // const SizedBox(height: 8), - // Text( - // 'ASSISTANCE NEEDED', - // style: - // Theme.of(context).textTheme.titleMedium?.copyWith( - // color: AppTheme.primaryColor, - // fontWeight: FontWeight.bold, - // ), - // ), - // const SizedBox(height: 8), - // Text( - // 'Use ${plateResult.bandAssistance} Band', - // style: Theme.of(context).textTheme.headlineSmall, - // ), - // Text( - // '(approx. -${(plateResult.totalAchieved - currentSet.targetWeightTotal).abs().toStringAsFixed(1)} kg)', - // style: Theme.of(context).textTheme.bodySmall, - // ), - // ], - // ), - // ) - // else - // PlateVisualizer( - // plateConfiguration: plateResult.plateConfiguration, - // isTwoSided: currentExercise.exerciseId == 'squat', - // exerciseName: currentExercise.exerciseName, - // ), - // const SizedBox(height: 32), - // Text( - // 'Reps Completed', - // style: Theme.of(context).textTheme.titleLarge, - // textAlign: TextAlign.center, - // ), - // const SizedBox(height: 16), - // Row( - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // IconButton( - // icon: const Icon(Icons.remove_circle), - // iconSize: 48, - // color: AppTheme.primaryColor, - // onPressed: _repsCompleted > 0 - // ? () => setState(() => _repsCompleted--) - // : null, - // ), - // const SizedBox(width: 24), - // SizedBox( - // width: 100, - // child: Text( - // _repsCompleted.toString(), - // style: Theme.of(context) - // .textTheme - // .displayLarge - // ?.copyWith( - // fontSize: 64, color: AppTheme.primaryColor), - // textAlign: TextAlign.center, - // ), - // ), - // const SizedBox(width: 24), - // IconButton( - // icon: const Icon(Icons.add_circle), - // iconSize: 48, - // color: AppTheme.primaryColor, - // onPressed: () => setState(() => _repsCompleted++), - // ), - // ], - // ), - // if (currentSet.isAmrap) - // Padding( - // padding: const EdgeInsets.only(top: 8), - // child: Text( - // 'AMRAP - Go for max reps!', - // style: Theme.of(context).textTheme.bodyMedium?.copyWith( - // color: AppTheme.secondaryColor, - // fontWeight: FontWeight.bold), - // textAlign: TextAlign.center, - // ), - // ), - // const SizedBox(height: 32), - // ElevatedButton( - // onPressed: _repsCompleted >= currentSet.repsTarget - // ? _completeSet - // : null, - // style: ElevatedButton.styleFrom( - // padding: const EdgeInsets.symmetric(vertical: 20)), - // child: const Text('COMPLETE SET'), - // ), - // ], - // ), - // ), - // ), - // ], - // ); - // } - - String _formatTime(int seconds) { - final minutes = seconds ~/ 60; - final secs = seconds % 60; - return '${minutes.toString().padLeft(2, '0')}:${secs.toString().padLeft(2, '0')}'; - } -} diff --git a/lib/src/features/workout_runner/presentation/widgets/emom_timer_widget.dart b/lib/src/features/workout_runner/presentation/widgets/emom_timer_widget.dart new file mode 100644 index 0000000..e81a43e --- /dev/null +++ b/lib/src/features/workout_runner/presentation/widgets/emom_timer_widget.dart @@ -0,0 +1,250 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import '../../../../core/theme/app_theme.dart'; +import 'package:audioplayers/audioplayers.dart'; +import '../../../../core/constants/asset_paths.dart'; + +class EmomTimerWidget extends StatefulWidget { + final int intervalSeconds; + final int totalSets; + final int currentSet; + final VoidCallback onSetComplete; + final VoidCallback onWorkoutComplete; + + const EmomTimerWidget({ + super.key, + required this.intervalSeconds, + required this.totalSets, + required this.currentSet, + required this.onSetComplete, + required this.onWorkoutComplete, + }); + + @override + State createState() => _EmomTimerWidgetState(); +} + +class _EmomTimerWidgetState extends State + with TickerProviderStateMixin { + Timer? _timer; + late int _secondsRemaining; + bool _isRunning = false; + late AnimationController _pulseController; + late AudioPlayer _audioPlayer; + + @override + void initState() { + super.initState(); + _secondsRemaining = widget.intervalSeconds; + _audioPlayer = AudioPlayer(); + + _audioPlayer.setAudioContext( + AudioContext( + android: AudioContextAndroid( + isSpeakerphoneOn: false, + stayAwake: false, + contentType: AndroidContentType.sonification, + usageType: AndroidUsageType.notificationEvent, + audioFocus: AndroidAudioFocus.none, + ), + // iOS: AudioContextIOS( + // category: AVAudioSessionCategory.ambient, + // options: [ + // AVAudioSessionOptions.mixWithOthers, + // ], + // ), + ), + ); + + _pulseController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 500), + lowerBound: 1.0, + upperBound: 1.1, + ); + } + + @override + void dispose() { + _timer?.cancel(); + _pulseController.dispose(); + _audioPlayer.dispose(); + super.dispose(); + } + + Future _playSound(bool isLong) async { + try { + final path = isLong ? 'audio/beep_long.ogg' : 'audio/beep_short.ogg'; + + if (_audioPlayer.state == PlayerState.playing) { + await _audioPlayer.stop(); + } + await _audioPlayer.play(AssetSource(path)); + } catch (e) { + debugPrint('Audio error: $e'); + } + } + + void _startTimer() { + setState(() => _isRunning = true); + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + if (_secondsRemaining > 0) { + setState(() => _secondsRemaining--); + if (_secondsRemaining <= 3) { + _pulseController.forward().then((_) => _pulseController.reverse()); + _playSound(false); + } + } else { + _playSound(true); + _handleRoundComplete(); + } + }); + } + + void _handleRoundComplete() { + if (widget.currentSet < widget.totalSets) { + widget.onSetComplete(); + setState(() { + _secondsRemaining = widget.intervalSeconds; + }); + } else { + _timer?.cancel(); + setState(() => _isRunning = false); + widget.onWorkoutComplete(); + } + } + + void _pauseTimer() { + _timer?.cancel(); + setState(() => _isRunning = false); + } + + String _formatTime(int seconds) { + final m = seconds ~/ 60; + final s = seconds % 60; + return '${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}'; + } + + @override + Widget build(BuildContext context) { + final progress = 1.0 - (_secondsRemaining / widget.intervalSeconds); + + return Column( + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: AppTheme.primaryColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: AppTheme.primaryColor), + ), + child: Text( + 'ROUND ${widget.currentSet} / ${widget.totalSets}', + style: const TextStyle( + color: AppTheme.primaryColor, + fontWeight: FontWeight.bold, + letterSpacing: 1.2, + ), + ), + ), + const SizedBox(height: 32), + ScaleTransition( + scale: _pulseController, + child: SizedBox( + width: 240, + height: 240, + child: Stack( + alignment: Alignment.center, + children: [ + SizedBox( + width: 240, + height: 240, + child: CircularProgressIndicator( + value: 1.0, + strokeWidth: 12, + color: Colors.white10, + ), + ), + SizedBox( + width: 240, + height: 240, + child: CircularProgressIndicator( + value: progress, + strokeWidth: 12, + color: _secondsRemaining <= 3 + ? AppTheme.errorColor + : AppTheme.primaryColor, + strokeCap: StrokeCap.round, + ), + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + _formatTime(_secondsRemaining), + style: Theme.of(context).textTheme.displayLarge?.copyWith( + fontSize: 64, + color: Colors.white, + fontFamily: 'monospace', + ), + ), + if (!_isRunning && + widget.currentSet == 1 && + _secondsRemaining == widget.intervalSeconds) + Text( + 'READY?', + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith(color: Colors.grey), + ) + else if (!_isRunning) + Text( + 'PAUSED', + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith(color: AppTheme.errorColor), + ), + ], + ), + ], + ), + ), + ), + const SizedBox(height: 48), + if (!_isRunning) + SizedBox( + width: double.infinity, + height: 56, + child: ElevatedButton.icon( + onPressed: _startTimer, + icon: const Icon(Icons.play_arrow), + label: Text(widget.currentSet == 1 && + _secondsRemaining == widget.intervalSeconds + ? 'IGNITE ENGINE' + : 'RESUME'), + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.successColor, + foregroundColor: Colors.white, + ), + ), + ) + else + SizedBox( + width: double.infinity, + height: 56, + child: OutlinedButton.icon( + onPressed: _pauseTimer, + icon: const Icon(Icons.pause), + label: const Text('PAUSE'), + style: OutlinedButton.styleFrom( + foregroundColor: AppTheme.errorColor, + side: const BorderSide(color: AppTheme.errorColor), + ), + ), + ), + ], + ); + } +} diff --git a/lib/src/features/workout_runner/presentation/widgets/enemy_hp_bar.dart b/lib/src/features/workout_runner/presentation/widgets/enemy_hp_bar.dart index 08f213f..997eadb 100644 --- a/lib/src/features/workout_runner/presentation/widgets/enemy_hp_bar.dart +++ b/lib/src/features/workout_runner/presentation/widgets/enemy_hp_bar.dart @@ -53,7 +53,7 @@ class EnemyHPBar extends StatelessWidget { color: Colors.red[900], borderRadius: BorderRadius.circular(12), border: Border.all( - color: AppTheme.errorColor.withOpacity(0.5), + color: AppTheme.errorColor.withValues(alpha: 0.5), width: 2, ), ), @@ -72,7 +72,7 @@ class EnemyHPBar extends StatelessWidget { borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( - color: AppTheme.errorColor.withOpacity(0.5), + color: AppTheme.errorColor.withValues(alpha: 0.5), blurRadius: 8, ), ], diff --git a/lib/src/features/workout_runner/presentation/widgets/plate_visualizer.dart b/lib/src/features/workout_runner/presentation/widgets/plate_visualizer.dart index 28941c0..9d95f41 100644 --- a/lib/src/features/workout_runner/presentation/widgets/plate_visualizer.dart +++ b/lib/src/features/workout_runner/presentation/widgets/plate_visualizer.dart @@ -1,3 +1,172 @@ +// import 'package:flutter/material.dart'; +// import '../../../../core/theme/app_theme.dart'; +// import '../../../../core/constants/asset_paths.dart'; + +// class PlateVisualizer extends StatelessWidget { +// final List plateConfiguration; +// final bool isTwoSided; +// final String exerciseName; + +// const PlateVisualizer({ +// super.key, +// required this.plateConfiguration, +// required this.isTwoSided, +// required this.exerciseName, +// }); + +// Color _getPlateColor(double weight) { +// final colorValue = PlateColors.colors[weight]; +// return colorValue != null ? Color(colorValue) : Colors.grey; +// } + +// Color _getTextColor(double weight) { +// if (weight == 5.0) { +// return Colors.black; +// } +// if (weight <= 2.5) { +// return Colors.white70; +// } +// return Colors.white; +// } + +// @override +// Widget build(BuildContext context) { +// if (plateConfiguration.isEmpty) { +// return Card( +// child: Padding( +// padding: const EdgeInsets.all(32), +// child: Column( +// children: [ +// Icon( +// isTwoSided ? Icons.fitness_center : Icons.accessibility, +// size: 64, +// color: AppTheme.primaryColor.withValues(alpha: 0.5), +// ), +// const SizedBox(height: 16), +// Text( +// isTwoSided ? 'Bar Only' : 'Bodyweight Only', +// style: Theme.of(context) +// .textTheme +// .titleLarge +// ?.copyWith(color: AppTheme.textSecondary), +// ), +// ], +// ), +// ), +// ); +// } + +// return Card( +// child: Padding( +// padding: const EdgeInsets.all(16), +// child: Column( +// children: [ +// Text( +// isTwoSided ? 'Load Per Side' : 'Load on Belt', +// style: Theme.of(context).textTheme.titleMedium, +// ), +// const SizedBox(height: 16), +// if (isTwoSided) _buildBarbellView() else _buildBeltView(), +// const SizedBox(height: 16), +// Text( +// 'Total: ${plateConfiguration.fold(0, (sum, p) => sum + p).toStringAsFixed(1)} kg ${isTwoSided ? 'per side' : ''}', +// style: Theme.of(context).textTheme.bodyMedium?.copyWith( +// color: AppTheme.primaryColor, +// ), +// ), +// ], +// ), +// ), +// ); +// } + +// Widget _buildBarbellView() { +// return SizedBox( +// height: 120, +// child: Row( +// mainAxisAlignment: MainAxisAlignment.center, +// crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// Container( +// width: 8, +// height: 80, +// color: Colors.grey[800], +// ), +// ...plateConfiguration.map((weight) { +// final size = _getPlateSize(weight); +// return Container( +// width: 20, +// height: size, +// margin: const EdgeInsets.symmetric(horizontal: 2), +// decoration: BoxDecoration( +// color: _getPlateColor(weight), +// border: Border.all(color: Colors.white24, width: 2), +// borderRadius: BorderRadius.circular(4), +// ), +// ); +// }).toList(), +// Container( +// width: 40, +// height: 20, +// decoration: BoxDecoration( +// color: Colors.grey[700], +// borderRadius: BorderRadius.circular(4), +// ), +// child: Center( +// child: Container( +// width: 30, +// height: 10, +// decoration: BoxDecoration( +// color: Colors.grey[600], +// borderRadius: BorderRadius.circular(2), +// ), +// ), +// ), +// ), +// ], +// ), +// ); +// } + +// Widget _buildBeltView() { +// return Wrap( +// spacing: 8, +// runSpacing: 8, +// alignment: WrapAlignment.center, +// children: plateConfiguration.map((weight) { +// return Container( +// width: _getPlateSize(weight) * 0.8, +// height: _getPlateSize(weight) * 0.8, +// decoration: BoxDecoration( +// color: _getPlateColor(weight), +// shape: BoxShape.circle, +// border: Border.all(color: Colors.white24, width: 3), +// ), +// child: Center( +// child: Text( +// weight == weight.toInt() +// ? '${weight.toInt()}' +// : weight.toStringAsFixed(2), +// style: const TextStyle( +// color: Colors.white, +// fontWeight: FontWeight.bold, +// fontSize: 14, +// ), +// ), +// ), +// ); +// }).toList(), +// ); +// } + +// double _getPlateSize(double weight) { +// if (weight >= 20) return 120.0; +// if (weight >= 10) return 100.0; +// if (weight >= 5) return 80.0; +// return 60.0; +// } +// } + import 'package:flutter/material.dart'; import '../../../../core/theme/app_theme.dart'; import '../../../../core/constants/asset_paths.dart'; @@ -19,6 +188,16 @@ class PlateVisualizer extends StatelessWidget { return colorValue != null ? Color(colorValue) : Colors.grey; } + Color _getTextColor(double weight) { + if (weight == 5.0) { + return Colors.black; + } + if (weight <= 2.5) { + return Colors.white70; + } + return Colors.white; + } + @override Widget build(BuildContext context) { if (plateConfiguration.isEmpty) { @@ -30,7 +209,7 @@ class PlateVisualizer extends StatelessWidget { Icon( isTwoSided ? Icons.fitness_center : Icons.accessibility, size: 64, - color: AppTheme.primaryColor.withOpacity(0.5), + color: AppTheme.primaryColor.withValues(alpha: 0.5), ), const SizedBox(height: 16), Text( @@ -59,7 +238,7 @@ class PlateVisualizer extends StatelessWidget { if (isTwoSided) _buildBarbellView() else _buildBeltView(), const SizedBox(height: 16), Text( - 'Total: ${plateConfiguration.fold(0, (sum, p) => sum + p).toStringAsFixed(1)} kg ${isTwoSided ? 'per side' : ''}', + 'Total: ${plateConfiguration.fold(0, (sum, p) => sum + p).toStringAsFixed(2)} kg ${isTwoSided ? 'per side' : ''}', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: AppTheme.primaryColor, ), @@ -137,8 +316,8 @@ class PlateVisualizer extends StatelessWidget { weight == weight.toInt() ? '${weight.toInt()}' : weight.toStringAsFixed(2), - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: _getTextColor(weight), fontWeight: FontWeight.bold, fontSize: 14, ), diff --git a/lib/src/shared/data/local/app_database.dart b/lib/src/shared/data/local/app_database.dart new file mode 100644 index 0000000..ccc097f --- /dev/null +++ b/lib/src/shared/data/local/app_database.dart @@ -0,0 +1,37 @@ +import 'dart:io'; +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as p; +import 'tables.dart'; +import 'converters/json_converter.dart'; + +part 'app_database.g.dart'; + +@DriftDatabase(tables: [Users, Cycles, Workouts, Quests]) +class AppDatabase extends _$AppDatabase { + AppDatabase() : super(_openConnection()); + + @override + int get schemaVersion => 3; + + @override + MigrationStrategy get migration => MigrationStrategy( + onUpgrade: (Migrator m, int from, int to) async { + if (from < 2) { + await m.createTable(quests); + } + if (from < 3) { + await m.addColumn(users, users.exerciseVariants); + } + }, + ); +} + +LazyDatabase _openConnection() { + return LazyDatabase(() async { + final dbFolder = await getApplicationDocumentsDirectory(); + final file = File(p.join(dbFolder.path, 'slrpg.sqlite')); + return NativeDatabase.createInBackground(file); + }); +} diff --git a/lib/src/shared/data/local/app_database.g.dart b/lib/src/shared/data/local/app_database.g.dart new file mode 100644 index 0000000..594744f --- /dev/null +++ b/lib/src/shared/data/local/app_database.g.dart @@ -0,0 +1,3657 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_database.dart'; + +// ignore_for_file: type=lint +class $UsersTable extends Users with TableInfo<$UsersTable, UserCollection> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $UsersTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _serverIdMeta = + const VerificationMeta('serverId'); + @override + late final GeneratedColumn serverId = GeneratedColumn( + 'server_id', aliasedName, true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE')); + static const VerificationMeta _emailMeta = const VerificationMeta('email'); + @override + late final GeneratedColumn email = GeneratedColumn( + 'email', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('')); + static const VerificationMeta _xpMeta = const VerificationMeta('xp'); + @override + late final GeneratedColumn xp = GeneratedColumn( + 'xp', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + static const VerificationMeta _levelMeta = const VerificationMeta('level'); + @override + late final GeneratedColumn level = GeneratedColumn( + 'level', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(1)); + static const VerificationMeta _currentBodyweightMeta = + const VerificationMeta('currentBodyweight'); + @override + late final GeneratedColumn currentBodyweight = + GeneratedColumn('current_bodyweight', aliasedName, false, + type: DriftSqlType.double, + requiredDuringInsert: false, + defaultValue: const Constant(70.0)); + @override + late final GeneratedColumnWithTypeConverter?, String> + exerciseVariants = GeneratedColumn( + 'exercise_variants', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false) + .withConverter?>( + $UsersTable.$converterexerciseVariantsn); + @override + late final GeneratedColumnWithTypeConverter?, String> + inventorySettings = GeneratedColumn( + 'inventory_settings', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false) + .withConverter?>( + $UsersTable.$converterinventorySettingsn); + @override + late final GeneratedColumnWithTypeConverter?, String> + avatarConfig = GeneratedColumn('avatar_config', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false) + .withConverter?>( + $UsersTable.$converteravatarConfign); + static const VerificationMeta _lastSyncAtMeta = + const VerificationMeta('lastSyncAt'); + @override + late final GeneratedColumn lastSyncAt = GeneratedColumn( + 'last_sync_at', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _isDirtyMeta = + const VerificationMeta('isDirty'); + @override + late final GeneratedColumn isDirty = GeneratedColumn( + 'is_dirty', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("is_dirty" IN (0, 1))'), + defaultValue: const Constant(false)); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => [ + id, + serverId, + email, + xp, + level, + currentBodyweight, + exerciseVariants, + inventorySettings, + avatarConfig, + lastSyncAt, + isDirty, + createdAt, + updatedAt + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'users'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('server_id')) { + context.handle(_serverIdMeta, + serverId.isAcceptableOrUnknown(data['server_id']!, _serverIdMeta)); + } + if (data.containsKey('email')) { + context.handle( + _emailMeta, email.isAcceptableOrUnknown(data['email']!, _emailMeta)); + } + if (data.containsKey('xp')) { + context.handle(_xpMeta, xp.isAcceptableOrUnknown(data['xp']!, _xpMeta)); + } + if (data.containsKey('level')) { + context.handle( + _levelMeta, level.isAcceptableOrUnknown(data['level']!, _levelMeta)); + } + if (data.containsKey('current_bodyweight')) { + context.handle( + _currentBodyweightMeta, + currentBodyweight.isAcceptableOrUnknown( + data['current_bodyweight']!, _currentBodyweightMeta)); + } + if (data.containsKey('last_sync_at')) { + context.handle( + _lastSyncAtMeta, + lastSyncAt.isAcceptableOrUnknown( + data['last_sync_at']!, _lastSyncAtMeta)); + } + if (data.containsKey('is_dirty')) { + context.handle(_isDirtyMeta, + isDirty.isAcceptableOrUnknown(data['is_dirty']!, _isDirtyMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + UserCollection map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserCollection( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + serverId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}server_id']), + email: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}email'])!, + xp: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}xp'])!, + level: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}level'])!, + currentBodyweight: attachedDatabase.typeMapping.read( + DriftSqlType.double, data['${effectivePrefix}current_bodyweight'])!, + exerciseVariants: $UsersTable.$converterexerciseVariantsn.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, + data['${effectivePrefix}exercise_variants'])), + inventorySettings: $UsersTable.$converterinventorySettingsn.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, + data['${effectivePrefix}inventory_settings'])), + avatarConfig: $UsersTable.$converteravatarConfign.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}avatar_config'])), + lastSyncAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}last_sync_at']), + isDirty: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_dirty'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + ); + } + + @override + $UsersTable createAlias(String alias) { + return $UsersTable(attachedDatabase, alias); + } + + static TypeConverter, String> + $converterexerciseVariants = const MapConverter(); + static TypeConverter?, String?> + $converterexerciseVariantsn = + NullAwareTypeConverter.wrap($converterexerciseVariants); + static TypeConverter, String> + $converterinventorySettings = const MapConverter(); + static TypeConverter?, String?> + $converterinventorySettingsn = + NullAwareTypeConverter.wrap($converterinventorySettings); + static TypeConverter, String> $converteravatarConfig = + const MapConverter(); + static TypeConverter?, String?> $converteravatarConfign = + NullAwareTypeConverter.wrap($converteravatarConfig); +} + +class UserCollection extends DataClass implements Insertable { + final int id; + final String? serverId; + final String email; + final int xp; + final int level; + final double currentBodyweight; + final Map? exerciseVariants; + final Map? inventorySettings; + final Map? avatarConfig; + final DateTime? lastSyncAt; + final bool isDirty; + final DateTime createdAt; + final DateTime updatedAt; + const UserCollection( + {required this.id, + this.serverId, + required this.email, + required this.xp, + required this.level, + required this.currentBodyweight, + this.exerciseVariants, + this.inventorySettings, + this.avatarConfig, + this.lastSyncAt, + required this.isDirty, + required this.createdAt, + required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || serverId != null) { + map['server_id'] = Variable(serverId); + } + map['email'] = Variable(email); + map['xp'] = Variable(xp); + map['level'] = Variable(level); + map['current_bodyweight'] = Variable(currentBodyweight); + if (!nullToAbsent || exerciseVariants != null) { + map['exercise_variants'] = Variable( + $UsersTable.$converterexerciseVariantsn.toSql(exerciseVariants)); + } + if (!nullToAbsent || inventorySettings != null) { + map['inventory_settings'] = Variable( + $UsersTable.$converterinventorySettingsn.toSql(inventorySettings)); + } + if (!nullToAbsent || avatarConfig != null) { + map['avatar_config'] = Variable( + $UsersTable.$converteravatarConfign.toSql(avatarConfig)); + } + if (!nullToAbsent || lastSyncAt != null) { + map['last_sync_at'] = Variable(lastSyncAt); + } + map['is_dirty'] = Variable(isDirty); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + UsersCompanion toCompanion(bool nullToAbsent) { + return UsersCompanion( + id: Value(id), + serverId: serverId == null && nullToAbsent + ? const Value.absent() + : Value(serverId), + email: Value(email), + xp: Value(xp), + level: Value(level), + currentBodyweight: Value(currentBodyweight), + exerciseVariants: exerciseVariants == null && nullToAbsent + ? const Value.absent() + : Value(exerciseVariants), + inventorySettings: inventorySettings == null && nullToAbsent + ? const Value.absent() + : Value(inventorySettings), + avatarConfig: avatarConfig == null && nullToAbsent + ? const Value.absent() + : Value(avatarConfig), + lastSyncAt: lastSyncAt == null && nullToAbsent + ? const Value.absent() + : Value(lastSyncAt), + isDirty: Value(isDirty), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + ); + } + + factory UserCollection.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserCollection( + id: serializer.fromJson(json['id']), + serverId: serializer.fromJson(json['serverId']), + email: serializer.fromJson(json['email']), + xp: serializer.fromJson(json['xp']), + level: serializer.fromJson(json['level']), + currentBodyweight: serializer.fromJson(json['currentBodyweight']), + exerciseVariants: + serializer.fromJson?>(json['exerciseVariants']), + inventorySettings: + serializer.fromJson?>(json['inventorySettings']), + avatarConfig: + serializer.fromJson?>(json['avatarConfig']), + lastSyncAt: serializer.fromJson(json['lastSyncAt']), + isDirty: serializer.fromJson(json['isDirty']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'serverId': serializer.toJson(serverId), + 'email': serializer.toJson(email), + 'xp': serializer.toJson(xp), + 'level': serializer.toJson(level), + 'currentBodyweight': serializer.toJson(currentBodyweight), + 'exerciseVariants': + serializer.toJson?>(exerciseVariants), + 'inventorySettings': + serializer.toJson?>(inventorySettings), + 'avatarConfig': serializer.toJson?>(avatarConfig), + 'lastSyncAt': serializer.toJson(lastSyncAt), + 'isDirty': serializer.toJson(isDirty), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + UserCollection copyWith( + {int? id, + Value serverId = const Value.absent(), + String? email, + int? xp, + int? level, + double? currentBodyweight, + Value?> exerciseVariants = const Value.absent(), + Value?> inventorySettings = const Value.absent(), + Value?> avatarConfig = const Value.absent(), + Value lastSyncAt = const Value.absent(), + bool? isDirty, + DateTime? createdAt, + DateTime? updatedAt}) => + UserCollection( + id: id ?? this.id, + serverId: serverId.present ? serverId.value : this.serverId, + email: email ?? this.email, + xp: xp ?? this.xp, + level: level ?? this.level, + currentBodyweight: currentBodyweight ?? this.currentBodyweight, + exerciseVariants: exerciseVariants.present + ? exerciseVariants.value + : this.exerciseVariants, + inventorySettings: inventorySettings.present + ? inventorySettings.value + : this.inventorySettings, + avatarConfig: + avatarConfig.present ? avatarConfig.value : this.avatarConfig, + lastSyncAt: lastSyncAt.present ? lastSyncAt.value : this.lastSyncAt, + isDirty: isDirty ?? this.isDirty, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + UserCollection copyWithCompanion(UsersCompanion data) { + return UserCollection( + id: data.id.present ? data.id.value : this.id, + serverId: data.serverId.present ? data.serverId.value : this.serverId, + email: data.email.present ? data.email.value : this.email, + xp: data.xp.present ? data.xp.value : this.xp, + level: data.level.present ? data.level.value : this.level, + currentBodyweight: data.currentBodyweight.present + ? data.currentBodyweight.value + : this.currentBodyweight, + exerciseVariants: data.exerciseVariants.present + ? data.exerciseVariants.value + : this.exerciseVariants, + inventorySettings: data.inventorySettings.present + ? data.inventorySettings.value + : this.inventorySettings, + avatarConfig: data.avatarConfig.present + ? data.avatarConfig.value + : this.avatarConfig, + lastSyncAt: + data.lastSyncAt.present ? data.lastSyncAt.value : this.lastSyncAt, + isDirty: data.isDirty.present ? data.isDirty.value : this.isDirty, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('UserCollection(') + ..write('id: $id, ') + ..write('serverId: $serverId, ') + ..write('email: $email, ') + ..write('xp: $xp, ') + ..write('level: $level, ') + ..write('currentBodyweight: $currentBodyweight, ') + ..write('exerciseVariants: $exerciseVariants, ') + ..write('inventorySettings: $inventorySettings, ') + ..write('avatarConfig: $avatarConfig, ') + ..write('lastSyncAt: $lastSyncAt, ') + ..write('isDirty: $isDirty, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + serverId, + email, + xp, + level, + currentBodyweight, + exerciseVariants, + inventorySettings, + avatarConfig, + lastSyncAt, + isDirty, + createdAt, + updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserCollection && + other.id == this.id && + other.serverId == this.serverId && + other.email == this.email && + other.xp == this.xp && + other.level == this.level && + other.currentBodyweight == this.currentBodyweight && + other.exerciseVariants == this.exerciseVariants && + other.inventorySettings == this.inventorySettings && + other.avatarConfig == this.avatarConfig && + other.lastSyncAt == this.lastSyncAt && + other.isDirty == this.isDirty && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class UsersCompanion extends UpdateCompanion { + final Value id; + final Value serverId; + final Value email; + final Value xp; + final Value level; + final Value currentBodyweight; + final Value?> exerciseVariants; + final Value?> inventorySettings; + final Value?> avatarConfig; + final Value lastSyncAt; + final Value isDirty; + final Value createdAt; + final Value updatedAt; + const UsersCompanion({ + this.id = const Value.absent(), + this.serverId = const Value.absent(), + this.email = const Value.absent(), + this.xp = const Value.absent(), + this.level = const Value.absent(), + this.currentBodyweight = const Value.absent(), + this.exerciseVariants = const Value.absent(), + this.inventorySettings = const Value.absent(), + this.avatarConfig = const Value.absent(), + this.lastSyncAt = const Value.absent(), + this.isDirty = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + UsersCompanion.insert({ + this.id = const Value.absent(), + this.serverId = const Value.absent(), + this.email = const Value.absent(), + this.xp = const Value.absent(), + this.level = const Value.absent(), + this.currentBodyweight = const Value.absent(), + this.exerciseVariants = const Value.absent(), + this.inventorySettings = const Value.absent(), + this.avatarConfig = const Value.absent(), + this.lastSyncAt = const Value.absent(), + this.isDirty = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + static Insertable custom({ + Expression? id, + Expression? serverId, + Expression? email, + Expression? xp, + Expression? level, + Expression? currentBodyweight, + Expression? exerciseVariants, + Expression? inventorySettings, + Expression? avatarConfig, + Expression? lastSyncAt, + Expression? isDirty, + Expression? createdAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (serverId != null) 'server_id': serverId, + if (email != null) 'email': email, + if (xp != null) 'xp': xp, + if (level != null) 'level': level, + if (currentBodyweight != null) 'current_bodyweight': currentBodyweight, + if (exerciseVariants != null) 'exercise_variants': exerciseVariants, + if (inventorySettings != null) 'inventory_settings': inventorySettings, + if (avatarConfig != null) 'avatar_config': avatarConfig, + if (lastSyncAt != null) 'last_sync_at': lastSyncAt, + if (isDirty != null) 'is_dirty': isDirty, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + UsersCompanion copyWith( + {Value? id, + Value? serverId, + Value? email, + Value? xp, + Value? level, + Value? currentBodyweight, + Value?>? exerciseVariants, + Value?>? inventorySettings, + Value?>? avatarConfig, + Value? lastSyncAt, + Value? isDirty, + Value? createdAt, + Value? updatedAt}) { + return UsersCompanion( + id: id ?? this.id, + serverId: serverId ?? this.serverId, + email: email ?? this.email, + xp: xp ?? this.xp, + level: level ?? this.level, + currentBodyweight: currentBodyweight ?? this.currentBodyweight, + exerciseVariants: exerciseVariants ?? this.exerciseVariants, + inventorySettings: inventorySettings ?? this.inventorySettings, + avatarConfig: avatarConfig ?? this.avatarConfig, + lastSyncAt: lastSyncAt ?? this.lastSyncAt, + isDirty: isDirty ?? this.isDirty, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (serverId.present) { + map['server_id'] = Variable(serverId.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (xp.present) { + map['xp'] = Variable(xp.value); + } + if (level.present) { + map['level'] = Variable(level.value); + } + if (currentBodyweight.present) { + map['current_bodyweight'] = Variable(currentBodyweight.value); + } + if (exerciseVariants.present) { + map['exercise_variants'] = Variable($UsersTable + .$converterexerciseVariantsn + .toSql(exerciseVariants.value)); + } + if (inventorySettings.present) { + map['inventory_settings'] = Variable($UsersTable + .$converterinventorySettingsn + .toSql(inventorySettings.value)); + } + if (avatarConfig.present) { + map['avatar_config'] = Variable( + $UsersTable.$converteravatarConfign.toSql(avatarConfig.value)); + } + if (lastSyncAt.present) { + map['last_sync_at'] = Variable(lastSyncAt.value); + } + if (isDirty.present) { + map['is_dirty'] = Variable(isDirty.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UsersCompanion(') + ..write('id: $id, ') + ..write('serverId: $serverId, ') + ..write('email: $email, ') + ..write('xp: $xp, ') + ..write('level: $level, ') + ..write('currentBodyweight: $currentBodyweight, ') + ..write('exerciseVariants: $exerciseVariants, ') + ..write('inventorySettings: $inventorySettings, ') + ..write('avatarConfig: $avatarConfig, ') + ..write('lastSyncAt: $lastSyncAt, ') + ..write('isDirty: $isDirty, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class $CyclesTable extends Cycles + with TableInfo<$CyclesTable, CycleCollection> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $CyclesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _serverIdMeta = + const VerificationMeta('serverId'); + @override + late final GeneratedColumn serverId = GeneratedColumn( + 'server_id', aliasedName, true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE')); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _cycleNumberMeta = + const VerificationMeta('cycleNumber'); + @override + late final GeneratedColumn cycleNumber = GeneratedColumn( + 'cycle_number', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _startDateMeta = + const VerificationMeta('startDate'); + @override + late final GeneratedColumn startDate = GeneratedColumn( + 'start_date', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _endDateMeta = + const VerificationMeta('endDate'); + @override + late final GeneratedColumn endDate = GeneratedColumn( + 'end_date', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _isActiveMeta = + const VerificationMeta('isActive'); + @override + late final GeneratedColumn isActive = GeneratedColumn( + 'is_active', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("is_active" IN (0, 1))'), + defaultValue: const Constant(true)); + @override + late final GeneratedColumnWithTypeConverter, String> + trainingMaxes = GeneratedColumn( + 'training_maxes', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true) + .withConverter>( + $CyclesTable.$convertertrainingMaxes); + static const VerificationMeta _isDirtyMeta = + const VerificationMeta('isDirty'); + @override + late final GeneratedColumn isDirty = GeneratedColumn( + 'is_dirty', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("is_dirty" IN (0, 1))'), + defaultValue: const Constant(false)); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => [ + id, + serverId, + userId, + cycleNumber, + startDate, + endDate, + isActive, + trainingMaxes, + isDirty, + createdAt, + updatedAt + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'cycles'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('server_id')) { + context.handle(_serverIdMeta, + serverId.isAcceptableOrUnknown(data['server_id']!, _serverIdMeta)); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('cycle_number')) { + context.handle( + _cycleNumberMeta, + cycleNumber.isAcceptableOrUnknown( + data['cycle_number']!, _cycleNumberMeta)); + } else if (isInserting) { + context.missing(_cycleNumberMeta); + } + if (data.containsKey('start_date')) { + context.handle(_startDateMeta, + startDate.isAcceptableOrUnknown(data['start_date']!, _startDateMeta)); + } else if (isInserting) { + context.missing(_startDateMeta); + } + if (data.containsKey('end_date')) { + context.handle(_endDateMeta, + endDate.isAcceptableOrUnknown(data['end_date']!, _endDateMeta)); + } + if (data.containsKey('is_active')) { + context.handle(_isActiveMeta, + isActive.isAcceptableOrUnknown(data['is_active']!, _isActiveMeta)); + } + if (data.containsKey('is_dirty')) { + context.handle(_isDirtyMeta, + isDirty.isAcceptableOrUnknown(data['is_dirty']!, _isDirtyMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + CycleCollection map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return CycleCollection( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + serverId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}server_id']), + userId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}user_id'])!, + cycleNumber: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}cycle_number'])!, + startDate: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}start_date'])!, + endDate: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}end_date']), + isActive: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_active'])!, + trainingMaxes: $CyclesTable.$convertertrainingMaxes.fromSql( + attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}training_maxes'])!), + isDirty: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_dirty'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + ); + } + + @override + $CyclesTable createAlias(String alias) { + return $CyclesTable(attachedDatabase, alias); + } + + static TypeConverter, String> $convertertrainingMaxes = + const MapConverter(); +} + +class CycleCollection extends DataClass implements Insertable { + final int id; + final String? serverId; + final String userId; + final int cycleNumber; + final DateTime startDate; + final DateTime? endDate; + final bool isActive; + final Map trainingMaxes; + final bool isDirty; + final DateTime createdAt; + final DateTime updatedAt; + const CycleCollection( + {required this.id, + this.serverId, + required this.userId, + required this.cycleNumber, + required this.startDate, + this.endDate, + required this.isActive, + required this.trainingMaxes, + required this.isDirty, + required this.createdAt, + required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || serverId != null) { + map['server_id'] = Variable(serverId); + } + map['user_id'] = Variable(userId); + map['cycle_number'] = Variable(cycleNumber); + map['start_date'] = Variable(startDate); + if (!nullToAbsent || endDate != null) { + map['end_date'] = Variable(endDate); + } + map['is_active'] = Variable(isActive); + { + map['training_maxes'] = Variable( + $CyclesTable.$convertertrainingMaxes.toSql(trainingMaxes)); + } + map['is_dirty'] = Variable(isDirty); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + CyclesCompanion toCompanion(bool nullToAbsent) { + return CyclesCompanion( + id: Value(id), + serverId: serverId == null && nullToAbsent + ? const Value.absent() + : Value(serverId), + userId: Value(userId), + cycleNumber: Value(cycleNumber), + startDate: Value(startDate), + endDate: endDate == null && nullToAbsent + ? const Value.absent() + : Value(endDate), + isActive: Value(isActive), + trainingMaxes: Value(trainingMaxes), + isDirty: Value(isDirty), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + ); + } + + factory CycleCollection.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return CycleCollection( + id: serializer.fromJson(json['id']), + serverId: serializer.fromJson(json['serverId']), + userId: serializer.fromJson(json['userId']), + cycleNumber: serializer.fromJson(json['cycleNumber']), + startDate: serializer.fromJson(json['startDate']), + endDate: serializer.fromJson(json['endDate']), + isActive: serializer.fromJson(json['isActive']), + trainingMaxes: + serializer.fromJson>(json['trainingMaxes']), + isDirty: serializer.fromJson(json['isDirty']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'serverId': serializer.toJson(serverId), + 'userId': serializer.toJson(userId), + 'cycleNumber': serializer.toJson(cycleNumber), + 'startDate': serializer.toJson(startDate), + 'endDate': serializer.toJson(endDate), + 'isActive': serializer.toJson(isActive), + 'trainingMaxes': serializer.toJson>(trainingMaxes), + 'isDirty': serializer.toJson(isDirty), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + CycleCollection copyWith( + {int? id, + Value serverId = const Value.absent(), + String? userId, + int? cycleNumber, + DateTime? startDate, + Value endDate = const Value.absent(), + bool? isActive, + Map? trainingMaxes, + bool? isDirty, + DateTime? createdAt, + DateTime? updatedAt}) => + CycleCollection( + id: id ?? this.id, + serverId: serverId.present ? serverId.value : this.serverId, + userId: userId ?? this.userId, + cycleNumber: cycleNumber ?? this.cycleNumber, + startDate: startDate ?? this.startDate, + endDate: endDate.present ? endDate.value : this.endDate, + isActive: isActive ?? this.isActive, + trainingMaxes: trainingMaxes ?? this.trainingMaxes, + isDirty: isDirty ?? this.isDirty, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + CycleCollection copyWithCompanion(CyclesCompanion data) { + return CycleCollection( + id: data.id.present ? data.id.value : this.id, + serverId: data.serverId.present ? data.serverId.value : this.serverId, + userId: data.userId.present ? data.userId.value : this.userId, + cycleNumber: + data.cycleNumber.present ? data.cycleNumber.value : this.cycleNumber, + startDate: data.startDate.present ? data.startDate.value : this.startDate, + endDate: data.endDate.present ? data.endDate.value : this.endDate, + isActive: data.isActive.present ? data.isActive.value : this.isActive, + trainingMaxes: data.trainingMaxes.present + ? data.trainingMaxes.value + : this.trainingMaxes, + isDirty: data.isDirty.present ? data.isDirty.value : this.isDirty, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('CycleCollection(') + ..write('id: $id, ') + ..write('serverId: $serverId, ') + ..write('userId: $userId, ') + ..write('cycleNumber: $cycleNumber, ') + ..write('startDate: $startDate, ') + ..write('endDate: $endDate, ') + ..write('isActive: $isActive, ') + ..write('trainingMaxes: $trainingMaxes, ') + ..write('isDirty: $isDirty, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, serverId, userId, cycleNumber, startDate, + endDate, isActive, trainingMaxes, isDirty, createdAt, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is CycleCollection && + other.id == this.id && + other.serverId == this.serverId && + other.userId == this.userId && + other.cycleNumber == this.cycleNumber && + other.startDate == this.startDate && + other.endDate == this.endDate && + other.isActive == this.isActive && + other.trainingMaxes == this.trainingMaxes && + other.isDirty == this.isDirty && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class CyclesCompanion extends UpdateCompanion { + final Value id; + final Value serverId; + final Value userId; + final Value cycleNumber; + final Value startDate; + final Value endDate; + final Value isActive; + final Value> trainingMaxes; + final Value isDirty; + final Value createdAt; + final Value updatedAt; + const CyclesCompanion({ + this.id = const Value.absent(), + this.serverId = const Value.absent(), + this.userId = const Value.absent(), + this.cycleNumber = const Value.absent(), + this.startDate = const Value.absent(), + this.endDate = const Value.absent(), + this.isActive = const Value.absent(), + this.trainingMaxes = const Value.absent(), + this.isDirty = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + CyclesCompanion.insert({ + this.id = const Value.absent(), + this.serverId = const Value.absent(), + required String userId, + required int cycleNumber, + required DateTime startDate, + this.endDate = const Value.absent(), + this.isActive = const Value.absent(), + required Map trainingMaxes, + this.isDirty = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : userId = Value(userId), + cycleNumber = Value(cycleNumber), + startDate = Value(startDate), + trainingMaxes = Value(trainingMaxes); + static Insertable custom({ + Expression? id, + Expression? serverId, + Expression? userId, + Expression? cycleNumber, + Expression? startDate, + Expression? endDate, + Expression? isActive, + Expression? trainingMaxes, + Expression? isDirty, + Expression? createdAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (serverId != null) 'server_id': serverId, + if (userId != null) 'user_id': userId, + if (cycleNumber != null) 'cycle_number': cycleNumber, + if (startDate != null) 'start_date': startDate, + if (endDate != null) 'end_date': endDate, + if (isActive != null) 'is_active': isActive, + if (trainingMaxes != null) 'training_maxes': trainingMaxes, + if (isDirty != null) 'is_dirty': isDirty, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + CyclesCompanion copyWith( + {Value? id, + Value? serverId, + Value? userId, + Value? cycleNumber, + Value? startDate, + Value? endDate, + Value? isActive, + Value>? trainingMaxes, + Value? isDirty, + Value? createdAt, + Value? updatedAt}) { + return CyclesCompanion( + id: id ?? this.id, + serverId: serverId ?? this.serverId, + userId: userId ?? this.userId, + cycleNumber: cycleNumber ?? this.cycleNumber, + startDate: startDate ?? this.startDate, + endDate: endDate ?? this.endDate, + isActive: isActive ?? this.isActive, + trainingMaxes: trainingMaxes ?? this.trainingMaxes, + isDirty: isDirty ?? this.isDirty, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (serverId.present) { + map['server_id'] = Variable(serverId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (cycleNumber.present) { + map['cycle_number'] = Variable(cycleNumber.value); + } + if (startDate.present) { + map['start_date'] = Variable(startDate.value); + } + if (endDate.present) { + map['end_date'] = Variable(endDate.value); + } + if (isActive.present) { + map['is_active'] = Variable(isActive.value); + } + if (trainingMaxes.present) { + map['training_maxes'] = Variable( + $CyclesTable.$convertertrainingMaxes.toSql(trainingMaxes.value)); + } + if (isDirty.present) { + map['is_dirty'] = Variable(isDirty.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CyclesCompanion(') + ..write('id: $id, ') + ..write('serverId: $serverId, ') + ..write('userId: $userId, ') + ..write('cycleNumber: $cycleNumber, ') + ..write('startDate: $startDate, ') + ..write('endDate: $endDate, ') + ..write('isActive: $isActive, ') + ..write('trainingMaxes: $trainingMaxes, ') + ..write('isDirty: $isDirty, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class $WorkoutsTable extends Workouts + with TableInfo<$WorkoutsTable, WorkoutCollection> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $WorkoutsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _serverIdMeta = + const VerificationMeta('serverId'); + @override + late final GeneratedColumn serverId = GeneratedColumn( + 'server_id', aliasedName, true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE')); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _cycleIdMeta = + const VerificationMeta('cycleId'); + @override + late final GeneratedColumn cycleId = GeneratedColumn( + 'cycle_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _weekMeta = const VerificationMeta('week'); + @override + late final GeneratedColumn week = GeneratedColumn( + 'week', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _dayMeta = const VerificationMeta('day'); + @override + late final GeneratedColumn day = GeneratedColumn( + 'day', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _scheduledDateMeta = + const VerificationMeta('scheduledDate'); + @override + late final GeneratedColumn scheduledDate = + GeneratedColumn('scheduled_date', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _completedAtMeta = + const VerificationMeta('completedAt'); + @override + late final GeneratedColumn completedAt = GeneratedColumn( + 'completed_at', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _xpEarnedMeta = + const VerificationMeta('xpEarned'); + @override + late final GeneratedColumn xpEarned = GeneratedColumn( + 'xp_earned', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + @override + late final GeneratedColumnWithTypeConverter, String> exercises = + GeneratedColumn('exercises', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true) + .withConverter>($WorkoutsTable.$converterexercises); + static const VerificationMeta _notesMeta = const VerificationMeta('notes'); + @override + late final GeneratedColumn notes = GeneratedColumn( + 'notes', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('')); + static const VerificationMeta _isDirtyMeta = + const VerificationMeta('isDirty'); + @override + late final GeneratedColumn isDirty = GeneratedColumn( + 'is_dirty', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("is_dirty" IN (0, 1))'), + defaultValue: const Constant(false)); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => [ + id, + serverId, + userId, + cycleId, + week, + day, + scheduledDate, + completedAt, + xpEarned, + exercises, + notes, + isDirty, + createdAt, + updatedAt + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'workouts'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('server_id')) { + context.handle(_serverIdMeta, + serverId.isAcceptableOrUnknown(data['server_id']!, _serverIdMeta)); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('cycle_id')) { + context.handle(_cycleIdMeta, + cycleId.isAcceptableOrUnknown(data['cycle_id']!, _cycleIdMeta)); + } else if (isInserting) { + context.missing(_cycleIdMeta); + } + if (data.containsKey('week')) { + context.handle( + _weekMeta, week.isAcceptableOrUnknown(data['week']!, _weekMeta)); + } else if (isInserting) { + context.missing(_weekMeta); + } + if (data.containsKey('day')) { + context.handle( + _dayMeta, day.isAcceptableOrUnknown(data['day']!, _dayMeta)); + } else if (isInserting) { + context.missing(_dayMeta); + } + if (data.containsKey('scheduled_date')) { + context.handle( + _scheduledDateMeta, + scheduledDate.isAcceptableOrUnknown( + data['scheduled_date']!, _scheduledDateMeta)); + } + if (data.containsKey('completed_at')) { + context.handle( + _completedAtMeta, + completedAt.isAcceptableOrUnknown( + data['completed_at']!, _completedAtMeta)); + } + if (data.containsKey('xp_earned')) { + context.handle(_xpEarnedMeta, + xpEarned.isAcceptableOrUnknown(data['xp_earned']!, _xpEarnedMeta)); + } + if (data.containsKey('notes')) { + context.handle( + _notesMeta, notes.isAcceptableOrUnknown(data['notes']!, _notesMeta)); + } + if (data.containsKey('is_dirty')) { + context.handle(_isDirtyMeta, + isDirty.isAcceptableOrUnknown(data['is_dirty']!, _isDirtyMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + WorkoutCollection map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return WorkoutCollection( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + serverId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}server_id']), + userId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}user_id'])!, + cycleId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}cycle_id'])!, + week: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}week'])!, + day: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}day'])!, + scheduledDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}scheduled_date']), + completedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}completed_at']), + xpEarned: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}xp_earned'])!, + exercises: $WorkoutsTable.$converterexercises.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}exercises'])!), + notes: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}notes'])!, + isDirty: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_dirty'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + ); + } + + @override + $WorkoutsTable createAlias(String alias) { + return $WorkoutsTable(attachedDatabase, alias); + } + + static TypeConverter, String> $converterexercises = + const ListConverter(); +} + +class WorkoutCollection extends DataClass + implements Insertable { + final int id; + final String? serverId; + final String userId; + final String cycleId; + final int week; + final int day; + final DateTime? scheduledDate; + final DateTime? completedAt; + final int xpEarned; + final List exercises; + final String notes; + final bool isDirty; + final DateTime createdAt; + final DateTime updatedAt; + const WorkoutCollection( + {required this.id, + this.serverId, + required this.userId, + required this.cycleId, + required this.week, + required this.day, + this.scheduledDate, + this.completedAt, + required this.xpEarned, + required this.exercises, + required this.notes, + required this.isDirty, + required this.createdAt, + required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || serverId != null) { + map['server_id'] = Variable(serverId); + } + map['user_id'] = Variable(userId); + map['cycle_id'] = Variable(cycleId); + map['week'] = Variable(week); + map['day'] = Variable(day); + if (!nullToAbsent || scheduledDate != null) { + map['scheduled_date'] = Variable(scheduledDate); + } + if (!nullToAbsent || completedAt != null) { + map['completed_at'] = Variable(completedAt); + } + map['xp_earned'] = Variable(xpEarned); + { + map['exercises'] = + Variable($WorkoutsTable.$converterexercises.toSql(exercises)); + } + map['notes'] = Variable(notes); + map['is_dirty'] = Variable(isDirty); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + WorkoutsCompanion toCompanion(bool nullToAbsent) { + return WorkoutsCompanion( + id: Value(id), + serverId: serverId == null && nullToAbsent + ? const Value.absent() + : Value(serverId), + userId: Value(userId), + cycleId: Value(cycleId), + week: Value(week), + day: Value(day), + scheduledDate: scheduledDate == null && nullToAbsent + ? const Value.absent() + : Value(scheduledDate), + completedAt: completedAt == null && nullToAbsent + ? const Value.absent() + : Value(completedAt), + xpEarned: Value(xpEarned), + exercises: Value(exercises), + notes: Value(notes), + isDirty: Value(isDirty), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + ); + } + + factory WorkoutCollection.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return WorkoutCollection( + id: serializer.fromJson(json['id']), + serverId: serializer.fromJson(json['serverId']), + userId: serializer.fromJson(json['userId']), + cycleId: serializer.fromJson(json['cycleId']), + week: serializer.fromJson(json['week']), + day: serializer.fromJson(json['day']), + scheduledDate: serializer.fromJson(json['scheduledDate']), + completedAt: serializer.fromJson(json['completedAt']), + xpEarned: serializer.fromJson(json['xpEarned']), + exercises: serializer.fromJson>(json['exercises']), + notes: serializer.fromJson(json['notes']), + isDirty: serializer.fromJson(json['isDirty']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'serverId': serializer.toJson(serverId), + 'userId': serializer.toJson(userId), + 'cycleId': serializer.toJson(cycleId), + 'week': serializer.toJson(week), + 'day': serializer.toJson(day), + 'scheduledDate': serializer.toJson(scheduledDate), + 'completedAt': serializer.toJson(completedAt), + 'xpEarned': serializer.toJson(xpEarned), + 'exercises': serializer.toJson>(exercises), + 'notes': serializer.toJson(notes), + 'isDirty': serializer.toJson(isDirty), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + WorkoutCollection copyWith( + {int? id, + Value serverId = const Value.absent(), + String? userId, + String? cycleId, + int? week, + int? day, + Value scheduledDate = const Value.absent(), + Value completedAt = const Value.absent(), + int? xpEarned, + List? exercises, + String? notes, + bool? isDirty, + DateTime? createdAt, + DateTime? updatedAt}) => + WorkoutCollection( + id: id ?? this.id, + serverId: serverId.present ? serverId.value : this.serverId, + userId: userId ?? this.userId, + cycleId: cycleId ?? this.cycleId, + week: week ?? this.week, + day: day ?? this.day, + scheduledDate: + scheduledDate.present ? scheduledDate.value : this.scheduledDate, + completedAt: completedAt.present ? completedAt.value : this.completedAt, + xpEarned: xpEarned ?? this.xpEarned, + exercises: exercises ?? this.exercises, + notes: notes ?? this.notes, + isDirty: isDirty ?? this.isDirty, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + WorkoutCollection copyWithCompanion(WorkoutsCompanion data) { + return WorkoutCollection( + id: data.id.present ? data.id.value : this.id, + serverId: data.serverId.present ? data.serverId.value : this.serverId, + userId: data.userId.present ? data.userId.value : this.userId, + cycleId: data.cycleId.present ? data.cycleId.value : this.cycleId, + week: data.week.present ? data.week.value : this.week, + day: data.day.present ? data.day.value : this.day, + scheduledDate: data.scheduledDate.present + ? data.scheduledDate.value + : this.scheduledDate, + completedAt: + data.completedAt.present ? data.completedAt.value : this.completedAt, + xpEarned: data.xpEarned.present ? data.xpEarned.value : this.xpEarned, + exercises: data.exercises.present ? data.exercises.value : this.exercises, + notes: data.notes.present ? data.notes.value : this.notes, + isDirty: data.isDirty.present ? data.isDirty.value : this.isDirty, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('WorkoutCollection(') + ..write('id: $id, ') + ..write('serverId: $serverId, ') + ..write('userId: $userId, ') + ..write('cycleId: $cycleId, ') + ..write('week: $week, ') + ..write('day: $day, ') + ..write('scheduledDate: $scheduledDate, ') + ..write('completedAt: $completedAt, ') + ..write('xpEarned: $xpEarned, ') + ..write('exercises: $exercises, ') + ..write('notes: $notes, ') + ..write('isDirty: $isDirty, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + serverId, + userId, + cycleId, + week, + day, + scheduledDate, + completedAt, + xpEarned, + exercises, + notes, + isDirty, + createdAt, + updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is WorkoutCollection && + other.id == this.id && + other.serverId == this.serverId && + other.userId == this.userId && + other.cycleId == this.cycleId && + other.week == this.week && + other.day == this.day && + other.scheduledDate == this.scheduledDate && + other.completedAt == this.completedAt && + other.xpEarned == this.xpEarned && + other.exercises == this.exercises && + other.notes == this.notes && + other.isDirty == this.isDirty && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class WorkoutsCompanion extends UpdateCompanion { + final Value id; + final Value serverId; + final Value userId; + final Value cycleId; + final Value week; + final Value day; + final Value scheduledDate; + final Value completedAt; + final Value xpEarned; + final Value> exercises; + final Value notes; + final Value isDirty; + final Value createdAt; + final Value updatedAt; + const WorkoutsCompanion({ + this.id = const Value.absent(), + this.serverId = const Value.absent(), + this.userId = const Value.absent(), + this.cycleId = const Value.absent(), + this.week = const Value.absent(), + this.day = const Value.absent(), + this.scheduledDate = const Value.absent(), + this.completedAt = const Value.absent(), + this.xpEarned = const Value.absent(), + this.exercises = const Value.absent(), + this.notes = const Value.absent(), + this.isDirty = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + WorkoutsCompanion.insert({ + this.id = const Value.absent(), + this.serverId = const Value.absent(), + required String userId, + required String cycleId, + required int week, + required int day, + this.scheduledDate = const Value.absent(), + this.completedAt = const Value.absent(), + this.xpEarned = const Value.absent(), + required List exercises, + this.notes = const Value.absent(), + this.isDirty = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : userId = Value(userId), + cycleId = Value(cycleId), + week = Value(week), + day = Value(day), + exercises = Value(exercises); + static Insertable custom({ + Expression? id, + Expression? serverId, + Expression? userId, + Expression? cycleId, + Expression? week, + Expression? day, + Expression? scheduledDate, + Expression? completedAt, + Expression? xpEarned, + Expression? exercises, + Expression? notes, + Expression? isDirty, + Expression? createdAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (serverId != null) 'server_id': serverId, + if (userId != null) 'user_id': userId, + if (cycleId != null) 'cycle_id': cycleId, + if (week != null) 'week': week, + if (day != null) 'day': day, + if (scheduledDate != null) 'scheduled_date': scheduledDate, + if (completedAt != null) 'completed_at': completedAt, + if (xpEarned != null) 'xp_earned': xpEarned, + if (exercises != null) 'exercises': exercises, + if (notes != null) 'notes': notes, + if (isDirty != null) 'is_dirty': isDirty, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + WorkoutsCompanion copyWith( + {Value? id, + Value? serverId, + Value? userId, + Value? cycleId, + Value? week, + Value? day, + Value? scheduledDate, + Value? completedAt, + Value? xpEarned, + Value>? exercises, + Value? notes, + Value? isDirty, + Value? createdAt, + Value? updatedAt}) { + return WorkoutsCompanion( + id: id ?? this.id, + serverId: serverId ?? this.serverId, + userId: userId ?? this.userId, + cycleId: cycleId ?? this.cycleId, + week: week ?? this.week, + day: day ?? this.day, + scheduledDate: scheduledDate ?? this.scheduledDate, + completedAt: completedAt ?? this.completedAt, + xpEarned: xpEarned ?? this.xpEarned, + exercises: exercises ?? this.exercises, + notes: notes ?? this.notes, + isDirty: isDirty ?? this.isDirty, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (serverId.present) { + map['server_id'] = Variable(serverId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (cycleId.present) { + map['cycle_id'] = Variable(cycleId.value); + } + if (week.present) { + map['week'] = Variable(week.value); + } + if (day.present) { + map['day'] = Variable(day.value); + } + if (scheduledDate.present) { + map['scheduled_date'] = Variable(scheduledDate.value); + } + if (completedAt.present) { + map['completed_at'] = Variable(completedAt.value); + } + if (xpEarned.present) { + map['xp_earned'] = Variable(xpEarned.value); + } + if (exercises.present) { + map['exercises'] = Variable( + $WorkoutsTable.$converterexercises.toSql(exercises.value)); + } + if (notes.present) { + map['notes'] = Variable(notes.value); + } + if (isDirty.present) { + map['is_dirty'] = Variable(isDirty.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('WorkoutsCompanion(') + ..write('id: $id, ') + ..write('serverId: $serverId, ') + ..write('userId: $userId, ') + ..write('cycleId: $cycleId, ') + ..write('week: $week, ') + ..write('day: $day, ') + ..write('scheduledDate: $scheduledDate, ') + ..write('completedAt: $completedAt, ') + ..write('xpEarned: $xpEarned, ') + ..write('exercises: $exercises, ') + ..write('notes: $notes, ') + ..write('isDirty: $isDirty, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class $QuestsTable extends Quests + with TableInfo<$QuestsTable, QuestCollection> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $QuestsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _typeMeta = const VerificationMeta('type'); + @override + late final GeneratedColumn type = GeneratedColumn( + 'type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _titleMeta = const VerificationMeta('title'); + @override + late final GeneratedColumn title = GeneratedColumn( + 'title', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _descriptionMeta = + const VerificationMeta('description'); + @override + late final GeneratedColumn description = GeneratedColumn( + 'description', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _targetValueMeta = + const VerificationMeta('targetValue'); + @override + late final GeneratedColumn targetValue = GeneratedColumn( + 'target_value', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _currentValueMeta = + const VerificationMeta('currentValue'); + @override + late final GeneratedColumn currentValue = GeneratedColumn( + 'current_value', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + static const VerificationMeta _rewardXPMeta = + const VerificationMeta('rewardXP'); + @override + late final GeneratedColumn rewardXP = GeneratedColumn( + 'reward_x_p', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _rewardItemMeta = + const VerificationMeta('rewardItem'); + @override + late final GeneratedColumn rewardItem = GeneratedColumn( + 'reward_item', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _isCompletedMeta = + const VerificationMeta('isCompleted'); + @override + late final GeneratedColumn isCompleted = GeneratedColumn( + 'is_completed', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_completed" IN (0, 1))'), + defaultValue: const Constant(false)); + static const VerificationMeta _isClaimedMeta = + const VerificationMeta('isClaimed'); + @override + late final GeneratedColumn isClaimed = GeneratedColumn( + 'is_claimed', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("is_claimed" IN (0, 1))'), + defaultValue: const Constant(false)); + static const VerificationMeta _expiresAtMeta = + const VerificationMeta('expiresAt'); + @override + late final GeneratedColumn expiresAt = GeneratedColumn( + 'expires_at', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => [ + id, + type, + title, + description, + targetValue, + currentValue, + rewardXP, + rewardItem, + isCompleted, + isClaimed, + expiresAt, + createdAt + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'quests'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('type')) { + context.handle( + _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + } else if (isInserting) { + context.missing(_typeMeta); + } + if (data.containsKey('title')) { + context.handle( + _titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta)); + } else if (isInserting) { + context.missing(_titleMeta); + } + if (data.containsKey('description')) { + context.handle( + _descriptionMeta, + description.isAcceptableOrUnknown( + data['description']!, _descriptionMeta)); + } else if (isInserting) { + context.missing(_descriptionMeta); + } + if (data.containsKey('target_value')) { + context.handle( + _targetValueMeta, + targetValue.isAcceptableOrUnknown( + data['target_value']!, _targetValueMeta)); + } else if (isInserting) { + context.missing(_targetValueMeta); + } + if (data.containsKey('current_value')) { + context.handle( + _currentValueMeta, + currentValue.isAcceptableOrUnknown( + data['current_value']!, _currentValueMeta)); + } + if (data.containsKey('reward_x_p')) { + context.handle(_rewardXPMeta, + rewardXP.isAcceptableOrUnknown(data['reward_x_p']!, _rewardXPMeta)); + } else if (isInserting) { + context.missing(_rewardXPMeta); + } + if (data.containsKey('reward_item')) { + context.handle( + _rewardItemMeta, + rewardItem.isAcceptableOrUnknown( + data['reward_item']!, _rewardItemMeta)); + } + if (data.containsKey('is_completed')) { + context.handle( + _isCompletedMeta, + isCompleted.isAcceptableOrUnknown( + data['is_completed']!, _isCompletedMeta)); + } + if (data.containsKey('is_claimed')) { + context.handle(_isClaimedMeta, + isClaimed.isAcceptableOrUnknown(data['is_claimed']!, _isClaimedMeta)); + } + if (data.containsKey('expires_at')) { + context.handle(_expiresAtMeta, + expiresAt.isAcceptableOrUnknown(data['expires_at']!, _expiresAtMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + QuestCollection map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return QuestCollection( + id: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}id'])!, + type: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + title: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}title'])!, + description: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}description'])!, + targetValue: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}target_value'])!, + currentValue: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}current_value'])!, + rewardXP: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}reward_x_p'])!, + rewardItem: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}reward_item']), + isCompleted: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_completed'])!, + isClaimed: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_claimed'])!, + expiresAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}expires_at']), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + $QuestsTable createAlias(String alias) { + return $QuestsTable(attachedDatabase, alias); + } +} + +class QuestCollection extends DataClass implements Insertable { + final String id; + final String type; + final String title; + final String description; + final int targetValue; + final int currentValue; + final int rewardXP; + final String? rewardItem; + final bool isCompleted; + final bool isClaimed; + final DateTime? expiresAt; + final DateTime createdAt; + const QuestCollection( + {required this.id, + required this.type, + required this.title, + required this.description, + required this.targetValue, + required this.currentValue, + required this.rewardXP, + this.rewardItem, + required this.isCompleted, + required this.isClaimed, + this.expiresAt, + required this.createdAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['type'] = Variable(type); + map['title'] = Variable(title); + map['description'] = Variable(description); + map['target_value'] = Variable(targetValue); + map['current_value'] = Variable(currentValue); + map['reward_x_p'] = Variable(rewardXP); + if (!nullToAbsent || rewardItem != null) { + map['reward_item'] = Variable(rewardItem); + } + map['is_completed'] = Variable(isCompleted); + map['is_claimed'] = Variable(isClaimed); + if (!nullToAbsent || expiresAt != null) { + map['expires_at'] = Variable(expiresAt); + } + map['created_at'] = Variable(createdAt); + return map; + } + + QuestsCompanion toCompanion(bool nullToAbsent) { + return QuestsCompanion( + id: Value(id), + type: Value(type), + title: Value(title), + description: Value(description), + targetValue: Value(targetValue), + currentValue: Value(currentValue), + rewardXP: Value(rewardXP), + rewardItem: rewardItem == null && nullToAbsent + ? const Value.absent() + : Value(rewardItem), + isCompleted: Value(isCompleted), + isClaimed: Value(isClaimed), + expiresAt: expiresAt == null && nullToAbsent + ? const Value.absent() + : Value(expiresAt), + createdAt: Value(createdAt), + ); + } + + factory QuestCollection.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return QuestCollection( + id: serializer.fromJson(json['id']), + type: serializer.fromJson(json['type']), + title: serializer.fromJson(json['title']), + description: serializer.fromJson(json['description']), + targetValue: serializer.fromJson(json['targetValue']), + currentValue: serializer.fromJson(json['currentValue']), + rewardXP: serializer.fromJson(json['rewardXP']), + rewardItem: serializer.fromJson(json['rewardItem']), + isCompleted: serializer.fromJson(json['isCompleted']), + isClaimed: serializer.fromJson(json['isClaimed']), + expiresAt: serializer.fromJson(json['expiresAt']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'type': serializer.toJson(type), + 'title': serializer.toJson(title), + 'description': serializer.toJson(description), + 'targetValue': serializer.toJson(targetValue), + 'currentValue': serializer.toJson(currentValue), + 'rewardXP': serializer.toJson(rewardXP), + 'rewardItem': serializer.toJson(rewardItem), + 'isCompleted': serializer.toJson(isCompleted), + 'isClaimed': serializer.toJson(isClaimed), + 'expiresAt': serializer.toJson(expiresAt), + 'createdAt': serializer.toJson(createdAt), + }; + } + + QuestCollection copyWith( + {String? id, + String? type, + String? title, + String? description, + int? targetValue, + int? currentValue, + int? rewardXP, + Value rewardItem = const Value.absent(), + bool? isCompleted, + bool? isClaimed, + Value expiresAt = const Value.absent(), + DateTime? createdAt}) => + QuestCollection( + id: id ?? this.id, + type: type ?? this.type, + title: title ?? this.title, + description: description ?? this.description, + targetValue: targetValue ?? this.targetValue, + currentValue: currentValue ?? this.currentValue, + rewardXP: rewardXP ?? this.rewardXP, + rewardItem: rewardItem.present ? rewardItem.value : this.rewardItem, + isCompleted: isCompleted ?? this.isCompleted, + isClaimed: isClaimed ?? this.isClaimed, + expiresAt: expiresAt.present ? expiresAt.value : this.expiresAt, + createdAt: createdAt ?? this.createdAt, + ); + QuestCollection copyWithCompanion(QuestsCompanion data) { + return QuestCollection( + id: data.id.present ? data.id.value : this.id, + type: data.type.present ? data.type.value : this.type, + title: data.title.present ? data.title.value : this.title, + description: + data.description.present ? data.description.value : this.description, + targetValue: + data.targetValue.present ? data.targetValue.value : this.targetValue, + currentValue: data.currentValue.present + ? data.currentValue.value + : this.currentValue, + rewardXP: data.rewardXP.present ? data.rewardXP.value : this.rewardXP, + rewardItem: + data.rewardItem.present ? data.rewardItem.value : this.rewardItem, + isCompleted: + data.isCompleted.present ? data.isCompleted.value : this.isCompleted, + isClaimed: data.isClaimed.present ? data.isClaimed.value : this.isClaimed, + expiresAt: data.expiresAt.present ? data.expiresAt.value : this.expiresAt, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('QuestCollection(') + ..write('id: $id, ') + ..write('type: $type, ') + ..write('title: $title, ') + ..write('description: $description, ') + ..write('targetValue: $targetValue, ') + ..write('currentValue: $currentValue, ') + ..write('rewardXP: $rewardXP, ') + ..write('rewardItem: $rewardItem, ') + ..write('isCompleted: $isCompleted, ') + ..write('isClaimed: $isClaimed, ') + ..write('expiresAt: $expiresAt, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + type, + title, + description, + targetValue, + currentValue, + rewardXP, + rewardItem, + isCompleted, + isClaimed, + expiresAt, + createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is QuestCollection && + other.id == this.id && + other.type == this.type && + other.title == this.title && + other.description == this.description && + other.targetValue == this.targetValue && + other.currentValue == this.currentValue && + other.rewardXP == this.rewardXP && + other.rewardItem == this.rewardItem && + other.isCompleted == this.isCompleted && + other.isClaimed == this.isClaimed && + other.expiresAt == this.expiresAt && + other.createdAt == this.createdAt); +} + +class QuestsCompanion extends UpdateCompanion { + final Value id; + final Value type; + final Value title; + final Value description; + final Value targetValue; + final Value currentValue; + final Value rewardXP; + final Value rewardItem; + final Value isCompleted; + final Value isClaimed; + final Value expiresAt; + final Value createdAt; + final Value rowid; + const QuestsCompanion({ + this.id = const Value.absent(), + this.type = const Value.absent(), + this.title = const Value.absent(), + this.description = const Value.absent(), + this.targetValue = const Value.absent(), + this.currentValue = const Value.absent(), + this.rewardXP = const Value.absent(), + this.rewardItem = const Value.absent(), + this.isCompleted = const Value.absent(), + this.isClaimed = const Value.absent(), + this.expiresAt = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + QuestsCompanion.insert({ + required String id, + required String type, + required String title, + required String description, + required int targetValue, + this.currentValue = const Value.absent(), + required int rewardXP, + this.rewardItem = const Value.absent(), + this.isCompleted = const Value.absent(), + this.isClaimed = const Value.absent(), + this.expiresAt = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }) : id = Value(id), + type = Value(type), + title = Value(title), + description = Value(description), + targetValue = Value(targetValue), + rewardXP = Value(rewardXP); + static Insertable custom({ + Expression? id, + Expression? type, + Expression? title, + Expression? description, + Expression? targetValue, + Expression? currentValue, + Expression? rewardXP, + Expression? rewardItem, + Expression? isCompleted, + Expression? isClaimed, + Expression? expiresAt, + Expression? createdAt, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (type != null) 'type': type, + if (title != null) 'title': title, + if (description != null) 'description': description, + if (targetValue != null) 'target_value': targetValue, + if (currentValue != null) 'current_value': currentValue, + if (rewardXP != null) 'reward_x_p': rewardXP, + if (rewardItem != null) 'reward_item': rewardItem, + if (isCompleted != null) 'is_completed': isCompleted, + if (isClaimed != null) 'is_claimed': isClaimed, + if (expiresAt != null) 'expires_at': expiresAt, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + QuestsCompanion copyWith( + {Value? id, + Value? type, + Value? title, + Value? description, + Value? targetValue, + Value? currentValue, + Value? rewardXP, + Value? rewardItem, + Value? isCompleted, + Value? isClaimed, + Value? expiresAt, + Value? createdAt, + Value? rowid}) { + return QuestsCompanion( + id: id ?? this.id, + type: type ?? this.type, + title: title ?? this.title, + description: description ?? this.description, + targetValue: targetValue ?? this.targetValue, + currentValue: currentValue ?? this.currentValue, + rewardXP: rewardXP ?? this.rewardXP, + rewardItem: rewardItem ?? this.rewardItem, + isCompleted: isCompleted ?? this.isCompleted, + isClaimed: isClaimed ?? this.isClaimed, + expiresAt: expiresAt ?? this.expiresAt, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (title.present) { + map['title'] = Variable(title.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (targetValue.present) { + map['target_value'] = Variable(targetValue.value); + } + if (currentValue.present) { + map['current_value'] = Variable(currentValue.value); + } + if (rewardXP.present) { + map['reward_x_p'] = Variable(rewardXP.value); + } + if (rewardItem.present) { + map['reward_item'] = Variable(rewardItem.value); + } + if (isCompleted.present) { + map['is_completed'] = Variable(isCompleted.value); + } + if (isClaimed.present) { + map['is_claimed'] = Variable(isClaimed.value); + } + if (expiresAt.present) { + map['expires_at'] = Variable(expiresAt.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('QuestsCompanion(') + ..write('id: $id, ') + ..write('type: $type, ') + ..write('title: $title, ') + ..write('description: $description, ') + ..write('targetValue: $targetValue, ') + ..write('currentValue: $currentValue, ') + ..write('rewardXP: $rewardXP, ') + ..write('rewardItem: $rewardItem, ') + ..write('isCompleted: $isCompleted, ') + ..write('isClaimed: $isClaimed, ') + ..write('expiresAt: $expiresAt, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +abstract class _$AppDatabase extends GeneratedDatabase { + _$AppDatabase(QueryExecutor e) : super(e); + $AppDatabaseManager get managers => $AppDatabaseManager(this); + late final $UsersTable users = $UsersTable(this); + late final $CyclesTable cycles = $CyclesTable(this); + late final $WorkoutsTable workouts = $WorkoutsTable(this); + late final $QuestsTable quests = $QuestsTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => + [users, cycles, workouts, quests]; +} + +typedef $$UsersTableCreateCompanionBuilder = UsersCompanion Function({ + Value id, + Value serverId, + Value email, + Value xp, + Value level, + Value currentBodyweight, + Value?> exerciseVariants, + Value?> inventorySettings, + Value?> avatarConfig, + Value lastSyncAt, + Value isDirty, + Value createdAt, + Value updatedAt, +}); +typedef $$UsersTableUpdateCompanionBuilder = UsersCompanion Function({ + Value id, + Value serverId, + Value email, + Value xp, + Value level, + Value currentBodyweight, + Value?> exerciseVariants, + Value?> inventorySettings, + Value?> avatarConfig, + Value lastSyncAt, + Value isDirty, + Value createdAt, + Value updatedAt, +}); + +class $$UsersTableFilterComposer extends Composer<_$AppDatabase, $UsersTable> { + $$UsersTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnFilters(column)); + + ColumnFilters get serverId => $composableBuilder( + column: $table.serverId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get email => $composableBuilder( + column: $table.email, builder: (column) => ColumnFilters(column)); + + ColumnFilters get xp => $composableBuilder( + column: $table.xp, builder: (column) => ColumnFilters(column)); + + ColumnFilters get level => $composableBuilder( + column: $table.level, builder: (column) => ColumnFilters(column)); + + ColumnFilters get currentBodyweight => $composableBuilder( + column: $table.currentBodyweight, + builder: (column) => ColumnFilters(column)); + + ColumnWithTypeConverterFilters?, Map, + String> + get exerciseVariants => $composableBuilder( + column: $table.exerciseVariants, + builder: (column) => ColumnWithTypeConverterFilters(column)); + + ColumnWithTypeConverterFilters?, Map, + String> + get inventorySettings => $composableBuilder( + column: $table.inventorySettings, + builder: (column) => ColumnWithTypeConverterFilters(column)); + + ColumnWithTypeConverterFilters?, Map, + String> + get avatarConfig => $composableBuilder( + column: $table.avatarConfig, + builder: (column) => ColumnWithTypeConverterFilters(column)); + + ColumnFilters get lastSyncAt => $composableBuilder( + column: $table.lastSyncAt, builder: (column) => ColumnFilters(column)); + + ColumnFilters get isDirty => $composableBuilder( + column: $table.isDirty, builder: (column) => ColumnFilters(column)); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, builder: (column) => ColumnFilters(column)); + + ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, builder: (column) => ColumnFilters(column)); +} + +class $$UsersTableOrderingComposer + extends Composer<_$AppDatabase, $UsersTable> { + $$UsersTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get serverId => $composableBuilder( + column: $table.serverId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get email => $composableBuilder( + column: $table.email, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get xp => $composableBuilder( + column: $table.xp, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get level => $composableBuilder( + column: $table.level, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get currentBodyweight => $composableBuilder( + column: $table.currentBodyweight, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get exerciseVariants => $composableBuilder( + column: $table.exerciseVariants, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get inventorySettings => $composableBuilder( + column: $table.inventorySettings, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get avatarConfig => $composableBuilder( + column: $table.avatarConfig, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get lastSyncAt => $composableBuilder( + column: $table.lastSyncAt, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get isDirty => $composableBuilder( + column: $table.isDirty, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); +} + +class $$UsersTableAnnotationComposer + extends Composer<_$AppDatabase, $UsersTable> { + $$UsersTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get serverId => + $composableBuilder(column: $table.serverId, builder: (column) => column); + + GeneratedColumn get email => + $composableBuilder(column: $table.email, builder: (column) => column); + + GeneratedColumn get xp => + $composableBuilder(column: $table.xp, builder: (column) => column); + + GeneratedColumn get level => + $composableBuilder(column: $table.level, builder: (column) => column); + + GeneratedColumn get currentBodyweight => $composableBuilder( + column: $table.currentBodyweight, builder: (column) => column); + + GeneratedColumnWithTypeConverter?, String> + get exerciseVariants => $composableBuilder( + column: $table.exerciseVariants, builder: (column) => column); + + GeneratedColumnWithTypeConverter?, String> + get inventorySettings => $composableBuilder( + column: $table.inventorySettings, builder: (column) => column); + + GeneratedColumnWithTypeConverter?, String> + get avatarConfig => $composableBuilder( + column: $table.avatarConfig, builder: (column) => column); + + GeneratedColumn get lastSyncAt => $composableBuilder( + column: $table.lastSyncAt, builder: (column) => column); + + GeneratedColumn get isDirty => + $composableBuilder(column: $table.isDirty, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); +} + +class $$UsersTableTableManager extends RootTableManager< + _$AppDatabase, + $UsersTable, + UserCollection, + $$UsersTableFilterComposer, + $$UsersTableOrderingComposer, + $$UsersTableAnnotationComposer, + $$UsersTableCreateCompanionBuilder, + $$UsersTableUpdateCompanionBuilder, + ( + UserCollection, + BaseReferences<_$AppDatabase, $UsersTable, UserCollection> + ), + UserCollection, + PrefetchHooks Function()> { + $$UsersTableTableManager(_$AppDatabase db, $UsersTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$UsersTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$UsersTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$UsersTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value id = const Value.absent(), + Value serverId = const Value.absent(), + Value email = const Value.absent(), + Value xp = const Value.absent(), + Value level = const Value.absent(), + Value currentBodyweight = const Value.absent(), + Value?> exerciseVariants = + const Value.absent(), + Value?> inventorySettings = + const Value.absent(), + Value?> avatarConfig = const Value.absent(), + Value lastSyncAt = const Value.absent(), + Value isDirty = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + }) => + UsersCompanion( + id: id, + serverId: serverId, + email: email, + xp: xp, + level: level, + currentBodyweight: currentBodyweight, + exerciseVariants: exerciseVariants, + inventorySettings: inventorySettings, + avatarConfig: avatarConfig, + lastSyncAt: lastSyncAt, + isDirty: isDirty, + createdAt: createdAt, + updatedAt: updatedAt, + ), + createCompanionCallback: ({ + Value id = const Value.absent(), + Value serverId = const Value.absent(), + Value email = const Value.absent(), + Value xp = const Value.absent(), + Value level = const Value.absent(), + Value currentBodyweight = const Value.absent(), + Value?> exerciseVariants = + const Value.absent(), + Value?> inventorySettings = + const Value.absent(), + Value?> avatarConfig = const Value.absent(), + Value lastSyncAt = const Value.absent(), + Value isDirty = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + }) => + UsersCompanion.insert( + id: id, + serverId: serverId, + email: email, + xp: xp, + level: level, + currentBodyweight: currentBodyweight, + exerciseVariants: exerciseVariants, + inventorySettings: inventorySettings, + avatarConfig: avatarConfig, + lastSyncAt: lastSyncAt, + isDirty: isDirty, + createdAt: createdAt, + updatedAt: updatedAt, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$UsersTableProcessedTableManager = ProcessedTableManager< + _$AppDatabase, + $UsersTable, + UserCollection, + $$UsersTableFilterComposer, + $$UsersTableOrderingComposer, + $$UsersTableAnnotationComposer, + $$UsersTableCreateCompanionBuilder, + $$UsersTableUpdateCompanionBuilder, + ( + UserCollection, + BaseReferences<_$AppDatabase, $UsersTable, UserCollection> + ), + UserCollection, + PrefetchHooks Function()>; +typedef $$CyclesTableCreateCompanionBuilder = CyclesCompanion Function({ + Value id, + Value serverId, + required String userId, + required int cycleNumber, + required DateTime startDate, + Value endDate, + Value isActive, + required Map trainingMaxes, + Value isDirty, + Value createdAt, + Value updatedAt, +}); +typedef $$CyclesTableUpdateCompanionBuilder = CyclesCompanion Function({ + Value id, + Value serverId, + Value userId, + Value cycleNumber, + Value startDate, + Value endDate, + Value isActive, + Value> trainingMaxes, + Value isDirty, + Value createdAt, + Value updatedAt, +}); + +class $$CyclesTableFilterComposer + extends Composer<_$AppDatabase, $CyclesTable> { + $$CyclesTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnFilters(column)); + + ColumnFilters get serverId => $composableBuilder( + column: $table.serverId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get userId => $composableBuilder( + column: $table.userId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get cycleNumber => $composableBuilder( + column: $table.cycleNumber, builder: (column) => ColumnFilters(column)); + + ColumnFilters get startDate => $composableBuilder( + column: $table.startDate, builder: (column) => ColumnFilters(column)); + + ColumnFilters get endDate => $composableBuilder( + column: $table.endDate, builder: (column) => ColumnFilters(column)); + + ColumnFilters get isActive => $composableBuilder( + column: $table.isActive, builder: (column) => ColumnFilters(column)); + + ColumnWithTypeConverterFilters, Map, + String> + get trainingMaxes => $composableBuilder( + column: $table.trainingMaxes, + builder: (column) => ColumnWithTypeConverterFilters(column)); + + ColumnFilters get isDirty => $composableBuilder( + column: $table.isDirty, builder: (column) => ColumnFilters(column)); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, builder: (column) => ColumnFilters(column)); + + ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, builder: (column) => ColumnFilters(column)); +} + +class $$CyclesTableOrderingComposer + extends Composer<_$AppDatabase, $CyclesTable> { + $$CyclesTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get serverId => $composableBuilder( + column: $table.serverId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get userId => $composableBuilder( + column: $table.userId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get cycleNumber => $composableBuilder( + column: $table.cycleNumber, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get startDate => $composableBuilder( + column: $table.startDate, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get endDate => $composableBuilder( + column: $table.endDate, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get isActive => $composableBuilder( + column: $table.isActive, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get trainingMaxes => $composableBuilder( + column: $table.trainingMaxes, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get isDirty => $composableBuilder( + column: $table.isDirty, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); +} + +class $$CyclesTableAnnotationComposer + extends Composer<_$AppDatabase, $CyclesTable> { + $$CyclesTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get serverId => + $composableBuilder(column: $table.serverId, builder: (column) => column); + + GeneratedColumn get userId => + $composableBuilder(column: $table.userId, builder: (column) => column); + + GeneratedColumn get cycleNumber => $composableBuilder( + column: $table.cycleNumber, builder: (column) => column); + + GeneratedColumn get startDate => + $composableBuilder(column: $table.startDate, builder: (column) => column); + + GeneratedColumn get endDate => + $composableBuilder(column: $table.endDate, builder: (column) => column); + + GeneratedColumn get isActive => + $composableBuilder(column: $table.isActive, builder: (column) => column); + + GeneratedColumnWithTypeConverter, String> + get trainingMaxes => $composableBuilder( + column: $table.trainingMaxes, builder: (column) => column); + + GeneratedColumn get isDirty => + $composableBuilder(column: $table.isDirty, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); +} + +class $$CyclesTableTableManager extends RootTableManager< + _$AppDatabase, + $CyclesTable, + CycleCollection, + $$CyclesTableFilterComposer, + $$CyclesTableOrderingComposer, + $$CyclesTableAnnotationComposer, + $$CyclesTableCreateCompanionBuilder, + $$CyclesTableUpdateCompanionBuilder, + ( + CycleCollection, + BaseReferences<_$AppDatabase, $CyclesTable, CycleCollection> + ), + CycleCollection, + PrefetchHooks Function()> { + $$CyclesTableTableManager(_$AppDatabase db, $CyclesTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$CyclesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$CyclesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$CyclesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value id = const Value.absent(), + Value serverId = const Value.absent(), + Value userId = const Value.absent(), + Value cycleNumber = const Value.absent(), + Value startDate = const Value.absent(), + Value endDate = const Value.absent(), + Value isActive = const Value.absent(), + Value> trainingMaxes = const Value.absent(), + Value isDirty = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + }) => + CyclesCompanion( + id: id, + serverId: serverId, + userId: userId, + cycleNumber: cycleNumber, + startDate: startDate, + endDate: endDate, + isActive: isActive, + trainingMaxes: trainingMaxes, + isDirty: isDirty, + createdAt: createdAt, + updatedAt: updatedAt, + ), + createCompanionCallback: ({ + Value id = const Value.absent(), + Value serverId = const Value.absent(), + required String userId, + required int cycleNumber, + required DateTime startDate, + Value endDate = const Value.absent(), + Value isActive = const Value.absent(), + required Map trainingMaxes, + Value isDirty = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + }) => + CyclesCompanion.insert( + id: id, + serverId: serverId, + userId: userId, + cycleNumber: cycleNumber, + startDate: startDate, + endDate: endDate, + isActive: isActive, + trainingMaxes: trainingMaxes, + isDirty: isDirty, + createdAt: createdAt, + updatedAt: updatedAt, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$CyclesTableProcessedTableManager = ProcessedTableManager< + _$AppDatabase, + $CyclesTable, + CycleCollection, + $$CyclesTableFilterComposer, + $$CyclesTableOrderingComposer, + $$CyclesTableAnnotationComposer, + $$CyclesTableCreateCompanionBuilder, + $$CyclesTableUpdateCompanionBuilder, + ( + CycleCollection, + BaseReferences<_$AppDatabase, $CyclesTable, CycleCollection> + ), + CycleCollection, + PrefetchHooks Function()>; +typedef $$WorkoutsTableCreateCompanionBuilder = WorkoutsCompanion Function({ + Value id, + Value serverId, + required String userId, + required String cycleId, + required int week, + required int day, + Value scheduledDate, + Value completedAt, + Value xpEarned, + required List exercises, + Value notes, + Value isDirty, + Value createdAt, + Value updatedAt, +}); +typedef $$WorkoutsTableUpdateCompanionBuilder = WorkoutsCompanion Function({ + Value id, + Value serverId, + Value userId, + Value cycleId, + Value week, + Value day, + Value scheduledDate, + Value completedAt, + Value xpEarned, + Value> exercises, + Value notes, + Value isDirty, + Value createdAt, + Value updatedAt, +}); + +class $$WorkoutsTableFilterComposer + extends Composer<_$AppDatabase, $WorkoutsTable> { + $$WorkoutsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnFilters(column)); + + ColumnFilters get serverId => $composableBuilder( + column: $table.serverId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get userId => $composableBuilder( + column: $table.userId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get cycleId => $composableBuilder( + column: $table.cycleId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get week => $composableBuilder( + column: $table.week, builder: (column) => ColumnFilters(column)); + + ColumnFilters get day => $composableBuilder( + column: $table.day, builder: (column) => ColumnFilters(column)); + + ColumnFilters get scheduledDate => $composableBuilder( + column: $table.scheduledDate, builder: (column) => ColumnFilters(column)); + + ColumnFilters get completedAt => $composableBuilder( + column: $table.completedAt, builder: (column) => ColumnFilters(column)); + + ColumnFilters get xpEarned => $composableBuilder( + column: $table.xpEarned, builder: (column) => ColumnFilters(column)); + + ColumnWithTypeConverterFilters, List, String> + get exercises => $composableBuilder( + column: $table.exercises, + builder: (column) => ColumnWithTypeConverterFilters(column)); + + ColumnFilters get notes => $composableBuilder( + column: $table.notes, builder: (column) => ColumnFilters(column)); + + ColumnFilters get isDirty => $composableBuilder( + column: $table.isDirty, builder: (column) => ColumnFilters(column)); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, builder: (column) => ColumnFilters(column)); + + ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, builder: (column) => ColumnFilters(column)); +} + +class $$WorkoutsTableOrderingComposer + extends Composer<_$AppDatabase, $WorkoutsTable> { + $$WorkoutsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get serverId => $composableBuilder( + column: $table.serverId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get userId => $composableBuilder( + column: $table.userId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get cycleId => $composableBuilder( + column: $table.cycleId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get week => $composableBuilder( + column: $table.week, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get day => $composableBuilder( + column: $table.day, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get scheduledDate => $composableBuilder( + column: $table.scheduledDate, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get completedAt => $composableBuilder( + column: $table.completedAt, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get xpEarned => $composableBuilder( + column: $table.xpEarned, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get exercises => $composableBuilder( + column: $table.exercises, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get notes => $composableBuilder( + column: $table.notes, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get isDirty => $composableBuilder( + column: $table.isDirty, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); +} + +class $$WorkoutsTableAnnotationComposer + extends Composer<_$AppDatabase, $WorkoutsTable> { + $$WorkoutsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get serverId => + $composableBuilder(column: $table.serverId, builder: (column) => column); + + GeneratedColumn get userId => + $composableBuilder(column: $table.userId, builder: (column) => column); + + GeneratedColumn get cycleId => + $composableBuilder(column: $table.cycleId, builder: (column) => column); + + GeneratedColumn get week => + $composableBuilder(column: $table.week, builder: (column) => column); + + GeneratedColumn get day => + $composableBuilder(column: $table.day, builder: (column) => column); + + GeneratedColumn get scheduledDate => $composableBuilder( + column: $table.scheduledDate, builder: (column) => column); + + GeneratedColumn get completedAt => $composableBuilder( + column: $table.completedAt, builder: (column) => column); + + GeneratedColumn get xpEarned => + $composableBuilder(column: $table.xpEarned, builder: (column) => column); + + GeneratedColumnWithTypeConverter, String> get exercises => + $composableBuilder(column: $table.exercises, builder: (column) => column); + + GeneratedColumn get notes => + $composableBuilder(column: $table.notes, builder: (column) => column); + + GeneratedColumn get isDirty => + $composableBuilder(column: $table.isDirty, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); +} + +class $$WorkoutsTableTableManager extends RootTableManager< + _$AppDatabase, + $WorkoutsTable, + WorkoutCollection, + $$WorkoutsTableFilterComposer, + $$WorkoutsTableOrderingComposer, + $$WorkoutsTableAnnotationComposer, + $$WorkoutsTableCreateCompanionBuilder, + $$WorkoutsTableUpdateCompanionBuilder, + ( + WorkoutCollection, + BaseReferences<_$AppDatabase, $WorkoutsTable, WorkoutCollection> + ), + WorkoutCollection, + PrefetchHooks Function()> { + $$WorkoutsTableTableManager(_$AppDatabase db, $WorkoutsTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$WorkoutsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$WorkoutsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$WorkoutsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value id = const Value.absent(), + Value serverId = const Value.absent(), + Value userId = const Value.absent(), + Value cycleId = const Value.absent(), + Value week = const Value.absent(), + Value day = const Value.absent(), + Value scheduledDate = const Value.absent(), + Value completedAt = const Value.absent(), + Value xpEarned = const Value.absent(), + Value> exercises = const Value.absent(), + Value notes = const Value.absent(), + Value isDirty = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + }) => + WorkoutsCompanion( + id: id, + serverId: serverId, + userId: userId, + cycleId: cycleId, + week: week, + day: day, + scheduledDate: scheduledDate, + completedAt: completedAt, + xpEarned: xpEarned, + exercises: exercises, + notes: notes, + isDirty: isDirty, + createdAt: createdAt, + updatedAt: updatedAt, + ), + createCompanionCallback: ({ + Value id = const Value.absent(), + Value serverId = const Value.absent(), + required String userId, + required String cycleId, + required int week, + required int day, + Value scheduledDate = const Value.absent(), + Value completedAt = const Value.absent(), + Value xpEarned = const Value.absent(), + required List exercises, + Value notes = const Value.absent(), + Value isDirty = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + }) => + WorkoutsCompanion.insert( + id: id, + serverId: serverId, + userId: userId, + cycleId: cycleId, + week: week, + day: day, + scheduledDate: scheduledDate, + completedAt: completedAt, + xpEarned: xpEarned, + exercises: exercises, + notes: notes, + isDirty: isDirty, + createdAt: createdAt, + updatedAt: updatedAt, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$WorkoutsTableProcessedTableManager = ProcessedTableManager< + _$AppDatabase, + $WorkoutsTable, + WorkoutCollection, + $$WorkoutsTableFilterComposer, + $$WorkoutsTableOrderingComposer, + $$WorkoutsTableAnnotationComposer, + $$WorkoutsTableCreateCompanionBuilder, + $$WorkoutsTableUpdateCompanionBuilder, + ( + WorkoutCollection, + BaseReferences<_$AppDatabase, $WorkoutsTable, WorkoutCollection> + ), + WorkoutCollection, + PrefetchHooks Function()>; +typedef $$QuestsTableCreateCompanionBuilder = QuestsCompanion Function({ + required String id, + required String type, + required String title, + required String description, + required int targetValue, + Value currentValue, + required int rewardXP, + Value rewardItem, + Value isCompleted, + Value isClaimed, + Value expiresAt, + Value createdAt, + Value rowid, +}); +typedef $$QuestsTableUpdateCompanionBuilder = QuestsCompanion Function({ + Value id, + Value type, + Value title, + Value description, + Value targetValue, + Value currentValue, + Value rewardXP, + Value rewardItem, + Value isCompleted, + Value isClaimed, + Value expiresAt, + Value createdAt, + Value rowid, +}); + +class $$QuestsTableFilterComposer + extends Composer<_$AppDatabase, $QuestsTable> { + $$QuestsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnFilters(column)); + + ColumnFilters get type => $composableBuilder( + column: $table.type, builder: (column) => ColumnFilters(column)); + + ColumnFilters get title => $composableBuilder( + column: $table.title, builder: (column) => ColumnFilters(column)); + + ColumnFilters get description => $composableBuilder( + column: $table.description, builder: (column) => ColumnFilters(column)); + + ColumnFilters get targetValue => $composableBuilder( + column: $table.targetValue, builder: (column) => ColumnFilters(column)); + + ColumnFilters get currentValue => $composableBuilder( + column: $table.currentValue, builder: (column) => ColumnFilters(column)); + + ColumnFilters get rewardXP => $composableBuilder( + column: $table.rewardXP, builder: (column) => ColumnFilters(column)); + + ColumnFilters get rewardItem => $composableBuilder( + column: $table.rewardItem, builder: (column) => ColumnFilters(column)); + + ColumnFilters get isCompleted => $composableBuilder( + column: $table.isCompleted, builder: (column) => ColumnFilters(column)); + + ColumnFilters get isClaimed => $composableBuilder( + column: $table.isClaimed, builder: (column) => ColumnFilters(column)); + + ColumnFilters get expiresAt => $composableBuilder( + column: $table.expiresAt, builder: (column) => ColumnFilters(column)); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, builder: (column) => ColumnFilters(column)); +} + +class $$QuestsTableOrderingComposer + extends Composer<_$AppDatabase, $QuestsTable> { + $$QuestsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get type => $composableBuilder( + column: $table.type, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get title => $composableBuilder( + column: $table.title, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get description => $composableBuilder( + column: $table.description, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get targetValue => $composableBuilder( + column: $table.targetValue, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get currentValue => $composableBuilder( + column: $table.currentValue, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get rewardXP => $composableBuilder( + column: $table.rewardXP, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get rewardItem => $composableBuilder( + column: $table.rewardItem, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get isCompleted => $composableBuilder( + column: $table.isCompleted, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get isClaimed => $composableBuilder( + column: $table.isClaimed, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get expiresAt => $composableBuilder( + column: $table.expiresAt, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, builder: (column) => ColumnOrderings(column)); +} + +class $$QuestsTableAnnotationComposer + extends Composer<_$AppDatabase, $QuestsTable> { + $$QuestsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get type => + $composableBuilder(column: $table.type, builder: (column) => column); + + GeneratedColumn get title => + $composableBuilder(column: $table.title, builder: (column) => column); + + GeneratedColumn get description => $composableBuilder( + column: $table.description, builder: (column) => column); + + GeneratedColumn get targetValue => $composableBuilder( + column: $table.targetValue, builder: (column) => column); + + GeneratedColumn get currentValue => $composableBuilder( + column: $table.currentValue, builder: (column) => column); + + GeneratedColumn get rewardXP => + $composableBuilder(column: $table.rewardXP, builder: (column) => column); + + GeneratedColumn get rewardItem => $composableBuilder( + column: $table.rewardItem, builder: (column) => column); + + GeneratedColumn get isCompleted => $composableBuilder( + column: $table.isCompleted, builder: (column) => column); + + GeneratedColumn get isClaimed => + $composableBuilder(column: $table.isClaimed, builder: (column) => column); + + GeneratedColumn get expiresAt => + $composableBuilder(column: $table.expiresAt, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); +} + +class $$QuestsTableTableManager extends RootTableManager< + _$AppDatabase, + $QuestsTable, + QuestCollection, + $$QuestsTableFilterComposer, + $$QuestsTableOrderingComposer, + $$QuestsTableAnnotationComposer, + $$QuestsTableCreateCompanionBuilder, + $$QuestsTableUpdateCompanionBuilder, + ( + QuestCollection, + BaseReferences<_$AppDatabase, $QuestsTable, QuestCollection> + ), + QuestCollection, + PrefetchHooks Function()> { + $$QuestsTableTableManager(_$AppDatabase db, $QuestsTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$QuestsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$QuestsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$QuestsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value id = const Value.absent(), + Value type = const Value.absent(), + Value title = const Value.absent(), + Value description = const Value.absent(), + Value targetValue = const Value.absent(), + Value currentValue = const Value.absent(), + Value rewardXP = const Value.absent(), + Value rewardItem = const Value.absent(), + Value isCompleted = const Value.absent(), + Value isClaimed = const Value.absent(), + Value expiresAt = const Value.absent(), + Value createdAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => + QuestsCompanion( + id: id, + type: type, + title: title, + description: description, + targetValue: targetValue, + currentValue: currentValue, + rewardXP: rewardXP, + rewardItem: rewardItem, + isCompleted: isCompleted, + isClaimed: isClaimed, + expiresAt: expiresAt, + createdAt: createdAt, + rowid: rowid, + ), + createCompanionCallback: ({ + required String id, + required String type, + required String title, + required String description, + required int targetValue, + Value currentValue = const Value.absent(), + required int rewardXP, + Value rewardItem = const Value.absent(), + Value isCompleted = const Value.absent(), + Value isClaimed = const Value.absent(), + Value expiresAt = const Value.absent(), + Value createdAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => + QuestsCompanion.insert( + id: id, + type: type, + title: title, + description: description, + targetValue: targetValue, + currentValue: currentValue, + rewardXP: rewardXP, + rewardItem: rewardItem, + isCompleted: isCompleted, + isClaimed: isClaimed, + expiresAt: expiresAt, + createdAt: createdAt, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$QuestsTableProcessedTableManager = ProcessedTableManager< + _$AppDatabase, + $QuestsTable, + QuestCollection, + $$QuestsTableFilterComposer, + $$QuestsTableOrderingComposer, + $$QuestsTableAnnotationComposer, + $$QuestsTableCreateCompanionBuilder, + $$QuestsTableUpdateCompanionBuilder, + ( + QuestCollection, + BaseReferences<_$AppDatabase, $QuestsTable, QuestCollection> + ), + QuestCollection, + PrefetchHooks Function()>; + +class $AppDatabaseManager { + final _$AppDatabase _db; + $AppDatabaseManager(this._db); + $$UsersTableTableManager get users => + $$UsersTableTableManager(_db, _db.users); + $$CyclesTableTableManager get cycles => + $$CyclesTableTableManager(_db, _db.cycles); + $$WorkoutsTableTableManager get workouts => + $$WorkoutsTableTableManager(_db, _db.workouts); + $$QuestsTableTableManager get quests => + $$QuestsTableTableManager(_db, _db.quests); +} diff --git a/lib/src/shared/data/local/collections/cycle_collection.dart b/lib/src/shared/data/local/collections/cycle_collection.dart deleted file mode 100644 index 72bd209..0000000 --- a/lib/src/shared/data/local/collections/cycle_collection.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:isar/isar.dart'; - -part 'cycle_collection.g.dart'; - -@collection -class CycleCollection { - Id id = Isar.autoIncrement; - - @Index(unique: true) - String? serverId; - - String userId = ''; // Local reference - int cycleNumber = 1; - - DateTime startDate = DateTime.now(); - DateTime? endDate; - - bool isActive = true; - - // Training Maxes (stored as JSON string) - String trainingMaxesJson = '{}'; - - bool isDirty = false; - DateTime createdAt = DateTime.now(); - DateTime updatedAt = DateTime.now(); -} - diff --git a/lib/src/shared/data/local/collections/cycle_collection.g.dart b/lib/src/shared/data/local/collections/cycle_collection.g.dart deleted file mode 100644 index 5642b32..0000000 --- a/lib/src/shared/data/local/collections/cycle_collection.g.dart +++ /dev/null @@ -1,1649 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'cycle_collection.dart'; - -// ************************************************************************** -// IsarCollectionGenerator -// ************************************************************************** - -// coverage:ignore-file -// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types - -extension GetCycleCollectionCollection on Isar { - IsarCollection get cycleCollections => this.collection(); -} - -const CycleCollectionSchema = CollectionSchema( - name: r'CycleCollection', - id: 3532561085934404, - properties: { - r'createdAt': PropertySchema( - id: 0, - name: r'createdAt', - type: IsarType.dateTime, - ), - r'cycleNumber': PropertySchema( - id: 1, - name: r'cycleNumber', - type: IsarType.long, - ), - r'endDate': PropertySchema( - id: 2, - name: r'endDate', - type: IsarType.dateTime, - ), - r'isActive': PropertySchema( - id: 3, - name: r'isActive', - type: IsarType.bool, - ), - r'isDirty': PropertySchema( - id: 4, - name: r'isDirty', - type: IsarType.bool, - ), - r'serverId': PropertySchema( - id: 5, - name: r'serverId', - type: IsarType.string, - ), - r'startDate': PropertySchema( - id: 6, - name: r'startDate', - type: IsarType.dateTime, - ), - r'trainingMaxesJson': PropertySchema( - id: 7, - name: r'trainingMaxesJson', - type: IsarType.string, - ), - r'updatedAt': PropertySchema( - id: 8, - name: r'updatedAt', - type: IsarType.dateTime, - ), - r'userId': PropertySchema( - id: 9, - name: r'userId', - type: IsarType.string, - ) - }, - estimateSize: _cycleCollectionEstimateSize, - serialize: _cycleCollectionSerialize, - deserialize: _cycleCollectionDeserialize, - deserializeProp: _cycleCollectionDeserializeProp, - idName: r'id', - indexes: { - r'serverId': IndexSchema( - id: -7950187970872907662, - name: r'serverId', - unique: true, - replace: false, - properties: [ - IndexPropertySchema( - name: r'serverId', - type: IndexType.hash, - caseSensitive: true, - ) - ], - ) - }, - links: {}, - embeddedSchemas: {}, - getId: _cycleCollectionGetId, - getLinks: _cycleCollectionGetLinks, - attach: _cycleCollectionAttach, - version: '3.1.0+1', -); - -int _cycleCollectionEstimateSize( - CycleCollection object, - List offsets, - Map> allOffsets, -) { - var bytesCount = offsets.last; - { - final value = object.serverId; - if (value != null) { - bytesCount += 3 + value.length * 3; - } - } - bytesCount += 3 + object.trainingMaxesJson.length * 3; - bytesCount += 3 + object.userId.length * 3; - return bytesCount; -} - -void _cycleCollectionSerialize( - CycleCollection object, - IsarWriter writer, - List offsets, - Map> allOffsets, -) { - writer.writeDateTime(offsets[0], object.createdAt); - writer.writeLong(offsets[1], object.cycleNumber); - writer.writeDateTime(offsets[2], object.endDate); - writer.writeBool(offsets[3], object.isActive); - writer.writeBool(offsets[4], object.isDirty); - writer.writeString(offsets[5], object.serverId); - writer.writeDateTime(offsets[6], object.startDate); - writer.writeString(offsets[7], object.trainingMaxesJson); - writer.writeDateTime(offsets[8], object.updatedAt); - writer.writeString(offsets[9], object.userId); -} - -CycleCollection _cycleCollectionDeserialize( - Id id, - IsarReader reader, - List offsets, - Map> allOffsets, -) { - final object = CycleCollection(); - object.createdAt = reader.readDateTime(offsets[0]); - object.cycleNumber = reader.readLong(offsets[1]); - object.endDate = reader.readDateTimeOrNull(offsets[2]); - object.id = id; - object.isActive = reader.readBool(offsets[3]); - object.isDirty = reader.readBool(offsets[4]); - object.serverId = reader.readStringOrNull(offsets[5]); - object.startDate = reader.readDateTime(offsets[6]); - object.trainingMaxesJson = reader.readString(offsets[7]); - object.updatedAt = reader.readDateTime(offsets[8]); - object.userId = reader.readString(offsets[9]); - return object; -} - -P _cycleCollectionDeserializeProp

( - IsarReader reader, - int propertyId, - int offset, - Map> allOffsets, -) { - switch (propertyId) { - case 0: - return (reader.readDateTime(offset)) as P; - case 1: - return (reader.readLong(offset)) as P; - case 2: - return (reader.readDateTimeOrNull(offset)) as P; - case 3: - return (reader.readBool(offset)) as P; - case 4: - return (reader.readBool(offset)) as P; - case 5: - return (reader.readStringOrNull(offset)) as P; - case 6: - return (reader.readDateTime(offset)) as P; - case 7: - return (reader.readString(offset)) as P; - case 8: - return (reader.readDateTime(offset)) as P; - case 9: - return (reader.readString(offset)) as P; - default: - throw IsarError('Unknown property with id $propertyId'); - } -} - -Id _cycleCollectionGetId(CycleCollection object) { - return object.id; -} - -List> _cycleCollectionGetLinks(CycleCollection object) { - return []; -} - -void _cycleCollectionAttach( - IsarCollection col, Id id, CycleCollection object) { - object.id = id; -} - -extension CycleCollectionByIndex on IsarCollection { - Future getByServerId(String? serverId) { - return getByIndex(r'serverId', [serverId]); - } - - CycleCollection? getByServerIdSync(String? serverId) { - return getByIndexSync(r'serverId', [serverId]); - } - - Future deleteByServerId(String? serverId) { - return deleteByIndex(r'serverId', [serverId]); - } - - bool deleteByServerIdSync(String? serverId) { - return deleteByIndexSync(r'serverId', [serverId]); - } - - Future> getAllByServerId( - List serverIdValues) { - final values = serverIdValues.map((e) => [e]).toList(); - return getAllByIndex(r'serverId', values); - } - - List getAllByServerIdSync(List serverIdValues) { - final values = serverIdValues.map((e) => [e]).toList(); - return getAllByIndexSync(r'serverId', values); - } - - Future deleteAllByServerId(List serverIdValues) { - final values = serverIdValues.map((e) => [e]).toList(); - return deleteAllByIndex(r'serverId', values); - } - - int deleteAllByServerIdSync(List serverIdValues) { - final values = serverIdValues.map((e) => [e]).toList(); - return deleteAllByIndexSync(r'serverId', values); - } - - Future putByServerId(CycleCollection object) { - return putByIndex(r'serverId', object); - } - - Id putByServerIdSync(CycleCollection object, {bool saveLinks = true}) { - return putByIndexSync(r'serverId', object, saveLinks: saveLinks); - } - - Future> putAllByServerId(List objects) { - return putAllByIndex(r'serverId', objects); - } - - List putAllByServerIdSync(List objects, - {bool saveLinks = true}) { - return putAllByIndexSync(r'serverId', objects, saveLinks: saveLinks); - } -} - -extension CycleCollectionQueryWhereSort - on QueryBuilder { - QueryBuilder anyId() { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(const IdWhereClause.any()); - }); - } -} - -extension CycleCollectionQueryWhere - on QueryBuilder { - QueryBuilder idEqualTo( - Id id) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IdWhereClause.between( - lower: id, - upper: id, - )); - }); - } - - QueryBuilder - idNotEqualTo(Id id) { - return QueryBuilder.apply(this, (query) { - if (query.whereSort == Sort.asc) { - return query - .addWhereClause( - IdWhereClause.lessThan(upper: id, includeUpper: false), - ) - .addWhereClause( - IdWhereClause.greaterThan(lower: id, includeLower: false), - ); - } else { - return query - .addWhereClause( - IdWhereClause.greaterThan(lower: id, includeLower: false), - ) - .addWhereClause( - IdWhereClause.lessThan(upper: id, includeUpper: false), - ); - } - }); - } - - QueryBuilder - idGreaterThan(Id id, {bool include = false}) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause( - IdWhereClause.greaterThan(lower: id, includeLower: include), - ); - }); - } - - QueryBuilder idLessThan( - Id id, - {bool include = false}) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause( - IdWhereClause.lessThan(upper: id, includeUpper: include), - ); - }); - } - - QueryBuilder idBetween( - Id lowerId, - Id upperId, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IdWhereClause.between( - lower: lowerId, - includeLower: includeLower, - upper: upperId, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - serverIdIsNull() { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.equalTo( - indexName: r'serverId', - value: [null], - )); - }); - } - - QueryBuilder - serverIdIsNotNull() { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.between( - indexName: r'serverId', - lower: [null], - includeLower: false, - upper: [], - )); - }); - } - - QueryBuilder - serverIdEqualTo(String? serverId) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.equalTo( - indexName: r'serverId', - value: [serverId], - )); - }); - } - - QueryBuilder - serverIdNotEqualTo(String? serverId) { - return QueryBuilder.apply(this, (query) { - if (query.whereSort == Sort.asc) { - return query - .addWhereClause(IndexWhereClause.between( - indexName: r'serverId', - lower: [], - upper: [serverId], - includeUpper: false, - )) - .addWhereClause(IndexWhereClause.between( - indexName: r'serverId', - lower: [serverId], - includeLower: false, - upper: [], - )); - } else { - return query - .addWhereClause(IndexWhereClause.between( - indexName: r'serverId', - lower: [serverId], - includeLower: false, - upper: [], - )) - .addWhereClause(IndexWhereClause.between( - indexName: r'serverId', - lower: [], - upper: [serverId], - includeUpper: false, - )); - } - }); - } -} - -extension CycleCollectionQueryFilter - on QueryBuilder { - QueryBuilder - createdAtEqualTo(DateTime value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'createdAt', - value: value, - )); - }); - } - - QueryBuilder - createdAtGreaterThan( - DateTime value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'createdAt', - value: value, - )); - }); - } - - QueryBuilder - createdAtLessThan( - DateTime value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'createdAt', - value: value, - )); - }); - } - - QueryBuilder - createdAtBetween( - DateTime lower, - DateTime upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'createdAt', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - cycleNumberEqualTo(int value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'cycleNumber', - value: value, - )); - }); - } - - QueryBuilder - cycleNumberGreaterThan( - int value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'cycleNumber', - value: value, - )); - }); - } - - QueryBuilder - cycleNumberLessThan( - int value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'cycleNumber', - value: value, - )); - }); - } - - QueryBuilder - cycleNumberBetween( - int lower, - int upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'cycleNumber', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - endDateIsNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNull( - property: r'endDate', - )); - }); - } - - QueryBuilder - endDateIsNotNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNotNull( - property: r'endDate', - )); - }); - } - - QueryBuilder - endDateEqualTo(DateTime? value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'endDate', - value: value, - )); - }); - } - - QueryBuilder - endDateGreaterThan( - DateTime? value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'endDate', - value: value, - )); - }); - } - - QueryBuilder - endDateLessThan( - DateTime? value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'endDate', - value: value, - )); - }); - } - - QueryBuilder - endDateBetween( - DateTime? lower, - DateTime? upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'endDate', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - idEqualTo(Id value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'id', - value: value, - )); - }); - } - - QueryBuilder - idGreaterThan( - Id value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'id', - value: value, - )); - }); - } - - QueryBuilder - idLessThan( - Id value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'id', - value: value, - )); - }); - } - - QueryBuilder - idBetween( - Id lower, - Id upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'id', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - isActiveEqualTo(bool value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'isActive', - value: value, - )); - }); - } - - QueryBuilder - isDirtyEqualTo(bool value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'isDirty', - value: value, - )); - }); - } - - QueryBuilder - serverIdIsNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNull( - property: r'serverId', - )); - }); - } - - QueryBuilder - serverIdIsNotNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNotNull( - property: r'serverId', - )); - }); - } - - QueryBuilder - serverIdEqualTo( - String? value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdGreaterThan( - String? value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdLessThan( - String? value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdBetween( - String? lower, - String? upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'serverId', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdContains(String value, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdMatches(String pattern, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'serverId', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'serverId', - value: '', - )); - }); - } - - QueryBuilder - serverIdIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'serverId', - value: '', - )); - }); - } - - QueryBuilder - startDateEqualTo(DateTime value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'startDate', - value: value, - )); - }); - } - - QueryBuilder - startDateGreaterThan( - DateTime value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'startDate', - value: value, - )); - }); - } - - QueryBuilder - startDateLessThan( - DateTime value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'startDate', - value: value, - )); - }); - } - - QueryBuilder - startDateBetween( - DateTime lower, - DateTime upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'startDate', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - trainingMaxesJsonEqualTo( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'trainingMaxesJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - trainingMaxesJsonGreaterThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'trainingMaxesJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - trainingMaxesJsonLessThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'trainingMaxesJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - trainingMaxesJsonBetween( - String lower, - String upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'trainingMaxesJson', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - trainingMaxesJsonStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'trainingMaxesJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - trainingMaxesJsonEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'trainingMaxesJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - trainingMaxesJsonContains(String value, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'trainingMaxesJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - trainingMaxesJsonMatches(String pattern, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'trainingMaxesJson', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - trainingMaxesJsonIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'trainingMaxesJson', - value: '', - )); - }); - } - - QueryBuilder - trainingMaxesJsonIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'trainingMaxesJson', - value: '', - )); - }); - } - - QueryBuilder - updatedAtEqualTo(DateTime value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'updatedAt', - value: value, - )); - }); - } - - QueryBuilder - updatedAtGreaterThan( - DateTime value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'updatedAt', - value: value, - )); - }); - } - - QueryBuilder - updatedAtLessThan( - DateTime value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'updatedAt', - value: value, - )); - }); - } - - QueryBuilder - updatedAtBetween( - DateTime lower, - DateTime upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'updatedAt', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - userIdEqualTo( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'userId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - userIdGreaterThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'userId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - userIdLessThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'userId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - userIdBetween( - String lower, - String upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'userId', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - userIdStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'userId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - userIdEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'userId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - userIdContains(String value, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'userId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - userIdMatches(String pattern, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'userId', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - userIdIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'userId', - value: '', - )); - }); - } - - QueryBuilder - userIdIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'userId', - value: '', - )); - }); - } -} - -extension CycleCollectionQueryObject - on QueryBuilder {} - -extension CycleCollectionQueryLinks - on QueryBuilder {} - -extension CycleCollectionQuerySortBy - on QueryBuilder { - QueryBuilder - sortByCreatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'createdAt', Sort.asc); - }); - } - - QueryBuilder - sortByCreatedAtDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'createdAt', Sort.desc); - }); - } - - QueryBuilder - sortByCycleNumber() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'cycleNumber', Sort.asc); - }); - } - - QueryBuilder - sortByCycleNumberDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'cycleNumber', Sort.desc); - }); - } - - QueryBuilder sortByEndDate() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'endDate', Sort.asc); - }); - } - - QueryBuilder - sortByEndDateDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'endDate', Sort.desc); - }); - } - - QueryBuilder - sortByIsActive() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isActive', Sort.asc); - }); - } - - QueryBuilder - sortByIsActiveDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isActive', Sort.desc); - }); - } - - QueryBuilder sortByIsDirty() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isDirty', Sort.asc); - }); - } - - QueryBuilder - sortByIsDirtyDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isDirty', Sort.desc); - }); - } - - QueryBuilder - sortByServerId() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'serverId', Sort.asc); - }); - } - - QueryBuilder - sortByServerIdDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'serverId', Sort.desc); - }); - } - - QueryBuilder - sortByStartDate() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'startDate', Sort.asc); - }); - } - - QueryBuilder - sortByStartDateDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'startDate', Sort.desc); - }); - } - - QueryBuilder - sortByTrainingMaxesJson() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'trainingMaxesJson', Sort.asc); - }); - } - - QueryBuilder - sortByTrainingMaxesJsonDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'trainingMaxesJson', Sort.desc); - }); - } - - QueryBuilder - sortByUpdatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'updatedAt', Sort.asc); - }); - } - - QueryBuilder - sortByUpdatedAtDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'updatedAt', Sort.desc); - }); - } - - QueryBuilder sortByUserId() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'userId', Sort.asc); - }); - } - - QueryBuilder - sortByUserIdDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'userId', Sort.desc); - }); - } -} - -extension CycleCollectionQuerySortThenBy - on QueryBuilder { - QueryBuilder - thenByCreatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'createdAt', Sort.asc); - }); - } - - QueryBuilder - thenByCreatedAtDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'createdAt', Sort.desc); - }); - } - - QueryBuilder - thenByCycleNumber() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'cycleNumber', Sort.asc); - }); - } - - QueryBuilder - thenByCycleNumberDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'cycleNumber', Sort.desc); - }); - } - - QueryBuilder thenByEndDate() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'endDate', Sort.asc); - }); - } - - QueryBuilder - thenByEndDateDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'endDate', Sort.desc); - }); - } - - QueryBuilder thenById() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'id', Sort.asc); - }); - } - - QueryBuilder thenByIdDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'id', Sort.desc); - }); - } - - QueryBuilder - thenByIsActive() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isActive', Sort.asc); - }); - } - - QueryBuilder - thenByIsActiveDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isActive', Sort.desc); - }); - } - - QueryBuilder thenByIsDirty() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isDirty', Sort.asc); - }); - } - - QueryBuilder - thenByIsDirtyDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isDirty', Sort.desc); - }); - } - - QueryBuilder - thenByServerId() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'serverId', Sort.asc); - }); - } - - QueryBuilder - thenByServerIdDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'serverId', Sort.desc); - }); - } - - QueryBuilder - thenByStartDate() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'startDate', Sort.asc); - }); - } - - QueryBuilder - thenByStartDateDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'startDate', Sort.desc); - }); - } - - QueryBuilder - thenByTrainingMaxesJson() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'trainingMaxesJson', Sort.asc); - }); - } - - QueryBuilder - thenByTrainingMaxesJsonDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'trainingMaxesJson', Sort.desc); - }); - } - - QueryBuilder - thenByUpdatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'updatedAt', Sort.asc); - }); - } - - QueryBuilder - thenByUpdatedAtDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'updatedAt', Sort.desc); - }); - } - - QueryBuilder thenByUserId() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'userId', Sort.asc); - }); - } - - QueryBuilder - thenByUserIdDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'userId', Sort.desc); - }); - } -} - -extension CycleCollectionQueryWhereDistinct - on QueryBuilder { - QueryBuilder - distinctByCreatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'createdAt'); - }); - } - - QueryBuilder - distinctByCycleNumber() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'cycleNumber'); - }); - } - - QueryBuilder - distinctByEndDate() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'endDate'); - }); - } - - QueryBuilder - distinctByIsActive() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'isActive'); - }); - } - - QueryBuilder - distinctByIsDirty() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'isDirty'); - }); - } - - QueryBuilder distinctByServerId( - {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'serverId', caseSensitive: caseSensitive); - }); - } - - QueryBuilder - distinctByStartDate() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'startDate'); - }); - } - - QueryBuilder - distinctByTrainingMaxesJson({bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'trainingMaxesJson', - caseSensitive: caseSensitive); - }); - } - - QueryBuilder - distinctByUpdatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'updatedAt'); - }); - } - - QueryBuilder distinctByUserId( - {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'userId', caseSensitive: caseSensitive); - }); - } -} - -extension CycleCollectionQueryProperty - on QueryBuilder { - QueryBuilder idProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'id'); - }); - } - - QueryBuilder - createdAtProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'createdAt'); - }); - } - - QueryBuilder cycleNumberProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'cycleNumber'); - }); - } - - QueryBuilder endDateProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'endDate'); - }); - } - - QueryBuilder isActiveProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'isActive'); - }); - } - - QueryBuilder isDirtyProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'isDirty'); - }); - } - - QueryBuilder serverIdProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'serverId'); - }); - } - - QueryBuilder - startDateProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'startDate'); - }); - } - - QueryBuilder - trainingMaxesJsonProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'trainingMaxesJson'); - }); - } - - QueryBuilder - updatedAtProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'updatedAt'); - }); - } - - QueryBuilder userIdProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'userId'); - }); - } -} diff --git a/lib/src/shared/data/local/collections/user_collection.dart b/lib/src/shared/data/local/collections/user_collection.dart deleted file mode 100644 index 7dd3978..0000000 --- a/lib/src/shared/data/local/collections/user_collection.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:isar/isar.dart'; - -part 'user_collection.g.dart'; - -@collection -class UserCollection { - Id id = Isar.autoIncrement; - - @Index(unique: true) - String? serverId; - - String email = ''; - int xp = 0; - int level = 1; - double currentBodyweight = 70.0; - - String? inventorySettingsJson; - String? avatarConfigJson; - - DateTime? lastSyncAt; - bool isDirty = false; - - DateTime createdAt = DateTime.now(); - DateTime updatedAt = DateTime.now(); -} diff --git a/lib/src/shared/data/local/collections/user_collection.g.dart b/lib/src/shared/data/local/collections/user_collection.g.dart deleted file mode 100644 index 4ecd4ee..0000000 --- a/lib/src/shared/data/local/collections/user_collection.g.dart +++ /dev/null @@ -1,1921 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'user_collection.dart'; - -// ************************************************************************** -// IsarCollectionGenerator -// ************************************************************************** - -// coverage:ignore-file -// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types - -extension GetUserCollectionCollection on Isar { - IsarCollection get userCollections => this.collection(); -} - -const UserCollectionSchema = CollectionSchema( - name: r'UserCollection', - id: 1551134645489327298, - properties: { - r'avatarConfigJson': PropertySchema( - id: 0, - name: r'avatarConfigJson', - type: IsarType.string, - ), - r'createdAt': PropertySchema( - id: 1, - name: r'createdAt', - type: IsarType.dateTime, - ), - r'currentBodyweight': PropertySchema( - id: 2, - name: r'currentBodyweight', - type: IsarType.double, - ), - r'email': PropertySchema( - id: 3, - name: r'email', - type: IsarType.string, - ), - r'inventorySettingsJson': PropertySchema( - id: 4, - name: r'inventorySettingsJson', - type: IsarType.string, - ), - r'isDirty': PropertySchema( - id: 5, - name: r'isDirty', - type: IsarType.bool, - ), - r'lastSyncAt': PropertySchema( - id: 6, - name: r'lastSyncAt', - type: IsarType.dateTime, - ), - r'level': PropertySchema( - id: 7, - name: r'level', - type: IsarType.long, - ), - r'serverId': PropertySchema( - id: 8, - name: r'serverId', - type: IsarType.string, - ), - r'updatedAt': PropertySchema( - id: 9, - name: r'updatedAt', - type: IsarType.dateTime, - ), - r'xp': PropertySchema( - id: 10, - name: r'xp', - type: IsarType.long, - ) - }, - estimateSize: _userCollectionEstimateSize, - serialize: _userCollectionSerialize, - deserialize: _userCollectionDeserialize, - deserializeProp: _userCollectionDeserializeProp, - idName: r'id', - indexes: { - r'serverId': IndexSchema( - id: -7950187970872907662, - name: r'serverId', - unique: true, - replace: false, - properties: [ - IndexPropertySchema( - name: r'serverId', - type: IndexType.hash, - caseSensitive: true, - ) - ], - ) - }, - links: {}, - embeddedSchemas: {}, - getId: _userCollectionGetId, - getLinks: _userCollectionGetLinks, - attach: _userCollectionAttach, - version: '3.1.0+1', -); - -int _userCollectionEstimateSize( - UserCollection object, - List offsets, - Map> allOffsets, -) { - var bytesCount = offsets.last; - { - final value = object.avatarConfigJson; - if (value != null) { - bytesCount += 3 + value.length * 3; - } - } - bytesCount += 3 + object.email.length * 3; - { - final value = object.inventorySettingsJson; - if (value != null) { - bytesCount += 3 + value.length * 3; - } - } - { - final value = object.serverId; - if (value != null) { - bytesCount += 3 + value.length * 3; - } - } - return bytesCount; -} - -void _userCollectionSerialize( - UserCollection object, - IsarWriter writer, - List offsets, - Map> allOffsets, -) { - writer.writeString(offsets[0], object.avatarConfigJson); - writer.writeDateTime(offsets[1], object.createdAt); - writer.writeDouble(offsets[2], object.currentBodyweight); - writer.writeString(offsets[3], object.email); - writer.writeString(offsets[4], object.inventorySettingsJson); - writer.writeBool(offsets[5], object.isDirty); - writer.writeDateTime(offsets[6], object.lastSyncAt); - writer.writeLong(offsets[7], object.level); - writer.writeString(offsets[8], object.serverId); - writer.writeDateTime(offsets[9], object.updatedAt); - writer.writeLong(offsets[10], object.xp); -} - -UserCollection _userCollectionDeserialize( - Id id, - IsarReader reader, - List offsets, - Map> allOffsets, -) { - final object = UserCollection(); - object.avatarConfigJson = reader.readStringOrNull(offsets[0]); - object.createdAt = reader.readDateTime(offsets[1]); - object.currentBodyweight = reader.readDouble(offsets[2]); - object.email = reader.readString(offsets[3]); - object.id = id; - object.inventorySettingsJson = reader.readStringOrNull(offsets[4]); - object.isDirty = reader.readBool(offsets[5]); - object.lastSyncAt = reader.readDateTimeOrNull(offsets[6]); - object.level = reader.readLong(offsets[7]); - object.serverId = reader.readStringOrNull(offsets[8]); - object.updatedAt = reader.readDateTime(offsets[9]); - object.xp = reader.readLong(offsets[10]); - return object; -} - -P _userCollectionDeserializeProp

( - IsarReader reader, - int propertyId, - int offset, - Map> allOffsets, -) { - switch (propertyId) { - case 0: - return (reader.readStringOrNull(offset)) as P; - case 1: - return (reader.readDateTime(offset)) as P; - case 2: - return (reader.readDouble(offset)) as P; - case 3: - return (reader.readString(offset)) as P; - case 4: - return (reader.readStringOrNull(offset)) as P; - case 5: - return (reader.readBool(offset)) as P; - case 6: - return (reader.readDateTimeOrNull(offset)) as P; - case 7: - return (reader.readLong(offset)) as P; - case 8: - return (reader.readStringOrNull(offset)) as P; - case 9: - return (reader.readDateTime(offset)) as P; - case 10: - return (reader.readLong(offset)) as P; - default: - throw IsarError('Unknown property with id $propertyId'); - } -} - -Id _userCollectionGetId(UserCollection object) { - return object.id; -} - -List> _userCollectionGetLinks(UserCollection object) { - return []; -} - -void _userCollectionAttach( - IsarCollection col, Id id, UserCollection object) { - object.id = id; -} - -extension UserCollectionByIndex on IsarCollection { - Future getByServerId(String? serverId) { - return getByIndex(r'serverId', [serverId]); - } - - UserCollection? getByServerIdSync(String? serverId) { - return getByIndexSync(r'serverId', [serverId]); - } - - Future deleteByServerId(String? serverId) { - return deleteByIndex(r'serverId', [serverId]); - } - - bool deleteByServerIdSync(String? serverId) { - return deleteByIndexSync(r'serverId', [serverId]); - } - - Future> getAllByServerId(List serverIdValues) { - final values = serverIdValues.map((e) => [e]).toList(); - return getAllByIndex(r'serverId', values); - } - - List getAllByServerIdSync(List serverIdValues) { - final values = serverIdValues.map((e) => [e]).toList(); - return getAllByIndexSync(r'serverId', values); - } - - Future deleteAllByServerId(List serverIdValues) { - final values = serverIdValues.map((e) => [e]).toList(); - return deleteAllByIndex(r'serverId', values); - } - - int deleteAllByServerIdSync(List serverIdValues) { - final values = serverIdValues.map((e) => [e]).toList(); - return deleteAllByIndexSync(r'serverId', values); - } - - Future putByServerId(UserCollection object) { - return putByIndex(r'serverId', object); - } - - Id putByServerIdSync(UserCollection object, {bool saveLinks = true}) { - return putByIndexSync(r'serverId', object, saveLinks: saveLinks); - } - - Future> putAllByServerId(List objects) { - return putAllByIndex(r'serverId', objects); - } - - List putAllByServerIdSync(List objects, - {bool saveLinks = true}) { - return putAllByIndexSync(r'serverId', objects, saveLinks: saveLinks); - } -} - -extension UserCollectionQueryWhereSort - on QueryBuilder { - QueryBuilder anyId() { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(const IdWhereClause.any()); - }); - } -} - -extension UserCollectionQueryWhere - on QueryBuilder { - QueryBuilder idEqualTo( - Id id) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IdWhereClause.between( - lower: id, - upper: id, - )); - }); - } - - QueryBuilder idNotEqualTo( - Id id) { - return QueryBuilder.apply(this, (query) { - if (query.whereSort == Sort.asc) { - return query - .addWhereClause( - IdWhereClause.lessThan(upper: id, includeUpper: false), - ) - .addWhereClause( - IdWhereClause.greaterThan(lower: id, includeLower: false), - ); - } else { - return query - .addWhereClause( - IdWhereClause.greaterThan(lower: id, includeLower: false), - ) - .addWhereClause( - IdWhereClause.lessThan(upper: id, includeUpper: false), - ); - } - }); - } - - QueryBuilder idGreaterThan( - Id id, - {bool include = false}) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause( - IdWhereClause.greaterThan(lower: id, includeLower: include), - ); - }); - } - - QueryBuilder idLessThan( - Id id, - {bool include = false}) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause( - IdWhereClause.lessThan(upper: id, includeUpper: include), - ); - }); - } - - QueryBuilder idBetween( - Id lowerId, - Id upperId, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IdWhereClause.between( - lower: lowerId, - includeLower: includeLower, - upper: upperId, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - serverIdIsNull() { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.equalTo( - indexName: r'serverId', - value: [null], - )); - }); - } - - QueryBuilder - serverIdIsNotNull() { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.between( - indexName: r'serverId', - lower: [null], - includeLower: false, - upper: [], - )); - }); - } - - QueryBuilder - serverIdEqualTo(String? serverId) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.equalTo( - indexName: r'serverId', - value: [serverId], - )); - }); - } - - QueryBuilder - serverIdNotEqualTo(String? serverId) { - return QueryBuilder.apply(this, (query) { - if (query.whereSort == Sort.asc) { - return query - .addWhereClause(IndexWhereClause.between( - indexName: r'serverId', - lower: [], - upper: [serverId], - includeUpper: false, - )) - .addWhereClause(IndexWhereClause.between( - indexName: r'serverId', - lower: [serverId], - includeLower: false, - upper: [], - )); - } else { - return query - .addWhereClause(IndexWhereClause.between( - indexName: r'serverId', - lower: [serverId], - includeLower: false, - upper: [], - )) - .addWhereClause(IndexWhereClause.between( - indexName: r'serverId', - lower: [], - upper: [serverId], - includeUpper: false, - )); - } - }); - } -} - -extension UserCollectionQueryFilter - on QueryBuilder { - QueryBuilder - avatarConfigJsonIsNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNull( - property: r'avatarConfigJson', - )); - }); - } - - QueryBuilder - avatarConfigJsonIsNotNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNotNull( - property: r'avatarConfigJson', - )); - }); - } - - QueryBuilder - avatarConfigJsonEqualTo( - String? value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'avatarConfigJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - avatarConfigJsonGreaterThan( - String? value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'avatarConfigJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - avatarConfigJsonLessThan( - String? value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'avatarConfigJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - avatarConfigJsonBetween( - String? lower, - String? upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'avatarConfigJson', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - avatarConfigJsonStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'avatarConfigJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - avatarConfigJsonEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'avatarConfigJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - avatarConfigJsonContains(String value, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'avatarConfigJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - avatarConfigJsonMatches(String pattern, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'avatarConfigJson', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - avatarConfigJsonIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'avatarConfigJson', - value: '', - )); - }); - } - - QueryBuilder - avatarConfigJsonIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'avatarConfigJson', - value: '', - )); - }); - } - - QueryBuilder - createdAtEqualTo(DateTime value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'createdAt', - value: value, - )); - }); - } - - QueryBuilder - createdAtGreaterThan( - DateTime value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'createdAt', - value: value, - )); - }); - } - - QueryBuilder - createdAtLessThan( - DateTime value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'createdAt', - value: value, - )); - }); - } - - QueryBuilder - createdAtBetween( - DateTime lower, - DateTime upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'createdAt', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - currentBodyweightEqualTo( - double value, { - double epsilon = Query.epsilon, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'currentBodyweight', - value: value, - epsilon: epsilon, - )); - }); - } - - QueryBuilder - currentBodyweightGreaterThan( - double value, { - bool include = false, - double epsilon = Query.epsilon, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'currentBodyweight', - value: value, - epsilon: epsilon, - )); - }); - } - - QueryBuilder - currentBodyweightLessThan( - double value, { - bool include = false, - double epsilon = Query.epsilon, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'currentBodyweight', - value: value, - epsilon: epsilon, - )); - }); - } - - QueryBuilder - currentBodyweightBetween( - double lower, - double upper, { - bool includeLower = true, - bool includeUpper = true, - double epsilon = Query.epsilon, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'currentBodyweight', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - epsilon: epsilon, - )); - }); - } - - QueryBuilder - emailEqualTo( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'email', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - emailGreaterThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'email', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - emailLessThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'email', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - emailBetween( - String lower, - String upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'email', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - emailStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'email', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - emailEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'email', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - emailContains(String value, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'email', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - emailMatches(String pattern, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'email', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - emailIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'email', - value: '', - )); - }); - } - - QueryBuilder - emailIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'email', - value: '', - )); - }); - } - - QueryBuilder idEqualTo( - Id value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'id', - value: value, - )); - }); - } - - QueryBuilder - idGreaterThan( - Id value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'id', - value: value, - )); - }); - } - - QueryBuilder - idLessThan( - Id value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'id', - value: value, - )); - }); - } - - QueryBuilder idBetween( - Id lower, - Id upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'id', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - inventorySettingsJsonIsNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNull( - property: r'inventorySettingsJson', - )); - }); - } - - QueryBuilder - inventorySettingsJsonIsNotNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNotNull( - property: r'inventorySettingsJson', - )); - }); - } - - QueryBuilder - inventorySettingsJsonEqualTo( - String? value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'inventorySettingsJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - inventorySettingsJsonGreaterThan( - String? value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'inventorySettingsJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - inventorySettingsJsonLessThan( - String? value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'inventorySettingsJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - inventorySettingsJsonBetween( - String? lower, - String? upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'inventorySettingsJson', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - inventorySettingsJsonStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'inventorySettingsJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - inventorySettingsJsonEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'inventorySettingsJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - inventorySettingsJsonContains(String value, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'inventorySettingsJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - inventorySettingsJsonMatches(String pattern, - {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'inventorySettingsJson', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - inventorySettingsJsonIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'inventorySettingsJson', - value: '', - )); - }); - } - - QueryBuilder - inventorySettingsJsonIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'inventorySettingsJson', - value: '', - )); - }); - } - - QueryBuilder - isDirtyEqualTo(bool value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'isDirty', - value: value, - )); - }); - } - - QueryBuilder - lastSyncAtIsNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNull( - property: r'lastSyncAt', - )); - }); - } - - QueryBuilder - lastSyncAtIsNotNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNotNull( - property: r'lastSyncAt', - )); - }); - } - - QueryBuilder - lastSyncAtEqualTo(DateTime? value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'lastSyncAt', - value: value, - )); - }); - } - - QueryBuilder - lastSyncAtGreaterThan( - DateTime? value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'lastSyncAt', - value: value, - )); - }); - } - - QueryBuilder - lastSyncAtLessThan( - DateTime? value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'lastSyncAt', - value: value, - )); - }); - } - - QueryBuilder - lastSyncAtBetween( - DateTime? lower, - DateTime? upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'lastSyncAt', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - levelEqualTo(int value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'level', - value: value, - )); - }); - } - - QueryBuilder - levelGreaterThan( - int value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'level', - value: value, - )); - }); - } - - QueryBuilder - levelLessThan( - int value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'level', - value: value, - )); - }); - } - - QueryBuilder - levelBetween( - int lower, - int upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'level', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - serverIdIsNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNull( - property: r'serverId', - )); - }); - } - - QueryBuilder - serverIdIsNotNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNotNull( - property: r'serverId', - )); - }); - } - - QueryBuilder - serverIdEqualTo( - String? value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdGreaterThan( - String? value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdLessThan( - String? value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdBetween( - String? lower, - String? upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'serverId', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdContains(String value, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdMatches(String pattern, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'serverId', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'serverId', - value: '', - )); - }); - } - - QueryBuilder - serverIdIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'serverId', - value: '', - )); - }); - } - - QueryBuilder - updatedAtEqualTo(DateTime value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'updatedAt', - value: value, - )); - }); - } - - QueryBuilder - updatedAtGreaterThan( - DateTime value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'updatedAt', - value: value, - )); - }); - } - - QueryBuilder - updatedAtLessThan( - DateTime value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'updatedAt', - value: value, - )); - }); - } - - QueryBuilder - updatedAtBetween( - DateTime lower, - DateTime upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'updatedAt', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder xpEqualTo( - int value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'xp', - value: value, - )); - }); - } - - QueryBuilder - xpGreaterThan( - int value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'xp', - value: value, - )); - }); - } - - QueryBuilder - xpLessThan( - int value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'xp', - value: value, - )); - }); - } - - QueryBuilder xpBetween( - int lower, - int upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'xp', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } -} - -extension UserCollectionQueryObject - on QueryBuilder {} - -extension UserCollectionQueryLinks - on QueryBuilder {} - -extension UserCollectionQuerySortBy - on QueryBuilder { - QueryBuilder - sortByAvatarConfigJson() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'avatarConfigJson', Sort.asc); - }); - } - - QueryBuilder - sortByAvatarConfigJsonDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'avatarConfigJson', Sort.desc); - }); - } - - QueryBuilder sortByCreatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'createdAt', Sort.asc); - }); - } - - QueryBuilder - sortByCreatedAtDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'createdAt', Sort.desc); - }); - } - - QueryBuilder - sortByCurrentBodyweight() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'currentBodyweight', Sort.asc); - }); - } - - QueryBuilder - sortByCurrentBodyweightDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'currentBodyweight', Sort.desc); - }); - } - - QueryBuilder sortByEmail() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'email', Sort.asc); - }); - } - - QueryBuilder sortByEmailDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'email', Sort.desc); - }); - } - - QueryBuilder - sortByInventorySettingsJson() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'inventorySettingsJson', Sort.asc); - }); - } - - QueryBuilder - sortByInventorySettingsJsonDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'inventorySettingsJson', Sort.desc); - }); - } - - QueryBuilder sortByIsDirty() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isDirty', Sort.asc); - }); - } - - QueryBuilder - sortByIsDirtyDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isDirty', Sort.desc); - }); - } - - QueryBuilder - sortByLastSyncAt() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'lastSyncAt', Sort.asc); - }); - } - - QueryBuilder - sortByLastSyncAtDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'lastSyncAt', Sort.desc); - }); - } - - QueryBuilder sortByLevel() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'level', Sort.asc); - }); - } - - QueryBuilder sortByLevelDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'level', Sort.desc); - }); - } - - QueryBuilder sortByServerId() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'serverId', Sort.asc); - }); - } - - QueryBuilder - sortByServerIdDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'serverId', Sort.desc); - }); - } - - QueryBuilder sortByUpdatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'updatedAt', Sort.asc); - }); - } - - QueryBuilder - sortByUpdatedAtDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'updatedAt', Sort.desc); - }); - } - - QueryBuilder sortByXp() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'xp', Sort.asc); - }); - } - - QueryBuilder sortByXpDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'xp', Sort.desc); - }); - } -} - -extension UserCollectionQuerySortThenBy - on QueryBuilder { - QueryBuilder - thenByAvatarConfigJson() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'avatarConfigJson', Sort.asc); - }); - } - - QueryBuilder - thenByAvatarConfigJsonDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'avatarConfigJson', Sort.desc); - }); - } - - QueryBuilder thenByCreatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'createdAt', Sort.asc); - }); - } - - QueryBuilder - thenByCreatedAtDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'createdAt', Sort.desc); - }); - } - - QueryBuilder - thenByCurrentBodyweight() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'currentBodyweight', Sort.asc); - }); - } - - QueryBuilder - thenByCurrentBodyweightDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'currentBodyweight', Sort.desc); - }); - } - - QueryBuilder thenByEmail() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'email', Sort.asc); - }); - } - - QueryBuilder thenByEmailDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'email', Sort.desc); - }); - } - - QueryBuilder thenById() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'id', Sort.asc); - }); - } - - QueryBuilder thenByIdDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'id', Sort.desc); - }); - } - - QueryBuilder - thenByInventorySettingsJson() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'inventorySettingsJson', Sort.asc); - }); - } - - QueryBuilder - thenByInventorySettingsJsonDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'inventorySettingsJson', Sort.desc); - }); - } - - QueryBuilder thenByIsDirty() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isDirty', Sort.asc); - }); - } - - QueryBuilder - thenByIsDirtyDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isDirty', Sort.desc); - }); - } - - QueryBuilder - thenByLastSyncAt() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'lastSyncAt', Sort.asc); - }); - } - - QueryBuilder - thenByLastSyncAtDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'lastSyncAt', Sort.desc); - }); - } - - QueryBuilder thenByLevel() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'level', Sort.asc); - }); - } - - QueryBuilder thenByLevelDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'level', Sort.desc); - }); - } - - QueryBuilder thenByServerId() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'serverId', Sort.asc); - }); - } - - QueryBuilder - thenByServerIdDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'serverId', Sort.desc); - }); - } - - QueryBuilder thenByUpdatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'updatedAt', Sort.asc); - }); - } - - QueryBuilder - thenByUpdatedAtDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'updatedAt', Sort.desc); - }); - } - - QueryBuilder thenByXp() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'xp', Sort.asc); - }); - } - - QueryBuilder thenByXpDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'xp', Sort.desc); - }); - } -} - -extension UserCollectionQueryWhereDistinct - on QueryBuilder { - QueryBuilder - distinctByAvatarConfigJson({bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'avatarConfigJson', - caseSensitive: caseSensitive); - }); - } - - QueryBuilder - distinctByCreatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'createdAt'); - }); - } - - QueryBuilder - distinctByCurrentBodyweight() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'currentBodyweight'); - }); - } - - QueryBuilder distinctByEmail( - {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'email', caseSensitive: caseSensitive); - }); - } - - QueryBuilder - distinctByInventorySettingsJson({bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'inventorySettingsJson', - caseSensitive: caseSensitive); - }); - } - - QueryBuilder distinctByIsDirty() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'isDirty'); - }); - } - - QueryBuilder - distinctByLastSyncAt() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'lastSyncAt'); - }); - } - - QueryBuilder distinctByLevel() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'level'); - }); - } - - QueryBuilder distinctByServerId( - {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'serverId', caseSensitive: caseSensitive); - }); - } - - QueryBuilder - distinctByUpdatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'updatedAt'); - }); - } - - QueryBuilder distinctByXp() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'xp'); - }); - } -} - -extension UserCollectionQueryProperty - on QueryBuilder { - QueryBuilder idProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'id'); - }); - } - - QueryBuilder - avatarConfigJsonProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'avatarConfigJson'); - }); - } - - QueryBuilder createdAtProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'createdAt'); - }); - } - - QueryBuilder - currentBodyweightProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'currentBodyweight'); - }); - } - - QueryBuilder emailProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'email'); - }); - } - - QueryBuilder - inventorySettingsJsonProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'inventorySettingsJson'); - }); - } - - QueryBuilder isDirtyProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'isDirty'); - }); - } - - QueryBuilder - lastSyncAtProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'lastSyncAt'); - }); - } - - QueryBuilder levelProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'level'); - }); - } - - QueryBuilder serverIdProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'serverId'); - }); - } - - QueryBuilder updatedAtProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'updatedAt'); - }); - } - - QueryBuilder xpProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'xp'); - }); - } -} diff --git a/lib/src/shared/data/local/collections/workout_collection.dart b/lib/src/shared/data/local/collections/workout_collection.dart deleted file mode 100644 index dc3c6d8..0000000 --- a/lib/src/shared/data/local/collections/workout_collection.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:isar/isar.dart'; - -part 'workout_collection.g.dart'; - -@collection -class WorkoutCollection { - Id id = Isar.autoIncrement; - - // @Index(unique: true) - @Index() - String? serverId; - - String userId = ''; - String cycleId = ''; - - int week = 1; // 1-4 - int day = 1; // 1-3 - - DateTime? scheduledDate; - DateTime? completedAt; - - int xpEarned = 0; - - // Exercises data (JSON string) - String exercisesJson = '[]'; - - String notes = ''; - - bool isDirty = false; - DateTime createdAt = DateTime.now(); - DateTime updatedAt = DateTime.now(); -} diff --git a/lib/src/shared/data/local/collections/workout_collection.g.dart b/lib/src/shared/data/local/collections/workout_collection.g.dart deleted file mode 100644 index fe6fde4..0000000 --- a/lib/src/shared/data/local/collections/workout_collection.g.dart +++ /dev/null @@ -1,2145 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'workout_collection.dart'; - -// ************************************************************************** -// IsarCollectionGenerator -// ************************************************************************** - -// coverage:ignore-file -// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types - -extension GetWorkoutCollectionCollection on Isar { - IsarCollection get workoutCollections => this.collection(); -} - -const WorkoutCollectionSchema = CollectionSchema( - name: r'WorkoutCollection', - id: -3773153767250735449, - properties: { - r'completedAt': PropertySchema( - id: 0, - name: r'completedAt', - type: IsarType.dateTime, - ), - r'createdAt': PropertySchema( - id: 1, - name: r'createdAt', - type: IsarType.dateTime, - ), - r'cycleId': PropertySchema( - id: 2, - name: r'cycleId', - type: IsarType.string, - ), - r'day': PropertySchema( - id: 3, - name: r'day', - type: IsarType.long, - ), - r'exercisesJson': PropertySchema( - id: 4, - name: r'exercisesJson', - type: IsarType.string, - ), - r'isDirty': PropertySchema( - id: 5, - name: r'isDirty', - type: IsarType.bool, - ), - r'notes': PropertySchema( - id: 6, - name: r'notes', - type: IsarType.string, - ), - r'scheduledDate': PropertySchema( - id: 7, - name: r'scheduledDate', - type: IsarType.dateTime, - ), - r'serverId': PropertySchema( - id: 8, - name: r'serverId', - type: IsarType.string, - ), - r'updatedAt': PropertySchema( - id: 9, - name: r'updatedAt', - type: IsarType.dateTime, - ), - r'userId': PropertySchema( - id: 10, - name: r'userId', - type: IsarType.string, - ), - r'week': PropertySchema( - id: 11, - name: r'week', - type: IsarType.long, - ), - r'xpEarned': PropertySchema( - id: 12, - name: r'xpEarned', - type: IsarType.long, - ) - }, - estimateSize: _workoutCollectionEstimateSize, - serialize: _workoutCollectionSerialize, - deserialize: _workoutCollectionDeserialize, - deserializeProp: _workoutCollectionDeserializeProp, - idName: r'id', - indexes: { - r'serverId': IndexSchema( - id: -7950187970872907662, - name: r'serverId', - unique: false, - replace: false, - properties: [ - IndexPropertySchema( - name: r'serverId', - type: IndexType.hash, - caseSensitive: true, - ) - ], - ) - }, - links: {}, - embeddedSchemas: {}, - getId: _workoutCollectionGetId, - getLinks: _workoutCollectionGetLinks, - attach: _workoutCollectionAttach, - version: '3.1.0+1', -); - -int _workoutCollectionEstimateSize( - WorkoutCollection object, - List offsets, - Map> allOffsets, -) { - var bytesCount = offsets.last; - bytesCount += 3 + object.cycleId.length * 3; - bytesCount += 3 + object.exercisesJson.length * 3; - bytesCount += 3 + object.notes.length * 3; - { - final value = object.serverId; - if (value != null) { - bytesCount += 3 + value.length * 3; - } - } - bytesCount += 3 + object.userId.length * 3; - return bytesCount; -} - -void _workoutCollectionSerialize( - WorkoutCollection object, - IsarWriter writer, - List offsets, - Map> allOffsets, -) { - writer.writeDateTime(offsets[0], object.completedAt); - writer.writeDateTime(offsets[1], object.createdAt); - writer.writeString(offsets[2], object.cycleId); - writer.writeLong(offsets[3], object.day); - writer.writeString(offsets[4], object.exercisesJson); - writer.writeBool(offsets[5], object.isDirty); - writer.writeString(offsets[6], object.notes); - writer.writeDateTime(offsets[7], object.scheduledDate); - writer.writeString(offsets[8], object.serverId); - writer.writeDateTime(offsets[9], object.updatedAt); - writer.writeString(offsets[10], object.userId); - writer.writeLong(offsets[11], object.week); - writer.writeLong(offsets[12], object.xpEarned); -} - -WorkoutCollection _workoutCollectionDeserialize( - Id id, - IsarReader reader, - List offsets, - Map> allOffsets, -) { - final object = WorkoutCollection(); - object.completedAt = reader.readDateTimeOrNull(offsets[0]); - object.createdAt = reader.readDateTime(offsets[1]); - object.cycleId = reader.readString(offsets[2]); - object.day = reader.readLong(offsets[3]); - object.exercisesJson = reader.readString(offsets[4]); - object.id = id; - object.isDirty = reader.readBool(offsets[5]); - object.notes = reader.readString(offsets[6]); - object.scheduledDate = reader.readDateTimeOrNull(offsets[7]); - object.serverId = reader.readStringOrNull(offsets[8]); - object.updatedAt = reader.readDateTime(offsets[9]); - object.userId = reader.readString(offsets[10]); - object.week = reader.readLong(offsets[11]); - object.xpEarned = reader.readLong(offsets[12]); - return object; -} - -P _workoutCollectionDeserializeProp

( - IsarReader reader, - int propertyId, - int offset, - Map> allOffsets, -) { - switch (propertyId) { - case 0: - return (reader.readDateTimeOrNull(offset)) as P; - case 1: - return (reader.readDateTime(offset)) as P; - case 2: - return (reader.readString(offset)) as P; - case 3: - return (reader.readLong(offset)) as P; - case 4: - return (reader.readString(offset)) as P; - case 5: - return (reader.readBool(offset)) as P; - case 6: - return (reader.readString(offset)) as P; - case 7: - return (reader.readDateTimeOrNull(offset)) as P; - case 8: - return (reader.readStringOrNull(offset)) as P; - case 9: - return (reader.readDateTime(offset)) as P; - case 10: - return (reader.readString(offset)) as P; - case 11: - return (reader.readLong(offset)) as P; - case 12: - return (reader.readLong(offset)) as P; - default: - throw IsarError('Unknown property with id $propertyId'); - } -} - -Id _workoutCollectionGetId(WorkoutCollection object) { - return object.id; -} - -List> _workoutCollectionGetLinks( - WorkoutCollection object) { - return []; -} - -void _workoutCollectionAttach( - IsarCollection col, Id id, WorkoutCollection object) { - object.id = id; -} - -extension WorkoutCollectionQueryWhereSort - on QueryBuilder { - QueryBuilder anyId() { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(const IdWhereClause.any()); - }); - } -} - -extension WorkoutCollectionQueryWhere - on QueryBuilder { - QueryBuilder - idEqualTo(Id id) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IdWhereClause.between( - lower: id, - upper: id, - )); - }); - } - - QueryBuilder - idNotEqualTo(Id id) { - return QueryBuilder.apply(this, (query) { - if (query.whereSort == Sort.asc) { - return query - .addWhereClause( - IdWhereClause.lessThan(upper: id, includeUpper: false), - ) - .addWhereClause( - IdWhereClause.greaterThan(lower: id, includeLower: false), - ); - } else { - return query - .addWhereClause( - IdWhereClause.greaterThan(lower: id, includeLower: false), - ) - .addWhereClause( - IdWhereClause.lessThan(upper: id, includeUpper: false), - ); - } - }); - } - - QueryBuilder - idGreaterThan(Id id, {bool include = false}) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause( - IdWhereClause.greaterThan(lower: id, includeLower: include), - ); - }); - } - - QueryBuilder - idLessThan(Id id, {bool include = false}) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause( - IdWhereClause.lessThan(upper: id, includeUpper: include), - ); - }); - } - - QueryBuilder - idBetween( - Id lowerId, - Id upperId, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IdWhereClause.between( - lower: lowerId, - includeLower: includeLower, - upper: upperId, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - serverIdIsNull() { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.equalTo( - indexName: r'serverId', - value: [null], - )); - }); - } - - QueryBuilder - serverIdIsNotNull() { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.between( - indexName: r'serverId', - lower: [null], - includeLower: false, - upper: [], - )); - }); - } - - QueryBuilder - serverIdEqualTo(String? serverId) { - return QueryBuilder.apply(this, (query) { - return query.addWhereClause(IndexWhereClause.equalTo( - indexName: r'serverId', - value: [serverId], - )); - }); - } - - QueryBuilder - serverIdNotEqualTo(String? serverId) { - return QueryBuilder.apply(this, (query) { - if (query.whereSort == Sort.asc) { - return query - .addWhereClause(IndexWhereClause.between( - indexName: r'serverId', - lower: [], - upper: [serverId], - includeUpper: false, - )) - .addWhereClause(IndexWhereClause.between( - indexName: r'serverId', - lower: [serverId], - includeLower: false, - upper: [], - )); - } else { - return query - .addWhereClause(IndexWhereClause.between( - indexName: r'serverId', - lower: [serverId], - includeLower: false, - upper: [], - )) - .addWhereClause(IndexWhereClause.between( - indexName: r'serverId', - lower: [], - upper: [serverId], - includeUpper: false, - )); - } - }); - } -} - -extension WorkoutCollectionQueryFilter - on QueryBuilder { - QueryBuilder - completedAtIsNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNull( - property: r'completedAt', - )); - }); - } - - QueryBuilder - completedAtIsNotNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNotNull( - property: r'completedAt', - )); - }); - } - - QueryBuilder - completedAtEqualTo(DateTime? value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'completedAt', - value: value, - )); - }); - } - - QueryBuilder - completedAtGreaterThan( - DateTime? value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'completedAt', - value: value, - )); - }); - } - - QueryBuilder - completedAtLessThan( - DateTime? value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'completedAt', - value: value, - )); - }); - } - - QueryBuilder - completedAtBetween( - DateTime? lower, - DateTime? upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'completedAt', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - createdAtEqualTo(DateTime value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'createdAt', - value: value, - )); - }); - } - - QueryBuilder - createdAtGreaterThan( - DateTime value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'createdAt', - value: value, - )); - }); - } - - QueryBuilder - createdAtLessThan( - DateTime value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'createdAt', - value: value, - )); - }); - } - - QueryBuilder - createdAtBetween( - DateTime lower, - DateTime upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'createdAt', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - cycleIdEqualTo( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'cycleId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - cycleIdGreaterThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'cycleId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - cycleIdLessThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'cycleId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - cycleIdBetween( - String lower, - String upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'cycleId', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - cycleIdStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'cycleId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - cycleIdEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'cycleId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - cycleIdContains(String value, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'cycleId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - cycleIdMatches(String pattern, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'cycleId', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - cycleIdIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'cycleId', - value: '', - )); - }); - } - - QueryBuilder - cycleIdIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'cycleId', - value: '', - )); - }); - } - - QueryBuilder - dayEqualTo(int value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'day', - value: value, - )); - }); - } - - QueryBuilder - dayGreaterThan( - int value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'day', - value: value, - )); - }); - } - - QueryBuilder - dayLessThan( - int value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'day', - value: value, - )); - }); - } - - QueryBuilder - dayBetween( - int lower, - int upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'day', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - exercisesJsonEqualTo( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'exercisesJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - exercisesJsonGreaterThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'exercisesJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - exercisesJsonLessThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'exercisesJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - exercisesJsonBetween( - String lower, - String upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'exercisesJson', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - exercisesJsonStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'exercisesJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - exercisesJsonEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'exercisesJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - exercisesJsonContains(String value, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'exercisesJson', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - exercisesJsonMatches(String pattern, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'exercisesJson', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - exercisesJsonIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'exercisesJson', - value: '', - )); - }); - } - - QueryBuilder - exercisesJsonIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'exercisesJson', - value: '', - )); - }); - } - - QueryBuilder - idEqualTo(Id value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'id', - value: value, - )); - }); - } - - QueryBuilder - idGreaterThan( - Id value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'id', - value: value, - )); - }); - } - - QueryBuilder - idLessThan( - Id value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'id', - value: value, - )); - }); - } - - QueryBuilder - idBetween( - Id lower, - Id upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'id', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - isDirtyEqualTo(bool value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'isDirty', - value: value, - )); - }); - } - - QueryBuilder - notesEqualTo( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'notes', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - notesGreaterThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'notes', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - notesLessThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'notes', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - notesBetween( - String lower, - String upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'notes', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - notesStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'notes', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - notesEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'notes', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - notesContains(String value, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'notes', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - notesMatches(String pattern, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'notes', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - notesIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'notes', - value: '', - )); - }); - } - - QueryBuilder - notesIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'notes', - value: '', - )); - }); - } - - QueryBuilder - scheduledDateIsNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNull( - property: r'scheduledDate', - )); - }); - } - - QueryBuilder - scheduledDateIsNotNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNotNull( - property: r'scheduledDate', - )); - }); - } - - QueryBuilder - scheduledDateEqualTo(DateTime? value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'scheduledDate', - value: value, - )); - }); - } - - QueryBuilder - scheduledDateGreaterThan( - DateTime? value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'scheduledDate', - value: value, - )); - }); - } - - QueryBuilder - scheduledDateLessThan( - DateTime? value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'scheduledDate', - value: value, - )); - }); - } - - QueryBuilder - scheduledDateBetween( - DateTime? lower, - DateTime? upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'scheduledDate', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - serverIdIsNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNull( - property: r'serverId', - )); - }); - } - - QueryBuilder - serverIdIsNotNull() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(const FilterCondition.isNotNull( - property: r'serverId', - )); - }); - } - - QueryBuilder - serverIdEqualTo( - String? value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdGreaterThan( - String? value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdLessThan( - String? value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdBetween( - String? lower, - String? upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'serverId', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdContains(String value, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'serverId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdMatches(String pattern, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'serverId', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - serverIdIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'serverId', - value: '', - )); - }); - } - - QueryBuilder - serverIdIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'serverId', - value: '', - )); - }); - } - - QueryBuilder - updatedAtEqualTo(DateTime value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'updatedAt', - value: value, - )); - }); - } - - QueryBuilder - updatedAtGreaterThan( - DateTime value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'updatedAt', - value: value, - )); - }); - } - - QueryBuilder - updatedAtLessThan( - DateTime value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'updatedAt', - value: value, - )); - }); - } - - QueryBuilder - updatedAtBetween( - DateTime lower, - DateTime upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'updatedAt', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - userIdEqualTo( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'userId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - userIdGreaterThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'userId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - userIdLessThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'userId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - userIdBetween( - String lower, - String upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'userId', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - userIdStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'userId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - userIdEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'userId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - userIdContains(String value, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'userId', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - userIdMatches(String pattern, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'userId', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - userIdIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'userId', - value: '', - )); - }); - } - - QueryBuilder - userIdIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'userId', - value: '', - )); - }); - } - - QueryBuilder - weekEqualTo(int value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'week', - value: value, - )); - }); - } - - QueryBuilder - weekGreaterThan( - int value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'week', - value: value, - )); - }); - } - - QueryBuilder - weekLessThan( - int value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'week', - value: value, - )); - }); - } - - QueryBuilder - weekBetween( - int lower, - int upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'week', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } - - QueryBuilder - xpEarnedEqualTo(int value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'xpEarned', - value: value, - )); - }); - } - - QueryBuilder - xpEarnedGreaterThan( - int value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'xpEarned', - value: value, - )); - }); - } - - QueryBuilder - xpEarnedLessThan( - int value, { - bool include = false, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'xpEarned', - value: value, - )); - }); - } - - QueryBuilder - xpEarnedBetween( - int lower, - int upper, { - bool includeLower = true, - bool includeUpper = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'xpEarned', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - )); - }); - } -} - -extension WorkoutCollectionQueryObject - on QueryBuilder {} - -extension WorkoutCollectionQueryLinks - on QueryBuilder {} - -extension WorkoutCollectionQuerySortBy - on QueryBuilder { - QueryBuilder - sortByCompletedAt() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'completedAt', Sort.asc); - }); - } - - QueryBuilder - sortByCompletedAtDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'completedAt', Sort.desc); - }); - } - - QueryBuilder - sortByCreatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'createdAt', Sort.asc); - }); - } - - QueryBuilder - sortByCreatedAtDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'createdAt', Sort.desc); - }); - } - - QueryBuilder - sortByCycleId() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'cycleId', Sort.asc); - }); - } - - QueryBuilder - sortByCycleIdDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'cycleId', Sort.desc); - }); - } - - QueryBuilder sortByDay() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'day', Sort.asc); - }); - } - - QueryBuilder - sortByDayDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'day', Sort.desc); - }); - } - - QueryBuilder - sortByExercisesJson() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'exercisesJson', Sort.asc); - }); - } - - QueryBuilder - sortByExercisesJsonDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'exercisesJson', Sort.desc); - }); - } - - QueryBuilder - sortByIsDirty() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isDirty', Sort.asc); - }); - } - - QueryBuilder - sortByIsDirtyDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isDirty', Sort.desc); - }); - } - - QueryBuilder - sortByNotes() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'notes', Sort.asc); - }); - } - - QueryBuilder - sortByNotesDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'notes', Sort.desc); - }); - } - - QueryBuilder - sortByScheduledDate() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'scheduledDate', Sort.asc); - }); - } - - QueryBuilder - sortByScheduledDateDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'scheduledDate', Sort.desc); - }); - } - - QueryBuilder - sortByServerId() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'serverId', Sort.asc); - }); - } - - QueryBuilder - sortByServerIdDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'serverId', Sort.desc); - }); - } - - QueryBuilder - sortByUpdatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'updatedAt', Sort.asc); - }); - } - - QueryBuilder - sortByUpdatedAtDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'updatedAt', Sort.desc); - }); - } - - QueryBuilder - sortByUserId() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'userId', Sort.asc); - }); - } - - QueryBuilder - sortByUserIdDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'userId', Sort.desc); - }); - } - - QueryBuilder - sortByWeek() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'week', Sort.asc); - }); - } - - QueryBuilder - sortByWeekDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'week', Sort.desc); - }); - } - - QueryBuilder - sortByXpEarned() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'xpEarned', Sort.asc); - }); - } - - QueryBuilder - sortByXpEarnedDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'xpEarned', Sort.desc); - }); - } -} - -extension WorkoutCollectionQuerySortThenBy - on QueryBuilder { - QueryBuilder - thenByCompletedAt() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'completedAt', Sort.asc); - }); - } - - QueryBuilder - thenByCompletedAtDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'completedAt', Sort.desc); - }); - } - - QueryBuilder - thenByCreatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'createdAt', Sort.asc); - }); - } - - QueryBuilder - thenByCreatedAtDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'createdAt', Sort.desc); - }); - } - - QueryBuilder - thenByCycleId() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'cycleId', Sort.asc); - }); - } - - QueryBuilder - thenByCycleIdDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'cycleId', Sort.desc); - }); - } - - QueryBuilder thenByDay() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'day', Sort.asc); - }); - } - - QueryBuilder - thenByDayDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'day', Sort.desc); - }); - } - - QueryBuilder - thenByExercisesJson() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'exercisesJson', Sort.asc); - }); - } - - QueryBuilder - thenByExercisesJsonDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'exercisesJson', Sort.desc); - }); - } - - QueryBuilder thenById() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'id', Sort.asc); - }); - } - - QueryBuilder - thenByIdDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'id', Sort.desc); - }); - } - - QueryBuilder - thenByIsDirty() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isDirty', Sort.asc); - }); - } - - QueryBuilder - thenByIsDirtyDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isDirty', Sort.desc); - }); - } - - QueryBuilder - thenByNotes() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'notes', Sort.asc); - }); - } - - QueryBuilder - thenByNotesDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'notes', Sort.desc); - }); - } - - QueryBuilder - thenByScheduledDate() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'scheduledDate', Sort.asc); - }); - } - - QueryBuilder - thenByScheduledDateDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'scheduledDate', Sort.desc); - }); - } - - QueryBuilder - thenByServerId() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'serverId', Sort.asc); - }); - } - - QueryBuilder - thenByServerIdDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'serverId', Sort.desc); - }); - } - - QueryBuilder - thenByUpdatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'updatedAt', Sort.asc); - }); - } - - QueryBuilder - thenByUpdatedAtDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'updatedAt', Sort.desc); - }); - } - - QueryBuilder - thenByUserId() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'userId', Sort.asc); - }); - } - - QueryBuilder - thenByUserIdDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'userId', Sort.desc); - }); - } - - QueryBuilder - thenByWeek() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'week', Sort.asc); - }); - } - - QueryBuilder - thenByWeekDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'week', Sort.desc); - }); - } - - QueryBuilder - thenByXpEarned() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'xpEarned', Sort.asc); - }); - } - - QueryBuilder - thenByXpEarnedDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'xpEarned', Sort.desc); - }); - } -} - -extension WorkoutCollectionQueryWhereDistinct - on QueryBuilder { - QueryBuilder - distinctByCompletedAt() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'completedAt'); - }); - } - - QueryBuilder - distinctByCreatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'createdAt'); - }); - } - - QueryBuilder - distinctByCycleId({bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'cycleId', caseSensitive: caseSensitive); - }); - } - - QueryBuilder - distinctByDay() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'day'); - }); - } - - QueryBuilder - distinctByExercisesJson({bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'exercisesJson', - caseSensitive: caseSensitive); - }); - } - - QueryBuilder - distinctByIsDirty() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'isDirty'); - }); - } - - QueryBuilder distinctByNotes( - {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'notes', caseSensitive: caseSensitive); - }); - } - - QueryBuilder - distinctByScheduledDate() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'scheduledDate'); - }); - } - - QueryBuilder - distinctByServerId({bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'serverId', caseSensitive: caseSensitive); - }); - } - - QueryBuilder - distinctByUpdatedAt() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'updatedAt'); - }); - } - - QueryBuilder - distinctByUserId({bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'userId', caseSensitive: caseSensitive); - }); - } - - QueryBuilder - distinctByWeek() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'week'); - }); - } - - QueryBuilder - distinctByXpEarned() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'xpEarned'); - }); - } -} - -extension WorkoutCollectionQueryProperty - on QueryBuilder { - QueryBuilder idProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'id'); - }); - } - - QueryBuilder - completedAtProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'completedAt'); - }); - } - - QueryBuilder - createdAtProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'createdAt'); - }); - } - - QueryBuilder cycleIdProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'cycleId'); - }); - } - - QueryBuilder dayProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'day'); - }); - } - - QueryBuilder - exercisesJsonProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'exercisesJson'); - }); - } - - QueryBuilder isDirtyProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'isDirty'); - }); - } - - QueryBuilder notesProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'notes'); - }); - } - - QueryBuilder - scheduledDateProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'scheduledDate'); - }); - } - - QueryBuilder - serverIdProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'serverId'); - }); - } - - QueryBuilder - updatedAtProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'updatedAt'); - }); - } - - QueryBuilder userIdProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'userId'); - }); - } - - QueryBuilder weekProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'week'); - }); - } - - QueryBuilder xpEarnedProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'xpEarned'); - }); - } -} diff --git a/lib/src/shared/data/local/converters/json_converter.dart b/lib/src/shared/data/local/converters/json_converter.dart new file mode 100644 index 0000000..80d82e7 --- /dev/null +++ b/lib/src/shared/data/local/converters/json_converter.dart @@ -0,0 +1,36 @@ +import 'dart:convert'; +import 'package:drift/drift.dart'; + +class MapConverter extends TypeConverter, String> { + const MapConverter(); + + @override + Map fromSql(String fromDb) { + if (fromDb.isEmpty) return {}; + try { + return json.decode(fromDb) as Map; + } catch (_) { + return {}; + } + } + + @override + String toSql(Map value) => json.encode(value); +} + +class ListConverter extends TypeConverter, String> { + const ListConverter(); + + @override + List fromSql(String fromDb) { + if (fromDb.isEmpty) return []; + try { + return json.decode(fromDb) as List; + } catch (_) { + return []; + } + } + + @override + String toSql(List value) => json.encode(value); +} diff --git a/lib/src/shared/data/local/tables.dart b/lib/src/shared/data/local/tables.dart new file mode 100644 index 0000000..e9703b9 --- /dev/null +++ b/lib/src/shared/data/local/tables.dart @@ -0,0 +1,92 @@ +import 'package:drift/drift.dart'; +import 'converters/json_converter.dart'; + +@DataClassName('UserCollection') +class Users extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get serverId => text().nullable().unique()(); + + TextColumn get email => text().withDefault(const Constant(''))(); + IntColumn get xp => integer().withDefault(const Constant(0))(); + IntColumn get level => integer().withDefault(const Constant(1))(); + RealColumn get currentBodyweight => + real().withDefault(const Constant(70.0))(); + TextColumn get exerciseVariants => + text().map(const MapConverter()).nullable()(); + + TextColumn get inventorySettings => + text().map(const MapConverter()).nullable()(); + TextColumn get avatarConfig => text().map(const MapConverter()).nullable()(); + + DateTimeColumn get lastSyncAt => dateTime().nullable()(); + BoolColumn get isDirty => boolean().withDefault(const Constant(false))(); + + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); +} + +@DataClassName('CycleCollection') +class Cycles extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get serverId => text().nullable().unique()(); + + TextColumn get userId => text()(); + + IntColumn get cycleNumber => integer()(); + DateTimeColumn get startDate => dateTime()(); + DateTimeColumn get endDate => dateTime().nullable()(); + BoolColumn get isActive => boolean().withDefault(const Constant(true))(); + + TextColumn get trainingMaxes => text().map(const MapConverter())(); + + BoolColumn get isDirty => boolean().withDefault(const Constant(false))(); + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); +} + +@DataClassName('WorkoutCollection') +class Workouts extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get serverId => text().nullable().unique()(); + + TextColumn get userId => text()(); + TextColumn get cycleId => text()(); + + IntColumn get week => integer()(); + IntColumn get day => integer()(); + + DateTimeColumn get scheduledDate => dateTime().nullable()(); + DateTimeColumn get completedAt => dateTime().nullable()(); + IntColumn get xpEarned => integer().withDefault(const Constant(0))(); + + TextColumn get exercises => text().map(const ListConverter())(); + TextColumn get notes => text().withDefault(const Constant(''))(); + + BoolColumn get isDirty => boolean().withDefault(const Constant(false))(); + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); +} + +@DataClassName('QuestCollection') +class Quests extends Table { + TextColumn get id => text()(); + + TextColumn get type => text()(); // 'daily', 'milestone', 'story' + TextColumn get title => text()(); + TextColumn get description => text()(); + + IntColumn get targetValue => integer()(); + IntColumn get currentValue => integer().withDefault(const Constant(0))(); + + IntColumn get rewardXP => integer()(); + TextColumn get rewardItem => text().nullable()(); + + BoolColumn get isCompleted => boolean().withDefault(const Constant(false))(); + BoolColumn get isClaimed => boolean().withDefault(const Constant(false))(); + + DateTimeColumn get expiresAt => dateTime().nullable()(); + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + + @override + Set get primaryKey => {id}; +} diff --git a/lib/src/shared/data/remote/api_client.dart b/lib/src/shared/data/remote/api_client.dart index d8d63a0..46a4ac7 100644 --- a/lib/src/shared/data/remote/api_client.dart +++ b/lib/src/shared/data/remote/api_client.dart @@ -10,6 +10,9 @@ class ApiClient { final FlutterSecureStorage _storage; final Logger _logger; + bool _isRefreshing = false; + final List _requestsQueue = []; + ApiClient({ FlutterSecureStorage? storage, Logger? logger, @@ -49,15 +52,117 @@ class ApiClient { }, onError: (error, handler) async { if (error.response?.statusCode == 401) { - _logger.w('Unauthorized - clearing token'); - await _storage.delete(key: AppConstants.keyAuthToken); + final token = await _storage.read(key: AppConstants.keyAuthToken); + + if (token != null && !_isRefreshing) { + _isRefreshing = true; + _logger.w('🔄 Token expired, attempting refresh...'); + + try { + final newToken = await refreshToken(); + + if (newToken != null) { + error.requestOptions.headers['Authorization'] = + 'Bearer $newToken'; + + final response = await _dio.fetch(error.requestOptions); + _isRefreshing = false; + + _processQueue(newToken); + + return handler.resolve(response); + } else { + _logger.e('❌ Token refresh failed - logging out'); + await _storage.delete(key: AppConstants.keyAuthToken); + _isRefreshing = false; + _clearQueue(); + return handler.next(error); + } + } catch (e) { + _logger.e('❌ Refresh error: $e'); + await _storage.delete(key: AppConstants.keyAuthToken); + _isRefreshing = false; + _clearQueue(); + return handler.next(error); + } + } else if (_isRefreshing) { + _logger.i('⏳ Waiting for token refresh...'); + return _queueRequest(() async { + final newToken = + await _storage.read(key: AppConstants.keyAuthToken); + if (newToken != null) { + error.requestOptions.headers['Authorization'] = + 'Bearer $newToken'; + return await _dio.fetch(error.requestOptions); + } + throw error; + }, handler); + } else { + await _storage.delete(key: AppConstants.keyAuthToken); + return handler.next(error); + } } + // onError: (error, handler) async { + // if (error.response?.statusCode == 401) { + // _logger.w('Unauthorized - clearing token'); + // await _storage.delete(key: AppConstants.keyAuthToken); + // } return handler.next(error); }, ), ); } + Future _queueRequest( + Future Function() request, + ErrorInterceptorHandler handler, + ) async { + _requestsQueue.add(() async { + try { + final response = await request(); + handler.resolve(response); + } catch (e) { + handler.reject(e as DioException); + } + }); + } + + void _processQueue(String newToken) { + for (final request in _requestsQueue) { + request(); + } + _requestsQueue.clear(); + } + + void _clearQueue() { + _requestsQueue.clear(); + } + + Future refreshToken() async { + try { + final token = await _storage.read(key: AppConstants.keyAuthToken); + if (token == null) return null; + + final response = await _dio.post( + '/api/collections/users/auth-refresh', + options: Options( + headers: {'Authorization': 'Bearer $token'}, + ), + ); + + final newToken = response.data['token']; + if (newToken != null) { + await _storage.write(key: AppConstants.keyAuthToken, value: newToken); + _logger.i('✅ Token refreshed successfully'); + return newToken; + } + return null; + } catch (e) { + _logger.e('❌ Token refresh failed', error: e); + return null; + } + } + Future> login(String email, String password) async { try { final response = await _dio.post( @@ -85,6 +190,7 @@ class ApiClient { required String password, required double bodyweight, required Map inventorySettings, + Map? exerciseVariants, }) async { try { final response = await _dio.post( @@ -97,6 +203,7 @@ class ApiClient { 'level': 1, 'current_bodyweight': bodyweight, 'inventory_settings': inventorySettings, + 'exercise_variants': exerciseVariants ?? {}, 'avatar_config': { 'skin_tone': 'medium', 'hair_style': 'short_01', diff --git a/lib/src/shared/data/remote/sync_service.dart b/lib/src/shared/data/remote/sync_service.dart index 543df98..2dcd380 100644 --- a/lib/src/shared/data/remote/sync_service.dart +++ b/lib/src/shared/data/remote/sync_service.dart @@ -1,30 +1,28 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:isar/isar.dart'; +import 'package:drift/drift.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import '../../../../main.dart'; import '../../../core/constants/app_constants.dart'; -import '../local/collections/user_collection.dart'; -import '../local/collections/cycle_collection.dart'; -import '../local/collections/workout_collection.dart'; -import 'api_client.dart'; +import '../local/app_database.dart'; import '../repositories/user_repository.dart'; +import 'api_client.dart'; final syncServiceProvider = Provider((ref) { - final isar = ref.watch(isarProvider); + final db = ref.watch(appDatabaseProvider); final apiClient = ref.watch(apiClientProvider); - return SyncService(isar: isar, apiClient: apiClient); + return SyncService(db: db, apiClient: apiClient); }); class SyncService { - final Isar isar; + final AppDatabase db; final ApiClient apiClient; final _storage = const FlutterSecureStorage(); bool _isSyncing = false; - SyncService({required this.isar, required this.apiClient}); + SyncService({required this.db, required this.apiClient}); Future sync() async { if (_isSyncing) return; @@ -32,160 +30,206 @@ class SyncService { try { debugPrint('🔄 Starting Sync...'); - final dirtyCycles = - await isar.cycleCollections.filter().isDirtyEqualTo(true).findAll(); + + final dirtyCycles = await (db.select(db.cycles) + ..where((c) => c.isDirty.equals(true))) + .get(); for (var cycle in dirtyCycles) { try { if (cycle.serverId == null) { - debugPrint( - '📤 Pushing new cycle ${cycle.cycleNumber} to server...'); - - Map tmsMap = {}; - try { - final tms = jsonDecode(cycle.trainingMaxesJson); - tmsMap = Map.from( - tms.map((k, v) => MapEntry(k, (v as num).toDouble()))); - } catch (e) { - debugPrint('⚠️ Error parsing TMs for cycle ${cycle.id}: $e'); - tmsMap = {'squat': 0.0, 'pullup': 0.0, 'dip': 0.0}; - } + debugPrint('📤 Pushing new cycle ${cycle.cycleNumber}...'); + final tmsMap = cycle.trainingMaxes + .map((k, v) => MapEntry(k, (v as num).toDouble())); final response = await apiClient.createCycle(tmsMap); final newServerId = response['id']; - await isar.writeTxn(() async { - cycle.serverId = newServerId; - cycle.isDirty = false; - await isar.cycleCollections.put(cycle); + await db.transaction(() async { + await (db.update(db.cycles)..where((c) => c.id.equals(cycle.id))) + .write( + CyclesCompanion( + serverId: Value(newServerId), + isDirty: const Value(false), + ), + ); final oldLocalIdRef = cycle.id.toString(); - - final orphanWorkouts = await isar.workoutCollections - .filter() - .cycleIdEqualTo(oldLocalIdRef) - .findAll(); - - for (var w in orphanWorkouts) { - w.cycleId = newServerId; - w.isDirty = true; - await isar.workoutCollections.put(w); - debugPrint('🔗 Relinked workout ${w.id} to cycle $newServerId'); - } + await (db.update(db.workouts) + ..where((w) => w.cycleId.equals(oldLocalIdRef))) + .write( + WorkoutsCompanion( + cycleId: Value(newServerId), + isDirty: const Value(true), + ), + ); }); } else { - await isar.writeTxn(() async { - cycle.isDirty = false; - await isar.cycleCollections.put(cycle); - }); + await (db.update(db.cycles)..where((c) => c.id.equals(cycle.id))) + .write(const CyclesCompanion(isDirty: Value(false))); } } catch (e) { - debugPrint('❌ Failed to sync cycle: $e'); - return; + debugPrint('❌ Failed to sync cycle ${cycle.id}: $e'); } } - final dirtyUser = - await isar.userCollections.filter().isDirtyEqualTo(true).findFirst(); + final dirtyUser = await (db.select(db.users) + ..where((u) => u.isDirty.equals(true))) + .getSingleOrNull(); + final dirtyWorkouts = await (db.select(db.workouts) + ..where((w) => w.isDirty.equals(true))) + .get(); - final dirtyWorkouts = - await isar.workoutCollections.filter().isDirtyEqualTo(true).findAll(); + final validWorkouts = + dirtyWorkouts.where((w) => w.cycleId.length > 5).toList(); - if (dirtyUser == null && dirtyWorkouts.isEmpty) { - debugPrint('✅ Nothing to push.'); - } else { - final pushData = { - 'workouts': dirtyWorkouts.where((w) { - return w.cycleId.length > 5; - }).map((w) { - return { - 'id': w.serverId, - 'local_id': w.id, - 'cycle_id': w.cycleId, - 'week': w.week, - 'day': w.day, - 'completed_at': w.completedAt?.toIso8601String(), - 'xp_earned': w.xpEarned, - 'notes': w.notes, - 'exercises': jsonDecode(w.exercisesJson), - }; - }).toList(), - 'user_stats': dirtyUser != null - ? { - 'xp': dirtyUser.xp, - 'level': dirtyUser.level, - 'current_bodyweight': dirtyUser.currentBodyweight, + final pushData = { + 'workouts': validWorkouts.map((w) { + return { + 'id': w.serverId, + 'local_id': w.id, + 'cycle_id': w.cycleId, + 'week': w.week, + 'day': w.day, + 'completed_at': w.completedAt?.toIso8601String(), + 'xp_earned': w.xpEarned, + 'notes': w.notes, + 'exercises': w.exercises, + }; + }).toList(), + 'user_stats': dirtyUser != null + ? { + 'xp': dirtyUser.xp, + 'level': dirtyUser.level, + 'current_bodyweight': dirtyUser.currentBodyweight, + 'exercise_variants': dirtyUser.exerciseVariants, + } + : null, + }; + + final lastSync = await _storage.read(key: AppConstants.keyLastSync); + + if ((pushData['workouts'] as List).isNotEmpty || + pushData['user_stats'] != null) { + debugPrint('📤 Pushing data...'); + final response = await apiClient.sync( + lastSyncTimestamp: lastSync ?? '', + pushData: pushData, + ); + + await db.transaction(() async { + if (dirtyUser != null) { + await (db.update(db.users)..where((u) => u.id.equals(dirtyUser.id))) + .write(const UsersCompanion(isDirty: Value(false))); + } + for (var w in validWorkouts) { + await (db.update(db.workouts)..where((dw) => dw.id.equals(w.id))) + .write(const WorkoutsCompanion(isDirty: Value(false))); + } + + if (response['pull_data'] != null) { + if (response['pull_data']['cycles'] != null) { + final pulledCycles = response['pull_data']['cycles'] as List; + for (var cJson in pulledCycles) { + final serverId = cJson['id'] as String; + final existing = await (db.select(db.cycles) + ..where((c) => c.serverId.equals(serverId))) + .getSingleOrNull(); + + final tms = cJson['training_maxes'] as Map; + final companion = CyclesCompanion( + serverId: Value(serverId), + userId: Value(cJson['user_id']), + cycleNumber: Value(cJson['cycle_number']), + startDate: Value(DateTime.parse(cJson['start_date'])), + endDate: Value(DateTime.tryParse(cJson['end_date'] ?? '')), + isActive: Value(cJson['is_active'] ?? false), + trainingMaxes: Value(tms), + isDirty: const Value(false), + updatedAt: Value(DateTime.now()), + createdAt: existing == null + ? Value(DateTime.now()) + : const Value.absent(), + ); + + if (existing != null) { + await (db.update(db.cycles) + ..where((c) => c.id.equals(existing.id))) + .write(companion); + } else { + await db.into(db.cycles).insert(companion); } - : null, - }; - - if ((pushData['workouts'] as List).length < dirtyWorkouts.length) { - debugPrint( - '⚠️ Skipped some workouts because they lack a valid server cycle ID.'); - } - - final lastSync = await _storage.read(key: AppConstants.keyLastSync); - - if ((pushData['workouts'] as List).isNotEmpty || - pushData['user_stats'] != null) { - debugPrint('📤 Pushing data...'); - final response = await apiClient.sync( - lastSyncTimestamp: lastSync ?? '', - pushData: pushData, - ); - - await isar.writeTxn(() async { - if (dirtyUser != null) { - dirtyUser.isDirty = false; - await isar.userCollections.put(dirtyUser); - } - - for (var w in dirtyWorkouts) { - w.isDirty = false; - await isar.workoutCollections.put(w); - } - - if (response['pull_data'] != null && - response['pull_data']['workouts'] != null) { - final pulledWorkouts = response['pull_data']['workouts'] as List; - for (var wJson in pulledWorkouts) { - final serverId = wJson['id']; - var workout = await isar.workoutCollections - .filter() - .serverIdEqualTo(serverId) - .findFirst(); - - workout ??= WorkoutCollection(); - - workout - ..serverId = serverId - ..cycleId = wJson['cycle_id'] - ..userId = wJson['user_id'] - ..week = wJson['week'] - ..day = wJson['day'] - ..completedAt = DateTime.tryParse(wJson['completed_at'] ?? '') - ..xpEarned = wJson['xp_earned'] ?? 0 - ..exercisesJson = jsonEncode(wJson['exercises']) - ..isDirty = false - ..updatedAt = DateTime.now(); - - await isar.workoutCollections.put(workout); } } - }); - if (response['server_timestamp'] != null) { - await _storage.write( - key: AppConstants.keyLastSync, - value: response['server_timestamp'], - ); + if (response['pull_data']['workouts'] != null) { + final pulledWorkouts = response['pull_data']['workouts'] as List; + debugPrint('📥 Pulled ${pulledWorkouts.length} workouts.'); + + for (var wJson in pulledWorkouts) { + final serverId = wJson['id'] as String; + final cycleId = wJson['cycle_id'] as String; + final week = wJson['week'] as int; + final day = wJson['day'] as int; + + var existing = await (db.select(db.workouts) + ..where((w) => w.serverId.equals(serverId))) + .getSingleOrNull(); + + if (existing == null) { + final candidates = await (db.select(db.workouts) + ..where((w) => + w.cycleId.equals(cycleId) & + w.week.equals(week) & + w.day.equals(day))) + .get(); + if (candidates.isNotEmpty) { + existing = candidates.first; + debugPrint( + '🔄 Merging local workout ${existing.id} with server ID $serverId'); + } + } + + final companion = WorkoutsCompanion( + serverId: Value(serverId), + cycleId: Value(cycleId), + userId: Value(wJson['user_id']), + week: Value(week), + day: Value(day), + completedAt: + Value(DateTime.tryParse(wJson['completed_at'] ?? '')), + xpEarned: Value(wJson['xp_earned'] ?? 0), + exercises: Value(wJson['exercises'] ?? []), + notes: Value(wJson['notes'] ?? ''), + isDirty: const Value(false), + updatedAt: Value(DateTime.now()), + createdAt: existing == null + ? Value(DateTime.now()) + : const Value.absent(), + ); + + if (existing != null) { + await (db.update(db.workouts) + ..where((w) => w.id.equals(existing!.id))) + .write(companion); + } else { + await db.into(db.workouts).insert(companion); + } + } + } } + }); + + if (response['server_timestamp'] != null) { + await _storage.write( + key: AppConstants.keyLastSync, + value: response['server_timestamp']); } } - debugPrint('✅ Sync completed successfully'); - } catch (e) { + } catch (e, stack) { debugPrint('❌ Sync failed: $e'); + debugPrint(stack.toString()); } finally { _isSyncing = false; } diff --git a/lib/src/shared/data/repositories/cycle_repository.dart b/lib/src/shared/data/repositories/cycle_repository.dart index 959de45..6108771 100644 --- a/lib/src/shared/data/repositories/cycle_repository.dart +++ b/lib/src/shared/data/repositories/cycle_repository.dart @@ -1,45 +1,49 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:isar/isar.dart'; +import 'package:drift/drift.dart'; import 'dart:convert'; -import '../local/collections/cycle_collection.dart'; +import '../local/app_database.dart'; import '../remote/api_client.dart'; import '../../../../main.dart'; import 'user_repository.dart'; import '../../../core/constants/app_constants.dart'; -import '../local/collections/workout_collection.dart'; final cycleRepositoryProvider = Provider((ref) { - final isar = ref.watch(isarProvider); + final db = ref.watch(appDatabaseProvider); final apiClient = ref.watch(apiClientProvider); - return CycleRepository(isar: isar, apiClient: apiClient); + return CycleRepository(db: db, apiClient: apiClient); }); class CycleRepository { - final Isar isar; + final AppDatabase db; final ApiClient apiClient; - CycleRepository({required this.isar, required this.apiClient}); + CycleRepository({required this.db, required this.apiClient}); Future getCurrentCycle() async { - return await isar.cycleCollections - .filter() - .isActiveEqualTo(true) - .findFirst(); + return await (db.select(db.cycles) + ..where((c) => c.isActive.equals(true)) + ..limit(1)) + .getSingleOrNull(); } Future> getAllCycles() async { - return await isar.cycleCollections.where().findAll(); + return await db.select(db.cycles).get(); } Future createCycle(Map trainingMaxes) async { - try { + return await db.transaction(() async { final currentCycle = await getCurrentCycle(); if (currentCycle != null) { - currentCycle.isActive = false; - currentCycle.endDate = DateTime.now(); - await saveCycle(currentCycle); + final updateOld = CyclesCompanion( + isActive: const Value(false), + endDate: Value(DateTime.now()), + updatedAt: Value(DateTime.now()), + isDirty: const Value(true), + ); + await (db.update(db.cycles)..where((c) => c.id.equals(currentCycle.id))) + .write(updateOld); } final allCycles = await getAllCycles(); @@ -50,34 +54,44 @@ class CycleRepository { .reduce((a, b) => a > b ? a : b) + 1; - final userRepo = UserRepository(isar: isar, apiClient: ApiClient()); - final user = await userRepo.getLocalUser(); - + final user = await (db.select(db.users)..limit(1)).getSingleOrNull(); if (user == null) { throw Exception('No user found for cycle creation'); } - final newCycle = CycleCollection() - ..userId = user.serverId ?? user.id.toString() - ..cycleNumber = nextNumber - ..startDate = DateTime.now() - ..isActive = true - ..trainingMaxesJson = jsonEncode(trainingMaxes) - ..isDirty = true; + final newCycleCompanion = CyclesCompanion( + userId: Value(user.serverId ?? user.id.toString()), + cycleNumber: Value(nextNumber), + startDate: Value(DateTime.now()), + isActive: const Value(true), + trainingMaxes: Value(trainingMaxes), + isDirty: const Value(true), + createdAt: Value(DateTime.now()), + updatedAt: Value(DateTime.now()), + ); - await saveCycle(newCycle); + final newId = await db.into(db.cycles).insert(newCycleCompanion); + var newCycle = await (db.select(db.cycles) + ..where((c) => c.id.equals(newId))) + .getSingle(); try { final response = await apiClient.createCycle(trainingMaxes); - newCycle.serverId = response['id']; - newCycle.isDirty = false; - await saveCycle(newCycle); - } catch (e) {} + await (db.update(db.cycles)..where((c) => c.id.equals(newId))).write( + CyclesCompanion( + serverId: Value(response['id']), + isDirty: const Value(false), + ), + ); + newCycle = await (db.select(db.cycles) + ..where((c) => c.id.equals(newId))) + .getSingle(); + } catch (e) { + // API Fehler ignorieren, wird später gesynct + } return newCycle; - } catch (e, stackTrace) { - rethrow; - } + }); } Future finishCycle() async { @@ -87,24 +101,26 @@ class CycleRepository { } final cycleIdRef = currentCycle.serverId ?? currentCycle.id.toString(); + final localCycleId = currentCycle.id.toString(); - final completedMainWorkouts = await isar.workoutCollections - .filter() - .weekLessThan(4) - .completedAtIsNotNull() - .group((q) => q - .cycleIdEqualTo(cycleIdRef) - .or() - .cycleIdEqualTo(currentCycle.id.toString())) - .count(); + final workoutsQuery = db.select(db.workouts) + ..where((w) { + final weekCheck = w.week.isSmallerThanValue(4); + final completedCheck = w.completedAt.isNotNull(); + final cycleCheck = + w.cycleId.equals(cycleIdRef) | w.cycleId.equals(localCycleId); + return weekCheck & completedCheck & cycleCheck; + }); + + final completedMainWorkouts = (await workoutsQuery.get()).length; if (completedMainWorkouts < 9) { final missing = 9 - completedMainWorkouts; throw Exception( 'Cycle incomplete! You still have $missing workouts left in the main phase (Weeks 1-3). Finish them before leveling up.'); } - final currentTMs = - jsonDecode(currentCycle.trainingMaxesJson) as Map; + + final currentTMs = currentCycle.trainingMaxes; final newTMs = { 'squat': (currentTMs['squat'] as num?)?.toDouble() ?? 0.0, @@ -112,23 +128,27 @@ class CycleRepository { 'dip': (currentTMs['dip'] as num?)?.toDouble() ?? 0.0, }; - final week3Workouts = await isar.workoutCollections - .filter() - .weekEqualTo(3) - .group((q) => q - .cycleIdEqualTo(cycleIdRef) - .or() - .cycleIdEqualTo(currentCycle.id.toString())) - .findAll(); + final week3Workouts = await (db.select(db.workouts) + ..where((w) { + final weekCheck = w.week.equals(3); + final cycleCheck = + w.cycleId.equals(cycleIdRef) | w.cycleId.equals(localCycleId); + return weekCheck & cycleCheck; + })) + .get(); bool checkSuccess(String exerciseId) { for (var workout in week3Workouts) { try { - final exercises = jsonDecode(workout.exercisesJson) as List; - for (var ex in exercises) { + final exercises = workout.exercises; + + for (var exData in exercises) { + final ex = exData as Map; + if (ex['exerciseId'] == exerciseId) { final sets = ex['sets'] as List; - for (var s in sets) { + for (var sData in sets) { + final s = sData as Map; if (s['isAmrap'] == true) { final reps = s['repsActual'] as int? ?? 0; if (reps >= 1) { @@ -170,33 +190,23 @@ class CycleRepository { try { await apiClient.finishCycle(currentCycle.serverId!); } catch (e) { - // Fehler ignorieren, wird später gesynct + // Fehler ignorieren } } return await createCycle(newTMs); } - Future saveCycle(CycleCollection cycle) async { - cycle.updatedAt = DateTime.now(); - await isar.writeTxn(() async { - await isar.cycleCollections.put(cycle); - }); - } - - Map getCurrentTrainingMaxes() { - final cycle = - isar.cycleCollections.filter().isActiveEqualTo(true).findFirstSync(); - + Future> getCurrentTrainingMaxesAsync() async { + final cycle = await getCurrentCycle(); if (cycle != null) { - final tms = jsonDecode(cycle.trainingMaxesJson); + final tms = cycle.trainingMaxes; return { 'squat': (tms['squat'] as num?)?.toDouble() ?? 0.0, 'pullup': (tms['pullup'] as num?)?.toDouble() ?? 0.0, 'dip': (tms['dip'] as num?)?.toDouble() ?? 0.0, }; } - return {'squat': 0.0, 'pullup': 0.0, 'dip': 0.0}; } } diff --git a/lib/src/shared/data/repositories/user_repository.dart b/lib/src/shared/data/repositories/user_repository.dart index 35e248b..12c53b9 100644 --- a/lib/src/shared/data/repositories/user_repository.dart +++ b/lib/src/shared/data/repositories/user_repository.dart @@ -1,62 +1,76 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:isar/isar.dart'; import 'dart:convert'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:drift/drift.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import '../local/collections/cycle_collection.dart'; -import '../local/collections/user_collection.dart'; -import '../local/collections/workout_collection.dart'; +import '../local/app_database.dart'; import '../remote/api_client.dart'; import '../../../../main.dart'; +import '../../../core/constants/app_constants.dart'; final userRepositoryProvider = Provider((ref) { - final isar = ref.watch(isarProvider); + final db = ref.watch(appDatabaseProvider); final apiClient = ref.watch(apiClientProvider); - return UserRepository(isar: isar, apiClient: apiClient); + return UserRepository(db: db, apiClient: apiClient); }); final apiClientProvider = Provider((ref) => ApiClient()); class UserRepository { - final Isar isar; + final AppDatabase db; final ApiClient apiClient; + final _storage = const FlutterSecureStorage(); // NEU: Instanz für Logout - UserRepository({required this.isar, required this.apiClient}); + UserRepository({required this.db, required this.apiClient}); Future getLocalUser() async { - return await isar.userCollections.where().findFirst(); + return await (db.select(db.users)..limit(1)).getSingleOrNull(); } Future saveLocalUser(UserCollection user) async { - user.updatedAt = DateTime.now(); - await isar.writeTxn(() async { - await isar.userCollections.put(user); - }); + final companion = user.toCompanion(true).copyWith( + updatedAt: Value(DateTime.now()), + ); + await db.into(db.users).insertOnConflictUpdate(companion); } Future updateXP(int xpToAdd) async { final user = await getLocalUser(); if (user != null) { - user.xp += xpToAdd; - user.isDirty = true; - await saveLocalUser(user); + final newXp = user.xp + xpToAdd; + final companion = UsersCompanion( + xp: Value(newXp), + isDirty: const Value(true), + updatedAt: Value(DateTime.now()), + ); + await (db.update(db.users)..where((u) => u.id.equals(user.id))) + .write(companion); } } Future updateLevel(int newLevel) async { final user = await getLocalUser(); if (user != null) { - user.level = newLevel; - user.isDirty = true; - await saveLocalUser(user); + final companion = UsersCompanion( + level: Value(newLevel), + isDirty: const Value(true), + updatedAt: Value(DateTime.now()), + ); + await (db.update(db.users)..where((u) => u.id.equals(user.id))) + .write(companion); } } Future updateBodyweight(double bodyweight) async { final user = await getLocalUser(); if (user != null) { - user.currentBodyweight = bodyweight; - user.isDirty = true; - await saveLocalUser(user); + final companion = UsersCompanion( + currentBodyweight: Value(bodyweight), + isDirty: const Value(true), + updatedAt: Value(DateTime.now()), + ); + await (db.update(db.users)..where((u) => u.id.equals(user.id))) + .write(companion); try { await apiClient.updateBodyweight(bodyweight); @@ -67,9 +81,13 @@ class UserRepository { Future updateInventory(Map inventory) async { final user = await getLocalUser(); if (user != null) { - user.inventorySettingsJson = jsonEncode(inventory); - user.isDirty = true; - await saveLocalUser(user); + final companion = UsersCompanion( + inventorySettings: Value(inventory), + isDirty: const Value(true), + updatedAt: Value(DateTime.now()), + ); + await (db.update(db.users)..where((u) => u.id.equals(user.id))) + .write(companion); try { await apiClient.updateInventory(inventory); @@ -79,21 +97,7 @@ class UserRepository { Future login(String email, String password) async { final response = await apiClient.login(email, password); - - final user = UserCollection() - ..serverId = response['record']['id'] - ..email = response['record']['email'] - ..xp = response['record']['xp'] ?? 0 - ..level = response['record']['level'] ?? 1 - ..currentBodyweight = - (response['record']['current_bodyweight'] ?? 70.0).toDouble() - ..inventorySettingsJson = - jsonEncode(response['record']['inventory_settings'] ?? {}) - ..avatarConfigJson = jsonEncode(response['record']['avatar_config'] ?? {}) - ..lastSyncAt = DateTime.now(); - - await saveLocalUser(user); - return user; + return _saveUserFromApi(response['record']); } Future register({ @@ -101,6 +105,7 @@ class UserRepository { required String password, required double bodyweight, required Map inventorySettings, + Map? exerciseVariants, }) async { try { final response = await apiClient.register( @@ -108,51 +113,79 @@ class UserRepository { password: password, bodyweight: bodyweight, inventorySettings: inventorySettings, + exerciseVariants: exerciseVariants, ); final record = response['record'] ?? response; + var user = await _saveUserFromApi(record); - final user = UserCollection() - ..serverId = record['id']?.toString() - ..email = record['email']?.toString() ?? email - ..xp = (record['xp'] as num?)?.toInt() ?? 0 - ..level = (record['level'] as num?)?.toInt() ?? 1 - ..currentBodyweight = - (record['current_bodyweight'] as num?)?.toDouble() ?? bodyweight - ..inventorySettingsJson = - jsonEncode(record['inventory_settings'] ?? inventorySettings) - ..avatarConfigJson = jsonEncode(record['avatar_config'] ?? - { - 'skin_tone': 'medium', - 'hair_style': 'short_01', - 'clothing': 'basic_tee', - 'unlocked_items': ['basic_tee'], - }) - ..lastSyncAt = DateTime.now(); + if (exerciseVariants != null && exerciseVariants.isNotEmpty) { + final serverVariants = user.exerciseVariants; + if (serverVariants == null || serverVariants.isEmpty) { + final companion = user.toCompanion(true).copyWith( + exerciseVariants: Value(exerciseVariants), + isDirty: const Value(true), + updatedAt: Value(DateTime.now()), + ); + await db.into(db.users).insertOnConflictUpdate(companion); - await saveLocalUser(user); + user = (await (db.select(db.users) + ..where((u) => u.id.equals(user.id))) + .getSingle()); + } + } try { await apiClient.login(email, password); } catch (e) {} return user; - } catch (e, stackTrace) { + } catch (e) { rethrow; } } + Future _saveUserFromApi(Map record) async { + await db.delete(db.users).go(); + + final companion = UsersCompanion( + serverId: Value(record['id']), + email: Value(record['email'] ?? ''), + xp: Value((record['xp'] as num?)?.toInt() ?? 0), + level: Value((record['level'] as num?)?.toInt() ?? 1), + currentBodyweight: + Value((record['current_bodyweight'] as num?)?.toDouble() ?? 70.0), + inventorySettings: Value(record['inventory_settings'] ?? {}), + exerciseVariants: Value(record['exercise_variants'] ?? {}), + avatarConfig: Value(record['avatar_config'] ?? {}), + lastSyncAt: Value(DateTime.now()), + isDirty: const Value(false), + createdAt: Value(DateTime.now()), + updatedAt: Value(DateTime.now()), + ); + + final id = await db.into(db.users).insert(companion); + return (await (db.select(db.users)..where((u) => u.id.equals(id))) + .getSingle()); + } + Future logout() async { await apiClient.logout(); - await isar.writeTxn(() async { - await isar.userCollections.clear(); + + await _storage.delete(key: AppConstants.keyLastSync); + + await db.transaction(() async { + await db.delete(db.users).go(); + await db.delete(db.cycles).go(); + await db.delete(db.workouts).go(); + await db.delete(db.quests).go(); }); } - Map getInventorySettings() { - final user = isar.userCollections.where().findFirstSync(); - if (user?.inventorySettingsJson != null) { - return jsonDecode(user!.inventorySettingsJson!); + Future> getInventorySettingsAsync() async { + final user = await getLocalUser(); + if (user?.inventorySettings != null) { + return user!.inventorySettings!; } return { 'bar_weight': 20.0, @@ -161,14 +194,14 @@ class UserRepository { }; } - List getAvailablePlates() { - final inventory = getInventorySettings(); + Future> getAvailablePlates() async { + final inventory = await getInventorySettingsAsync(); final plates = inventory['plates'] as List?; return plates?.map((e) => (e as num).toDouble()).toList() ?? []; } - double getBarWeight() { - final inventory = getInventorySettings(); + Future getBarWeight() async { + final inventory = await getInventorySettingsAsync(); return (inventory['bar_weight'] as num?)?.toDouble() ?? 20.0; } @@ -204,17 +237,17 @@ class UserRepository { "Server connection required to reset progress. Please try again when online."); } - user.xp = 0; - user.level = 1; + final companion = UsersCompanion( + xp: const Value(0), + level: const Value(1), + isDirty: const Value(false), + updatedAt: Value(DateTime.now()), + ); + await (db.update(db.users)..where((u) => u.id.equals(user.id))) + .write(companion); - user.isDirty = false; - - await isar.writeTxn(() async { - await isar.userCollections.put(user); - - await isar.cycleCollections.clear(); - await isar.workoutCollections.clear(); - }); + await db.delete(db.cycles).go(); + await db.delete(db.workouts).go(); } } } diff --git a/lib/src/shared/data/repositories/workout_repository.dart b/lib/src/shared/data/repositories/workout_repository.dart index fd38d1e..a16904c 100644 --- a/lib/src/shared/data/repositories/workout_repository.dart +++ b/lib/src/shared/data/repositories/workout_repository.dart @@ -1,49 +1,44 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:isar/isar.dart'; -import 'dart:convert'; - -import '../local/collections/workout_collection.dart'; +import 'package:drift/drift.dart'; +import '../local/app_database.dart'; import '../remote/api_client.dart'; import '../../../../main.dart'; import 'user_repository.dart'; final workoutRepositoryProvider = Provider((ref) { - final isar = ref.watch(isarProvider); + final db = ref.watch(appDatabaseProvider); final apiClient = ref.watch(apiClientProvider); - return WorkoutRepository(isar: isar, apiClient: apiClient); + return WorkoutRepository(db: db, apiClient: apiClient); }); class WorkoutRepository { - final Isar isar; + final AppDatabase db; final ApiClient apiClient; - WorkoutRepository({required this.isar, required this.apiClient}); + WorkoutRepository({required this.db, required this.apiClient}); Future> getAllWorkouts() async { - return await isar.workoutCollections.where().findAll(); + return await db.select(db.workouts).get(); } Future> getWorkoutsForCycle(String cycleId) async { - return await isar.workoutCollections - .filter() - .cycleIdEqualTo(cycleId) - .findAll(); + return await (db.select(db.workouts) + ..where((w) => w.cycleId.equals(cycleId))) + .get(); } Future> getCompletedWorkouts(String userId) async { - return await isar.workoutCollections - .filter() - .userIdEqualTo(userId) - .completedAtIsNotNull() - .findAll(); + return await (db.select(db.workouts) + ..where((w) => w.userId.equals(userId) & w.completedAt.isNotNull())) + .get(); } Future saveWorkout(WorkoutCollection workout) async { - workout.updatedAt = DateTime.now(); - workout.isDirty = true; - await isar.writeTxn(() async { - await isar.workoutCollections.put(workout); - }); + final companion = workout.toCompanion(true).copyWith( + updatedAt: Value(DateTime.now()), + isDirty: const Value(true), + ); + await db.into(db.workouts).insertOnConflictUpdate(companion); } Future createWorkout({ @@ -51,27 +46,42 @@ class WorkoutRepository { required String cycleId, required int week, required int day, - required String exercisesJson, + required List exercises, }) async { - final workout = WorkoutCollection() - ..userId = userId - ..cycleId = cycleId - ..week = week - ..day = day - ..exercisesJson = exercisesJson - ..scheduledDate = DateTime.now(); + final companion = WorkoutsCompanion( + userId: Value(userId), + cycleId: Value(cycleId), + week: Value(week), + day: Value(day), + exercises: Value(exercises), + scheduledDate: Value(DateTime.now()), + xpEarned: const Value(0), + notes: const Value(''), + isDirty: const Value(true), + createdAt: Value(DateTime.now()), + updatedAt: Value(DateTime.now()), + ); - await saveWorkout(workout); - return workout; + final id = await db.into(db.workouts).insert(companion); + return await (db.select(db.workouts)..where((w) => w.id.equals(id))) + .getSingle(); } Future completeWorkout( WorkoutCollection workout, { required int xpEarned, }) async { - workout.completedAt = DateTime.now(); - workout.xpEarned = xpEarned; - await saveWorkout(workout); + final companion = WorkoutsCompanion( + id: Value(workout.id), + completedAt: Value(DateTime.now()), + xpEarned: Value(xpEarned), + exercises: Value(workout.exercises), + isDirty: const Value(true), + updatedAt: Value(DateTime.now()), + ); + + await (db.update(db.workouts)..where((w) => w.id.equals(workout.id))) + .write(companion); } Future getWorkoutByWeekDay({ @@ -80,16 +90,23 @@ class WorkoutRepository { required int week, required int day, }) async { - return await isar.workoutCollections - .filter() - .weekEqualTo(week) - .dayEqualTo(day) - .group((q) { - var query = q.cycleIdEqualTo(cycleId); - if (localCycleId != null) { - query = query.or().cycleIdEqualTo(localCycleId); - } - return query; - }).findFirst(); + return await (db.select(db.workouts) + ..where((w) { + final weekDayCheck = w.week.equals(week) & w.day.equals(day); + + Expression cycleCheck = w.cycleId.equals(cycleId); + if (localCycleId != null) { + cycleCheck = cycleCheck | w.cycleId.equals(localCycleId); + } + + return weekDayCheck & cycleCheck; + }) + ..limit(1)) + .getSingleOrNull(); + } + + Future getWorkoutById(int id) async { + return await (db.select(db.workouts)..where((w) => w.id.equals(id))) + .getSingleOrNull(); } } diff --git a/lib/src/shared/domain/entities/exercise.dart b/lib/src/shared/domain/entities/exercise.dart index f58d3b7..edca1a5 100644 --- a/lib/src/shared/domain/entities/exercise.dart +++ b/lib/src/shared/domain/entities/exercise.dart @@ -5,15 +5,15 @@ part 'exercise.freezed.dart'; part 'exercise.g.dart'; @freezed -class Exercise with _$Exercise { +abstract class Exercise with _$Exercise { const factory Exercise({ required String exerciseId, required String exerciseName, @Default(0.0) double bodyweightAtSession, @Default([]) List sets, + int? intervalSeconds, }) = _Exercise; factory Exercise.fromJson(Map json) => _$ExerciseFromJson(json); } - diff --git a/lib/src/shared/domain/entities/exercise.freezed.dart b/lib/src/shared/domain/entities/exercise.freezed.dart index db27c4c..924ab22 100644 --- a/lib/src/shared/domain/entities/exercise.freezed.dart +++ b/lib/src/shared/domain/entities/exercise.freezed.dart @@ -25,8 +25,12 @@ mixin _$Exercise { double get bodyweightAtSession => throw _privateConstructorUsedError; List get sets => throw _privateConstructorUsedError; + /// Serializes this Exercise to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of Exercise + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ExerciseCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -53,6 +57,8 @@ class _$ExerciseCopyWithImpl<$Res, $Val extends Exercise> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of Exercise + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -105,6 +111,8 @@ class __$$ExerciseImplCopyWithImpl<$Res> _$ExerciseImpl _value, $Res Function(_$ExerciseImpl) _then) : super(_value, _then); + /// Create a copy of Exercise + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -182,12 +190,14 @@ class _$ExerciseImpl implements _Exercise { const DeepCollectionEquality().equals(other._sets, _sets)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, exerciseId, exerciseName, bodyweightAtSession, const DeepCollectionEquality().hash(_sets)); - @JsonKey(ignore: true) + /// Create a copy of Exercise + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ExerciseImplCopyWith<_$ExerciseImpl> get copyWith => @@ -219,8 +229,11 @@ abstract class _Exercise implements Exercise { double get bodyweightAtSession; @override List get sets; + + /// Create a copy of Exercise + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ExerciseImplCopyWith<_$ExerciseImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/src/shared/domain/entities/training_maxes.dart b/lib/src/shared/domain/entities/training_maxes.dart index 8f4296f..db39871 100644 --- a/lib/src/shared/domain/entities/training_maxes.dart +++ b/lib/src/shared/domain/entities/training_maxes.dart @@ -4,7 +4,7 @@ part 'training_maxes.freezed.dart'; part 'training_maxes.g.dart'; @freezed -class TrainingMaxes with _$TrainingMaxes { +abstract class TrainingMaxes with _$TrainingMaxes { const factory TrainingMaxes({ @Default(0.0) double squat, @Default(0.0) double pullup, @@ -14,4 +14,3 @@ class TrainingMaxes with _$TrainingMaxes { factory TrainingMaxes.fromJson(Map json) => _$TrainingMaxesFromJson(json); } - diff --git a/lib/src/shared/domain/entities/training_maxes.freezed.dart b/lib/src/shared/domain/entities/training_maxes.freezed.dart index 1d92794..19b9f4d 100644 --- a/lib/src/shared/domain/entities/training_maxes.freezed.dart +++ b/lib/src/shared/domain/entities/training_maxes.freezed.dart @@ -24,8 +24,12 @@ mixin _$TrainingMaxes { double get pullup => throw _privateConstructorUsedError; double get dip => throw _privateConstructorUsedError; + /// Serializes this TrainingMaxes to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of TrainingMaxes + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $TrainingMaxesCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -49,6 +53,8 @@ class _$TrainingMaxesCopyWithImpl<$Res, $Val extends TrainingMaxes> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of TrainingMaxes + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -92,6 +98,8 @@ class __$$TrainingMaxesImplCopyWithImpl<$Res> _$TrainingMaxesImpl _value, $Res Function(_$TrainingMaxesImpl) _then) : super(_value, _then); + /// Create a copy of TrainingMaxes + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -150,11 +158,13 @@ class _$TrainingMaxesImpl implements _TrainingMaxes { (identical(other.dip, dip) || other.dip == dip)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, squat, pullup, dip); - @JsonKey(ignore: true) + /// Create a copy of TrainingMaxes + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$TrainingMaxesImplCopyWith<_$TrainingMaxesImpl> get copyWith => @@ -183,8 +193,11 @@ abstract class _TrainingMaxes implements TrainingMaxes { double get pullup; @override double get dip; + + /// Create a copy of TrainingMaxes + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$TrainingMaxesImplCopyWith<_$TrainingMaxesImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/src/shared/domain/entities/workout_set.dart b/lib/src/shared/domain/entities/workout_set.dart index a8a8700..c537dd9 100644 --- a/lib/src/shared/domain/entities/workout_set.dart +++ b/lib/src/shared/domain/entities/workout_set.dart @@ -4,7 +4,7 @@ part 'workout_set.freezed.dart'; part 'workout_set.g.dart'; @freezed -class WorkoutSet with _$WorkoutSet { +abstract class WorkoutSet with _$WorkoutSet { const factory WorkoutSet({ @Default(1) int setNumber, @Default(0) int targetPercentage, @@ -20,4 +20,3 @@ class WorkoutSet with _$WorkoutSet { factory WorkoutSet.fromJson(Map json) => _$WorkoutSetFromJson(json); } - diff --git a/lib/src/shared/domain/entities/workout_set.freezed.dart b/lib/src/shared/domain/entities/workout_set.freezed.dart index 7040af7..f63d83f 100644 --- a/lib/src/shared/domain/entities/workout_set.freezed.dart +++ b/lib/src/shared/domain/entities/workout_set.freezed.dart @@ -30,8 +30,12 @@ mixin _$WorkoutSet { bool get completed => throw _privateConstructorUsedError; int? get rpe => throw _privateConstructorUsedError; + /// Serializes this WorkoutSet to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of WorkoutSet + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $WorkoutSetCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -64,6 +68,8 @@ class _$WorkoutSetCopyWithImpl<$Res, $Val extends WorkoutSet> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of WorkoutSet + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -146,6 +152,8 @@ class __$$WorkoutSetImplCopyWithImpl<$Res> _$WorkoutSetImpl _value, $Res Function(_$WorkoutSetImpl) _then) : super(_value, _then); + /// Create a copy of WorkoutSet + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -272,7 +280,7 @@ class _$WorkoutSetImpl implements _WorkoutSet { (identical(other.rpe, rpe) || other.rpe == rpe)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -286,7 +294,9 @@ class _$WorkoutSetImpl implements _WorkoutSet { completed, rpe); - @JsonKey(ignore: true) + /// Create a copy of WorkoutSet + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$WorkoutSetImplCopyWith<_$WorkoutSetImpl> get copyWith => @@ -333,8 +343,11 @@ abstract class _WorkoutSet implements WorkoutSet { bool get completed; @override int? get rpe; + + /// Create a copy of WorkoutSet + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$WorkoutSetImplCopyWith<_$WorkoutSetImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/src/shared/domain/logic/wendler_calculator.dart b/lib/src/shared/domain/logic/wendler_calculator.dart index f430394..b1b7059 100644 --- a/lib/src/shared/domain/logic/wendler_calculator.dart +++ b/lib/src/shared/domain/logic/wendler_calculator.dart @@ -2,7 +2,35 @@ import 'dart:math'; import '../entities/workout_set.dart'; import '../../../core/constants/app_constants.dart'; -enum ExerciseType { squat, pullup, dip } +enum ExerciseType { + // Main Lifts + squat, + pullup, + dip, + row, + bench, + + // Hypertrophy Accessories + deadlift_romanian, + curl_barbell, + press_overhead, + face_pull, + ab_wheel, + plank, + + // Conditioning (Kettlebell) + kb_swing, + kb_snatch, + kb_thruster, + kb_clean_press, + + // pullup journey + scapular_pull, + inverted_row, + negative_pullup, +} + +enum AccessoryTemplate { none, hypertrophy, conditioning, journey_pullup } class WendlerCalculator { static const Map> weekPercentages = { @@ -34,7 +62,9 @@ class WendlerCalculator { final rounded = _roundWeight(targetTotal, exerciseType); double plateWeight = 0; - if (exerciseType != ExerciseType.squat) { + if (exerciseType != ExerciseType.squat || + exerciseType != ExerciseType.row || + exerciseType != ExerciseType.bench) { plateWeight = max(0, rounded - currentBodyweight); } @@ -51,8 +81,38 @@ class WendlerCalculator { return sets; } + static List generateLinearSets({ + required double trainingMax, + required ExerciseType exerciseType, + required double currentBodyweight, + int setsCount = 3, + int repsCount = 5, + }) { + final sets = []; + + final targetTotal = _roundWeight(trainingMax, exerciseType); + + double plateWeight = 0; + + for (int i = 0; i < setsCount; i++) { + sets.add(WorkoutSet( + setNumber: i + 1, + targetPercentage: 100, + targetWeightTotal: targetTotal, + plateWeight: plateWeight, + repsTarget: repsCount, + repsActual: 0, + isAmrap: (i == setsCount - 1), + )); + } + + return sets; + } + static double _roundWeight(double weight, ExerciseType type) { - final step = type == ExerciseType.squat + final step = (type == ExerciseType.squat || + type == ExerciseType.row || + type == ExerciseType.bench) ? AppConstants.squatRoundingStep : AppConstants.calisthenicsRoundingStep; return (weight / step).floor() * step; @@ -86,7 +146,9 @@ class WendlerCalculator { final rounded = _roundWeight(targetTotal, exerciseType); double plateWeight = 0; - if (exerciseType != ExerciseType.squat) { + if (exerciseType != ExerciseType.squat || + exerciseType != ExerciseType.row || + exerciseType != ExerciseType.bench) { plateWeight = max(0, rounded - currentBodyweight); } diff --git a/lib/src/shared/domain/models/exercise_guide.dart b/lib/src/shared/domain/models/exercise_guide.dart new file mode 100644 index 0000000..02a18da --- /dev/null +++ b/lib/src/shared/domain/models/exercise_guide.dart @@ -0,0 +1,148 @@ +import 'package:slrpg_app/l10n/app_localizations.dart'; + +class ExerciseGuide { + final String title; + final String difficulty; + final String rpgLore; + final List steps; + final List muscles; + final List commonMistakes; + + const ExerciseGuide({ + required this.title, + required this.difficulty, + required this.rpgLore, + required this.steps, + required this.muscles, + required this.commonMistakes, + }); + + static List _split(String input) { + return input.split('|').map((e) => e.trim()).toList(); + } + + static Map getLibrary(AppLocalizations l10n) { + return { + 'pullup': ExerciseGuide( + title: l10n.guidePullupTitle, + difficulty: 'Adept', + rpgLore: l10n.guidePullupLore, + steps: _split(l10n.guidePullupSteps), + muscles: _split(l10n.guidePullupMuscles), + commonMistakes: _split(l10n.guidePullupMistakes), + ), + 'dip': ExerciseGuide( + title: l10n.guideDipTitle, + difficulty: 'Adept', + rpgLore: l10n.guideDipLore, + steps: _split(l10n.guideDipSteps), + muscles: _split(l10n.guideDipMuscles), + commonMistakes: _split(l10n.guideDipMistakes), + ), + 'squat': ExerciseGuide( + title: l10n.guideSquatTitle, + difficulty: 'Master', + rpgLore: l10n.guideSquatLore, + steps: _split(l10n.guideSquatSteps), + muscles: _split(l10n.guideSquatMuscles), + commonMistakes: _split(l10n.guideSquatMistakes), + ), + 'bench': ExerciseGuide( + title: l10n.guideBenchTitle, + difficulty: 'Novice', + rpgLore: l10n.guideBenchLore, + steps: _split(l10n.guideBenchSteps), + muscles: _split(l10n.guideBenchMuscles), + commonMistakes: _split(l10n.guideBenchMistakes), + ), + 'ohp': ExerciseGuide( + title: l10n.guideOhpTitle, + difficulty: 'Adept', + rpgLore: l10n.guideOhpLore, + steps: _split(l10n.guideOhpSteps), + muscles: _split(l10n.guideOhpMuscles), + commonMistakes: _split(l10n.guideOhpMistakes), + ), + 'rdl': ExerciseGuide( + title: l10n.guideRdlTitle, + difficulty: 'Adept', + rpgLore: l10n.guideRdlLore, + steps: _split(l10n.guideRdlSteps), + muscles: _split(l10n.guideRdlMuscles), + commonMistakes: _split(l10n.guideRdlMistakes), + ), + 'row': ExerciseGuide( + title: l10n.guideRowTitle, + difficulty: 'Adept', + rpgLore: l10n.guideRowLore, + steps: _split(l10n.guideRowSteps), + muscles: _split(l10n.guideRowMuscles), + commonMistakes: _split(l10n.guideRowMistakes), + ), + 'curl': ExerciseGuide( + title: l10n.guideCurlTitle, + difficulty: 'Novice', + rpgLore: l10n.guideCurlLore, + steps: _split(l10n.guideCurlSteps), + muscles: _split(l10n.guideCurlMuscles), + commonMistakes: _split(l10n.guideCurlMistakes), + ), + 'kb_swing': ExerciseGuide( + title: l10n.guideKbSwingTitle, + difficulty: 'Adept', + rpgLore: l10n.guideKbSwingLore, + steps: _split(l10n.guideKbSwingSteps), + muscles: _split(l10n.guideKbSwingMuscles), + commonMistakes: _split(l10n.guideKbSwingMistakes), + ), + 'kb_snatch': ExerciseGuide( + title: l10n.guideKbSnatchTitle, + difficulty: 'Master', + rpgLore: l10n.guideKbSnatchLore, + steps: _split(l10n.guideKbSnatchSteps), + muscles: _split(l10n.guideKbSnatchMuscles), + commonMistakes: _split(l10n.guideKbSnatchMistakes), + ), + 'kb_thruster': ExerciseGuide( + title: l10n.guideKbThrusterTitle, + difficulty: 'Master', + rpgLore: l10n.guideKbThrusterLore, + steps: _split(l10n.guideKbThrusterSteps), + muscles: _split(l10n.guideKbThrusterMuscles), + commonMistakes: _split(l10n.guideKbThrusterMistakes), + ), + 'kb_clean_press': ExerciseGuide( + title: l10n.guideKbCleanPressTitle, + difficulty: 'Adept', + rpgLore: l10n.guideKbCleanPressLore, + steps: _split(l10n.guideKbCleanPressSteps), + muscles: _split(l10n.guideKbCleanPressMuscles), + commonMistakes: _split(l10n.guideKbCleanPressMistakes), + ), + 'face_pull': ExerciseGuide( + title: l10n.guideFacePullTitle, + difficulty: 'Novice', + rpgLore: l10n.guideFacePullLore, + steps: _split(l10n.guideFacePullSteps), + muscles: _split(l10n.guideFacePullMuscles), + commonMistakes: _split(l10n.guideFacePullMistakes), + ), + 'ab_wheel': ExerciseGuide( + title: l10n.guideAbWheelTitle, + difficulty: 'Adept', + rpgLore: l10n.guideAbWheelLore, + steps: _split(l10n.guideAbWheelSteps), + muscles: _split(l10n.guideAbWheelMuscles), + commonMistakes: _split(l10n.guideAbWheelMistakes), + ), + 'plank': ExerciseGuide( + title: l10n.guidePlankTitle, + difficulty: 'Novice', + rpgLore: l10n.guidePlankLore, + steps: _split(l10n.guidePlankSteps), + muscles: _split(l10n.guidePlankMuscles), + commonMistakes: _split(l10n.guidePlankMistakes), + ), + }; + } +} diff --git a/pubspec.lock b/pubspec.lock index a06b297..c5e912b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,26 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d url: "https://pub.dev" source: hosted - version: "61.0.0" + version: "91.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08 url: "https://pub.dev" source: hosted - version: "5.13.0" - analyzer_plugin: + version: "8.4.1" + analyzer_buffer: dependency: transitive description: - name: analyzer_plugin - sha256: c1d5f167683de03d5ab6c3b53fc9aeefc5d59476e7810ba7bbddff50c6f4392d + name: analyzer_buffer + sha256: aba2f75e63b3135fd1efaa8b6abefe1aa6e41b6bd9806221620fa48f98156033 url: "https://pub.dev" source: hosted - version: "0.11.2" + version: "0.1.11" args: dependency: transitive description: @@ -41,6 +41,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" + audioplayers: + dependency: "direct main" + description: + name: audioplayers + sha256: "5441fa0ceb8807a5ad701199806510e56afde2b4913d9d17c2f19f2902cf0ae4" + url: "https://pub.dev" + source: hosted + version: "6.5.1" + audioplayers_android: + dependency: transitive + description: + name: audioplayers_android + sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605" + url: "https://pub.dev" + source: hosted + version: "5.2.1" + audioplayers_darwin: + dependency: transitive + description: + name: audioplayers_darwin + sha256: "0811d6924904ca13f9ef90d19081e4a87f7297ddc19fc3d31f60af1aaafee333" + url: "https://pub.dev" + source: hosted + version: "6.3.0" + audioplayers_linux: + dependency: transitive + description: + name: audioplayers_linux + sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001 + url: "https://pub.dev" + source: hosted + version: "4.2.1" + audioplayers_platform_interface: + dependency: transitive + description: + name: audioplayers_platform_interface + sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656" + url: "https://pub.dev" + source: hosted + version: "7.1.1" + audioplayers_web: + dependency: transitive + description: + name: audioplayers_web + sha256: "1c0f17cec68455556775f1e50ca85c40c05c714a99c5eb1d2d57cc17ba5522d7" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + audioplayers_windows: + dependency: transitive + description: + name: audioplayers_windows + sha256: "4048797865105b26d47628e6abb49231ea5de84884160229251f37dfcbe52fd7" + url: "https://pub.dev" + source: hosted + version: "4.2.1" boolean_selector: dependency: transitive description: @@ -53,18 +109,18 @@ packages: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: c1668065e9ba04752570ad7e038288559d1e2ca5c6d0131c0f5f55e39e777413 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "4.0.3" build_config: dependency: transitive description: name: build_config - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.0" build_daemon: dependency: transitive description: @@ -73,30 +129,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.1" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" - url: "https://pub.dev" - source: hosted - version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + sha256: "110c56ef29b5eb367b4d17fc79375fa8c18a6cd7acd92c05bb3986c17a079057" url: "https://pub.dev" source: hosted - version: "2.4.13" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 - url: "https://pub.dev" - source: hosted - version: "7.3.2" + version: "2.10.4" built_collection: dependency: transitive description: @@ -145,6 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + url: "https://pub.dev" + source: hosted + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -153,6 +201,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" clock: dependency: transitive description: @@ -185,6 +249,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + url: "https://pub.dev" + source: hosted + version: "1.15.0" crypto: dependency: transitive description: @@ -201,30 +273,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" - custom_lint_core: - dependency: transitive - description: - name: custom_lint_core - sha256: a85e8f78f4c52f6c63cdaf8c872eb573db0231dcdf3c3a5906d493c1f8bc20e6 - url: "https://pub.dev" - source: hosted - version: "0.6.3" dart_style: dependency: transitive description: name: dart_style - sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b url: "https://pub.dev" source: hosted - version: "2.3.2" - dartx: - dependency: transitive - description: - name: dartx - sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" - url: "https://pub.dev" - source: hosted - version: "1.2.0" + version: "3.1.3" dio: dependency: "direct main" description: @@ -241,6 +297,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + drift: + dependency: "direct main" + description: + name: drift + sha256: "3669e1b68d7bffb60192ac6ba9fd2c0306804d7a00e5879f6364c69ecde53a7f" + url: "https://pub.dev" + source: hosted + version: "2.30.0" + drift_dev: + dependency: "direct dev" + description: + name: drift_dev + sha256: afe4d1d2cfce6606c86f11a6196e974a2ddbfaa992956ce61e054c9b1899c769 + url: "https://pub.dev" + source: hosted + version: "2.30.0" + drift_flutter: + dependency: "direct main" + description: + name: drift_flutter + sha256: c07120854742a0cae2f7501a0da02493addde550db6641d284983c08762e60a7 + url: "https://pub.dev" + source: hosted + version: "0.2.8" equatable: dependency: "direct main" description: @@ -302,6 +382,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.4.1" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b + url: "https://pub.dev" + source: hosted + version: "5.2.1" flutter_lints: dependency: "direct dev" description: @@ -314,58 +402,58 @@ packages: dependency: "direct main" description: name: flutter_riverpod - sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" + sha256: "38ec6c303e2c83ee84512f5fc2a82ae311531021938e63d7137eccc107bf3c02" url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "3.1.0" flutter_secure_storage: dependency: "direct main" description: name: flutter_secure_storage - sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40 url: "https://pub.dev" source: hosted - version: "9.2.4" + version: "10.0.0" + flutter_secure_storage_darwin: + dependency: transitive + description: + name: flutter_secure_storage_darwin + sha256: "8878c25136a79def1668c75985e8e193d9d7d095453ec28730da0315dc69aee3" + url: "https://pub.dev" + source: hosted + version: "0.2.0" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda" url: "https://pub.dev" source: hosted - version: "1.2.3" - flutter_secure_storage_macos: - dependency: transitive - description: - name: flutter_secure_storage_macos - sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" - url: "https://pub.dev" - source: hosted - version: "3.1.3" + version: "3.0.0" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "2.0.1" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.1.0" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.1.0" flutter_svg: dependency: "direct main" description: @@ -388,18 +476,18 @@ packages: dependency: "direct dev" description: name: freezed - sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1 + sha256: "13065f10e135263a4f5a4391b79a8efc5fb8106f8dd555a9e49b750b45393d77" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "3.2.3" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "3.1.0" frontend_server_client: dependency: transitive description: @@ -420,18 +508,18 @@ packages: dependency: "direct main" description: name: go_router - sha256: c92d18e1fe994cb06d48aa786c46b142a5633067e8297cff6b5a3ac742620104 + sha256: eff94d2a6fc79fa8b811dde79c7549808c2346037ee107a1121b4a644c745f2a url: "https://pub.dev" source: hosted - version: "17.0.0" + version: "17.0.1" google_fonts: dependency: "direct main" description: name: google_fonts - sha256: "517b20870220c48752eafa0ba1a797a092fb22df0d89535fd9991e86ee2cdd9c" + sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055 url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.3" graphs: dependency: transitive description: @@ -480,38 +568,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" - isar: - dependency: "direct main" - description: - name: isar - sha256: "99165dadb2cf2329d3140198363a7e7bff9bbd441871898a87e26914d25cf1ea" - url: "https://pub.dev" - source: hosted - version: "3.1.0+1" - isar_flutter_libs: - dependency: "direct main" - description: - name: isar_flutter_libs - sha256: bc6768cc4b9c61aabff77152e7f33b4b17d2fc93134f7af1c3dd51500fe8d5e8 - url: "https://pub.dev" - source: hosted - version: "3.1.0+1" - isar_generator: - dependency: "direct dev" - description: - name: isar_generator - sha256: "76c121e1295a30423604f2f819bc255bc79f852f3bc8743a24017df6068ad133" - url: "https://pub.dev" - source: hosted - version: "3.1.0+1" js: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.2" json_annotation: dependency: "direct main" description: @@ -524,10 +588,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b + sha256: c5b2ee75210a0f263c6c7b9eeea80553dbae96ea1bf57f02484e806a3ffdffa3 url: "https://pub.dev" source: hosted - version: "6.8.0" + version: "6.11.2" leak_tracker: dependency: transitive description: @@ -608,6 +672,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + mockito: + dependency: transitive + description: + name: mockito + sha256: dac24d461418d363778d53198d9ac0510b9d073869f078450f195766ec48d05e + url: "https://pub.dev" + source: hosted + version: "5.6.1" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" octo_image: dependency: transitive description: @@ -744,38 +824,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.0" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" riverpod: dependency: transitive description: name: riverpod - sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" + sha256: "16ff608d21e8ea64364f2b7c049c94a02ab81668f78845862b6e88b71dd4935a" url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "3.1.0" riverpod_analyzer_utils: dependency: transitive description: name: riverpod_analyzer_utils - sha256: "8b71f03fc47ae27d13769496a1746332df4cec43918aeba9aff1e232783a780f" + sha256: "947b05d04c52a546a2ac6b19ef2a54b08520ff6bdf9f23d67957a4c8df1c3bc0" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.0.0-dev.8" riverpod_annotation: dependency: "direct main" description: name: riverpod_annotation - sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8 + sha256: cc1474bc2df55ec3c1da1989d139dcef22cd5e2bd78da382e867a69a8eca2e46 url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "4.0.0" riverpod_generator: dependency: "direct dev" description: name: riverpod_generator - sha256: d451608bf17a372025fc36058863737636625dfdb7e3cbf6142e0dfeb366ab22 + sha256: e43b1537229cc8f487f09b0c20d15dba840acbadcf5fc6dad7ad5e8ab75950dc url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "4.0.0+1" rxdart: dependency: transitive description: @@ -788,18 +876,18 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.4" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "46a46fd64659eff15f4638bbe19de43f9483f0e0bf024a9fb6b3582064bacc7b" + sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc" url: "https://pub.dev" source: hosted - version: "2.4.17" + version: "2.4.18" shared_preferences_foundation: dependency: transitive description: @@ -848,14 +936,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.0" shimmer: dependency: "direct main" description: @@ -873,18 +977,34 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: "07b277b67e0096c45196cbddddf2d8c6ffc49342e88bf31d460ce04605ddac75" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "4.1.1" source_helper: dependency: transitive description: name: source_helper - sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" + sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723" url: "https://pub.dev" source: hosted - version: "1.3.5" + version: "1.3.8" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" source_span: dependency: transitive description: @@ -933,6 +1053,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.0" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2" + url: "https://pub.dev" + source: hosted + version: "2.9.4" + sqlite3_flutter_libs: + dependency: "direct main" + description: + name: sqlite3_flutter_libs + sha256: "1e800ebe7f85a80a66adacaa6febe4d5f4d8b75f244e9838a27cb2ffc7aec08d" + url: "https://pub.dev" + source: hosted + version: "0.5.41" + sqlparser: + dependency: transitive + description: + name: sqlparser + sha256: "162435ede92bcc793ea939fdc0452eef0a73d11f8ed053b58a89792fba749da5" + url: "https://pub.dev" + source: hosted + version: "0.42.1" stack_trace: dependency: transitive description: @@ -989,6 +1133,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.2" + test: + dependency: transitive + description: + name: test + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + url: "https://pub.dev" + source: hosted + version: "1.26.3" test_api: dependency: transitive description: @@ -997,22 +1149,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.7" - time: + test_core: dependency: transitive description: - name: time - sha256: "370572cf5d1e58adcb3e354c47515da3f7469dac3a95b447117e728e7be6f461" + name: test_core + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" url: "https://pub.dev" source: hosted - version: "2.1.5" - timing: - dependency: transitive - description: - name: timing - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" - url: "https://pub.dev" - source: hosted - version: "1.0.2" + version: "0.6.12" typed_data: dependency: transitive description: @@ -1073,10 +1217,10 @@ packages: dependency: transitive description: name: watcher - sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" + sha256: f52385d4f73589977c80797e60fe51014f7f2b957b5e9a62c3f6ada439889249 url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.2.0" web: dependency: transitive description: @@ -1101,6 +1245,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" win32: dependency: transitive description: @@ -1125,14 +1277,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.6.1" - xxh3: - dependency: transitive - description: - name: xxh3 - sha256: "399a0438f5d426785723c99da6b16e136f4953fb1e9db0bf270bd41dd4619916" - url: "https://pub.dev" - source: hosted - version: "1.2.0" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4bc1368..eb7bccb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,22 +1,27 @@ name: slrpg_app description: Streetlifting RPG - Gamified Training App -publish_to: 'none' +publish_to: "none" version: 1.0.0+1 environment: - sdk: '>=3.2.0 <4.0.0' + sdk: ">=3.2.0 <4.0.0" dependencies: flutter: sdk: flutter + flutter_localizations: + sdk: flutter + audioplayers: ^6.0.0 + flutter_dotenv: ^5.1.0 # State Management - flutter_riverpod: ^2.5.1 - riverpod_annotation: ^2.3.5 + flutter_riverpod: ^3.1.0 + riverpod_annotation: ^4.0.0 # Local Database - isar: ^3.1.0+1 - isar_flutter_libs: ^3.1.0+1 + drift: ^2.16.0 + drift_flutter: ^0.2.8 + sqlite3_flutter_libs: ^0.5.20 path_provider: ^2.1.3 # Networking @@ -24,7 +29,7 @@ dependencies: pretty_dio_logger: ^1.3.1 # Storage - flutter_secure_storage: ^9.0.0 + flutter_secure_storage: ^10.0.0 shared_preferences: ^2.2.3 # UI Components @@ -36,7 +41,7 @@ dependencies: # Utilities intl: ^0.20.2 - freezed_annotation: ^2.4.1 + freezed_annotation: ^3.1.0 json_annotation: ^4.9.0 equatable: ^2.0.5 logger: ^2.3.0 @@ -51,13 +56,14 @@ dev_dependencies: # Code Generation build_runner: ^2.4.9 - riverpod_generator: ^2.4.0 - isar_generator: ^3.1.0+1 - freezed: ^2.5.2 + riverpod_generator: ^4.0.0+1 + drift_dev: ^2.16.0 + freezed: ^3.2.3 json_serializable: ^6.8.0 flutter: uses-material-design: true + generate: true assets: - assets/images/ @@ -66,6 +72,10 @@ flutter: - assets/images/plates/ - assets/images/enemies/ - assets/images/backgrounds/ + - assets/audio/ + - .env + - .env.development + - .env.production # fonts: # - family: PixelFont