Implement basic pagination for challenges

This commit is contained in:
Phillip Thelen 2019-01-07 14:54:45 +01:00
parent 74e936b649
commit 4b27e3858a
11 changed files with 71 additions and 47 deletions

View file

@ -148,7 +148,7 @@ android {
buildConfigField "String", "STORE", "\"google\""
multiDexEnabled true
versionCode 2053
versionCode 2055
versionName "1.7"
}

View file

@ -302,7 +302,9 @@ public interface ApiService {
/* challenges api */
@GET("challenges/user")
Flowable<HabitResponse<List<Challenge>>> getUserChallenges();
Flowable<HabitResponse<List<Challenge>>> getUserChallenges(@Query("page") Integer page, @Query("member") boolean memberOnly);
@GET("challenges/user")
Flowable<HabitResponse<List<Challenge>>> getUserChallenges(@Query("page") Integer page);
@GET("tasks/challenge/{challengeId}")
Flowable<HabitResponse<TaskList>> getChallengeTasks(@Path("challengeId") String challengeId);

View file

@ -40,7 +40,7 @@ interface ApiClient {
/* challenges api */
val userChallenges: Flowable<List<Challenge>>
fun getUserChallenges(page: Int, memberOnly: Boolean): Flowable<List<Challenge>>
val worldState: Flowable<WorldState>
fun setLanguageCode(languageCode: String)

View file

@ -11,7 +11,7 @@ import io.realm.RealmResults
interface ChallengeRepository : BaseRepository {
fun retrieveChallenges(user: User): Flowable<List<Challenge>>
fun retrieveChallenges(page: Int = 0, memberOnly: Boolean): Flowable<List<Challenge>>
fun getChallenges(): Flowable<RealmResults<Challenge>>
fun getChallenge(challengeId: String): Flowable<Challenge>
fun getChallengeTasks(challengeId: String): Flowable<RealmResults<Task>>

View file

@ -17,8 +17,6 @@ import java.util.*
interface SocialRepository : BaseRepository {
fun getPublicGuilds(): Flowable<RealmResults<Group>>
fun getUserChallenges(): Flowable<List<Challenge>>
fun getUserGroups(): Flowable<RealmResults<Group>>
fun retrieveGroupChat(groupId: String): Single<List<ChatMessage>>
fun getGroupChat(groupId: String): Flowable<RealmResults<ChatMessage>>

View file

@ -599,8 +599,13 @@ class ApiClientImpl//private OnHabitsAPIResult mResultListener;
return apiService.deletePushDevice(regId).compose(configureApiCallObserver())
}
override val userChallenges: Flowable<List<Challenge>>
get() = apiService.userChallenges.compose(configureApiCallObserver())
override fun getUserChallenges(page: Int, memberOnly: Boolean): Flowable<List<Challenge>> {
return if (memberOnly) {
apiService.getUserChallenges(page, memberOnly).compose(configureApiCallObserver())
} else {
apiService.getUserChallenges(page).compose(configureApiCallObserver())
}
}
override fun getChallengeTasks(challengeId: String): Flowable<TaskList> {
return apiService.getChallengeTasks(challengeId).compose(configureApiCallObserver())

View file

@ -9,14 +9,12 @@ import com.habitrpg.android.habitica.models.social.ChallengeMembership
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.tasks.TaskList
import com.habitrpg.android.habitica.models.tasks.TasksOrder
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.Flowable
import io.realm.RealmResults
class ChallengeRepositoryImpl(localRepository: ChallengeLocalRepository, apiClient: ApiClient, userID: String) : BaseRepositoryImpl<ChallengeLocalRepository>(localRepository, apiClient, userID), ChallengeRepository {
override fun isChallengeMember(challengeID: String): Flowable<Boolean> {
return localRepository.isChallengeMember(userID, challengeID)
}
@ -124,9 +122,9 @@ class ChallengeRepositoryImpl(localRepository: ChallengeLocalRepository, apiClie
return localRepository.getUserChallenges(userId)
}
override fun retrieveChallenges(user: User): Flowable<List<Challenge>> {
return apiClient.userChallenges
.doOnNext { localRepository.saveChallenges(it) }
override fun retrieveChallenges(page: Int, memberOnly: Boolean): Flowable<List<Challenge>> {
return apiClient.getUserChallenges(page, memberOnly)
.doOnNext { localRepository.saveChallenges(it, page == 0, memberOnly) }
}
override fun leaveChallenge(challenge: Challenge, keepTasks: String): Flowable<Void> {

View file

@ -196,8 +196,6 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
override fun inviteToGroup(id: String, inviteData: Map<String, Any>): Flowable<List<String>> = apiClient.inviteToGroup(id, inviteData)
override fun getUserChallenges(): Flowable<List<Challenge>> = apiClient.userChallenges
override fun getMember(userId: String?): Flowable<Member> {
return if (userId == null) {
Flowable.empty()

View file

@ -3,7 +3,6 @@ package com.habitrpg.android.habitica.data.local
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.ChallengeMembership
import com.habitrpg.android.habitica.models.social.GroupMembership
import com.habitrpg.android.habitica.models.tasks.Task
import io.reactivex.Flowable
@ -19,7 +18,7 @@ interface ChallengeLocalRepository : BaseLocalRepository {
fun setParticipating(userID: String, challengeID: String, isParticipating: Boolean)
fun saveChallenges(challenges: List<Challenge>)
fun saveChallenges(challenges: List<Challenge>, clearChallenges: Boolean, memberOnly: Boolean)
fun getChallengeMembership(userId: String, id: String): Flowable<ChallengeMembership>
fun getChallengeMemberships(userId: String): Flowable<RealmResults<ChallengeMembership>>
fun isChallengeMember(userID: String, challengeID: String): Flowable<Boolean>

View file

@ -94,17 +94,19 @@ class RealmChallengeLocalRepository(realm: Realm) : RealmBaseLocalRepository(rea
}
}
override fun saveChallenges(challenges: List<Challenge>) {
val localChallenges = realm.where(Challenge::class.java).findAll().createSnapshot()
val challengesToDelete = ArrayList<Challenge>()
for (localTask in localChallenges) {
if (!challenges.contains(localTask)) {
challengesToDelete.add(localTask)
override fun saveChallenges(challenges: List<Challenge>, clearChallenges: Boolean, memberOnly: Boolean) {
if (clearChallenges && !memberOnly) {
val localChallenges = realm.where(Challenge::class.java).findAll().createSnapshot()
val challengesToDelete = ArrayList<Challenge>()
for (localTask in localChallenges) {
if (!challenges.contains(localTask)) {
challengesToDelete.add(localTask)
}
}
}
realm.executeTransaction {
for (localTask in challengesToDelete) {
localTask.deleteFromRealm()
realm.executeTransaction {
for (localTask in challengesToDelete) {
localTask.deleteFromRealm()
}
}
}
realm.executeTransaction { realm1 -> realm1.insertOrUpdate(challenges) }

View file

@ -2,24 +2,25 @@ package com.habitrpg.android.habitica.ui.fragments.social.challenges
import android.content.Intent
import android.os.Bundle
import androidx.core.view.MenuItemCompat
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.recyclerview.widget.LinearLayoutManager
import android.view.*
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.core.view.MenuItemCompat
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.AppComponent
import com.habitrpg.android.habitica.data.ChallengeRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.extensions.notNull
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.activities.ChallengeFormActivity
import com.habitrpg.android.habitica.ui.adapter.social.ChallengesListViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.RecyclerViewEmptySupport
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.helpers.bindView
@ -30,13 +31,20 @@ import io.reactivex.functions.Consumer
import io.realm.RealmResults
import javax.inject.Inject
import javax.inject.Named
import android.widget.Toast
import com.habitrpg.android.habitica.R.id.recyclerView
class ChallengeListFragment : BaseMainFragment(), androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener {
class ChallengeListFragment : BaseFragment(), androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener {
@Inject
lateinit var challengeRepository: ChallengeRepository
@Inject
lateinit var userRepository: UserRepository
@field:[Inject Named(AppModule.NAMED_USER_ID)]
lateinit var userId: String
var user: User? = null
private val swipeRefreshLayout: androidx.swiperefreshlayout.widget.SwipeRefreshLayout? by bindView(R.id.refreshLayout)
private val recyclerView: RecyclerViewEmptySupport? by bindView(R.id.recyclerView)
@ -45,6 +53,8 @@ class ChallengeListFragment : BaseMainFragment(), androidx.swiperefreshlayout.wi
private var challengeAdapter: ChallengesListViewAdapter? = null
private var viewUserChallengesOnly: Boolean = false
private var nextPageToLoad = 0
private var loadedAllData = false
private var challenges: RealmResults<Challenge>? = null
@ -82,11 +92,23 @@ class ChallengeListFragment : BaseMainFragment(), androidx.swiperefreshlayout.wi
this.recyclerView?.setBackgroundResource(R.color.white)
}
compositeSubscription.add(userRepository.getUser().subscribe(Consumer { this.user = it}, RxErrorHandler.handleEmptyError()))
recyclerView?.setEmptyView(emptyView)
recyclerView?.itemAnimator = SafeDefaultItemAnimator()
challengeAdapter?.updateUnfilteredData(challenges)
loadLocalChallenges()
recyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (!recyclerView.canScrollVertically(1)) {
retrieveChallengesPage()
}
}
})
}
private fun openDetailFragment(challengeID: String) {
@ -98,7 +120,7 @@ class ChallengeListFragment : BaseMainFragment(), androidx.swiperefreshlayout.wi
}
override fun onRefresh() {
fetchOnlineChallenges()
retrieveChallengesPage()
}
private fun setRefreshing(state: Boolean) {
@ -114,26 +136,26 @@ class ChallengeListFragment : BaseMainFragment(), androidx.swiperefreshlayout.wi
compositeSubscription.add(observable.firstElement().subscribe(Consumer { challenges ->
if (challenges.size == 0) {
fetchOnlineChallenges()
retrieveChallengesPage()
}
this.challenges = challenges
challengeAdapter?.updateUnfilteredData(challenges)
}, RxErrorHandler.handleEmptyError()))
}
private fun fetchOnlineChallenges() {
setRefreshing(true)
user.notNull {
challengeRepository.retrieveChallenges(it).doOnComplete {
setRefreshing(false)
} .subscribe(Consumer { }, RxErrorHandler.handleEmptyError())
private fun retrieveChallengesPage() {
if (swipeRefreshLayout?.isRefreshing == true || loadedAllData) {
return
}
}
override fun customTitle(): String {
return if (!isAdded) {
""
} else getString(R.string.sidebar_challenges)
setRefreshing(true)
compositeSubscription.add(challengeRepository.retrieveChallenges(nextPageToLoad, viewUserChallengesOnly).doOnComplete {
setRefreshing(false)
} .subscribe(Consumer {
if (it.size < 10) {
loadedAllData = true
}
nextPageToLoad += 1
}, RxErrorHandler.handleEmptyError()))
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
@ -180,7 +202,7 @@ class ChallengeListFragment : BaseMainFragment(), androidx.swiperefreshlayout.wi
return true
}
R.id.action_reload -> {
fetchOnlineChallenges()
retrieveChallengesPage()
return true
}
R.id.action_search -> {