├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── font
│ │ │ │ ├── nunito_bold.ttf
│ │ │ │ ├── nunito_italic.ttf
│ │ │ │ ├── nunito_light.ttf
│ │ │ │ ├── outfit_bold.ttf
│ │ │ │ ├── outfit_light.ttf
│ │ │ │ ├── nunito_regular.ttf
│ │ │ │ ├── outfit_regular.ttf
│ │ │ │ └── nunito_semi_bold.ttf
│ │ │ ├── drawable
│ │ │ │ ├── abs_anatomy_nb.png
│ │ │ │ ├── chest_anatomy_nb.png
│ │ │ │ ├── lats_anatomy_nb.png
│ │ │ │ ├── login_background.jpg
│ │ │ │ ├── traps_anatomy_nb.png
│ │ │ │ ├── biceps_anatomy_nb.png
│ │ │ │ ├── deltoids_anatomy_nb.png
│ │ │ │ ├── register_background.jpg
│ │ │ │ ├── stockimagebarbell.png
│ │ │ │ ├── triceps_anatomy_nb.png
│ │ │ │ ├── hamstrings_anatomy_nb.png
│ │ │ │ ├── calf_muscles_anatomy_nb.png
│ │ │ │ ├── home_screen_background.png
│ │ │ │ ├── stats_screen_background.png
│ │ │ │ ├── workout_card_background.jpg
│ │ │ │ ├── quadriceps_muscles_anatomy_nb.png
│ │ │ │ ├── ic_level.xml
│ │ │ │ ├── ic_dot.xml
│ │ │ │ ├── ic_remove.xml
│ │ │ │ ├── ic_skill_level_advanced_icon.xml
│ │ │ │ ├── ic_skill_level_intermediate_icon.xml
│ │ │ │ ├── ic_delete.xml
│ │ │ │ ├── ic_skill_level_basic_icon.xml
│ │ │ │ ├── ic_list.xml
│ │ │ │ ├── ic_timer.xml
│ │ │ │ ├── ic_exercises.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_infit_logo.png
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_infit_logo_full.png
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ ├── ic_infit_logo_round.png
│ │ │ │ ├── ic_infit_logo_foreground.png
│ │ │ │ ├── ic_infit_logo_full_round.png
│ │ │ │ └── ic_infit_logo_full_foreground.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_infit_logo.png
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_infit_logo_full.png
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ ├── ic_infit_logo_round.png
│ │ │ │ ├── ic_infit_logo_foreground.png
│ │ │ │ ├── ic_infit_logo_full_round.png
│ │ │ │ └── ic_infit_logo_full_foreground.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_infit_logo.png
│ │ │ │ ├── ic_infit_logo_full.png
│ │ │ │ ├── ic_infit_logo_round.png
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ ├── ic_infit_logo_foreground.png
│ │ │ │ ├── ic_infit_logo_full_round.png
│ │ │ │ └── ic_infit_logo_full_foreground.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_infit_logo.png
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_infit_logo_full.png
│ │ │ │ ├── ic_infit_logo_round.png
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ ├── ic_infit_logo_foreground.png
│ │ │ │ ├── ic_infit_logo_full_round.png
│ │ │ │ └── ic_infit_logo_full_foreground.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_infit_logo.png
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_infit_logo_full.png
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ ├── ic_infit_logo_round.png
│ │ │ │ ├── ic_infit_logo_foreground.png
│ │ │ │ ├── ic_infit_logo_full_round.png
│ │ │ │ └── ic_infit_logo_full_foreground.png
│ │ │ ├── values
│ │ │ │ ├── ic_infit_logo_background.xml
│ │ │ │ ├── ic_infit_logo_full_background.xml
│ │ │ │ ├── themes.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── strings.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_infit_logo.xml
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ ├── ic_infit_logo_round.xml
│ │ │ │ ├── ic_launcher_round.xml
│ │ │ │ ├── ic_infit_logo_full.xml
│ │ │ │ └── ic_infit_logo_full_round.xml
│ │ │ └── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ ├── ic_infit_logo-playstore.png
│ │ ├── ic_infit_logo_full-playstore.png
│ │ ├── java
│ │ │ └── ken
│ │ │ │ └── projects
│ │ │ │ └── infit
│ │ │ │ ├── data
│ │ │ │ ├── models
│ │ │ │ │ ├── User.kt
│ │ │ │ │ ├── ExerciseVolume.kt
│ │ │ │ │ ├── states
│ │ │ │ │ │ ├── WorkoutPlanState.kt
│ │ │ │ │ │ └── AuthState.kt
│ │ │ │ │ ├── Workout.kt
│ │ │ │ │ ├── WorkoutPlan.kt
│ │ │ │ │ ├── ExerciseHistoryItem.kt
│ │ │ │ │ └── ExerciseItem.kt
│ │ │ │ ├── repository
│ │ │ │ │ ├── UserRepositoryImpl.kt
│ │ │ │ │ └── WorkoutRepositoryImpl.kt
│ │ │ │ └── service
│ │ │ │ │ └── WorkoutTimerService.kt
│ │ │ │ ├── InfitApp.kt
│ │ │ │ ├── util
│ │ │ │ ├── Resource.kt
│ │ │ │ ├── ext.kt
│ │ │ │ ├── DifficultyLevels.kt
│ │ │ │ └── DefaultWorkoutPlans.kt
│ │ │ │ ├── ui
│ │ │ │ ├── theme
│ │ │ │ │ ├── Shape.kt
│ │ │ │ │ ├── Color.kt
│ │ │ │ │ ├── Theme.kt
│ │ │ │ │ └── Type.kt
│ │ │ │ ├── composables
│ │ │ │ │ ├── profile
│ │ │ │ │ │ └── ProfileScreen.kt
│ │ │ │ │ ├── home
│ │ │ │ │ │ ├── EmptyWorkoutPlanView.kt
│ │ │ │ │ │ └── TrainingCard.kt
│ │ │ │ │ ├── stats
│ │ │ │ │ │ ├── StatsScreen.kt
│ │ │ │ │ │ └── StatsDetailScreen.kt
│ │ │ │ │ ├── CommandsDisplay.kt
│ │ │ │ │ ├── workout
│ │ │ │ │ │ ├── WorkoutDetailItems.kt
│ │ │ │ │ │ ├── WorkoutCard.kt
│ │ │ │ │ │ └── WorkoutPlanSetUp.kt
│ │ │ │ │ ├── Buttons.kt
│ │ │ │ │ ├── CalendarDisplay.kt
│ │ │ │ │ ├── signup
│ │ │ │ │ │ └── SignUpScreen.kt
│ │ │ │ │ ├── exercises
│ │ │ │ │ │ ├── ExercisesScreen.kt
│ │ │ │ │ │ └── ExerciseDetailScreen.kt
│ │ │ │ │ └── login
│ │ │ │ │ │ └── LoginScreen.kt
│ │ │ │ └── navigation
│ │ │ │ │ ├── LoginNavGraph.kt
│ │ │ │ │ ├── Screens.kt
│ │ │ │ │ ├── RootNavGraph.kt
│ │ │ │ │ └── MainNavGraph.kt
│ │ │ │ ├── domain
│ │ │ │ ├── UserRepository.kt
│ │ │ │ └── WorkoutRepository.kt
│ │ │ │ ├── di
│ │ │ │ ├── AppModule.kt
│ │ │ │ ├── UserRepositoryModule.kt
│ │ │ │ └── WorkoutRepositoryModule.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ └── viewmodel
│ │ │ │ └── UserViewModel.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── ken
│ │ │ └── projects
│ │ │ └── infit
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── ken
│ │ └── projects
│ │ └── infit
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── .idea
├── .gitignore
├── compiler.xml
├── vcs.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
└── misc.xml
├── inFit_banner_1.png
├── inFit_banner_2.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
├── gradle.properties
├── README.md
├── gradlew.bat
├── gradlew
└── LICENSE
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/inFit_banner_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/inFit_banner_1.png
--------------------------------------------------------------------------------
/inFit_banner_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/inFit_banner_2.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/font/nunito_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/font/nunito_bold.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/nunito_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/font/nunito_italic.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/nunito_light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/font/nunito_light.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/outfit_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/font/outfit_bold.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/outfit_light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/font/outfit_light.ttf
--------------------------------------------------------------------------------
/app/src/main/ic_infit_logo-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/ic_infit_logo-playstore.png
--------------------------------------------------------------------------------
/app/src/main/res/font/nunito_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/font/nunito_regular.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/outfit_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/font/outfit_regular.ttf
--------------------------------------------------------------------------------
/app/src/main/res/drawable/abs_anatomy_nb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/drawable/abs_anatomy_nb.png
--------------------------------------------------------------------------------
/app/src/main/res/font/nunito_semi_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/font/nunito_semi_bold.ttf
--------------------------------------------------------------------------------
/app/src/main/ic_infit_logo_full-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/ic_infit_logo_full-playstore.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/chest_anatomy_nb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/drawable/chest_anatomy_nb.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/lats_anatomy_nb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/drawable/lats_anatomy_nb.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/login_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/drawable/login_background.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/traps_anatomy_nb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/drawable/traps_anatomy_nb.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_infit_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-hdpi/ic_infit_logo.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_infit_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-mdpi/ic_infit_logo.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/drawable/biceps_anatomy_nb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/drawable/biceps_anatomy_nb.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/deltoids_anatomy_nb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/drawable/deltoids_anatomy_nb.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/register_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/drawable/register_background.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/stockimagebarbell.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/drawable/stockimagebarbell.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/triceps_anatomy_nb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/drawable/triceps_anatomy_nb.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_infit_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xhdpi/ic_infit_logo.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_infit_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xxhdpi/ic_infit_logo.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_infit_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_infit_logo.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/drawable/hamstrings_anatomy_nb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/drawable/hamstrings_anatomy_nb.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_infit_logo_full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-hdpi/ic_infit_logo_full.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_infit_logo_full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-mdpi/ic_infit_logo_full.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/drawable/calf_muscles_anatomy_nb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/drawable/calf_muscles_anatomy_nb.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/home_screen_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/drawable/home_screen_background.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/stats_screen_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/drawable/stats_screen_background.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/workout_card_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/drawable/workout_card_background.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_infit_logo_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-hdpi/ic_infit_logo_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_infit_logo_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-mdpi/ic_infit_logo_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_infit_logo_full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xhdpi/ic_infit_logo_full.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_infit_logo_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xhdpi/ic_infit_logo_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_infit_logo_full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xxhdpi/ic_infit_logo_full.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_infit_logo_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xxhdpi/ic_infit_logo_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_infit_logo_full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_infit_logo_full.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_infit_logo_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_infit_logo_round.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/quadriceps_muscles_anatomy_nb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/drawable/quadriceps_muscles_anatomy_nb.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_infit_logo_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-hdpi/ic_infit_logo_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_infit_logo_full_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-hdpi/ic_infit_logo_full_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_infit_logo_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-mdpi/ic_infit_logo_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_infit_logo_full_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-mdpi/ic_infit_logo_full_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_infit_logo_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xhdpi/ic_infit_logo_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_infit_logo_full_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xhdpi/ic_infit_logo_full_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_infit_logo_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xxhdpi/ic_infit_logo_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_infit_logo_full_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xxhdpi/ic_infit_logo_full_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_infit_logo_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_infit_logo_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_infit_logo_full_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_infit_logo_full_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_infit_logo_full_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-hdpi/ic_infit_logo_full_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_infit_logo_full_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-mdpi/ic_infit_logo_full_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_infit_logo_full_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xhdpi/ic_infit_logo_full_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_infit_logo_full_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xxhdpi/ic_infit_logo_full_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_infit_logo_full_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KenAli77/InFit/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_infit_logo_full_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_infit_logo_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_infit_logo_full_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3DDC84
4 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/data/models/User.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.data.models
2 |
3 |
4 | data class User(
5 | val userEmail:String? = null,
6 | val userName:String? = null
7 | )
8 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/data/models/ExerciseVolume.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.data.models
2 |
3 | data class ExerciseVolume(
4 | val set:Int? = 0,
5 | val reps:Int? = 10,
6 | val weight:Double? = 0.00,
7 | )
8 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/InfitApp.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.AndroidEntryPoint
5 | import dagger.hilt.android.HiltAndroidApp
6 |
7 | @HiltAndroidApp
8 | class InfitApp:Application()
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Jul 17 11:14:31 GST 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_infit_logo.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_infit_logo_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/data/models/states/WorkoutPlanState.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.data.models.states
2 |
3 | import ken.projects.infit.data.models.WorkoutPlan
4 |
5 | data class WorkoutPlanState(
6 | val workoutPlan:WorkoutPlan? = null,
7 | val loading:Boolean = false,
8 | val error:String? = null
9 | )
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_infit_logo_full.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_infit_logo_full_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /google-services.json
13 | /captures
14 | .externalNativeBuild
15 | .cxx
16 | local.properties
17 | /app/google-services.json
18 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/data/models/states/AuthState.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.data.models.states
2 |
3 | import com.google.firebase.auth.AuthResult
4 |
5 | data class AuthState(
6 | val data: AuthResult? = null,
7 | val loading: Boolean = false,
8 | val success:Boolean = false,
9 | val error: String? = null,
10 | val uid:String? = null
11 | )
12 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/util/Resource.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.util
2 |
3 | sealed class Resource(val data: T? = null, val message: String? = null) {
4 | class Success(data: T?): Resource(data)
5 | class Loading(data: T? = null) : Resource(data)
6 | class Error(message: String, data: T? = null): Resource(data, message)
7 | }
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_level.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val Shapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(4.dp),
10 | large = RoundedCornerShape(0.dp)
11 | )
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "InFit"
16 | include ':app'
17 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/data/models/Workout.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.data.models
2 |
3 |
4 | import java.time.DayOfWeek
5 |
6 | data class Workout(
7 | val name: String? = null,
8 | val targetMuscleGroups: ArrayList? = null,
9 | val duration: Int? = null,
10 | val dayOfWeek: DayOfWeek? = null,
11 | val exerciseItems: ArrayList? = null,
12 | )
13 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/data/models/WorkoutPlan.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.data.models
2 |
3 | import ken.projects.infit.util.DifficultyLevels
4 | import java.time.DayOfWeek
5 |
6 | data class WorkoutPlan (
7 | val name:String? = null,
8 | val workouts:ArrayList? = null,
9 | val difficulty: DifficultyLevels.Difficulty? = null,
10 | val duration:Int? = null,
11 | )
12 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 |
6 | val lightBlue = Color(0xFF183446)
7 | val holoGreen = Color(0xFF9ff39f)
8 | val holoRed = Color(0xFFFF4444)
9 | val darkBlue = Color(0xFF002633)
10 | val white = Color(0xFFFFFFFF)
11 | val blue = Color(0xFF2400FF)
12 | val veryDarkBlue = Color(0xFF001016)
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_dot.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/test/java/ken/projects/infit/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 | #002633
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_remove.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/domain/UserRepository.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.domain
2 |
3 | import com.google.firebase.auth.AuthResult
4 | import ken.projects.infit.util.Resource
5 |
6 | interface UserRepository {
7 |
8 | suspend fun createNewUser(
9 | userName: String,
10 | userEmailAddress: String,
11 | userLoginPassword: String
12 | ): Resource
13 |
14 |
15 | suspend fun loginUser(email: String, password: String): Resource
16 |
17 | suspend fun logOutUser()
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/di/AppModule.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.di
2 |
3 |
4 | import com.google.firebase.auth.FirebaseAuth
5 | import dagger.Module
6 | import dagger.Provides
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 | import javax.inject.Singleton
10 |
11 | @Module
12 | @InstallIn(SingletonComponent::class)
13 | object AppModule {
14 |
15 | @Provides
16 | @Singleton
17 | fun provideFirebaseAuth(): FirebaseAuth {
18 | return FirebaseAuth.getInstance()
19 | }
20 |
21 |
22 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_skill_level_advanced_icon.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/util/ext.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.util
2 |
3 | import kotlin.math.roundToInt
4 |
5 | fun getTimeStringFromDouble(timeElapsed: Double): String {
6 |
7 | val resultInt = timeElapsed.roundToInt()
8 | val hours = resultInt % 86400 / 3600
9 | val minutes = resultInt % 86400 % 3600 / 60
10 | val seconds = resultInt % 86400 % 3600 % 60
11 |
12 | return makeTimeString(hours, minutes, seconds)
13 | }
14 |
15 | fun makeTimeString(hours: Int, min: Int, sec: Int): String =
16 | String.format("%02d:%02d:%02d", hours, min, sec)
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_skill_level_intermediate_icon.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_skill_level_basic_icon.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/di/UserRepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.di
2 |
3 | import dagger.Binds
4 | import dagger.Module
5 | import dagger.hilt.InstallIn
6 | import dagger.hilt.components.SingletonComponent
7 | import ken.projects.infit.data.repository.UserRepositoryImpl
8 | import ken.projects.infit.domain.UserRepository
9 | import javax.inject.Singleton
10 |
11 | @Module
12 | @InstallIn(SingletonComponent::class)
13 | abstract class UserRepositoryModule {
14 |
15 | @Binds
16 | @Singleton
17 | abstract fun bindUserRepository(userRepositoryImpl: UserRepositoryImpl):UserRepository
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/di/WorkoutRepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.di
2 |
3 | import dagger.Binds
4 | import dagger.Module
5 | import dagger.hilt.InstallIn
6 | import dagger.hilt.components.SingletonComponent
7 | import ken.projects.infit.data.repository.WorkoutRepositoryImpl
8 | import ken.projects.infit.domain.WorkoutRepository
9 | import javax.inject.Singleton
10 |
11 | @Module
12 | @InstallIn(SingletonComponent::class)
13 | abstract class WorkoutRepositoryModule {
14 |
15 | @Binds
16 | @Singleton
17 | abstract fun bindWorkoutRepository (workoutRepositoryImpl: WorkoutRepositoryImpl): WorkoutRepository
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/composables/profile/ProfileScreen.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.composables.profile
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.material.Surface
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import ken.projects.infit.ui.composables.home.Title
8 | import ken.projects.infit.ui.theme.darkBlue
9 |
10 | @Composable
11 | fun ProfileScreen() {
12 |
13 | Surface(modifier = Modifier.fillMaxSize(), color = darkBlue) {
14 |
15 | Title(text = "coming soon", modifier = Modifier.fillMaxSize())
16 |
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_list.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/data/models/ExerciseHistoryItem.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.data.models
2 |
3 |
4 | data class ExerciseHistoryItem(
5 | val exercise: Exercise? = null,
6 | val date: Long? = System.currentTimeMillis(),
7 | val exerciseVolume: List? = null,
8 | val maxWeight: Double? = exerciseVolume?.let {
9 | it.maxOf {
10 | it.weight!!
11 | }
12 | },
13 | val minWeight: Double? = exerciseVolume?.let {
14 | it.minOf {
15 | it.weight!!
16 | }
17 | },
18 | val totalSets: Int? = exerciseVolume?.count(),
19 | val totalReps: Int? = exerciseVolume?.let {
20 | it.sumOf { it.reps!! }
21 | }
22 | )
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/util/DifficultyLevels.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.util
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.annotation.StringRes
5 | import ken.projects.infit.R
6 |
7 | sealed class DifficultyLevels() {
8 |
9 | data class Difficulty(
10 | @StringRes
11 | val difficulty: Int? = null,
12 | @DrawableRes
13 | val icon: Int? = null
14 | )
15 |
16 | companion object {
17 | val Beginner = Difficulty(R.string.beginner, R.drawable.ic_skill_level_basic_icon)
18 | val Intermediate = Difficulty(R.string.intermediate, R.drawable.ic_skill_level_intermediate_icon)
19 | val Advanced = Difficulty(R.string.advanced, R.drawable.ic_skill_level_advanced_icon)
20 |
21 | }
22 |
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/ken/projects/infit/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("ken.projects.infit", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_timer.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.theme
2 |
3 | import android.annotation.SuppressLint
4 | import androidx.compose.foundation.isSystemInDarkTheme
5 | import androidx.compose.material.MaterialTheme
6 | import androidx.compose.material.darkColors
7 | import androidx.compose.material.lightColors
8 | import androidx.compose.runtime.Composable
9 |
10 | private val DarkColorPalette = darkColors(
11 | primary = darkBlue,
12 | primaryVariant = lightBlue,
13 | secondary = holoGreen
14 | )
15 |
16 | @SuppressLint("ConflictingOnColor")
17 | private val LightColorPalette = lightColors(
18 | primary = darkBlue,
19 | primaryVariant = lightBlue,
20 | secondary = holoGreen,
21 |
22 |
23 | )
24 |
25 | @Composable
26 | fun InFitTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
27 | val colors = if (darkTheme) {
28 | DarkColorPalette
29 | } else {
30 | LightColorPalette
31 | }
32 |
33 | MaterialTheme(
34 | colors = colors,
35 | typography = Typography,
36 | shapes = Shapes,
37 | content = content
38 | )
39 | }
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/navigation/LoginNavGraph.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.navigation
2 |
3 | import androidx.compose.material.ScaffoldState
4 | import androidx.compose.runtime.MutableState
5 | import androidx.navigation.NavGraphBuilder
6 | import androidx.navigation.NavHostController
7 | import androidx.navigation.compose.composable
8 | import androidx.navigation.navigation
9 | import ken.projects.infit.ui.composables.login.LoginScreen
10 | import ken.projects.infit.ui.composables.signup.SignUpScreen
11 | import ken.projects.infit.viewmodel.UserViewModel
12 |
13 | fun NavGraphBuilder.loginNavGraph(
14 | navController: NavHostController,
15 | bottomBarState: MutableState,
16 | userViewModel: UserViewModel,
17 | scaffoldState: ScaffoldState
18 | ) {
19 |
20 | navigation(startDestination = Screens.Login.route, route = LOGIN_ROUTE)
21 | {
22 | composable(route = Screens.Login.route){
23 | LoginScreen(navController,userViewModel,scaffoldState)
24 | bottomBarState.value = false
25 | }
26 | composable(route = Screens.Signup.route){
27 | SignUpScreen(navController,userViewModel,scaffoldState)
28 | bottomBarState.value = false
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
16 |
18 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.theme
2 |
3 | import androidx.compose.material.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.Font
6 | import androidx.compose.ui.text.font.FontFamily
7 | import androidx.compose.ui.text.font.FontWeight
8 | import androidx.compose.ui.unit.sp
9 | import ken.projects.infit.R
10 |
11 | // Set of Material typography styles to start with
12 | val outfit = FontFamily(
13 | Font(R.font.outfit_regular),
14 | Font(R.font.outfit_bold, FontWeight.Bold),
15 | Font(R.font.outfit_light, FontWeight.Thin)
16 | )
17 |
18 | val nunito = FontFamily(
19 | Font(R.font.nunito_regular),
20 | Font(R.font.nunito_bold, FontWeight.Bold),
21 | Font(R.font.nunito_light, FontWeight.Thin)
22 | )
23 |
24 | val Typography = Typography(
25 | body1 = TextStyle(
26 | fontFamily = nunito,
27 | fontWeight = FontWeight.Normal,
28 | fontSize = 16.sp
29 | ),
30 | defaultFontFamily = outfit,
31 |
32 | /* Other default text styles to override
33 | button = TextStyle(
34 | fontFamily = FontFamily.Default,
35 | fontWeight = FontWeight.W500,
36 | fontSize = 14.sp
37 | ),
38 | caption = TextStyle(
39 | fontFamily = FontFamily.Default,
40 | fontWeight = FontWeight.Normal,
41 | fontSize = 12.sp
42 | )
43 | */
44 | )
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # 👷🚧 inFit App 🚧👷
4 |
5 | An app to log your workouts in the gym and keep track of your progress.
6 | Built entirely in Kotlin using following latest architecture components.
7 |
8 |
9 | ## Build 🏗️
10 |
11 | InFit is built following MVVM design pattern guidelines and using the following components:
12 |
13 | ### Front End
14 |
15 | - [Kotlin]() - Android's official programming language.
16 | - [Jetpack Compose](https://developer.android.com/jetpack/compose) - Android’s modern toolkit for building native UI.
17 | - [Coroutines](https://developer.android.com/kotlin/coroutines) - Design pattern for asynchronous code execution.
18 | - [Dagger Hilt](https://developer.android.com/training/dependency-injection/hilt) - Dependency injection library for Android.
19 | - [Android Architecture Components](https://developer.android.com/topic/architecture) - Collection of libraries that contain the lifecycle-aware components.
20 |
21 | ### Back End
22 |
23 | - [Cloud Firestore](https://firebase.google.com/docs/firestore) - Cloud Firestore is a cloud-hosted, NoSQL database that your Apple, Android, and web apps can access directly via native SDKs.
24 | - [Firebase Authentication](https://firebase.google.com/products/auth) - Firebase Authentication provides backend services and SDKs to authenticate users to your app.
25 |
26 | 
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_exercises.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/domain/WorkoutRepository.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.domain
2 |
3 | import androidx.lifecycle.MutableLiveData
4 | import com.google.firebase.firestore.DocumentSnapshot
5 | import com.google.firebase.firestore.QuerySnapshot
6 | import ken.projects.infit.data.models.*
7 | import ken.projects.infit.util.Resource
8 |
9 | interface WorkoutRepository {
10 |
11 |
12 | suspend fun addWorkoutPlan(
13 | workoutPlan: WorkoutPlan,
14 | workouts: ArrayList,
15 | uid:String
16 | ): Resource
17 |
18 | suspend fun getWorkoutPlan(uid:String): Resource
19 |
20 | suspend fun addWorkouts(uid:String): Resource
21 |
22 | suspend fun getWorkouts(uid:String): Resource
23 |
24 | suspend fun getExercises(uid:String):MutableLiveData>
25 |
26 | suspend fun getUser(uid:String): Resource
27 |
28 | suspend fun addExerciseToWorkout(exerciseItem: ExerciseItem, workoutId:String,uid:String): Resource
29 | suspend fun updateWorkout(workoutId: String,
30 | exerciseItem: ExerciseItem? = null,
31 | volume: ExerciseVolume? = null,
32 | workout: Workout,
33 | uid:String
34 | ): Resource
35 |
36 | suspend fun addNewExercise(exercise: Exercise,uid:String):Resource
37 |
38 | suspend fun addExerciseHistory(exercise:ExerciseHistoryItem,uid:String)
39 |
40 | suspend fun getHistoryData(uid:String):Resource
41 |
42 | suspend fun getHistoryDataDetails(exerciseId: String,uid:String): Resource
43 | suspend fun deleteWorkoutPlan(workoutPlanId: String,uid:String)
44 |
45 |
46 |
47 |
48 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/composables/home/EmptyWorkoutPlanView.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.composables.home
2 |
3 | import androidx.compose.foundation.layout.*
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.ui.Alignment
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.res.stringResource
9 | import androidx.compose.ui.tooling.preview.Preview
10 | import androidx.compose.ui.unit.dp
11 | import com.airbnb.lottie.compose.LottieAnimation
12 | import com.airbnb.lottie.compose.LottieCompositionSpec
13 | import com.airbnb.lottie.compose.rememberLottieComposition
14 | import ken.projects.infit.R
15 | import ken.projects.infit.ui.composables.RegularButton
16 |
17 | @Preview
18 | @Composable
19 | fun EmptyWorkoutPlanView(modifier: Modifier = Modifier,user:String="", onClick: () -> Unit = {}) {
20 |
21 | val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.empty_workout_anim))
22 |
23 |
24 | Column(
25 | modifier = modifier.fillMaxSize(),
26 | verticalArrangement = Arrangement.SpaceAround,
27 | horizontalAlignment = Alignment.Start
28 | ) {
29 |
30 | Heading(text = stringResource(R.string.welcome) +" "+user.replaceFirstChar { it.uppercase() })
31 |
32 | SubHeading(text = stringResource(R.string.empty_plan_text))
33 |
34 | LottieAnimation(
35 | composition = composition,
36 | modifier = Modifier
37 | .fillMaxWidth()
38 | .height(250.dp)
39 | ,
40 | iterations = Int.MAX_VALUE
41 | )
42 |
43 |
44 | RegularButton(text = stringResource(R.string.get_started),
45 | modifier = Modifier.align(Alignment.CenterHorizontally),
46 | onClick = onClick)
47 |
48 |
49 | }
50 | }
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/navigation/Screens.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.navigation
2 |
3 | import androidx.annotation.StringRes
4 | import androidx.compose.material.icons.Icons
5 | import androidx.compose.material.icons.rounded.*
6 | import androidx.compose.ui.graphics.vector.ImageVector
7 | import ken.projects.infit.R
8 |
9 | const val ROOT_ROUTE = "root_route"
10 | const val LOGIN_ROUTE = "login_route"
11 | const val MAIN_ROUTE = "main_route"
12 |
13 | sealed class Screens(
14 | val route: String,
15 | @StringRes
16 | val title: Int, val icon: ImageVector? = null
17 | ) {
18 |
19 |
20 | object Home : Screens(route = "home_screen", title = R.string.home, icon = Icons.Rounded.Home)
21 | object Login : Screens(route = "login_screen", title = R.string.login)
22 | object Signup : Screens(route = "signup_screen", title = R.string.signup)
23 | object Workout : Screens(route = "workout_screen", title = R.string.workout)
24 | object Stats :
25 | Screens(route = "stats_screen", title = R.string.stats, icon = Icons.Rounded.Analytics)
26 |
27 | object WorkoutDetails :
28 | Screens(route = "workout_details_screen", title = R.string.workout_details)
29 |
30 | object StatsDetails : Screens(route = "stats_details_screen", title = R.string.stats_details)
31 | object Profile :
32 | Screens(route = "profile_screen", title = R.string.profile, icon = Icons.Rounded.Person)
33 |
34 | object Exercises : Screens(
35 | route = "exercises_screen",
36 | title = R.string.exercises,
37 | icon = Icons.Rounded.FitnessCenter
38 | )
39 |
40 | object ExerciseDetails : Screens(route = "exercises_details_screen", title = R.string.exercise)
41 | object WorkoutPlanSetUp :
42 | Screens(route = "workout_plan_setup_screen", title = R.string.set_up_workout_plan_heading)
43 |
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/data/repository/UserRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.data.repository
2 |
3 | import android.app.Application
4 | import android.util.Log
5 | import com.google.firebase.auth.AuthResult
6 | import com.google.firebase.auth.FirebaseAuth
7 | import com.google.firebase.firestore.ktx.firestore
8 | import com.google.firebase.ktx.Firebase
9 | import ken.projects.infit.data.models.User
10 | import ken.projects.infit.domain.UserRepository
11 | import ken.projects.infit.util.Resource
12 | import kotlinx.coroutines.tasks.await
13 | import javax.inject.Inject
14 |
15 | class UserRepositoryImpl @Inject constructor(
16 | private val app: Application,
17 | private val auth: FirebaseAuth
18 | ) : UserRepository {
19 |
20 | private val fireStoreUserCollection = Firebase.firestore.collection("users")
21 |
22 | val currentUser = auth.currentUser
23 |
24 | override suspend fun createNewUser(
25 | userName: String,
26 | userEmailAddress: String,
27 | userLoginPassword: String
28 | ): Resource {
29 |
30 | return try {
31 |
32 | val registrationResult =
33 | auth.createUserWithEmailAndPassword(userEmailAddress, userLoginPassword)
34 | .await()
35 |
36 | val userId = registrationResult.user?.uid!!
37 | val newUser = User(
38 | userName = userName,
39 | userEmail = userEmailAddress
40 | )
41 | fireStoreUserCollection.document(userId).set(newUser).await()
42 |
43 | Resource.Success(registrationResult)
44 |
45 | } catch (e: Exception) {
46 | e.printStackTrace()
47 | Resource.Error(e.message.toString())
48 | }
49 |
50 |
51 | }
52 |
53 | override suspend fun loginUser(email: String, password: String): Resource {
54 |
55 | return try {
56 | val result = auth.signInWithEmailAndPassword(email, password).await()
57 | Log.e("login", "logged in user ${result.user?.uid}")
58 | Resource.Success(result)
59 | } catch (e: Exception) {
60 | e.printStackTrace()
61 | Resource.Error(e.message.toString())
62 | }
63 |
64 |
65 | }
66 |
67 | override suspend fun logOutUser() {
68 | auth.signOut()
69 | }
70 |
71 |
72 | }
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.content.IntentFilter
7 | import android.os.Bundle
8 | import android.util.Log
9 | import androidx.activity.ComponentActivity
10 | import androidx.activity.compose.setContent
11 | import androidx.activity.viewModels
12 | import androidx.hilt.navigation.compose.hiltViewModel
13 | import androidx.navigation.NavHostController
14 | import androidx.navigation.compose.rememberNavController
15 | import dagger.hilt.android.AndroidEntryPoint
16 | import ken.projects.infit.data.service.WorkoutTimerService
17 | import ken.projects.infit.ui.navigation.RootNavGraph
18 | import ken.projects.infit.ui.theme.InFitTheme
19 | import ken.projects.infit.util.getTimeStringFromDouble
20 | import ken.projects.infit.viewmodel.UserViewModel
21 | import ken.projects.infit.viewmodel.WorkoutViewModel
22 |
23 | @AndroidEntryPoint
24 | class MainActivity : ComponentActivity() {
25 |
26 | lateinit var navController:NavHostController
27 |
28 | private val userViewModel: UserViewModel by viewModels()
29 | private val workoutViewModel: WorkoutViewModel by viewModels()
30 | private var timeElapsed = 0.0
31 | private lateinit var serviceIntent: Intent
32 |
33 |
34 | private val updateTime: BroadcastReceiver = object : BroadcastReceiver() {
35 |
36 | override fun onReceive(context: Context, intent: Intent) {
37 | timeElapsed = intent.getDoubleExtra(WorkoutTimerService.TIME_ELAPSED, 0.0)
38 | workoutViewModel.timerText = getTimeStringFromDouble(timeElapsed)
39 | }
40 |
41 | }
42 |
43 | override fun onCreate(savedInstanceState: Bundle?) {
44 | super.onCreate(savedInstanceState)
45 |
46 | serviceIntent = Intent(this, WorkoutTimerService::class.java)
47 | registerReceiver(
48 | updateTime,
49 | IntentFilter(WorkoutTimerService.TIMER_UPDATED)
50 | )
51 | setContent {
52 |
53 | InFitTheme {
54 |
55 | val workoutViewModel = hiltViewModel()
56 | val userViewModel = hiltViewModel()
57 | navController = rememberNavController()
58 | RootNavGraph(navController,userViewModel,workoutViewModel)
59 |
60 | }
61 | }
62 | }
63 |
64 | override fun onDestroy() {
65 | super.onDestroy()
66 | stopService(serviceIntent)
67 | }
68 | }
69 |
70 |
71 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/navigation/RootNavGraph.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.navigation
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material.Scaffold
6 | import androidx.compose.material.rememberScaffoldState
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.getValue
9 | import androidx.compose.runtime.mutableStateOf
10 | import androidx.compose.runtime.saveable.rememberSaveable
11 | import androidx.compose.ui.Modifier
12 | import androidx.lifecycle.viewmodel.compose.viewModel
13 | import androidx.navigation.NavHostController
14 | import androidx.navigation.compose.NavHost
15 | import androidx.navigation.compose.currentBackStackEntryAsState
16 | import ken.projects.infit.ui.theme.darkBlue
17 | import ken.projects.infit.viewmodel.UserViewModel
18 | import ken.projects.infit.viewmodel.WorkoutViewModel
19 |
20 | @Composable
21 | fun RootNavGraph(
22 | navController: NavHostController,
23 | userViewModel: UserViewModel = viewModel(),
24 | workoutViewModel: WorkoutViewModel = viewModel(),
25 | ) {
26 |
27 | val bottomBarState = rememberSaveable { (mutableStateOf(false)) }
28 | val scaffoldState = rememberScaffoldState()
29 |
30 |
31 | val navBackStackEntry by navController.currentBackStackEntryAsState()
32 |
33 | when (navBackStackEntry?.destination?.route) {
34 | LOGIN_ROUTE -> bottomBarState.value = false
35 | MAIN_ROUTE -> bottomBarState.value = true
36 | }
37 |
38 | Scaffold(
39 | scaffoldState = scaffoldState,
40 | modifier = Modifier.fillMaxSize(),
41 | bottomBar = {
42 | when (bottomBarState.value) {
43 | true -> BottomNavBar(navController = navController, bottomBarState)
44 | false -> {}
45 | }
46 | },
47 | backgroundColor = darkBlue,
48 |
49 | ) {
50 |
51 | NavHost(
52 | navController = navController,
53 | startDestination = LOGIN_ROUTE,
54 | route = ROOT_ROUTE,
55 | modifier = Modifier.padding(it)
56 | )
57 | {
58 |
59 | loginNavGraph(
60 | navController = navController,
61 | bottomBarState,
62 | userViewModel,
63 | scaffoldState
64 | )
65 |
66 | mainNavGraph(
67 | navController = navController,
68 | bottomBarState,
69 | userViewModel,
70 | workoutViewModel,
71 | scaffoldState
72 | )
73 |
74 | }
75 | }
76 |
77 | }
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/util/DefaultWorkoutPlans.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.util
2 |
3 | import ken.projects.infit.data.models.*
4 | import ken.projects.infit.util.DifficultyLevels.Companion.Beginner
5 | import java.time.DayOfWeek
6 |
7 | sealed class DefaultWorkoutPlans(val workoutPlan: WorkoutPlan) {
8 |
9 | object TotalBody : DefaultWorkoutPlans(
10 | workoutPlan = WorkoutPlan(
11 | name = "Total Body",
12 | workouts = arrayListOf(
13 | DayOfWeek.MONDAY,
14 | DayOfWeek.TUESDAY,
15 | DayOfWeek.WEDNESDAY,
16 | DayOfWeek.THURSDAY,
17 | DayOfWeek.FRIDAY,
18 | DayOfWeek.SATURDAY,
19 | DayOfWeek.SUNDAY
20 | ),
21 | difficulty = Beginner,
22 | duration = 45
23 | )
24 | )
25 |
26 | }
27 |
28 | val defaultWorkouts = generateWorkouts()
29 |
30 | fun generateWorkouts(): ArrayList {
31 |
32 | val defaultWorkouts = ArrayList()
33 | val volume = ArrayList()
34 |
35 | for (i in 1..4) {
36 | volume.add(ExerciseVolume(set = i, reps = 15))
37 | }
38 |
39 | val days = listOf(
40 | DayOfWeek.MONDAY,
41 | DayOfWeek.TUESDAY,
42 | DayOfWeek.WEDNESDAY,
43 | DayOfWeek.THURSDAY,
44 | DayOfWeek.FRIDAY,
45 | DayOfWeek.SATURDAY,
46 | DayOfWeek.SUNDAY
47 | )
48 |
49 | val exerciseItems = arrayListOf(
50 | ExerciseItem(
51 | name = "squat",
52 | targetMuscles = arrayListOf(Muscle.Quads, Muscle.Hamstrings),
53 | equipments = equipments[0],
54 | volume = volume
55 | ),
56 | ExerciseItem(
57 | name = "chest press",
58 | targetMuscles = arrayListOf(Muscle.Quads, Muscle.Hamstrings),
59 | equipments = equipments[0],
60 | volume = volume
61 | ),
62 | ExerciseItem(
63 | name = "dips",
64 | targetMuscles = arrayListOf(Muscle.Quads, Muscle.Hamstrings),
65 | equipments = equipments[0],
66 | volume = volume
67 | ),
68 | ExerciseItem(
69 | name = "rows",
70 | targetMuscles = arrayListOf(Muscle.Quads, Muscle.Hamstrings),
71 | equipments = equipments[0],
72 | volume = volume
73 | )
74 |
75 |
76 | )
77 |
78 | for (day in days) {
79 |
80 | defaultWorkouts.add(
81 | Workout(
82 | name = day.name,
83 | dayOfWeek = day,
84 | exerciseItems = exerciseItems
85 | )
86 | )
87 |
88 | }
89 |
90 | return defaultWorkouts
91 |
92 |
93 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'kotlin-kapt'
5 | id 'dagger.hilt.android.plugin'
6 | id 'com.google.gms.google-services'
7 | }
8 |
9 | android {
10 | compileSdk 32
11 |
12 | defaultConfig {
13 | applicationId "ken.projects.infit"
14 | minSdk 26
15 | targetSdk 32
16 | versionCode 1
17 | versionName "1.0"
18 |
19 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
20 | vectorDrawables {
21 | useSupportLibrary true
22 | }
23 | }
24 |
25 | buildTypes {
26 | release {
27 | minifyEnabled true
28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
29 | }
30 | }
31 | compileOptions {
32 | sourceCompatibility JavaVersion.VERSION_1_8
33 | targetCompatibility JavaVersion.VERSION_1_8
34 | }
35 | kotlinOptions {
36 | jvmTarget = '1.8'
37 | }
38 | buildFeatures {
39 | compose true
40 | }
41 | composeOptions {
42 | kotlinCompilerExtensionVersion compose_version
43 | }
44 | packagingOptions {
45 | resources {
46 | excludes += '/META-INF/{AL2.0,LGPL2.1}'
47 | }
48 | }
49 | }
50 |
51 | dependencies {
52 |
53 | def nav_version = "2.5.0"
54 | def compose_version = "1.2.0"
55 |
56 | implementation "androidx.navigation:navigation-compose:$nav_version"
57 |
58 | implementation 'androidx.core:core-ktx:1.8.0'
59 | implementation "androidx.compose.ui:ui:$compose_version"
60 | implementation "androidx.compose.material:material:$compose_version"
61 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
62 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.0'
63 | implementation 'androidx.activity:activity-compose:1.5.0'
64 | implementation "dev.chrisbanes.snapper:snapper:0.2.2"
65 | implementation "androidx.compose.material:material-icons-extended:$compose_version"
66 |
67 | //Coroutines
68 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1'
69 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.1'
70 |
71 | //Dagger - Hilt
72 | implementation "com.google.dagger:hilt-android:2.40.5"
73 | kapt "com.google.dagger:hilt-android-compiler:2.40.5"
74 | implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"
75 | kapt "androidx.hilt:hilt-compiler:1.0.0"
76 | implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'
77 |
78 |
79 | //Firebase
80 | implementation 'com.google.firebase:firebase-auth-ktx'
81 | implementation platform('com.google.firebase:firebase-bom:30.0.1')
82 | implementation 'com.google.firebase:firebase-firestore-ktx:24.2.1'
83 |
84 |
85 | // Lottie Animation
86 | implementation "com.airbnb.android:lottie-compose:5.2.0"
87 |
88 | // Number wheel
89 | implementation "com.chargemap.compose:numberpicker:1.0.3"
90 |
91 | testImplementation 'junit:junit:4.13.2'
92 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
93 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
94 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
95 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
96 | }
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/composables/stats/StatsScreen.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.composables.stats
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.layout.*
6 | import androidx.compose.foundation.lazy.LazyColumn
7 | import androidx.compose.foundation.lazy.items
8 | import androidx.compose.material.Icon
9 | import androidx.compose.material.Surface
10 | import androidx.compose.material.icons.Icons
11 | import androidx.compose.material.icons.rounded.KeyboardArrowRight
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.layout.ContentScale
16 | import androidx.compose.ui.res.painterResource
17 | import androidx.compose.ui.res.stringResource
18 | import androidx.compose.ui.tooling.preview.Preview
19 | import androidx.compose.ui.unit.dp
20 | import androidx.lifecycle.viewmodel.compose.viewModel
21 | import androidx.navigation.NavHostController
22 | import androidx.navigation.compose.rememberNavController
23 | import ken.projects.infit.R
24 | import ken.projects.infit.ui.composables.home.Heading
25 | import ken.projects.infit.ui.composables.home.SubHeading
26 | import ken.projects.infit.ui.navigation.Screens
27 | import ken.projects.infit.ui.theme.holoGreen
28 | import ken.projects.infit.ui.theme.white
29 | import ken.projects.infit.viewmodel.WorkoutViewModel
30 |
31 | @Preview
32 | @Composable
33 | fun StatsScreen(
34 | navController: NavHostController = rememberNavController(),
35 | workoutViewModel: WorkoutViewModel = viewModel()
36 | ) = with(workoutViewModel) {
37 |
38 | getHistoryData()
39 |
40 | Surface(modifier = Modifier.fillMaxSize()) {
41 | Image(
42 | painter = painterResource(id = R.drawable.stats_screen_background),
43 | contentDescription = null,
44 | modifier = Modifier.fillMaxSize(),
45 | contentScale = ContentScale.Crop
46 | )
47 | Column(
48 | horizontalAlignment = Alignment.Start,
49 | verticalArrangement = Arrangement.spacedBy(40.dp, Alignment.CenterVertically),
50 | modifier = Modifier.padding(start = 15.dp, top = 40.dp, end = 15.dp)
51 | ) {
52 | Heading(text = stringResource(R.string.your_stats))
53 | LazyColumn(modifier = Modifier.padding(end = 40.dp, top = 25.dp).fillMaxHeight()) {
54 | items(statsExercises.toList()) { exercise ->
55 | StatsItem(exercise) {
56 | navController.navigate(Screens.StatsDetails.route)
57 | workoutViewModel.currentExerciseId = exercise
58 | }
59 | Spacer(modifier = Modifier.height(20.dp))
60 | }
61 | }
62 |
63 | }
64 |
65 | }
66 |
67 | }
68 |
69 |
70 | @Composable
71 | fun StatsItem(exercise:String,onClick:()->Unit) {
72 | Row(
73 | horizontalArrangement = Arrangement.SpaceBetween,
74 | modifier = Modifier.fillMaxWidth().clickable { onClick() },
75 | verticalAlignment = Alignment.CenterVertically
76 | ) {
77 |
78 | SubHeading(text = exercise, color = white)
79 | Icon(
80 | imageVector = Icons.Rounded.KeyboardArrowRight,
81 | contentDescription = null,
82 | tint = holoGreen,
83 | modifier = Modifier.size(50.dp)
84 | )
85 |
86 |
87 | }
88 | }
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/composables/CommandsDisplay.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.composables
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.shape.CircleShape
6 | import androidx.compose.material.Icon
7 | import androidx.compose.material.Surface
8 | import androidx.compose.material.icons.Icons
9 | import androidx.compose.material.icons.rounded.ArrowBack
10 | import androidx.compose.material.icons.rounded.ArrowForward
11 | import androidx.compose.material.icons.rounded.PlayArrow
12 | import androidx.compose.runtime.*
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.graphics.vector.ImageVector
16 | import androidx.compose.ui.tooling.preview.Preview
17 | import androidx.compose.ui.unit.dp
18 | import ken.projects.infit.ui.theme.darkBlue
19 | import ken.projects.infit.ui.theme.holoGreen
20 |
21 | @Preview
22 | @Composable
23 | fun CommandsDisplay(
24 | modifier: Modifier = Modifier,
25 | iconStart: ImageVector = Icons.Rounded.ArrowBack,
26 | iconStartClick: () -> Unit = {},
27 | iconCenter: ImageVector = Icons.Rounded.PlayArrow,
28 | iconCenterClick: () -> Unit = {},
29 | iconEnd: ImageVector = Icons.Rounded.ArrowForward,
30 | iconEndClick: () -> Unit = {},
31 | isWorkoutDay:Boolean = true
32 |
33 | ) {
34 |
35 | Row(
36 | modifier = modifier.fillMaxWidth(),
37 | horizontalArrangement = Arrangement.SpaceEvenly,
38 | verticalAlignment = Alignment.CenterVertically
39 | ) {
40 |
41 | Surface(
42 | shape = CircleShape, modifier = Modifier
43 | .width(60.dp)
44 | .height(60.dp)
45 | .align(Alignment.Top)
46 | .clickable { iconStartClick() },
47 | elevation = 4.dp,
48 | color = holoGreen
49 | ) {
50 | Icon(
51 | imageVector = iconStart,
52 | contentDescription = null,
53 | tint = darkBlue,
54 | modifier = Modifier.padding(15.dp)
55 | )
56 | }
57 |
58 | if (isWorkoutDay)
59 | {
60 | Surface(
61 | shape = CircleShape,
62 | modifier = Modifier
63 | .width(95.dp)
64 | .height(95.dp)
65 | .clickable { iconCenterClick() },
66 | elevation = 4.dp,
67 | color = holoGreen
68 | ) {
69 |
70 | Column(
71 | horizontalAlignment = Alignment.CenterHorizontally,
72 | verticalArrangement = Arrangement.Center,
73 | ) {
74 |
75 | Icon(
76 | imageVector = iconCenter,
77 | contentDescription = null,
78 | tint = darkBlue,
79 | modifier = Modifier.size(50.dp)
80 | )
81 | }
82 |
83 | }
84 | } else { Spacer(modifier = Modifier.size(95.dp))}
85 |
86 | Surface(
87 | shape = CircleShape, modifier = Modifier
88 | .width(60.dp)
89 | .height(60.dp)
90 | .align(Alignment.Top)
91 | .clickable { iconEndClick() },
92 | elevation = 4.dp,
93 | color = holoGreen
94 | ) {
95 |
96 | Icon(
97 | imageVector = iconEnd,
98 | contentDescription = null,
99 | tint = darkBlue,
100 | modifier = Modifier.padding(15.dp)
101 | )
102 | }
103 |
104 | }
105 |
106 | }
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/data/service/WorkoutTimerService.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.data.service
2 |
3 | import android.app.NotificationChannel
4 | import android.app.NotificationManager
5 | import android.app.PendingIntent
6 | import android.app.Service
7 | import android.content.Context
8 | import android.content.Intent
9 | import android.os.Build
10 | import android.os.IBinder
11 | import androidx.activity.viewModels
12 | import androidx.core.app.NotificationCompat
13 | import androidx.navigation.NavDeepLinkBuilder
14 | import dagger.hilt.android.AndroidEntryPoint
15 | import dagger.hilt.android.scopes.ViewModelScoped
16 | import ken.projects.infit.MainActivity
17 | import ken.projects.infit.R
18 | import ken.projects.infit.domain.WorkoutRepository
19 | import ken.projects.infit.util.getTimeStringFromDouble
20 | import ken.projects.infit.viewmodel.WorkoutViewModel
21 | import java.util.*
22 | import javax.inject.Inject
23 |
24 | @AndroidEntryPoint
25 | class WorkoutTimerService: Service() {
26 |
27 |
28 |
29 | override fun onBind(p0: Intent?): IBinder? = null
30 |
31 | private val timer: Timer = Timer()
32 | private var isTimerRunning = false
33 |
34 | override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
35 | val timeElapsed = intent.getDoubleExtra(TIME_ELAPSED, 0.0)
36 | timer.scheduleAtFixedRate(TimeTask(timeElapsed), 0, 1000)
37 | isTimerRunning = intent.getBooleanExtra(TIMER_RUNNING,false)
38 | return START_NOT_STICKY
39 | }
40 |
41 | private inner class TimeTask (private var timeElapsed: Double) : TimerTask() {
42 | override fun run() {
43 | val intent = Intent(TIMER_UPDATED)
44 | timeElapsed++
45 | startForegroundService(timeElapsed)
46 | intent.putExtra(TIME_ELAPSED, timeElapsed)
47 | intent.putExtra(TIMER_RUNNING,isTimerRunning)
48 | sendBroadcast(intent)
49 | }
50 |
51 | }
52 |
53 | override fun stopService(name: Intent?): Boolean {
54 | isTimerRunning = false
55 | return super.stopService(name)
56 | }
57 |
58 | override fun onDestroy() {
59 | timer.cancel()
60 | isTimerRunning = false
61 | super.onDestroy()
62 | }
63 |
64 | private fun startForegroundService(timeElapsed: Double) {
65 | val notificationManager =
66 | getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
67 |
68 | createNotificationChannel(notificationManager)
69 |
70 | val time = getTimeStringFromDouble(timeElapsed)
71 |
72 | val notificationBuilder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
73 | .setAutoCancel(false)
74 | .setOngoing(true)
75 | .setContentTitle("ongoing workout")
76 | .setContentText(time)
77 | .setSmallIcon(R.drawable.ic_timer)
78 | // .setContentIntent(getMainActivityPendingIntent())
79 |
80 | startForeground(NOTIFICATION_ID, notificationBuilder.build())
81 | }
82 |
83 | private fun createNotificationChannel(notificationManager: NotificationManager) {
84 | val channel = NotificationChannel(
85 | NOTIFICATION_CHANNEL_ID,
86 | NOTIFICATION_CHANNEL_NAME,
87 | NotificationManager.IMPORTANCE_LOW
88 | )
89 | notificationManager.createNotificationChannel(channel)
90 | }
91 |
92 | private fun getMainActivityPendingIntent() =
93 | PendingIntent.getActivity(
94 | applicationContext,
95 | 0,
96 | Intent(this, MainActivity::class.java)
97 | .addCategory(Intent.CATEGORY_LAUNCHER)
98 | .setPackage(null)
99 | .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED )
100 | ,
101 | PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
102 |
103 | )
104 |
105 |
106 | companion object {
107 |
108 | const val TIMER_UPDATED = "timerUpdated"
109 | const val TIMER_RUNNING = "isTimerRunning"
110 | const val TIME_ELAPSED = "timeElapsed"
111 | const val NOTIFICATION_CHANNEL_ID = "notification_channel"
112 | const val NOTIFICATION_ID = 1
113 | const val NOTIFICATION_CHANNEL_NAME = "notification"
114 | }
115 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | InFit
3 | Delete Workout Plan?
4 | All your saved data will be lost
5 | confirm
6 | dismiss
7 | close
8 | Your schedule
9 | Hi
10 | password
11 | email
12 | Login
13 | Don\'t have an account yet?
14 | Sign Up
15 | Exercises
16 | chest
17 | shoulders
18 | biceps
19 | triceps
20 | upper back
21 | lats
22 | calves
23 | hamstrings
24 | quads
25 | You haven\'t set a workout plan yet
26 | Welcome
27 | get started
28 | take a rest.
29 | No sessions scheduled
30 | Log
31 | Sets:
32 | Min weight:
33 | Reps:
34 | Max weight:
35 | Add new exercise
36 | Exercise
37 | equipments
38 | add
39 | please enter the exercise name
40 | Signup
41 | enter your name
42 | enter email
43 | password
44 | confirm password
45 | continue
46 | Your stats
47 | Sets
48 | Kg
49 | Reps
50 | edit
51 | Add an exercise
52 | Set up a workout plan
53 | Choose a name
54 | Select training days
55 | Difficulty level
56 | Duration
57 | min
58 | save
59 | edit set
60 | please enter valid data
61 | workout ongoing
62 | you must start workout to edit it
63 | Home
64 | Workout
65 | Stats
66 | Workout Details
67 | Stats Details
68 | Profile
69 | Beginner
70 | Intermediate
71 | Advanced
72 | Barbell
73 | Kettle Bell
74 | Body Weight
75 | Resistance Bands
76 | Machine
77 | Smith Machine
78 | Rope
79 | Cable
80 | Dumbbells
81 | equipment
82 | Logout
83 | are you sure you want to logout?
84 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/data/models/ExerciseItem.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.data.models
2 |
3 | import androidx.annotation.StringRes
4 | import ken.projects.infit.R
5 |
6 | data class ExerciseItem(
7 | val exercise: Exercise? = null,
8 | val name: String? = null,
9 | val targetMuscles: ArrayList? = null,
10 | val equipments: Equipment? = null,
11 | val volume: ArrayList? = null,
12 | val sets: Int? = volume?.count()
13 | )
14 |
15 | data class Exercise(
16 | val name: String? = null,
17 | val equipments: Equipments? = null,
18 | val targetMuscles: List? = null
19 | )
20 |
21 | data class Equipment(
22 | @StringRes
23 | val name: Int? = null,
24 | val equipments: Equipments? = null
25 | )
26 |
27 |
28 | enum class Muscle {
29 | Biceps,
30 | Chest,
31 | Quads,
32 | Hamstrings,
33 | Triceps,
34 | Traps,
35 | Glutes,
36 | Calves,
37 | AnteriorDeltoids,
38 | PosteriorDeltoids,
39 | LateralDeltoids,
40 | Abs,
41 | Lats,
42 | Rhomboids,
43 | LowerBack,
44 | }
45 |
46 | enum class Equipments {
47 | Dumbbells,
48 | Barbell,
49 | KettleBell,
50 | BodyWeight,
51 | ResistanceBands,
52 | Machine,
53 | SmithMachine,
54 | Rope,
55 | Cable
56 | }
57 |
58 | val exercises = ArrayList()
59 |
60 | fun exercises(): List {
61 |
62 | val squat = Exercise("Squat", Equipments.Barbell, arrayListOf(Muscle.Quads, Muscle.Hamstrings))
63 | val benchPress =
64 | Exercise("Bench Press", Equipments.Barbell, arrayListOf(Muscle.Chest, Muscle.Triceps))
65 | val deadlift =
66 | Exercise("Deadlift", Equipments.Barbell, arrayListOf(Muscle.Hamstrings, Muscle.LowerBack))
67 | val shrugs = Exercise("Shrugs", Equipments.Dumbbells, arrayListOf(Muscle.Traps))
68 | val bicepCurl = Exercise("Bicep Curl", Equipments.Barbell, arrayListOf(Muscle.Biceps))
69 | val tricepsExtension =
70 | Exercise("Triceps Extension", Equipments.Rope, arrayListOf(Muscle.Triceps))
71 | val narrowGripPress =
72 | Exercise("Narrow Grip Press", Equipments.Barbell, arrayListOf(Muscle.Triceps))
73 | val lateralRaise =
74 | Exercise("Lateral Raise", Equipments.Dumbbells, arrayListOf(Muscle.LateralDeltoids))
75 | val legCurl = Exercise("Leg Curl", Equipments.Machine, arrayListOf(Muscle.Hamstrings))
76 | val calveRaise = Exercise("Calf Raise", Equipments.Machine, arrayListOf(Muscle.Calves))
77 | val legPress = Exercise("Leg Press", Equipments.Machine, arrayListOf(Muscle.Quads))
78 | val overheadPress = Exercise(
79 | "Overhead Press",
80 | Equipments.Barbell,
81 | arrayListOf(Muscle.LateralDeltoids, Muscle.AnteriorDeltoids)
82 | )
83 |
84 | exercises.addAll(
85 | arrayListOf(
86 | squat,
87 | benchPress,
88 | deadlift,
89 | shrugs,
90 | bicepCurl,
91 | tricepsExtension,
92 | narrowGripPress,
93 | lateralRaise,
94 | legCurl,
95 | calveRaise,
96 | legPress,
97 | overheadPress
98 |
99 | )
100 | )
101 |
102 | return exercises
103 | }
104 |
105 | val equipments = ArrayList()
106 |
107 | fun equipments(): List {
108 |
109 | val dumbbells = Equipment(name = R.string.dumbbells, equipments = Equipments.Dumbbells)
110 | val barbell = Equipment(name = R.string.barbell, equipments = Equipments.Barbell)
111 | val kettleBell = Equipment(name = R.string.kettleBell, equipments = Equipments.KettleBell)
112 | val bodyWeight = Equipment(name = R.string.bodyWeight, equipments = Equipments.BodyWeight)
113 | val resistanceBands =
114 | Equipment(name = R.string.resistanceBands, equipments = Equipments.ResistanceBands)
115 | val machine = Equipment(name = R.string.machine, equipments = Equipments.Machine)
116 | val smithMachine = Equipment(name = R.string.smithMachine, equipments = Equipments.SmithMachine)
117 | val rope = Equipment(name = R.string.rope, equipments = Equipments.Rope)
118 | val cable = Equipment(name = R.string.cable, equipments = Equipments.Cable)
119 |
120 | equipments.addAll(
121 | arrayListOf(
122 | dumbbells,
123 | barbell,
124 | kettleBell,
125 | bodyWeight,
126 | resistanceBands,
127 | machine,
128 | smithMachine,
129 | rope,
130 | cable
131 | )
132 | )
133 |
134 | return equipments
135 | }
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/viewmodel/UserViewModel.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.viewmodel
2 |
3 | import android.app.Application
4 | import android.util.Log
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.setValue
8 | import androidx.lifecycle.ViewModel
9 | import androidx.lifecycle.viewModelScope
10 | import dagger.hilt.android.lifecycle.HiltViewModel
11 | import ken.projects.infit.data.models.User
12 | import ken.projects.infit.data.models.states.AuthState
13 | import ken.projects.infit.domain.UserRepository
14 | import ken.projects.infit.util.Resource
15 | import kotlinx.coroutines.launch
16 | import javax.inject.Inject
17 |
18 |
19 | @HiltViewModel
20 | class UserViewModel @Inject constructor(
21 | private val app: Application,
22 | private val repository: UserRepository
23 | ) : ViewModel() {
24 |
25 | var signUpState by mutableStateOf(AuthState())
26 | private set
27 |
28 | var signInState by mutableStateOf(AuthState())
29 | private set
30 |
31 | var user by mutableStateOf(User())
32 | private set
33 |
34 | fun signUpUser(
35 | userName: String,
36 | userEmail: String,
37 | userPassword: String,
38 | confirmPassword: String
39 | ) =
40 | viewModelScope.launch {
41 |
42 | val arePasswordMatching = userPassword == confirmPassword
43 |
44 | if (arePasswordMatching) {
45 |
46 | signUpState = signUpState.copy(
47 | loading = true,
48 | error = null,
49 | success = false
50 | )
51 |
52 | val result = repository.createNewUser(
53 | userName = userName,
54 | userEmailAddress = userEmail,
55 | userLoginPassword = userPassword
56 | )
57 |
58 | when (result) {
59 | is Resource.Success -> {
60 | signUpState = signUpState.copy(
61 | data = result.data,
62 | loading = false,
63 | success = true,
64 | error = null
65 | )
66 | }
67 |
68 | is Resource.Error -> {
69 | signUpState = signUpState.copy(
70 | data = null,
71 | loading = false,
72 | success = false,
73 | error = result.message.toString()
74 | )
75 | }
76 |
77 | is Resource.Loading -> {
78 | signUpState = signUpState.copy(
79 | data = null,
80 | loading = true,
81 | success = false,
82 | error = null
83 | )
84 | }
85 | }
86 | } else {
87 | signUpState = signUpState.copy(
88 | error = "passwords are not matching"
89 | )
90 | }
91 |
92 | }
93 |
94 | fun signInUser(userEmail: String, userPassword: String) =
95 |
96 | viewModelScope.launch {
97 |
98 | signInState = signInState.copy(
99 | loading = true
100 | )
101 |
102 | val result = repository.loginUser(email = userEmail, password = userPassword)
103 |
104 | signInState = when (result) {
105 | is Resource.Success -> {
106 | signInState.copy(
107 | loading = false,
108 | success = true,
109 | uid = result.data?.user?.uid
110 | )
111 |
112 | }
113 |
114 | is Resource.Loading -> {
115 | signInState.copy(
116 | loading = true,
117 | success = false,
118 | )
119 | }
120 |
121 | is Resource.Error -> {
122 | signInState.copy(
123 | loading = false,
124 | success = false,
125 | error = result.message
126 | )
127 | }
128 | }
129 | }
130 |
131 | fun logOut() = viewModelScope.launch {
132 |
133 | repository.logOutUser()
134 |
135 | signInState = AuthState()
136 | signUpState = AuthState()
137 |
138 | }
139 |
140 |
141 | }
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/composables/workout/WorkoutDetailItems.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.composables.workout
2 |
3 | import androidx.compose.foundation.layout.*
4 | import androidx.compose.foundation.lazy.LazyColumn
5 | import androidx.compose.foundation.lazy.itemsIndexed
6 | import androidx.compose.material.*
7 | import androidx.compose.runtime.*
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.graphics.RectangleShape
12 | import androidx.compose.ui.res.stringResource
13 | import androidx.compose.ui.unit.dp
14 | import ken.projects.infit.R
15 | import ken.projects.infit.data.models.ExerciseItem
16 | import ken.projects.infit.ui.composables.home.Heading
17 | import ken.projects.infit.ui.composables.home.Title
18 | import ken.projects.infit.ui.theme.veryDarkBlue
19 | import ken.projects.infit.viewmodel.WorkoutViewModel
20 |
21 | @OptIn(ExperimentalMaterialApi::class)
22 | @Composable
23 | fun ExerciseItemsDisplay(
24 | modifier: Modifier = Modifier,
25 | workoutViewModel: WorkoutViewModel
26 | ) {
27 |
28 | val exerciseList =
29 | workoutViewModel.workoutState.exerciseItems
30 |
31 | LazyColumn(modifier = modifier) {
32 |
33 | exerciseList?.let { exercises ->
34 |
35 | itemsIndexed(
36 | items = exercises,
37 | key = {index,item -> item.hashCode()+index+exercises.indexOf(item)}
38 | ) { index, data ->
39 | val dismissState = rememberDismissState()
40 |
41 | if (dismissState.targetValue == DismissValue.DismissedToEnd)
42 | workoutViewModel.removeExercise(data)
43 | workoutViewModel.getWorkouts()
44 |
45 | SwipeToDismiss(
46 | state = dismissState,
47 | directions = setOf(DismissDirection.StartToEnd),
48 | dismissThresholds = { direction ->
49 | FractionalThreshold(if (direction == DismissDirection.StartToEnd) 0.25f else 0.5f)
50 | },
51 | background = {}
52 | ) {
53 |
54 | ExerciseItem(data)
55 |
56 | }
57 |
58 | }
59 |
60 | }
61 |
62 | }
63 |
64 |
65 | }
66 |
67 |
68 | @Composable
69 | fun ExerciseItem(data: ExerciseItem) {
70 |
71 | val exerciseName by remember { mutableStateOf(data.name) }
72 | val sets by remember { mutableStateOf(data.sets) }
73 | val equipment by remember { mutableStateOf(data.equipments) }
74 |
75 | Surface(
76 | color = veryDarkBlue,
77 | shape = RectangleShape,
78 |
79 | ) {
80 | Row(
81 | verticalAlignment = Alignment.CenterVertically,
82 | modifier = Modifier.padding(horizontal = 15.dp, vertical = 20.dp)
83 | ) {
84 |
85 | Column(
86 | modifier = Modifier
87 | .weight(0.95f)
88 | .padding(end = 30.dp)
89 | ) {
90 |
91 |
92 | Row(
93 | horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier
94 | .fillMaxWidth()
95 |
96 | ) {
97 | Heading(text = exerciseName.toString().replaceFirstChar { it.uppercase() })
98 | Heading(text = sets.toString())
99 | }
100 |
101 | Row(
102 | horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier
103 | .fillMaxWidth()
104 | ) {
105 | Title(text = stringResource(id = equipment?.name!!))
106 | Title(text = stringResource(id = R.string.sets))
107 | }
108 |
109 | }
110 |
111 | Column(
112 | modifier = Modifier
113 | .weight(0.1f)
114 | .size(50.dp),
115 | verticalArrangement = Arrangement.Center,
116 | horizontalAlignment = Alignment.CenterHorizontally
117 | ) {
118 |
119 | /* TODO: IMPLEMENT EDIT LOGIC
120 | Icon(
121 | imageVector = Icons.Rounded.Edit,
122 | contentDescription = stringResource(R.string.edit),
123 | tint = holoGreen,
124 | modifier = Modifier.size(50.dp)
125 |
126 | )
127 | */
128 | }
129 |
130 |
131 | }
132 |
133 | Divider(
134 | modifier = Modifier
135 | .height(1.dp)
136 | .fillMaxWidth(), color = Color.Gray.copy(0.1f)
137 | )
138 |
139 | }
140 |
141 |
142 | }
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/composables/Buttons.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.composables
2 |
3 | import androidx.compose.animation.*
4 | import androidx.compose.foundation.BorderStroke
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.border
7 | import androidx.compose.foundation.clickable
8 | import androidx.compose.foundation.layout.*
9 | import androidx.compose.foundation.shape.CircleShape
10 | import androidx.compose.foundation.shape.RoundedCornerShape
11 | import androidx.compose.material.Icon
12 | import androidx.compose.material.Surface
13 | import androidx.compose.material.Text
14 | import androidx.compose.material.icons.Icons
15 | import androidx.compose.material.icons.rounded.Add
16 | import androidx.compose.material.icons.rounded.CheckCircle
17 | import androidx.compose.runtime.*
18 | import androidx.compose.ui.Alignment
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.draw.clip
21 | import androidx.compose.ui.graphics.Color
22 | import androidx.compose.ui.text.font.FontWeight
23 | import androidx.compose.ui.tooling.preview.Preview
24 | import androidx.compose.ui.unit.dp
25 | import androidx.compose.ui.unit.sp
26 | import ken.projects.infit.ui.theme.darkBlue
27 | import ken.projects.infit.ui.theme.holoGreen
28 | import ken.projects.infit.ui.theme.outfit
29 | import ken.projects.infit.ui.theme.white
30 |
31 | @Preview
32 | @Composable
33 | fun RegularButton(
34 | modifier: Modifier = Modifier,
35 | text: String = "button",
36 | textColor: Color = darkBlue,
37 | backgroundColor: Color = holoGreen,
38 | onClick: () -> Unit = {}
39 | ) {
40 |
41 |
42 | Surface(
43 | color = backgroundColor,
44 | shape = RoundedCornerShape(40.dp),
45 | modifier = modifier.clickable { onClick() },
46 | elevation = 10.dp,
47 |
48 | ) {
49 |
50 | Row(
51 | modifier = Modifier.padding(vertical = 10.dp, horizontal = 20.dp),
52 | verticalAlignment = Alignment.CenterVertically,
53 | horizontalArrangement = Arrangement.Center,
54 | ) {
55 | ButtonText(
56 | text = text.lowercase(),
57 | color = textColor,
58 | )
59 | }
60 |
61 |
62 | }
63 | }
64 |
65 | @Composable
66 | fun FloatingAddButton(modifier: Modifier = Modifier, onClick: () -> Unit = {}) {
67 | Box(
68 | modifier = modifier
69 | .clip(CircleShape)
70 | .size(70.dp)
71 | .background(holoGreen)
72 | .clickable { onClick() },
73 | contentAlignment = Alignment.Center
74 | ) {
75 | Icon(
76 | imageVector = Icons.Rounded.Add,
77 | contentDescription = null,
78 | tint = darkBlue,
79 | modifier = Modifier.size(40.dp)
80 | )
81 | }
82 |
83 | }
84 |
85 | @OptIn(ExperimentalAnimationApi::class)
86 | @Composable
87 | fun RoundedCheckBox(
88 | modifier: Modifier = Modifier,
89 | onCheckedChange: (Boolean) -> Unit = {},
90 | ) {
91 |
92 | var color by remember { mutableStateOf(Color.Transparent) }
93 | var checkedState by remember { mutableStateOf(false) }
94 | Box(
95 | modifier = modifier
96 | .size(30.dp)
97 | .clip(CircleShape)
98 | .clickable {
99 | checkedState = !checkedState
100 | onCheckedChange(checkedState)
101 |
102 | color = if (checkedState) {
103 | holoGreen
104 | } else {
105 | Color.Transparent
106 | }
107 | }
108 | .border(BorderStroke(1.dp, holoGreen), shape = CircleShape),
109 | contentAlignment = Alignment.Center
110 | ) {
111 | Surface(modifier = Modifier.fillMaxSize(), color = color) {
112 | AnimatedVisibility(
113 | visible = checkedState,
114 | enter = fadeIn() + scaleIn(),
115 | exit = fadeOut() + scaleOut()
116 | ) {
117 | Icon(
118 | imageVector = Icons.Rounded.CheckCircle,
119 | contentDescription = null,
120 | tint = darkBlue,
121 | modifier = Modifier.size(40.dp)
122 | )
123 | }
124 |
125 | }
126 |
127 |
128 | }
129 |
130 | }
131 |
132 | @Composable
133 | fun ButtonText(text: String, modifier: Modifier = Modifier, color: Color = white) {
134 | Text(
135 | text = text,
136 | fontWeight = FontWeight.Light,
137 | fontFamily = outfit,
138 | fontSize = 20.sp,
139 | color = color,
140 | modifier = modifier
141 | )
142 | }
143 |
144 | @Preview
145 | @Composable
146 | fun Preview() {
147 | RoundedCheckBox()
148 | }
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/composables/CalendarDisplay.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.composables
2 |
3 | import android.util.Log
4 | import androidx.compose.foundation.BorderStroke
5 | import androidx.compose.foundation.Image
6 | import androidx.compose.foundation.clickable
7 | import androidx.compose.foundation.layout.*
8 | import androidx.compose.foundation.lazy.LazyRow
9 | import androidx.compose.foundation.lazy.items
10 | import androidx.compose.foundation.shape.RoundedCornerShape
11 | import androidx.compose.material.Surface
12 | import androidx.compose.material.Text
13 | import androidx.compose.runtime.*
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.graphics.ColorFilter
18 | import androidx.compose.ui.res.painterResource
19 | import androidx.compose.ui.text.font.FontWeight
20 | import androidx.compose.ui.unit.dp
21 | import androidx.compose.ui.unit.sp
22 | import androidx.lifecycle.viewmodel.compose.viewModel
23 | import ken.projects.infit.R
24 | import ken.projects.infit.ui.theme.*
25 | import ken.projects.infit.viewmodel.WorkoutViewModel
26 | import java.time.DayOfWeek
27 | import java.time.LocalDateTime
28 | import kotlin.collections.ArrayList
29 |
30 | @Composable
31 | fun CalendarDisplay(
32 | modifier: Modifier = Modifier,
33 | workoutDays: ArrayList?,
34 | workoutViewModel: WorkoutViewModel = viewModel()
35 | ) {
36 |
37 | LazyRow(
38 | modifier = modifier,
39 | contentPadding = PaddingValues(horizontal = 15.dp),
40 | horizontalArrangement = Arrangement.spacedBy(15.dp)
41 | ) {
42 | items(getDates()) { date ->
43 | workoutDays?.let { workoutDays ->
44 |
45 | var isSelected by remember { mutableStateOf(false) }
46 |
47 | val colorFilter = if (workoutDays.contains(date.dayOfWeek)) {
48 | ColorFilter.tint(holoRed)
49 | } else {
50 | ColorFilter.tint(Color.Transparent)
51 | }
52 |
53 | CalendarDisplayItem(
54 | date = date,
55 | colorFilter = colorFilter,
56 | workoutViewModel = workoutViewModel
57 | )
58 | }
59 | }
60 | }
61 |
62 | }
63 |
64 | fun getDates(): ArrayList {
65 |
66 | val today = LocalDateTime.now()
67 | val dates = ArrayList()
68 | dates.add(today)
69 | for (i in 1..13) {
70 | val nextDate = today.plusDays(i.toLong())
71 | dates.add(nextDate)
72 | }
73 |
74 | return dates
75 | }
76 |
77 | @Composable
78 | fun CalendarDisplayItem(
79 | modifier: Modifier = Modifier,
80 | date: LocalDateTime,
81 | colorFilter: ColorFilter,
82 | workoutViewModel: WorkoutViewModel = viewModel()
83 | ) {
84 |
85 | var isSelected by remember { mutableStateOf(false) }
86 |
87 | isSelected = workoutViewModel.calendarSelection.dayOfYear == date.dayOfYear
88 |
89 |
90 | var surfaceColor by remember {
91 | mutableStateOf(lightBlue)
92 | }
93 |
94 | surfaceColor = if (isSelected) holoGreen else lightBlue
95 |
96 | var textColor by remember {
97 | mutableStateOf(white)
98 | }
99 |
100 | textColor = if (isSelected) Color.Black else white
101 |
102 | Surface(
103 | color = surfaceColor,
104 | shape = RoundedCornerShape(40.dp),
105 | modifier = modifier
106 | .height(90.dp)
107 | .width(50.dp)
108 | .clickable {
109 | workoutViewModel.selectDay(date)
110 | workoutViewModel.getDayString(date)
111 | },
112 | border = BorderStroke(1.dp, white.copy(0.3f)),
113 |
114 | ) {
115 |
116 | Column(
117 | modifier = modifier.fillMaxSize(),
118 | verticalArrangement = Arrangement.SpaceAround,
119 | horizontalAlignment = Alignment.CenterHorizontally
120 | ) {
121 |
122 |
123 | Image(
124 | painter = painterResource(id = R.drawable.ic_dot),
125 | contentDescription = "",
126 | colorFilter = colorFilter,
127 | modifier = Modifier
128 | .height(10.dp)
129 | .width(10.dp)
130 | )
131 |
132 |
133 | Text(
134 | text = date.dayOfMonth.toString(),
135 | fontSize = 20.sp,
136 | fontFamily = outfit,
137 | fontWeight = FontWeight.SemiBold,
138 | color = textColor
139 | )
140 |
141 | Text(
142 | text = date.dayOfWeek.name.substring(0, 3),
143 | fontSize = 10.sp,
144 | fontFamily = outfit,
145 | modifier = Modifier.padding(bottom = 10.dp),
146 | fontWeight = FontWeight.Light,
147 | color = textColor.copy(0.6f)
148 | )
149 |
150 | }
151 |
152 | }
153 | }
154 |
155 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/composables/home/TrainingCard.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.composables
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.shape.RoundedCornerShape
6 | import androidx.compose.material.Icon
7 | import androidx.compose.material.Surface
8 | import androidx.compose.material.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.draw.clip
14 | import androidx.compose.ui.draw.paint
15 | import androidx.compose.ui.draw.shadow
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.layout.ContentScale
18 | import androidx.compose.ui.res.painterResource
19 | import androidx.compose.ui.res.stringResource
20 | import androidx.compose.ui.text.style.TextAlign
21 | import androidx.compose.ui.tooling.preview.Preview
22 | import androidx.compose.ui.unit.dp
23 | import com.airbnb.lottie.compose.LottieAnimation
24 | import com.airbnb.lottie.compose.LottieCompositionSpec
25 | import com.airbnb.lottie.compose.rememberLottieComposition
26 | import ken.projects.infit.R
27 | import ken.projects.infit.data.models.WorkoutPlan
28 | import ken.projects.infit.ui.composables.home.Heading
29 | import ken.projects.infit.ui.composables.home.Title
30 | import ken.projects.infit.ui.theme.veryDarkBlue
31 | import ken.projects.infit.ui.theme.white
32 | import ken.projects.infit.util.DifficultyLevels
33 |
34 | @Composable
35 | fun TrainingCard(
36 | modifier: Modifier,
37 | workoutPlan: WorkoutPlan,
38 | onClick: () -> Unit
39 | ) {
40 |
41 |
42 | Box(
43 | modifier = modifier
44 | .fillMaxWidth()
45 | .height(235.dp)
46 | .clip(RoundedCornerShape(40.dp))
47 | .shadow(4.dp),
48 |
49 | ) {
50 | Surface(
51 | color = veryDarkBlue.copy(0.5f),
52 | modifier = Modifier
53 | .fillMaxSize()
54 | .clickable { onClick() }
55 | .paint(
56 | painter = painterResource(id = R.drawable.stockimagebarbell),
57 | contentScale = ContentScale.Crop
58 | ),
59 |
60 | ) {
61 |
62 | Column(
63 | modifier = Modifier
64 | .fillMaxSize()
65 | .padding(start = 20.dp),
66 | verticalArrangement = Arrangement.spacedBy(10.dp, Alignment.Bottom)
67 | ) {
68 |
69 | Heading(text = workoutPlan.name.toString(), modifier = Modifier)
70 |
71 | WorkoutInfo(
72 | modifier = Modifier
73 | .paddingFromBaseline(bottom = 20.dp),
74 | duration = workoutPlan.duration.toString(),
75 | difficulty = workoutPlan.difficulty!!,
76 |
77 | )
78 |
79 | }
80 |
81 | }
82 |
83 | }
84 | }
85 |
86 | @Composable
87 | fun WorkoutInfo(
88 | modifier: Modifier = Modifier,
89 | duration: String,
90 | difficulty: DifficultyLevels.Difficulty
91 | ) {
92 |
93 | Row(
94 | modifier = modifier
95 | .fillMaxWidth(),
96 | verticalAlignment = Alignment.CenterVertically,
97 | horizontalArrangement = Arrangement.spacedBy(10.dp)
98 | ) {
99 |
100 | Icon(
101 | painter = painterResource(id = R.drawable.ic_timer),
102 | contentDescription = null,
103 | tint = white
104 | )
105 |
106 | Text(
107 | text = "$duration min",
108 | color = white,
109 | textAlign = TextAlign.End,
110 | modifier = Modifier
111 | .align(Alignment.Bottom)
112 | )
113 | Spacer(modifier = Modifier.width(10.dp))
114 | Icon(
115 | painter = painterResource(id = difficulty.icon!!),
116 | contentDescription = null,
117 | tint = white,
118 | modifier = Modifier
119 | .width(20.dp)
120 | .height(20.dp)
121 | )
122 |
123 | Text(
124 | text = stringResource(id = difficulty.difficulty!!),
125 | color = white,
126 | textAlign = TextAlign.End,
127 | modifier = Modifier
128 | .align(Alignment.Bottom)
129 | )
130 | }
131 | }
132 |
133 | @Preview
134 | @Composable
135 | fun RestDayView(modifier: Modifier = Modifier) {
136 |
137 | val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.panda_animation))
138 |
139 |
140 | Surface(color = Color.Transparent, modifier = modifier) {
141 |
142 | Column(modifier = modifier.fillMaxSize(), horizontalAlignment = Alignment.Start) {
143 | Heading(text = stringResource(R.string.no_sessions_text))
144 |
145 | LottieAnimation(
146 | composition = composition,
147 | modifier = Modifier
148 | .height(250.dp)
149 | .fillMaxWidth()
150 | ,
151 | iterations = Int.MAX_VALUE
152 | )
153 |
154 | Title(
155 | text = stringResource(R.string.rest_view_text)
156 |
157 | )
158 |
159 | }
160 |
161 |
162 | }
163 |
164 | }
165 |
166 |
167 |
168 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/composables/signup/SignUpScreen.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.composables.signup
2 |
3 | import androidx.compose.foundation.layout.*
4 | import androidx.compose.material.*
5 | import androidx.compose.material.icons.Icons
6 | import androidx.compose.material.icons.rounded.Email
7 | import androidx.compose.material.icons.rounded.Lock
8 | import androidx.compose.material.icons.rounded.Person
9 | import androidx.compose.runtime.*
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.draw.paint
13 | import androidx.compose.ui.layout.ContentScale
14 | import androidx.compose.ui.res.painterResource
15 | import androidx.compose.ui.res.stringResource
16 | import androidx.compose.ui.text.input.KeyboardType
17 | import androidx.compose.ui.unit.dp
18 | import androidx.lifecycle.viewmodel.compose.viewModel
19 | import androidx.navigation.NavHostController
20 | import androidx.navigation.compose.rememberNavController
21 | import ken.projects.infit.R
22 | import ken.projects.infit.ui.composables.RegularButton
23 | import ken.projects.infit.ui.composables.home.Heading
24 | import ken.projects.infit.ui.composables.login.InputField
25 | import ken.projects.infit.ui.navigation.Screens
26 | import ken.projects.infit.ui.theme.holoGreen
27 | import ken.projects.infit.ui.theme.veryDarkBlue
28 | import ken.projects.infit.viewmodel.UserViewModel
29 |
30 | @Composable
31 | fun SignUpScreen(
32 | navController: NavHostController = rememberNavController(),
33 | userViewModel: UserViewModel = viewModel(),
34 | scaffoldState: ScaffoldState
35 | ) {
36 |
37 | val state = userViewModel.signUpState
38 |
39 | var userName by remember { mutableStateOf("") }
40 | var eMail by remember { mutableStateOf("") }
41 |
42 | var password by remember { mutableStateOf("") }
43 | var confirmPassword by remember { mutableStateOf("") }
44 |
45 | LaunchedEffect(key1 = state.error) {
46 | state.error?.let {
47 | scaffoldState.snackbarHostState.showSnackbar(
48 | it,
49 | null,
50 | SnackbarDuration.Short
51 | )
52 | }
53 | }
54 |
55 | LaunchedEffect(key1 = state.success) {
56 |
57 | if (state.success) {
58 | navController.navigate(Screens.Login.route)
59 | }
60 |
61 | }
62 |
63 | Surface(
64 | color = veryDarkBlue.copy(0.6f),
65 | modifier = Modifier.paint(
66 | painterResource(id = R.drawable.register_background),
67 | contentScale = ContentScale.Crop,
68 | ).fillMaxSize(),
69 | ) {
70 |
71 | if (state.loading) {
72 |
73 | Column(
74 | verticalArrangement = Arrangement.Center,
75 | horizontalAlignment = Alignment.CenterHorizontally,
76 |
77 | ) {
78 | CircularProgressIndicator(
79 | color = holoGreen.copy(0.6f),
80 | strokeWidth = 5.dp,
81 | )
82 | }
83 |
84 | } else
85 |
86 | Column(
87 | verticalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterVertically),
88 | horizontalAlignment = Alignment.Start,
89 | modifier = Modifier.padding(horizontal = 50.dp)
90 | ) {
91 | Heading(
92 | text = stringResource(R.string.signup), modifier = Modifier
93 | .align(Alignment.Start)
94 | .padding(bottom = 20.dp)
95 | .fillMaxWidth()
96 | )
97 |
98 | InputField(
99 | userName,
100 | { userName = it },
101 | stringResource(R.string.enter_name_hint),
102 | Icons.Rounded.Person,
103 | type = KeyboardType.Text
104 | )
105 | InputField(
106 | eMail,
107 | { eMail = it },
108 | stringResource(R.string.enter_email_hint),
109 | Icons.Rounded.Email,
110 | type = KeyboardType.Email
111 | )
112 |
113 | InputField(
114 | password,
115 | { password = it },
116 | stringResource(R.string.enter_password_hint),
117 | Icons.Rounded.Lock,
118 | type = KeyboardType.Password,
119 | true
120 | )
121 | InputField(
122 | confirmPassword,
123 | { confirmPassword = it },
124 | stringResource(R.string.confirm_password_hint),
125 | Icons.Rounded.Lock,
126 | type = KeyboardType.Password,
127 | true
128 | )
129 |
130 | RegularButton(modifier = Modifier
131 | .align(Alignment.CenterHorizontally)
132 | .padding(top = 20.dp),
133 | text = stringResource(R.string.continue_text)
134 | ) {
135 | userViewModel.signUpUser(
136 | userName = userName,
137 | userEmail = eMail,
138 | userPassword = password,
139 | confirmPassword = confirmPassword
140 | )
141 |
142 | }
143 |
144 |
145 | }
146 |
147 |
148 | }
149 |
150 |
151 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/navigation/MainNavGraph.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.navigation
2 |
3 | import androidx.compose.animation.AnimatedVisibility
4 | import androidx.compose.animation.slideInVertically
5 | import androidx.compose.animation.slideOutVertically
6 | import androidx.compose.foundation.layout.RowScope
7 | import androidx.compose.foundation.layout.height
8 | import androidx.compose.foundation.shape.RoundedCornerShape
9 | import androidx.compose.material.*
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.MutableState
12 | import androidx.compose.runtime.getValue
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.graphicsLayer
15 | import androidx.compose.ui.res.stringResource
16 | import androidx.compose.ui.unit.dp
17 | import androidx.navigation.NavDestination
18 | import androidx.navigation.NavDestination.Companion.hierarchy
19 | import androidx.navigation.NavGraph.Companion.findStartDestination
20 | import androidx.navigation.NavGraphBuilder
21 | import androidx.navigation.NavHostController
22 | import androidx.navigation.compose.composable
23 | import androidx.navigation.compose.currentBackStackEntryAsState
24 | import androidx.navigation.compose.rememberNavController
25 | import androidx.navigation.navigation
26 | import ken.projects.infit.ui.composables.exercises.ExerciseDetailScreen
27 | import ken.projects.infit.ui.composables.exercises.ExercisesScreen
28 | import ken.projects.infit.ui.composables.home.HomeScreen
29 | import ken.projects.infit.ui.composables.profile.ProfileScreen
30 | import ken.projects.infit.ui.composables.stats.StatsDetailScreen
31 | import ken.projects.infit.ui.composables.stats.StatsScreen
32 | import ken.projects.infit.ui.composables.workout.WorkoutDetailScreen
33 | import ken.projects.infit.ui.composables.workout.WorkoutPlanSetUpScreen
34 | import ken.projects.infit.ui.composables.workout.WorkoutScreen
35 | import ken.projects.infit.ui.theme.holoGreen
36 | import ken.projects.infit.ui.theme.veryDarkBlue
37 | import ken.projects.infit.viewmodel.UserViewModel
38 | import ken.projects.infit.viewmodel.WorkoutViewModel
39 |
40 | fun NavGraphBuilder.mainNavGraph(
41 | navController: NavHostController,
42 | bottomBarState: MutableState,
43 | userViewModel: UserViewModel,
44 | workoutViewModel: WorkoutViewModel,
45 | scaffoldState: ScaffoldState
46 | ) {
47 |
48 | navigation(startDestination = Screens.Home.route, route = MAIN_ROUTE)
49 | {
50 |
51 | composable(
52 | route = Screens.Home.route
53 | ) {
54 | HomeScreen(navController, userViewModel, workoutViewModel, scaffoldState)
55 | bottomBarState.value = true
56 | }
57 |
58 | composable(
59 | route = Screens.Stats.route
60 | ) {
61 | StatsScreen(navController, workoutViewModel)
62 | bottomBarState.value = true
63 | }
64 |
65 | composable(
66 | route = Screens.Profile.route
67 | ) {
68 | /*
69 | TODO: PROFILE SCREEN
70 | */
71 | ProfileScreen()
72 | bottomBarState.value = true
73 | }
74 |
75 | composable(
76 | route = Screens.StatsDetails.route
77 | ) {
78 | StatsDetailScreen(navController, workoutViewModel)
79 | bottomBarState.value = true
80 | }
81 |
82 | composable(
83 | route = Screens.WorkoutDetails.route
84 | ) {
85 | WorkoutDetailScreen(navController, workoutViewModel)
86 | bottomBarState.value = true
87 | }
88 |
89 | composable(
90 | route = Screens.Workout.route
91 | ) {
92 | WorkoutScreen(workoutViewModel, navController)
93 | bottomBarState.value = true
94 | }
95 |
96 | composable(route = Screens.Exercises.route) {
97 | ExercisesScreen(navController, workoutViewModel)
98 | bottomBarState.value = true
99 | }
100 |
101 | composable(route = Screens.ExerciseDetails.route) {
102 | ExerciseDetailScreen(Modifier, navController, workoutViewModel)
103 | bottomBarState.value = true
104 | }
105 |
106 | composable(route = Screens.WorkoutPlanSetUp.route) {
107 | WorkoutPlanSetUpScreen(workoutViewModel = workoutViewModel, navController)
108 | bottomBarState.value = true
109 | }
110 |
111 |
112 |
113 | }
114 | }
115 |
116 | @Composable
117 | fun BottomNavBar(
118 | navController: NavHostController = rememberNavController(),
119 | bottomBarState: MutableState
120 | ) {
121 | val screens = listOf(
122 | Screens.Home,
123 | Screens.Stats,
124 | Screens.Profile,
125 | Screens.Exercises
126 | )
127 | val navBackStackEntry by navController.currentBackStackEntryAsState()
128 | val currentDestination = navBackStackEntry?.destination
129 |
130 | AnimatedVisibility(
131 | visible = bottomBarState.value, enter = slideInVertically(initialOffsetY = { it }),
132 | exit = slideOutVertically(targetOffsetY = { it }),
133 | ) {
134 | BottomNavigation(
135 | modifier = Modifier
136 | .graphicsLayer {
137 | clip = true
138 | shape = RoundedCornerShape(topStart = 40.dp, topEnd = 40.dp)
139 | }
140 | .height(70.dp),
141 | elevation = 10.dp,
142 | backgroundColor = holoGreen,
143 |
144 |
145 | ) {
146 |
147 | screens.forEach {
148 |
149 | this@BottomNavigation.AddItem(
150 | screens = it,
151 | currentDestination = currentDestination,
152 | navController = navController
153 | )
154 | }
155 |
156 |
157 | }
158 | }
159 |
160 | }
161 |
162 | @Composable
163 | fun RowScope.AddItem(
164 | screens: Screens,
165 | currentDestination: NavDestination?,
166 | navController: NavHostController
167 | ) {
168 |
169 | BottomNavigationItem(
170 | label = {
171 | Text(text = stringResource(id = screens.title))
172 | },
173 |
174 | onClick = {
175 | navController.navigate(screens.route) {
176 | popUpTo(navController.graph.findStartDestination().id)
177 | launchSingleTop = true
178 | }
179 | },
180 | icon = { Icon(imageVector = screens.icon!!, contentDescription = null) },
181 | selected = currentDestination?.hierarchy?.any { it.route == screens.route } == true,
182 | selectedContentColor = veryDarkBlue,
183 | unselectedContentColor = veryDarkBlue.copy(ContentAlpha.disabled),
184 |
185 |
186 | )
187 | }
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/composables/exercises/ExercisesScreen.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.composables.exercises
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.annotation.StringRes
5 | import androidx.compose.foundation.Image
6 | import androidx.compose.foundation.layout.*
7 | import androidx.compose.foundation.shape.CircleShape
8 | import androidx.compose.material.ExperimentalMaterialApi
9 | import androidx.compose.material.Scaffold
10 | import androidx.compose.material.Surface
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.layout.ContentScale
16 | import androidx.compose.ui.res.painterResource
17 | import androidx.compose.ui.res.stringResource
18 | import androidx.compose.ui.tooling.preview.Preview
19 | import androidx.compose.ui.unit.dp
20 | import androidx.lifecycle.viewmodel.compose.viewModel
21 | import androidx.navigation.NavHostController
22 | import androidx.navigation.compose.rememberNavController
23 | import ken.projects.infit.R
24 | import ken.projects.infit.data.models.Muscle
25 | import ken.projects.infit.ui.composables.home.Heading
26 | import ken.projects.infit.ui.composables.home.Title
27 | import ken.projects.infit.ui.navigation.Screens
28 | import ken.projects.infit.ui.theme.darkBlue
29 | import ken.projects.infit.ui.theme.lightBlue
30 | import ken.projects.infit.viewmodel.WorkoutViewModel
31 |
32 | @Composable
33 | fun ExercisesScreen(
34 | navController: NavHostController = rememberNavController(),
35 | workoutViewModel: WorkoutViewModel = viewModel()
36 | ) {
37 |
38 | Surface(modifier = Modifier.fillMaxSize(), color = darkBlue) {
39 |
40 | Column(
41 | modifier = Modifier
42 | .fillMaxSize()
43 | .padding(top = 20.dp)
44 | .padding(horizontal = 15.dp),
45 | verticalArrangement = Arrangement.spacedBy(40.dp),
46 | horizontalAlignment = Alignment.Start,
47 | ) {
48 |
49 | Heading(text = stringResource(R.string.exercises))
50 |
51 | Row(
52 | horizontalArrangement = Arrangement.SpaceBetween,
53 | verticalAlignment = Alignment.CenterVertically,
54 | modifier = Modifier.fillMaxWidth()
55 | ) {
56 | ExerciseItem(image = R.drawable.chest_anatomy_nb, bodyPart = stringResource(R.string.chest)) {
57 | workoutViewModel.selectMuscleGroup(listOf(Muscle.Chest), "Chest")
58 | navController.navigate(Screens.ExerciseDetails.route)
59 | }
60 | ExerciseItem(image = R.drawable.deltoids_anatomy_nb, bodyPart = stringResource(R.string.shoulders)) {
61 | workoutViewModel.selectMuscleGroup(
62 | listOf(
63 | Muscle.AnteriorDeltoids,
64 | Muscle.LateralDeltoids
65 | ), "Shoulders"
66 | )
67 | navController.navigate(Screens.ExerciseDetails.route)
68 | }
69 | ExerciseItem(image = R.drawable.biceps_anatomy_nb, bodyPart = stringResource(R.string.biceps)) {
70 | workoutViewModel.selectMuscleGroup(listOf(Muscle.Biceps), "Biceps")
71 | navController.navigate(Screens.ExerciseDetails.route)
72 | }
73 | }
74 |
75 | Row(
76 | horizontalArrangement = Arrangement.SpaceBetween,
77 | verticalAlignment = Alignment.CenterVertically,
78 | modifier = Modifier.fillMaxWidth()
79 | ) {
80 | ExerciseItem(image = R.drawable.triceps_anatomy_nb, bodyPart = stringResource(R.string.triceps)) {
81 | workoutViewModel.selectMuscleGroup(listOf(Muscle.Triceps), "Triceps")
82 | navController.navigate(Screens.ExerciseDetails.route)
83 | }
84 | ExerciseItem(image = R.drawable.traps_anatomy_nb, bodyPart = stringResource(R.string.upper_back)) {
85 | workoutViewModel.selectMuscleGroup(listOf(Muscle.Traps), "Upper Back")
86 | navController.navigate(Screens.ExerciseDetails.route)
87 | }
88 | ExerciseItem(image = R.drawable.lats_anatomy_nb, bodyPart = stringResource(R.string.lats)) {
89 | workoutViewModel.selectMuscleGroup(listOf(Muscle.Lats), "Lats")
90 | navController.navigate(Screens.ExerciseDetails.route)
91 | }
92 | }
93 |
94 | Row(
95 | horizontalArrangement = Arrangement.SpaceBetween,
96 | verticalAlignment = Alignment.CenterVertically,
97 | modifier = Modifier.fillMaxWidth()
98 | ) {
99 | ExerciseItem(
100 | image = R.drawable.calf_muscles_anatomy_nb,
101 | bodyPart = stringResource(R.string.calves),
102 | ) {
103 | workoutViewModel.selectMuscleGroup(listOf(Muscle.Calves), "Calves")
104 | navController.navigate(Screens.ExerciseDetails.route)
105 | }
106 | ExerciseItem(
107 | image = R.drawable.hamstrings_anatomy_nb,
108 | bodyPart = stringResource(R.string.hamstrings),
109 | ) {
110 | workoutViewModel.selectMuscleGroup(listOf(Muscle.Hamstrings), "Hamstrings")
111 | navController.navigate(Screens.ExerciseDetails.route)
112 | }
113 | ExerciseItem(
114 | image = R.drawable.quadriceps_muscles_anatomy_nb,
115 | bodyPart = stringResource(R.string.quads),
116 | ) {
117 | workoutViewModel.selectMuscleGroup(listOf(Muscle.Quads), "Quads")
118 | navController.navigate(Screens.ExerciseDetails.route)
119 | }
120 | }
121 |
122 | }
123 |
124 |
125 | }
126 |
127 |
128 | }
129 |
130 |
131 | @OptIn(ExperimentalMaterialApi::class)
132 | @Composable
133 | fun ExerciseItem(
134 | @DrawableRes
135 | image: Int,
136 | bodyPart: String,
137 | onClick: () -> Unit
138 | ) {
139 |
140 |
141 | Column(
142 | verticalArrangement = Arrangement.spacedBy(5.dp),
143 | horizontalAlignment = Alignment.CenterHorizontally
144 | ) {
145 |
146 | Surface(
147 | shape = CircleShape,
148 | modifier = Modifier.size(100.dp),
149 | elevation = 10.dp,
150 | color = lightBlue,
151 | onClick = onClick
152 | ) {
153 | Image(
154 | painter = painterResource(id = image),
155 | contentDescription = null,
156 | contentScale = ContentScale.Crop
157 | )
158 | }
159 |
160 | Title(text = bodyPart.lowercase())
161 |
162 |
163 | }
164 |
165 | }
166 |
167 | @Preview
168 | @Composable
169 | fun Preview() {
170 |
171 | ExercisesScreen()
172 |
173 | }
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/composables/login/LoginScreen.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.composables.login
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.text.KeyboardOptions
6 | import androidx.compose.material.*
7 | import androidx.compose.material.TextFieldDefaults.textFieldColors
8 | import androidx.compose.material.icons.Icons
9 | import androidx.compose.material.icons.rounded.Email
10 | import androidx.compose.material.icons.rounded.Lock
11 | import androidx.compose.runtime.*
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Alignment.Companion.CenterHorizontally
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.draw.paint
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.graphics.vector.ImageVector
18 | import androidx.compose.ui.layout.ContentScale
19 | import androidx.compose.ui.res.painterResource
20 | import androidx.compose.ui.res.stringResource
21 | import androidx.compose.ui.text.font.FontWeight
22 | import androidx.compose.ui.text.input.ImeAction
23 | import androidx.compose.ui.text.input.KeyboardType
24 | import androidx.compose.ui.text.input.PasswordVisualTransformation
25 | import androidx.compose.ui.text.input.VisualTransformation
26 | import androidx.compose.ui.unit.dp
27 | import androidx.navigation.NavHostController
28 | import ken.projects.infit.R
29 | import ken.projects.infit.ui.composables.RegularButton
30 | import ken.projects.infit.ui.composables.home.Heading
31 | import ken.projects.infit.ui.navigation.MAIN_ROUTE
32 | import ken.projects.infit.ui.navigation.Screens
33 | import ken.projects.infit.ui.theme.*
34 | import ken.projects.infit.viewmodel.UserViewModel
35 |
36 | @Composable
37 | fun LoginScreen(
38 | navController: NavHostController,
39 | userViewModel: UserViewModel,
40 | scaffoldState: ScaffoldState
41 | ) {
42 |
43 | val state = userViewModel.signInState
44 |
45 | var eMail by remember { mutableStateOf("") }
46 | var password by remember { mutableStateOf("") }
47 |
48 | LaunchedEffect(key1 = state.error) {
49 | state.error?.let {
50 | scaffoldState.snackbarHostState.showSnackbar(
51 | it,
52 | null,
53 | SnackbarDuration.Short
54 | )
55 | }
56 |
57 | }
58 | LaunchedEffect(key1 = state.success) {
59 | if (state.success) {
60 | navController.navigate(MAIN_ROUTE)
61 | }
62 | }
63 |
64 |
65 | Surface(
66 | color = veryDarkBlue.copy(0.7f),
67 | modifier = Modifier
68 | .paint(
69 | painterResource(id = R.drawable.login_background),
70 | contentScale = ContentScale.Crop,
71 | )
72 | .fillMaxSize(),
73 | )
74 | {
75 |
76 |
77 | if (state.loading) {
78 |
79 | Column(
80 | verticalArrangement = Arrangement.Center,
81 | horizontalAlignment = CenterHorizontally,
82 |
83 | ) {
84 | CircularProgressIndicator(
85 | color = holoGreen,
86 | strokeWidth = 5.dp,
87 | )
88 | }
89 |
90 | } else
91 | Column(
92 | modifier = Modifier
93 | .fillMaxSize()
94 | .padding(horizontal = 50.dp),
95 | verticalArrangement = Arrangement.Center,
96 | horizontalAlignment = Alignment.Start,
97 |
98 | ) {
99 |
100 | Heading(
101 | text = stringResource(R.string.login), modifier = Modifier
102 | .align(Alignment.Start)
103 | .padding(bottom = 20.dp)
104 | .fillMaxWidth()
105 | )
106 | InputField(
107 | eMail,
108 | onValueChange = { eMail = it },
109 | placeholder = stringResource(R.string.email),
110 | icon = Icons.Rounded.Email,
111 | type = KeyboardType.Email
112 | )
113 | Spacer(modifier = Modifier.height(10.dp))
114 |
115 | InputField(
116 | input = password,
117 | onValueChange = { password = it },
118 | placeholder = stringResource(R.string.password),
119 | icon = Icons.Rounded.Lock,
120 | type = KeyboardType.Password,
121 | true
122 | )
123 | SignUpRow(
124 | modifier = Modifier
125 | .padding(top = 10.dp)
126 | .fillMaxWidth()
127 | .clickable { navController.navigate(Screens.Signup.route) }
128 | )
129 | RegularButton(
130 | Modifier
131 | .padding(top = 20.dp)
132 | .align(CenterHorizontally),
133 | stringResource(R.string.login).lowercase(),
134 | onClick = {
135 | userViewModel.signInUser(
136 | userEmail = eMail,
137 | userPassword = password
138 | )
139 |
140 | eMail = ""
141 | password = ""
142 |
143 |
144 | }
145 | )
146 |
147 |
148 | }
149 |
150 |
151 | }
152 | }
153 |
154 |
155 | @Composable
156 | fun InputField(
157 | input: String = "",
158 | onValueChange: (String) -> Unit,
159 | placeholder: String = "email",
160 | icon: ImageVector = Icons.Rounded.Email,
161 | type: KeyboardType = KeyboardType.Email,
162 | password: Boolean = false
163 | ) {
164 |
165 | TextField(
166 | value = input,
167 | onValueChange = onValueChange,
168 | leadingIcon = {
169 | Icon(
170 | imageVector = icon,
171 | contentDescription = null,
172 |
173 | )
174 |
175 | },
176 | colors = textFieldColors(backgroundColor = white, textColor = Color.Black),
177 | label = { Text(text = placeholder) },
178 | modifier = Modifier.fillMaxWidth(),
179 | maxLines = 1,
180 | keyboardOptions = KeyboardOptions(
181 | keyboardType = type,
182 | imeAction = ImeAction.Done
183 | ),
184 | visualTransformation = if (password) PasswordVisualTransformation() else {
185 | VisualTransformation.None
186 | }
187 |
188 | )
189 |
190 | }
191 |
192 | @Composable
193 | fun SignUpRow(modifier: Modifier = Modifier) {
194 |
195 | Row(
196 | verticalAlignment = Alignment.CenterVertically,
197 | modifier = modifier
198 | ) {
199 |
200 | Text(
201 | text = stringResource(R.string.sign_up_text),
202 | fontWeight = FontWeight.Normal,
203 | color = white,
204 | fontFamily = outfit
205 | )
206 | Spacer(modifier = Modifier.width(5.dp))
207 | Text(
208 | text = stringResource(R.string.sign_up),
209 | fontWeight = FontWeight.Bold,
210 | color = holoGreen,
211 | fontFamily = outfit
212 | )
213 | }
214 | }
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/composables/stats/StatsDetailScreen.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.composables.stats
2 |
3 | import android.graphics.PointF
4 | import androidx.compose.foundation.Canvas
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.layout.*
7 | import androidx.compose.foundation.lazy.LazyColumn
8 | import androidx.compose.foundation.lazy.items
9 | import androidx.compose.material.*
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.geometry.Offset
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.graphics.PathEffect
16 | import androidx.compose.ui.graphics.RectangleShape
17 | import androidx.compose.ui.res.stringResource
18 | import androidx.compose.ui.text.font.FontWeight
19 | import androidx.compose.ui.tooling.preview.Preview
20 | import androidx.compose.ui.unit.dp
21 | import androidx.compose.ui.unit.sp
22 | import androidx.lifecycle.viewmodel.compose.viewModel
23 | import androidx.navigation.NavHostController
24 | import androidx.navigation.compose.rememberNavController
25 | import ken.projects.infit.R
26 | import ken.projects.infit.data.models.ExerciseHistoryItem
27 | import ken.projects.infit.ui.composables.home.Heading
28 | import ken.projects.infit.ui.theme.darkBlue
29 | import ken.projects.infit.ui.theme.holoGreen
30 | import ken.projects.infit.ui.theme.veryDarkBlue
31 | import ken.projects.infit.ui.theme.white
32 | import ken.projects.infit.viewmodel.WorkoutViewModel
33 | import java.text.SimpleDateFormat
34 | import java.util.*
35 |
36 | @Preview
37 | @Composable
38 | fun StatsDetailScreen(
39 | navController: NavHostController = rememberNavController(),
40 | workoutViewModel: WorkoutViewModel = viewModel()
41 | ) = with(workoutViewModel) {
42 |
43 | getHistoryDataDetails()
44 |
45 | Surface(modifier = Modifier.fillMaxSize(), color = darkBlue) {
46 |
47 | Column(
48 | Modifier
49 | .fillMaxSize()
50 | .padding(top = 20.dp),
51 | verticalArrangement = Arrangement.spacedBy(30.dp)
52 | ) {
53 | Heading(text = currentExerciseId, modifier = Modifier.padding(horizontal = 15.dp))
54 | ChartView(modifier = Modifier, chartData.toList())
55 | Heading(
56 | text = stringResource(R.string.log),
57 | modifier = Modifier.padding(horizontal = 15.dp)
58 | )
59 | StatsDetailsColumn(modifier = Modifier, historyData.toList())
60 |
61 |
62 | }
63 |
64 | }
65 |
66 | }
67 |
68 |
69 | @Composable
70 | fun StatsDetailsColumn(modifier: Modifier = Modifier, historyData: List) {
71 | LazyColumn(
72 | modifier = modifier
73 | .background(
74 | veryDarkBlue
75 | ), contentPadding = PaddingValues(vertical = 10.dp)
76 | ) {
77 | items(historyData) { data ->
78 | StatsDetailsItem(data)
79 | Divider(
80 | modifier = Modifier
81 | .height(1.dp)
82 | .fillMaxWidth(), color = Color.Gray.copy(0.1f)
83 | )
84 | }
85 | }
86 | }
87 |
88 | @Composable
89 | fun StatsDetailsItem(data: ExerciseHistoryItem) {
90 | Surface(
91 | color = veryDarkBlue,
92 | shape = RectangleShape,
93 | ) {
94 |
95 | Column(
96 | Modifier
97 | .fillMaxWidth()
98 | .padding(horizontal = 15.dp, vertical = 10.dp)
99 | ) {
100 |
101 | val sdf = SimpleDateFormat("dd-MM-yy", Locale.ENGLISH)
102 | val date = sdf.format(data.date).toString()
103 |
104 | Text(
105 | text = date,
106 | fontWeight = FontWeight.SemiBold,
107 | color = holoGreen,
108 | fontSize = 20.sp
109 | )
110 |
111 | Spacer(modifier = Modifier.height(10.dp))
112 | Row(
113 | verticalAlignment = Alignment.CenterVertically,
114 | modifier = Modifier
115 | .fillMaxWidth(),
116 | ) {
117 |
118 |
119 | Column(Modifier.fillMaxWidth()) {
120 |
121 | Row(
122 | horizontalArrangement = Arrangement.SpaceBetween,
123 | modifier = Modifier.fillMaxWidth()
124 |
125 | ) {
126 | Text(
127 | text = stringResource(R.string.sets_stat) + data.totalSets,
128 | color = white,
129 | fontWeight = FontWeight.Normal,
130 | fontSize = 20.sp
131 | )
132 | Text(
133 | text = stringResource(R.string.min_weight_stat) + data.minWeight,
134 | color = white,
135 | fontWeight = FontWeight.Normal,
136 | fontSize = 20.sp
137 | )
138 | }
139 |
140 | Row(
141 | horizontalArrangement = Arrangement.SpaceBetween,
142 | modifier = Modifier.fillMaxWidth()
143 | ) {
144 | Text(
145 | text = stringResource(R.string.reps_stat) + data.totalReps,
146 | color = white,
147 | fontWeight = FontWeight.Normal,
148 | fontSize = 20.sp
149 |
150 |
151 | )
152 | Text(
153 | text = stringResource(R.string.max_weight_stat) + data.maxWeight,
154 | color = white,
155 | fontWeight = FontWeight.Normal,
156 | fontSize = 20.sp
157 | )
158 | }
159 |
160 | }
161 |
162 |
163 | }
164 | }
165 |
166 |
167 | }
168 |
169 | }
170 |
171 | @Composable
172 | fun ChartView(modifier: Modifier = Modifier, chartData: List) {
173 | Surface(
174 | modifier = Modifier
175 | .fillMaxWidth()
176 | .height(300.dp),
177 | elevation = 10.dp,
178 | color = veryDarkBlue
179 | ) {
180 | Column(
181 | modifier = Modifier
182 | .wrapContentSize(align = Alignment.BottomStart)
183 | ) {
184 | Canvas(
185 | modifier = Modifier
186 | .fillMaxWidth()
187 | .height(250.dp)
188 | ) {
189 | val distance = size.width / (chartData.size + 1)
190 | var currentX = 0F
191 | val maxValue = chartData.maxOrNull() ?: 0
192 | val points = mutableListOf()
193 |
194 | chartData.forEachIndexed { index, data ->
195 | if (chartData.size >= index + 2) {
196 | val y0 = (maxValue.toDouble() - data) * (size.height / maxValue.toDouble())
197 | val x0 = currentX + distance
198 | points.add(PointF(x0, y0.toFloat()))
199 | currentX += distance
200 | }
201 | }
202 |
203 | for (i in 0 until points.size - 1) {
204 | drawLine(
205 | start = Offset(points[i].x, points[i].y),
206 | end = Offset(points[i + 1].x, points[i + 1].y),
207 | color = holoGreen,
208 | strokeWidth = 8f,
209 | pathEffect = PathEffect.cornerPathEffect(4.dp.toPx())
210 |
211 | )
212 | }
213 | }
214 | }
215 | }
216 | }
217 |
218 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/composables/workout/WorkoutCard.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.composables.workout
2 |
3 | import androidx.compose.animation.*
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.layout.*
7 | import androidx.compose.foundation.lazy.LazyColumn
8 | import androidx.compose.foundation.lazy.itemsIndexed
9 | import androidx.compose.foundation.shape.RoundedCornerShape
10 | import androidx.compose.material.Divider
11 | import androidx.compose.material.Icon
12 | import androidx.compose.material.Surface
13 | import androidx.compose.material.icons.Icons
14 | import androidx.compose.material.icons.rounded.Close
15 | import androidx.compose.runtime.*
16 | import androidx.compose.ui.Alignment
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.draw.clip
19 | import androidx.compose.ui.draw.paint
20 | import androidx.compose.ui.layout.ContentScale
21 | import androidx.compose.ui.res.painterResource
22 | import androidx.compose.ui.res.stringResource
23 | import androidx.compose.ui.tooling.preview.Preview
24 | import androidx.compose.ui.unit.dp
25 | import ken.projects.infit.R
26 | import ken.projects.infit.data.models.ExerciseItem
27 | import ken.projects.infit.ui.composables.FloatingAddButton
28 | import ken.projects.infit.ui.composables.RoundedCheckBox
29 | import ken.projects.infit.ui.composables.home.Heading
30 | import ken.projects.infit.ui.composables.home.SubHeading
31 | import ken.projects.infit.ui.composables.home.Title
32 | import ken.projects.infit.ui.theme.*
33 | import ken.projects.infit.viewmodel.WorkoutViewModel
34 |
35 | @OptIn(ExperimentalAnimationApi::class)
36 | @Composable
37 | fun WorkoutCard(
38 | exercise: ExerciseItem,
39 | modifier: Modifier = Modifier,
40 | workoutViewModel: WorkoutViewModel,
41 | onRemoveSet: (Int) -> Unit,
42 | onAddSet: () -> Unit,
43 | onItemClick: () -> Unit,
44 | ) {
45 |
46 | val sets = exercise.sets
47 | val equipment = exercise.equipments
48 | var openDialog by remember { mutableStateOf(false) }
49 |
50 | AnimatedContent(
51 | targetState = exercise.name,
52 | transitionSpec = { fadeIn() + scaleIn() with fadeOut() + scaleOut() }) {
53 | Box(
54 | modifier = modifier
55 | .width(370.dp)
56 | .height(520.dp)
57 | .clip(RoundedCornerShape(40.dp))
58 | .background(veryDarkBlue),
59 |
60 | ) {
61 | Surface(
62 | color = veryDarkBlue.copy(0.6f),
63 | modifier = Modifier
64 | .paint(
65 | painter = painterResource(id = R.drawable.workout_card_background),
66 | contentScale = ContentScale.Crop
67 | ),
68 | ) {
69 |
70 | Column(
71 | modifier = Modifier
72 | .fillMaxSize()
73 | .padding(top = 20.dp)
74 | ) {
75 | Row(
76 | horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier
77 | .fillMaxWidth()
78 | .padding(horizontal = 20.dp)
79 | ) {
80 | Heading(text = exercise.name.toString(), modifier = Modifier)
81 | Heading(text = sets.toString(), modifier = Modifier)
82 | }
83 | Row(
84 | horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier
85 | .fillMaxWidth()
86 | .padding(horizontal = 20.dp)
87 | ) {
88 | Title(text = stringResource(id = equipment?.name!!))
89 | Title(text = stringResource(R.string.sets))
90 | }
91 | Spacer(modifier = Modifier.height(5.dp))
92 | Divider(
93 | Modifier
94 | .fillMaxWidth()
95 | .height(2.dp), color = holoGreen.copy(0.5f)
96 | )
97 |
98 | Spacer(modifier = Modifier.height(5.dp))
99 | Row(
100 | horizontalArrangement = Arrangement.SpaceAround, modifier = Modifier
101 | .fillMaxWidth()
102 | .padding(horizontal = 80.dp)
103 | ) {
104 | Title(text = stringResource(R.string.kg))
105 | Title(text = stringResource(R.string.reps))
106 | }
107 |
108 |
109 |
110 | workoutViewModel.currentExercise?.let {
111 | LazyColumn(content = {
112 | it.volume?.let { list ->
113 |
114 | itemsIndexed(list) { index, data ->
115 | SetItem(
116 | set = data.set!!,
117 | reps = data.reps!!,
118 | weight = data.weight!!,
119 | modifier = Modifier,
120 | onRemoveClick = { onRemoveSet(index) },
121 | onClick = {
122 | workoutViewModel.volumeIndex = index
123 | onItemClick()
124 | workoutViewModel.selectedSetItem = data}
125 | )
126 | }
127 | }
128 | }, modifier = Modifier.height(300.dp))
129 | }
130 |
131 | Spacer(modifier = Modifier.height(10.dp))
132 | FloatingAddButton(
133 | modifier = Modifier
134 | .align(Alignment.CenterHorizontally),
135 | onClick = onAddSet
136 | )
137 |
138 |
139 | }
140 |
141 | }
142 |
143 |
144 | }
145 | }
146 |
147 |
148 | }
149 |
150 |
151 | @Preview
152 | @Composable
153 | fun SetItem(
154 | modifier: Modifier = Modifier,
155 | set: Int = 1,
156 | reps: Int = 12,
157 | weight: Double = 80.00,
158 | onRemoveClick: () -> Unit = {},
159 | onClick: () -> Unit = {}
160 | ) {
161 |
162 | var checkedState by remember { mutableStateOf(false) }
163 |
164 | Surface(
165 | shape = RoundedCornerShape(20.dp),
166 | modifier = modifier
167 | .fillMaxWidth()
168 | .padding(vertical = 8.dp, horizontal = 15.dp)
169 | .clickable { onClick() },
170 | color = darkBlue,
171 | elevation = 20.dp,
172 |
173 | ) {
174 |
175 | Row(
176 | modifier = modifier
177 | .fillMaxWidth()
178 | .padding(vertical = 10.dp),
179 | horizontalArrangement = Arrangement.SpaceAround,
180 | verticalAlignment = Alignment.CenterVertically
181 | ) {
182 |
183 | RoundedCheckBox(onCheckedChange = { checkedState = it })
184 |
185 | Row(
186 | horizontalArrangement = Arrangement.SpaceEvenly,
187 | verticalAlignment = Alignment.CenterVertically
188 | ) {
189 | SubHeading(text = weight.toString())
190 | Spacer(modifier = Modifier.width(15.dp))
191 | Icon(imageVector = Icons.Rounded.Close, contentDescription = null, tint = white)
192 | Spacer(modifier = Modifier.width(15.dp))
193 | SubHeading(text = reps.toString())
194 | }
195 | Icon(
196 | painter = painterResource(id = R.drawable.ic_remove),
197 | contentDescription = null,
198 | modifier = Modifier
199 | .size(30.dp)
200 | .clickable {
201 | onRemoveClick()
202 | },
203 | tint = holoRed
204 | )
205 |
206 | }
207 |
208 | }
209 |
210 | }
211 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/composables/workout/WorkoutPlanSetUp.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.composables.workout
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.lazy.LazyRow
6 | import androidx.compose.foundation.shape.CircleShape
7 | import androidx.compose.foundation.shape.RoundedCornerShape
8 | import androidx.compose.material.*
9 | import androidx.compose.runtime.*
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.graphics.Color
13 | import androidx.compose.ui.res.stringResource
14 | import androidx.compose.ui.text.TextStyle
15 | import androidx.compose.ui.unit.dp
16 | import androidx.compose.ui.unit.sp
17 | import androidx.navigation.NavHostController
18 | import com.chargemap.compose.numberpicker.NumberPicker
19 | import ken.projects.infit.R
20 | import ken.projects.infit.data.models.WorkoutPlan
21 | import ken.projects.infit.ui.composables.RegularButton
22 | import ken.projects.infit.ui.composables.home.Heading
23 | import ken.projects.infit.ui.composables.home.SubHeading
24 | import ken.projects.infit.ui.composables.home.Title
25 | import ken.projects.infit.ui.navigation.Screens
26 | import ken.projects.infit.ui.theme.*
27 | import ken.projects.infit.util.DifficultyLevels
28 | import ken.projects.infit.util.DifficultyLevels.Companion.Advanced
29 | import ken.projects.infit.util.DifficultyLevels.Companion.Beginner
30 | import ken.projects.infit.util.DifficultyLevels.Companion.Intermediate
31 | import ken.projects.infit.viewmodel.WorkoutViewModel
32 | import java.time.DayOfWeek
33 |
34 | @Composable
35 | fun WorkoutPlanSetUpScreen(workoutViewModel: WorkoutViewModel, navController: NavHostController) =
36 |
37 | with(workoutViewModel) {
38 |
39 | var workoutPlanName by remember { mutableStateOf("") }
40 |
41 | Surface(modifier = Modifier.fillMaxSize(), color = darkBlue) {
42 |
43 | Column(
44 | modifier = Modifier
45 | .fillMaxSize()
46 | .padding(horizontal = 5.dp),
47 | verticalArrangement = Arrangement.SpaceAround,
48 | horizontalAlignment = Alignment.Start
49 | ) {
50 |
51 | Heading(
52 | text = stringResource(R.string.set_up_workout_plan_heading),
53 | modifier = Modifier.padding(start = 5.dp, top = 10.dp)
54 | )
55 |
56 | SubHeading(
57 | text = stringResource(R.string.choose_a_name), modifier = Modifier
58 | .padding(horizontal = 5.dp)
59 | )
60 | TextField(
61 | value = workoutPlanName,
62 | onValueChange = { workoutPlanName = it },
63 | modifier = Modifier.padding(horizontal = 5.dp),
64 | textStyle = TextStyle(fontSize = 18.sp)
65 | )
66 |
67 | SubHeading(
68 | text = stringResource(R.string.select_training_days),
69 | modifier = Modifier.padding(horizontal = 5.dp)
70 | )
71 |
72 | LazyRow() {
73 | DayOfWeek.values().forEach {
74 |
75 | item {
76 |
77 | WeekdaysChipItem(day = it, onCheck = { day, checked ->
78 |
79 | if (checked) addDay(day) else removeDay(
80 | day
81 | )
82 |
83 |
84 | })
85 | }
86 |
87 | }
88 | }
89 |
90 | SubHeading(
91 | text = stringResource(R.string.difficulty_level),
92 | modifier = Modifier.padding(horizontal = 5.dp)
93 | )
94 |
95 | LazyRow() {
96 | val levels = listOf(Beginner, Intermediate, Advanced)
97 |
98 | levels.forEach {
99 |
100 | item {
101 |
102 | DifficultyLevelItems(
103 | difficulty = it,
104 | onCheck = { selectDifficulty(it) },
105 | checked = selectedDifficulty == it
106 | )
107 | }
108 |
109 | }
110 | }
111 |
112 | SubHeading(text = stringResource(R.string.duration), modifier = Modifier.padding(horizontal = 5.dp))
113 |
114 | var duration by remember { mutableStateOf(0) }
115 |
116 | Row(
117 | modifier = Modifier.padding(horizontal = 10.dp),
118 | verticalAlignment = Alignment.CenterVertically,
119 | horizontalArrangement = Arrangement.spacedBy(10.dp)
120 | ) {
121 | NumberPicker(
122 | value = duration,
123 | onValueChange = { duration = it },
124 | range = 10..120,
125 | textStyle = TextStyle(color = white),
126 | dividersColor = holoGreen,
127 | modifier = Modifier.width(100.dp)
128 | )
129 | Title(text = stringResource(R.string.minutes))
130 | }
131 |
132 | RegularButton(
133 | text = stringResource(R.string.save),
134 | modifier = Modifier.align(Alignment.CenterHorizontally)
135 | ) {
136 | if (workoutPlanName.isNotEmpty() && selectedDays.isNotEmpty()) {
137 | val workoutPlan = WorkoutPlan(
138 | name = workoutPlanName,
139 | workouts = selectedDays.toList() as ArrayList,
140 | difficulty = selectedDifficulty,
141 | duration = duration
142 | )
143 | addWorkoutPlan(workoutPlan)
144 | navController.navigate(Screens.Home.route)
145 | }
146 | }
147 | }
148 |
149 |
150 | }
151 |
152 |
153 | }
154 |
155 | @Composable
156 | fun WeekdaysChipItem(
157 | modifier: Modifier = Modifier,
158 | onCheck: (DayOfWeek, Boolean) -> Unit,
159 | day: DayOfWeek
160 | ) {
161 |
162 | val text = day.name.substring(0..1).uppercase()
163 |
164 |
165 | var checked by remember {
166 | mutableStateOf(false)
167 | }
168 | var backgroundColor by remember { mutableStateOf(lightBlue) }
169 |
170 | backgroundColor = if (checked) holoGreen else veryDarkBlue.copy(0.6f)
171 |
172 | var textColor by remember {
173 | mutableStateOf(Color.Black)
174 | }
175 |
176 |
177 | textColor = if (checked) Color.Black else white
178 |
179 | Surface(color = backgroundColor, shape = CircleShape, modifier = modifier
180 | .clickable {
181 | checked = !checked
182 | onCheck(day, checked)
183 | }
184 | .size(50.dp)
185 | .padding(5.dp),
186 | elevation = 10.dp) {
187 |
188 | Column(
189 | horizontalAlignment = Alignment.CenterHorizontally,
190 | verticalArrangement = Arrangement.SpaceAround,
191 | modifier = Modifier.fillMaxSize()
192 | ) {
193 |
194 | Text(text = text, color = textColor)
195 |
196 | }
197 |
198 |
199 | }
200 | }
201 |
202 |
203 | @Composable
204 | fun DifficultyLevelItems(
205 | difficulty: DifficultyLevels.Difficulty,
206 | onCheck: (DifficultyLevels.Difficulty) -> Unit,
207 | checked: Boolean
208 | ) {
209 |
210 | var backgroundColor by remember { mutableStateOf(holoGreen) }
211 |
212 | backgroundColor =
213 | if (checked) holoGreen else veryDarkBlue
214 |
215 | var textColor by remember {
216 | mutableStateOf(Color.Black)
217 | }
218 |
219 | textColor = if (checked) Color.Black else white
220 |
221 | Surface(color = backgroundColor, shape = RoundedCornerShape(40.dp), modifier = Modifier
222 | .clickable {
223 | onCheck(difficulty)
224 | }
225 | .padding(5.dp),
226 | elevation = 10.dp) {
227 |
228 | Row(
229 | modifier = Modifier.padding(10.dp),
230 | horizontalArrangement = Arrangement.SpaceAround,
231 | verticalAlignment = Alignment.CenterVertically
232 | ) {
233 |
234 | Text(text = stringResource(id = difficulty.difficulty!!), color = textColor)
235 |
236 | }
237 |
238 | }
239 |
240 | }
241 |
242 |
243 |
244 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/ui/composables/exercises/ExerciseDetailScreen.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.ui.composables.exercises
2 |
3 | import android.content.Intent
4 | import android.widget.Toast
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.layout.*
7 | import androidx.compose.foundation.lazy.LazyColumn
8 | import androidx.compose.foundation.lazy.items
9 | import androidx.compose.foundation.shape.RoundedCornerShape
10 | import androidx.compose.material.*
11 | import androidx.compose.material.icons.Icons
12 | import androidx.compose.material.icons.rounded.KeyboardArrowRight
13 | import androidx.compose.runtime.*
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.platform.LocalContext
17 | import androidx.compose.ui.res.stringResource
18 | import androidx.compose.ui.text.TextStyle
19 | import androidx.compose.ui.unit.dp
20 | import androidx.compose.ui.unit.sp
21 | import androidx.compose.ui.window.Dialog
22 | import androidx.navigation.NavHostController
23 | import ken.projects.infit.R
24 | import ken.projects.infit.data.models.*
25 | import ken.projects.infit.ui.composables.FloatingAddButton
26 | import ken.projects.infit.ui.composables.RegularButton
27 | import ken.projects.infit.ui.composables.home.Heading
28 | import ken.projects.infit.ui.composables.home.SubHeading
29 | import ken.projects.infit.ui.theme.darkBlue
30 | import ken.projects.infit.ui.theme.holoGreen
31 | import ken.projects.infit.ui.theme.lightBlue
32 | import ken.projects.infit.ui.theme.white
33 | import ken.projects.infit.viewmodel.WorkoutViewModel
34 |
35 | @OptIn(ExperimentalMaterialApi::class)
36 | @Composable
37 | fun ExerciseDetailScreen(
38 | modifier: Modifier = Modifier,
39 | navController: NavHostController,
40 | workoutViewModel: WorkoutViewModel
41 | ) = with(workoutViewModel) {
42 |
43 | getExercises()
44 |
45 | val heading = selectedMuscleGroup
46 |
47 | val exercises = filteredExerciseList
48 |
49 | var openDialog by remember { mutableStateOf(false) }
50 |
51 | var selectedExercise by remember { mutableStateOf("") }
52 |
53 | LaunchedEffect(key1 = openDialog, block = {getExercises()})
54 |
55 | val equipments = equipments().toSet().toList()
56 | var selectedEquipment by remember { mutableStateOf(equipments[0]) }
57 | var eqBoxExpanded by remember { mutableStateOf(false) }
58 |
59 |
60 | if (openDialog) Dialog(onDismissRequest = { openDialog = false })
61 | {
62 | Surface(
63 | modifier = Modifier.fillMaxWidth(),
64 | color = lightBlue,
65 | shape = RoundedCornerShape(40.dp)
66 | ) {
67 |
68 | Column(
69 | verticalArrangement = Arrangement.spacedBy(20.dp),
70 | horizontalAlignment = Alignment.Start,
71 | modifier = Modifier
72 | .fillMaxWidth()
73 | .padding(20.dp)
74 |
75 | ) {
76 | SubHeading(text = stringResource(R.string.add_new_exercise))
77 |
78 | TextField(
79 | readOnly = false,
80 | value = selectedExercise,
81 | onValueChange = { selectedExercise = it },
82 | label = { Text(stringResource(R.string.exercise).lowercase(), color = holoGreen) },
83 | textStyle = TextStyle(fontSize = 20.sp)
84 | )
85 |
86 | ExposedDropdownMenuBox(
87 | expanded = eqBoxExpanded,
88 | onExpandedChange = {
89 | eqBoxExpanded = !eqBoxExpanded
90 | }
91 | ) {
92 | TextField(
93 | readOnly = true,
94 | value = stringResource(selectedEquipment.name!!),
95 | onValueChange = { },
96 | label = { Text(stringResource(R.string.equipment), color = holoGreen) },
97 | trailingIcon = {
98 | ExposedDropdownMenuDefaults.TrailingIcon(
99 | expanded = eqBoxExpanded,
100 |
101 | )
102 | },
103 | colors = ExposedDropdownMenuDefaults.textFieldColors(
104 | trailingIconColor = holoGreen,
105 | focusedTrailingIconColor = holoGreen,
106 | disabledTrailingIconColor = holoGreen
107 | ),
108 | textStyle = TextStyle(fontSize = 20.sp)
109 | )
110 | ExposedDropdownMenu(
111 | expanded = eqBoxExpanded,
112 | onDismissRequest = {
113 | eqBoxExpanded = false
114 | }
115 | ) {
116 | equipments.forEach { selectionOption ->
117 | DropdownMenuItem(
118 | onClick = {
119 | selectedEquipment = selectionOption
120 | eqBoxExpanded = false
121 | }
122 | ) {
123 | Text(text = stringResource(selectionOption.name!!))
124 | }
125 | }
126 | }
127 | }
128 |
129 | val context = LocalContext.current
130 |
131 | RegularButton(
132 | text = stringResource(R.string.add),
133 | modifier = Modifier.align(Alignment.CenterHorizontally),
134 | onClick = {
135 | getExercises()
136 |
137 | if (selectedExercise.isNotEmpty()) {
138 | openDialog = false
139 | addNewExercise(
140 | Exercise(
141 | name = selectedExercise.replaceFirstChar { it.uppercase() },
142 | equipments = selectedEquipment.equipments,
143 | targetMuscles = muscleGroup.toList()
144 | )
145 | )
146 | getExercises()
147 | } else {
148 | Toast.makeText(
149 | context,
150 | context.getString(R.string.please_enter_exercise_name),
151 | Toast.LENGTH_SHORT
152 | ).show()
153 | }
154 |
155 |
156 | }
157 | )
158 | }
159 |
160 | }
161 | }
162 |
163 |
164 | Surface(color = darkBlue, modifier = Modifier.fillMaxSize()) {
165 |
166 | Column(
167 | modifier = Modifier
168 | .fillMaxSize()
169 | .padding(top = 20.dp)
170 | .padding(horizontal = 15.dp),
171 | verticalArrangement = Arrangement.spacedBy(40.dp)
172 | ) {
173 |
174 | Heading(text = heading)
175 |
176 | LazyColumn(modifier = Modifier.height(500.dp)) {
177 |
178 | items(exercises) { exercise ->
179 | val context = LocalContext.current
180 | ExerciseItem(exercise = exercise, modifier = Modifier.clickable {
181 | val intent = Intent(Intent.ACTION_SEARCH)
182 | intent.setPackage("com.google.android.youtube")
183 | intent.putExtra("query", "${exercise.name} tutorial gym")
184 | intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
185 | context.startActivity(intent)
186 | })
187 | Spacer(modifier = Modifier.height(20.dp))
188 |
189 | }
190 |
191 | }
192 |
193 | FloatingAddButton(
194 | onClick = { openDialog = true },
195 | modifier = Modifier.align(Alignment.CenterHorizontally)
196 | )
197 |
198 | }
199 |
200 | }
201 |
202 | }
203 |
204 | @Composable
205 | fun ExerciseItem(exercise: Exercise, modifier: Modifier = Modifier) {
206 | Row(
207 | horizontalArrangement = Arrangement.SpaceBetween,
208 | modifier = modifier.fillMaxWidth(),
209 | verticalAlignment = Alignment.CenterVertically
210 | ) {
211 |
212 | SubHeading(text = exercise.name.toString(), color = white)
213 | Icon(
214 | imageVector = Icons.Rounded.KeyboardArrowRight,
215 | contentDescription = null,
216 | tint = holoGreen,
217 | modifier = Modifier.size(50.dp)
218 | )
219 |
220 |
221 | }
222 | }
223 |
224 |
225 |
226 |
227 |
--------------------------------------------------------------------------------
/app/src/main/java/ken/projects/infit/data/repository/WorkoutRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package ken.projects.infit.data.repository
2 |
3 | import android.app.Application
4 | import android.util.Log
5 | import androidx.lifecycle.MutableLiveData
6 | import com.google.firebase.auth.FirebaseAuth
7 | import com.google.firebase.firestore.DocumentSnapshot
8 | import com.google.firebase.firestore.FieldValue
9 | import com.google.firebase.firestore.QuerySnapshot
10 | import com.google.firebase.firestore.ktx.firestore
11 | import com.google.firebase.ktx.Firebase
12 | import ken.projects.infit.data.models.*
13 | import ken.projects.infit.domain.WorkoutRepository
14 | import ken.projects.infit.util.Resource
15 | import kotlinx.coroutines.Dispatchers
16 | import kotlinx.coroutines.tasks.await
17 | import kotlinx.coroutines.withContext
18 | import java.text.SimpleDateFormat
19 | import java.util.*
20 | import javax.inject.Inject
21 |
22 | class WorkoutRepositoryImpl @Inject constructor(
23 | private val app: Application,
24 | ) : WorkoutRepository {
25 |
26 |
27 |
28 | private var workoutPlanId: String? = null
29 |
30 | private val userCollection = Firebase.firestore.collection("users")
31 |
32 |
33 | override suspend fun deleteWorkoutPlan(workoutPlanId: String,uid:String) {
34 | withContext(Dispatchers.IO) {
35 | try {
36 |
37 | userCollection.document(uid).collection("workout_plan")
38 | .document(workoutPlanId).delete()
39 | userCollection.document(uid).collection("workout_plan")
40 | .document(workoutPlanId).collection("workouts").get()
41 | .addOnCompleteListener {
42 | val docs = it.result.documents
43 |
44 | docs.forEach { doc ->
45 | userCollection.document(uid).collection("workout_plan")
46 | .document(workoutPlanId)
47 | .collection("workouts").document(doc.id).delete()
48 | userCollection.document(uid).collection("workout_plan")
49 | .document(workoutPlanId)
50 | .collection("workouts").document(doc.id).collection("exercises")
51 | .get()
52 | .addOnCompleteListener {
53 | val exercises = it.result.documents
54 | exercises.forEach { e ->
55 | userCollection.document(uid).collection("workout_plan")
56 | .document(workoutPlanId)
57 | .collection("workouts").document(doc.id)
58 | .collection("exercises").document(e.id).delete()
59 | }
60 |
61 | }
62 | }
63 | }
64 |
65 | } catch (e: Exception) {
66 | e.printStackTrace()
67 | }
68 | }
69 | }
70 |
71 | override suspend fun addWorkoutPlan(
72 | workoutPlan: WorkoutPlan,
73 | workouts: ArrayList,
74 | uid:String
75 | ): Resource {
76 |
77 | return withContext(Dispatchers.IO) {
78 |
79 | try {
80 | val result =
81 |
82 | // create workout_plan document
83 | userCollection.document(uid).collection("workout_plan")
84 | .document(workoutPlan.name.toString()).set(workoutPlan).await()
85 |
86 | // add workouts collection to workout_plan
87 | workouts.forEach {
88 | userCollection.document(uid).collection("workout_plan")
89 | .document(workoutPlan.name.toString())
90 | .collection("workouts").document(it.dayOfWeek.toString()).set(it)
91 | .await()
92 | }
93 |
94 |
95 | Resource.Success(result)
96 | } catch (e: Exception) {
97 | e.printStackTrace()
98 | Resource.Error(e.message.toString())
99 | }
100 |
101 | }
102 | }
103 |
104 | override suspend fun getWorkoutPlan(uid:String): Resource {
105 | return withContext(Dispatchers.IO) {
106 |
107 | try {
108 | val result = userCollection.document(uid).collection("workout_plan")
109 | .get().await()
110 |
111 | workoutPlanId = result?.documents?.firstOrNull()?.id
112 |
113 | Resource.Success(result)
114 | } catch (e: Exception) {
115 | e.printStackTrace()
116 | Resource.Error(e.message.toString())
117 |
118 | }
119 |
120 | }
121 | }
122 |
123 | override suspend fun addWorkouts(uid:String): Resource {
124 | TODO("Not yet implemented")
125 | }
126 |
127 | fun editWorkout(uid:String) {
128 | workoutPlanId?.let {
129 |
130 | userCollection.document(uid).collection("workout_plan")
131 | .document(it).collection("workouts").document("workoutId")
132 | .update("exerciseItems", FieldValue.arrayUnion())
133 | }
134 | }
135 |
136 | override suspend fun getHistoryData(uid:String): Resource {
137 |
138 | return withContext(Dispatchers.IO) {
139 |
140 | try {
141 |
142 | val result =
143 | userCollection.document(uid).collection("exercise_history").get().await()
144 |
145 |
146 | Resource.Success(result)
147 | } catch (e: Exception) {
148 |
149 | e.printStackTrace()
150 | Resource.Error(e.message.toString())
151 | }
152 |
153 | }
154 |
155 |
156 | }
157 |
158 | override suspend fun getWorkouts(uid:String): Resource {
159 | return withContext(Dispatchers.IO) {
160 | try {
161 |
162 | val result =
163 | userCollection.document(uid).collection("workout_plan")
164 | .document(workoutPlanId!!).collection("workouts").get()
165 | .await()
166 |
167 | Resource.Success(result)
168 |
169 | } catch (e: Exception) {
170 | e.printStackTrace()
171 | Resource.Error(e.message.toString())
172 | }
173 | }
174 | }
175 |
176 |
177 | override suspend fun getUser(uid:String): Resource {
178 |
179 | return withContext(Dispatchers.IO) {
180 | try {
181 |
182 | val result = userCollection.document(uid).get().await()
183 |
184 | Resource.Success(result)
185 |
186 | } catch (e: Exception) {
187 | e.printStackTrace()
188 | Resource.Error(e.message!!)
189 | }
190 | }
191 |
192 | }
193 |
194 | override suspend fun addExerciseToWorkout(
195 | exerciseItem: ExerciseItem,
196 | workoutId: String,
197 | uid:String
198 | ): Resource {
199 | return withContext(Dispatchers.IO) {
200 |
201 | try {
202 | val result =
203 | userCollection.document(uid).collection("workout_plan")
204 | .document(workoutPlanId!!).collection("workouts")
205 | .document(workoutId)
206 | .update("exerciseItems", FieldValue.arrayUnion(exerciseItem)).await()
207 | Resource.Success(result)
208 |
209 | } catch (e: Exception) {
210 | e.printStackTrace()
211 | Resource.Error(e.message.toString())
212 | }
213 |
214 | }
215 | }
216 |
217 | override suspend fun updateWorkout(
218 | workoutId: String,
219 | exerciseItem: ExerciseItem?,
220 | volume: ExerciseVolume?,
221 | workout: Workout,
222 | uid:String
223 | ): Resource {
224 | return withContext(Dispatchers.IO) {
225 | try {
226 | val result =
227 | userCollection.document(uid).collection("workout_plan")
228 | .document(workoutPlanId!!).collection("workouts")
229 | .document(workoutId).set(workout).await()
230 |
231 | Resource.Success(result)
232 |
233 | } catch (e: Exception) {
234 | e.printStackTrace()
235 | Resource.Error(e.message.toString())
236 | }
237 | }
238 | }
239 |
240 |
241 | override suspend fun addNewExercise(exercise: Exercise,uid:String): Resource {
242 | return withContext(Dispatchers.IO) {
243 | try {
244 |
245 | val result =
246 | userCollection.document(uid).collection("exercises")
247 | .document(exercise.name.toString()).set(exercise).await()
248 |
249 | Resource.Success(result)
250 |
251 | } catch (e: Exception) {
252 | e.printStackTrace()
253 | Resource.Error(e.message.toString())
254 | }
255 | }
256 | }
257 |
258 | override suspend fun addExerciseHistory(exercise: ExerciseHistoryItem,uid:String) {
259 |
260 | val sdf = SimpleDateFormat("dd-MM-yyyy HH:mm", Locale.ENGLISH)
261 | val date = sdf.format(exercise.date).toString()
262 |
263 |
264 | userCollection.document(uid).collection("exercise_history")
265 | .document(exercise.exercise?.name.toString())
266 | .set(Exercise(name = exercise.exercise?.name.toString())).addOnCompleteListener {
267 |
268 | if (it.isSuccessful) {
269 |
270 | userCollection.document(uid).collection("exercise_history")
271 | .document(exercise.exercise?.name.toString())
272 | .collection("data")
273 | .document(date)
274 | .set(exercise)
275 | }
276 |
277 | }.await()
278 |
279 |
280 | }
281 |
282 | override suspend fun getHistoryDataDetails(exerciseId: String,uid: String): Resource {
283 |
284 | return withContext(Dispatchers.IO) {
285 |
286 | try {
287 | val result =
288 | userCollection.document(uid).collection("exercise_history")
289 | .document(exerciseId).collection("data").get().await()
290 | Resource.Success(result)
291 | } catch (e: Exception) {
292 | e.printStackTrace()
293 | Log.e("failed to get history data", e.message.toString())
294 | Resource.Error(e.message.toString())
295 | }
296 | }
297 |
298 | }
299 |
300 | override suspend fun getExercises(uid:String): MutableLiveData> {
301 |
302 | val data = MutableLiveData>()
303 |
304 | userCollection.document(uid).collection("exercises")
305 | .addSnapshotListener { snapshot, error ->
306 |
307 | snapshot?.let {
308 | data.value = Resource.Success(it)
309 |
310 | }
311 |
312 | error?.let {
313 | data.value = Resource.Error(it.message.toString())
314 | Log.e("Error", data.value?.message.toString())
315 | return@addSnapshotListener
316 | }
317 |
318 | }
319 |
320 |
321 | return data
322 | }
323 |
324 |
325 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2023 Kenidiid Ali Hassan
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------