migrate reporting to paging 3 (#2205)
* migrate reporting to paging 3 * apply PR feedback
This commit is contained in:
parent
920c71560b
commit
554820de5f
13 changed files with 162 additions and 874 deletions
|
|
@ -51,7 +51,6 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
|
||||
viewModel.init(accountId, accountUserName, intent?.getStringExtra(STATUS_ID))
|
||||
|
||||
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||
|
|
|
|||
|
|
@ -17,25 +17,35 @@ package com.keylesspalace.tusky.components.report
|
|||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.paging.PagedList
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.cachedIn
|
||||
import com.keylesspalace.tusky.appstore.BlockEvent
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.MuteEvent
|
||||
import com.keylesspalace.tusky.components.report.adapter.StatusesRepository
|
||||
import com.keylesspalace.tusky.components.report.adapter.StatusesPagingSource
|
||||
import com.keylesspalace.tusky.components.report.model.StatusViewState
|
||||
import com.keylesspalace.tusky.entity.Relationship
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.Error
|
||||
import com.keylesspalace.tusky.util.Loading
|
||||
import com.keylesspalace.tusky.util.Resource
|
||||
import com.keylesspalace.tusky.util.RxAwareViewModel
|
||||
import com.keylesspalace.tusky.util.Success
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class ReportViewModel @Inject constructor(
|
||||
private val mastodonApi: MastodonApi,
|
||||
private val eventHub: EventHub,
|
||||
private val statusesRepository: StatusesRepository) : RxAwareViewModel() {
|
||||
private val eventHub: EventHub
|
||||
) : RxAwareViewModel() {
|
||||
|
||||
private val navigationMutable = MutableLiveData<Screen?>()
|
||||
val navigation: LiveData<Screen?> = navigationMutable
|
||||
|
|
@ -52,11 +62,19 @@ class ReportViewModel @Inject constructor(
|
|||
private val checkUrlMutable = MutableLiveData<String?>()
|
||||
val checkUrl: LiveData<String?> = checkUrlMutable
|
||||
|
||||
private val repoResult = MutableLiveData<BiListing<Status>>()
|
||||
val statuses: LiveData<PagedList<Status>> = Transformations.switchMap(repoResult) { it.pagedList }
|
||||
val networkStateAfter: LiveData<NetworkState> = Transformations.switchMap(repoResult) { it.networkStateAfter }
|
||||
val networkStateBefore: LiveData<NetworkState> = Transformations.switchMap(repoResult) { it.networkStateBefore }
|
||||
val networkStateRefresh: LiveData<NetworkState> = Transformations.switchMap(repoResult) { it.refreshState }
|
||||
private val accountIdFlow = MutableSharedFlow<String>(
|
||||
replay = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||
)
|
||||
|
||||
val statusesFlow = accountIdFlow.flatMapLatest { accountId ->
|
||||
Pager(
|
||||
initialKey = statusId,
|
||||
config = PagingConfig(pageSize = 20, initialLoadSize = 20),
|
||||
pagingSourceFactory = { StatusesPagingSource(accountId, mastodonApi) }
|
||||
).flow
|
||||
}
|
||||
.cachedIn(viewModelScope)
|
||||
|
||||
private val selectedIds = HashSet<String>()
|
||||
val statusViewState = StatusViewState()
|
||||
|
|
@ -84,7 +102,10 @@ class ReportViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
obtainRelationship()
|
||||
repoResult.value = statusesRepository.getStatuses(accountId, statusId, disposables)
|
||||
|
||||
viewModelScope.launch {
|
||||
accountIdFlow.emit(accountId)
|
||||
}
|
||||
}
|
||||
|
||||
fun navigateTo(screen: Screen) {
|
||||
|
|
@ -95,7 +116,6 @@ class ReportViewModel @Inject constructor(
|
|||
navigationMutable.value = null
|
||||
}
|
||||
|
||||
|
||||
private fun obtainRelationship() {
|
||||
val ids = listOf(accountId)
|
||||
muteStateMutable.value = Loading()
|
||||
|
|
@ -115,7 +135,6 @@ class ReportViewModel @Inject constructor(
|
|||
.autoDispose()
|
||||
}
|
||||
|
||||
|
||||
private fun updateRelationship(relationship: Relationship?) {
|
||||
if (relationship != null) {
|
||||
muteStateMutable.value = Success(relationship.muting)
|
||||
|
|
@ -194,14 +213,6 @@ class ReportViewModel @Inject constructor(
|
|||
|
||||
}
|
||||
|
||||
fun retryStatusLoad() {
|
||||
repoResult.value?.retry?.invoke()
|
||||
}
|
||||
|
||||
fun refreshStatuses() {
|
||||
repoResult.value?.refresh?.invoke()
|
||||
}
|
||||
|
||||
fun checkClickedUrl(url: String?) {
|
||||
checkUrlMutable.value = url
|
||||
}
|
||||
|
|
@ -221,5 +232,4 @@ class ReportViewModel @Inject constructor(
|
|||
fun isStatusChecked(id: String): Boolean {
|
||||
return selectedIds.contains(id)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ package com.keylesspalace.tusky.components.report.adapter
|
|||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.paging.PagedListAdapter
|
||||
import androidx.paging.PagingDataAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.components.report.model.StatusViewState
|
||||
|
|
@ -29,7 +29,7 @@ class StatusesAdapter(
|
|||
private val statusDisplayOptions: StatusDisplayOptions,
|
||||
private val statusViewState: StatusViewState,
|
||||
private val adapterHandler: AdapterHandler
|
||||
) : PagedListAdapter<Status, StatusViewHolder>(STATUS_COMPARATOR) {
|
||||
) : PagingDataAdapter<Status, StatusViewHolder>(STATUS_COMPARATOR) {
|
||||
|
||||
private val statusForPosition: (Int) -> Status? = { position: Int ->
|
||||
if (position != RecyclerView.NO_POSITION) getItem(position) else null
|
||||
|
|
|
|||
|
|
@ -1,150 +0,0 @@
|
|||
/* Copyright 2019 Joel Pyska
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* Tusky 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 Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.components.report.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.paging.ItemKeyedDataSource
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.NetworkState
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.functions.BiFunction
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
class StatusesDataSource(private val accountId: String,
|
||||
private val mastodonApi: MastodonApi,
|
||||
private val disposables: CompositeDisposable,
|
||||
private val retryExecutor: Executor) : ItemKeyedDataSource<String, Status>() {
|
||||
|
||||
val networkStateAfter = MutableLiveData<NetworkState>()
|
||||
val networkStateBefore = MutableLiveData<NetworkState>()
|
||||
|
||||
private var retryAfter: (() -> Any)? = null
|
||||
private var retryBefore: (() -> Any)? = null
|
||||
private var retryInitial: (() -> Any)? = null
|
||||
|
||||
val initialLoad = MutableLiveData<NetworkState>()
|
||||
fun retryAllFailed() {
|
||||
var prevRetry = retryInitial
|
||||
retryInitial = null
|
||||
prevRetry?.let {
|
||||
retryExecutor.execute {
|
||||
it.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
prevRetry = retryAfter
|
||||
retryAfter = null
|
||||
prevRetry?.let {
|
||||
retryExecutor.execute {
|
||||
it.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
prevRetry = retryBefore
|
||||
retryBefore = null
|
||||
prevRetry?.let {
|
||||
retryExecutor.execute {
|
||||
it.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
override fun loadInitial(params: LoadInitialParams<String>, callback: LoadInitialCallback<Status>) {
|
||||
networkStateAfter.postValue(NetworkState.LOADED)
|
||||
networkStateBefore.postValue(NetworkState.LOADED)
|
||||
retryAfter = null
|
||||
retryBefore = null
|
||||
retryInitial = null
|
||||
initialLoad.postValue(NetworkState.LOADING)
|
||||
val initialKey = params.requestedInitialKey
|
||||
if (initialKey == null) {
|
||||
mastodonApi.accountStatusesObservable(accountId, null, null, params.requestedLoadSize, true)
|
||||
} else {
|
||||
mastodonApi.statusObservable(initialKey).zipWith(
|
||||
mastodonApi.accountStatusesObservable(accountId, params.requestedInitialKey, null, params.requestedLoadSize - 1, true),
|
||||
BiFunction { status: Status, list: List<Status> ->
|
||||
val ret = ArrayList<Status>()
|
||||
ret.add(status)
|
||||
ret.addAll(list)
|
||||
return@BiFunction ret
|
||||
})
|
||||
}
|
||||
.doOnSubscribe {
|
||||
disposables.add(it)
|
||||
}
|
||||
.subscribe(
|
||||
{
|
||||
callback.onResult(it)
|
||||
initialLoad.postValue(NetworkState.LOADED)
|
||||
},
|
||||
{
|
||||
retryInitial = {
|
||||
loadInitial(params, callback)
|
||||
}
|
||||
initialLoad.postValue(NetworkState.error(it.message))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<Status>) {
|
||||
networkStateAfter.postValue(NetworkState.LOADING)
|
||||
retryAfter = null
|
||||
mastodonApi.accountStatusesObservable(accountId, params.key, null, params.requestedLoadSize, true)
|
||||
.doOnSubscribe {
|
||||
disposables.add(it)
|
||||
}
|
||||
.subscribe(
|
||||
{
|
||||
callback.onResult(it)
|
||||
networkStateAfter.postValue(NetworkState.LOADED)
|
||||
},
|
||||
{
|
||||
retryAfter = {
|
||||
loadAfter(params, callback)
|
||||
}
|
||||
networkStateAfter.postValue(NetworkState.error(it.message))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<Status>) {
|
||||
networkStateBefore.postValue(NetworkState.LOADING)
|
||||
retryBefore = null
|
||||
mastodonApi.accountStatusesObservable(accountId, null, params.key, params.requestedLoadSize, true)
|
||||
.doOnSubscribe {
|
||||
disposables.add(it)
|
||||
}
|
||||
.subscribe(
|
||||
{
|
||||
callback.onResult(it)
|
||||
networkStateBefore.postValue(NetworkState.LOADED)
|
||||
},
|
||||
{
|
||||
retryBefore = {
|
||||
loadBefore(params, callback)
|
||||
}
|
||||
networkStateBefore.postValue(NetworkState.error(it.message))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun getKey(item: Status): String = item.id
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
/* Copyright 2019 Joel Pyska
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* Tusky 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 Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.components.report.adapter
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.paging.DataSource
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
class StatusesDataSourceFactory(
|
||||
private val accountId: String,
|
||||
private val mastodonApi: MastodonApi,
|
||||
private val disposables: CompositeDisposable,
|
||||
private val retryExecutor: Executor) : DataSource.Factory<String, Status>() {
|
||||
val sourceLiveData = MutableLiveData<StatusesDataSource>()
|
||||
override fun create(): DataSource<String, Status> {
|
||||
val source = StatusesDataSource(accountId, mastodonApi, disposables, retryExecutor)
|
||||
sourceLiveData.postValue(source)
|
||||
return source
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/* Copyright 2021 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* Tusky 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 Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.components.report.adapter
|
||||
|
||||
import android.util.Log
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.rx3.await
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class StatusesPagingSource(
|
||||
private val accountId: String,
|
||||
private val mastodonApi: MastodonApi
|
||||
) : PagingSource<String, Status>() {
|
||||
|
||||
override fun getRefreshKey(state: PagingState<String, Status>): String? {
|
||||
return state.anchorPosition?.let { anchorPosition ->
|
||||
state.closestItemToPosition(anchorPosition)?.id
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(params: LoadParams<String>): LoadResult<String, Status> {
|
||||
val key = params.key
|
||||
try {
|
||||
val result = if (params is LoadParams.Refresh && key != null) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val initialStatus = async { getSingleStatus(key) }
|
||||
val additionalStatuses = async { getStatusList(maxId = key, limit = params.loadSize - 1) }
|
||||
listOf(initialStatus.await()) + additionalStatuses.await()
|
||||
}
|
||||
} else {
|
||||
val maxId = if (params is LoadParams.Refresh || params is LoadParams.Append) {
|
||||
params.key
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val minId = if (params is LoadParams.Prepend) {
|
||||
params.key
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
getStatusList(minId = minId, maxId = maxId, limit = params.loadSize)
|
||||
}
|
||||
return LoadResult.Page(
|
||||
data = result,
|
||||
prevKey = result.firstOrNull()?.id,
|
||||
nextKey = result.lastOrNull()?.id
|
||||
)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.w("StatusesPagingSource", "failed to load statuses", e)
|
||||
return LoadResult.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getSingleStatus(statusId: String): Status {
|
||||
return mastodonApi.statusObservable(statusId).await()
|
||||
}
|
||||
|
||||
private suspend fun getStatusList(minId: String? = null, maxId: String? = null, limit: Int): List<Status> {
|
||||
return mastodonApi.accountStatusesObservable(
|
||||
accountId = accountId,
|
||||
maxId = maxId,
|
||||
sinceId = null,
|
||||
minId = minId,
|
||||
limit = limit,
|
||||
excludeReblogs = true
|
||||
).await()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
/* Copyright 2019 Joel Pyska
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* Tusky 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 Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.components.report.adapter
|
||||
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.paging.Config
|
||||
import androidx.paging.toLiveData
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.BiListing
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import java.util.concurrent.Executors
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class StatusesRepository @Inject constructor(private val mastodonApi: MastodonApi) {
|
||||
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
|
||||
fun getStatuses(accountId: String, initialStatus: String?, disposables: CompositeDisposable, pageSize: Int = 20): BiListing<Status> {
|
||||
val sourceFactory = StatusesDataSourceFactory(accountId, mastodonApi, disposables, executor)
|
||||
val livePagedList = sourceFactory.toLiveData(
|
||||
config = Config(pageSize = pageSize, enablePlaceholders = false, initialLoadSizeHint = pageSize * 2),
|
||||
fetchExecutor = executor, initialLoadKey = initialStatus
|
||||
)
|
||||
return BiListing(
|
||||
pagedList = livePagedList,
|
||||
networkStateBefore = Transformations.switchMap(sourceFactory.sourceLiveData) {
|
||||
it.networkStateBefore
|
||||
},
|
||||
networkStateAfter = Transformations.switchMap(sourceFactory.sourceLiveData) {
|
||||
it.networkStateAfter
|
||||
},
|
||||
retry = {
|
||||
sourceFactory.sourceLiveData.value?.retryAllFailed()
|
||||
},
|
||||
refresh = {
|
||||
sourceFactory.sourceLiveData.value?.invalidate()
|
||||
},
|
||||
refreshState = Transformations.switchMap(sourceFactory.sourceLiveData) {
|
||||
it.initialLoad
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -27,7 +27,12 @@ import com.keylesspalace.tusky.components.report.Screen
|
|||
import com.keylesspalace.tusky.databinding.FragmentReportNoteBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.Error
|
||||
import com.keylesspalace.tusky.util.Loading
|
||||
import com.keylesspalace.tusky.util.Success
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -92,12 +97,10 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
|||
binding.progressBar.hide()
|
||||
|
||||
Snackbar.make(binding.buttonBack, if (error is IOException) R.string.error_network else R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
.apply {
|
||||
setAction(R.string.action_retry) {
|
||||
sendReport()
|
||||
}
|
||||
}
|
||||
.show()
|
||||
.setAction(R.string.action_retry) {
|
||||
sendReport()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun sendReport() {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import androidx.core.app.ActivityOptionsCompat
|
|||
import androidx.core.view.ViewCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.paging.LoadState
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
|
|
@ -43,10 +45,11 @@ import com.keylesspalace.tusky.entity.Status
|
|||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.util.CardViewMode
|
||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Injectable, AdapterHandler {
|
||||
|
|
@ -70,13 +73,11 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
|||
when (actionable.attachments[idx].type) {
|
||||
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
|
||||
val attachments = AttachmentViewData.list(actionable)
|
||||
val intent = ViewMediaActivity.newIntent(context, attachments,
|
||||
idx)
|
||||
val intent = ViewMediaActivity.newIntent(context, attachments, idx)
|
||||
if (v != null) {
|
||||
val url = actionable.attachments[idx].url
|
||||
ViewCompat.setTransitionName(v, url)
|
||||
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(),
|
||||
v, url)
|
||||
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), v, url)
|
||||
startActivity(intent, options.toBundle())
|
||||
} else {
|
||||
startActivity(intent)
|
||||
|
|
@ -85,7 +86,6 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
|||
Attachment.Type.UNKNOWN -> {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
|||
|
||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||
snackbarErrorRetry?.dismiss()
|
||||
viewModel.refreshStatuses()
|
||||
adapter.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -118,62 +118,46 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
|||
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
||||
)
|
||||
|
||||
adapter = StatusesAdapter(statusDisplayOptions,
|
||||
viewModel.statusViewState, this)
|
||||
adapter = StatusesAdapter(statusDisplayOptions, viewModel.statusViewState, this)
|
||||
|
||||
binding.recyclerView.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL))
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
binding.recyclerView.adapter = adapter
|
||||
(binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
|
||||
viewModel.statuses.observe(viewLifecycleOwner) {
|
||||
adapter.submitList(it)
|
||||
lifecycleScope.launch {
|
||||
viewModel.statusesFlow.collectLatest { pagingData ->
|
||||
adapter.submitData(pagingData)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.networkStateAfter.observe(viewLifecycleOwner) {
|
||||
if (it?.status == com.keylesspalace.tusky.util.Status.RUNNING)
|
||||
binding.progressBarBottom.show()
|
||||
else
|
||||
binding.progressBarBottom.hide()
|
||||
adapter.addLoadStateListener { loadState ->
|
||||
if (loadState.refresh is LoadState.Error
|
||||
|| loadState.append is LoadState.Error
|
||||
|| loadState.prepend is LoadState.Error) {
|
||||
showError()
|
||||
}
|
||||
|
||||
if (it?.status == com.keylesspalace.tusky.util.Status.FAILED)
|
||||
showError(it.msg)
|
||||
}
|
||||
binding.progressBarBottom.visible(loadState.append == LoadState.Loading)
|
||||
binding.progressBarTop.visible(loadState.prepend == LoadState.Loading)
|
||||
binding.progressBarLoading.visible(loadState.refresh == LoadState.Loading && !binding.swipeRefreshLayout.isRefreshing)
|
||||
|
||||
viewModel.networkStateBefore.observe(viewLifecycleOwner) {
|
||||
if (it?.status == com.keylesspalace.tusky.util.Status.RUNNING)
|
||||
binding.progressBarTop.show()
|
||||
else
|
||||
binding.progressBarTop.hide()
|
||||
|
||||
if (it?.status == com.keylesspalace.tusky.util.Status.FAILED)
|
||||
showError(it.msg)
|
||||
}
|
||||
|
||||
viewModel.networkStateRefresh.observe(viewLifecycleOwner) {
|
||||
if (it?.status == com.keylesspalace.tusky.util.Status.RUNNING && !binding.swipeRefreshLayout.isRefreshing)
|
||||
binding.progressBarLoading.show()
|
||||
else
|
||||
binding.progressBarLoading.hide()
|
||||
|
||||
if (it?.status != com.keylesspalace.tusky.util.Status.RUNNING)
|
||||
if (loadState.refresh != LoadState.Loading) {
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
if (it?.status == com.keylesspalace.tusky.util.Status.FAILED)
|
||||
showError(it.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showError(@Suppress("UNUSED_PARAMETER") msg: String?) {
|
||||
private fun showError() {
|
||||
if (snackbarErrorRetry?.isShown != true) {
|
||||
snackbarErrorRetry = Snackbar.make(binding.swipeRefreshLayout, R.string.failed_fetch_statuses, Snackbar.LENGTH_INDEFINITE)
|
||||
snackbarErrorRetry?.setAction(R.string.action_retry) {
|
||||
viewModel.retryStatusLoad()
|
||||
adapter.retry()
|
||||
}
|
||||
snackbarErrorRetry?.show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun handleClicks() {
|
||||
binding.buttonCancel.setOnClickListener {
|
||||
viewModel.navigateTo(Screen.Back)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue