├── 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 | 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 | ![](https://github.com/KenAli77/InFit/blob/master/inFit_banner_1.png) 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 | ![](https://github.com/KenAli77/InFit/blob/master/inFit_banner_2.png) 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 | 19 | 20 | 21 | 22 | 23 | 24 | 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 | --------------------------------------------------------------------------------