diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..405b9af --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Get up \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..a0de2a1 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..5c9f89f --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..044bdd9 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,52 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + id 'kotlin-parcelize' +} + +android { + namespace 'com.practice.getup' + compileSdk 32 + + defaultConfig { + applicationId "com.practice.getup" + minSdk 22 + targetSdk 32 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + + buildFeatures{ + viewBinding = true + } +} + +dependencies { + + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'com.google.android.material:material:1.6.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + implementation 'com.github.bumptech.glide:glide:4.12.0' + + +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/practice/getup/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/practice/getup/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..953d583 --- /dev/null +++ b/app/src/androidTest/java/com/practice/getup/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.practice.getup + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.practice.getup", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/debug/ic_double_arrow-playstore.png b/app/src/debug/ic_double_arrow-playstore.png new file mode 100644 index 0000000..59a7062 Binary files /dev/null and b/app/src/debug/ic_double_arrow-playstore.png differ diff --git a/app/src/debug/res/values/ic_double_arrow_background.xml b/app/src/debug/res/values/ic_double_arrow_background.xml new file mode 100644 index 0000000..9ff46fc --- /dev/null +++ b/app/src/debug/res/values/ic_double_arrow_background.xml @@ -0,0 +1,4 @@ + + + #F44336 + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ae28d58 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_double_arrow-playstore.png b/app/src/main/ic_double_arrow-playstore.png new file mode 100644 index 0000000..f849b8e Binary files /dev/null and b/app/src/main/ic_double_arrow-playstore.png differ diff --git a/app/src/main/java/com/practice/getup/activities/DetailActivity.kt b/app/src/main/java/com/practice/getup/activities/DetailActivity.kt new file mode 100644 index 0000000..55461d7 --- /dev/null +++ b/app/src/main/java/com/practice/getup/activities/DetailActivity.kt @@ -0,0 +1,47 @@ +package com.practice.getup.activities + +import android.net.Uri +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import com.practice.getup.R +import com.practice.getup.databinding.ActivityDetailBinding + +class DetailActivity : AppCompatActivity() { + + private lateinit var binding: ActivityDetailBinding + + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityDetailBinding.inflate(layoutInflater) + setContentView(binding.root) + + loadVideo() + + } + + + + companion object{ + const val VIDEO_ID = "VIDEO_ID" + } + + private fun loadVideo(){ + + val mediaController = android.widget.MediaController(this) + mediaController.setAnchorView(binding.videoView1) + + val intRes = intent.getIntExtra(VIDEO_ID, R.raw.pushups_video) + val offlineVideoUri: Uri = + Uri.parse("android.resource://$packageName/${intRes}") + + with(binding.videoView1) { + setMediaController(mediaController) + setVideoURI(offlineVideoUri) + requestFocus() + start() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/practice/getup/activities/ListActivity.kt b/app/src/main/java/com/practice/getup/activities/ListActivity.kt new file mode 100644 index 0000000..4b9c9f7 --- /dev/null +++ b/app/src/main/java/com/practice/getup/activities/ListActivity.kt @@ -0,0 +1,67 @@ +package com.practice.getup.activities + +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.snackbar.Snackbar +import com.practice.getup.R +import com.practice.getup.adapters.ListAdapter +import com.practice.getup.data.DataSource +import com.practice.getup.databinding.ActivityListBinding +import com.practice.getup.model.Exercise + +class ListActivity : AppCompatActivity() { + + private lateinit var binding: ActivityListBinding + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityListBinding.inflate(layoutInflater) + setContentView(binding.root) + + loadRecycleView(DataSource().loadExercise()) + + with(binding) + { + chipEasy.setOnClickListener { + loadRecycleView(DataSource().loadEasyList()) + } + chipMedium.setOnClickListener { + loadRecycleView(DataSource().loadMediumList()) + } + chipHard.setOnClickListener { + loadRecycleView(DataSource().loadHardList()) + } + chipAll.setOnClickListener { + loadRecycleView(DataSource().loadExercise()) + } + } + } + + private fun loadRecycleView(data: List) { + + binding.recyclerView.layoutManager = LinearLayoutManager(this) + binding.recyclerView.adapter = ListAdapter(this,data, object: ListAdapter.ExerciseActionListener{ + override fun onDeleteExercise(exercise: Exercise) { + Snackbar.make(binding.recyclerView, R.string.snackbar_delete, Snackbar.LENGTH_SHORT) + .setAction(R.string.snackbar_got_it) {} + .show() + } + override fun onClickExercise(exercise: Exercise) { + val intent = Intent(this@ListActivity, DetailActivity::class.java) + intent.putExtra(DetailActivity.VIDEO_ID,exercise.videoResource) + startActivity(intent) + } + override fun onAddExercise(exercise: Exercise) { + Snackbar.make(binding.recyclerView, R.string.snackbar_delete, Snackbar.LENGTH_SHORT) + .setAction(R.string.snackbar_got_it) {} + .show() + } + }) + + binding.recyclerView.setHasFixedSize(true) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/practice/getup/activities/MainActivity.kt b/app/src/main/java/com/practice/getup/activities/MainActivity.kt new file mode 100644 index 0000000..bea598c --- /dev/null +++ b/app/src/main/java/com/practice/getup/activities/MainActivity.kt @@ -0,0 +1,82 @@ +package com.practice.getup.activities + +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import com.practice.getup.databinding.ActivityMainBinding +import com.practice.getup.model.Options + + +class MainActivity : AppCompatActivity() { + + + private lateinit var binding: ActivityMainBinding + private lateinit var options: Options + private lateinit var launcherOptions: ActivityResultLauncher + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + options = savedInstanceState?.getParcelable(KEY_OPTIONS) ?: Options.DEFAULT + + binding.testTextView0.text = options.preparingTime.toString() + binding.testTextView1.text = options.workTime.toString() + binding.testTextView2.text = options.restTime.toString() + binding.testTextView3.text = options.numberOfSets.toString() + binding.testTextView4.text = this.getString(options.exerciseType) + + launcherOptions = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> + + if (result.resultCode == RESULT_OK) { + options = result.data?.getParcelableExtra(BACK_OPTIONS)!! + binding.testTextView0.text = options.preparingTime.toString() + binding.testTextView1.text = options.workTime.toString() + binding.testTextView2.text = options.restTime.toString() + binding.testTextView3.text = options.numberOfSets.toString() + binding.testTextView4.text = this.getString(options.exerciseType) + } + } + + binding.buttonSettings.setOnClickListener { onOptionsClick() } + + binding.buttonWatchList.setOnClickListener { onListClick() } + + binding.buttonStart.setOnClickListener { onStartClick() } + + } + + + private fun onOptionsClick() { + launcherOptions.launch(Intent(this, OptionsActivity::class.java)) + + } + + private fun onListClick() { + val intent = Intent(this, ListActivity::class.java) + startActivity(intent) + } + + private fun onStartClick(){ + val intent = Intent(this, WorkoutActivity::class.java) + intent.putExtra(KEY_OPTIONS, options) + startActivity(intent) + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putParcelable(KEY_OPTIONS, options) + } + + private companion object { + const val KEY_OPTIONS = "OPTIONS" + const val BACK_OPTIONS = "BACK OPTIONS" + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/practice/getup/activities/OptionsActivity.kt b/app/src/main/java/com/practice/getup/activities/OptionsActivity.kt new file mode 100644 index 0000000..e07507b --- /dev/null +++ b/app/src/main/java/com/practice/getup/activities/OptionsActivity.kt @@ -0,0 +1,173 @@ +package com.practice.getup.activities +import android.app.Activity +import android.content.Context +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.view.View +import android.view.inputmethod.InputMethodManager +import androidx.core.widget.addTextChangedListener +import com.google.android.material.snackbar.Snackbar +import com.practice.getup.R +import com.practice.getup.databinding.ActivityOptionsBinding +import com.practice.getup.model.Options + +class OptionsActivity : AppCompatActivity() { + + private lateinit var binding: ActivityOptionsBinding + private lateinit var options: Options + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityOptionsBinding.inflate(layoutInflater).also { setContentView(it.root) } + + options = Options.DEFAULT + + //биндинги, чтобы прочекать как присваиваются значния из эдит текста. Если юзать их, + //то включи и в лэйауте + /* binding.testTextView1.text = options.workTime.toString() + binding.testTextView2.text = options.restTime.toString() + binding.testTextView3.text = options.numberOfSets.toString() + binding.testTextView4.text = this.getString(options.exerciseType)*/ + + /*binding.optionsOkButton.setOnClickListener { sendOptionsBack() }*/ + + with(binding) { + optionsOkButton.setOnClickListener { + setOptions() + } + totalWorkoutTime.text = calculateTotalTime() + + editPreparationTime.addTextChangedListener { + binding.totalWorkoutTime.text = countTotalTime() + + val preparationTimeInput = it.toString().toIntOrNull() + if (preparationTimeInput == 0) { + showZeroException(R.string.number_format_exception_preparation) + } + } + + editWorkTime.addTextChangedListener { + binding.totalWorkoutTime.text = countTotalTime() + + val workTimeInput = it.toString().toIntOrNull() + if (workTimeInput == 0) { + showZeroException(R.string.number_format_exception_work) + } + } + + editRestTime.addTextChangedListener { + binding.totalWorkoutTime.text = countTotalTime() + + val restTimeInput = it.toString().toIntOrNull() + if (restTimeInput == 0) { + showZeroException(R.string.number_format_exception_rest) + } + } + + editSetsNumber.addTextChangedListener { + binding.totalWorkoutTime.text = countTotalTime() + + val numberOfSetsInput = it.toString().toIntOrNull() + if (numberOfSetsInput == 0) { + showZeroException(R.string.number_format_exception_sets) + } + } + } + } + + /* override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putParcelable(KEY_OPTIONS, options) + }*/ + + private fun setOptions(){ + + val preparationTimeInput = binding.editPreparationTime.text.toString().toIntOrNull() ?: 10 + val workTimeInput = binding.editWorkTime.text.toString().toIntOrNull() ?: 30 + val restTimeInput = binding.editRestTime.text.toString().toIntOrNull() ?: 60 + val numberOfSets = binding.editSetsNumber.text.toString().toIntOrNull() ?: 5 + + + if (preparationTimeInput == 0) { + showZeroException(R.string.number_format_exception_preparation) + return + } + if (workTimeInput == 0) { + showZeroException(R.string.number_format_exception_work) + return + } + if (restTimeInput == 0) { + showZeroException(R.string.number_format_exception_rest) + return + } + if (numberOfSets == 0) { + showZeroException(R.string.number_format_exception_sets) + return + } + + //биндинги, чтобы прочекать как присваиваются значния из эдит текста. Если юзать их, + //то включи и в лэйауте + /* binding.testTextView1.text = workTimeInput.toString() + binding.testTextView2.text = restTimeInput.toString() + binding.testTextView3.text = numberOfSets.toString()*/ + + hideKeyboard(binding.optionsActivity) + + options = Options(preparationTimeInput, workTimeInput, restTimeInput, numberOfSets, + R.string.exercise3 + ) + + val backOptionsIntent = Intent() + backOptionsIntent.putExtra(BACK_OPTIONS, options ) + setResult(Activity.RESULT_OK, backOptionsIntent) + finish() + } + + + private fun showZeroException(exceptionString: Int){ + Snackbar.make(binding.optionsActivity, exceptionString, 20000) + .setAction(R.string.snackbar_ok_button) {} + .show() + hideKeyboard(binding.optionsActivity) + } + + + private fun calculateTotalTime(preparationTime: Int = 10, workTime: Int = 30, restTime: Int = 60, numberOfSets: Int = 5): String{ + + val totalSeconds = (workTime + restTime) * numberOfSets + preparationTime + val secondsToShow = totalSeconds % 60 + val minutesToShow = (totalSeconds / 60) % 60 + val hoursToShow = totalSeconds / 3600 + + return "$hoursToShow : $minutesToShow : $secondsToShow" + + } + + private fun countTotalTime(): String { + + val preparationTimeInput = binding.editPreparationTime.text.toString().toIntOrNull() ?: 10 + val workTimeInput = binding.editWorkTime.text.toString().toIntOrNull() ?: 30 + val restTimeInput = binding.editRestTime.text.toString().toIntOrNull() ?: 60 + val numberOfSetsInput = binding.editSetsNumber.text.toString().toIntOrNull() ?: 5 + + return calculateTotalTime(preparationTimeInput, workTimeInput, restTimeInput, numberOfSetsInput) + + } + + //hide keyboard when Snackbar is shown. PS: view-parameter is a current Edit text shown + private fun hideKeyboard(view: View) { + val inputMethodManager = + getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0) + } + + companion object { + const val BACK_OPTIONS = "BACK OPTIONS" + private const val KEY_OPTIONS = "KEY_OPTIONS" + } + +} + + diff --git a/app/src/main/java/com/practice/getup/activities/WorkoutActivity.kt b/app/src/main/java/com/practice/getup/activities/WorkoutActivity.kt new file mode 100644 index 0000000..c9d3837 --- /dev/null +++ b/app/src/main/java/com/practice/getup/activities/WorkoutActivity.kt @@ -0,0 +1,214 @@ +package com.practice.getup.activities + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.os.CountDownTimer +import android.view.View +import com.practice.getup.R +import com.practice.getup.databinding.ActivityWorkoutBinding +import com.practice.getup.model.Options + +class WorkoutActivity : AppCompatActivity() { + + private lateinit var binding: ActivityWorkoutBinding + private lateinit var options: Options + private lateinit var timer: CountDownTimer + + private var totalTimeForLocalTimer: Long = 0 + private var totalTimeForGlobalTimer: Long = 0 + private var isTimerOn = false + + private var setsDone = -1 + private var isWorkTime: Boolean? = null + private var timePassed: Long = 0 + + + override fun onCreate(savedInstanceState: Bundle?) { + + super.onCreate(savedInstanceState) + + options = intent.getParcelableExtra(OPTIONS)!! + binding = ActivityWorkoutBinding.inflate(layoutInflater).also { setContentView(it.root) } + + + val preparationTime = (options.preparingTime * 1000).toLong() + + val totalWorkoutTime = + with(options) { ((workTime + restTime) * numberOfSets + preparingTime) * 1000 }.toLong() + + totalTimeForLocalTimer = preparationTime + totalTimeForGlobalTimer = totalWorkoutTime + + binding.startButton.setOnClickListener { + startTimer() + } + binding.pauseButton.setOnClickListener { pauseTimer() } + + binding.restartButton.setOnClickListener { restartTimer() } + + + } + + private fun startTimer() { + + if (isTimerOn) return + + timer = object : CountDownTimer(totalTimeForLocalTimer, 1000) { + + val workTime = (options.workTime * 1000).toLong() + val restTime = (options.restTime * 1000).toLong() + val preparationTime = (options.preparingTime * 1000).toLong() + val numberOfSets = (options.numberOfSets) + val totalWorkoutTime = (workTime + restTime) * numberOfSets + preparationTime + + val fixedTimeForSet = when (isWorkTime) { + null -> preparationTime + true -> workTime + false -> restTime + } + + override fun onTick(millisUntilFinished: Long) { + + //allows to resume to the timer with the same time left + totalTimeForLocalTimer = millisUntilFinished + + updateLocalTime(millisUntilFinished) + updateGlobalTime(fixedTimeForSet, millisUntilFinished) + updateGlobalProgressIndicator( + totalWorkoutTime, + fixedTimeForSet, + millisUntilFinished + ) + } + + override fun onFinish() { + setsDone++ + isTimerOn = false + + timePassed += when (isWorkTime) { + null -> preparationTime + true -> workTime + false -> restTime + } + + totalTimeForGlobalTimer -= fixedTimeForSet + binding.currentStageView.text = setsDone.toString() + + if (isWorkTime == true || isWorkTime == null) { + totalTimeForLocalTimer = restTime + isWorkTime = false + + } else { + totalTimeForLocalTimer = workTime + isWorkTime = true + } + + if (setsDone == options.numberOfSets * 2) { + binding.currentStageView.text = "Тренировка закончена" + binding.startButton.visibility = View.INVISIBLE + binding.pauseButton.visibility = View.INVISIBLE + binding.restartButton.visibility = View.VISIBLE + return + } + startTimer() + + } + }.start() + isTimerOn = true + switchControlButton() + } + + //потом подумать над текстовыми полями над таймером + private fun pauseTimer() { + if (!isTimerOn) return + timer.cancel() + isTimerOn = false + switchControlButton() + + } + + //перенести все в отдельный класс таймер после добавления текстовых полей и тд + private fun restartTimer() { + if (isTimerOn) return + //simply sets all values to default, может как то упростить установку на дефолт + totalTimeForLocalTimer = ((options.preparingTime) * 1000).toLong() + totalTimeForGlobalTimer = + with(options) { ((workTime + restTime) * numberOfSets + preparingTime) * 1000 }.toLong() + setsDone = -1 + isWorkTime = null + timePassed = 0 + startTimer() + binding.restartButton.visibility = View.INVISIBLE + } + + private fun switchControlButton() { + if (isTimerOn) { + binding.startButton.visibility = View.INVISIBLE + binding.pauseButton.visibility = View.VISIBLE + } else { + binding.pauseButton.visibility = View.INVISIBLE + binding.startButton.visibility = View.VISIBLE + binding.startButton.text = resources.getText(R.string.resume_button) + } + } + + private fun updateGlobalTime(fixedTimeForSet: Long, millisUntilFinished: Long) { + val timePassed = fixedTimeForSet - millisUntilFinished + val totalSecondsLeft = ((totalTimeForGlobalTimer - timePassed) / 1000).toInt() + binding.generalTimerView.text = calculateTimeToShow(totalSecondsLeft) + } + + private fun updateLocalTime(millisUntilFinished: Long) { + val totalSecondsLeft = (millisUntilFinished / 1000).toInt() + binding.localTimerView.text = calculateTimeToShow(totalSecondsLeft) + } + + //padStart adds 0 before time figure if necessary + private fun calculateTimeToShow(totalSecondsLeft: Int): String { + + val secondsToShow = (totalSecondsLeft % 60).toString().padStart(2, '0') + val minutesToShow = ((totalSecondsLeft / 60) % 60).toString().padStart(2, '0') + val hoursToShow = (totalSecondsLeft / 3600) + + val timeToShow = when { + (hoursToShow > 0) -> { + hoursToShow.toString().padStart(2, '0') + "$hoursToShow : $minutesToShow : $secondsToShow" + } + else -> "$minutesToShow : $secondsToShow" + } + return timeToShow + } + + private fun updateGlobalProgressIndicator( + totalTimeMs: Long, + fixedTimeForSet: Long, + millisUntilFinished: Long + ) { + val totalTimeSec = (totalTimeMs / 1000).toDouble() + val timePassedSec = (((fixedTimeForSet + timePassed) - millisUntilFinished) / 1000).toDouble() + binding.globalProgressIndicator.progress = (timePassedSec / totalTimeSec * 100).toInt() + } + + private fun switchStagesNames(){ + with(binding){ + when (isWorkTime) { + null -> { + /*currentStageView.text = resources.*/ + } + true -> { + + } + false -> { + + } + } + } + + } + + + companion object { + const val OPTIONS = "OPTIONS" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/practice/getup/adapters/ListAdapter.kt b/app/src/main/java/com/practice/getup/adapters/ListAdapter.kt new file mode 100644 index 0000000..b932c9f --- /dev/null +++ b/app/src/main/java/com/practice/getup/adapters/ListAdapter.kt @@ -0,0 +1,106 @@ +package com.practice.getup.adapters + +import android.content.Context +import android.graphics.Color +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.practice.getup.R +import com.practice.getup.databinding.ListItemBinding +import com.practice.getup.model.Exercise + + +class ListAdapter( + + private val context: Context, + private val dataSet: List, + private val actionListener: ExerciseActionListener + +): RecyclerView.Adapter(), View.OnClickListener{ + + //create viewholder for adapter via binding + class ListViewHolder( + val binding: ListItemBinding + ): RecyclerView.ViewHolder(binding.root) + + //create onClick method (necessary) + override fun onClick(v: View?) { + val exercise = v?.tag as Exercise + when (v.id) { + R.id.button_delete -> actionListener.onDeleteExercise(exercise) + R.id.button_add -> actionListener.onAddExercise(exercise) + else -> actionListener.onClickExercise(exercise) + } + } + + //standard 3 functions for recyclerview adapter are below + //don`t forget do add clickListeners for action buttons and body of ItemView + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = ListItemBinding.inflate(inflater, parent, false) + + binding.root.setOnClickListener(this) + binding.buttonDelete.setOnClickListener(this) + binding.buttonAdd.setOnClickListener(this) + + return ListViewHolder(binding) + } + + //don`t forget do add tags for action buttons in item_layout + override fun onBindViewHolder(holder: ListViewHolder, position: Int) { + val item = dataSet[position] + + with(holder.binding) { + itemName.text = context.resources.getString(item.stringNameId) + itemDifficulty.text = context.resources.getString(item.stringDifficulty) + + root.tag = item + buttonDelete.tag = item + buttonAdd.tag = item + } + + //load gif images via Glide + Glide.with(context).load(item.gifImageResourceId).into(holder.binding.itemGifImage) + + //change a color of background in textViewDifficulty and imageViews difficultyBricks + when (item.stringDifficulty) { + R.string.difficulty_easy -> { + holder.binding.easyBrick.setBackgroundColor(Color.parseColor("#64dd17")) + //change other bricks color to default + holder.binding.mediumBrick.setBackgroundColor(Color.parseColor("#bdbdbd")) + holder.binding.hardBrick.setBackgroundColor(Color.parseColor("#bdbdbd")) + } + + R.string.difficulty_medium -> { + holder.binding.mediumBrick.setBackgroundColor(Color.parseColor("#f4dd47")) + //change other bricks color to default + holder.binding.easyBrick.setBackgroundColor(Color.parseColor("#bdbdbd")) + holder.binding.hardBrick.setBackgroundColor(Color.parseColor("#bdbdbd")) + + } + + else -> { + holder.binding.hardBrick.setBackgroundColor(Color.parseColor("#ff5b56")) + //change other bricks color to default + holder.binding.easyBrick.setBackgroundColor(Color.parseColor("#bdbdbd")) + holder.binding.mediumBrick.setBackgroundColor(Color.parseColor("#bdbdbd")) + } + } + + + } + + override fun getItemCount() = dataSet.size + + //interface to move our functions to ListActivity. Don`t forget to put private val listener in class + interface ExerciseActionListener{ + fun onDeleteExercise(exercise: Exercise) + fun onClickExercise(exercise: Exercise) + fun onAddExercise(exercise: Exercise) + } + + +} + diff --git a/app/src/main/java/com/practice/getup/data/DataSource.kt b/app/src/main/java/com/practice/getup/data/DataSource.kt new file mode 100644 index 0000000..51fa9c5 --- /dev/null +++ b/app/src/main/java/com/practice/getup/data/DataSource.kt @@ -0,0 +1,107 @@ +package com.practice.getup.data + +import com.practice.getup.R +import com.practice.getup.model.Exercise + +class DataSource { + + fun loadExercise(): List { + + return listOf( + Exercise( + R.string.exercise1, + R.string.exercise1_description, + R.string.difficulty_easy, + R.drawable.prone_position, + R.raw.prone_position_video + ), + Exercise( + R.string.exercise2, + R.string.exercise2_description, + R.string.difficulty_easy, + R.drawable.pushups, + R.raw.pushups_video + ), + Exercise( + R.string.exercise3, + R.string.exercise3_description, + R.string.difficulty_easy, + R.drawable.squats, + R.raw.squats_video + ), + Exercise( + R.string.exercise4, + R.string.exercise4_description, + R.string.difficulty_easy, + R.drawable.crunches, + R.raw.crunches_video + ), + Exercise( + R.string.exercise5, + R.string.exercise5_description, + R.string.difficulty_medium, + R.drawable.lunges, + R.raw.lunges_video + ), + Exercise( + R.string.exercise6, + R.string.exercise6_description, + R.string.difficulty_medium, + R.drawable.crunches_hard, + R.raw.crunches_hard_video + ), + Exercise( + R.string.exercise7, + R.string.exercise7_description, + R.string.difficulty_medium, + R.drawable.plank, + R.raw.plank_video + ), + Exercise( + R.string.exercise8, + R.string.exercise8_description, + R.string.difficulty_medium, + R.drawable.slow_pushups, + R.raw.slow_pushups_video + ), + Exercise( + R.string.exercise9, + R.string.exercise9_description, + R.string.difficulty_hard, + R.drawable.hard_squats, + R.raw.hard_squats_video + ), + Exercise( + R.string.exercise10, + R.string.exercise10_description, + R.string.difficulty_hard, + R.drawable.hard_plank, + R.raw.hard_plank_video + ), + Exercise( + R.string.exercise11, + R.string.exercise11_description, + R.string.difficulty_hard, + R.drawable.diamond_pushup, + R.raw.diamond_pushups_video + ), + Exercise( + R.string.exercise12, + R.string.exercise12_description, + R.string.difficulty_hard, + R.drawable.climber, + R.raw.climber_video + ) + ) + } + + fun loadEasyList(): List { + return loadExercise().filter { it.stringDifficulty == R.string.difficulty_easy } + } + fun loadMediumList(): List { + return loadExercise().filter { it.stringDifficulty == R.string.difficulty_medium } + } + fun loadHardList(): List { + return loadExercise().filter { it.stringDifficulty == R.string.difficulty_hard } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/practice/getup/model/Exercise.kt b/app/src/main/java/com/practice/getup/model/Exercise.kt new file mode 100644 index 0000000..f7f7e40 --- /dev/null +++ b/app/src/main/java/com/practice/getup/model/Exercise.kt @@ -0,0 +1,9 @@ +package com.practice.getup.model + +data class Exercise( + val stringNameId: Int, + val stringDescriptionId: Int, + val stringDifficulty: Int, + val gifImageResourceId: Int, + val videoResource: Int +) \ No newline at end of file diff --git a/app/src/main/java/com/practice/getup/model/Options.kt b/app/src/main/java/com/practice/getup/model/Options.kt new file mode 100644 index 0000000..fda5394 --- /dev/null +++ b/app/src/main/java/com/practice/getup/model/Options.kt @@ -0,0 +1,20 @@ +package com.practice.getup.model + +import android.os.Parcelable +import com.practice.getup.R +import kotlinx.parcelize.Parcelize + +@Parcelize +data class Options( + val preparingTime: Int, + val workTime: Int, + val restTime: Int, + val numberOfSets: Int, + val exerciseType: Int +) : Parcelable { + companion object { + @JvmStatic val DEFAULT = Options(10,30, 60,5, R.string.exercise2) + } +} + + diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/climber.gif b/app/src/main/res/drawable/climber.gif new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/res/drawable/crunches.gif b/app/src/main/res/drawable/crunches.gif new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/res/drawable/crunches_hard.gif b/app/src/main/res/drawable/crunches_hard.gif new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/res/drawable/diamond_pushup.gif b/app/src/main/res/drawable/diamond_pushup.gif new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/res/drawable/hard_plank.gif b/app/src/main/res/drawable/hard_plank.gif new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/res/drawable/hard_squats.gif b/app/src/main/res/drawable/hard_squats.gif new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 0000000..fee19c1 --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_checklist.xml b/app/src/main/res/drawable/ic_checklist.xml new file mode 100644 index 0000000..7dab33f --- /dev/null +++ b/app/src/main/res/drawable/ic_checklist.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_default_sport.xml b/app/src/main/res/drawable/ic_default_sport.xml new file mode 100644 index 0000000..ee59433 --- /dev/null +++ b/app/src/main/res/drawable/ic_default_sport.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml new file mode 100644 index 0000000..8e255b0 --- /dev/null +++ b/app/src/main/res/drawable/ic_delete.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_list.xml b/app/src/main/res/drawable/ic_list.xml new file mode 100644 index 0000000..be57cab --- /dev/null +++ b/app/src/main/res/drawable/ic_list.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_pause.xml b/app/src/main/res/drawable/ic_pause.xml new file mode 100644 index 0000000..b80d755 --- /dev/null +++ b/app/src/main/res/drawable/ic_pause.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_schedule.xml b/app/src/main/res/drawable/ic_schedule.xml new file mode 100644 index 0000000..688762c --- /dev/null +++ b/app/src/main/res/drawable/ic_schedule.xml @@ -0,0 +1,6 @@ + + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 0000000..e2c5a31 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_start.xml b/app/src/main/res/drawable/ic_start.xml new file mode 100644 index 0000000..2c9d159 --- /dev/null +++ b/app/src/main/res/drawable/ic_start.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/icons/ic_double_arrow_round.png b/app/src/main/res/drawable/icons/ic_double_arrow_round.png new file mode 100644 index 0000000..08ef702 Binary files /dev/null and b/app/src/main/res/drawable/icons/ic_double_arrow_round.png differ diff --git a/app/src/main/res/drawable/lunges.gif b/app/src/main/res/drawable/lunges.gif new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/res/drawable/plank.gif b/app/src/main/res/drawable/plank.gif new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/res/drawable/prone_position.gif b/app/src/main/res/drawable/prone_position.gif new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/res/drawable/pushups.gif b/app/src/main/res/drawable/pushups.gif new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/res/drawable/slow_pushups.gif b/app/src/main/res/drawable/slow_pushups.gif new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/res/drawable/squats.gif b/app/src/main/res/drawable/squats.gif new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/res/font/bebas_neue_bold.ttf b/app/src/main/res/font/bebas_neue_bold.ttf new file mode 100644 index 0000000..6fff605 Binary files /dev/null and b/app/src/main/res/font/bebas_neue_bold.ttf differ diff --git a/app/src/main/res/font/globersemiboldfree.ttf b/app/src/main/res/font/globersemiboldfree.ttf new file mode 100644 index 0000000..0239f1d Binary files /dev/null and b/app/src/main/res/font/globersemiboldfree.ttf differ diff --git a/app/src/main/res/font/lato_light.ttf b/app/src/main/res/font/lato_light.ttf new file mode 100644 index 0000000..0809b8e Binary files /dev/null and b/app/src/main/res/font/lato_light.ttf differ diff --git a/app/src/main/res/layout/activity_detail.xml b/app/src/main/res/layout/activity_detail.xml new file mode 100644 index 0000000..283422d --- /dev/null +++ b/app/src/main/res/layout/activity_detail.xml @@ -0,0 +1,22 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_list.xml b/app/src/main/res/layout/activity_list.xml new file mode 100644 index 0000000..7f0fc21 --- /dev/null +++ b/app/src/main/res/layout/activity_list.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..ce1a7ed --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_options.xml b/app/src/main/res/layout/activity_options.xml new file mode 100644 index 0000000..c59090d --- /dev/null +++ b/app/src/main/res/layout/activity_options.xml @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +