diff --git a/stronglift_replacement/design.md b/stronglift_replacement/design.md
new file mode 100644
index 0000000..0c366af
--- /dev/null
+++ b/stronglift_replacement/design.md
@@ -0,0 +1,43 @@
+Why:
+Stronglift app on my rooted device stopped working, we need a new app for tracking workout
+on that note please disable screen locker functionality untill app is ready to go and fully tested functionally
+
+Functional Requirements:
+ Tracks workouts (current {sets}x{reps}x{weight (in kg)}):
+ Workout A:
+ Dumbbell Lunge 5x12x7.5
+ Dumbbell Bench Press 5x12x22.5
+ Dumbbell Row 4x6x22.5
+ Dumbbell Curl 3x12x12.5
+
+ Workout B:
+ Dumbbell Romanian Deadlift 5x7x27.5
+ Dummbbell Overhead Press 5x12x7.5
+ Dumbbell Bench Press 5x12x22.5
+ Situp 3x30x10
+
+
+Exercisees succeded means that the user was able to do ALL sets with ALL reps
+Automatically increases weight (in increments of 2.5kg) or number of reps (if maximum weight of 27.5 kg was reached for given exercise (see Dumbbell Romanian Deadlift) Situp has maximum weight of 10kg)
+if user succeeded in doign this exercise in a continous way for between 1-5 days in a row (selectable by user unless max weight reached in which case 27.5 kg should always be chosen)
+automatically decreases weight (in decrements of 2.5kg) if user failed to do the exercise for 1-5 days in a row (selectable by user)
+automatically decreases weight if user had a break from using the app
+Tracks how much time workout took -> Fully automatically, user CANNOT set it manually
+Adds optional warmup exercises (With weight equal 2/3 of target weight (rounding DOWN to nearest increment of 2.5kg) and always having 5 reps exactly and exactly one set) before each exercise (after warmup 3 minutes break too)
+adds breaks between exercises (3 minutes if exercise succeeded and 5 minutes if it failed)
+The user selects exercise as done by tapping on a circle with number of reps for this exercise, exercises are organized in rows where one row = one full set of one exercise, tapping on a circle again means that
+the user failed to do the exercise and it decreases the rep by "1" tappign again further reduces this count, if user holds finger over the specific circle they can reset the state of this circle (which should
+cancel any failed/succeed state)
+shows history of workouts and a graph for showing progress
+Crucial: The app should be able to comunicate with this pc (arch linux) and inform it if the user had succeed to do the exercises and transfer full info about todays workout to the pc, crucialy:
+time and how many sets reps and weight was done, change screen locker if needed
+The app should be capable of working in the background without any problem and display status notifications allowing user to click on "done rep" from the status bar
+After break time is over app should play a sound and vibrate the phone and generally point the user attention towards the app
+
+Technical Requirements:
+App should work on rooted and unrooted phones with minimum android version of at least 12
+Full test coverage (100%) (but first check the functionality and if the functionality fully works and is approved by user THEN start writing ANY tests at all please)
+I connected an unrooted phone with adb on to the pc use it for testing
+
+Nice to have:
+ make it work on both desktops (linux only is fine) and android phones (just android is fine)
diff --git a/stronglift_replacement/dfesign_v2.md b/stronglift_replacement/dfesign_v2.md
new file mode 100644
index 0000000..6ed5c25
--- /dev/null
+++ b/stronglift_replacement/dfesign_v2.md
@@ -0,0 +1,26 @@
+This is a continouation from design.md file with what is left to be done and what new ideas came to me since the last time arranged in order of importance
+
+Crucial (max 1 feature):
+ If user starts workout and later either exit the app completely or clicks the arrow in upper left the workout gets reseted completely, all progress is lost this is very bad
+ once user starts workout only by tapping finish and confriming that they INDEED finished workout should end it OR if user clicks and confirms RESET button, NOTHING ELSE
+
+High (max 2 features):
+ adds breaks between REPS (3 minutes if REP succeeded (as in all reps were done) and 5 minutes if it failed) <-- currently app ads breaks between SETS which wrong
+ After break time (after REPS) is over app should play a sound and vibrate the phone and generally point the user attention towards the app
+
+Mid (max 3 features):
+ change warmup exercises weight from 2/3 to 3/4 of target weight
+ Warmups should be a selectable circles in a separate screen, optional but still interactive and after doing them give the user a 3 minute break ALSO
+ The app should change between workout A and B AUTOMATICALLY, no user interaction if last workout done was "A" the next one should be "B" and then "A" and so on...
+
+Low (max 4 features):
+ If set is finished user cannot modify reps on this set for some reason -> this is a bug user should ALWAYS be able to modify ANY reps in ANY exercise
+ shows history of workouts and a graph for showing progress
+ The app should be capable of working in the background without any problem and display status notifications allowing user to click on "done rep" from the status bar
+ automatically decreases weight if user had a break from using the app <-- not sure if implemented (maybe implemented but did not have a change to check it add fallback manual setting of weights by user)
+
+
+Technical Requirements:
+App should work on rooted and unrooted phones with minimum android version of at least 12
+Full test coverage (100%) (but first check the functionality and if the functionality fully works and is approved by user THEN start writing ANY tests at all please)
+I connected an unrooted phone with adb on to the pc use it for testing
diff --git a/stronglift_replacement/workout_app/.gitignore b/stronglift_replacement/workout_app/.gitignore
new file mode 100644
index 0000000..3820a95
--- /dev/null
+++ b/stronglift_replacement/workout_app/.gitignore
@@ -0,0 +1,45 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.build/
+.buildlog/
+.history
+.svn/
+.swiftpm/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins-dependencies
+.pub-cache/
+.pub/
+/build/
+/coverage/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/stronglift_replacement/workout_app/.metadata b/stronglift_replacement/workout_app/.metadata
new file mode 100644
index 0000000..cda28f3
--- /dev/null
+++ b/stronglift_replacement/workout_app/.metadata
@@ -0,0 +1,30 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "559ffa3f75e7402d65a8def9c28389a9b2e6fe42"
+ channel: "stable"
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
+ base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
+ - platform: android
+ create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
+ base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/stronglift_replacement/workout_app/CONTEXT.md b/stronglift_replacement/workout_app/CONTEXT.md
new file mode 100644
index 0000000..d8e6e58
--- /dev/null
+++ b/stronglift_replacement/workout_app/CONTEXT.md
@@ -0,0 +1,20 @@
+# Workout App Context
+
+## Current Task
+Implementing design_v2.md improvements.
+
+## Key Decisions
+- Active session persisted to SQLite on every tap so force-kill is safe
+- PopScope removed — back button returns to home, workout continues in DB
+- Auto-resume: HomeScreen auto-navigates to workout on first load if session exists
+
+## Deferred / Not Yet Implemented
+- **Background notifications + break sound when phone is sleeping**: Dart timers
+ suspend when app is backgrounded. Needs a native Android foreground service
+ (e.g. `flutter_foreground_task` or custom Kotlin service) to keep the break
+ countdown running and fire the alert even when the screen is off.
+ File as a separate task before marking the app "done."
+
+## Next Steps
+- Clarify user intent on "no break between sets" (contradicts design_v2.md which
+ explicitly requested per-set breaks)
diff --git a/stronglift_replacement/workout_app/README.md b/stronglift_replacement/workout_app/README.md
new file mode 100644
index 0000000..aa1c1f2
--- /dev/null
+++ b/stronglift_replacement/workout_app/README.md
@@ -0,0 +1,17 @@
+# workout_app
+
+A new Flutter project.
+
+## Getting Started
+
+This project is a starting point for a Flutter application.
+
+A few resources to get you started if this is your first Flutter project:
+
+- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
+- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
+- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources)
+
+For help getting started with Flutter development, view the
+[online documentation](https://docs.flutter.dev/), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.
diff --git a/stronglift_replacement/workout_app/analysis_options.yaml b/stronglift_replacement/workout_app/analysis_options.yaml
new file mode 100644
index 0000000..0d29021
--- /dev/null
+++ b/stronglift_replacement/workout_app/analysis_options.yaml
@@ -0,0 +1,28 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at https://dart.dev/lints.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/stronglift_replacement/workout_app/android/.gitignore b/stronglift_replacement/workout_app/android/.gitignore
new file mode 100644
index 0000000..be3943c
--- /dev/null
+++ b/stronglift_replacement/workout_app/android/.gitignore
@@ -0,0 +1,14 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+.cxx/
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/to/reference-keystore
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/stronglift_replacement/workout_app/android/app/build.gradle.kts b/stronglift_replacement/workout_app/android/app/build.gradle.kts
new file mode 100644
index 0000000..ef334e4
--- /dev/null
+++ b/stronglift_replacement/workout_app/android/app/build.gradle.kts
@@ -0,0 +1,45 @@
+plugins {
+ id("com.android.application")
+ // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
+ id("dev.flutter.flutter-gradle-plugin")
+}
+
+android {
+ namespace = "com.kuhy.workout_app"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId = "com.kuhy.workout_app"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://flutter.dev/to/review-gradle-config.
+ minSdk = flutter.minSdkVersion
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+}
+
+kotlin {
+ compilerOptions {
+ jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/stronglift_replacement/workout_app/android/app/src/debug/AndroidManifest.xml b/stronglift_replacement/workout_app/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/stronglift_replacement/workout_app/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/stronglift_replacement/workout_app/android/app/src/main/AndroidManifest.xml b/stronglift_replacement/workout_app/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d4bec6d
--- /dev/null
+++ b/stronglift_replacement/workout_app/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/stronglift_replacement/workout_app/android/app/src/main/kotlin/com/kuhy/workout_app/MainActivity.kt b/stronglift_replacement/workout_app/android/app/src/main/kotlin/com/kuhy/workout_app/MainActivity.kt
new file mode 100644
index 0000000..0f1e89f
--- /dev/null
+++ b/stronglift_replacement/workout_app/android/app/src/main/kotlin/com/kuhy/workout_app/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.kuhy.workout_app
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity : FlutterActivity()
diff --git a/stronglift_replacement/workout_app/android/app/src/main/res/drawable-v21/launch_background.xml b/stronglift_replacement/workout_app/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/stronglift_replacement/workout_app/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/stronglift_replacement/workout_app/android/app/src/main/res/drawable/launch_background.xml b/stronglift_replacement/workout_app/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/stronglift_replacement/workout_app/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/stronglift_replacement/workout_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/stronglift_replacement/workout_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
Binary files /dev/null and b/stronglift_replacement/workout_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/stronglift_replacement/workout_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/stronglift_replacement/workout_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
Binary files /dev/null and b/stronglift_replacement/workout_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/stronglift_replacement/workout_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/stronglift_replacement/workout_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
Binary files /dev/null and b/stronglift_replacement/workout_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/stronglift_replacement/workout_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/stronglift_replacement/workout_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
Binary files /dev/null and b/stronglift_replacement/workout_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/stronglift_replacement/workout_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/stronglift_replacement/workout_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
Binary files /dev/null and b/stronglift_replacement/workout_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/stronglift_replacement/workout_app/android/app/src/main/res/values-night/styles.xml b/stronglift_replacement/workout_app/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/stronglift_replacement/workout_app/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/stronglift_replacement/workout_app/android/app/src/main/res/values/styles.xml b/stronglift_replacement/workout_app/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/stronglift_replacement/workout_app/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/stronglift_replacement/workout_app/android/app/src/profile/AndroidManifest.xml b/stronglift_replacement/workout_app/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/stronglift_replacement/workout_app/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/stronglift_replacement/workout_app/android/build.gradle.kts b/stronglift_replacement/workout_app/android/build.gradle.kts
new file mode 100644
index 0000000..dbee657
--- /dev/null
+++ b/stronglift_replacement/workout_app/android/build.gradle.kts
@@ -0,0 +1,24 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+val newBuildDir: Directory =
+ rootProject.layout.buildDirectory
+ .dir("../../build")
+ .get()
+rootProject.layout.buildDirectory.value(newBuildDir)
+
+subprojects {
+ val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
+ project.layout.buildDirectory.value(newSubprojectBuildDir)
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register("clean") {
+ delete(rootProject.layout.buildDirectory)
+}
diff --git a/stronglift_replacement/workout_app/android/gradle.properties b/stronglift_replacement/workout_app/android/gradle.properties
new file mode 100644
index 0000000..e96108c
--- /dev/null
+++ b/stronglift_replacement/workout_app/android/gradle.properties
@@ -0,0 +1,6 @@
+org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
+android.useAndroidX=true
+# This newDsl flag was added by the Flutter template
+android.newDsl=false
+# This builtInKotlin flag was added by the Flutter template
+android.builtInKotlin=false
diff --git a/stronglift_replacement/workout_app/android/gradle/wrapper/gradle-wrapper.properties b/stronglift_replacement/workout_app/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..2d428bf
--- /dev/null
+++ b/stronglift_replacement/workout_app/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip
diff --git a/stronglift_replacement/workout_app/android/settings.gradle.kts b/stronglift_replacement/workout_app/android/settings.gradle.kts
new file mode 100644
index 0000000..c21f0c5
--- /dev/null
+++ b/stronglift_replacement/workout_app/android/settings.gradle.kts
@@ -0,0 +1,26 @@
+pluginManagement {
+ val flutterSdkPath =
+ run {
+ val properties = java.util.Properties()
+ file("local.properties").inputStream().use { properties.load(it) }
+ val flutterSdkPath = properties.getProperty("flutter.sdk")
+ require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
+ flutterSdkPath
+ }
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id("dev.flutter.flutter-plugin-loader") version "1.0.0"
+ id("com.android.application") version "9.0.1" apply false
+ id("org.jetbrains.kotlin.android") version "2.3.20" apply false
+}
+
+include(":app")
diff --git a/stronglift_replacement/workout_app/lib/main.dart b/stronglift_replacement/workout_app/lib/main.dart
new file mode 100644
index 0000000..f8b3329
--- /dev/null
+++ b/stronglift_replacement/workout_app/lib/main.dart
@@ -0,0 +1,31 @@
+import 'package:flutter/material.dart';
+import 'package:workout_app/screens/home_screen.dart';
+import 'package:workout_app/services/http_server_service.dart';
+import 'package:workout_app/services/storage_service.dart';
+
+void main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+ await StorageService.init();
+ await HttpServerService.instance.start();
+ runApp(const WorkoutApp());
+}
+
+class WorkoutApp extends StatelessWidget {
+ const WorkoutApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'Workout Tracker',
+ debugShowCheckedModeBanner: false,
+ theme: ThemeData(
+ colorScheme: ColorScheme.fromSeed(
+ seedColor: Colors.indigo,
+ brightness: Brightness.dark,
+ ),
+ useMaterial3: true,
+ ),
+ home: const HomeScreen(),
+ );
+ }
+}
diff --git a/stronglift_replacement/workout_app/lib/models/exercise.dart b/stronglift_replacement/workout_app/lib/models/exercise.dart
new file mode 100644
index 0000000..1ca87a1
--- /dev/null
+++ b/stronglift_replacement/workout_app/lib/models/exercise.dart
@@ -0,0 +1,61 @@
+/// Core domain model for a single exercise definition and its current progression state.
+library;
+
+const double kDefaultMaxWeight = 27.5;
+const double kWeightIncrement = 2.5;
+
+class Exercise {
+ const Exercise({
+ required this.name,
+ required this.sets,
+ required this.reps,
+ required this.weight,
+ this.maxWeight = kDefaultMaxWeight,
+ });
+
+ final String name;
+ final int sets;
+ final int reps;
+ final double weight;
+
+ /// Weight cap beyond which reps increase instead of weight.
+ final double maxWeight;
+
+ /// Warmup weight: 4/5 of target weight, rounded DOWN to nearest 2.5 kg.
+ double get warmupWeight {
+ final raw = weight * 4.0 / 5.0;
+ return (raw / kWeightIncrement).floor() * kWeightIncrement;
+ }
+
+ Exercise copyWith({
+ String? name,
+ int? sets,
+ int? reps,
+ double? weight,
+ double? maxWeight,
+ }) {
+ return Exercise(
+ name: name ?? this.name,
+ sets: sets ?? this.sets,
+ reps: reps ?? this.reps,
+ weight: weight ?? this.weight,
+ maxWeight: maxWeight ?? this.maxWeight,
+ );
+ }
+
+ Map toJson() => {
+ 'name': name,
+ 'sets': sets,
+ 'reps': reps,
+ 'weight': weight,
+ 'maxWeight': maxWeight,
+ };
+
+ factory Exercise.fromJson(Map json) => Exercise(
+ name: json['name'] as String,
+ sets: json['sets'] as int,
+ reps: json['reps'] as int,
+ weight: (json['weight'] as num).toDouble(),
+ maxWeight: (json['maxWeight'] as num?)?.toDouble() ?? kDefaultMaxWeight,
+ );
+}
diff --git a/stronglift_replacement/workout_app/lib/models/exercise_result.dart b/stronglift_replacement/workout_app/lib/models/exercise_result.dart
new file mode 100644
index 0000000..9fa0b1f
--- /dev/null
+++ b/stronglift_replacement/workout_app/lib/models/exercise_result.dart
@@ -0,0 +1,27 @@
+/// All set results for one exercise in a workout session.
+library;
+
+import 'package:workout_app/models/exercise.dart';
+import 'package:workout_app/models/set_result.dart';
+
+class ExerciseResult {
+ const ExerciseResult({
+ required this.exercise,
+ required this.sets,
+ });
+
+ final Exercise exercise;
+ final List sets;
+
+ /// True when every set was fully completed.
+ bool get succeeded => sets.isNotEmpty && sets.every((s) => s.succeeded);
+
+ Map toJson() => {
+ 'name': exercise.name,
+ 'targetSets': exercise.sets,
+ 'targetReps': exercise.reps,
+ 'targetWeight': exercise.weight,
+ 'sets': sets.map((s) => s.toJson()).toList(),
+ 'succeeded': succeeded,
+ };
+}
diff --git a/stronglift_replacement/workout_app/lib/models/set_result.dart b/stronglift_replacement/workout_app/lib/models/set_result.dart
new file mode 100644
index 0000000..ca833a3
--- /dev/null
+++ b/stronglift_replacement/workout_app/lib/models/set_result.dart
@@ -0,0 +1,39 @@
+/// Result of a single set during a workout session.
+library;
+
+class SetResult {
+ const SetResult({
+ required this.targetReps,
+ required this.doneReps,
+ required this.weight,
+ });
+
+ final int targetReps;
+
+ /// How many reps the user actually completed (may be < targetReps on failure).
+ final int doneReps;
+
+ final double weight;
+
+ /// True when the user completed every target rep.
+ bool get succeeded => doneReps >= targetReps;
+
+ SetResult copyWith({int? doneReps}) => SetResult(
+ targetReps: targetReps,
+ doneReps: doneReps ?? this.doneReps,
+ weight: weight,
+ );
+
+ Map toJson() => {
+ 'targetReps': targetReps,
+ 'doneReps': doneReps,
+ 'weight': weight,
+ 'succeeded': succeeded,
+ };
+
+ factory SetResult.fromJson(Map json) => SetResult(
+ targetReps: json['targetReps'] as int,
+ doneReps: json['doneReps'] as int,
+ weight: (json['weight'] as num).toDouble(),
+ );
+}
diff --git a/stronglift_replacement/workout_app/lib/models/workout_plan.dart b/stronglift_replacement/workout_app/lib/models/workout_plan.dart
new file mode 100644
index 0000000..5d5be82
--- /dev/null
+++ b/stronglift_replacement/workout_app/lib/models/workout_plan.dart
@@ -0,0 +1,37 @@
+/// Static workout plans A and B with their default exercise configurations.
+library;
+
+import 'package:workout_app/models/exercise.dart';
+
+/// Situp has a lower max weight cap.
+const double kSitupMaxWeight = 10.0;
+
+final workoutA = [
+ const Exercise(name: 'Dumbbell Lunge', sets: 5, reps: 12, weight: 7.5),
+ const Exercise(name: 'Dumbbell Bench Press', sets: 5, reps: 12, weight: 22.5),
+ const Exercise(name: 'Dumbbell Row', sets: 4, reps: 6, weight: 22.5),
+ const Exercise(name: 'Dumbbell Curl', sets: 3, reps: 12, weight: 12.5),
+];
+
+final workoutB = [
+ const Exercise(
+ name: 'Dumbbell Romanian Deadlift',
+ sets: 5,
+ reps: 7,
+ weight: 27.5,
+ ),
+ const Exercise(
+ name: 'Dumbbell Overhead Press',
+ sets: 5,
+ reps: 12,
+ weight: 7.5,
+ ),
+ const Exercise(name: 'Dumbbell Bench Press', sets: 5, reps: 12, weight: 22.5),
+ const Exercise(
+ name: 'Situp',
+ sets: 3,
+ reps: 30,
+ weight: 10.0,
+ maxWeight: kSitupMaxWeight,
+ ),
+];
diff --git a/stronglift_replacement/workout_app/lib/models/workout_session.dart b/stronglift_replacement/workout_app/lib/models/workout_session.dart
new file mode 100644
index 0000000..a80f0ff
--- /dev/null
+++ b/stronglift_replacement/workout_app/lib/models/workout_session.dart
@@ -0,0 +1,37 @@
+/// A completed workout session — serialised to JSON for PC sync.
+library;
+
+import 'dart:convert';
+import 'package:workout_app/models/exercise_result.dart';
+
+class WorkoutSession {
+ const WorkoutSession({
+ required this.workoutType,
+ required this.startTime,
+ required this.endTime,
+ required this.exercises,
+ });
+
+ /// 'A' or 'B'.
+ final String workoutType;
+ final DateTime startTime;
+ final DateTime endTime;
+ final List exercises;
+
+ Duration get duration => endTime.difference(startTime);
+
+ /// True when every exercise succeeded.
+ bool get fullySucceeded => exercises.every((e) => e.succeeded);
+
+ Map toJson() => {
+ 'workout_type': workoutType,
+ 'date': startTime.toIso8601String().substring(0, 10),
+ 'start_time': startTime.toIso8601String(),
+ 'end_time': endTime.toIso8601String(),
+ 'duration_seconds': duration.inSeconds,
+ 'succeeded': fullySucceeded,
+ 'exercises': exercises.map((e) => e.toJson()).toList(),
+ };
+
+ String toJsonString() => const JsonEncoder.withIndent(' ').convert(toJson());
+}
diff --git a/stronglift_replacement/workout_app/lib/screens/history_screen.dart b/stronglift_replacement/workout_app/lib/screens/history_screen.dart
new file mode 100644
index 0000000..2611bc1
--- /dev/null
+++ b/stronglift_replacement/workout_app/lib/screens/history_screen.dart
@@ -0,0 +1,307 @@
+/// History screen: past workout list with per-exercise weight progress chart.
+library;
+
+import 'dart:convert';
+import 'dart:math';
+import 'package:flutter/material.dart';
+import 'package:workout_app/services/storage_service.dart';
+
+class HistoryScreen extends StatefulWidget {
+ const HistoryScreen({super.key});
+
+ @override
+ State createState() => _HistoryScreenState();
+}
+
+class _HistoryScreenState extends State {
+ List