diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 644028492..7cd9a1e8e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -53,7 +53,6 @@ configurations.all { dependencies { implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) - implementation(Dependencies.kotlin_stdlib) implementation(Dependencies.design) implementation(Dependencies.constraint_layout) implementation(Dependencies.appCompat) @@ -78,4 +77,11 @@ dependencies { implementation(Dependencies.viewPager2) implementation(Dependencies.swipe_refresh_layout) + + implementation(Dependencies.room_ktx) + implementation(Dependencies.room_runtime) + kapt(Dependencies.room_compiler) + implementation(Dependencies.room_paging) + + implementation(Dependencies.paging3) } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 63009251f..027da5301 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,12 +19,10 @@ android:theme="@style/AppTheme.NoActionBar" /> - + android:theme="@style/AppTheme.NoActionBar" + android:exported="true"> diff --git a/app/src/main/java/org/anitab/mentorship/Injection.kt b/app/src/main/java/org/anitab/mentorship/Injection.kt new file mode 100644 index 000000000..ef560c91a --- /dev/null +++ b/app/src/main/java/org/anitab/mentorship/Injection.kt @@ -0,0 +1,20 @@ +package org.anitab.mentorship + +import org.anitab.mentorship.local.UserDatabase +/** + * Class that handles object creation. + * Like this, objects can be passed as parameters in the constructors and then replaced for + * testing, where needed. + */ +object Injection { + + /** + * Provides context of [MentorshipApplication] + */ + private fun provideApplicationContext() = MentorshipApplication.getContext() + + /** + * Provides an instance of [UserDatabase] + */ + fun provideUserDatabase(): UserDatabase = UserDatabase.getInstance(provideApplicationContext()) +} diff --git a/app/src/main/java/org/anitab/mentorship/local/RemoteKeysDao.kt b/app/src/main/java/org/anitab/mentorship/local/RemoteKeysDao.kt new file mode 100644 index 000000000..d43cff203 --- /dev/null +++ b/app/src/main/java/org/anitab/mentorship/local/RemoteKeysDao.kt @@ -0,0 +1,22 @@ +package org.anitab.mentorship.local + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query + +@Dao +interface RemoteKeysDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(remoteKey: List) + + @Query("SELECT * FROM remote_keys") + suspend fun getRemoteKeys(): List + + @Query("SELECT * FROM remote_keys WHERE userId = :userId") + suspend fun remoteKeysRepoId(userId: Int): RemoteKeysEntity? + + @Query("DELETE FROM remote_keys") + suspend fun clearRemoteKeys() +} diff --git a/app/src/main/java/org/anitab/mentorship/local/RemoteKeysEntity.kt b/app/src/main/java/org/anitab/mentorship/local/RemoteKeysEntity.kt new file mode 100644 index 000000000..b1f023f85 --- /dev/null +++ b/app/src/main/java/org/anitab/mentorship/local/RemoteKeysEntity.kt @@ -0,0 +1,14 @@ +package org.anitab.mentorship.local + +import android.os.Parcelable +import androidx.room.Entity +import androidx.room.PrimaryKey +import kotlinx.android.parcel.Parcelize + +@Entity(tableName = "remote_keys") +@Parcelize +data class RemoteKeysEntity( + @PrimaryKey val userId: Int?, + val prevKey: Int?, + val nextKey: Int? +) : Parcelable diff --git a/app/src/main/java/org/anitab/mentorship/local/UserDao.kt b/app/src/main/java/org/anitab/mentorship/local/UserDao.kt new file mode 100644 index 000000000..c431b0424 --- /dev/null +++ b/app/src/main/java/org/anitab/mentorship/local/UserDao.kt @@ -0,0 +1,21 @@ +package org.anitab.mentorship.local + +import androidx.paging.PagingSource +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import org.anitab.mentorship.models.User + +@Dao +interface UserDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertUsers(users: List) + + @Query("SELECT * from user_table") + fun getAllUsers(): PagingSource + + @Query("DELETE from user_table") + suspend fun clearAllUsers() +} diff --git a/app/src/main/java/org/anitab/mentorship/local/UserDatabase.kt b/app/src/main/java/org/anitab/mentorship/local/UserDatabase.kt new file mode 100644 index 000000000..b5796e767 --- /dev/null +++ b/app/src/main/java/org/anitab/mentorship/local/UserDatabase.kt @@ -0,0 +1,38 @@ +package org.anitab.mentorship.local + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import org.anitab.mentorship.models.User + +@Database( + entities = [User::class, RemoteKeysEntity::class], + version = 2, + exportSchema = false +) +abstract class UserDatabase : RoomDatabase() { + + abstract fun userDao(): UserDao + abstract fun remoteKeyDao(): RemoteKeysDao + + companion object { + + @Volatile + private var INSTANCE: UserDatabase? = null + + fun getInstance(context: Context): UserDatabase = + INSTANCE ?: synchronized(this) { + INSTANCE + ?: buildDatabase(context).also { INSTANCE = it } + } + + private fun buildDatabase(context: Context) = + Room.databaseBuilder( + context, + UserDatabase::class.java, "User.db" + ) + .fallbackToDestructiveMigration() + .build() + } +} diff --git a/app/src/main/java/org/anitab/mentorship/models/User.kt b/app/src/main/java/org/anitab/mentorship/models/User.kt index 815d312c3..54110c5fa 100644 --- a/app/src/main/java/org/anitab/mentorship/models/User.kt +++ b/app/src/main/java/org/anitab/mentorship/models/User.kt @@ -1,6 +1,9 @@ package org.anitab.mentorship.models import android.os.Parcelable +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.google.gson.annotations.SerializedName import kotlinx.android.parcel.Parcelize /** @@ -20,19 +23,21 @@ import kotlinx.android.parcel.Parcelize * @param availableToMentor true, if user is available to mentor, false if otherwise * @param slackUsername Slack username */ + +@Entity(tableName = "user_table") @Parcelize data class User( - var id: Int? = null, - var username: String? = null, - var name: String? = null, - var email: String? = null, - var bio: String? = null, - var location: String? = null, - var occupation: String? = null, - var organization: String? = null, - var interests: String? = null, - var skills: String? = null, - var needMentoring: Boolean? = null, - var availableToMentor: Boolean? = null, - var slackUsername: String? = null + @PrimaryKey @field:SerializedName("id") var id: Int, + @field:SerializedName("username") var username: String? = null, + @field:SerializedName("name") var name: String? = null, + @field:SerializedName("email") var email: String? = null, + @field:SerializedName("bio") var bio: String? = null, + @field:SerializedName("location") var location: String? = null, + @field:SerializedName("occupation") var occupation: String? = null, + @field:SerializedName("organization") var organization: String? = null, + @field:SerializedName("interests") var interests: String? = null, + @field:SerializedName("skills") var skills: String? = null, + @field:SerializedName("need_mentoring") var needMentoring: Boolean? = null, + @field:SerializedName("available_to_mentor") var availableToMentor: Boolean? = null, + @field:SerializedName("slack_username") var slackUsername: String? = null ) : Parcelable diff --git a/app/src/main/java/org/anitab/mentorship/remote/datamanager/UserDataManager.kt b/app/src/main/java/org/anitab/mentorship/remote/datamanager/UserDataManager.kt index f6d7f89c1..0e5a3851c 100644 --- a/app/src/main/java/org/anitab/mentorship/remote/datamanager/UserDataManager.kt +++ b/app/src/main/java/org/anitab/mentorship/remote/datamanager/UserDataManager.kt @@ -1,14 +1,23 @@ package org.anitab.mentorship.remote.datamanager +import androidx.lifecycle.LiveData +import androidx.lifecycle.map +import androidx.paging.ExperimentalPagingApi +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.filter +import androidx.paging.liveData import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.anitab.mentorship.Injection import org.anitab.mentorship.models.HomeStatistics import org.anitab.mentorship.models.User import org.anitab.mentorship.remote.ApiManager import org.anitab.mentorship.remote.requests.ChangePassword -import org.anitab.mentorship.remote.requests.PaginationRequest import org.anitab.mentorship.remote.responses.CustomResponse +import org.anitab.mentorship.utils.Constants.ITEMS_PER_PAGE /** * This class represents the data manager related to Users API @@ -16,23 +25,93 @@ import org.anitab.mentorship.remote.responses.CustomResponse class UserDataManager(private val dispatcher: CoroutineDispatcher = Dispatchers.IO) { private val apiManager = ApiManager.instance + private val userDatabase = Injection.provideUserDatabase() + + /** Paging 3 user data management starts **/ /** - * This will call the getVerifiedUsers method of UserService interface - * @return an Observable of a list of [User] + * This will call the getAllUsers() method from UserDao interface + * @return stream of [User] list as PagingData with the help of [UserRemoteMediator] + * which manages making network call and storing user list in room db. */ - suspend fun getUsers(): List { - return withContext(dispatcher) { apiManager.userService.getVerifiedUsers() } + + @OptIn(ExperimentalPagingApi::class) + fun getAllUsers(): LiveData> { + val pagingSourceFactory = { userDatabase.userDao().getAllUsers() } + + return Pager( + config = PagingConfig( + pageSize = ITEMS_PER_PAGE, + enablePlaceholders = false + ), + remoteMediator = UserRemoteMediator(apiManager.userService, userDatabase), + pagingSourceFactory = pagingSourceFactory + ).liveData } /** - * This will call the getVerifiedUsers(pagination) method of UserService interface - * @return an Observable of a list of [User] + * This will call the getUsersWhoAreAvailableToMentor() method from UserDao interface + * @return stream of [User] list, who are available to be mentored as PagingData with + * the help of [UserRemoteMediator] which manages making network call and storing user + * list in room db. */ - suspend fun getUsers(paginationRequest: PaginationRequest): List { - return withContext(dispatcher) { apiManager.userService.getVerifiedUsers(paginationRequest.pagination) } + @OptIn(ExperimentalPagingApi::class) + fun getUsersWhoAreAvailableToMentor(): LiveData> { + val pagingSourceFactory = { userDatabase.userDao().getAllUsers() } + + return Pager( + config = PagingConfig( + pageSize = ITEMS_PER_PAGE, + enablePlaceholders = false + ), + remoteMediator = UserRemoteMediator(apiManager.userService, userDatabase), + pagingSourceFactory = pagingSourceFactory + ).liveData + .map { pagingData -> pagingData.filter { user -> user.availableToMentor != null && user.availableToMentor == true } } } + /** + * This will call the getUsersWhoAreNeedMentoring() method from UserDao interface + * @return stream of [User] list, who are need mentoring as PagingData with the help of + * [UserRemoteMediator] which manages making network call and storing user list in room db. + */ + @OptIn(ExperimentalPagingApi::class) + fun getUsersWhoAreNeedMentoring(): LiveData> { + val pagingSourceFactory = { userDatabase.userDao().getAllUsers() } + + return Pager( + config = PagingConfig( + pageSize = ITEMS_PER_PAGE, + enablePlaceholders = false + ), + remoteMediator = UserRemoteMediator(apiManager.userService, userDatabase), + pagingSourceFactory = pagingSourceFactory + ).liveData + .map { pagingData -> pagingData.filter { user -> user.needMentoring != null && user.needMentoring == true } } + } + + /** + * This will call the getUserWhoHaveSkills() method from UserDao interface + * @return stream of [User] list, who have at least 1 skill as PagingData with the help of + * [UserRemoteMediator] which manages making network call and storing user list in room db. + */ + @OptIn(ExperimentalPagingApi::class) + fun getUserWhoHaveSkills(): LiveData> { + val pagingSourceFactory = { userDatabase.userDao().getAllUsers() } + + return Pager( + config = PagingConfig( + pageSize = ITEMS_PER_PAGE, + enablePlaceholders = false + ), + remoteMediator = UserRemoteMediator(apiManager.userService, userDatabase), + pagingSourceFactory = pagingSourceFactory + ).liveData + .map { pagingData -> pagingData.filter { user -> user.skills != null } } + } + + /** Paging 3 user data management end **/ + /** * This will call the getUser method of UserService interface * @return an Observable of [User] diff --git a/app/src/main/java/org/anitab/mentorship/remote/datamanager/UserRemoteMediator.kt b/app/src/main/java/org/anitab/mentorship/remote/datamanager/UserRemoteMediator.kt new file mode 100644 index 000000000..d91cf276b --- /dev/null +++ b/app/src/main/java/org/anitab/mentorship/remote/datamanager/UserRemoteMediator.kt @@ -0,0 +1,94 @@ +package org.anitab.mentorship.remote.datamanager + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import androidx.room.withTransaction +import java.io.IOException +import org.anitab.mentorship.local.RemoteKeysEntity +import org.anitab.mentorship.local.UserDatabase +import org.anitab.mentorship.models.User +import org.anitab.mentorship.remote.services.UserService +import org.anitab.mentorship.utils.Constants.START_PAGE_INDEX +import retrofit2.HttpException + +@ExperimentalPagingApi +class UserRemoteMediator( + private val userService: UserService, + private val userDatabase: UserDatabase +) : RemoteMediator() { + + override suspend fun initialize(): InitializeAction { + // Launching paging with refresh first, without triggering prepend or append. + return InitializeAction.LAUNCH_INITIAL_REFRESH + } + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): MediatorResult { + // calculating page number as per load type. + val page = when (loadType) { + LoadType.APPEND -> { + val remoteKey = userDatabase.remoteKeyDao().getRemoteKeys().lastOrNull() + val nextKey = remoteKey?.nextKey + ?: return MediatorResult.Success(endOfPaginationReached = true) + nextKey + } + LoadType.PREPEND -> { + val remoteKey = userDatabase.remoteKeyDao().getRemoteKeys().firstOrNull() + val prevKey = remoteKey?.prevKey + ?: return MediatorResult.Success(endOfPaginationReached = remoteKey != null) + prevKey + } + LoadType.REFRESH -> { + val remoteKey = getRemoteKeyClosestToCurrentPosition(state) + remoteKey?.nextKey?.minus(1) ?: START_PAGE_INDEX + } + } + + try { + // making network call + val users: List = userService.getVerifiedUsers(page, state.config.pageSize) + + // storing if user list is empty + val endOfPaginationReached = users.isEmpty() + + userDatabase.withTransaction { + // clear all table in database + if (loadType == LoadType.REFRESH) { + userDatabase.remoteKeyDao().clearRemoteKeys() + userDatabase.userDao().clearAllUsers() + } + + // storing previous and next keys + val prevKey = if (page == START_PAGE_INDEX) null else page - 1 + val nextKey = if (endOfPaginationReached) null else page + 1 + + // storing the remote key and fetched users + val keys = users.map { + RemoteKeysEntity(userId = it.id, prevKey = prevKey, nextKey = nextKey) + } + userDatabase.remoteKeyDao().insertAll(keys) + userDatabase.userDao().insertUsers(users) + } + return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) + } catch (e: IOException) { + return MediatorResult.Error(e) + } catch (e: HttpException) { + return MediatorResult.Error(e) + } + } + + private suspend fun getRemoteKeyClosestToCurrentPosition(state: PagingState): RemoteKeysEntity? { + // Getting the remote key closest to anchor position + // Paging library will load data after the anchor position + // and get that item, which is closest to anchor position + return state.anchorPosition?.let { position -> + state.closestItemToPosition(position)?.id?.let { userId -> + userDatabase.remoteKeyDao().remoteKeysRepoId(userId) + } + } + } +} diff --git a/app/src/main/java/org/anitab/mentorship/remote/services/UserService.kt b/app/src/main/java/org/anitab/mentorship/remote/services/UserService.kt index 726527ed0..364f13e66 100644 --- a/app/src/main/java/org/anitab/mentorship/remote/services/UserService.kt +++ b/app/src/main/java/org/anitab/mentorship/remote/services/UserService.kt @@ -8,6 +8,7 @@ import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.PUT import retrofit2.http.Path +import retrofit2.http.Query import retrofit2.http.QueryMap /** @@ -35,7 +36,10 @@ interface UserService { * @return an observable instance of a list of [User]s */ @GET("users/verified") - suspend fun getVerifiedUsers(): List + suspend fun getVerifiedUsers( + @Query("page") page: Int, + @Query("per_page") perPage: Int + ): List /** * This function returns a user's public profile of the system diff --git a/app/src/main/java/org/anitab/mentorship/utils/BindingUtils.kt b/app/src/main/java/org/anitab/mentorship/utils/BindingUtils.kt new file mode 100644 index 000000000..d5a088a17 --- /dev/null +++ b/app/src/main/java/org/anitab/mentorship/utils/BindingUtils.kt @@ -0,0 +1,20 @@ +package org.anitab.mentorship.utils + +import android.widget.TextView +import androidx.databinding.BindingAdapter +import org.anitab.mentorship.R + +@BindingAdapter("isAvailableToMentor", "isMentoringNeeded", requireAll = true) +fun TextView.showAvailability(availableToMentor: Boolean?, needMentoring: Boolean?) { + text = if (availableToMentor != null && needMentoring != null) { + if (availableToMentor && needMentoring) resources.getString(R.string.available_to_mentor_and_mentee) + else if (availableToMentor) resources.getString(R.string.only_available_to_mentor) + else if (needMentoring) resources.getString(R.string.only_available_to_mentee) + else resources.getString(R.string.not_available_to_mentor_or_mentee) + } else resources.getString(R.string.not_available_to_mentor_or_mentee) +} + +@BindingAdapter("setInterest") +fun TextView.showInterests(interests: String?) { + text = interests?.let { "Interests: $it" } ?: "Interests: --" +} diff --git a/app/src/main/java/org/anitab/mentorship/utils/Constants.kt b/app/src/main/java/org/anitab/mentorship/utils/Constants.kt index 98cf08f1b..c3f858708 100644 --- a/app/src/main/java/org/anitab/mentorship/utils/Constants.kt +++ b/app/src/main/java/org/anitab/mentorship/utils/Constants.kt @@ -2,16 +2,15 @@ package org.anitab.mentorship.utils object Constants { - const val ITEMS_PER_PAGE = 20 + const val ITEMS_PER_PAGE = 50 + const val START_PAGE_INDEX = 1 const val TOTAL_REQUEST_TABS = 3 - const val MEMBER_USER_ID = "member_user_id" + const val MEMBER_USER_EXTRAS = "member_user_extras" const val REQUEST_LIST = "request_list" const val REQUEST_EMPTY_LIST_TEXT = "request_empty_list_text" const val RELATIONSHIP_EXTRA = "relationship_extra" const val DELETE_REQUEST_RESULT_ID = 1000 const val REQUEST_ID = "request_id" - // filter function in MembersFragment - const val FILTER_REQUEST_CODE = 8000 const val FILTER_MAP = "filter_map" const val SORT_KEY = "sort_key" const val NEED_MENTORING_KEY = "need_mentoring" diff --git a/app/src/main/java/org/anitab/mentorship/utils/PreferenceManager.kt b/app/src/main/java/org/anitab/mentorship/utils/PreferenceManager.kt index a2b73d79e..c9ed0a448 100644 --- a/app/src/main/java/org/anitab/mentorship/utils/PreferenceManager.kt +++ b/app/src/main/java/org/anitab/mentorship/utils/PreferenceManager.kt @@ -30,7 +30,7 @@ class PreferenceManager { } val authToken: String - get() = sharedPreferences.getString(AUTH_TOKEN, "") + get() = sharedPreferences.getString(AUTH_TOKEN, "").toString() /** * Clears all the data that has been saved in the preferences file. diff --git a/app/src/main/java/org/anitab/mentorship/utils/RecyclerViewItemDecoration.kt b/app/src/main/java/org/anitab/mentorship/utils/RecyclerViewItemDecoration.kt new file mode 100644 index 000000000..9d779f922 --- /dev/null +++ b/app/src/main/java/org/anitab/mentorship/utils/RecyclerViewItemDecoration.kt @@ -0,0 +1,23 @@ +package org.anitab.mentorship.utils + +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView + +class RecyclerViewItemDecoration : + RecyclerView.ItemDecoration() { + private val decorationHeightWidth: Int = 10 + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + val itemPosition = parent.getChildAdapterPosition(view) + val totalCount = parent.adapter!!.itemCount + if (itemPosition >= 0 && itemPosition <= totalCount - 1) { + outRect.bottom = decorationHeightWidth + } + } +} diff --git a/app/src/main/java/org/anitab/mentorship/utils/UsersDiffCallback.kt b/app/src/main/java/org/anitab/mentorship/utils/UsersDiffCallback.kt deleted file mode 100644 index ddad8291d..000000000 --- a/app/src/main/java/org/anitab/mentorship/utils/UsersDiffCallback.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.anitab.mentorship.utils - -import androidx.recyclerview.widget.DiffUtil -import org.anitab.mentorship.models.User - -class UsersDiffCallback(private val mOldUsersList: List, private val mNewUsersList: List) : DiffUtil.Callback() { - - override fun getOldListSize(): Int { - return mOldUsersList.size - } - - override fun getNewListSize(): Int { - return mNewUsersList.size - } - - override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - return mOldUsersList[oldItemPosition].id == mNewUsersList[newItemPosition].id - } - - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - return mOldUsersList[oldItemPosition] == mNewUsersList[newItemPosition] - } -} diff --git a/app/src/main/java/org/anitab/mentorship/view/activities/FilterActivity.kt b/app/src/main/java/org/anitab/mentorship/view/activities/FilterActivity.kt deleted file mode 100644 index 47319a68c..000000000 --- a/app/src/main/java/org/anitab/mentorship/view/activities/FilterActivity.kt +++ /dev/null @@ -1,203 +0,0 @@ -package org.anitab.mentorship.view.activities - -import android.app.Activity -import android.content.Intent -import android.os.Bundle -import android.view.MenuItem -import android.view.View -import android.widget.TextView -import androidx.cardview.widget.CardView -import androidx.core.content.res.ResourcesCompat -import com.google.android.material.snackbar.Snackbar -import kotlinx.android.synthetic.main.activity_filter.* -import org.anitab.mentorship.R -import org.anitab.mentorship.utils.Constants.AVAILABLE_TO_MENTOR_KEY -import org.anitab.mentorship.utils.Constants.FILTER_MAP -import org.anitab.mentorship.utils.Constants.INTERESTS_KEY -import org.anitab.mentorship.utils.Constants.LOCATION_KEY -import org.anitab.mentorship.utils.Constants.NEED_MENTORING_KEY -import org.anitab.mentorship.utils.Constants.SKILLS_KEY -import org.anitab.mentorship.utils.Constants.SORT_KEY -import org.anitab.mentorship.view.fragments.MembersFragment - -class FilterActivity : BaseActivity() { - - // a backup variable for the view that was selected in sort by fragment - private var previousSelectionSort: View? = null - - private var needMentoring = false - private var availableToMentor = false - - private var map: HashMap? = hashMapOf() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_filter) - - setTitle(R.string.filter) - - supportActionBar?.setDisplayHomeAsUpEnabled(true) - supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_close_black_24dp) - - map = intent?.extras?.get(FILTER_MAP) as HashMap? - initializeViews() - initializeClickListeners() - } - - private fun initializeViews() { - when (map?.get(SORT_KEY)) { - MembersFragment.SortValues.REGISTRATION_DATE.name -> { - cardSortRegistrationDate.setBackgroundResource(R.drawable.background_rounded_primary) - tvSortRegistrationDate.setTextColor(ResourcesCompat.getColor( - resources, R.color.white, null)) - previousSelectionSort = cardSortRegistrationDate - } - MembersFragment.SortValues.NAMEAZ.name -> { - cardSortNameAZ.setBackgroundResource(R.drawable.background_rounded_primary) - tvSortNameAZ.setTextColor(ResourcesCompat.getColor( - resources, R.color.white, null)) - previousSelectionSort = cardSortNameAZ - } - MembersFragment.SortValues.NAMEZA.name -> { - cardSortNameZA.setBackgroundResource(R.drawable.background_rounded_primary) - tvSortNameZA.setTextColor(ResourcesCompat.getColor( - resources, R.color.white, null)) - previousSelectionSort = cardSortNameZA - } - } - - if (map?.get(NEED_MENTORING_KEY) == "true") { - cardFilterNeedMentoring.setBackgroundResource(R.drawable.background_rounded_primary) - tvFilterNeedMentoring.setTextColor(ResourcesCompat.getColor( - resources, R.color.white, null)) - needMentoring = true - } - if (map?.get(AVAILABLE_TO_MENTOR_KEY) == "true") { - cardFilterAvailableToMentor.setBackgroundResource(R.drawable.background_rounded_primary) - tvFilterAvailableToMentor.setTextColor(ResourcesCompat.getColor( - resources, R.color.white, null)) - availableToMentor = true - } - - etFilterInterests.setText(map?.get(INTERESTS_KEY) ?: "") - etFilterLocation.setText(map?.get(LOCATION_KEY) ?: "") - etFilterSkills.setText(map?.get(SKILLS_KEY) ?: "") - } - - private fun initializeClickListeners() { - cardSortAge.setOnClickListener { - Snackbar.make(scrollViewFilter, getString(R.string.not_implemented), Snackbar.LENGTH_SHORT).show() - // TODO: Add DateOfBirth field to the user model - } - - cardFilterNeedMentoring.setOnClickListener { - if (needMentoring) { - it.setBackgroundResource(R.drawable.background_rounded_white) - tvFilterNeedMentoring.setTextColor(ResourcesCompat.getColor( - resources, R.color.textColorBlack, null)) - } else { - it.setBackgroundResource(R.drawable.background_rounded_primary) - tvFilterNeedMentoring.setTextColor(ResourcesCompat.getColor( - resources, R.color.white, null)) - } - needMentoring = !needMentoring - map?.put(NEED_MENTORING_KEY, needMentoring.toString()) - } - cardFilterAvailableToMentor.setOnClickListener { - if (availableToMentor) { - it.setBackgroundResource(R.drawable.background_rounded_white) - tvFilterAvailableToMentor.setTextColor(ResourcesCompat.getColor( - resources, R.color.textColorBlack, null)) - } else { - it.setBackgroundResource(R.drawable.background_rounded_primary) - tvFilterAvailableToMentor.setTextColor(ResourcesCompat.getColor( - resources, R.color.white, null)) - } - availableToMentor = !availableToMentor - map?.put(AVAILABLE_TO_MENTOR_KEY, availableToMentor.toString()) - } - - btnClearAll.setOnClickListener { - // sort by fragment - with(previousSelectionSort) { - this?.setBackgroundResource(R.drawable.background_rounded_white) - ((this as CardView?)?.getChildAt(0) as TextView?) - ?.setTextColor(ResourcesCompat.getColor( - resources, R.color.textColorBlack, null)) - } - cardSortRegistrationDate.setBackgroundResource(R.drawable.background_rounded_primary) - tvSortRegistrationDate.setTextColor(ResourcesCompat.getColor( - resources, R.color.white, null)) - previousSelectionSort = cardSortRegistrationDate - - // filter by fragment - // buttons - cardFilterNeedMentoring.setBackgroundResource(R.drawable.background_rounded_white) - tvFilterNeedMentoring.setTextColor(ResourcesCompat.getColor( - resources, R.color.textColorBlack, null)) - cardFilterAvailableToMentor.setBackgroundResource(R.drawable.background_rounded_white) - tvFilterAvailableToMentor.setTextColor(ResourcesCompat.getColor( - resources, R.color.textColorBlack, null)) - - // EditTexts - etFilterInterests.setText("") - etFilterLocation.setText("") - etFilterSkills.setText("") - - map = hashMapOf(SORT_KEY to MembersFragment.SortValues.REGISTRATION_DATE.name) - } - - btnApplyFilter.setOnClickListener { - map?.put(SORT_KEY, when (previousSelectionSort?.id) { - R.id.cardSortNameAZ -> - MembersFragment.SortValues.NAMEAZ.name - R.id.cardSortNameZA -> - MembersFragment.SortValues.NAMEZA.name - R.id.cardSortRegistrationDate -> - MembersFragment.SortValues.REGISTRATION_DATE.name - else -> - MembersFragment.SortValues.REGISTRATION_DATE.name - }) - - map?.put(INTERESTS_KEY, etFilterInterests.text.toString()) - map?.put(LOCATION_KEY, etFilterLocation.text.toString()) - map?.put(SKILLS_KEY, etFilterSkills.text.toString()) - - finishActivity() - } - } - - private fun finishActivity() { - val resultIntent = Intent() - resultIntent.putExtra(FILTER_MAP, map) - setResult(Activity.RESULT_OK, resultIntent) - onBackPressed() - } - - fun buttonOnClickSort(view: View) { - with(previousSelectionSort) { - this?.setBackgroundResource(R.drawable.background_rounded_white) - ((this as CardView?)?.getChildAt(0) as TextView?) - ?.setTextColor(ResourcesCompat.getColor( - resources, R.color.textColorBlack, null)) - } - - view.setBackgroundResource(R.drawable.background_rounded_primary) - ((view as CardView).getChildAt(0) as TextView) - .setTextColor(ResourcesCompat.getColor( - resources, R.color.white, null)) - - previousSelectionSort = view - } - - override fun onOptionsItemSelected(item: MenuItem?): Boolean { - if (item?.itemId == android.R.id.home) - onBackPressed() - return super.onOptionsItemSelected(item) - } - - override fun onBackPressed() { - super.onBackPressed() - overridePendingTransition(R.anim.anim_stay, R.anim.anim_slide_to_bottom) - } -} diff --git a/app/src/main/java/org/anitab/mentorship/view/activities/MainActivity.kt b/app/src/main/java/org/anitab/mentorship/view/activities/MainActivity.kt index 93feda963..73fa70f05 100644 --- a/app/src/main/java/org/anitab/mentorship/view/activities/MainActivity.kt +++ b/app/src/main/java/org/anitab/mentorship/view/activities/MainActivity.kt @@ -6,7 +6,8 @@ import android.view.Menu import android.view.MenuItem import android.widget.Toast import com.google.android.material.bottomnavigation.BottomNavigationView -import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.android.synthetic.main.activity_main.bottomNavigation +import kotlinx.android.synthetic.main.activity_main.toolbar import org.anitab.mentorship.R import org.anitab.mentorship.utils.PreferenceManager import org.anitab.mentorship.view.fragments.HomeFragment diff --git a/app/src/main/java/org/anitab/mentorship/view/activities/MemberProfileActivity.kt b/app/src/main/java/org/anitab/mentorship/view/activities/MemberProfileActivity.kt index 7207726a7..9c53f4ed8 100644 --- a/app/src/main/java/org/anitab/mentorship/view/activities/MemberProfileActivity.kt +++ b/app/src/main/java/org/anitab/mentorship/view/activities/MemberProfileActivity.kt @@ -2,11 +2,22 @@ package org.anitab.mentorship.view.activities import android.content.Intent import android.os.Bundle -import android.view.Menu import android.view.MenuItem import androidx.activity.viewModels import com.google.android.material.snackbar.Snackbar -import kotlinx.android.synthetic.main.activity_member_profile.* +import kotlinx.android.synthetic.main.activity_member_profile.btnSendRequest +import kotlinx.android.synthetic.main.activity_member_profile.srlMemberProfile +import kotlinx.android.synthetic.main.activity_member_profile.tvAvailableToMentor +import kotlinx.android.synthetic.main.activity_member_profile.tvBio +import kotlinx.android.synthetic.main.activity_member_profile.tvInterests +import kotlinx.android.synthetic.main.activity_member_profile.tvLocation +import kotlinx.android.synthetic.main.activity_member_profile.tvName +import kotlinx.android.synthetic.main.activity_member_profile.tvNeedMentoring +import kotlinx.android.synthetic.main.activity_member_profile.tvOccupation +import kotlinx.android.synthetic.main.activity_member_profile.tvOrganization +import kotlinx.android.synthetic.main.activity_member_profile.tvSkills +import kotlinx.android.synthetic.main.activity_member_profile.tvSlackUsername +import kotlinx.android.synthetic.main.activity_member_profile.tvUsername import org.anitab.mentorship.R import org.anitab.mentorship.models.User import org.anitab.mentorship.utils.Constants @@ -18,7 +29,10 @@ import org.anitab.mentorship.viewmodels.ProfileViewModel * This activity will show the public profile of a user of the system */ class MemberProfileActivity : BaseActivity() { + private val memberProfileViewModel: MemberProfileViewModel by viewModels() + private val profileViewModel: ProfileViewModel by viewModels() + private lateinit var userProfile: User private lateinit var currentUser: User @@ -27,117 +41,122 @@ class MemberProfileActivity : BaseActivity() { setContentView(R.layout.activity_member_profile) supportActionBar?.title = getString(R.string.member_profile) supportActionBar?.setDisplayHomeAsUpEnabled(true) - val profileViewModel: ProfileViewModel by viewModels() - profileViewModel.successfulGet.observe(this, { - successful -> + + /** + * getting passed member, showing UI and saving on to memberProfileViewModel. + * Showing snackbar if any error. + */ + val member: User? = intent.getParcelableExtra(Constants.MEMBER_USER_EXTRAS) + member?.let { + setUserProfile(it) + memberProfileViewModel.userId = it.id + } ?: Snackbar.make(getRootView(), getString(R.string.error_filter_not_found), Snackbar.LENGTH_LONG) + .show() + + // Refresh data from network on swipe down gesture + srlMemberProfile.setOnRefreshListener { fetchNewest() } + + btnSendRequest.setOnClickListener { + if (userProfile.availableToMentor == true && userProfile.needMentoring != true && + (currentUser.availableToMentor == true && currentUser.needMentoring != true) + ) { + Snackbar.make(getRootView(), getString(R.string.both_users_only_available_to_mentor), Snackbar.LENGTH_LONG) + .show() + } else { + val intent = Intent(this@MemberProfileActivity, SendRequestActivity::class.java) + intent.putExtra(SendRequestActivity.OTHER_USER_ID_INTENT_EXTRA, userProfile.id) + intent.putExtra(SendRequestActivity.OTHER_USER_NAME_INTENT_EXTRA, userProfile.name) + startActivity(intent) + } + } + + setObservers() + } + + private fun setObservers() { + // observing from profile viewmodel + profileViewModel.successfulGet.observe(this) { successful -> if (successful != null) { if (successful) { setCurrentUser(profileViewModel.user) } else { Snackbar.make(getRootView(), profileViewModel.message, Snackbar.LENGTH_LONG) - .show() + .show() } } - }) + } profileViewModel.getProfile() - srlMemberProfile.setOnRefreshListener { fetchNewest() } - - memberProfileViewModel.successful.observe(this, { - successful -> + // observing from member profile viewmodel + memberProfileViewModel.successful.observe(this) { successful -> srlMemberProfile.isRefreshing = false if (successful != null) { if (successful) { setUserProfile(memberProfileViewModel.userProfile) } else { Snackbar.make(getRootView(), memberProfileViewModel.message, Snackbar.LENGTH_LONG) - .show() - } - } - }) - - val memberId = intent.getIntExtra(Constants.MEMBER_USER_ID, -1) - - memberProfileViewModel.userId = memberId - - fetchNewest() - - btnSendRequest.setOnClickListener { - if (userProfile?.availableToMentor ?: false && !(userProfile?.needMentoring ?:false) && - (currentUser?.availableToMentor ?: false && !(currentUser?.needMentoring ?:false))) { - Snackbar.make(getRootView(), getString(R.string.both_users_only_available_to_mentor), Snackbar.LENGTH_LONG) .show() - } else { - val intent = Intent(this@MemberProfileActivity, SendRequestActivity::class.java) - intent.putExtra(SendRequestActivity.OTHER_USER_ID_INTENT_EXTRA, userProfile.id) - intent.putExtra(SendRequestActivity.OTHER_USER_NAME_INTENT_EXTRA, userProfile.name) - startActivity(intent) + } } } } - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.main_menu, menu) - return true - } - + // To set back button override fun onOptionsItemSelected(menuItem: MenuItem): Boolean { return when (menuItem.itemId) { android.R.id.home -> { onBackPressed() true } - R.id.menu_refresh -> { - fetchNewest() - true - } else -> super.onOptionsItemSelected(menuItem) } } + // Called when swipe down to refresh triggered private fun fetchNewest() { srlMemberProfile.isRefreshing = true memberProfileViewModel.getUserProfile() } + // To set current user profile data private fun setCurrentUser(user: User) { currentUser = user } + + // Setting user data to textviews private fun setUserProfile(user: User) { userProfile = user tvName.text = user.name + // checker for available to mentor if (user.availableToMentor != null) { - setTextViewStartingWithBoldSpan( - tvAvailableToMentor, - getString(R.string.available_to_mentor), - if (user.availableToMentor!!) - getString(R.string.yes) else getString(R.string.no)) + setTextViewStartingWithBoldSpan(tvAvailableToMentor, getString(R.string.available_to_mentor), + if (user.availableToMentor == true) getString(R.string.yes) else getString(R.string.no) + ) } + + // checker for need mentoring if (user.needMentoring != null) { - setTextViewStartingWithBoldSpan( - tvNeedMentoring, - getString(R.string.need_mentoring), - if (user.needMentoring!!) - getString(R.string.yes) else getString(R.string.no)) + setTextViewStartingWithBoldSpan(tvNeedMentoring, getString(R.string.need_mentoring), + if (user.needMentoring == true) getString(R.string.yes) else getString(R.string.no) + ) } + setTextViewStartingWithBoldSpan(tvBio, getString(R.string.bio), user.bio) - setTextViewStartingWithBoldSpan( - tvLocation, getString(R.string.location), user.location) - setTextViewStartingWithBoldSpan( - tvOrganization, getString(R.string.organization), user.organization) - setTextViewStartingWithBoldSpan( - tvOccupation, getString(R.string.occupation), user.occupation) - setTextViewStartingWithBoldSpan( - tvInterests, getString(R.string.interests), user.interests) - setTextViewStartingWithBoldSpan( - tvSkills, getString(R.string.skills), user.skills) - setTextViewStartingWithBoldSpan( - tvUsername, getString(R.string.username), user.username) - setTextViewStartingWithBoldSpan( - tvSlackUsername, getString(R.string.slack_username), user.slackUsername) - if (!user.availableToMentor!! && !user.needMentoring!!) - btnSendRequest.isEnabled = false + setTextViewStartingWithBoldSpan(tvLocation, getString(R.string.location), user.location) + setTextViewStartingWithBoldSpan(tvOrganization, getString(R.string.organization), user.organization) + setTextViewStartingWithBoldSpan(tvOccupation, getString(R.string.occupation), user.occupation) + setTextViewStartingWithBoldSpan(tvInterests, getString(R.string.interests), user.interests) + setTextViewStartingWithBoldSpan(tvSkills, getString(R.string.skills), user.skills) + setTextViewStartingWithBoldSpan(tvUsername, getString(R.string.username), user.username) + setTextViewStartingWithBoldSpan(tvSlackUsername, getString(R.string.slack_username), user.slackUsername) + + // disable 'send request' button when [availableToMentor] & [needMentoring] is null or false + user.run { + if ((availableToMentor == null || availableToMentor == false) && + (needMentoring == null || needMentoring == false) + ) btnSendRequest.isEnabled = false + } } override fun onDestroy() { diff --git a/app/src/main/java/org/anitab/mentorship/view/activities/RequestDetailActivity.kt b/app/src/main/java/org/anitab/mentorship/view/activities/RequestDetailActivity.kt index 9b9f57ac7..4f34100df 100644 --- a/app/src/main/java/org/anitab/mentorship/view/activities/RequestDetailActivity.kt +++ b/app/src/main/java/org/anitab/mentorship/view/activities/RequestDetailActivity.kt @@ -39,9 +39,11 @@ class RequestDetailActivity : BaseActivity() { supportActionBar?.title = getString(R.string.request_detail) supportActionBar?.setDisplayHomeAsUpEnabled(true) - populateView(mentorshipRelationResponse) - setObservables(mentorshipRelationResponse) - setOnClickListeners(mentorshipRelationResponse) + mentorshipRelationResponse?.let { + populateView(it) + setObservables(it) + setOnClickListeners(it) + } } private fun populateView(relationResponse: Relationship) { diff --git a/app/src/main/java/org/anitab/mentorship/view/activities/SendRequestActivity.kt b/app/src/main/java/org/anitab/mentorship/view/activities/SendRequestActivity.kt index 54846d4de..b0013b113 100644 --- a/app/src/main/java/org/anitab/mentorship/view/activities/SendRequestActivity.kt +++ b/app/src/main/java/org/anitab/mentorship/view/activities/SendRequestActivity.kt @@ -44,7 +44,7 @@ class SendRequestActivity : BaseActivity() { tvRequestEndDate.isEnabled = false supportActionBar?.title = getString(R.string.send_request) supportActionBar?.setDisplayHomeAsUpEnabled(true) - val otherUserName = intent.getStringExtra(OTHER_USER_NAME_INTENT_EXTRA) + val otherUserName = intent.getStringExtra(OTHER_USER_NAME_INTENT_EXTRA).toString() val otherUserId = intent.getIntExtra(OTHER_USER_ID_INTENT_EXTRA, 0) val currentUserId = getAuthTokenPayload().identity setObservables() diff --git a/app/src/main/java/org/anitab/mentorship/view/adapters/MemberLoadingStateAdapter.kt b/app/src/main/java/org/anitab/mentorship/view/adapters/MemberLoadingStateAdapter.kt new file mode 100644 index 000000000..95ba4b378 --- /dev/null +++ b/app/src/main/java/org/anitab/mentorship/view/adapters/MemberLoadingStateAdapter.kt @@ -0,0 +1,56 @@ +package org.anitab.mentorship.view.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.paging.LoadState +import androidx.paging.LoadStateAdapter +import androidx.recyclerview.widget.RecyclerView +import org.anitab.mentorship.databinding.ListMemberNetworkStateItemBinding + +class MemberLoadingStateAdapter(private val retry: () -> Unit) : + LoadStateAdapter() { + + override fun onBindViewHolder( + holder: MemberLoadingStateAdapter.LoadStateViewHolder, + loadState: LoadState + ) { + + holder.binding.apply { + when (loadState) { + is LoadState.Loading -> { + pbMembersListState.isVisible = true + btnRetry.isVisible = false + tvErrorMsg.isVisible = false + } + is LoadState.Error -> { + pbMembersListState.isVisible = false + btnRetry.isVisible = true + tvErrorMsg.apply { + isVisible = true + text = loadState.error.message + } + } + else -> pbMembersListState.isVisible = true + } + + btnRetry.setOnClickListener { retry.invoke() } + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + loadState: LoadState + ): MemberLoadingStateAdapter.LoadStateViewHolder { + return LoadStateViewHolder( + ListMemberNetworkStateItemBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + + inner class LoadStateViewHolder(val binding: ListMemberNetworkStateItemBinding) : + RecyclerView.ViewHolder(binding.root) +} diff --git a/app/src/main/java/org/anitab/mentorship/view/adapters/MemberPagingAdapter.kt b/app/src/main/java/org/anitab/mentorship/view/adapters/MemberPagingAdapter.kt new file mode 100644 index 000000000..5a7f585b6 --- /dev/null +++ b/app/src/main/java/org/anitab/mentorship/view/adapters/MemberPagingAdapter.kt @@ -0,0 +1,66 @@ +package org.anitab.mentorship.view.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.view.animation.AnimationUtils +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import org.anitab.mentorship.R +import org.anitab.mentorship.databinding.ListMemberItemBinding +import org.anitab.mentorship.models.User + +class MemberPagingAdapter( + private val openDetailFunction: (memberId: User) -> Unit +) : PagingDataAdapter(MemberDiffUtilCallback()) { + + private var lastPosition = -1 + + override fun onBindViewHolder(holder: MemberPagingAdapter.MemberViewHolder, position: Int) { + val data = getItem(position) + holder.bind(data, position) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): MemberPagingAdapter.MemberViewHolder { + return MemberViewHolder( + ListMemberItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + } + + inner class MemberViewHolder( + private val binding: ListMemberItemBinding + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(data: User?, position: Int) { + data?.let { + binding.apply { + user = data + executePendingBindings() + listMemberItemContainer.setOnClickListener { + openDetailFunction(data) + } + + val animation = AnimationUtils.loadAnimation( + root.context, + if (position > lastPosition) R.anim.bottom_to_top else R.anim.top_to_bottom + ) + itemView.startAnimation(animation) + lastPosition = position + } + } + } + } +} + +private class MemberDiffUtilCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: User, newItem: User): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: User, newItem: User): Boolean { + return oldItem.hashCode() == newItem.hashCode() + } +} diff --git a/app/src/main/java/org/anitab/mentorship/view/adapters/MembersAdapter.kt b/app/src/main/java/org/anitab/mentorship/view/adapters/MembersAdapter.kt deleted file mode 100644 index e1cce6c3d..000000000 --- a/app/src/main/java/org/anitab/mentorship/view/adapters/MembersAdapter.kt +++ /dev/null @@ -1,170 +0,0 @@ -package org.anitab.mentorship.view.adapters - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.animation.AnimationUtils -import android.widget.ImageView -import android.widget.TextView -import androidx.annotation.NonNull -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import kotlinx.android.synthetic.main.list_member_item.view.* -import org.anitab.mentorship.MentorshipApplication -import org.anitab.mentorship.R -import org.anitab.mentorship.models.User -import org.anitab.mentorship.utils.Constants -import org.anitab.mentorship.utils.Constants.INTERESTS_KEY -import org.anitab.mentorship.utils.Constants.LOCATION_KEY -import org.anitab.mentorship.utils.Constants.SKILLS_KEY -import org.anitab.mentorship.utils.NON_VALID_VALUE_REPLACEMENT -import org.anitab.mentorship.utils.UsersDiffCallback -import org.anitab.mentorship.view.fragments.MembersFragment - -/** - * This class represents the adapter that fills in each view of the Members recyclerView - * @param userList list of users to show - * @param openDetailFunction function to be called when an item from Members list is clicked - */ -class MembersAdapter( - private var userList: ArrayList = arrayListOf(), - private val openDetailFunction: (memberId: Int, sharedImageView: ImageView, sharedTextView: TextView) -> Unit - -) : RecyclerView.Adapter() { - - val context = MentorshipApplication.getContext() - var lastPosition = -1 - private var filterMap = hashMapOf(Constants.SORT_KEY to MembersFragment.SortValues.REGISTRATION_DATE.name) - private var filteredUserList = mutableListOf() - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MembersViewHolder = - MembersViewHolder( - LayoutInflater.from(parent.context) - .inflate(R.layout.list_member_item, parent, false) - ) - - override fun onBindViewHolder(@NonNull holder: MembersViewHolder, position: Int) { - val item = filteredUserList[position] - val itemView = holder.itemView - - itemView.tvName.text = item.name - itemView.tvUsername.text = item.username - itemView.tvMentorshipAvailability.text = getMentorshipAvailabilityText(item.availableToMentor, item.needMentoring) - - val userInterests = item.interests - val validText = if (userInterests.isNullOrBlank()) NON_VALID_VALUE_REPLACEMENT else userInterests - val keyText = context.getString(R.string.interests) - val keyValueText = "$keyText: $validText" - itemView.tvInterests.text = keyValueText - - itemView.setOnClickListener { openDetailFunction(item.id!!, itemView.circleImageView, itemView.tvName) } - - val animation = AnimationUtils.loadAnimation(context, - if (position > lastPosition) R.anim.bottom_to_top else R.anim.top_to_bottom) - holder.itemView.startAnimation(animation) - lastPosition = position - } - - override fun getItemCount(): Int = filteredUserList.size - - fun updateUsersList(map: HashMap, newUsers: List) { - // updating users list - setData(newUsers) - // getting updated filtered users - val newFilteredUsers = getFilteredUsers(map, newUsers) - - // applying changes to adapter - val usersDiffCallback = UsersDiffCallback(filteredUserList, newFilteredUsers) - val diffResult = DiffUtil.calculateDiff(usersDiffCallback) - filteredUserList.clear() - filteredUserList.addAll(newFilteredUsers) - diffResult.dispatchUpdatesTo(this) - } - - private fun getFilteredUsers(map: HashMap, newUsers: List): List { - var newFilteredList: List = arrayListOf() - when (map[Constants.SORT_KEY]) { - MembersFragment.SortValues.REGISTRATION_DATE.name -> { - newFilteredList = newUsers - } - MembersFragment.SortValues.NAMEAZ.name -> { - newFilteredList = newUsers.sortedBy { - it.name - } as MutableList - } - MembersFragment.SortValues.NAMEZA.name -> { - newFilteredList = newUsers.sortedByDescending { - it.name - } as MutableList - } - } - - if (map[Constants.NEED_MENTORING_KEY] == "true") - newFilteredList = newFilteredList.filter { - it.needMentoring == true - } as MutableList - - if (map[Constants.AVAILABLE_TO_MENTOR_KEY] == "true") - newFilteredList = newFilteredList.filter { - it.availableToMentor == true - } as MutableList - - val interests = map[INTERESTS_KEY] - val location = map[LOCATION_KEY] - val skills = map[SKILLS_KEY] - - newFilteredList = newFilteredList.filter { - var valid = true - - if (!interests.isNullOrEmpty()) - if (it.interests.isNullOrEmpty()) - valid = false - else if (it.interests?.contains(interests, ignoreCase = true) == false) - valid = false - - if (!location.isNullOrEmpty()) - if (it.location.isNullOrEmpty()) - valid = false - else if (it.location?.contains(location, ignoreCase = true) == false) - valid = false - - if (!skills.isNullOrEmpty()) - if (it.skills.isNullOrEmpty()) - valid = false - else if (it.skills?.contains(skills, ignoreCase = true) == false) - valid = false - - valid - } as MutableList - - return newFilteredList - } - - private fun setData(users: List) { - userList.clear() - userList.addAll(users) - } - - /** - * This class holds a view for each item of the Members list - * @param itemView represents each view of Members list - */ - class MembersViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) - - private fun getMentorshipAvailabilityText(availableToMentor: Boolean?, needMentoring: Boolean?): String { - - if (availableToMentor != null && needMentoring != null) { - return if (availableToMentor && needMentoring) context.getString(R.string.available_to_mentor_and_mentee) - else if (availableToMentor) context.getString(R.string.only_available_to_mentor) - else if (needMentoring) context.getString(R.string.only_available_to_mentee) - else context.getString(R.string.not_available_to_mentor_or_mentee) - } - - return context.getString(R.string.not_available_to_mentor_or_mentee) - } - - override fun onViewDetachedFromWindow(holder: MembersViewHolder) { - super.onViewDetachedFromWindow(holder) - holder.itemView.clearAnimation() - } -} diff --git a/app/src/main/java/org/anitab/mentorship/view/fragments/ChangePasswordFragment.kt b/app/src/main/java/org/anitab/mentorship/view/fragments/ChangePasswordFragment.kt index 134ed0194..28cfebbeb 100644 --- a/app/src/main/java/org/anitab/mentorship/view/fragments/ChangePasswordFragment.kt +++ b/app/src/main/java/org/anitab/mentorship/view/fragments/ChangePasswordFragment.kt @@ -10,8 +10,11 @@ import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import androidx.fragment.app.viewModels -import kotlinx.android.synthetic.main.fragment_change_password.* -import kotlinx.android.synthetic.main.fragment_change_password.view.* +import kotlinx.android.synthetic.main.fragment_change_password.tilConfirmPassword +import kotlinx.android.synthetic.main.fragment_change_password.tilNewPassword +import kotlinx.android.synthetic.main.fragment_change_password.view.tilConfirmPassword +import kotlinx.android.synthetic.main.fragment_change_password.view.tilCurrentPassword +import kotlinx.android.synthetic.main.fragment_change_password.view.tilNewPassword import org.anitab.mentorship.R import org.anitab.mentorship.remote.requests.ChangePassword import org.anitab.mentorship.utils.checkPasswordSecurity diff --git a/app/src/main/java/org/anitab/mentorship/view/fragments/EditProfileFragment.kt b/app/src/main/java/org/anitab/mentorship/view/fragments/EditProfileFragment.kt index 2711f5843..3b6ce95e5 100644 --- a/app/src/main/java/org/anitab/mentorship/view/fragments/EditProfileFragment.kt +++ b/app/src/main/java/org/anitab/mentorship/view/fragments/EditProfileFragment.kt @@ -31,7 +31,7 @@ class EditProfileFragment : DialogFragment() { * Creates an instance of EditProfileFragment */ fun newInstance(user: User): EditProfileFragment { - tempUser = user.copy(id = null, username = null, email = null) + tempUser = user.copy(id = 0, username = null, email = null) return EditProfileFragment() } } diff --git a/app/src/main/java/org/anitab/mentorship/view/fragments/HomeFragment.kt b/app/src/main/java/org/anitab/mentorship/view/fragments/HomeFragment.kt index fd93ffcb2..432778296 100644 --- a/app/src/main/java/org/anitab/mentorship/view/fragments/HomeFragment.kt +++ b/app/src/main/java/org/anitab/mentorship/view/fragments/HomeFragment.kt @@ -10,7 +10,10 @@ import androidx.fragment.app.viewModels import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar -import kotlinx.android.synthetic.main.fragment_home.* +import kotlinx.android.synthetic.main.fragment_home.homeContainer +import kotlinx.android.synthetic.main.fragment_home.rvAchievements +import kotlinx.android.synthetic.main.fragment_home.srlHome +import kotlinx.android.synthetic.main.fragment_home.tvNoAchievements import org.anitab.mentorship.R import org.anitab.mentorship.databinding.FragmentHomeBinding import org.anitab.mentorship.view.adapters.AchievementsAdapter @@ -31,7 +34,6 @@ class HomeFragment : BaseFragment() { * Creates an instance of HomeFragment */ fun newInstance() = HomeFragment() - val TAG: String = HomeFragment::class.java.simpleName } override fun getLayoutResourceId(): Int = R.layout.fragment_home diff --git a/app/src/main/java/org/anitab/mentorship/view/fragments/MembersFragment.kt b/app/src/main/java/org/anitab/mentorship/view/fragments/MembersFragment.kt index 6e6f727bc..786d01e82 100644 --- a/app/src/main/java/org/anitab/mentorship/view/fragments/MembersFragment.kt +++ b/app/src/main/java/org/anitab/mentorship/view/fragments/MembersFragment.kt @@ -1,37 +1,28 @@ package org.anitab.mentorship.view.fragments -import android.app.Activity.RESULT_OK import android.content.Intent import android.os.Bundle -import android.view.Menu -import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.animation.AnimationUtils -import android.widget.ImageView -import android.widget.SearchView -import android.widget.TextView -import android.widget.Toast -import androidx.core.app.ActivityOptionsCompat -import androidx.core.util.Pair -import androidx.core.view.ViewCompat +import android.widget.PopupMenu +import androidx.core.view.isVisible import androidx.fragment.app.viewModels -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.google.android.material.snackbar.Snackbar -import kotlinx.android.synthetic.main.fragment_members.* +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.whenStarted +import androidx.paging.LoadState +import kotlinx.android.synthetic.main.fragment_members.fabFilter +import kotlinx.android.synthetic.main.fragment_members.rvMembers +import kotlinx.android.synthetic.main.fragment_members.srlMembers +import kotlinx.android.synthetic.main.fragment_members.tvEmptyList +import kotlinx.coroutines.launch import org.anitab.mentorship.R import org.anitab.mentorship.models.User import org.anitab.mentorship.utils.Constants -import org.anitab.mentorship.utils.Constants.FILTER_MAP -import org.anitab.mentorship.utils.Constants.FILTER_REQUEST_CODE -import org.anitab.mentorship.utils.Constants.SORT_KEY -import org.anitab.mentorship.utils.EndlessRecyclerScrollListener -import org.anitab.mentorship.view.activities.FilterActivity -import org.anitab.mentorship.view.activities.MainActivity +import org.anitab.mentorship.utils.RecyclerViewItemDecoration import org.anitab.mentorship.view.activities.MemberProfileActivity -import org.anitab.mentorship.view.adapters.MembersAdapter +import org.anitab.mentorship.view.adapters.MemberLoadingStateAdapter +import org.anitab.mentorship.view.adapters.MemberPagingAdapter +import org.anitab.mentorship.viewmodels.ListFilter import org.anitab.mentorship.viewmodels.MembersViewModel /** @@ -46,188 +37,109 @@ class MembersFragment : BaseFragment() { fun newInstance() = MembersFragment() } - private var memberListInitialized = false + private lateinit var memberAdapter: MemberPagingAdapter private val membersViewModel: MembersViewModel by viewModels() - private lateinit var rvAdapter: MembersAdapter - private var isLoading = false - private var isRecyclerView = false - private var filterMap = hashMapOf(SORT_KEY to SortValues.REGISTRATION_DATE.name) override fun getLayoutResourceId(): Int = R.layout.fragment_members - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - super.onCreateOptionsMenu(menu, inflater) - inflater.inflate(R.menu.menu_members, menu) - menu.findItem(R.id.search_item)?.let { searchItem -> - val searchView = searchItem.actionView as SearchView - searchView.queryHint = "Search members" - searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { - override fun onQueryTextChange(newText: String): Boolean { - if (memberListInitialized) - searchUsers(newText) - return false - } - override fun onQueryTextSubmit(query: String): Boolean { - return false - } - }) - } - } - - fun searchUsers(query: String) { - val userList = arrayListOf() - for (user in membersViewModel.userList) { - // ""+ to convert String to CharSequence - if (("" + user.username).contains(query, ignoreCase = true)) { - userList.add(user) - } - } - rvMembers.apply { - layoutManager = LinearLayoutManager(context) - addLoadMoreListener(this) - adapter = MembersAdapter(userList, ::openUserProfile) - } - tvEmptyList.visibility = View.GONE - } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) setHasOptionsMenu(true) - rvAdapter = MembersAdapter(arrayListOf(), ::openUserProfile) - srlMembers.setOnRefreshListener { - if (!isLoading) { - fetchNewest(true) - isLoading = true - } - } - membersViewModel.successful.observe(viewLifecycleOwner, { successful -> - (activity as MainActivity).hideProgressDialog() - srlMembers.isRefreshing = false - isLoading = false - pbMembers.visibility = View.INVISIBLE - if (successful != null) { - if (successful) { - - rvAdapter.updateUsersList(filterMap, membersViewModel.userList) - if (membersViewModel.userList.isEmpty()) { - tvEmptyList.text = getString(R.string.empty_members_list) - rvMembers.visibility = View.GONE - } else { - if (!isRecyclerView) { - rvMembers.apply { - layoutManager = LinearLayoutManager(context) - adapter = MembersAdapter(membersViewModel.userList, ::openUserProfile) - - addLoadMoreListener(this) - runLayoutAnimation(this) - - val dividerItemDecoration = DividerItemDecoration( - this.context, DividerItemDecoration.VERTICAL) - addItemDecoration(dividerItemDecoration) - adapter = rvAdapter - isRecyclerView = true - } - } else { - if (!filterMap["location"].isNullOrEmpty()) { - - val hasUsersWithLocation = membersViewModel.userList.any { - (it.location)?.contains(filterMap["location"]!!, ignoreCase = true) == true - } - - if (!hasUsersWithLocation) { - Toast.makeText(activity, getString(R.string.error_filter_not_found), - Toast.LENGTH_SHORT).show() - } - } - } - memberListInitialized = true - tvEmptyList.visibility = View.GONE - } - } else { - view?.let { - Snackbar.make(it, membersViewModel.message, Snackbar.LENGTH_LONG).show() - } - } - } - }) - fetchNewest(true) + // initializing MemberPagingAdaptermember + memberAdapter = MemberPagingAdapter(::openUserProfile) + + // calling adapter and livedata observer methods + setUpAdapter() + setObserver() + + // setting member list refresh listener + srlMembers.setOnRefreshListener { memberAdapter.refresh() } + + // setting member list filter option fab + with(fabFilter) { + setOnClickListener { view -> view?.let { showListFilterPopup(it) } } + show() + } } - private fun runLayoutAnimation(recyclerView: RecyclerView) { - val context = recyclerView.context - recyclerView.layoutAnimation = AnimationUtils.loadLayoutAnimation(context, - R.anim.layout_fall_down) - recyclerView.adapter?.notifyDataSetChanged() - recyclerView.scheduleLayoutAnimation() + // Setting up member list recyclerview adapter and item decor + private fun setUpAdapter() { + rvMembers.apply { + addItemDecoration(RecyclerViewItemDecoration()) + adapter = + memberAdapter.withLoadStateFooter(footer = MemberLoadingStateAdapter { memberAdapter.retry() }) + } } - private fun addLoadMoreListener(recyclerView: RecyclerView) { - recyclerView.addOnScrollListener(object : - EndlessRecyclerScrollListener(recyclerView.layoutManager as LinearLayoutManager) { - override fun onLoadMore(page: Int, totalItemsCount: Int) { - if (!isLoading) { - fetchNewest(false) - isLoading = true - pbMembers.visibility = View.VISIBLE + // Setting observer to viewmodel to listen changes in member list livedata + private fun setObserver() { + lifecycleScope.launch { + lifecycle.whenStarted { + showMemberListLoadingState() + + membersViewModel.userList.observe(viewLifecycleOwner) { data -> + rvMembers.smoothScrollToPosition(0) + memberAdapter.submitData(viewLifecycleOwner.lifecycle, data) } } - }) + } } - private fun openUserProfile(memberId: Int, sharedImageView: ImageView, sharedTextView: TextView) { - val intent = Intent(activity, MemberProfileActivity::class.java) - intent.putExtra(Constants.MEMBER_USER_ID, memberId) - val imgAnim = Pair.create(sharedImageView, - ViewCompat.getTransitionName(sharedImageView)!!) - - val options = ActivityOptionsCompat.makeSceneTransitionAnimation(baseActivity, imgAnim) - - startActivity(intent, options.toBundle()) - } - private val openUserProfile: (Int) -> Unit = - { memberId -> - val intent = Intent(activity, MemberProfileActivity::class.java) - intent.putExtra(Constants.MEMBER_USER_ID, memberId) - startActivity(intent) - } + // Setting member list loading states + private fun showMemberListLoadingState() { + memberAdapter.addLoadStateListener { loadState -> + if (loadState.refresh is LoadState.Loading) { + srlMembers.isRefreshing = memberAdapter.snapshot().isEmpty() + tvEmptyList.isVisible = false + } else { + srlMembers.isRefreshing = false + + val error = loadState.let { state -> + when { + state.prepend is LoadState.Error -> state.prepend as LoadState.Error + state.append is LoadState.Error -> state.append as LoadState.Error + state.refresh is LoadState.Error -> state.refresh as LoadState.Error + else -> null + } + } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.action_filter -> { - val intent = Intent(context, FilterActivity::class.java) - intent.putExtra(FILTER_MAP, filterMap) - startActivityForResult(intent, FILTER_REQUEST_CODE) - activity?.overridePendingTransition( - R.anim.anim_slide_from_bottom, - R.anim.anim_stay) - true - } - R.id.menu_refresh -> { - fetchNewest(true) - true + error?.let { + if (memberAdapter.snapshot().isEmpty()) { + tvEmptyList.apply { + isVisible = true + text = it.error.message ?: "No such users available" + } + } + } } - else -> super.onOptionsItemSelected(item) } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == FILTER_REQUEST_CODE && resultCode == RESULT_OK) { - filterMap = data?.extras?.get(FILTER_MAP) as HashMap? - ?: hashMapOf(SORT_KEY to SortValues.REGISTRATION_DATE.name) - rvAdapter.updateUsersList(filterMap, membersViewModel.userList) + // On click method on recyclerview member item + private fun openUserProfile(member: User?) { + member?.let { + val intent = Intent(activity, MemberProfileActivity::class.java) + intent.putExtra(Constants.MEMBER_USER_EXTRAS, it) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(intent) } } - enum class SortValues { - NAMEAZ, - NAMEZA, - REGISTRATION_DATE - } - - private fun fetchNewest(isRefresh: Boolean) { - srlMembers.isRefreshing = isRefresh - membersViewModel.getUsers(isRefresh) + // Show member list filter option pop-up + private fun showListFilterPopup(view: View) { + val popup = PopupMenu(requireContext(), view) + popup.inflate(R.menu.menu_members) + popup.setOnMenuItemClickListener { item: MenuItem? -> + when (item!!.itemId) { + R.id.allMembers -> membersViewModel.getFilteredUserList(ListFilter.NO_FILTER) + R.id.availableToMentor -> membersViewModel.getFilteredUserList(ListFilter.AVAILABLE_TO_MENTOR) + R.id.needMentoring -> membersViewModel.getFilteredUserList(ListFilter.NEED_MENTORING) + R.id.haveSkill -> membersViewModel.getFilteredUserList(ListFilter.HAVE_SKILL) + } + true + } + popup.show() } } diff --git a/app/src/main/java/org/anitab/mentorship/view/fragments/ProfileFragment.kt b/app/src/main/java/org/anitab/mentorship/view/fragments/ProfileFragment.kt index 2bd37a44a..5d03b0b1a 100644 --- a/app/src/main/java/org/anitab/mentorship/view/fragments/ProfileFragment.kt +++ b/app/src/main/java/org/anitab/mentorship/view/fragments/ProfileFragment.kt @@ -10,7 +10,7 @@ import android.view.ViewGroup import androidx.databinding.DataBindingUtil import androidx.fragment.app.viewModels import com.google.android.material.snackbar.Snackbar -import kotlinx.android.synthetic.main.fragment_profile.* +import kotlinx.android.synthetic.main.fragment_profile.srlProfile import org.anitab.mentorship.R import org.anitab.mentorship.databinding.FragmentProfileBinding import org.anitab.mentorship.viewmodels.ProfileViewModel @@ -25,7 +25,6 @@ class ProfileFragment : BaseFragment() { * Creates an instance of ProfileFragment */ fun newInstance() = ProfileFragment() - val TAG: String = ProfileFragment::class.java.simpleName } private lateinit var fragmentProfileBinding: FragmentProfileBinding diff --git a/app/src/main/java/org/anitab/mentorship/view/fragments/RequestPagerFragment.kt b/app/src/main/java/org/anitab/mentorship/view/fragments/RequestPagerFragment.kt index 974219c6f..cc4ed7b5e 100644 --- a/app/src/main/java/org/anitab/mentorship/view/fragments/RequestPagerFragment.kt +++ b/app/src/main/java/org/anitab/mentorship/view/fragments/RequestPagerFragment.kt @@ -4,7 +4,8 @@ import android.content.Intent import android.os.Bundle import android.view.View import androidx.recyclerview.widget.LinearLayoutManager -import kotlinx.android.synthetic.main.fragment_request_pager.* +import kotlinx.android.synthetic.main.fragment_request_pager.rvRequestsList +import kotlinx.android.synthetic.main.fragment_request_pager.tvEmptyList import org.anitab.mentorship.R import org.anitab.mentorship.models.Relationship import org.anitab.mentorship.utils.Constants @@ -44,8 +45,8 @@ class RequestPagerFragment : BaseFragment() { super.onActivityCreated(savedInstanceState) arguments?.let { - requestsList = it.getParcelableArrayList(Constants.REQUEST_LIST) - emptyListText = it.getString(Constants.REQUEST_EMPTY_LIST_TEXT) + requestsList = it.getParcelableArrayList(Constants.REQUEST_LIST) ?: mutableListOf() + emptyListText = it.getString(Constants.REQUEST_EMPTY_LIST_TEXT).toString() } setView() } diff --git a/app/src/main/java/org/anitab/mentorship/viewmodels/MembersViewModel.kt b/app/src/main/java/org/anitab/mentorship/viewmodels/MembersViewModel.kt index 124b57c43..4e38d5fb9 100644 --- a/app/src/main/java/org/anitab/mentorship/viewmodels/MembersViewModel.kt +++ b/app/src/main/java/org/anitab/mentorship/viewmodels/MembersViewModel.kt @@ -1,46 +1,72 @@ package org.anitab.mentorship.viewmodels -import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.launch +import androidx.paging.PagingData +import androidx.paging.cachedIn import org.anitab.mentorship.models.User import org.anitab.mentorship.remote.datamanager.UserDataManager -import org.anitab.mentorship.remote.requests.PaginationRequest -import org.anitab.mentorship.utils.CommonUtils -import org.anitab.mentorship.utils.Constants.ITEMS_PER_PAGE /** * This class represents the [ViewModel] component used for the Members Activity */ class MembersViewModel : ViewModel() { - var tag = MembersViewModel::class.java.simpleName - private val userDataManager: UserDataManager = UserDataManager() - val successful: MutableLiveData = MutableLiveData() - var currentPage = 1 - lateinit var message: String - var userList: ArrayList = arrayListOf() - - /** - * Fetches users list from getUsers method of the UserService - */ - fun getUsers(isRefresh: Boolean) { - viewModelScope.launch { - if (isRefresh) { - userList.clear() - currentPage = 1 + private val userListNoFilter: LiveData> = + userDataManager.getAllUsers().cachedIn(viewModelScope) + + private val userLisFilterByNeedMentoring: LiveData> = + userDataManager.getUsersWhoAreNeedMentoring().cachedIn(viewModelScope) + + private val userListFilterByAvailableToMentor: LiveData> = + userDataManager.getUsersWhoAreAvailableToMentor().cachedIn(viewModelScope) + + private val userListFilterByHaveSkill: LiveData> = + userDataManager.getUserWhoHaveSkills().cachedIn(viewModelScope) + + val userList = MediatorLiveData>() + + // setting default filter sa no filter + private var selectedUserFilter = ListFilter.NO_FILTER + + init { + userList.addSource(userListNoFilter) { list -> + if (selectedUserFilter == ListFilter.NO_FILTER) { + list?.let { userList.value = it } + } + } + + userList.addSource(userLisFilterByNeedMentoring) { list -> + if (selectedUserFilter == ListFilter.NEED_MENTORING) { + list?.let { userList.value = it } } - try { - userList.addAll(userDataManager.getUsers(PaginationRequest(currentPage, ITEMS_PER_PAGE))) - currentPage++ - successful.postValue(true) - } catch (throwable: Throwable) { - message = CommonUtils.getErrorMessage(throwable, tag) - successful.postValue(false) + } + + userList.addSource(userListFilterByAvailableToMentor) { list -> + if (selectedUserFilter == ListFilter.AVAILABLE_TO_MENTOR) { + list?.let { userList.value = it } + } + } + + userList.addSource(userListFilterByHaveSkill) { list -> + if (selectedUserFilter == ListFilter.HAVE_SKILL) { + list?.let { userList.value = it } } } } + + fun getFilteredUserList(filter: ListFilter) = when (filter) { + ListFilter.NO_FILTER -> userListNoFilter.value?.let { userList.value = it } + ListFilter.NEED_MENTORING -> userLisFilterByNeedMentoring.value?.let { userList.value = it } + ListFilter.AVAILABLE_TO_MENTOR -> userListFilterByAvailableToMentor.value?.let { userList.value = it } + ListFilter.HAVE_SKILL -> userListFilterByHaveSkill.value?.let { userList.value = it } + }.also { selectedUserFilter = filter } +} + +enum class ListFilter { + NO_FILTER, NEED_MENTORING, AVAILABLE_TO_MENTOR, HAVE_SKILL } diff --git a/app/src/main/res/layout/activity_filter.xml b/app/src/main/res/layout/activity_filter.xml deleted file mode 100644 index 0b6e9a105..000000000 --- a/app/src/main/res/layout/activity_filter.xml +++ /dev/null @@ -1,285 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_members.xml b/app/src/main/res/layout/fragment_members.xml index e61eda731..1a08be6ed 100644 --- a/app/src/main/res/layout/fragment_members.xml +++ b/app/src/main/res/layout/fragment_members.xml @@ -1,9 +1,10 @@ + android:layout_height="match_parent"> - - - + app:layout_constraintTop_toTopOf="parent" + tools:itemCount="3" + tools:listitem="@layout/list_member_item" /> - + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_member_item.xml b/app/src/main/res/layout/list_member_item.xml index fed8f9d54..88417e743 100644 --- a/app/src/main/res/layout/list_member_item.xml +++ b/app/src/main/res/layout/list_member_item.xml @@ -1,80 +1,93 @@ - + xmlns:tools="http://schemas.android.com/tools"> - + + + - + android:background="?attr/selectableItemBackground"> - + - + - + - + + + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_member_network_state_item.xml b/app/src/main/res/layout/list_member_network_state_item.xml new file mode 100644 index 000000000..cee5501b6 --- /dev/null +++ b/app/src/main/res/layout/list_member_network_state_item.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_members.xml b/app/src/main/res/menu/menu_members.xml index 3773a22f9..b83f2e01b 100644 --- a/app/src/main/res/menu/menu_members.xml +++ b/app/src/main/res/menu/menu_members.xml @@ -2,15 +2,20 @@ + + android:id="@+id/allMembers" + android:title="@string/all_members" /> + + android:id="@+id/availableToMentor" + android:title="@string/available_to_mentor" /> + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3fcf004b6..dc2cd3f98 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -22,6 +22,7 @@ Add a new task Age All + All members Mentorship System Apply filter Available to be a: @@ -114,6 +115,7 @@ Mentorship Requests From Github Repository + Have skill I will be a … Please fill in the input fields and/or assign a rating Interests @@ -201,6 +203,7 @@ %1$s %2$s You want to be %1$s\'s %2$s until %3$s %1$s wants to be your %2$s until %3$s + Retry You have already sent a similar request to\u0020 Save Send Request diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 431e64373..2f95afd50 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -16,13 +16,6 @@ true - -