diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 496ccd6..d1a3a3f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -97,8 +97,8 @@ jobs: # exempt-all-pr-assignees: true # @maintainer: Or exempt specific maintainer/bot accounts (comma-separated): - # exempt-issue-assignees: 'maintainer1,maintainer2' - # exempt-pr-assignees: 'maintainer1,maintainer2' + exempt-issue-assignees: 'DecodeX15' + exempt-pr-assignees: 'DecodeX15' # ── PR filtering ───────────────────────────────────────────────────── # @maintainer: Uncomment to process ONLY PRs that carry a specific label diff --git a/.metadata b/.metadata index 08c2478..768732a 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "3b62efc2a3da49882f43c372e0bc53daef7295a6" + revision: "559ffa3f75e7402d65a8def9c28389a9b2e6fe42" channel: "stable" project_type: app @@ -13,26 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 - base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 - platform: android - create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 - base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 - platform: ios - create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 - base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 - platform: linux - create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 - base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 - platform: macos - create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 - base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 - platform: web - create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 - base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 - platform: windows - create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 - base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 # User provided section diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/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/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..2b58534 --- /dev/null +++ b/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.example.template_flutter" + 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.example.template_flutter" + // 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/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..81606dd --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/template_flutter/MainActivity.kt b/android/app/src/main/kotlin/com/example/template_flutter/MainActivity.kt new file mode 100644 index 0000000..4530d3a --- /dev/null +++ b/android/app/src/main/kotlin/com/example/template_flutter/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.template_flutter + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/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/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..e96108c --- /dev/null +++ b/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/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2d428bf --- /dev/null +++ b/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/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..c21f0c5 --- /dev/null +++ b/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/assets/exercises/exercises.json b/assets/exercises/exercises.json new file mode 100644 index 0000000..4ef7ab2 --- /dev/null +++ b/assets/exercises/exercises.json @@ -0,0 +1,1873 @@ +{ + "exercises": [ + { + "exercise_id": "Ex1", + "name": "Squat Reach", + "type": "bodyweight", + "primary_muscles": "quadriceps;glutes", + "secondary_muscles": "hamstrings;shoulders;core", + "body_region": "lower_body;mobility_and_flexibility", + "movement_pattern": "squat + reach", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "mobility;warmup;fat_burn", + "estimated_duration": "45s", + "overview": "A dynamic squat variation that adds an overhead reach to increase mobility and coordination.", + "benefits": "Improves lower body mobility activates legs and warms up hips and shoulders", + "contraindications": "knee_injury ,severe_hip_pain", + "instructions": "Keep feet shoulder-width apart and chest upright throughout.\nPush hips back and bend knees to lower into the squat.\nAs you stand, extend hips fully and reach both arms overhead.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/squat-reach-Dp1w6IjOlQ" + }, + { + "exercise_id": "Ex2", + "name": "Jumping Squats", + "type": "bodyweight", + "primary_muscles": "quadriceps;glutes", + "secondary_muscles": "hamstrings;calves", + "body_region": "lower_body;cardio_and_endurance", + "movement_pattern": "plyometric squat", + "difficulty": "intermediate", + "intensity": "high", + "goal_tags": "power;fat_burn;conditioning", + "estimated_duration": "30s", + "overview": "A plyometric squat designed to develop explosive leg power.", + "benefits": "Increases leg power boosts cardiovascular intensity and improves athletic performance", + "contraindications": "knee injury , ankle injury", + "instructions": "Start with feet shoulder-width apart and lower into a controlled squat.\nDrive through your heels and jump explosively upward.\nLand softly with bent knees and flow straight into the next squat.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/jumping-squats-9hzVV8Ohi6" + }, + { + "exercise_id": "Ex3", + "name": "Squat Kicks", + "type": "bodyweight", + "primary_muscles": "quadriceps;glutes", + "secondary_muscles": "core;hip flexors", + "body_region": "lower_body;cardio_and_endurance", + "movement_pattern": "squat + kick", + "difficulty": "intermediate", + "intensity": "moderate", + "goal_tags": "cardio;coordination", + "estimated_duration": "40s", + "overview": "A dynamic lower body movement combining squats and alternating kicks.", + "benefits": "Improves coordination balance and lower body endurance", + "contraindications": "knee injury, hip pain", + "instructions": "Lower into a squat with feet shoulder-width apart and core tight.\nAs you rise, extend one leg forward in a controlled kick.\nReturn the foot down and alternate legs with each rep.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/squat-kicks-2UaqzO5zfR" + }, + { + "exercise_id": "Ex4", + "name": "Burpee Jump", + "type": "bodyweight", + "primary_muscles": "quadriceps;chest;core", + "secondary_muscles": "core;quadriceps;chest", + "body_region": "upper_body;lower_body;core;cardio_and_endurance", + "movement_pattern": "burpee plyometric", + "difficulty": "advanced", + "intensity": "high", + "goal_tags": "cardio;conditioning;fat_burn", + "estimated_duration": "30s", + "overview": "A high intensity full body conditioning movement combining squat plank and jump.", + "benefits": "Raises heart rate quickly improves endurance and full body strength", + "contraindications": "heart condition, joint injuries", + "instructions": "Drop into a squat, place hands on the floor, and kick feet back to plank.\nReturn feet to hands quickly and keep your core tight throughout.\nJump explosively upward and reach arms overhead to complete the rep.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/burpee-and-jump-exercise-gCOcxxnr1X" + }, + { + "exercise_id": "Ex5", + "name": "Push Ups", + "type": "bodyweight", + "primary_muscles": "chest", + "secondary_muscles": "triceps;shoulders;core", + "body_region": "upper_body;core", + "movement_pattern": "push", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "strength;upper_body", + "estimated_duration": "40s", + "overview": "A classic upper body strength exercise using bodyweight resistance.", + "benefits": "Strengthens chest shoulders triceps and core stability", + "contraindications": "wrist pain, shoulder injury", + "instructions": "Keep hands slightly wider than shoulders and body in a straight line.\nLower chest toward the floor with elbows bending in a controlled manner.\nPush through palms to return to start without letting hips sag.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/military-push-ups-sFmjfnsBQ9" + }, + { + "exercise_id": "Ex6", + "name": "T Plank", + "type": "bodyweight", + "primary_muscles": "core", + "secondary_muscles": "shoulders;obliques", + "body_region": "core;upper_body", + "movement_pattern": "plank stabilization", + "difficulty": "intermediate", + "intensity": "moderate", + "goal_tags": "core_stability;balance", + "estimated_duration": "40s", + "overview": "A plank variation that introduces rotation and shoulder stability work.", + "benefits": "Enhances core stability shoulder control and balance", + "contraindications": "shoulder injury, lower back pain", + "instructions": "Start in a solid plank with hands under shoulders and body aligned.\nRotate torso to one side and lift one arm toward the ceiling.\nHold briefly, return to plank, and alternate sides with control.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/t-plank-exercise-g5qVU6RPYY" + }, + { + "exercise_id": "Ex7", + "name": "Lunges", + "type": "bodyweight", + "primary_muscles": "quadriceps;glutes", + "secondary_muscles": "hamstrings;calves", + "body_region": "lower_body", + "movement_pattern": "lunge", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "strength;mobility", + "estimated_duration": "45s", + "overview": "A foundational lower body exercise focused on unilateral leg strength.", + "benefits": "Builds leg strength improves balance and enhances mobility", + "contraindications": "knee injury ,severe joint pain", + "instructions": "Step forward and lower hips until both knees are at right angles.\nKeep torso upright and front knee aligned over the foot.\nPush through the front heel to return to standing then alternate legs.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1772802973/Character_Lunges_For_Both_Legs_jkvuon.mp4" + }, + { + "exercise_id": "Ex8", + "name": "Mountain Climbers", + "type": "bodyweight", + "primary_muscles": "core;shoulders", + "secondary_muscles": "shoulders;hip flexors", + "body_region": "core;cardio_and_endurance", + "movement_pattern": "cardio core", + "difficulty": "intermediate", + "intensity": "high", + "goal_tags": "cardio;fat_burn;core", + "estimated_duration": "30s", + "overview": "A fast paced core and cardio exercise performed from a plank position.", + "benefits": "Improves cardiovascular fitness core strength and agility", + "contraindications": "wrist pain ,shoulder injury", + "instructions": "Hold a strong high plank with hands directly under shoulders.\nDrive one knee toward the chest while the other leg stays extended.\nSwitch legs quickly in a running motion without letting hips rise.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1772806453/mountain_cliber_yhpwqf.mp4" + }, + { + "exercise_id": "Ex9", + "name": "Jumping Jacks", + "type": "bodyweight", + "primary_muscles": "full body", + "secondary_muscles": "shoulders;calves", + "body_region": "cardio_and_endurance", + "movement_pattern": "cardio", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "cardio;warmup", + "estimated_duration": "40s", + "overview": "A classic aerobic exercise often used during warm ups.", + "benefits": "Increases heart rate improves coordination and warms the entire body", + "contraindications": "ankle injury ,severe knee pain", + "instructions": "Start with feet together and arms by your sides.\nJump feet outward while raising arms overhead in a wide arc.\nJump back to start and keep a steady rhythmic pace throughout.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/jumping-jack-Q3NN7cRkd4" + }, + { + "exercise_id": "Ex10", + "name": "Walking Hip Opener", + "type": "bodyweight", + "primary_muscles": "hip flexors;glutes", + "secondary_muscles": "core;adductors;abductors", + "body_region": "lower_body;mobility_and_flexibility", + "movement_pattern": "dynamic mobility", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "mobility;warmup;hip_activation", + "estimated_duration": "40s", + "overview": "A dynamic warm-up exercise where the hip is lifted and externally rotated while walking to improve mobility.", + "benefits": "Increases hip mobility activates glutes and prepares lower body for movement", + "contraindications": "hip injury ,severe groin strain", + "instructions": "Step forward and lift the knee toward the chest as you walk.\nRotate the lifted leg outward to open the hip with torso tall.\nPlace the foot down and repeat the same movement with the opposite leg.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/walking-hip-opener-standard-yJtiYrJwk0" + }, + { + "exercise_id": "Ex11", + "name": "Straight Leg March", + "type": "bodyweight", + "primary_muscles": "hamstrings;hip_flexors", + "secondary_muscles": "glutes;core;quadriceps", + "body_region": "lower_body;mobility_and_flexibility", + "movement_pattern": "march + hip hinge", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "mobility;warmup;flexibility;balance", + "estimated_duration": "45s", + "overview": "A dynamic walking exercise that stretches the hamstrings, activates core stability, and improves dynamic balance.", + "benefits": "Stretches hamstrings dynamically and improves single-leg balance which activates core and enhances hip mobility and coordination", + "contraindications": "hamstring_tear;severe_lower_back_pain;acute_hip_injury", + "instructions": "Stand tall with chest up and chin up and transfer weight onto one foot. \nkick the opposite leg up as high as comfortably possible without bending it, and simultaneously extend the opposite arm to meet the foot.\nPull the leg back down, step forward, and repeat on the other side.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1773866946/straight-leg_march_dmfcjt.mp4" + }, + { + "exercise_id": "Ex12", + "name": "Leg Raise To Knee Tuck", + "type": "bodyweight", + "primary_muscles": "lower_abs", + "secondary_muscles": "obliques;quadriceps;upper_abs", + "body_region": "core", + "movement_pattern": "leg_raise + knee_tuck", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "core;fat_burn", + "estimated_duration": "40s", + "overview": "A core exercise combining leg raises with knee tucks to maximize abdominal engagement.", + "benefits": "Strengthens lower abs improves control and enhances core coordination", + "contraindications": "lower_back_pain", + "instructions": "Lie on your back with legs extended and arms by your sides.\nLift legs then bend knees toward chest while raising hips slightly.\nExtend legs back out and lower down in control.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1780722130/Leg_Raise_To_Knee_Tuck_na99qi.mp4" + }, + { + "exercise_id": "Ex13", + "name": "Split Jump", + "type": "bodyweight", + "primary_muscles": "quadriceps;glutes", + "secondary_muscles": "hamstrings;calves;core", + "body_region": "lower_body;cardio_and_endurance", + "movement_pattern": "plyometric lunge", + "difficulty": "intermediate", + "intensity": "high", + "goal_tags": "power;conditioning;fat_burn", + "estimated_duration": "30s", + "overview": "A plyometric lower body exercise where the athlete jumps from a split lunge and switches legs midair.", + "benefits": "Develops explosive leg power improves coordination and increases cardiovascular intensity", + "contraindications": "knee injury ,ankle instability, hip pain", + "instructions": "Begin in a lunge with torso upright and core engaged.\nJump explosively and switch leg positions in midair.\nLand softly in the opposite lunge with bent knees and immediately reset.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/split-jump-exercise-AHHpx1bumx" + }, + { + "exercise_id": "Ex14", + "name": "Press Up Position Toe Tap", + "type": "bodyweight", + "primary_muscles": "core", + "secondary_muscles": "shoulders;glutes;hip flexors", + "body_region": "core;upper_body", + "movement_pattern": "plank stability", + "difficulty": "intermediate", + "intensity": "moderate", + "goal_tags": "core_stability;coordination", + "estimated_duration": "40s", + "overview": "A plank variation where one foot taps outward to challenge stability and core control.", + "benefits": "Improves core stability enhances shoulder endurance and develops coordination", + "contraindications": "wrist pain ,shoulder injury, lower back pain", + "instructions": "Start in a high plank with hands directly under shoulders.\nLift one foot and tap it out to the side keeping hips stable.\nReturn foot to start and alternate sides while maintaining plank form.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/press-up-postion-toe-tap-2nA1VGIcK3" + }, + { + "exercise_id": "Ex15", + "name": "Abdominal Crunches", + "type": "bodyweight", + "primary_muscles": "rectus_abdominis", + "secondary_muscles": "obliques;transverse_abdominis", + "body_region": "core", + "movement_pattern": "spinal_flexion", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "strength;core;fat_burn;toning", + "estimated_duration": "45s", + "overview": "A classic core exercise performed on the floor where the upper body curls toward the knees, isolating the abdominal muscles without lifting the lower back off the ground.", + "benefits": "Strengthens and tones abdominal muscles; improves core stability and posture; trims waistline; enhances balance and stability", + "contraindications": "lower_back_injury;neck_injury;osteoporosis;post_abdominal_surgery", + "instructions": "Lie on your back, knees bent, feet flat on the floor, hands on chest or behind head.\nContract your abs and lift your shoulders off the floor, curling toward your knees.\nHold briefly at the top, then slowly lower back down and repeat.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/abs-crunches-kqvZGz4NGQ" + }, + { + "exercise_id": "Ex16", + "name": "Box Jump", + "type": "bodyweight", + "primary_muscles": "quadriceps;glutes", + "secondary_muscles": "hamstrings;calves;core", + "body_region": "lower_body;cardio_and_endurance", + "movement_pattern": "plyometric jump", + "difficulty": "intermediate", + "intensity": "high", + "goal_tags": "power;athletic_performance;conditioning", + "estimated_duration": "30s", + "overview": "A plyometric lower body exercise where the athlete jumps from the ground onto an elevated surface.", + "benefits": "Develops explosive leg power improves coordination and enhances lower body strength", + "contraindications": "knee injury ,ankle instability ,balance disorders", + "instructions": "Stand facing the box with feet shoulder-width apart.\nBend knees, swing arms back, and jump explosively onto the platform.\nLand softly with bent knees then step down carefully before repeating.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/box-jump-5Ac829O8Kk" + }, + { + "exercise_id": "Ex17", + "name": "Cobra Stretch", + "type": "bodyweight", + "primary_muscles": "lower back", + "secondary_muscles": "abdominals;shoulders;chest", + "body_region": "core;mobility_and_flexibility", + "movement_pattern": "spinal extension", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "mobility;flexibility;posture;back_pain_relief", + "estimated_duration": "40s", + "overview": "A floor based spinal extension stretch commonly used in yoga and mobility routines.", + "benefits": "Improves spinal mobility stretches abdominal muscles and helps reduce stiffness from prolonged sitting", + "contraindications": "lower back injury ,spinal disc problems, pregnancy caution", + "instructions": "Lie face down with legs extended and palms placed under shoulders.\nPress through hands to lift the chest while keeping hips on the floor.\nHold the stretch briefly then lower torso back down with control.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/cobras-w0bL4N81bN" + }, + { + "exercise_id": "Ex18", + "name": "Punches", + "type": "bodyweight", + "primary_muscles": "shoulders", + "secondary_muscles": "core;triceps;chest", + "body_region": "upper_body;cardio_and_endurance", + "movement_pattern": "striking cardio", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "cardio;coordination;upper_body", + "estimated_duration": "40s", + "overview": "A simple cardio exercise inspired by boxing movements performed without equipment.", + "benefits": "Increases cardiovascular activity improves coordination and strengthens shoulders and arms", + "contraindications": "shoulder injury ,elbow pain ,wrist injury", + "instructions": "Keep knees slightly bent, core engaged, and stance stable.\nExtend one arm forward with a slight torso rotation for power.\nQuickly retract and alternate arms in a steady controlled rhythm.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/punches-DK1guXUDFv" + }, + { + "exercise_id": "Ex19", + "name": "Wide Arm Push Up", + "type": "bodyweight", + "primary_muscles": "chest", + "secondary_muscles": "shoulders;triceps;core", + "body_region": "upper_body;core", + "movement_pattern": "push", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "strength;upper_body;bodyweight", + "estimated_duration": "40s", + "overview": "A push-up variation where the hands are positioned wider to emphasize chest activation.", + "benefits": "Increases chest engagement strengthens shoulders and improves upper body pushing strength", + "contraindications": "wrist pain ,shoulder injury ,elbow pain", + "instructions": "Place hands wider than shoulders and keep body in a straight line.\nLower chest toward the floor with elbows flaring slightly outward.\nPush through palms to return to start while keeping core tight.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/wide-arm-push-up-KD96z4DT4Y" + }, + { + "exercise_id": "Ex20", + "name": "Shoulder Stretch", + "type": "bodyweight", + "primary_muscles": "shoulders", + "secondary_muscles": "upper back;triceps", + "body_region": "upper_body;mobility_and_flexibility", + "movement_pattern": "static stretch", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "mobility;flexibility;warmup", + "estimated_duration": "40s", + "overview": "A simple upper body stretch that targets the shoulder muscles and improves joint mobility.", + "benefits": "Improves shoulder flexibility reduces stiffness and helps prevent injury during upper body exercises", + "contraindications": "shoulder injury , severe rotator cuff tear", + "instructions": "Extend one arm straight across the chest at shoulder height.\nUse the opposite hand to gently pull the arm closer to the body.\nKeep the shoulder relaxed, hold briefly, then switch arms.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/shoulder-stretch-EN29iDeQQg" + }, + { + "exercise_id": "Ex21", + "name": "Leg Raise Hold", + "type": "bodyweight", + "primary_muscles": "lower_abs", + "secondary_muscles": "obliques;quadriceps;upper_abs", + "body_region": "core", + "movement_pattern": "isometric_hold", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "core;stability", + "estimated_duration": "30s", + "overview": "A static core exercise where legs are held at an angle to build abdominal strength.", + "benefits": "Strengthens lower abs improves endurance and enhances core stability", + "contraindications": "lower_back_pain", + "instructions": "Lie on your back with legs extended and arms by your sides.\nRaise legs to about 45 degrees while keeping them straight.\nHold position then lower legs in control.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/image/upload/v1780722114/Leg_Raise_Hold_uzmsmq.png" + }, + { + "exercise_id": "Ex22", + "name": "Seated Abs Circles", + "type": "bodyweight", + "primary_muscles": "core", + "secondary_muscles": "hip flexors;obliques", + "body_region": "core", + "movement_pattern": "core rotation", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "core_strength;stability;mobility", + "estimated_duration": "40s", + "overview": "A seated core stability exercise where the legs move in circular patterns to engage abdominal muscles.", + "benefits": "Strengthens the core improves hip control and enhances abdominal endurance", + "contraindications": "lower back pain ,hip flexor strain", + "instructions": "Sit with knees bent, feet lifted, and torso slightly leaned back.\nEngage the core and move knees together in a circular motion.\nKeep core tight throughout and reverse the direction after several reps.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/seated-abs-circles-kgOH7mJGKJ" + }, + { + "exercise_id": "Ex23", + "name": "Inchworm", + "type": "bodyweight", + "primary_muscles": "hamstrings", + "secondary_muscles": "core;shoulders;calves", + "body_region": "lower_body;upper_body;core;mobility_and_flexibility", + "movement_pattern": "walkout mobility", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "mobility;warmup;core_stability", + "estimated_duration": "40s", + "overview": "A dynamic mobility exercise that transitions from standing to plank by walking the hands forward.", + "benefits": "Improves hamstring flexibility activates the core and warms up the shoulders and posterior chain", + "contraindications": "lower back injury ,severe hamstring strain ,wrist pain", + "instructions": "Bend forward from standing and place hands flat on the floor.\nWalk hands forward until body reaches a high plank with core engaged.\nWalk feet toward hands with legs relatively straight then return to standing.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/inchworm-oMKbWGAaDD" + }, + { + "exercise_id": "Ex24", + "name": "Step Up on Chair", + "type": "bodyweight", + "primary_muscles": "quadriceps;glutes", + "secondary_muscles": "hamstrings;calves;core", + "body_region": "lower_body", + "movement_pattern": "step up", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "strength;lower_body;functional", + "estimated_duration": "40s", + "overview": "A functional lower body exercise where the body steps onto an elevated surface using leg strength.", + "benefits": "Builds leg strength improves balance and enhances functional movement ability", + "contraindications": "knee injury ,balance problems ,ankle instability", + "instructions": "Place one foot firmly on the chair and stand upright with core engaged.\nPush through the heel to lift your body until the leg is fully extended.\nStep down with control and alternate legs each rep.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/step-up-on-chair-zcvfCJwhkF" + }, + { + "exercise_id": "Ex25", + "name": "Glute Bridge hold", + "type": "strength", + "primary_muscles": "glutes", + "secondary_muscles": "hamstrings,core", + "body_region": "lower_body;core", + "movement_pattern": "hip hinge", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "glutes,lower body strength,posterior_chain", + "estimated_duration": "30-45 sec", + "overview": "A bodyweight exercise that strengthens the glutes and posterior chain by lifting the hips from a supine position.", + "benefits": "Strengthens glutes and hamstrings, improves hip stability, supports lower back health, activates posterior chain.", + "contraindications": "lower back pain ,hip injury.", + "instructions": "Lie on your back with knees bent and feet flat on the floor.\nPress through heels and lift hips until body forms a straight line.\nSqueeze glutes at the top then lower slowly and repeat with control.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1780605511/Glute_Bridge_Hold_ay4csf.mp4" + }, + { + "exercise_id": "Ex26", + "name": "Plank Shoulder Tap", + "type": "strength", + "primary_muscles": "core", + "secondary_muscles": "shoulders,abs", + "body_region": "core;upper_body", + "movement_pattern": "anti-rotation plank", + "difficulty": "intermediate", + "intensity": "moderate", + "goal_tags": "core stability,shoulder stability,balance", + "estimated_duration": "30-40 sec", + "overview": "A core stability exercise performed in a plank position while alternating shoulder taps to challenge balance and anti-rotation control.", + "benefits": "Improves core stability, strengthens shoulders, enhances balance and coordination, trains anti-rotation control.", + "contraindications": "wrist, shoulder, or severe lower back injury", + "instructions": "Hold a high plank with feet slightly wider apart for stability.\nLift one hand and tap the opposite shoulder while keeping hips still.\nReturn hand to floor and alternate sides in a controlled manner.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1772902865/Plank_Shoulder_Tap_Animation_Generated_ozbkjh.mp4" + }, + { + "exercise_id": "Ex27", + "name": "High Knees", + "type": "bodyweight", + "primary_muscles": "hip flexors;quadriceps", + "secondary_muscles": "core;calves;glutes", + "body_region": "cardio_and_endurance;lower_body", + "movement_pattern": "cardio run", + "difficulty": "beginner", + "intensity": "high", + "goal_tags": "cardio;warmup;fat_burn;conditioning", + "estimated_duration": "30s", + "overview": "A standing cardio exercise where alternating knees are driven upward rapidly to simulate running in place.", + "benefits": "Elevates heart rate quickly improves cardiovascular endurance strengthens hip flexors and enhances coordination", + "contraindications": "knee pain;hip flexor strain;ankle injury", + "instructions": "Stand tall with feet hip-width apart and keep your core tight throughout.\nDrive one knee up toward the chest and pump the opposite arm simultaneously.\nSwitch legs quickly in a running motion without letting your posture drop.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1773089263/High_Knees_Exercise_Video_Generation_trjemc.mp4" + }, + { + "exercise_id": "Ex28", + "name": "Bird Dog", + "type": "bodyweight", + "primary_muscles": "core;lower back", + "secondary_muscles": "glutes;shoulders;hamstrings", + "body_region": "core;lower_body", + "movement_pattern": "anti-rotation stability", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "core_stability;balance;posture;back_pain_relief", + "estimated_duration": "40s", + "overview": "A floor-based stability exercise where opposite arm and leg are extended simultaneously to challenge core control and balance.", + "benefits": "Strengthens core and lower back improves balance and coordination and helps prevent lower back pain", + "contraindications": "severe lower back injury;wrist pain;knee pain", + "instructions": "Start on all fours with hands under shoulders, knees under hips and back flat.\nExtend one arm forward and the opposite leg back while keeping hips level and core tight.\nHold briefly, return to start and repeat on the other side with control.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1773089438/Bird_Dog_Exercise_Video_Generation_pk6kag.mp4" + }, + { + "exercise_id": "Ex29", + "name": "Reverse Crunches", + "type": "bodyweight", + "primary_muscles": "lower abdominals", + "secondary_muscles": "core;hip flexors", + "body_region": "core", + "movement_pattern": "spinal flexion", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "core_strength;abdominal;bodyweight", + "estimated_duration": "40s", + "overview": "A core focused abdominal exercise where the hips lift off the floor to target the lower abdominals.", + "benefits": "Strengthens lower abdominal muscles improves core stability and supports spinal control", + "contraindications": "lower back injury ,abdominal strain", + "instructions": "Lie on your back with knees bent and feet lifted off the floor.\nCurl hips upward lifting knees toward the chest using core strength only.\nLower hips slowly with control and avoid using momentum.", + "is_lottie": true, + "third_party": false, + "equipments": "", + "animation_link": "https://lottiefiles.com/free-animation/reverse-crunches-79P2FMTS7Z" + }, + { + "exercise_id": "Ex30", + "name": "Plank", + "type": "bodyweight", + "primary_muscles": "lower_abs;upper_abs", + "secondary_muscles": "obliques", + "body_region": "core;upper_body", + "movement_pattern": "isometric_hold", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "core;stability", + "estimated_duration": "30s", + "overview": "A static core exercise that builds strength by holding a straight body position.", + "benefits": "Strengthens core improves stability and enhances posture", + "contraindications": "lower_back_pain,shoulder_pain", + "instructions": "Place forearms under shoulders with legs extended behind.\nKeep body straight and core engaged without dropping hips.\nHold position steadily while maintaining control.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/image/upload/v1780722113/plank_pvuczq.png" + }, + { + "exercise_id": "Ex31", + "name": "Mountain Pose (Tadasana)", + "type": "yoga", + "primary_muscles": "quadriceps; core; spinal erectors", + "secondary_muscles": "glutes; calves; shoulders", + "body_region": "lower_body;core;mobility_and_flexibility", + "movement_pattern": "isometric hold; posture alignment", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "posture; balance; stability; mindfulness", + "estimated_duration": "30-60 seconds", + "overview": "Foundational standing yoga pose focusing on alignment, balance, and body awareness with the body upright and stable.", + "benefits": "Improves posture, enhances balance, strengthens the legs and core, increases body awareness, and promotes relaxation.", + "contraindications": "low blood pressure; balance issues ", + "instructions": "Stand upright with your feet together or hip-width apart and distribute your weight evenly across both feet.\nEngage your thighs and core, lengthen your spine, and roll your shoulders back and down while keeping your arms relaxed at your sides.\nHold the position steady, maintain proper alignment, and breathe slowly and deeply.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1774035563/tadasana_or_mountain_pose_ffq03a.mp4" + }, + { + "exercise_id": "Ex32", + "name": "Tree Pose (Vrikshasana)", + "type": "yoga", + "primary_muscles": "quadriceps; glutes; core", + "secondary_muscles": "calves; ankles; hip flexors", + "body_region": "lower_body;core;mobility_and_flexibility", + "movement_pattern": "balance; isometric hold", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "balance; stability; focus; posture", + "estimated_duration": "30-60 seconds", + "overview": "A standing balance pose where one foot is placed on the opposite leg, improving stability and concentration.", + "benefits": "Improves balance, strengthens legs and core, enhances focus, promotes stability, and supports posture", + "contraindications": "ankle injury; knee injury; balance disorders (use wall support if needed)", + "instructions": "Stand tall and shift your weight onto one leg while keeping your balance steady.\nPlace the sole of your opposite foot on your inner calf or thigh and bring your hands together.\nHold steady, fix your gaze on one point, and breathe slowly.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1774035563/Tree_Pose_or_Vrikshasana_yoga_d3fjlu.mp4" + }, + { + "exercise_id": "Ex33", + "name": "Child’s Pose (Balasana)", + "type": "yoga", + "primary_muscles": "hip flexors; lower back; glutes", + "secondary_muscles": "thighs; ankles; shoulders", + "body_region": "lower_body;mobility_and_flexibility", + "movement_pattern": "flexion; isometric hold", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "recovery; flexibility; relaxation; mobility", + "estimated_duration": "30-90 seconds", + "overview": "A resting yoga pose where the body folds forward, promoting relaxation and gentle stretching of the back and hips.", + "benefits": "Relieves lower back tension, stretches hips and thighs, promotes relaxation, reduces stress, and improves flexibility", + "contraindications": "knee injury; severe back injury; pregnancy (modify with support)", + "instructions": "Kneel on the floor and sit back on your heels while keeping your knees together or slightly apart.\nBend forward and rest your torso between your thighs with your arms extended or relaxed.\nRest your forehead down, relax your body, and breathe deeply.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1774035563/Child_s_Pose_Balasana_yoga_go1gmv.mp4" + }, + { + "exercise_id": "Ex34", + "name": "Cat-Cow Pose (Marjaryasana–Bitilasana)", + "type": "yoga", + "primary_muscles": "spinal erectors; core; shoulders", + "secondary_muscles": "neck; hips", + "body_region": "core;mobility_and_flexibility", + "movement_pattern": "spinal flexion; spinal extension; dynamic flow", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "mobility; flexibility; posture; warm-up", + "estimated_duration": "30-60 seconds", + "overview": "A gentle flowing sequence between two poses that warms up the spine and improves mobility through coordinated breathing.", + "benefits": "Improves spinal flexibility, relieves back tension, enhances posture, promotes mobility, and supports relaxation", + "contraindications": "wrist pain; neck injury; severe spine issues (modify range of motion)", + "instructions": "Start on your hands and knees with your wrists under shoulders and knees under hips.\nInhale as you drop your belly, lift your chest, and look slightly upward.\nExhale as you round your spine, tuck your chin, and draw your belly inward.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1774035563/Cat_Cow_Yoga_Animation_Video_y8a09j.mp4" + }, + { + "exercise_id": "Ex35", + "name": "Seated Forward Bend (Paschimottanasana)", + "type": "yoga", + "primary_muscles": "hamstrings; lower back; calves", + "secondary_muscles": "spine; shoulders; hips", + "body_region": "lower_body;mobility_and_flexibility", + "movement_pattern": "hip hinge; flexion; isometric hold", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "flexibility; relaxation; mobility; posture", + "estimated_duration": "30-90 seconds", + "overview": "A seated forward fold that stretches the entire posterior chain, especially the hamstrings and lower back, while calming the mind.", + "benefits": "Stretches hamstrings and spine, relieves lower back tension, improves flexibility, promotes relaxation, and supports digestion", + "contraindications": "lower back injury; herniated disc; hamstring injury; pregnancy (modify depth)", + "instructions": "Sit upright with your legs extended straight in front of you and keep your spine tall.\nInhale to lengthen your spine, then exhale as you hinge forward from your hips.\nHold the stretch comfortably, keep your back long, and breathe slowly.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1774035563/Seated_Forward_Bend_Paschimottanasana_yoga_eiishl.mp4" + }, + { + "exercise_id": "Ex36", + "name": "Bridge Pose (Setu Bandhasana)", + "type": "yoga", + "primary_muscles": "glutes;hamstrings", + "secondary_muscles": "quadriceps;core;hip_flexors;erector_spinae", + "body_region": "lower_body;core;mobility_and_flexibility", + "movement_pattern": "spinal_extension ; hip_hinge", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "mobility;flexibility;stress_relief;posture;warmup", + "estimated_duration": "60s", + "overview": "A beginner-friendly backbend yoga pose where the hips are lifted off the floor forming a bridge shape, strengthening the posterior chain while opening the chest and stretching the hip flexors and spine.", + "benefits": "Strengthens glutes and hamstrings; opens chest and hip flexors; relieves lower back pain; improves posture; reduces stress and anxiety; stimulates digestion", + "contraindications": "neck_injury;lower_back_injury;shoulder_injury;ankle_injury;spinal_surgery;osteoporosis;pregnancy", + "instructions": "Lie on your back, bend your knees, feet flat on the floor hip-width apart, arms by your sides.\nPress your feet down and slowly lift your hips and back off the ground, rolling shoulders underneath.\nHold for 10–15 seconds breathing steadily, then slowly lower back down and relax.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1774275307/Bridge_Pose_Setu_Bandhasana_yoga_meqkbx.mp4" + }, + { + "exercise_id": "Ex37", + "name": "Warrior I (Virabhadrasana I)", + "type": "yoga", + "primary_muscles": "quadriceps;glutes;hip_flexors", + "secondary_muscles": "hamstrings;core;shoulders;calves;erector_spinae", + "body_region": "lower_body;core;mobility_and_flexibility", + "movement_pattern": "lunge;spinal_extension", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "mobility;flexibility;balance;strength;stress_relief;posture", + "estimated_duration": "60s", + "overview": "A powerful standing yoga pose where one leg lunges forward and arms reach overhead, building strength, stability, and focus while opening the chest and hips simultaneously.", + "benefits": "Strengthens legs and core; opens hips and chest; improves balance and posture; boosts circulation; reduces stress; increases hip flexibility and range of motion", + "contraindications": "hip_injury;knee_injury;back_injury;shoulder_injury;high_blood_pressure;balance_disorders;pregnancy", + "instructions": "Stand tall, step one foot back 3–4 feet and angle it outward at 45 degrees, front foot pointing forward.\nBend your front knee to 90 degrees over your ankle, keeping your hips squared to the front.\nRaise both arms overhead with palms facing each other, lift your chest, hold, then switch sides.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1774275305/Warrior_I_Virabhadrasana_I_z5kv5u.mp4" + }, + { + "exercise_id": "Ex38", + "name": "Moon Pose (Ardha Chandrasana)", + "type": "yoga", + "primary_muscles": "quadriceps;glutes;obliques", + "secondary_muscles": "hamstrings;calves;core;shoulders;hip_flexors", + "body_region": "lower_body;core;mobility_and_flexibility", + "movement_pattern": "balance ; lateral_extension", + "difficulty": "intermediate", + "intensity": "moderate", + "goal_tags": "balance;flexibility;strength;stress_relief;posture;mobility", + "estimated_duration": "60s", + "overview": "A graceful single-legged standing yoga pose where the body forms a half-moon shape, balancing on one leg while extending the other parallel to the floor and reaching both arms in opposite directions to build balance, strength, and flexibility.", + "benefits": "Strengthens legs, core, and glutes; improves balance and stability; opens chest and hips; relieves back pain; reduces stress and anxiety; improves digestion and circulation", + "contraindications": "hip_injury;knee_injury;back_injury;shoulder_injury;low_blood_pressure;migraine;insomnia;varicose_veins;pregnancy", + "instructions": "Stand tall, shift your weight onto one foot, and place your fingertips on the floor 8–12 inches in front of you.\nLift your opposite leg straight back parallel to the floor and extend your top arm upward, stacking both arms in a line.\nHold steady, breathe evenly for 15–30 seconds, then lower your leg and switch sides.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1774275305/Moon_Pose_Ardha_Chandrasana_z3ijot.mp4" + }, + { + "exercise_id": "Ex39", + "name": "Plank Pose (Phalakasana)", + "type": "yoga", + "primary_muscles": "core;shoulders;wrists", + "secondary_muscles": "glutes;hamstrings;quadriceps;calves;erector_spinae", + "body_region": "core;upper_body", + "movement_pattern": "static_hold ; spinal_stabilization", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "strength;core;posture;balance;toning", + "estimated_duration": "60s", + "overview": "A foundational yoga pose where the body is held straight and firm like a plank, supported on hands and toes, deeply engaging the core, arms and shoulders while building full body strength and stability.", + "benefits": "Strengthens core, arms, shoulders and wrists; improves posture and balance; tones abdominal muscles; relieves sciatica; builds full body endurance and stability", + "contraindications": "wrist_injury;carpal_tunnel;shoulder_injury;lower_back_injury;ankle_injury;post_abdominal_surgery;post_spinal_surgery", + "instructions": "Start on all fours, hands shoulder-width apart, fingers spread wide, wrists directly under your shoulders.\nEngage your core, tuck your toes, and step both legs back until your body forms a straight line from head to heels.\nHold steady, keep hips level and do not let them sag or rise ,then gently lower your knees to exit.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1774275304/Plank_Pose_Phalakasana_hhkrja.mp4" + }, + { + "exercise_id": "Ex40", + "name": "Standing Forward Fold (Uttanasana)", + "type": "yoga", + "primary_muscles": "hamstrings;erector_spinae;glutes", + "secondary_muscles": "calves;core;hip_flexors;quadriceps", + "body_region": "lower_body;mobility_and_flexibility", + "movement_pattern": "forward_fold ; spinal_flexion", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "flexibility;stress_relief;mobility;posture;warmup", + "estimated_duration": "60s", + "overview": "A fundamental standing yoga pose where you hinge forward at the hips and let the upper body fold down toward the floor, deeply stretching the entire back body while calming the mind and relieving tension.", + "benefits": "Stretches hamstrings, calves and spine; relieves lower back tension; improves posture; stimulates digestion; reduces stress and anxiety; boosts circulation to the brain", + "contraindications": "lower_back_injury;hamstring_tear;knee_injury;hip_injury;spinal_disorders;high_blood_pressure;vertigo;migraine;glaucoma;pregnancy", + "instructions": "Stand tall, feet hip-width apart, and take a deep breath to lengthen your spine.\nExhale, hinge forward from your hips, and reach your hands toward the floor and bend knees if needed.\nLet your head hang freely, hold for a few breaths, then slowly rise back up.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1774275305/Standing_Forward_Fold_Uttanasana_yoga_mxvkcu.mp4" + }, + { + "exercise_id": "Ex41", + "name": "Low Lunge (Anjaneyasana)", + "type": "yoga", + "primary_muscles": "hip_flexors;quadriceps;glutes", + "secondary_muscles": "hamstrings;core;calves;erector_spinae;shoulders", + "body_region": "lower_body;mobility_and_flexibility", + "movement_pattern": "lunge ; spinal_extension", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "flexibility;mobility;posture;stress_relief;warmup;balance", + "estimated_duration": "60s", + "overview": "A foundational kneeling yoga pose where one leg lunges forward and the back knee rests on the mat, deeply stretching the hip flexors and opening the chest while building strength, balance and stability throughout the body.", + "benefits": "Stretches hip flexors and groin; strengthens legs and core; opens chest and shoulders; improves balance and posture; stimulates digestion; reduces stress and back pain", + "contraindications": "knee_injury;hip_injury;lower_back_injury;ankle_injury;high_blood_pressure;vertigo;pregnancy;post_spinal_surgery", + "instructions": "Step one foot forward between your hands, knee over ankle, and gently lower your back knee to the mat.\nInhale, lift your torso, and raise both arms overhead keeping shoulders relaxed and core engaged.\nHold for a few breaths, gently sink your hips forward, then switch sides and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1774275304/Low_Lunge_Anjaneyasana_yoga_sxlpbt.mp4" + }, + { + "exercise_id": "Ex42", + "name": "Chair Pose (Utkatasana)", + "type": "yoga", + "primary_muscles": "quadriceps;glutes", + "secondary_muscles": "hamstrings;calves;core;erector_spinae;shoulders", + "body_region": "lower_body;core;mobility_and_flexibility", + "movement_pattern": "squat ; spinal_extension", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "strength;balance;posture;core;stress_relief;toning", + "estimated_duration": "60s", + "overview": "A powerful standing yoga pose where you sit on an imaginary chair with knees bent and arms raised overhead, deeply engaging the legs and core while opening the chest and shoulders to build strength, focus and stability.", + "benefits": "Strengthens quadriceps, glutes and calves; tones core and lower back; improves posture and balance; stimulates digestion; reduces stress; enhances mental focus and concentration", + "contraindications": "knee_injury;hip_injury;ankle_injury;shoulder_injury;lower_back_injury;high_blood_pressure;vertigo;migraine;insomnia;pregnancy", + "instructions": "Stand tall, feet hip-width apart, and raise both arms overhead with palms facing each other.\nBend your knees and push your hips back and down as if sitting on an imaginary chair, keeping chest lifted.\nHold for a few breaths with knees behind toes, then press through your feet to rise back up.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1774275307/Chair_Pose_Utkatasana_yoga_wkd1hf.mp4" + }, + { + "exercise_id": "Ex43", + "name": "Triangle Pose (Trikonasana)", + "type": "yoga", + "primary_muscles": "hamstrings;obliques;quadriceps", + "secondary_muscles": "glutes;calves;core;shoulders;erector_spinae;hip_flexors", + "body_region": "lower_body;core;mobility_and_flexibility", + "movement_pattern": "lateral_flexion; spinal_extension", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "flexibility;strength;balance;posture;stress_relief;mobility", + "estimated_duration": "60s", + "overview": "A foundational standing yoga pose where the body extends sideways forming a triangle shape, deeply stretching the legs, hips and spine while strengthening the core and improving balance and mental focus.", + "benefits": "Strengthens legs, core and shoulders; stretches hamstrings, hips and spine; improves balance and posture; relieves back pain and sciatica; reduces stress and anxiety; stimulates digestion", + "contraindications": "neck_injury;lower_back_injury;knee_injury;hip_injury;shoulder_injury;high_blood_pressure;low_blood_pressure;vertigo;migraine;diarrhea;pregnancy;post_spinal_surgery", + "instructions": "Stand with feet 3–4 feet apart, right foot turned out 90 degrees, and raise both arms parallel to the floor.\nHinge sideways from your hips, reaching your right hand down to your shin or floor and left arm straight up.\nHold for a few breaths keeping your torso long, then rise back up and switch sides.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1774275306/Triangle_Pose_Trikonasana_yoga_hlx813.mp4" + }, + { + "exercise_id": "Ex44", + "name": "Corpse Pose (Shavasana)", + "type": "yoga", + "primary_muscles": "erector_spinae;intercostals", + "secondary_muscles": "nervous_system;diaphragm;core", + "body_region": "mobility_and_flexibility", + "movement_pattern": "static_hold ; relaxation", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "stress_relief;flexibility;sleep;mindfulness;recovery;posture", + "estimated_duration": "90s", + "overview": "A deeply restorative yoga pose practiced at the end of a session where you lie flat on your back in complete stillness, allowing the body and mind to fully absorb the benefits of the practice and enter a state of deep healing and relaxation.", + "benefits": "Reduces stress and anxiety; lowers blood pressure; relieves muscle tension; improves sleep quality; enhances mental clarity and focus; promotes deep healing and cell repair", + "contraindications": "severe_back_injury;pregnancy(modify_to_side_lying);respiratory_disorders", + "instructions": "Lie flat on your back, legs slightly apart, arms alongside your body with palms facing up.\nClose your eyes, relax every muscle from head to toe, and breathe slowly and deeply through your nose.\nStay still for90 seconds, then gently wiggle fingers and toes, roll to one side, and slowly sit up.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1774275303/Corpse_Pose_Shavasana_yoga_on1rwj.mp4" + }, + { + "exercise_id": "Ex45", + "name": "Alternating V-Up", + "type": "bodyweight", + "primary_muscles": "rectus_abdominis;obliques", + "secondary_muscles": "hip_flexors;quadriceps;adductors;core", + "body_region": "core", + "movement_pattern": "spinal_flexion ; rotation", + "difficulty": "intermediate", + "intensity": "moderate", + "goal_tags": "core;toning;strength;balance;fat_burn", + "estimated_duration": "90s", + "overview": "A dynamic core exercise where you alternate lifting one leg at a time while crunching your torso up to reach the opposite arm toward the raised leg, forming a V-shape and deeply engaging the abs and obliques.", + "benefits": "Strengthens rectus abdominis and obliques; improves balance and coordination; tones entire core; reduces lower back pain; enhances hip flexor strength and athletic performance", + "contraindications": "lower_back_injury;hip_injury;neck_injury;post_abdominal_surgery;spinal_conditions;osteoporosis", + "instructions": "Lie flat on your back, arms extended overhead, legs straight and feet 6 inches off the ground.\nLift one leg up and crunch your torso, reaching your opposite arm to touch that shin.\nLower back down keeping feet off the ground and repeat on the other side.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1774275303/Alternating_V-Up_exercise_r2svgn.mp4" + }, + { + "exercise_id": "Ex46", + "name": "Seated Spinal Twist - Reverse (Ardha Matsyendrasana)", + "type": "yoga", + "primary_muscles": "obliques;erector_spinae", + "secondary_muscles": "core;glutes;hip_flexors;shoulders;hamstrings", + "body_region": "core;mobility_and_flexibility", + "movement_pattern": "spinal_rotation ;seated_twist", + "difficulty": "intermediate", + "intensity": "low", + "goal_tags": "flexibility;mobility;stress_relief;posture;detox", + "estimated_duration": "60s", + "overview": "A classic seated yoga twist where you rotate your torso in the opposite direction of the bent knee, deeply mobilizing the spine, massaging the abdominal organs and releasing tension built up across the back and hips.", + "benefits": "Improves spinal mobility and posture; stretches outer hips and shoulders; relieves back pain; stimulates digestion; reduces stress and anxiety; increases energy and focus", + "contraindications": "spinal_injury;herniated_disc;lower_back_injury;knee_injury;hip_injury;shoulder_injury;hernia;peptic_ulcer;post_abdominal_surgery;post_spinal_surgery;pregnancy;menstruation", + "instructions": "Sit tall, bend your right knee and place your foot outside your left thigh, left heel tucked near your right hip.\nPlace your right hand behind you, inhale to lengthen your spine, then exhale and twist left, hooking your left elbow outside your right knee.\nHold for a few breaths breathing steadily, then gently release and switch sides.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1774276471/Seated_Spinal_Twist_Ardha_Matsyendrasana_kbi8dz.mp4" + }, + { + "exercise_id": "Ex47", + "name": "Butt Kicks", + "type": "bodyweight", + "primary_muscles": "hamstrings;glutes", + "secondary_muscles": "calves;hip_flexors;core;quadriceps", + "body_region": "lower_body;cardio_and_endurance", + "movement_pattern": "cardio + knee_flexion", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "warmup;cardio;fat_burn;mobility;flexibility", + "estimated_duration": "45s", + "overview": "A dynamic cardio exercise where you jog in place while rapidly kicking your heels up toward your glutes, activating the hamstrings, warming up the lower body and improving running mechanics and stride efficiency.", + "benefits": "Strengthens hamstrings and glutes; improves cardiovascular fitness; increases hamstring flexibility; enhances running speed and coordination; burns calories; warms up lower body", + "contraindications": "hamstring_injury;knee_injury;ankle_injury;lower_back_injury;plantar_fasciitis", + "instructions": "Stand tall, feet hip-width apart, core engaged, and arms bent at 90 degrees at your sides.\nJog in place, alternately kicking each heel up toward your glutes, landing softly on the balls of your feet.\nPump arms in sync with the opposite leg, gradually build speed and maintain a steady rhythm.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778442624/Butts_kick_exercise_zyophy.mp4" + }, + { + "exercise_id": "Ex48", + "name": "Bottom Leg Lift (Right)", + "type": "bodyweight", + "primary_muscles": "adductors;inner_thighs", + "secondary_muscles": "glutes;core;obliques;hip_flexors", + "body_region": "lower_body", + "movement_pattern": "hip_adduction", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "toning;strength;mobility;stability", + "estimated_duration": "45s", + "overview": "A side-lying exercise where you lie on your right side and lift the bottom leg upward to target the inner thigh adductor muscles, improving hip stability and lower body strength.", + "benefits": "Strengthens inner thighs and adductors; improves hip stability and pelvic control; tones lower body; enhances balance; prevents knee and hip injuries", + "contraindications": "hip_injury;groin_injury;knee_injury;lower_back_injury;post_hip_surgery", + "instructions": "Lie on your right side with legs stacked, bend your top knee and place your top foot flat on the floor in front of you for support.\nExhale and slowly lift your bottom leg 6–12 inches off the floor, squeezing your inner thigh muscles.\nHold briefly at the top, then slowly lower back down and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778442621/Bottom_Leg_Lift_Right_cp7t9d.mp4" + }, + { + "exercise_id": "Ex49", + "name": "Bottom Leg Lift (Left)", + "type": "bodyweight", + "primary_muscles": "adductors;inner_thighs", + "secondary_muscles": "glutes;core;obliques;hip_flexors", + "body_region": "lower_body", + "movement_pattern": "hip_adduction", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "toning;strength;mobility;stability", + "estimated_duration": "45s", + "overview": "A side-lying exercise where you lie on your left side and lift the bottom leg upward to target the inner thigh adductor muscles, improving hip stability and lower body strength.", + "benefits": "Strengthens inner thighs and adductors; improves hip stability and pelvic control; tones lower body; enhances balance; prevents knee and hip injuries", + "contraindications": "hip_injury;groin_injury;knee_injury;lower_back_injury;post_hip_surgery", + "instructions": "Lie on your left side with legs stacked, bend your top knee and place your top foot flat on the floor in front of you for support.\nExhale and slowly lift your bottom leg 6–12 inches off the floor, squeezing your inner thigh muscles.\nHold briefly at the top, then slowly lower back down and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778442622/Bottom_Leg_Lift_left_uyh8gv.mp4" + }, + { + "exercise_id": "Ex50", + "name": "Butterfly Pose (Baddha Konasana)", + "type": "yoga", + "primary_muscles": "adductors;hip_flexors", + "secondary_muscles": "glutes;hamstrings;core;erector_spinae;pelvic_floor", + "body_region": "lower_body;mobility_and_flexibility", + "movement_pattern": "hip_external_rotation + seated_stretch", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "flexibility;mobility;stress_relief;posture;warmup;recovery", + "estimated_duration": "60s", + "overview": "A beginner-friendly seated yoga pose where the soles of the feet are brought together and knees fall out to the sides like butterfly wings, deeply opening the hips and inner thighs while calming the nervous system.", + "benefits": "Stretches inner thighs and groin; opens hips and improves flexibility; strengthens pelvic floor; improves posture and spinal alignment; reduces stress and anxiety; stimulates digestion and pelvic circulation", + "contraindications": "groin_injury;knee_injury;hip_injury;ankle_injury;SI_joint_instability;lower_back_pain;post_hip_surgery;post_knee_surgery", + "instructions": "Sit tall, bend both knees and bring the soles of your feet together, letting your knees fall out to the sides.\nHold your feet or ankles, draw your heels toward your pelvis, and lengthen your spine tall.\nBreathe deeply and with each exhale allow your knees to gently drop lower — hold for 30–60 seconds.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778442622/Butterfly_Pose_Baddha_Konasana_qoxl2p.mp4" + }, + { + "exercise_id": "Ex51", + "name": "Happy Baby Pose (Ananda Balasana)", + "type": "yoga", + "primary_muscles": "hip_flexors;adductors;hamstrings", + "secondary_muscles": "glutes;core;erector_spinae;groin;shoulders", + "body_region": "lower_body;core;mobility_and_flexibility", + "movement_pattern": "hip_external_rotation + spinal_decompression", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "flexibility;stress_relief;mobility;recovery;posture", + "estimated_duration": "60s", + "overview": "A gentle supine yoga pose where you lie on your back, hold the outer edges of your feet and pull your knees toward your armpits like a happy baby, deeply releasing the hips, inner thighs and lower back while calming the mind.", + "benefits": "Opens hips and inner thighs; relieves lower back tension; stretches hamstrings and groin; improves spinal mobility; reduces stress and anxiety; stimulates digestion; promotes deep relaxation", + "contraindications": "hip_injury;knee_injury;groin_injury;neck_injury;ankle_injury;osteoporosis;post_abdominal_surgery;post_hip_surgery;pregnancy;menstruation", + "instructions": "Lie on your back, bend both knees toward your chest and open them slightly wider than your torso.\nReach inside your knees and hold the outer edges of your feet, soles facing up toward the ceiling.\nPress your feet into your hands and pull down gently, keeping your lower back flat on the floor.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778442629/happybabypose_ymo4uw.mp4" + }, + { + "exercise_id": "Ex52", + "name": "Garland Pose (Malasana)", + "type": "yoga", + "primary_muscles": "hip_flexors;adductors;glutes", + "secondary_muscles": "quadriceps;hamstrings;calves;core;erector_spinae;ankles", + "body_region": "lower_body;core;mobility_and_flexibility", + "movement_pattern": "deep_squat + hip_external_rotation", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "flexibility;mobility;posture;stress_relief;digestion;warmup", + "estimated_duration": "60s", + "overview": "A deep squatting yoga pose where the feet are flat on the floor and knees are wide apart, forming a deep hip opener that stretches the groin and inner thighs while strengthening the lower body and stimulating the digestive organs.", + "benefits": "Opens hips and groin; improves ankle flexibility; strengthens legs and core; supports pelvic health; stimulates digestion; relieves lower back tension; improves posture and spinal alignment", + "contraindications": "knee_injury;ankle_injury;hip_injury;lower_back_injury;groin_injury;post_hip_surgery;post_knee_surgery;post_abdominal_surgery", + "instructions": "Stand with feet wider than hips and toes turned out, then bend your knees and lower into a deep squat with heels flat.\nBring palms together at your chest and press your elbows against the inner knees to open the hips wider.\nKeep your spine tall and chest lifted, breathe steadily and hold for 30–60 seconds.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778442621/Garland_Pose_Yoga_Animation_Loop_eklrow.mp4" + }, + { + "exercise_id": "Ex53", + "name": "Diamond Push Up", + "type": "bodyweight", + "primary_muscles": "triceps", + "secondary_muscles": "pectoralis_major;deltoids;serratus_anterior;core;glutes", + "body_region": "upper_body;core", + "movement_pattern": "push + elbow_extension", + "difficulty": "intermediate", + "intensity": "moderate", + "goal_tags": "strength;toning;fat_burn;upper_body", + "estimated_duration": "45s", + "overview": "An advanced push-up variation where hands are placed close together forming a diamond shape under the chest, shifting the workload heavily onto the triceps while also engaging the chest, shoulders and core for a powerful upper body workout.", + "benefits": "Builds tricep strength and definition; increases chest and shoulder activation; improves core stability; enhances pushing power; requires no equipment; boosts upper body endurance", + "contraindications": "wrist_injury;elbow_injury;shoulder_injury;carpal_tunnel;lower_back_injury;rotator_cuff_injury", + "instructions": "Get into a plank, bring hands together under your chest with thumbs and index fingers forming a diamond shape.\nKeeping elbows tucked at 45 degrees, slowly lower your chest toward your hands until it almost touches the floor.\nPush through your palms to fully extend your arms back to the start and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778447207/diamond_push_ups_y5rb6a.mp4" + }, + { + "exercise_id": "Ex54", + "name": "Donkey Kicks (Right)", + "type": "bodyweight", + "primary_muscles": "gluteus_maximus;gluteus_medius;gluteus_minimus", + "secondary_muscles": "hamstrings;core;erector_spinae;hip_flexors", + "body_region": "lower_body;core", + "movement_pattern": "hip_extension", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "toning;strength;glutes;stability;fat_burn", + "estimated_duration": "45s", + "overview": "A quadruped exercise where you kneel on all fours and kick your right leg up toward the ceiling with knee bent, isolating the glute muscles to build strength, improve hip stability and tone the entire posterior chain.", + "benefits": "Strengthens and tones glutes; improves hip stability and pelvic control; activates core; corrects muscle imbalance; improves posture; low impact and joint-friendly", + "contraindications": "knee_injury;lower_back_injury;wrist_injury;hip_injury;shoulder_injury;broken_limb_bones", + "instructions": "Get on all fours with hands under shoulders and knees under hips, back flat and neck neutral.\nSqueeze your glutes and kick one leg up with knee bent at 90 degrees until your thigh is parallel to the floor.\nSlowly lower your knee back down without touching the floor and repeat all reps before switching sides.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778844846/0515Straight_Leg_Donkey_Kick_left_dwdqyk.mp4" + }, + { + "exercise_id": "Ex55", + "name": "Donkey Kicks (Left)", + "type": "bodyweight", + "primary_muscles": "gluteus_maximus;gluteus_medius;gluteus_minimus", + "secondary_muscles": "hamstrings;core;erector_spinae;hip_flexors", + "body_region": "lower_body;core", + "movement_pattern": "hip_extension", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "toning;strength;glutes;stability;fat_burn", + "estimated_duration": "45s", + "overview": "A quadruped exercise where you kneel on all fours and kick your left leg up toward the ceiling with knee bent, isolating the glute muscles to build strength, improve hip stability and tone the entire posterior chain.", + "benefits": "Strengthens and tones glutes; improves hip stability and pelvic control; activates core; corrects muscle imbalance; improves posture; low impact and joint-friendly", + "contraindications": "knee_injury;lower_back_injury;wrist_injury;hip_injury;shoulder_injury;broken_limb_bones", + "instructions": "Get on all fours with hands under shoulders and knees under hips, back flat and neck neutral.\nSqueeze your glutes and kick one leg up with knee bent at 90 degrees until your thigh is parallel to the floor.\nSlowly lower your knee back down without touching the floor and repeat all reps before switching sides.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778844849/Straight_Leg_Donkey_Kick_right_s1iycy.mp4" + }, + { + "exercise_id": "Ex56", + "name": "Diagonal Plank", + "type": "bodyweight", + "primary_muscles": "core;transverse_abdominis;erector_spinae", + "secondary_muscles": "glutes;shoulders;hamstrings;obliques;deltoids", + "body_region": "core;upper_body", + "movement_pattern": "static_hold + diagonal_extension", + "difficulty": "intermediate", + "intensity": "moderate", + "goal_tags": "core;strength;balance;stability;posture", + "estimated_duration": "45s", + "overview": "An advanced plank variation where you hold a plank position and simultaneously extend the opposite arm and leg diagonally, challenging the core, improving balance and coordination, and building full body stability.", + "benefits": "Strengthens core and spinal stabilizers; improves balance and coordination; activates glutes and shoulders; enhances posture; builds functional full body strength; reduces lower back pain", + "contraindications": "wrist_injury;shoulder_injury;lower_back_injury;hip_injury;carpal_tunnel;post_spinal_surgery", + "instructions": "Get into a high plank with hands under shoulders and body in a straight line from head to heels.\nEngage your core and slowly raise one arm straight forward and the opposite leg straight back, both parallel to the floor.\nHold for 2 seconds, lower back with control, then repeat on the opposite side.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778447200/diagonal_plank_left_right_q28loy.mp4" + }, + { + "exercise_id": "Ex57", + "name": "Dead Bug", + "type": "bodyweight", + "primary_muscles": "transverse_abdominis;rectus_abdominis;obliques", + "secondary_muscles": "erector_spinae;hip_flexors;deltoids;quadriceps;latissimus_dorsi", + "body_region": "core", + "movement_pattern": "anti_extension + contralateral_movement", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "core;stability;posture;rehabilitation;strength;balance", + "estimated_duration": "45s", + "overview": "A supine core stability exercise where you lie on your back and slowly extend opposite arm and leg toward the floor while keeping the lower back pressed flat, deeply engaging the deep core muscles and improving coordination and spinal control.", + "benefits": "Strengthens all four abdominal muscle groups; improves spinal stability and posture; enhances balance and coordination; reduces lower back pain; safe for all fitness levels and older adults; ideal for rehabilitation", + "contraindications": "herniated_disc;spinal_stenosis;severe_lower_back_pain;post_abdominal_surgery;post_spinal_surgery;neuromuscular_disorders", + "instructions": "Lie on your back, raise arms straight up to the ceiling and lift legs with knees bent at 90 degrees, lower back pressed firmly into the floor.\nSlowly lower your right arm behind your head and extend your left leg toward the floor at the same time, keeping your core tight.\nReturn to the start position and repeat on the opposite side.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778447196/dead_bug_judpdx.mp4" + }, + { + "exercise_id": "Ex58", + "name": "Seated Dumbbell Shoulder Press (Both Hands)", + "type": "dumbbell", + "primary_muscles": "deltoids;anterior_deltoid;lateral_deltoid", + "secondary_muscles": "triceps_brachii;upper_trapezius;pectoralis_major;core;posterior_deltoid", + "body_region": "upper_body", + "movement_pattern": "overhead_press + elbow_extension", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "strength;toning;upper_body;muscle_building;posture", + "estimated_duration": "45s", + "overview": "A foundational seated upper body exercise where both dumbbells are pressed overhead simultaneously from shoulder height, isolating the deltoids and building shoulder strength, stability and size with reduced momentum compared to the standing variation.", + "benefits": "Builds shoulder strength and size; improves shoulder stability; corrects muscle imbalances; enhances upper body pressing power; tones arms and shoulders; supports posture and spine alignment", + "contraindications": "shoulder_injury;rotator_cuff_injury;elbow_injury;wrist_injury;cervical_spine_injury;shoulder_impingement;high_blood_pressure;post_shoulder_surgery", + "instructions": "Sit upright on a bench, hold a dumbbell in each hand at shoulder height with palms facing forward and elbows at 90 degrees.\nExhale and press both dumbbells straight up until arms are fully extended — do not lock your elbows.\nInhale and slowly lower the dumbbells back to shoulder height with control and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "dumbbells;bench", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778498195/Seated_Dumbbell_Shoulder_Press_xgmyao.mp4" + }, + { + "exercise_id": "Ex59", + "name": "Side Lateral Raise", + "type": "dumbbell", + "primary_muscles": "lateral_deltoid", + "secondary_muscles": "anterior_deltoid;posterior_deltoid;trapezius;supraspinatus;core", + "body_region": "upper_body", + "movement_pattern": "shoulder_abduction", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "strength;toning;upper_body;posture;muscle_building", + "estimated_duration": "45s", + "overview": "An isolation shoulder exercise where dumbbells are raised out to the sides until arms are parallel to the floor, targeting the lateral deltoids to build shoulder width, definition and that classic V-shaped upper body.", + "benefits": "Builds shoulder width and definition; improves shoulder stability; enhances athletic performance; corrects shoulder imbalances; improves posture; easy to perform with minimal equipment", + "contraindications": "shoulder_injury;rotator_cuff_injury;shoulder_impingement;wrist_injury;elbow_injury;neck_injury", + "instructions": "Stand tall, hold a dumbbell in each hand at your sides with palms facing in and a slight bend in your elbows.\nRaise both arms out to the sides until parallel to the floor, leading with your elbows not your hands.\nSlowly lower the dumbbells back to your sides with control and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "dumbbells", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778498195/dumbel_side_lateral_raise_hwuge2.mp4" + }, + { + "exercise_id": "Ex60", + "name": "Dumbbell Concentration Curl (Left)", + "type": "dumbbell", + "primary_muscles": "biceps_brachii", + "secondary_muscles": "brachialis;brachioradialis;forearm_flexors", + "body_region": "upper_body", + "movement_pattern": "elbow_flexion", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "strength;toning;muscle_building;upper_body", + "estimated_duration": "45s", + "overview": "A seated single-arm isolation exercise where the left elbow is braced against the inner thigh and the dumbbell is curled upward in a focused arc, maximizing bicep tension and eliminating momentum for superior muscle contraction and peak development.", + "benefits": "Isolates and strengthens left bicep; improves bicep peak and definition; corrects left-right muscle imbalance; enhances mind-muscle connection; reduces momentum cheating; accessible for all fitness levels", + "contraindications": "elbow_injury;wrist_injury;bicep_tendon_injury;elbow_tendinitis;post_elbow_surgery", + "instructions": "Sit on a bench, place your elbow against the inside of your thigh just above the knee, arm hanging straight down with palm facing forward.\nCurl the dumbbell upward in a smooth arc toward your shoulder, keeping your upper arm pressed firmly against your thigh.\nSqueeze your bicep hard at the top, then slowly lower back to the start and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "dumbbell;bench", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778498590/dumbbell_concentration_curl_left_di6w10.mp4" + }, + { + "exercise_id": "Ex61", + "name": "Dumbbell Concentration Curl (Right)", + "type": "dumbbell", + "primary_muscles": "biceps_brachii", + "secondary_muscles": "brachialis;brachioradialis;forearm_flexors", + "body_region": "upper_body", + "movement_pattern": "elbow_flexion", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "strength;toning;muscle_building;upper_body", + "estimated_duration": "45s", + "overview": "A seated single-arm isolation exercise where the right elbow is braced against the inner thigh and the dumbbell is curled upward in a focused arc, maximizing bicep tension and eliminating momentum for superior muscle contraction and peak development.", + "benefits": "Isolates and strengthens right bicep; improves bicep peak and definition; corrects left-right muscle imbalance; enhances mind-muscle connection; reduces momentum cheating; accessible for all fitness levels", + "contraindications": "elbow_injury;wrist_injury;bicep_tendon_injury;elbow_tendinitis;post_elbow_surgery", + "instructions": "Sit on a bench, place your elbow against the inside of your thigh just above the knee, arm hanging straight down with palm facing forward.\nCurl the dumbbell upward in a smooth arc toward your shoulder, keeping your upper arm pressed firmly against your thigh.\nSqueeze your bicep hard at the top, then slowly lower back to the start and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "dumbbell;bench", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778498590/dumbbell_concentration_curl_right_fa4q7x.mp4" + }, + { + "exercise_id": "Ex62", + "name": "Curtsy Lunges", + "type": "bodyweight", + "primary_muscles": "glutes;quadriceps;adductors", + "secondary_muscles": "hamstrings;calves;core;hip_abductors", + "body_region": "lower_body", + "movement_pattern": "diagonal_lunge + hip_adduction", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "strength;toning;balance;stability;fat_burn", + "estimated_duration": "45s", + "overview": "A lunge variation where one foot steps diagonally behind and across the other in a curtsy-like motion, uniquely targeting all three glute muscles — including the often-neglected gluteus medius — along with the inner thighs and quads for improved lower body strength and hip stability.", + "benefits": "Strengthens glutes, quads and adductors; targets gluteus medius more than standard lunges; improves hip stability and balance; corrects lower body muscle imbalances; enhances coordination; burns calories", + "contraindications": "knee_injury;hip_injury;ankle_injury;lower_back_injury;SI_joint_instability", + "instructions": "Stand tall, feet hip-width apart, core engaged, hands on hips.\nStep one foot diagonally behind and across the other leg, lowering hips until your front thigh is parallel to the floor.\nPress through your front foot to rise back to start, then switch sides and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778685414/crusty_lunges_updates_qqy9mx.mp4" + }, + { + "exercise_id": "Ex63", + "name": "Crunch Kicks", + "type": "bodyweight", + "primary_muscles": "rectus_abdominis;hip_flexors", + "secondary_muscles": "obliques;transverse_abdominis;quadriceps;glutes", + "body_region": "core", + "movement_pattern": "spinal_flexion + hip_extension", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "core;toning;strength;warmup;fat_burn", + "estimated_duration": "45s", + "overview": "A lying core exercise where you perform a crunch while simultaneously extending one leg straight out and then kicking it back in, targeting the lower abs and hip flexors while building core stability and endurance.", + "benefits": "Strengthens lower abs and core; improves hip flexibility; activates glutes and quads; builds core endurance; requires no equipment; suitable as a warm-up or core finisher", + "contraindications": "lower_back_injury;neck_injury;hip_flexor_strain;post_abdominal_surgery", + "instructions": "Lie on your back, knees bent, feet flat on the floor, hands lightly behind your head.\nCrunch up lifting your shoulders off the ground while extending one leg straight out.\nKick the leg back toward your chest, lower shoulders to start, and alternate legs each rep.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778685403/crunch_kicks_updated_vty7bz.mp4" + }, + { + "exercise_id": "Ex64", + "name": "Dumbbell Shrug", + "type": "dumbbell", + "primary_muscles": "trapezius;upper_trapezius", + "secondary_muscles": "rhomboids;levator_scapulae;forearm_flexors;core", + "body_region": "upper_body", + "movement_pattern": "shoulder_elevation", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "strength;muscle_building;posture;upper_body;toning", + "estimated_duration": "45s", + "overview": "An isolation exercise where you stand holding dumbbells at your sides and elevate your shoulders straight up toward your ears, directly targeting the upper trapezius muscles to build neck and upper back strength, thickness and improved posture.", + "benefits": "Builds upper trapezius strength and size; improves posture and spinal alignment; enhances shoulder stability; supports compound lifts like squats and deadlifts; corrects upper back imbalances; accessible and easy to learn", + "contraindications": "neck_injury;shoulder_injury;cervical_spine_injury;rotator_cuff_injury;shoulder_impingement;trapezius_strain", + "instructions": "Stand tall, hold a dumbbell in each hand at your sides with palms facing each other and arms straight.\nShrug both shoulders straight up toward your ears as high as possible and squeeze your traps hard at the top.\nPause for a second, then slowly lower your shoulders back down and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "dumbbells", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/image/upload/v1778685420/dumbell_shrug_blcfp5.png" + }, + { + "exercise_id": "Ex65", + "name": "Dumbbell Kickbacks", + "type": "dumbbell", + "primary_muscles": "triceps_brachii", + "secondary_muscles": "posterior_deltoid;rhomboids;trapezius;core", + "body_region": "upper_body", + "movement_pattern": "elbow_extension + hip_hinge", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "strength;toning;upper_body;muscle_building", + "estimated_duration": "45s", + "overview": "An isolation exercise performed in a bent-over position where the elbow is kept stationary while the forearm is extended backward, targeting all three heads of the triceps for arm definition and pushing strength.", + "benefits": "Isolates and strengthens triceps; improves elbow stability; builds arm definition; low wrist stress; enhances pushing power; easy to program for all fitness levels", + "contraindications": "elbow_injury;shoulder_injury;wrist_injury;lower_back_injury;rotator_cuff_injury;elbow_tendinitis", + "instructions": "Hinge forward, hold dumbbells and raise elbows to shoulder height with upper arms parallel to the floor.\nExtend both forearms straight back until arms are fully locked, squeezing triceps hard at the top.\nSlowly bend elbows back to 90 degrees and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "dumbbells", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778844790/dumbbell_kickbacks_sciulz.mp4" + }, + { + "exercise_id": "Ex66", + "name": "Dumbbell Punches", + "type": "dumbbell", + "primary_muscles": "anterior_deltoid;triceps_brachii", + "secondary_muscles": "lateral_deltoid;core;latissimus_dorsi;pectoralis_major", + "body_region": "upper_body;cardio_and_endurance", + "movement_pattern": "horizontal_push + rotation", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "cardio;strength;toning;fat_burn;upper_body;warmup", + "estimated_duration": "45s", + "overview": "A boxing-inspired cardio exercise where you alternately punch forward with light dumbbells, building shoulder and arm endurance while elevating the heart rate and improving coordination and upper body speed.", + "benefits": "Strengthens shoulders and triceps; improves cardiovascular endurance; boosts coordination and speed; burns calories; builds functional upper body strength; improves punching power", + "contraindications": "shoulder_injury;elbow_injury;wrist_injury;rotator_cuff_injury;neck_injury;shoulder_impingement", + "instructions": "Stand with knees slightly bent, hold light dumbbells at chest height with palms facing each other.\nPunch one arm forward, rotating your wrist palm-down at full extension — do not lock the elbow.\nReturn to start and immediately punch with the other arm, alternating in a steady controlled rhythm.", + "is_lottie": false, + "third_party": false, + "equipments": "dumbbells", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778844779/dumbell_punches_updated_q7c2ve.mp4" + }, + { + "exercise_id": "Ex67", + "name": "Froggy Glute Lifts", + "type": "bodyweight", + "primary_muscles": "gluteus_maximus;gluteus_medius;gluteus_minimus", + "secondary_muscles": "hamstrings;erector_spinae;core", + "body_region": "lower_body;core", + "movement_pattern": "hip_extension + external_rotation", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "glutes;toning;strength;stability;warmup", + "estimated_duration": "45s", + "overview": "A prone floor exercise where you lie face down, bring your feet together in a frog position with knees wide, and lift both knees off the ground by squeezing the glutes, deeply targeting all three glute heads and the underbutt area with no equipment needed.", + "benefits": "Targets all three glute heads; tones and lifts the underbutt; strengthens hamstrings and lower back; improves hip stability; low impact and joint-friendly; no equipment needed", + "contraindications": "lower_back_injury;hip_injury;knee_injury;post_hip_surgery;lumbar_stenosis", + "instructions": "Lie face down, bend both knees and bring feet together with knees spread wide out to the sides in a frog position.\nSqueeze your glutes and lift both knees off the floor simultaneously, pressing heels together.\nHold briefly at the top, then slowly lower your knees back down and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778844801/froggy_glutes_lifts_egcp4y.mp4" + }, + { + "exercise_id": "Ex68", + "name": "Flutter Kicks", + "type": "bodyweight", + "primary_muscles": "rectus_abdominis;hip_flexors", + "secondary_muscles": "transverse_abdominis;obliques;quadriceps;glutes", + "body_region": "core", + "movement_pattern": "anti_extension + alternating_leg_drive", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "core;strength;fat_burn;endurance;toning", + "estimated_duration": "45s", + "overview": "A supine core exercise where both legs are raised slightly off the floor and alternately kicked up and down in a rapid fluttering motion, targeting the lower abs and hip flexors while building core endurance and stability.", + "benefits": "Strengthens lower abs and core; improves hip flexor endurance; enhances core stability and posture; burns calories; low impact; builds coordination and body awareness", + "contraindications": "lower_back_injury;hip_flexor_injury;spinal_stenosis;herniated_disc;post_abdominal_surgery;pregnancy", + "instructions": "Lie on your back, hands tucked under your glutes, lower back pressed firmly into the floor.\nLift both legs 6–12 inches off the ground and engage your core tightly before moving.\nAlternate kicking one leg slightly higher while lowering the other in a quick controlled flutter, keeping your back flat.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1778844810/flutter_kicks_cuifaf.mp4" + }, + { + "exercise_id": "Ex69", + "name": "Wall Calf Raise", + "type": "bodyweight", + "primary_muscles": "gastrocnemius;soleus", + "secondary_muscles": "tibialis_posterior;peroneals;core;hip_flexors", + "body_region": "lower_body", + "movement_pattern": "plantar_flexion + ankle_extension", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "strength;toning;balance;stability;warmup;rehabilitation", + "estimated_duration": "45s", + "overview": "A standing calf raise performed with one or both hands lightly touching a wall for balance, where you rise up onto the balls of your feet to strengthen the calf muscles, improve ankle stability and build lower leg definition safely and effectively.", + "benefits": "Strengthens gastrocnemius and soleus; improves ankle stability and joint health; enhances balance; reduces risk of ankle injury; improves posture; easy to perform anywhere with no equipment", + "contraindications": "achilles_tendon_injury;ankle_injury;plantar_fasciitis;calf_strain;post_ankle_surgery;severe_osteoporosis", + "instructions": "Stand facing a wall, fingertips lightly touching it for balance, feet hip-width apart and back straight.\nPress through the balls of your feet and raise your heels as high as possible, squeezing your calves at the top.\nHold for 1–2 seconds, then slowly lower your heels back down and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1780351811/Wall_Calf_Raise_xogjh2.mp4" + }, + { + "exercise_id": "Ex70", + "name": "Wall Sit", + "type": "bodyweight", + "primary_muscles": "quadriceps", + "secondary_muscles": "glutes;hamstrings;calves;core;erector_spinae", + "body_region": "lower_body", + "movement_pattern": "isometric_hold + knee_flexion", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "strength;endurance;toning;rehabilitation;stability", + "estimated_duration": "60s", + "overview": "A static lower body exercise where you slide your back down a wall until thighs are parallel to the floor and hold the seated position, building quad strength and muscular endurance through isometric contraction with no equipment required.", + "benefits": "Strengthens quadriceps, glutes and hamstrings; builds muscular endurance; improves core stability and posture; rehabilitates knee injuries; burns calories; suitable for all fitness levels", + "contraindications": "severe_knee_injury;knee_replacement;hip_injury;lower_back_injury;ankle_injury;osteoarthritis_of_knee;post_knee_surgery", + "instructions": "Stand with your back flat against a wall, feet shoulder-width apart and about 2 feet away from the wall.\nSlide your back down the wall until your thighs are parallel to the floor and knees are at 90 degrees over your ankles.\nHold the position, breathe steadily and keep your back firmly pressed into the wall for as long as possible.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/image/upload/v1780351811/wall_sit_lgzaj8.png" + }, + { + "exercise_id": "Ex71", + "name": "Dumbbell Front Raise", + "type": "dumbbell", + "primary_muscles": "anterior_deltoid", + "secondary_muscles": "lateral_deltoid;upper_trapezius;serratus_anterior;pectoralis_major;biceps_brachii;core", + "body_region": "upper_body", + "movement_pattern": "shoulder_flexion", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "strength;toning;posture;upper_body;muscle_building", + "estimated_duration": "45s", + "overview": "An isolation shoulder exercise where dumbbells are raised straight in front of the body to shoulder height with palms facing down, targeting the front deltoids to build shoulder definition, improve posture and strengthen pushing movements.", + "benefits": "Builds anterior deltoid size and definition; improves posture and shoulder symmetry; enhances overhead pressing strength; strengthens front shoulder for daily lifting activities; easy to learn for all fitness levels", + "contraindications": "shoulder_injury;rotator_cuff_injury;shoulder_impingement;elbow_injury;wrist_injury;neck_injury;post_shoulder_surgery", + "instructions": "Stand tall, hold dumbbells in front of your thighs with palms facing down and a slight bend in elbows.\nRaise both dumbbells straight in front of you to shoulder height, keeping your torso upright, do not swing.\nPause at the top, then slowly lower back down and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "dumbbells", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1780351863/Dumbbell_Front_Raise_md6jw3.mp4" + }, + { + "exercise_id": "Ex72", + "name": "Standing Side Bend", + "type": "bodyweight", + "primary_muscles": "external_obliques;internal_obliques", + "secondary_muscles": "rectus_abdominis;erector_spinae;quadratus_lumborum;latissimus_dorsi", + "body_region": "core;mobility_and_flexibility", + "movement_pattern": "lateral_flexion", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "flexibility;mobility;core;posture;warmup;stress_relief", + "estimated_duration": "45s", + "overview": "A standing bodyweight exercise where you raise one arm overhead and bend your torso sideways to stretch and strengthen the obliques and lateral core muscles, improving spinal flexibility, waist definition and side-to-side stability.", + "benefits": "Strengthens and tones obliques; improves spinal lateral flexibility; relieves back and hip muscle tension; enhances posture and core stability; easy to perform anywhere with no equipment needed", + "contraindications": "herniated_disc;spinal_injury;lower_back_injury;SI_joint_instability;scoliosis;post_spinal_surgery", + "instructions": "Stand tall, feet shoulder-width apart, and raise one arm straight overhead close to your ear.\nSlowly bend your torso to the opposite side as far as comfortable, feeling the stretch along your raised arm side.\nReturn to upright, complete all reps, then switch sides and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1780351882/Standing_Side_Bend_a8kpgd.mp4" + }, + { + "exercise_id": "Ex73", + "name": "Wall Push-Ups", + "type": "bodyweight", + "primary_muscles": "pectoralis_major;anterior_deltoid;triceps_brachii", + "secondary_muscles": "serratus_anterior;posterior_deltoid;core;rhomboids;erector_spinae", + "body_region": "upper_body", + "movement_pattern": "horizontal_push + elbow_extension", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "strength;toning;upper_body;posture;rehabilitation;warmup", + "estimated_duration": "45s", + "overview": "A beginner-friendly push-up variation performed standing against a wall, reducing body weight resistance to build chest, shoulder and tricep strength safely — ideal for beginners, older adults and those recovering from injuries.", + "benefits": "Builds upper body strength with low joint stress; improves chest and shoulder definition; enhances core stability; easy progression toward floor push-ups; suitable for rehabilitation; no equipment needed", + "contraindications": "shoulder_replacement;post_shoulder_surgery;severe_wrist_injury;acute_elbow_injury", + "instructions": "Stand facing a wall, place hands flat on the wall slightly wider than shoulder-width at chest height.\nKeep body straight, bend your elbows and lean your chest toward the wall until your nose nearly touches it.\nPush through your palms to straighten arms back to the start and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1780351877/wall_pushups_updated_fs7tsu.mp4" + }, + { + "exercise_id": "Ex74", + "name": "Dumbbell Bicep Curl (Overhand Grip)", + "type": "dumbbell", + "primary_muscles": "brachioradialis;brachialis", + "secondary_muscles": "biceps_brachii;forearm_extensors;anterior_deltoid;core", + "body_region": "upper_body", + "movement_pattern": "elbow_flexion + pronated_grip", + "difficulty": "intermediate", + "intensity": "moderate", + "goal_tags": "strength;toning;muscle_building;grip_strength;upper_body", + "estimated_duration": "45s", + "overview": "A bicep curl variation performed with an overhand (pronated) grip where palms face downward, shifting the primary workload from the biceps to the brachioradialis and brachialis muscles to build forearm thickness, grip strength and balanced arm development.", + "benefits": "Builds brachioradialis and forearm strength; improves grip endurance; corrects arm muscle imbalances; enhances wrist and elbow joint stability; develops well-rounded arm definition", + "contraindications": "elbow_injury;wrist_injury;forearm_strain;elbow_tendinitis;carpal_tunnel;post_elbow_surgery", + "instructions": "Stand tall, hold dumbbells with an overhand grip (palms facing down), arms extended at your sides.\nKeeping upper arms still and elbows tucked, curl both dumbbells upward toward your shoulders with wrists straight.\nSqueeze at the top, then slowly lower back down and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "dumbbells", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1780351861/Dumbbell_Bicep_Curl_Overhand_Grip_ybdtl6.mp4" + }, + { + "exercise_id": "Ex75", + "name": "V Hold", + "type": "bodyweight", + "primary_muscles": "rectus_abdominis;transverse_abdominis", + "secondary_muscles": "obliques;hip_flexors;quadriceps;erector_spinae", + "body_region": "core", + "movement_pattern": "isometric_hold + spinal_flexion", + "difficulty": "intermediate", + "intensity": "moderate", + "goal_tags": "core;strength;stability;balance;posture", + "estimated_duration": "45s", + "overview": "An isometric core exercise where you balance on your sitting bones with both legs and torso raised off the floor forming a V shape, deeply engaging the entire abdominal wall and hip flexors while building core strength, stability and balance.", + "benefits": "Strengthens entire core including upper and lower abs; improves balance and body control; builds spinal stability; enhances hip flexor strength; improves posture; no equipment needed", + "contraindications": "lower_back_injury;herniated_disc;hip_flexor_injury;post_abdominal_surgery;osteoporosis;spinal_stenosis", + "instructions": "Sit on the floor, lean back slightly and place hands lightly behind your thighs for support.\nLift both feet off the floor, straighten your legs and raise your torso to form a V shape, balancing on your sitting bones.\nHold steady, breathe evenly, keep your back straight and core tight, then slowly lower back down.", + "is_lottie": false, + "third_party": false, + "equipments": "", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/image/upload/v1780351871/v-hold_exercise_bxbs35.png" + }, + { + "exercise_id": "Ex76", + "name": "Dumbbell Hammer Curl", + "type": "dumbbell", + "primary_muscles": "brachialis;brachioradialis", + "secondary_muscles": "biceps_brachii;forearm_flexors;anterior_deltoid;core", + "body_region": "upper_body", + "movement_pattern": "elbow_flexion + neutral_grip", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "strength;toning;muscle_building;grip_strength;upper_body", + "estimated_duration": "45s", + "overview": "A bicep curl variation performed with a neutral grip where palms face inward, shifting emphasis onto the brachialis and brachioradialis to build arm thickness and grip strength.", + "benefits": "Builds arm thickness and definition; strengthens brachialis and brachioradialis; improves grip strength; corrects arm muscle imbalances; enhances pulling performance", + "contraindications": "elbow_injury;wrist_injury;bicep_tendon_injury;elbow_tendinitis;post_elbow_surgery", + "instructions": "Stand tall, hold dumbbells with palms facing inward, arms fully extended at your sides.\nKeep upper arms still, curl both dumbbells up toward your shoulders without rotating your wrists.\nSqueeze at the top, then slowly lower back down and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "dumbbells", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/image/upload/v1780605514/Dumbbell_Hammer_Curl_ar61pp.png" + }, + { + "exercise_id": "Ex77", + "name": "Superman Hold", + "type": "bodyweight", + "primary_muscles": "erector_spinae;gluteus_maximus", + "secondary_muscles": "hamstrings;posterior_deltoid;trapezius;rhomboids;core", + "body_region": "core;lower_body", + "movement_pattern": "spinal_extension + isometric_hold", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "strength;posture;rehabilitation;core;stability", + "estimated_duration": "45s", + "overview": "A prone isometric exercise where you lie face down and simultaneously lift your arms, chest and legs off the floor to strengthen the entire posterior chain and improve spinal stability.", + "benefits": "Strengthens lower and upper back; improves posture and spinal stability; activates glutes and hamstrings; reduces lower back pain risk; no equipment needed", + "contraindications": "acute_lower_back_pain;herniated_disc;spinal_stenosis;post_spinal_surgery;severe_osteoporosis", + "instructions": "Lie face down, arms extended overhead, legs straight and forehead resting on the floor.\nLift your arms, chest and legs off the floor, reaching fingertips and toes as far apart as possible.\nHold for 3 to 5 seconds breathing steadily, then slowly lower everything back down and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "yoga_mat", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/image/upload/v1780605515/superman_hold_iuhvfr.png" + }, + { + "exercise_id": "Ex78", + "name": "Camel Pose (Ustrasana)", + "type": "yoga", + "primary_muscles": "erector_spinae;gluteus_maximus;hamstrings", + "secondary_muscles": "quadriceps;hip_flexors;rectus_abdominis;anterior_deltoid;trapezius", + "body_region": "core;upper_body;mobility_and_flexibility", + "movement_pattern": "spinal_extension + backbend", + "difficulty": "intermediate", + "intensity": "moderate", + "goal_tags": "flexibility;mobility;posture;stress_relief;chest_opening", + "estimated_duration": "60s", + "overview": "A kneeling backbend yoga pose where you arch the spine backward and reach both hands to your heels, deeply opening the chest and hip flexors while strengthening the back and improving spinal mobility.", + "benefits": "Opens chest and hip flexors; improves spinal flexibility and posture; stimulates digestion; relieves mild back pain; reduces stress; stimulates thyroid gland", + "contraindications": "lower_back_injury;neck_injury;knee_injury;ankle_injury;spinal_injury;high_blood_pressure;low_blood_pressure;hernia;post_abdominal_surgery;pregnancy;migraine;vertigo", + "instructions": "Kneel on your mat, knees hip-width apart, hands on hips and inhale to lengthen your spine tall.\nExhale, arch your back and slide your palms toward your heels, keeping your neck neutral.\nHold for a few breaths, then place hands back on hips and slowly return upright.", + "is_lottie": false, + "third_party": false, + "equipments": "yoga_mat", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/image/upload/v1780605511/camel_Pose_Ustrasana_mkekdz.png" + }, + { + "exercise_id": "Ex79", + "name": "Easy Pose (Sukhasana)", + "type": "yoga", + "primary_muscles": "hip_flexors;adductors", + "secondary_muscles": "erector_spinae;core;piriformis;glutes", + "body_region": "lower_body;mobility_and_flexibility", + "movement_pattern": "seated + hip_external_rotation", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "stress_relief;flexibility;posture;mindfulness;breathing", + "estimated_duration": "60s", + "overview": "A foundational seated cross-legged yoga pose used for meditation and breathing exercises, gently opening the hips and inner thighs while promoting spinal alignment, calmness and mental focus.", + "benefits": "Opens hips and inner thighs; improves posture and spinal alignment; reduces stress and anxiety; promotes mental clarity; strengthens core; accessible for all ages", + "contraindications": "severe_knee_injury;hip_injury;ankle_injury;SI_joint_instability;sciatica;post_hip_surgery;post_knee_surgery", + "instructions": "Sit on your mat, cross your shins and draw each foot beneath the opposite knee, knees falling outward.\nSit tall on your sit bones, lengthen your spine and rest your hands on your knees.\nRelax your shoulders, soften your gaze and breathe deeply and steadily.", + "is_lottie": false, + "third_party": false, + "equipments": "yoga_mat", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1780605508/Easy_Pose_Sukhasana_qmnhi1.mp4" + }, + { + "exercise_id": "Ex80", + "name": "Hero Pose (Virasana)", + "type": "yoga", + "primary_muscles": "quadriceps;hip_flexors", + "secondary_muscles": "glutes;hamstrings;calves;ankles;erector_spinae", + "body_region": "lower_body;mobility_and_flexibility", + "movement_pattern": "kneeling + knee_flexion", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "flexibility;mobility;stress_relief;posture;digestion;mindfulness", + "estimated_duration": "60s", + "overview": "A kneeling yoga pose where you sit between your heels with thighs internally rotated, deeply stretching the quadriceps, ankles and feet while promoting spinal alignment and aiding digestion.", + "benefits": "Stretches quadriceps, ankles and feet; improves digestion and circulation in legs; relieves tired legs; reduces lower back pain; improves posture; relieves menstrual discomfort", + "contraindications": "severe_knee_injury;ankle_injury;hip_injury;arthritis;heart_conditions;varicose_veins;post_knee_surgery;headaches", + "instructions": "Kneel on your mat, shins flat, knees together and feet spread just wider than your hips.\nSlowly lower your buttocks to the floor between your feet — use a block if needed.\nSit tall, lengthen your spine, rest hands on thighs and breathe steadily.", + "is_lottie": false, + "third_party": false, + "equipments": "yoga_mat", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/image/upload/v1780605514/Hero_Pose_Virasana_ej58w4.png" + }, + { + "exercise_id": "Ex81", + "name": "Staff Pose (Dandasana)", + "type": "yoga", + "primary_muscles": "erector_spinae;rectus_abdominis;transverse_abdominis", + "secondary_muscles": "hamstrings;quadriceps;hip_flexors;shoulders;core", + "body_region": "core;mobility_and_flexibility", + "movement_pattern": "seated + spinal_extension", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "posture;strength;flexibility;core;mindfulness", + "estimated_duration": "60s", + "overview": "A foundational seated yoga pose where you sit with legs extended straight and spine held tall like a staff, building core and back strength while improving posture and serving as the base for all seated yoga poses.", + "benefits": "Strengthens core and back muscles; improves posture and spinal alignment; stretches hamstrings; increases body awareness; improves respiratory function; reduces stress", + "contraindications": "severe_lower_back_injury;knee_injury;ankle_injury;wrist_injury;stiff_hamstrings;post_spinal_surgery;high_blood_pressure", + "instructions": "Sit on your mat with both legs extended straight, feet together and toes pointing up.\nPlace hands flat beside your hips, sit firmly on your sit bones and lengthen your spine upward.\nLift your chest, pull shoulders back and down, and breathe steadily for 8 to 10 breaths.", + "is_lottie": false, + "third_party": false, + "equipments": "yoga_mat", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/image/upload/v1780605512/staff_pose_dandasana_x39zcs.png" + }, + { + "exercise_id": "Ex82", + "name": "Bent Over Dumbbell Row", + "type": "dumbbell", + "primary_muscles": "latissimus_dorsi;trapezius;rhomboids", + "secondary_muscles": "biceps_brachii;posterior_deltoid;erector_spinae;core;brachialis", + "body_region": "upper_body", + "movement_pattern": "horizontal_pull + hip_hinge", + "difficulty": "intermediate", + "intensity": "moderate", + "goal_tags": "strength;muscle_building;posture;upper_body;back", + "estimated_duration": "45s", + "overview": "A compound pulling exercise where you hinge forward at the hips and row both dumbbells toward your waist, building a thick strong back by targeting the lats, traps and rhomboids while improving posture and pulling power.", + "benefits": "Builds upper and mid-back thickness; improves posture; corrects muscle imbalances; enhances pulling strength; engages core stabilizers; balances out pressing exercises", + "contraindications": "lower_back_injury;herniated_disc;shoulder_injury;rotator_cuff_injury;wrist_injury;bicep_tendon_injury;post_spinal_surgery", + "instructions": "Hinge forward at your hips, torso nearly parallel to the floor, arms hanging straight down.\nDrive your elbows back and pull both dumbbells toward your waist, squeezing shoulder blades together.\nSlowly lower the dumbbells back down with control and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "dumbbells", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1780605537/Bent_Over_Dumbbell_Row_dkcuzp.mp4" + }, + { + "exercise_id": "Ex83", + "name": "Jumping Rope", + "type": "cardio", + "primary_muscles": "gastrocnemius;soleus", + "secondary_muscles": "quadriceps;hamstrings;glutes;core;deltoids;forearm_flexors", + "body_region": "cardio_and_endurance;lower_body", + "movement_pattern": "plyometric + cardio", + "difficulty": "beginner", + "intensity": "high", + "goal_tags": "cardio;fat_burn;endurance;coordination;warmup", + "estimated_duration": "60s", + "overview": "A classic full-body cardio exercise where a rope is swung overhead and jumped over repeatedly, strengthening the calves while elevating heart rate, improving coordination and burning a high number of calories in a short time.", + "benefits": "Improves cardiovascular fitness; strengthens calves and legs; burns calories rapidly; enhances coordination and balance; improves bone density; boosts agility and footwork", + "contraindications": "ankle_injury;knee_injury;plantar_fasciitis;achilles_tendon_injury;heart_conditions;severe_osteoporosis;joint_pain", + "instructions": "Stand tall, hold one handle in each hand and swing the rope overhead in a smooth arc.\nJump lightly off the balls of your feet — just 1 to 2 inches off the ground — elbows close to your sides.\nLand softly with knees slightly bent and maintain a steady rhythm throughout.", + "is_lottie": false, + "third_party": false, + "equipments": "jump_rope", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1780605532/Jumping_Rope_exercise_uhbatp.mp4" + }, + { + "exercise_id": "Ex84", + "name": "Leg In and Out", + "type": "bodyweight", + "primary_muscles": "rectus_abdominis;transverse_abdominis", + "secondary_muscles": "obliques;hip_flexors;quadriceps", + "body_region": "core", + "movement_pattern": "spinal_flexion + hip_flexion", + "difficulty": "beginner", + "intensity": "moderate", + "goal_tags": "core;strength;toning;endurance;fat_burn", + "estimated_duration": "45s", + "overview": "A seated core exercise where you balance on your sit bones and alternate between pulling your knees toward your chest and extending your legs out, deeply engaging the abs and hip flexors while building core endurance.", + "benefits": "Strengthens upper and lower abs; improves core stability and endurance; enhances hip flexor strength; improves balance and body control; no equipment needed", + "contraindications": "lower_back_injury;herniated_disc;hip_flexor_injury;post_abdominal_surgery;wrist_injury", + "instructions": "Sit on your mat, hands behind your hips, lean back slightly and lift your feet off the floor.\nPull your knees toward your chest — this is the in position.\nExtend your legs straight out while leaning slightly back — this is the out position — repeat without touching the floor.", + "is_lottie": false, + "third_party": false, + "equipments": "yoga_mat", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1780605526/leg_in_and_out_c6g9ol.mp4" + }, + { + "exercise_id": "Ex85", + "name": "Side Plank (Left)", + "type": "bodyweight", + "primary_muscles": "external_obliques;internal_obliques;transverse_abdominis", + "secondary_muscles": "gluteus_medius;quadratus_lumborum;lateral_deltoid;serratus_anterior;core", + "body_region": "core", + "movement_pattern": "isometric_hold + lateral_stabilization", + "difficulty": "intermediate", + "intensity": "moderate", + "goal_tags": "core;strength;stability;posture;balance", + "estimated_duration": "45s", + "overview": "An isometric core exercise performed on the left side where you support your body on your left forearm and feet, forming a straight diagonal line to deeply engage the obliques and lateral core stabilizers.", + "benefits": "Strengthens obliques and lateral core; improves spinal stability and posture; reduces lower back pain; enhances balance and coordination; builds hip abductor strength", + "contraindications": "wrist_injury;shoulder_injury;hip_injury;severe_lower_back_injury;post_shoulder_surgery;rotator_cuff_injury", + "instructions": "Lie on your left side, left forearm flat on the mat with elbow directly under your shoulder.\nEngage your core and lift your hips until your body forms a straight line from head to heels.\nHold steady, breathe evenly and keep hips lifted — do not let them sag.", + "is_lottie": false, + "third_party": false, + "equipments": "yoga_mat", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1780605518/side_plank_left_bzzxvq.mp4" + }, + { + "exercise_id": "Ex86", + "name": "Side Plank (Right)", + "type": "bodyweight", + "primary_muscles": "external_obliques;internal_obliques;transverse_abdominis", + "secondary_muscles": "gluteus_medius;quadratus_lumborum;lateral_deltoid;serratus_anterior;core", + "body_region": "core", + "movement_pattern": "isometric_hold + lateral_stabilization", + "difficulty": "intermediate", + "intensity": "moderate", + "goal_tags": "core;strength;stability;posture;balance", + "estimated_duration": "45s", + "overview": "An isometric core exercise performed on the right side where you support your body on your right forearm and feet, forming a straight diagonal line to deeply engage the obliques and lateral core stabilizers.", + "benefits": "Strengthens obliques and lateral core; improves spinal stability and posture; reduces lower back pain; enhances balance and coordination; builds hip abductor strength", + "contraindications": "wrist_injury;shoulder_injury;hip_injury;severe_lower_back_injury;post_shoulder_surgery;rotator_cuff_injury", + "instructions": "Lie on your right side, right forearm flat on the mat with elbow directly under your shoulder.\nEngage your core and lift your hips until your body forms a straight line from head to heels.\nHold steady, breathe evenly and keep hips lifted — do not let them sag.", + "is_lottie": false, + "third_party": false, + "equipments": "yoga_mat", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/image/upload/v1780605527/side_plank_right_acklpj.png" + }, + { + "exercise_id": "Ex87", + "name": "Side-Lying Leg Raise (Left)", + "type": "bodyweight", + "primary_muscles": "gluteus_medius;gluteus_minimus;tensor_fasciae_latae", + "secondary_muscles": "gluteus_maximus;core;obliques;hip_flexors", + "body_region": "lower_body", + "movement_pattern": "hip_abduction", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "toning;strength;stability;glutes;rehabilitation", + "estimated_duration": "45s", + "overview": "A side-lying exercise where you lie on your right side and raise your top left leg upward to target the hip abductors, strengthening the outer glutes and improving hip stability and pelvic control.", + "benefits": "Strengthens hip abductors and outer glutes; improves hip and pelvic stability; reduces knee and hip injury risk; corrects muscle imbalances; improves balance; safe and low impact", + "contraindications": "hip_injury;hip_replacement;groin_injury;IT_band_syndrome;post_hip_surgery;severe_lower_back_pain", + "instructions": "Lie on your right side, legs stacked straight, head supported and left hand on the floor for balance.\nKeeping your left leg straight, slowly raise it upward to 30 to 45 degrees, squeezing your outer glute.\nHold briefly at the top, then slowly lower back down without resting on the bottom leg and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "yoga_mat", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1780605539/Side-Lying_left_Leg_Raise_mszo8h.mp4" + }, + { + "exercise_id": "Ex88", + "name": "Side-Lying Leg Raise (Right)", + "type": "bodyweight", + "primary_muscles": "gluteus_medius;gluteus_minimus;tensor_fasciae_latae", + "secondary_muscles": "gluteus_maximus;core;obliques;hip_flexors", + "body_region": "lower_body", + "movement_pattern": "hip_abduction", + "difficulty": "beginner", + "intensity": "low", + "goal_tags": "toning;strength;stability;glutes;rehabilitation", + "estimated_duration": "45s", + "overview": "A side-lying exercise where you lie on your left side and raise your top right leg upward to target the hip abductors, strengthening the outer glutes and improving hip stability and pelvic control.", + "benefits": "Strengthens hip abductors and outer glutes; improves hip and pelvic stability; reduces knee and hip injury risk; corrects muscle imbalances; improves balance; safe and low impact", + "contraindications": "hip_injury;hip_replacement;groin_injury;IT_band_syndrome;post_hip_surgery;severe_lower_back_pain", + "instructions": "Lie on your left side, legs stacked straight, head supported and right hand on the floor for balance.\nKeeping your right leg straight, slowly raise it upward to 30 to 45 degrees, squeezing your outer glute.\nHold briefly at the top, then slowly lower back down without resting on the bottom leg and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "yoga_mat", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1780605535/Side-Lying_Rights_Leg_Raise_c5mpl3.mp4" + }, + { + "exercise_id": "Ex89", + "name": "Stability Ball Crunch", + "type": "stabilityBall", + "primary_muscles": "rectus_abdominis", + "secondary_muscles": "external_obliques;internal_obliques;transverse_abdominis;hip_flexors", + "body_region": "core", + "movement_pattern": "spinal_flexion", + "difficulty": "intermediate", + "intensity": "moderate", + "goal_tags": "core;strength;toning;stability;fat_burn", + "estimated_duration": "45s", + "overview": "A crunch variation performed on a stability ball that provides a greater range of motion than floor crunches, increasing abdominal muscle activation by up to 38% while also improving core stability, balance and coordination.", + "benefits": "Increases abdominal muscle activation; improves core stability and balance; greater range of motion than floor crunches; reduces lower back stress; engages deep stabilizer muscles; tones and strengthens the entire core", + "contraindications": "lower_back_injury;neck_injury;spinal_stenosis;herniated_disc;post_abdominal_surgery;balance_disorders;osteoporosis", + "instructions": "Sit on the stability ball and walk your feet forward until your lower back rests on the ball, knees bent and feet flat.\nEngage your core and curl your upper body upward until your shoulders lift off the ball — do not pull on your neck.\nHold briefly at the top, then slowly lower back down over the ball and repeat.", + "is_lottie": false, + "third_party": false, + "equipments": "stability_ball", + "animation_link": "https://res.cloudinary.com/dyvolu8tc/video/upload/v1780759092/Stability_Ball_Crunch_qqcxhn.mp4" + } + ] +} \ No newline at end of file diff --git a/assets/images/Move_your_body_logo.png b/assets/images/Move_your_body_logo.png new file mode 100644 index 0000000..e51d0e9 Binary files /dev/null and b/assets/images/Move_your_body_logo.png differ diff --git a/assets/images/workout.png b/assets/images/workout.png new file mode 100644 index 0000000..2bcc506 Binary files /dev/null and b/assets/images/workout.png differ diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..391a902 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..0bf1600 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,644 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.templateFlutter; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.templateFlutter.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.templateFlutter.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.templateFlutter.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.templateFlutter; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.templateFlutter; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..c3fedb2 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..c30b367 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,16 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..2c922d6 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,70 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Template Flutter + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + template_flutter + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/Runner/SceneDelegate.swift b/ios/Runner/SceneDelegate.swift new file mode 100644 index 0000000..b9ce8ea --- /dev/null +++ b/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/lib/core/database/db_config.dart b/lib/core/database/db_config.dart new file mode 100644 index 0000000..92c5832 --- /dev/null +++ b/lib/core/database/db_config.dart @@ -0,0 +1,37 @@ +import 'package:path/path.dart'; +import 'package:sqflite/sqflite.dart'; +import 'package:template_flutter/core/database/schema/exercise_schema.dart'; +import 'package:template_flutter/core/database/schema/user_schema.dart'; +import 'package:flutter/foundation.dart'; +class DatabaseService { + static final DatabaseService instance = DatabaseService._internal(); + + DatabaseService._internal(); + + Database? _database; + + Future get database async { + if (_database != null) return _database!; + + _database = await _initDatabase(); + return _database!; + } + + Future _initDatabase() async { + final path = join(await getDatabasesPath(), 'move_your_body.db'); + debugPrint('DB PATH: $path'); + return openDatabase( + path, + version: 2, + onCreate: (db, version) async { + await UserDatabaseService.createTable(db); + await ExerciseDatabaseService.createTable(db); + }, + onUpgrade: (db, oldVersion, newVersion) async { + if (oldVersion < 2) { + await ExerciseDatabaseService.createTable(db); + } + }, + ); + } +} diff --git a/lib/core/database/schema/exercise_schema.dart b/lib/core/database/schema/exercise_schema.dart new file mode 100644 index 0000000..db13903 --- /dev/null +++ b/lib/core/database/schema/exercise_schema.dart @@ -0,0 +1,30 @@ +import 'package:sqflite/sqflite.dart'; +import 'package:template_flutter/core/database/tables/exercise_table.dart'; + +class ExerciseDatabaseService { + static Future createTable(Database db) async { + await db.execute(''' + CREATE TABLE ${ExerciseTable.tableName}( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ${ExerciseTable.exerciseId} TEXT UNIQUE, + ${ExerciseTable.name} TEXT NOT NULL, + ${ExerciseTable.type} TEXT NOT NULL, + ${ExerciseTable.primaryMuscles} TEXT, + ${ExerciseTable.secondaryMuscles} TEXT, + ${ExerciseTable.bodyRegions} TEXT, + ${ExerciseTable.movementPattern} TEXT, + ${ExerciseTable.difficulty} TEXT, + ${ExerciseTable.intensity} TEXT, + ${ExerciseTable.goalTags} TEXT, + ${ExerciseTable.estimatedTime} INTEGER, + ${ExerciseTable.overview} TEXT, + ${ExerciseTable.benefits} TEXT, + ${ExerciseTable.contraindications} TEXT, + ${ExerciseTable.instructions} TEXT, + ${ExerciseTable.isLottie} INTEGER, + ${ExerciseTable.equipments} TEXT, + ${ExerciseTable.animationLink} TEXT + ) + '''); + } +} diff --git a/lib/core/database/schema/user_schema.dart b/lib/core/database/schema/user_schema.dart new file mode 100644 index 0000000..ed78e03 --- /dev/null +++ b/lib/core/database/schema/user_schema.dart @@ -0,0 +1,25 @@ +import 'package:sqflite/sqflite.dart'; +import 'package:template_flutter/core/database/tables/user_table.dart'; + +class UserDatabaseService { + static Future createTable(Database db) async { + await db.execute(''' + CREATE TABLE ${UserTable.tableName}( + ${UserTable.id} INTEGER PRIMARY KEY AUTOINCREMENT, + ${UserTable.username} TEXT, + ${UserTable.height} REAL, + ${UserTable.weight} REAL, + ${UserTable.age} INTEGER, + ${UserTable.goalTags} TEXT, + ${UserTable.customGoal} TEXT, + ${UserTable.healthIssueTags} TEXT, + ${UserTable.customHealthIssue} TEXT, + ${UserTable.difficulty} TEXT, + ${UserTable.intensity} TEXT, + ${UserTable.targetBodyRegion} TEXT, + ${UserTable.equipments} TEXT, + ${UserTable.isOnboarded} INTEGER NOT NULL + ) + '''); + } +} diff --git a/lib/core/database/tables/exercise_table.dart b/lib/core/database/tables/exercise_table.dart new file mode 100644 index 0000000..f43b08e --- /dev/null +++ b/lib/core/database/tables/exercise_table.dart @@ -0,0 +1,32 @@ +class ExerciseTable { + static const tableName = 'exercises'; + static const id = 'id'; + static const exerciseId = 'exercise_id'; + static const name = 'name'; + static const type = 'type'; + + static const primaryMuscles = 'primary_muscles'; + static const secondaryMuscles = 'secondary_muscles'; + + static const bodyRegions = 'body_regions'; + + static const movementPattern = 'movement_pattern'; + + static const difficulty = 'difficulty'; + static const intensity = 'intensity'; + + static const goalTags = 'goal_tags'; + + static const estimatedTime = 'estimated_time'; + + static const overview = 'overview'; + static const benefits = 'benefits'; + + static const contraindications = 'contraindications'; + + static const instructions = 'instructions'; + + static const isLottie = 'is_lottie'; + static const equipments = 'equipments'; + static const animationLink = 'animation_link'; +} diff --git a/lib/core/database/tables/user_table.dart b/lib/core/database/tables/user_table.dart new file mode 100644 index 0000000..faf9f6b --- /dev/null +++ b/lib/core/database/tables/user_table.dart @@ -0,0 +1,22 @@ +class UserTable { + static const tableName = 'user_data'; + + static const id = 'id'; + static const username = 'username'; + static const height = 'height'; + static const weight = 'weight'; + static const age = 'age'; + + static const goalTags = 'goal_tags'; + static const customGoal = 'custom_goal'; + + static const healthIssueTags = 'health_issue_tags'; + static const customHealthIssue = 'custom_health_issue'; + + static const difficulty = 'difficulty'; + static const intensity = 'intensity'; + + static const targetBodyRegion = 'target_body_region'; + static const equipments = 'equipments'; + static const isOnboarded = 'is_onboarded'; +} diff --git a/lib/core/model/exercise_data.dart b/lib/core/model/exercise_data.dart new file mode 100644 index 0000000..16b1f61 --- /dev/null +++ b/lib/core/model/exercise_data.dart @@ -0,0 +1,228 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; + +import '../database/tables/exercise_table.dart'; + +enum ExerciseType { + bodyweight, + stabilityBall, + cardio, + dumbbell, + yoga, + strength, +} + +enum BodyRegion { + lowerBody, + core, + fullBody, + upperBody, + cardioAndEndurance, + mobilityAndFlexibility, +} + +enum Difficulty { beginner, intermediate, advanced } + +enum Intensity { low, moderate, high } + +enum Equipment { stabilityBall, bench, dumbbells, yogaMat, jumpRope } + +class Exercise { + final String exerciseId; + final String name; + + final ExerciseType type; + + final List primaryMuscles; + final List secondaryMuscles; + + final List bodyRegions; + + final String movementPattern; + + final Difficulty difficulty; + + final Intensity intensity; + + final List goalTags; + + final int estimatedTime; + + final String overview; + final String benefits; + + final List contraindications; + + final String instructions; + + final bool isLottie; + + final List equipments; + + final String animationLink; + + const Exercise({ + required this.exerciseId, + required this.name, + required this.type, + required this.primaryMuscles, + required this.secondaryMuscles, + required this.bodyRegions, + required this.movementPattern, + required this.difficulty, + required this.intensity, + required this.goalTags, + required this.estimatedTime, + required this.overview, + required this.benefits, + required this.contraindications, + required this.instructions, + required this.isLottie, + required this.equipments, + required this.animationLink, + }); + + factory Exercise.fromJson(Map json) { + debugPrint( + 'Parsing: ${json['exercise_id']} ' + 'type=${json['type']} ' + 'difficulty=${json['difficulty']} ' + 'intensity=${json['intensity']}', + ); + return Exercise( + exerciseId: json['exercise_id'], + name: json['name'], + + type: ExerciseType.values.firstWhere((e) => e.name == json['type']), + + primaryMuscles: _splitByComma(json['primary_muscles']), + + secondaryMuscles: _splitByComma(json['secondary_muscles']), + + bodyRegions: _parseBodyRegions(json['body_region']), + + movementPattern: json['movement_pattern'] ?? '', + + difficulty: Difficulty.values.firstWhere( + (e) => e.name == json['difficulty'], + ), + + intensity: Intensity.values.firstWhere( + (e) => e.name == json['intensity'], + ), + + goalTags: _splitByComma(json['goal_tags']), + + estimatedTime: 30, + + overview: json['overview'] ?? '', + + benefits: json['benefits'] ?? '', + + contraindications: _splitByComma(json['contraindications']), + + instructions: json['instructions'] ?? '', + + isLottie: json['is_lottie'] ?? false, + + equipments: _parseEquipments(json['equipments']), + + animationLink: json['animation_link'] ?? '', + ); + } + + Map toMap() { + return { + ExerciseTable.exerciseId: exerciseId, + ExerciseTable.name: name, + ExerciseTable.type: type.name, + + ExerciseTable.primaryMuscles: jsonEncode(primaryMuscles), + + ExerciseTable.secondaryMuscles: jsonEncode(secondaryMuscles), + + ExerciseTable.bodyRegions: jsonEncode( + bodyRegions.map((e) => e.name).toList(), + ), + + ExerciseTable.movementPattern: movementPattern, + + ExerciseTable.difficulty: difficulty.name, + + ExerciseTable.intensity: intensity.name, + + ExerciseTable.goalTags: jsonEncode(goalTags), + + ExerciseTable.estimatedTime: estimatedTime, + + ExerciseTable.overview: overview, + + ExerciseTable.benefits: benefits, + + ExerciseTable.contraindications: jsonEncode(contraindications), + + ExerciseTable.instructions: instructions, + + ExerciseTable.isLottie: isLottie ? 1 : 0, + + ExerciseTable.equipments: jsonEncode( + equipments.map((e) => e.name).toList(), + ), + + ExerciseTable.animationLink: animationLink, + }; + } + + static List _splitByComma(dynamic value) { + if (value == null || value.toString().trim().isEmpty) { + return []; + } + return value + .toString() + .split(RegExp(r'[;,]')) + .map((e) => e.trim()) + .where((e) => e.isNotEmpty) + .toList(); + } + + static List _parseBodyRegions(String value) { + if (value.isEmpty || value == "") return []; + + return value.split(';').map((e) { + switch (e.trim()) { + case 'lower_body': + return BodyRegion.lowerBody; + case 'core': + return BodyRegion.core; + case 'full_body': + return BodyRegion.fullBody; + case 'upper_body': + return BodyRegion.upperBody; + case 'cardio_and_endurance': + return BodyRegion.cardioAndEndurance; + default: + return BodyRegion.mobilityAndFlexibility; + } + }).toList(); + } + + static List _parseEquipments(String value) { + if (value.trim().isEmpty) return []; + + return value.split(',').map((e) { + switch (e.trim()) { + case 'bench': + return Equipment.bench; + case 'dumbbells': + return Equipment.dumbbells; + case 'yoga_mat': + return Equipment.yogaMat; + case 'jump_rope': + return Equipment.jumpRope; + default: + return Equipment.stabilityBall; + } + }).toList(); + } +} diff --git a/lib/core/model/user_data.dart b/lib/core/model/user_data.dart new file mode 100644 index 0000000..3be8a99 --- /dev/null +++ b/lib/core/model/user_data.dart @@ -0,0 +1,107 @@ +import 'package:template_flutter/features/onboarding/model/onboarding_state.dart'; + +import '../database/tables/user_table.dart'; + +class UserData { + final String username; + final double height; + final double weight; + final int age; + + final List goalTags; + final String customGoal; + + final List healthIssueTags; + final String customHealthIssue; + + final String difficulty; + final String intensity; + + final List targetBodyRegion; + final List equipments; + final bool isOnboarded; + const UserData({ + required this.username, + required this.height, + required this.weight, + required this.age, + required this.goalTags, + required this.customGoal, + required this.healthIssueTags, + required this.customHealthIssue, + required this.difficulty, + required this.intensity, + required this.targetBodyRegion, + required this.equipments, + required this.isOnboarded, + }); + + Map toMap() { + return { + UserTable.username: username, + UserTable.height: height, + UserTable.weight: weight, + UserTable.age: age, + + UserTable.goalTags: goalTags.join(','), + UserTable.customGoal: customGoal, + + UserTable.healthIssueTags: healthIssueTags.join(','), + UserTable.customHealthIssue: customHealthIssue, + + UserTable.difficulty: difficulty, + UserTable.intensity: intensity, + + UserTable.targetBodyRegion: targetBodyRegion.join(','), + UserTable.equipments: equipments.join(','), + UserTable.isOnboarded: isOnboarded ? 1 : 0, + }; + } + + factory UserData.fromMap(Map map) { + return UserData( + username: map[UserTable.username] ?? '', + height: map[UserTable.height] ?? 0, + weight: map[UserTable.weight] ?? 0, + age: map[UserTable.age] ?? 0, + + goalTags: (map[UserTable.goalTags] as String).split(','), + + customGoal: map[UserTable.customGoal] ?? '', + + healthIssueTags: (map[UserTable.healthIssueTags] as String).split(','), + + customHealthIssue: map[UserTable.customHealthIssue] ?? '', + + difficulty: map[UserTable.difficulty] ?? '', + + intensity: map[UserTable.intensity] ?? '', + + targetBodyRegion: (map[UserTable.targetBodyRegion] as String).split(','), + + equipments: (map[UserTable.equipments] as String).split(','), + + isOnboarded: map[UserTable.isOnboarded] == 1, + ); + } +} + +extension UserDataMapper on OnboardingState { + UserData toUserdata() { + return UserData( + username: username ?? '', + height: height ?? 0, + weight: weight ?? 0, + age: age ?? 0, + goalTags: goalTags.toList(), + customGoal: customGoal, + healthIssueTags: healthIssueTags.toList(), + customHealthIssue: customHealthIssue, + difficulty: difficulty ?? '', + intensity: intensity ?? '', + targetBodyRegion: targetbodyRegion.toList(), + equipments: equipments.toList(), + isOnboarded: true, + ); + } +} diff --git a/lib/core/service/seed_service.dart b/lib/core/service/seed_service.dart new file mode 100644 index 0000000..70518d9 --- /dev/null +++ b/lib/core/service/seed_service.dart @@ -0,0 +1,58 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:sqflite/sqflite.dart'; + +import '../database/db_config.dart'; +import '../database/tables/exercise_table.dart'; +import '../model/exercise_data.dart'; + +class SeedService { + Future seedExercises() async { + try { + final db = await DatabaseService.instance.database; + + final existingExercises = await db.rawQuery( + 'SELECT COUNT(*) as count FROM ${ExerciseTable.tableName}', + ); + final count = existingExercises.first['count'] as int; + if (count > 0) { + debugPrint('Exercises already seeded. Skipping seeding.'); + return; + } + final jsonString = await rootBundle.loadString( + 'assets/exercises/exercises.json', + ); + + final decodedJson = jsonDecode(jsonString) as Map; + final exercisesJson = decodedJson['exercises'] as List; + final exercises = exercisesJson + .map((json) => Exercise.fromJson(json as Map)) + .toList(); + final Batch batch = db.batch(); + for (final exercise in exercises) { + batch.insert( + ExerciseTable.tableName, + exercise.toMap(), + conflictAlgorithm: ConflictAlgorithm.ignore, + ); + } + + await batch.commit(noResult: true); + debugPrint('${exercises.length} exercises seeded successfully.'); + } catch (e, stackTrace) { + debugPrint('Exercise seeding failed: $e'); + debugPrint(stackTrace.toString()); + } + } + + Future debugPrintExercises() async { + final db = await DatabaseService.instance.database; + final result = await db.query(ExerciseTable.tableName); + debugPrint('Total exercises: ${result.length}'); + for (final exercise in result) { + debugPrint(exercise.toString()); + } + } +} diff --git a/lib/core/theme/app_colors.dart b/lib/core/theme/app_colors.dart new file mode 100644 index 0000000..db607e1 --- /dev/null +++ b/lib/core/theme/app_colors.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class AppColors { + AppColors._(); + + static const Color background = Color(0xFF0A1A0A); + + static const Color surface = Color(0xFF122412); + + static const Color card = Color(0xFF1A2E1A); + + static const Color cardSelected = Color(0xFF254025); + + static const Color primary = Color(0xFF4CAF50); + + static const Color primaryLight = Color(0xFF81C784); + + static const Color textPrimary = Color(0xFFFFFFFF); + + static const Color textSecondary = Color(0xFFB0BEC5); + + static const Color border = Color(0xFF2E7D32); + + static const Color borderSelected = Color(0xFF66BB6A); + + static const Color divider = Color(0xFF1B3A1B); +} diff --git a/lib/core/theme/app_themes.dart b/lib/core/theme/app_themes.dart new file mode 100644 index 0000000..6932450 --- /dev/null +++ b/lib/core/theme/app_themes.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'app_colors.dart'; + +class AppTheme { + AppTheme._(); + + static ThemeData darkTheme = ThemeData( + brightness: Brightness.dark, + fontFamily: GoogleFonts.figtree().fontFamily, + scaffoldBackgroundColor: AppColors.background, + + primaryColor: AppColors.primary, + + colorScheme: const ColorScheme.dark( + primary: AppColors.primary, + secondary: AppColors.primaryLight, + surface: AppColors.surface, + outline: AppColors.divider, + ), + + appBarTheme: const AppBarTheme( + backgroundColor: Colors.transparent, + elevation: 0, + centerTitle: true, + titleTextStyle: TextStyle( + color: AppColors.textPrimary, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + iconTheme: IconThemeData(color: AppColors.textPrimary), + ), + + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + foregroundColor: Colors.black, + minimumSize: const Size(double.infinity, 52), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), + textStyle: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + letterSpacing: 0.5, + ), + elevation: 0, + ), + ), + + textTheme: GoogleFonts.figtreeTextTheme( + const TextTheme( + headlineLarge: TextStyle( + color: AppColors.textPrimary, + fontSize: 28, + fontWeight: FontWeight.bold, + height: 1.3, + ), + + headlineMedium: TextStyle( + color: AppColors.textPrimary, + fontSize: 22, + fontWeight: FontWeight.w700, + ), + + titleMedium: TextStyle( + color: AppColors.textPrimary, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + + bodyLarge: TextStyle( + color: AppColors.textPrimary, + fontSize: 16, + height: 1.5, + ), + + bodyMedium: TextStyle( + color: AppColors.textPrimary, + fontSize: 14, + height: 1.5, + ), + + labelLarge: TextStyle( + color: AppColors.textPrimary, + fontSize: 15, + fontWeight: FontWeight.w500, + ), + ), + ), + + inputDecorationTheme: InputDecorationTheme( + hintStyle: TextStyle(color: Colors.white.withValues(alpha: 0.5)), + ), + + dividerColor: AppColors.divider, + + progressIndicatorTheme: const ProgressIndicatorThemeData( + color: AppColors.primary, + ), + ); +} diff --git a/lib/core/widgets/app_scaffold.dart b/lib/core/widgets/app_scaffold.dart new file mode 100644 index 0000000..8f61966 --- /dev/null +++ b/lib/core/widgets/app_scaffold.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +class AppScaffold extends StatelessWidget { + const AppScaffold({ + super.key, + required this.child, + }); + + final Widget child; + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Theme.of(context).scaffoldBackgroundColor, + colorScheme.surface.withValues(alpha: 0.95), + ], + ), + ), + child: SafeArea( + child: child, + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/core/widgets/button_card.dart b/lib/core/widgets/button_card.dart new file mode 100644 index 0000000..a69a15f --- /dev/null +++ b/lib/core/widgets/button_card.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; + +class ButtonCard extends StatelessWidget { + const ButtonCard({ + required this.title, + this.subtitle, + required this.selected, + required this.onTap, + this.titleFontSize, + super.key, + }); + + final String title; + final String? subtitle; + final bool selected; + final VoidCallback onTap; + + final double? titleFontSize; + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + + return Semantics( + button: true, + selected: selected, + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(18), + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: colorScheme.surface.withValues(alpha: 0.75), + borderRadius: BorderRadius.circular(18), + border: Border.all( + color: selected + ? colorScheme.primary + : colorScheme.outline.withValues(alpha: 1), + width: 2, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: double.infinity, + child: Text( + title, + textAlign: TextAlign.center, + style: textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.w600, + fontSize: titleFontSize, + ), + ), + ), + if (subtitle != null) ...[ + const SizedBox(height: 8), + Text( + subtitle!, + style: TextStyle( + color: colorScheme.onSurface.withValues(alpha: 0.6), + fontSize: 12, + ), + ), + ], + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/core/widgets/featurechip.dart b/lib/core/widgets/featurechip.dart new file mode 100644 index 0000000..ead4add --- /dev/null +++ b/lib/core/widgets/featurechip.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; + +class FeatureChip extends StatelessWidget { + const FeatureChip({ + super.key, + required this.text, + this.selected = false, + this.onTap, + this.minWidth = 120, + }); + + final String text; + final bool selected; + final VoidCallback? onTap; + final double minWidth; + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + + return GestureDetector( + onTap: onTap, + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: minWidth, maxWidth: 250), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + decoration: BoxDecoration( + color: selected + ? colorScheme.primary.withValues(alpha: 0.1) + : colorScheme.surface.withValues(alpha: 0.7), + borderRadius: BorderRadius.circular(18), + border: Border.all( + color: selected + ? colorScheme.primary + : colorScheme.primary.withValues(alpha: 0.22), + ), + ), + child: Text( + text, + textAlign: TextAlign.center, + style: textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500), + ), + ), + ), + ); + } +} diff --git a/lib/features/onboarding/model/onboarding_state.dart b/lib/features/onboarding/model/onboarding_state.dart new file mode 100644 index 0000000..2587fd4 --- /dev/null +++ b/lib/features/onboarding/model/onboarding_state.dart @@ -0,0 +1,61 @@ +class OnboardingState { + final Set goalTags; + final String customGoal; + final Set healthIssueTags; + final String customHealthIssue; + final String? difficulty; + final String? intensity; + final Set targetbodyRegion; + final Set equipments; + final String? username; + final double? height; + final double? weight; + final int? age; + final bool isOnboarded; + const OnboardingState({ + this.goalTags = const {}, + this.customGoal = '', + this.healthIssueTags = const {}, + this.customHealthIssue = '', + this.difficulty, + this.intensity, + this.targetbodyRegion = const {}, + this.equipments = const {}, + this.username, + this.height, + this.weight, + this.age, + this.isOnboarded = false, + }); + OnboardingState copyWith({ + Set? goalTags, + String? customGoal, + Set? healthIssueTags, + String? customHealthIssue, + String? difficulty, + String? intensity, + Set? targetbodyRegion, + Set? equipments, + String? username, + double? height, + double? weight, + int? age, + bool? isOnboarded, + }) { + return OnboardingState( + goalTags: goalTags ?? this.goalTags, + customGoal: customGoal ?? this.customGoal, + healthIssueTags: healthIssueTags ?? this.healthIssueTags, + customHealthIssue: customHealthIssue ?? this.customHealthIssue, + difficulty: difficulty ?? this.difficulty, + intensity: intensity ?? this.intensity, + targetbodyRegion: targetbodyRegion ?? this.targetbodyRegion, + equipments: equipments ?? this.equipments, + username: username ?? this.username, + height: height ?? this.height, + weight: weight ?? this.weight, + age: age ?? this.age, + isOnboarded: isOnboarded ?? this.isOnboarded, + ); + } +} diff --git a/lib/features/onboarding/repository/user_repository.dart b/lib/features/onboarding/repository/user_repository.dart new file mode 100644 index 0000000..b2122b4 --- /dev/null +++ b/lib/features/onboarding/repository/user_repository.dart @@ -0,0 +1,55 @@ +import 'package:flutter/cupertino.dart'; +import 'package:template_flutter/core/database/tables/user_table.dart'; +import 'package:template_flutter/core/model/user_data.dart'; +import '../../../core/database/db_config.dart'; + +class UserRepository { + Future saveUser(UserData user) async { + final db = await DatabaseService.instance.database; + await db.insert('user_data', user.toMap()); + } + + Future getUserData() async { + final db = await DatabaseService.instance.database; + debugPrint("Fetching user data from database..."); + final result = await db.query(UserTable.tableName, limit: 1); + + if (result.isEmpty) { + return null; + } + + return UserData.fromMap(result.first); + } + + Future deleteUserData() async { + final user = await getUserData(); + + if (user == null) { + debugPrint('No user data found to delete'); + return; + } + + final db = await DatabaseService.instance.database; + + await db.delete(UserTable.tableName); + + debugPrint('User data deleted successfully'); + } + + Future debugPrintUserData() async { + final user = await getUserData(); + debugPrint('==================='); + debugPrint(user?.username.toString()); + debugPrint(user?.age.toString()); + debugPrint(user?.height.toString()); + debugPrint(user?.weight.toString()); + debugPrint(user?.goalTags.toString()); + debugPrint(user?.healthIssueTags.toString()); + debugPrint(user?.difficulty.toString()); + debugPrint(user?.intensity.toString()); + debugPrint(user?.targetBodyRegion.toString()); + debugPrint(user?.equipments.toString()); + debugPrint(user?.isOnboarded.toString()); + debugPrint('==================='); + } +} diff --git a/lib/features/onboarding/view/body_region_screen.dart b/lib/features/onboarding/view/body_region_screen.dart new file mode 100644 index 0000000..90ba4fe --- /dev/null +++ b/lib/features/onboarding/view/body_region_screen.dart @@ -0,0 +1,142 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:template_flutter/core/widgets/button_card.dart'; + +import '../../../core/widgets/app_scaffold.dart'; +import '../view_model/onboarding_view_model.dart'; + +class BodyRegionScreen extends ConsumerWidget { + const BodyRegionScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + + final selectedBodyRegions = ref.watch( + onboardingProvider.select((state) => state.targetbodyRegion), + ); + + final onboardingNotifier = ref.read(onboardingProvider.notifier); + + return AppScaffold( + child: SingleChildScrollView( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + Row( + children: [ + IconButton( + onPressed: () => Navigator.of(context).maybePop(), + icon: const Icon(Icons.arrow_back_ios_new_rounded), + ), + + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + 6, + (index) => Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + width: 8, + height: 8, + decoration: BoxDecoration( + color: index == 2 + ? colorScheme.primary + : colorScheme.onSurface.withValues(alpha: 0.25), + borderRadius: BorderRadius.circular(999), + ), + ), + ), + ), + ), + ], + ), + + const SizedBox(height: 10), + + Text( + 'What would you like to focus on most?', + textAlign: TextAlign.center, + style: textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + + const SizedBox(height: 20), + ButtonCard( + title: 'Cardio and Endurance', + selected: selectedBodyRegions.contains('Cardio and Endurance'), + titleFontSize: 20, + onTap: () { + onboardingNotifier.toggleTargetBodyRegion( + 'Cardio and Endurance', + ); + }, + ), + const SizedBox(height: 20), + + ButtonCard( + title: 'Upper Body', + selected: selectedBodyRegions.contains('Upper Body'), + titleFontSize: 20, + onTap: () { + onboardingNotifier.toggleTargetBodyRegion('Upper Body'); + }, + ), + + const SizedBox(height: 20), + + ButtonCard( + title: 'Core Strength', + selected: selectedBodyRegions.contains('Core Strength'), + titleFontSize: 20, + onTap: () { + onboardingNotifier.toggleTargetBodyRegion('Core Strength'); + }, + ), + + const SizedBox(height: 20), + + ButtonCard( + title: 'Lower Body', + selected: selectedBodyRegions.contains('Lower Body'), + titleFontSize: 20, + onTap: () { + onboardingNotifier.toggleTargetBodyRegion('Lower Body'); + }, + ), + + const SizedBox(height: 20), + ButtonCard( + title: 'Mobility and Flexibility', + selected: selectedBodyRegions.contains( + 'Mobility and Flexibility', + ), + titleFontSize: 20, + onTap: () { + onboardingNotifier.toggleTargetBodyRegion( + 'Mobility and Flexibility', + ); + }, + ), + + const SizedBox(height: 50), + + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.pushNamed(context, '/onboarding/equipments'); + }, + child: const Text('Continue'), + ), + ), + + const SizedBox(height: 40), + ], + ), + ), + ); + } +} diff --git a/lib/features/onboarding/view/difficulty_level.dart b/lib/features/onboarding/view/difficulty_level.dart new file mode 100644 index 0000000..234d607 --- /dev/null +++ b/lib/features/onboarding/view/difficulty_level.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:template_flutter/core/widgets/button_card.dart'; + +import '../../../core/widgets/app_scaffold.dart'; +import '../view_model/onboarding_view_model.dart'; + +class DifficultyScreen extends ConsumerWidget { + const DifficultyScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + + final selectedDifficulty = ref.watch( + onboardingProvider.select((state) => state.difficulty), + ); + + final onboardingNotifier = ref.read(onboardingProvider.notifier); + + return AppScaffold( + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + /// Progress + Row( + children: [ + IconButton( + onPressed: () => Navigator.of(context).maybePop(), + icon: const Icon(Icons.arrow_back_ios_new_rounded), + ), + + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + 6, + (index) => Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + width: 8, + height: 8, + decoration: BoxDecoration( + color: index == 2 + ? colorScheme.primary + : colorScheme.onSurface.withValues(alpha: 0.25), + borderRadius: BorderRadius.circular(999), + ), + ), + ), + ), + ), + ], + ), + + const SizedBox(height: 10), + + Text( + 'Choose your Difficulty Level', + textAlign: TextAlign.center, + style: textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + + const SizedBox(height: 20), + + ButtonCard( + title: 'Beginner', + subtitle: + 'New to fitness, rarely exercise or returning after a long break.', + selected: selectedDifficulty == 'Beginner', + onTap: () { + onboardingNotifier.setDifficulty('Beginner'); + }, + ), + + const SizedBox(height: 20), + + ButtonCard( + title: 'Intermediate', + subtitle: + 'Exercise regularly and have a basic fitness foundation.', + selected: selectedDifficulty == 'Intermediate', + onTap: () { + onboardingNotifier.setDifficulty('Intermediate'); + }, + ), + + const SizedBox(height: 20), + + ButtonCard( + title: 'Advanced', + subtitle: + 'Train consistently with strong endurance and strength.', + selected: selectedDifficulty == 'Advanced', + onTap: () { + onboardingNotifier.setDifficulty('Advanced'); + }, + ), + + const Spacer(), + + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.pushNamed(context, '/onboarding/intensity'); + }, + child: const Text('Continue'), + ), + ), + + const SizedBox(height: 20), + ], + ), + ), + ); + } +} diff --git a/lib/features/onboarding/view/equipment_screen.dart b/lib/features/onboarding/view/equipment_screen.dart new file mode 100644 index 0000000..6ebfd52 --- /dev/null +++ b/lib/features/onboarding/view/equipment_screen.dart @@ -0,0 +1,148 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:template_flutter/core/widgets/button_card.dart'; + +import '../../../core/widgets/app_scaffold.dart'; +import '../view_model/onboarding_view_model.dart'; + +class EquipmentScreen extends ConsumerWidget { + const EquipmentScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + + final selectedEquipments = ref.watch( + onboardingProvider.select((state) => state.equipments), + ); + + final onboardingNotifier = ref.read(onboardingProvider.notifier); + + return AppScaffold( + child: Padding( + padding: const EdgeInsets.all(20), + child: SingleChildScrollView( + child: Column( + children: [ + /// Progress + Row( + children: [ + IconButton( + onPressed: () => Navigator.of(context).maybePop(), + icon: const Icon(Icons.arrow_back_ios_new_rounded), + ), + + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + 6, + (index) => Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + width: 8, + height: 8, + decoration: BoxDecoration( + color: index == 2 + ? colorScheme.primary + : colorScheme.onSurface.withValues(alpha: 0.25), + borderRadius: BorderRadius.circular(999), + ), + ), + ), + ), + ), + ], + ), + + const SizedBox(height: 10), + + Text( + 'What equipment do you have access to?', + textAlign: TextAlign.center, + style: textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + + const SizedBox(height: 20), + ButtonCard( + title: 'Yoga Mat', + selected: selectedEquipments.contains('Yoga Mat'), + titleFontSize: 20, + onTap: () { + onboardingNotifier.toggleEquipment('Yoga Mat'); + }, + ), + const SizedBox(height: 20), + + ButtonCard( + title: 'Dumbbells', + selected: selectedEquipments.contains('Dumbbells'), + titleFontSize: 20, + onTap: () { + onboardingNotifier.toggleEquipment('Dumbbells'); + }, + ), + + const SizedBox(height: 20), + + ButtonCard( + title: 'Resistance Bands', + selected: selectedEquipments.contains('Resistance Bands'), + titleFontSize: 20, + onTap: () { + onboardingNotifier.toggleEquipment('Resistance Bands'); + }, + ), + + const SizedBox(height: 20), + + ButtonCard( + title: 'Pull-Up Bar', + selected: selectedEquipments.contains('Pull-Up Bar'), + titleFontSize: 20, + onTap: () { + onboardingNotifier.toggleEquipment('Pull-Up Bar'); + }, + ), + + const SizedBox(height: 20), + ButtonCard( + title: 'Yoga Blocks and Belts ', + selected: selectedEquipments.contains('Yoga Blocks and Belts '), + titleFontSize: 20, + onTap: () { + onboardingNotifier.toggleEquipment('Yoga Blocks and Belts '); + }, + ), + + const SizedBox(height: 20), + ButtonCard( + title: 'Exercise Bench', + selected: selectedEquipments.contains('Exercise Bench'), + titleFontSize: 20, + onTap: () { + onboardingNotifier.toggleEquipment('Exercise Bench'); + }, + ), + const SizedBox(height: 30), + + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.pushNamed(context, '/onboarding/userdata'); + }, + child: const Text('Continue'), + ), + ), + + const SizedBox(height: 20), + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/onboarding/view/goal_screen.dart b/lib/features/onboarding/view/goal_screen.dart new file mode 100644 index 0000000..43f6658 --- /dev/null +++ b/lib/features/onboarding/view/goal_screen.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:template_flutter/features/onboarding/view_model/onboarding_view_model.dart'; +import '../../../core/widgets/app_scaffold.dart'; +import '../../../core/widgets/featurechip.dart'; + +class GoalScreen extends ConsumerWidget { + const GoalScreen({super.key}); + static const List goals = [ + 'Burn Fat', + 'Increase Mobility', + 'Tone Body', + 'Build Muscle', + 'Lose Weight', + 'Daily Movement', + 'Sleep Better', + 'Cardio Fitness', + 'Better Posture', + 'Reduce Stress', + 'Core Strength', + 'Bodyweight Only', + 'Improve Stamina and Strength', + ]; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final selectedGoals = ref.watch( + onboardingProvider.select((state) => state.goalTags), + ); + final onboardingNotifier = ref.read(onboardingProvider.notifier); + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + + return AppScaffold( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + IconButton( + onPressed: () => Navigator.of(context).maybePop(), + icon: const Icon(Icons.arrow_back_ios_new_rounded), + tooltip: 'Go back', + ), + + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + 6, + (index) => Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + width: index == 0 ? 8 : 8, + height: 8, + decoration: BoxDecoration( + color: index == 0 + ? colorScheme.primary + : colorScheme.onSurface.withValues(alpha: 0.25), + borderRadius: BorderRadius.circular(999), + ), + ), + ), + ), + ), + ], + ), + + const SizedBox(height: 10), + + Text( + 'Choose Your Goals', + textAlign: TextAlign.center, + style: textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + + const SizedBox(height: 25), + + /// GOAL CHIPS + Wrap( + alignment: WrapAlignment.center, + spacing: 12, + runSpacing: 12, + children: goals.map((goal) { + final isSelected = selectedGoals.contains(goal); + return FeatureChip( + text: goal, + selected: isSelected, + onTap: () { + onboardingNotifier.toggleGoal(goal); + }, + ); + }).toList(), + ), + + const SizedBox(height: 25), + + TextField( + onChanged: (value) { + onboardingNotifier.updateCustomGoal(value); + }, + maxLines: 1, + decoration: InputDecoration( + hintText: 'Any custom fitness goals', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(18), + ), + contentPadding: const EdgeInsets.all(10), + ), + ), + + const SizedBox(height: 28), + + SizedBox( + child: ElevatedButton( + onPressed: () { + Navigator.pushNamed(context, '/onboarding/health-issues'); + }, + child: const Text('Continue'), + ), + ), + + const SizedBox(height: 16), + ], + ), + ), + ); + } +} diff --git a/lib/features/onboarding/view/health_issues.dart b/lib/features/onboarding/view/health_issues.dart new file mode 100644 index 0000000..9ae7105 --- /dev/null +++ b/lib/features/onboarding/view/health_issues.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:template_flutter/features/onboarding/view_model/onboarding_view_model.dart'; +import '../../../core/widgets/app_scaffold.dart'; +import '../../../core/widgets/featurechip.dart'; + +class HealthIssuesScreen extends ConsumerWidget { + const HealthIssuesScreen({super.key}); + static const List healthIssues = [ + 'Lower Back Pain', + 'Ankle Pain', + 'Poor Cardiovascular Endurance', + 'Neck Stiffness', + 'Hip Joint', + 'Hamstring Tightness', + 'Knee Pain', + 'Low Flexibility', + 'Tight Muscles', + 'Shortness of Breath During Light Activity', + 'Stress-Related Body Tension', + ]; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final selectedHealthIssues = ref.watch( + onboardingProvider.select((state) => state.healthIssueTags), + ); + final onboardingNotifier = ref.read(onboardingProvider.notifier); + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + + return AppScaffold( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + IconButton( + onPressed: () => Navigator.of(context).maybePop(), + icon: const Icon(Icons.arrow_back_ios_new_rounded), + tooltip: 'Go back', + ), + + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + 6, + (index) => Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + width: index == 0 ? 8 : 8, + height: 8, + decoration: BoxDecoration( + color: index == 0 + ? colorScheme.primary + : colorScheme.onSurface.withValues(alpha: 0.25), + borderRadius: BorderRadius.circular(999), + ), + ), + ), + ), + ), + ], + ), + + const SizedBox(height: 10), + + Text( + 'Choose Your Health Issues', + textAlign: TextAlign.center, + style: textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + + const SizedBox(height: 25), + + /// GOAL CHIPS + Wrap( + alignment: WrapAlignment.center, + spacing: 10, + runSpacing: 12, + children: healthIssues.map((issues) { + final isSelected = selectedHealthIssues.contains(issues); + return FeatureChip( + text: issues, + selected: isSelected, + minWidth: 50, + onTap: () { + onboardingNotifier.toggleHealthIssue(issues); + }, + ); + }).toList(), + ), + + const SizedBox(height: 25), + + TextField( + onChanged: (value) { + onboardingNotifier.updateCustomHealthIssue(value); + }, + maxLines: 1, + decoration: InputDecoration( + hintText: 'Any custom health issues', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(18), + ), + contentPadding: const EdgeInsets.all(10), + ), + ), + + const SizedBox(height: 28), + + SizedBox( + child: ElevatedButton( + onPressed: () { + Navigator.of(context).pushNamed('/onboarding/difficulty'); + }, + child: const Text('Continue'), + ), + ), + + const SizedBox(height: 16), + ], + ), + ), + ); + } +} diff --git a/lib/features/onboarding/view/intensity_level.dart b/lib/features/onboarding/view/intensity_level.dart new file mode 100644 index 0000000..2a3e432 --- /dev/null +++ b/lib/features/onboarding/view/intensity_level.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:template_flutter/core/widgets/button_card.dart'; + +import '../../../core/widgets/app_scaffold.dart'; +import '../view_model/onboarding_view_model.dart'; + +class IntensityScreen extends ConsumerWidget { + const IntensityScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + + final selectedIntensity = ref.watch( + onboardingProvider.select((state) => state.intensity), + ); + + final onboardingNotifier = ref.read(onboardingProvider.notifier); + + return AppScaffold( + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + /// Progress + Row( + children: [ + IconButton( + onPressed: () => Navigator.of(context).maybePop(), + icon: const Icon(Icons.arrow_back_ios_new_rounded), + ), + + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + 6, + (index) => Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + width: 8, + height: 8, + decoration: BoxDecoration( + color: index == 2 + ? colorScheme.primary + : colorScheme.onSurface.withValues(alpha: 0.25), + borderRadius: BorderRadius.circular(999), + ), + ), + ), + ), + ), + ], + ), + + const SizedBox(height: 10), + + Text( + 'Choose your Intensity Level', + textAlign: TextAlign.center, + style: textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + + const SizedBox(height: 20), + + ButtonCard( + title: 'Light', + subtitle: + 'Easy-paced workouts focused on mobility, consistency, and recovery.', + selected: selectedIntensity == 'Light', + onTap: () { + onboardingNotifier.setIntensity('Light'); + }, + ), + + const SizedBox(height: 20), + + ButtonCard( + title: 'Moderate', + subtitle: + 'Balanced challenge to improve endurance, fitness and strength.', + selected: selectedIntensity == 'Moderate', + onTap: () { + onboardingNotifier.setIntensity('Moderate'); + }, + ), + + const SizedBox(height: 20), + + ButtonCard( + title: 'High', + subtitle: + 'Demanding sessions designed to maximize performance and results.', + selected: selectedIntensity == 'High', + onTap: () { + onboardingNotifier.setIntensity('High'); + }, + ), + + const Spacer(), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.of(context).pushNamed('/onboarding/target-body-region'); + }, + child: const Text('Continue'), + ), + ), + + const SizedBox(height: 20), + ], + ), + ), + ); + } +} diff --git a/lib/features/onboarding/view/resultscreen.dart b/lib/features/onboarding/view/resultscreen.dart new file mode 100644 index 0000000..0a1fe84 --- /dev/null +++ b/lib/features/onboarding/view/resultscreen.dart @@ -0,0 +1,108 @@ +// this file to just that what we are collection for onboarding and then we will use this data to show the result in the result screen +// after mentor review this will be removed + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../repository/user_repository.dart'; +import '../../../core/widgets/app_scaffold.dart'; +import '../view_model/onboarding_view_model.dart'; + +class ResultScreen extends ConsumerWidget { + const ResultScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(onboardingProvider); + + return AppScaffold( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + + const Center( + child: Text( + 'Collected Onboarding Data', + style: TextStyle(fontSize: 26, fontWeight: FontWeight.bold), + ), + ), + + const SizedBox(height: 24), + + _DataTile( + title: 'Username', + value: state.username?.toString() ?? '', + ), + + _DataTile(title: 'Age', value: state.age?.toString() ?? ''), + + _DataTile(title: 'Height', value: '${state.height ?? ''} cm'), + + _DataTile(title: 'Weight', value: '${state.weight ?? ''} kg'), + + _DataTile(title: 'Goals', value: state.goalTags.join(', ')), + + _DataTile(title: 'Custom Goal', value: state.customGoal), + + _DataTile( + title: 'Health Issues', + value: state.healthIssueTags.join(', '), + ), + + _DataTile( + title: 'Custom Health Issue', + value: state.customHealthIssue, + ), + + _DataTile(title: 'Difficulty', value: state.difficulty ?? ''), + + _DataTile(title: 'Intensity', value: state.intensity ?? ''), + + _DataTile( + title: 'Target Body Region', + value: state.targetbodyRegion.join(', '), + ), + + _DataTile(title: 'Equipments', value: state.equipments.join(', ')), + ElevatedButton( + onPressed: () async { + await UserRepository().deleteUserData(); + // await UserRepository().debugPrintUserData(); + }, + child: const Text('Delete User'), + ), + ], + ), + ), + ); + } +} + +class _DataTile extends StatelessWidget { + const _DataTile({required this.title, required this.value}); + + final String title; + final String value; + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.only(bottom: 12), + child: Padding( + padding: const EdgeInsets.all(14), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: const TextStyle(fontWeight: FontWeight.bold)), + + const SizedBox(height: 6), + + Text(value.isEmpty ? 'Not Provided' : value), + ], + ), + ), + ); + } +} diff --git a/lib/features/onboarding/view/splash_screen.dart b/lib/features/onboarding/view/splash_screen.dart new file mode 100644 index 0000000..4089054 --- /dev/null +++ b/lib/features/onboarding/view/splash_screen.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:template_flutter/core/service/seed_service.dart'; + +import '../repository/user_repository.dart'; +import '../view_model/onboarding_view_model.dart'; + +class SplashScreen extends ConsumerStatefulWidget { + const SplashScreen({super.key}); + + @override + ConsumerState createState() => _SplashScreenState(); +} + +class _SplashScreenState extends ConsumerState { + @override + void initState() { + super.initState(); + _checkUser(); + } + + Future _checkUser() async { + final user = await UserRepository().getUserData(); + await SeedService().seedExercises(); + // await SeedService().debugPrintExercises(); + + if (user != null) { + ref.read(onboardingProvider.notifier).loadFromUserData(user); + } + + if (!mounted) return; + + Navigator.pushReplacementNamed( + context, + user?.isOnboarded == true ? '/onboarding/result' : '/onboarding/welcome', + ); + } + + @override + Widget build(BuildContext context) { + return const Scaffold(body: Center(child: CircularProgressIndicator())); + } +} diff --git a/lib/features/onboarding/view/user_details.dart b/lib/features/onboarding/view/user_details.dart new file mode 100644 index 0000000..3d4bf85 --- /dev/null +++ b/lib/features/onboarding/view/user_details.dart @@ -0,0 +1,168 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:template_flutter/core/model/user_data.dart'; +import 'package:template_flutter/features/onboarding/repository/user_repository.dart'; + +import '../../../core/widgets/app_scaffold.dart'; +import '../view_model/onboarding_view_model.dart'; + +class UserdataScreen extends ConsumerWidget { + const UserdataScreen({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + final onboardingNotifier = ref.read(onboardingProvider.notifier); + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + + return AppScaffold( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + IconButton( + onPressed: () => Navigator.of(context).maybePop(), + icon: const Icon(Icons.arrow_back_ios_new_rounded), + ), + + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + 6, + (index) => Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + width: 8, + height: 8, + decoration: BoxDecoration( + color: index == 5 + ? colorScheme.primary + : colorScheme.onSurface.withValues(alpha: 0.25), + borderRadius: BorderRadius.circular(999), + ), + ), + ), + ), + ), + ], + ), + + const SizedBox(height: 24), + + Text( + 'A few details about you', + textAlign: TextAlign.center, + style: textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + + const SizedBox(height: 30), + + TextField( + onChanged: onboardingNotifier.setUsername, + decoration: InputDecoration( + hintText: 'Your name', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(18), + ), + ), + ), + + const SizedBox(height: 20), + + TextField( + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[0-9.]')), + ], + onChanged: (value) { + final weight = double.tryParse(value); + + if (weight != null && weight > 0 && weight <= 500) { + onboardingNotifier.setWeight(weight); + } + }, + decoration: InputDecoration( + hintText: 'Weight (kg)', + helperText: '1 - 500 kg', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(18), + ), + ), + ), + + const SizedBox(height: 20), + + TextField( + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[0-9.]')), + ], + onChanged: (value) { + final height = double.tryParse(value); + + if (height != null && height > 50 && height <= 250) { + onboardingNotifier.setHeight(height); + } + }, + decoration: InputDecoration( + hintText: 'Height (cm)', + helperText: '50 - 250 cm', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(18), + ), + ), + ), + + const SizedBox(height: 20), + + TextField( + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + onChanged: (value) { + final age = int.tryParse(value); + + if (age != null && age > 0 && age <= 150) { + onboardingNotifier.setAge(age); + } + }, + decoration: InputDecoration( + hintText: 'Age', + helperText: '1 - 150 years', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(18), + ), + ), + ), + + const SizedBox(height: 40), + + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () async { + final state = ref.read(onboardingProvider); + final user = state.toUserdata(); + await UserRepository().saveUser(user); + if (context.mounted) { + Navigator.pushNamedAndRemoveUntil( + context, + '/onboarding/result', + (route) => false, + ); + } + }, + child: const Text('Continue'), + ), + ), + const SizedBox(height: 40), + ], + ), + ), + ); + } +} diff --git a/lib/features/onboarding/view/welcome_screen.dart b/lib/features/onboarding/view/welcome_screen.dart new file mode 100644 index 0000000..a520f4f --- /dev/null +++ b/lib/features/onboarding/view/welcome_screen.dart @@ -0,0 +1,141 @@ +import 'package:flutter/material.dart'; +import 'package:template_flutter/core/widgets/app_scaffold.dart'; +import '../../../core/widgets/featurechip.dart'; + +class WelcomeScreen extends StatelessWidget { + const WelcomeScreen({super.key}); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + final colorScheme = Theme.of(context).colorScheme; + + return AppScaffold( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + /// LOGO + TITLE + Padding( + padding: const EdgeInsets.only(left: 25), + child: Center( + child: IntrinsicWidth( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 95, + height: 95, + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: colorScheme.surface, + border: Border.all( + color: colorScheme.primary.withValues(alpha: 0.35), + width: 1.5, + ), + boxShadow: [ + BoxShadow( + color: colorScheme.primary.withValues(alpha: 0.2), + blurRadius: 24, + spreadRadius: 1, + ), + ], + ), + child: ClipOval( + child: Image.asset( + 'assets/images/move_your_body_logo.png', + fit: BoxFit.cover, + ), + ), + ), + + const SizedBox(width: 18), + + Container( + width: 1, + height: 72, + decoration: BoxDecoration( + color: colorScheme.onSurface.withValues(alpha: 0.45), + borderRadius: BorderRadius.circular(999), + ), + ), + + const SizedBox(width: 18), + + Expanded( + child: Text.rich( + TextSpan( + children: [ + const TextSpan(text: 'Your '), + TextSpan( + text: 'AI', + style: textTheme.headlineLarge?.copyWith( + color: colorScheme.primary, + ), + ), + const TextSpan(text: ' Fitness Mentor'), + ], + ), + style: textTheme.headlineLarge?.copyWith( + height: 1.05, + letterSpacing: -1, + fontWeight: FontWeight.w700, + ), + ), + ), + ], + ), + ), + ), + ), + + /// FEATURES + Wrap( + alignment: WrapAlignment.center, + spacing: 10, + runSpacing: 10, + children: const [ + FeatureChip( + text: + 'Smart short workouts tailored personalised to your goals', + ), + FeatureChip( + text: 'Designed for busy schedules and daily consistency', + ), + FeatureChip(text: 'Fully offline and privacy-focused'), + FeatureChip(text: 'Make movement enjoyable, not a burden'), + ], + ), + + /// BUTTON + SUBTEXT + Column( + children: [ + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.pushNamed(context, '/onboarding/goals'); + }, + child: const Text('Get Started'), + ), + ), + + const SizedBox(height: 18), + + Text( + 'A few quick questions to personalize your experience.', + style: textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.75), + ), + textAlign: TextAlign.center, + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/onboarding/view_model/onboarding_view_model.dart b/lib/features/onboarding/view_model/onboarding_view_model.dart new file mode 100644 index 0000000..69ec671 --- /dev/null +++ b/lib/features/onboarding/view_model/onboarding_view_model.dart @@ -0,0 +1,105 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:template_flutter/core/model/user_data.dart'; +import '../model/onboarding_state.dart'; + +class OnboardingViewModel extends Notifier { + @override + OnboardingState build() { + return const OnboardingState(); + } + + void toggleGoal(String goal) { + final updatedGoals = Set.from(state.goalTags); + if (updatedGoals.contains(goal)) { + updatedGoals.remove(goal); + } else { + updatedGoals.add(goal); + } + + state = state.copyWith(goalTags: updatedGoals); + } + + void updateCustomGoal(String goals) { + state = state.copyWith(customGoal: goals); + } + + void toggleHealthIssue(String issue) { + final updatedIssues = Set.from(state.healthIssueTags); + if (updatedIssues.contains(issue)) { + updatedIssues.remove(issue); + } else { + updatedIssues.add(issue); + } + state = state.copyWith(healthIssueTags: updatedIssues); + } + + void updateCustomHealthIssue(String healthIssue) { + state = state.copyWith(customHealthIssue: healthIssue); + } + + void setDifficulty(String difficulty) { + state = state.copyWith(difficulty: difficulty); + } + + void setIntensity(String intensity) { + state = state.copyWith(intensity: intensity); + } + + void toggleTargetBodyRegion(String region) { + final updatedRegions = Set.from(state.targetbodyRegion); + if (updatedRegions.contains(region)) { + updatedRegions.remove(region); + } else { + updatedRegions.add(region); + } + state = state.copyWith(targetbodyRegion: updatedRegions); + } + + void toggleEquipment(String equipment) { + final updatedEquipments = Set.from(state.equipments); + if (updatedEquipments.contains(equipment)) { + updatedEquipments.remove(equipment); + } else { + updatedEquipments.add(equipment); + } + state = state.copyWith(equipments: updatedEquipments); + } + + void setUsername(String username) { + state = state.copyWith(username: username); + } + + void setAge(int age) { + state = state.copyWith(age: age); + } + + void setWeight(double weight) { + state = state.copyWith(weight: weight); + } + + void setHeight(double height) { + state = state.copyWith(height: height); + } + + void loadFromUserData(UserData user) { + state = state.copyWith( + username: user.username, + age: user.age, + height: user.height, + weight: user.weight, + goalTags: user.goalTags.toSet(), + customGoal: user.customGoal, + healthIssueTags: user.healthIssueTags.toSet(), + customHealthIssue: user.customHealthIssue, + difficulty: user.difficulty, + intensity: user.intensity, + targetbodyRegion: user.targetBodyRegion.toSet(), + equipments: user.equipments.toSet(), + ); + } +} + +final onboardingProvider = + NotifierProvider( + OnboardingViewModel.new, + ); diff --git a/lib/generated/app_localizations.dart b/lib/generated/app_localizations.dart new file mode 100644 index 0000000..f5cedf5 --- /dev/null +++ b/lib/generated/app_localizations.dart @@ -0,0 +1,134 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_en.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of AppLocalizations +/// returned by `AppLocalizations.of(context)`. +/// +/// Applications need to include `AppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'generated/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: AppLocalizations.localizationsDelegates, +/// supportedLocales: AppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the AppLocalizations.supportedLocales +/// property. +abstract class AppLocalizations { + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static AppLocalizations? of(BuildContext context) { + return Localizations.of(context, AppLocalizations); + } + + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [Locale('en')]; + + /// No description provided for @hello. + /// + /// In en, this message translates to: + /// **'Hello'** + String get hello; +} + +class _AppLocalizationsDelegate + extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => + ['en'].contains(locale.languageCode); + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} + +AppLocalizations lookupAppLocalizations(Locale locale) { + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'en': + return AppLocalizationsEn(); + } + + throw FlutterError( + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.', + ); +} diff --git a/lib/generated/app_localizations_en.dart b/lib/generated/app_localizations_en.dart new file mode 100644 index 0000000..7323f21 --- /dev/null +++ b/lib/generated/app_localizations_en.dart @@ -0,0 +1,13 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class AppLocalizationsEn extends AppLocalizations { + AppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get hello => 'Hello'; +} diff --git a/lib/main.dart b/lib/main.dart index 244a702..b666143 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,122 +1,43 @@ import 'package:flutter/material.dart'; +import 'package:template_flutter/features/onboarding/view/body_region_screen.dart'; +import 'package:template_flutter/features/onboarding/view/difficulty_level.dart'; +import 'package:template_flutter/features/onboarding/view/equipment_screen.dart'; +import 'package:template_flutter/features/onboarding/view/goal_screen.dart'; +import 'package:template_flutter/features/onboarding/view/health_issues.dart'; +import 'package:template_flutter/features/onboarding/view/intensity_level.dart'; +import 'package:template_flutter/features/onboarding/view/resultscreen.dart'; +import 'package:template_flutter/features/onboarding/view/splash_screen.dart'; +import 'package:template_flutter/features/onboarding/view/user_details.dart'; + +import 'core/theme/app_themes.dart'; +import 'features/onboarding/view/welcome_screen.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; void main() { - runApp(const MyApp()); + runApp(const ProviderScope(child: MyApp())); } class MyApp extends StatelessWidget { const MyApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: .fromSeed(seedColor: Colors.deepPurple), - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: .center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), + debugShowCheckedModeBanner: false, + theme: AppTheme.darkTheme, + initialRoute: '/', + routes: { + '/': (context) => const SplashScreen(), + '/onboarding/welcome': (context) => const WelcomeScreen(), + '/onboarding/goals': (context) => const GoalScreen(), + '/onboarding/health-issues': (context) => const HealthIssuesScreen(), + '/onboarding/difficulty': (context) => const DifficultyScreen(), + '/onboarding/intensity': (context) => const IntensityScreen(), + '/onboarding/target-body-region': (context) => const BodyRegionScreen(), + '/onboarding/equipments': (context) => const EquipmentScreen(), + '/onboarding/userdata': (context) => const UserdataScreen(), + '/onboarding/result': (context) => const ResultScreen(), + }, ); } } diff --git a/linux/.gitignore b/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 0000000..4a6b60f --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "template_flutter") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.template_flutter") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..e71a16d --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/linux/flutter/generated_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..be1ee3e --- /dev/null +++ b/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/linux/runner/CMakeLists.txt b/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/linux/runner/main.cc b/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc new file mode 100644 index 0000000..9bf5f73 --- /dev/null +++ b/linux/runner/my_application.cc @@ -0,0 +1,148 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView* view) { + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "template_flutter"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "template_flutter"); + } + + gtk_window_set_default_size(window, 1280, 720); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 + // for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); + gtk_widget_realize(GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/linux/runner/my_application.h b/linux/runner/my_application.h new file mode 100644 index 0000000..db16367 --- /dev/null +++ b/linux/runner/my_application.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, + my_application, + MY, + APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/macos/.gitignore b/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..29d4e7f --- /dev/null +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import sqflite_darwin + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) +} diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..0367b53 --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,729 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* template_flutter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "template_flutter.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* template_flutter.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* template_flutter.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.templateFlutter.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/template_flutter.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/template_flutter"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.templateFlutter.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/template_flutter.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/template_flutter"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.templateFlutter.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/template_flutter.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/template_flutter"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..313869c --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..b3c1761 --- /dev/null +++ b/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..95aee29 --- /dev/null +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = template_flutter + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.templateFlutter + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/pubspec.lock b/pubspec.lock index 7a83180..5e66fc3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: a49d6cf99e8d8e7a8e93668d09ced0bbdb954d0b4fccc2f5f9241c6b87fad95c + url: "https://pub.dev" + source: hosted + version: "99.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "663efa951fb8a45e06f491223a604c93820598f20e6a99c25617a1576065e8b7" + url: "https://pub.dev" + source: hosted + version: "12.1.0" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -21,10 +45,18 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" clock: dependency: transitive description: @@ -33,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "67cf6d84013f9c601e42a6f8a6b74c4c0d9dc1a1619d775f2b28b732d3551b85" + url: "https://pub.dev" + source: hosted + version: "1.2.0" collection: dependency: transitive description: @@ -41,6 +81,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + 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: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" cupertino_icons: dependency: "direct main" description: @@ -57,6 +121,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -70,11 +150,112 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "4e166be88e1dbbaa34a280bdb744aeae73b7ef25fdf8db7a3bb776760a3648e2" + url: "https://pub.dev" + source: hosted + version: "3.3.1" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055 + url: "https://pub.dev" + source: hosted + version: "6.3.3" + hooks: + dependency: transitive + description: + name: hooks + sha256: a41af4e8fc687cd6d33de9751eb936c8c0204ebe2bcb6c15ecf707504bf47f31 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + jni: + dependency: transitive + description: + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" leak_tracker: dependency: transitive description: @@ -107,43 +288,227 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" - path: + version: "1.18.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + objective_c: dependency: transitive + description: + name: objective_c + sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" + url: "https://pub.dev" + source: hosted + version: "9.4.1" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: "direct main" description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + riverpod: + dependency: transitive + description: + name: riverpod + sha256: "8c22216be8ad3ef2b44af3a329693558c98eca7b8bd4ef495c92db0bba279f83" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + 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: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.0" + 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: @@ -152,6 +517,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.2" + sqflite: + dependency: "direct main" + description: + name: sqflite + sha256: "58a799e6ac17dd32fbab93813d39ed835a75ccc0f8f85b8955fe318c6712b082" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: d0548f9d7422a2dae99ec6f8b0a3074463b132d216fa5ba0d230eeefc901983b + url: "https://pub.dev" + source: hosted + version: "2.4.3" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: cce558075afe2a83f3fd7fc123acd6b090683e4f23910d44fbb31ecd7800b014 + url: "https://pub.dev" + source: hosted + version: "2.5.9" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "164a5d73ab87a134566057219988bafde837029a64264e61f1f04376ef3cfcd2" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: f84939f84350d92d04416f8bc4dc52d3896aec7716cc9e80cf0146342139dc50 + url: "https://pub.dev" + source: hosted + version: "2.4.1" stack_trace: dependency: transitive description: @@ -160,6 +565,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" stream_channel: dependency: transitive description: @@ -176,6 +589,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "93b153dcb6a26dcddee6ca087dd634b53e38c10b5aa163e8e49501a776456153" + url: "https://pub.dev" + source: hosted + version: "3.4.1" term_glyph: dependency: transitive description: @@ -184,14 +605,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.2" + test: + dependency: transitive + description: + name: test + sha256: "8d9ceddbab833f180fbefed08afa76d7c03513dfdba87ffcec2718b02bbcbf20" + url: "https://pub.dev" + source: hosted + version: "1.31.0" test_api: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + test_core: + dependency: transitive + description: + name: test_core + sha256: "1991d4cfe85d5043241acac92962c3977c8d2f2add1ee73130c7b286417d1d34" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.6.17" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -208,6 +653,62 @@ packages: url: "https://pub.dev" source: hosted version: "15.2.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + 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" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: - dart: ">=3.10.7 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.12.0 <4.0.0" + flutter: ">=3.44.0" diff --git a/pubspec.yaml b/pubspec.yaml index c78cb04..3aeec09 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,10 +33,12 @@ dependencies: flutter_localizations: sdk: flutter intl: any - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 - + google_fonts: ^6.2.1 + flutter_riverpod: ^3.3.1 + sqflite: ^2.4.3 + path: ^1.9.1 + dev_dependencies: flutter_test: sdk: flutter @@ -62,7 +64,8 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - - assets/images/ + - assets/images/ + - assets/exercises/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..1375f8f --- /dev/null +++ b/web/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + template_flutter + + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..4fc949b --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "template_flutter", + "short_name": "template_flutter", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 0000000..ef661f8 --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(template_flutter LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "template_flutter") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..8b6d468 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..3ad69c6 --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 0000000..0d4f917 --- /dev/null +++ b/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "template_flutter" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "template_flutter" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "template_flutter.exe" "\0" + VALUE "ProductName", "template_flutter" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 0000000..bf3ca36 --- /dev/null +++ b/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"template_flutter", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/windows/runner/resources/app_icon.ico differ diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 0000000..3cb7146 --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,69 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + // First, find the length of the string with a safe upper bound (CWE-126). + // UNICODE_STRING_MAX_CHARS (32767) is the maximum length of a UNICODE_STRING. + int input_length = static_cast(wcsnlen(utf16_string, UNICODE_STRING_MAX_CHARS)); + // Now use that bounded length to determine the required buffer size. + // When an explicit length is passed, WideCharToMultiByte does not include + // the null terminator in its returned size. + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, nullptr, 0, nullptr, nullptr); + std::string utf8_string; + if (target_length == 0 || static_cast(target_length) > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_