Skip to content

Commit

Permalink
Merge pull request #27 from Modarb-Ai-Trainer/feature_add_custom_exer…
Browse files Browse the repository at this point in the history
…cise

Prepare for adding custom workout
  • Loading branch information
amrhossamdev authored Jun 9, 2024
2 parents d184e27 + 599a19a commit d56d127
Show file tree
Hide file tree
Showing 38 changed files with 596 additions and 89 deletions.
6 changes: 6 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ dependencies {
// Retrofit
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
// Paging
implementation("androidx.paging:paging-runtime:3.1.1")

// LiveData and ViewModel
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
Expand All @@ -63,6 +67,8 @@ dependencies {
implementation("com.tbuonomo:dotsindicator:5.0")
implementation("androidx.legacy:legacy-support-v4:1.0.0")
implementation("androidx.activity:activity:1.9.0")
// Image fetch
implementation("com.github.bumptech.glide:glide:4.16.0")


testImplementation("junit:junit:4.13.2")
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/com/modarb/android/network/ApiService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.modarb.android.network
import com.modarb.android.network.models.BaseResponse
import com.modarb.android.ui.home.ui.home.domain.models.HomePageResponse
import com.modarb.android.ui.home.ui.plan.domain.models.PlanPageResponse
import com.modarb.android.ui.home.ui.plan.domain.models.allExercises.ExercisesResponse
import com.modarb.android.ui.home.ui.plan.domain.models.customworkout.CustomWorkoutResponse
import com.modarb.android.ui.onboarding.models.LoginResponse
import com.modarb.android.ui.onboarding.models.RequestModels.LoginRequest
Expand All @@ -14,6 +15,7 @@ import retrofit2.http.Header
import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query

interface ApiService {
@POST("/api/v1/user/auth/login")
Expand Down Expand Up @@ -55,5 +57,22 @@ interface ApiService {
): Response<CustomWorkoutResponse>


// Exercises Selection APIs
@GET("api/v1/user/exercises/")
suspend fun getExercises(
@Header("Authorization") token: String,
@Query("filterName") filter: String,
@Query("skip") skip: Int,
@Query("limit") limit: Int
): Response<ExercisesResponse>

@GET("api/v1/user/exercises/search")
suspend fun getExercisesSearch(
@Header("Authorization") token: String,
@Query("searchTerm") search: String,
@Query("filter") filter: String
): Response<ExercisesResponse>


}

23 changes: 18 additions & 5 deletions app/src/main/java/com/modarb/android/network/RetrofitService.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package com.modarb.android.network

import com.google.gson.Gson
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory


object RetrofitService {

private var BASE_URL = "http://10.0.2.2:4000/"
private var BASE_URL = "https://moahmedwafy-modarb-be.hf.space/"
inline fun <reified T> handleRequest(
response: Response<T>, onSuccess: (T) -> Unit, onError: (T?) -> Unit
) {
Expand Down Expand Up @@ -39,11 +42,21 @@ object RetrofitService {
}

fun createService(): ApiService {
if (!NetworkHelper.isEmulator()) {
BASE_URL = "http://192.168.1.9:4000/"
}
// if (!NetworkHelper.isEmulator()) {
// BASE_URL = "http://192.168.1.9:4000/"
// }

val logging = HttpLoggingInterceptor()
logging.setLevel(HttpLoggingInterceptor.Level.BODY)


// Create an OkHttpClient and attach the logging interceptor
val client: OkHttpClient = OkHttpClient.Builder()
.addInterceptor(logging)
.build()

try {
return Retrofit.Builder().baseUrl(BASE_URL)
return Retrofit.Builder().baseUrl(BASE_URL).client(client)
.addConverterFactory(GsonConverterFactory.create()).build()
.create(ApiService::class.java)
} catch (e: Exception) {
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/java/com/modarb/android/ui/helpers/ViewUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.modarb.android.ui.helpers

import android.content.Context
import android.widget.ImageView
import com.bumptech.glide.Glide

object ViewUtils {


fun loadImage(context: Context, imageUrl: String, imageView: ImageView) {
Glide.with(context).asBitmap().load(imageUrl).into(imageView)
}


}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.modarb.android.ui.home.helpers
package com.modarb.android.ui.helpers

import com.modarb.android.ui.home.ui.plan.domain.models.Day
import com.modarb.android.ui.home.ui.plan.domain.models.Exercise
import com.modarb.android.ui.home.ui.plan.domain.models.Week
import com.modarb.android.ui.home.ui.plan.domain.models.allExercises.Data

object WorkoutData {

Expand All @@ -12,7 +13,7 @@ object WorkoutData {
lateinit var weekList: List<Week>
lateinit var selectedExercise: Exercise
var selectedExerciseNumber: Int = 1

var selectedCustomExercisesList: MutableList<Data> = mutableListOf()

fun getWeekDaysCount(): Int {
return weekList[currentWeekPosition].days.size
Expand Down Expand Up @@ -66,6 +67,28 @@ object WorkoutData {
}


fun getBodyParts(): Array<String> {
return arrayOf(
"All",
"waist",
"upper legs",
"upper arms",
"lower legs",
"chest",
"lower arms",
"back",
"neck",
"shoulders",
"cardio"
)
}

fun getSelectedCustomExercises(): List<Data> {
return selectedCustomExercisesList
}



// TODO handle getting reps after fixing the api
// fun getTotalExerciseReps(exercise: List<com.modarb.android.ui.home.ui.plan.domain.models.customworkout.Exercise>): String {
// var totalReps = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import com.modarb.android.R
import com.modarb.android.databinding.FragmentHomeBinding
import com.modarb.android.network.ApiResult
import com.modarb.android.network.NetworkHelper
import com.modarb.android.ui.helpers.WorkoutData
import com.modarb.android.ui.home.HomeActivity
import com.modarb.android.ui.home.helpers.WorkoutData
import com.modarb.android.ui.home.ui.home.domain.models.HomePageResponse
import com.modarb.android.ui.home.ui.home.presentation.HomeViewModel
import com.modarb.android.ui.home.ui.plan.domain.models.PlanPageResponse
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.modarb.android.ui.home.ui.plan

import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.modarb.android.network.ApiService
import com.modarb.android.ui.home.ui.plan.domain.models.allExercises.Data

class ExercisesPagingSource(
private val apiService: ApiService,
private val token: String,
private val filter: String,
) : PagingSource<Int, Data>() {

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Data> {
val page = params.key ?: 0
try {
val response = apiService.getExercises(token, filter, page, params.loadSize)
return LoadResult.Page(
data = response.body()!!.data,
prevKey = if (page == 0) null else page - 1,
nextKey = if (response.body()!!.data.isEmpty()) null else page + 1
)
} catch (e: Exception) {
return LoadResult.Error(e)
}
}


override fun getRefreshKey(state: PagingState<Int, Data>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.modarb.android.ui.home.ui.plan

interface LoadingStateListener {
fun onLoadingStarted()
fun onLoadingFinished()
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.modarb.android.databinding.ItemCustomworkoutTemplateBinding
import com.modarb.android.network.NetworkHelper
import com.modarb.android.ui.home.helpers.WorkoutData
import com.modarb.android.ui.helpers.WorkoutData
import com.modarb.android.ui.home.ui.plan.domain.models.customworkout.Data

class CustomWorkoutTemplateAdapter(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,62 +1,107 @@
package com.modarb.android.ui.home.ui.plan.adapters

import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import androidx.paging.PagingData
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.modarb.android.databinding.ItemExerciseSelectionDetailsBinding
import com.modarb.android.ui.workout.domain.models.WorkoutModel
import com.modarb.android.ui.home.ui.plan.domain.models.allExercises.Data

class ExercisesAddAdapter(private val itemList: List<WorkoutModel>) :
RecyclerView.Adapter<ExercisesAddAdapter.ViewHolder>() {
class ExercisesAddAdapter(private var context: Context, private val isAdd: Boolean) :
PagingDataAdapter<Data, ExercisesAddAdapter.ViewHolder>(DIFF_CALLBACK) {

private val selectedItems = mutableListOf<Int>()
private var selectedItems: HashMap<String, Data> = HashMap()

inner class ViewHolder(private val binding: ItemExerciseSelectionDetailsBinding) :
RecyclerView.ViewHolder(binding.root) {

fun bind(item: WorkoutModel) {
val isSelected = selectedItems.contains(item.id)
binding.root.isActivated = isSelected
binding.overlay.visibility = if (isSelected) ViewGroup.VISIBLE else ViewGroup.INVISIBLE
binding.checkMark.visibility =
if (isSelected) ViewGroup.VISIBLE else ViewGroup.INVISIBLE
fun bind(item: Data?) {
item?.let { data ->
// TODO uncomment this
//ViewUtils.loadImage(context,data.coverImage,binding.exerciseImage)

binding.exerciseTitle.text = data.name
binding.exerciseDesc.text = data.benefits

if (isAdd) {
val isSelected = selectedItems.contains(data.id)
binding.root.isActivated = isSelected
binding.overlay.visibility =
if (isSelected) ViewGroup.VISIBLE else ViewGroup.INVISIBLE
binding.checkMark.visibility =
if (isSelected) ViewGroup.VISIBLE else ViewGroup.INVISIBLE

binding.root.setOnClickListener {
toggleSelection(data.id, adapterPosition)
}
}

binding.root.setOnClickListener {
toggleSelection(item.id, adapterPosition)
Log.d("selected", getSelectedItems().toString())
}
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemExerciseSelectionDetailsBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
LayoutInflater.from(parent.context), parent, false
)
return ViewHolder(binding)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = itemList[position]
holder.bind(item)
holder.bind(getItem(position))
}

override fun getItemCount(): Int {
return itemList.size
}

private fun toggleSelection(itemId: Int, position: Int) {
private fun toggleSelection(itemId: String, position: Int) {
if (selectedItems.contains(itemId)) {
selectedItems.remove(itemId)

} else {
selectedItems.add(itemId)
selectedItems[itemId] = getItem(position)!!
}
getSelectedItems()
notifyItemChanged(position)
}

fun getSelectedItems(): List<Int> {
return selectedItems.toList()
private fun getSelectedItems() {
Log.d("selectedItems", selectedItems.toString())
}

fun updateData(lifecycleOwner: LifecycleOwner, newData: List<Data>) {
submitData(lifecycleOwner.lifecycle, PagingData.from(newData))
}

fun clearData(lifecycleOwner: LifecycleOwner) {
submitData(lifecycleOwner.lifecycle, PagingData.empty())
}

fun clearSelectedData() {
selectedItems.clear()
notifyDataSetChanged()
}

fun getSelectedData(): List<Data> {
val selectedDataList = mutableListOf<Data>()

for ((_, value) in selectedItems) {
selectedDataList.add(value)
}

return selectedDataList
}


companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Data>() {
override fun areItemsTheSame(oldItem: Data, newItem: Data): Boolean {
return oldItem.id == newItem.id
}

override fun areContentsTheSame(oldItem: Data, newItem: Data): Boolean {
return oldItem == newItem
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.modarb.android.R
import com.modarb.android.databinding.CustomWorkoutViewBinding
import com.modarb.android.databinding.MyPlanViewBinding
import com.modarb.android.ui.home.helpers.WorkoutData
import com.modarb.android.ui.helpers.WorkoutData
import com.modarb.android.ui.home.ui.plan.domain.models.Data
import com.modarb.android.ui.home.ui.plan.domain.models.PlanPageResponse
import com.modarb.android.ui.home.ui.plan.domain.models.customworkout.CustomWorkoutResponse
Expand Down
Loading

0 comments on commit d56d127

Please sign in to comment.