migrate profile fragment to paging v3

This commit is contained in:
Konrad Pozniak 2020-06-17 19:58:05 +02:00
parent d1b5c73d8b
commit 444988a623
10 changed files with 80 additions and 132 deletions

View File

@ -1,95 +0,0 @@
/*
* Copyright (C) 2020 Conny Duck
*
* This file is part of Pixelcat.
*
* Pixelcat is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Pixelcat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package at.connyduck.pixelcat.components.profile
import androidx.paging.DataSource
import androidx.paging.ItemKeyedDataSource
import at.connyduck.pixelcat.db.AccountManager
import at.connyduck.pixelcat.model.Status
import at.connyduck.pixelcat.network.FediverseApi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class ProfileDataSourceFactory(
private val api: FediverseApi,
private val accountId: String?,
private val accountManager: AccountManager,
private val scope: CoroutineScope
) : DataSource.Factory<String, Status>() {
override fun create(): DataSource<String, Status> {
val source = ProfileImageDataSource(api, accountId, accountManager, scope)
return source
}
}
class ProfileImageDataSource(
private val api: FediverseApi,
private val accountId: String?,
private val accountManager: AccountManager,
private val scope: CoroutineScope
) : ItemKeyedDataSource<String, Status>() {
override fun loadInitial(
params: LoadInitialParams<String>,
callback: LoadInitialCallback<Status>
) {
scope.launch(context = Dispatchers.IO) {
val id = accountId ?: accountManager.activeAccount()?.accountId!!
api.accountStatuses(
id,
limit = params.requestedLoadSize,
onlyMedia = true,
excludeReblogs = true
).fold(
{
callback.onResult(it)
},
{
}
)
}
}
override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<Status>) {
scope.launch(context = Dispatchers.IO) {
val id = accountId ?: accountManager.activeAccount()?.accountId!!
api.accountStatuses(
id,
maxId = params.key,
limit = params.requestedLoadSize,
onlyMedia = true,
excludeReblogs = true
).fold(
{
callback.onResult(it)
},
{
}
)
}
}
override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<Status>) {
// we always load from top
}
override fun getKey(item: Status) = item.id
}

View File

@ -23,6 +23,8 @@ import android.os.Bundle
import android.view.View import android.view.View
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.paging.ExperimentalPagingApi
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.MergeAdapter import androidx.recyclerview.widget.MergeAdapter
import at.connyduck.pixelcat.R import at.connyduck.pixelcat.R
@ -41,6 +43,8 @@ import at.connyduck.pixelcat.util.viewBinding
import at.connyduck.pixelcat.util.withArgs import at.connyduck.pixelcat.util.withArgs
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class ProfileFragment : DaggerFragment(R.layout.fragment_profile) { class ProfileFragment : DaggerFragment(R.layout.fragment_profile) {
@ -60,6 +64,7 @@ class ProfileFragment : DaggerFragment(R.layout.fragment_profile) {
private val headerAdapter = ProfileHeaderAdapter() private val headerAdapter = ProfileHeaderAdapter()
private lateinit var imageAdapter: ProfileImageAdapter private lateinit var imageAdapter: ProfileImageAdapter
@ExperimentalPagingApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (activity is MainActivity) { if (activity is MainActivity) {
@ -124,12 +129,9 @@ class ProfileFragment : DaggerFragment(R.layout.fragment_profile) {
} }
} }
) )
viewModel.profileImages.observe( lifecycleScope.launch {
viewLifecycleOwner, viewModel.imageFlow.collectLatest { imageAdapter.submitData(it) }
Observer { }
imageAdapter.submitList(it)
}
)
} }
private fun onAccountChanged(account: Account?) { private fun onAccountChanged(account: Account?) {

View File

@ -21,7 +21,7 @@ package at.connyduck.pixelcat.components.profile
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.paging.PagedListAdapter import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import at.connyduck.pixelcat.R import at.connyduck.pixelcat.R
@ -34,7 +34,7 @@ import coil.api.load
class ProfileImageAdapter( class ProfileImageAdapter(
private val imageSizePx: Int private val imageSizePx: Int
) : PagedListAdapter<Status, ProfileImageViewHolder>( ) : PagingDataAdapter<Status, ProfileImageViewHolder>(
object : DiffUtil.ItemCallback<Status>() { object : DiffUtil.ItemCallback<Status>() {
override fun areItemsTheSame(old: Status, new: Status): Boolean { override fun areItemsTheSame(old: Status, new: Status): Boolean {
return false return false

View File

@ -0,0 +1,57 @@
/*
* Copyright (C) 2020 Conny Duck
*
* This file is part of Pixelcat.
*
* Pixelcat is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Pixelcat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package at.connyduck.pixelcat.components.profile
import androidx.paging.PagingSource
import at.connyduck.pixelcat.db.AccountManager
import at.connyduck.pixelcat.model.Status
import at.connyduck.pixelcat.network.FediverseApi
class ProfileImagePagingSource(
private val api: FediverseApi,
private val accountId: String?,
private val accountManager: AccountManager
) : PagingSource<String, Status>() {
override suspend fun load(params: LoadParams<String>): LoadResult<String, Status> {
if (params is LoadParams.Prepend) {
// we only load from top
return LoadResult.Page(data = emptyList(), nextKey = null, prevKey = null)
}
val id = accountId ?: accountManager.activeAccount()?.accountId!!
return api.accountStatuses(
id,
maxId = params.key,
limit = params.loadSize,
onlyMedia = true,
excludeReblogs = true
).fold(
{
LoadResult.Page(data = it, prevKey = null, nextKey = it.lastOrNull()?.id)
},
{
LoadResult.Error(it)
}
)
}
}

View File

@ -22,16 +22,18 @@ package at.connyduck.pixelcat.components.profile
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.paging.PagedList import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
import at.connyduck.pixelcat.components.util.Error import at.connyduck.pixelcat.components.util.Error
import at.connyduck.pixelcat.components.util.Success import at.connyduck.pixelcat.components.util.Success
import at.connyduck.pixelcat.components.util.UiState import at.connyduck.pixelcat.components.util.UiState
import at.connyduck.pixelcat.db.AccountManager import at.connyduck.pixelcat.db.AccountManager
import at.connyduck.pixelcat.model.Account import at.connyduck.pixelcat.model.Account
import at.connyduck.pixelcat.model.Relationship import at.connyduck.pixelcat.model.Relationship
import at.connyduck.pixelcat.model.Status
import at.connyduck.pixelcat.network.FediverseApi import at.connyduck.pixelcat.network.FediverseApi
import com.bumptech.glide.util.Executors import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -42,7 +44,14 @@ class ProfileViewModel @Inject constructor(
val profile = MutableLiveData<UiState<Account>>() val profile = MutableLiveData<UiState<Account>>()
val relationship = MutableLiveData<UiState<Relationship>>() val relationship = MutableLiveData<UiState<Relationship>>()
val profileImages = MutableLiveData<PagedList<Status>>()
@OptIn(FlowPreview::class)
@ExperimentalPagingApi
val imageFlow = Pager(
config = PagingConfig(pageSize = 10, enablePlaceholders = false),
pagingSourceFactory = { ProfileImagePagingSource(fediverseApi, accountId, accountManager) }
).flow
.cachedIn(viewModelScope)
val isSelf: Boolean val isSelf: Boolean
get() = accountId == null get() = accountId == null
@ -54,7 +63,6 @@ class ProfileViewModel @Inject constructor(
if (!isSelf) { if (!isSelf) {
loadRelationship(reload) loadRelationship(reload)
} }
loadImages(reload)
} }
fun setAccountInfo(accountId: String?) { fun setAccountInfo(accountId: String?) {
@ -92,22 +100,6 @@ class ProfileViewModel @Inject constructor(
} }
} }
private fun loadImages(reload: Boolean = false) {
if (profileImages.value == null || reload) {
profileImages.value = PagedList.Builder(
ProfileImageDataSource(
fediverseApi,
accountId,
accountManager,
viewModelScope
),
20
).setNotifyExecutor(Executors.mainThreadExecutor())
.setFetchExecutor(java.util.concurrent.Executors.newSingleThreadExecutor())
.build()
}
}
private suspend fun getAccountId(): String { private suspend fun getAccountId(): String {
return accountId ?: accountManager.activeAccount()?.accountId!! return accountId ?: accountManager.activeAccount()?.accountId!!
} }

View File

@ -17,8 +17,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package at.connyduck.pixelcat.db package at.connyduck.pixelcat.db
import android.util.Log import android.util.Log

View File

@ -17,8 +17,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package at.connyduck.pixelcat.db.entitity package at.connyduck.pixelcat.db.entitity
import androidx.room.Embedded import androidx.room.Embedded

View File

@ -17,8 +17,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package at.connyduck.pixelcat.db.entitity package at.connyduck.pixelcat.db.entitity
import androidx.room.Embedded import androidx.room.Embedded

View File

@ -17,7 +17,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package at.connyduck.pixelcat.model package at.connyduck.pixelcat.model
import android.os.Parcelable import android.os.Parcelable

View File

@ -17,7 +17,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package at.connyduck.pixelcat.model package at.connyduck.pixelcat.model
import com.squareup.moshi.Json import com.squareup.moshi.Json