improved timeline position after load

This commit is contained in:
Mariotaku Lee 2017-12-30 17:26:14 +08:00
parent d728a86f17
commit e8774425a7
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
6 changed files with 164 additions and 76 deletions

View File

@ -0,0 +1,26 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* 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.
*
* This program 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 <http://www.gnu.org/licenses/>.
*/
package android.arch.paging
fun <T> PagedListAdapterHelper<T>.setPagedListListener(listener: ((list: PagedList<T>?) -> Unit)?) {
mListener = if (listener != null) PagedListAdapterHelper.PagedListListener { pagedList ->
listener(pagedList)
} else null
}

View File

@ -22,6 +22,7 @@ package org.mariotaku.twidere.adapter
import android.annotation.SuppressLint
import android.arch.paging.PagedList
import android.arch.paging.PagedListAdapterHelper
import android.arch.paging.setPagedListListener
import android.content.Context
import android.support.v7.recyclerview.extensions.ListAdapterConfig
import android.support.v7.widget.RecyclerView
@ -44,7 +45,11 @@ import org.mariotaku.twidere.annotation.TimelineStyle
import org.mariotaku.twidere.constant.newDocumentApiKey
import org.mariotaku.twidere.exception.UnsupportedCountIndexException
import org.mariotaku.twidere.extension.model.activityStatus
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.ItemCounts
import org.mariotaku.twidere.model.ObjectId
import org.mariotaku.twidere.model.ParcelableActivity
import org.mariotaku.twidere.model.ParcelableMedia
import org.mariotaku.twidere.model.placeholder.ParcelableActivityPlaceholder
import org.mariotaku.twidere.util.IntentUtils
import org.mariotaku.twidere.util.OnLinkClickHandler
import org.mariotaku.twidere.util.TwidereLinkify
@ -119,6 +124,12 @@ class ParcelableActivitiesAdapter(
var activityClickListener: ActivityAdapterListener? = null
var pagedListListener: ((list: PagedList<ParcelableActivity>?) -> Unit)? = null
set(value) {
field = value
pagedActivitiesHelper.setPagedListListener(value)
}
private val inflater = LayoutInflater.from(context)
private val twidereLinkify = TwidereLinkify(OnLinkClickHandler(context, null, preferences))
private val statusAdapterDelegate = DummyItemAdapter(context, twidereLinkify, this, requestManager)
@ -400,18 +411,6 @@ class ParcelableActivitiesAdapter(
}
}
object ParcelableActivityPlaceholder : ParcelableActivity() {
init {
id = "none"
account_key = UserKey.INVALID
user_key = UserKey.INVALID
}
override fun hashCode(): Int {
return -1
}
}
companion object {
const val ITEM_VIEW_TYPE_STUB = 0
const val ITEM_VIEW_TYPE_GAP = 1

View File

@ -21,6 +21,7 @@ package org.mariotaku.twidere.adapter
import android.arch.paging.PagedList
import android.arch.paging.PagedListAdapterHelper
import android.arch.paging.setPagedListListener
import android.content.Context
import android.support.v4.widget.Space
import android.support.v7.recyclerview.extensions.ListAdapterConfig
@ -99,6 +100,20 @@ class ParcelableStatusesAdapter(
override val itemCounts = ItemCounts(5)
override var loadMoreIndicatorPosition: Int
get() = super.loadMoreIndicatorPosition
set(value) {
super.loadMoreIndicatorPosition = value
updateItemCounts()
}
override var loadMoreSupportedPosition: Int
get() = super.loadMoreSupportedPosition
set(value) {
super.loadMoreSupportedPosition = value
updateItemCounts()
}
var isShowInReplyTo: Boolean = false
set(value) {
if (field == value) return
@ -134,18 +149,10 @@ class ParcelableStatusesAdapter(
val statusStartIndex: Int
get() = getItemStartPosition(ITEM_INDEX_STATUS)
override var loadMoreIndicatorPosition: Int
get() = super.loadMoreIndicatorPosition
var pagedListListener: ((list: PagedList<ParcelableStatus>?) -> Unit)? = null
set(value) {
super.loadMoreIndicatorPosition = value
updateItemCounts()
}
override var loadMoreSupportedPosition: Int
get() = super.loadMoreSupportedPosition
set(value) {
super.loadMoreSupportedPosition = value
updateItemCounts()
field = value
pagedStatusesHelper.setPagedListListener(value)
}
private val inflater: LayoutInflater = LayoutInflater.from(context)

View File

@ -60,7 +60,9 @@ import org.mariotaku.twidere.data.processor.DataSourceItemProcessor
import org.mariotaku.twidere.extension.model.activityStatus
import org.mariotaku.twidere.extension.queryOne
import org.mariotaku.twidere.extension.showContextMenuForChild
import org.mariotaku.twidere.extension.view.PositionWithOffset
import org.mariotaku.twidere.extension.view.firstVisibleItemPosition
import org.mariotaku.twidere.extension.view.firstVisibleItemPositionWithOffset
import org.mariotaku.twidere.extension.view.lastVisibleItemPosition
import org.mariotaku.twidere.fragment.AbsContentRecyclerViewFragment
import org.mariotaku.twidere.fragment.timeline.AbsTimelineFragment
@ -82,6 +84,7 @@ import org.mariotaku.twidere.view.ExtendedRecyclerView
import org.mariotaku.twidere.view.holder.ActivityTitleSummaryViewHolder
import org.mariotaku.twidere.view.holder.GapViewHolder
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder
import java.util.concurrent.atomic.AtomicReference
abstract class AbsActivitiesFragment : AbsContentRecyclerViewFragment<ParcelableActivitiesAdapter, RecyclerView.LayoutManager>() {
@ -125,12 +128,15 @@ abstract class AbsActivitiesFragment : AbsContentRecyclerViewFragment<Parcelable
private val busEventHandler = BusEventHandler()
private val scrollHandler = ScrollHandler()
private val timelineBoundaryCallback = ActivitiesBoundaryCallback()
private val positionBackup: AtomicReference<PositionWithOffset> = AtomicReference()
private var dataController: ExtendedPagedListProvider.DataController? = null
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
registerForContextMenu(recyclerView)
adapter.activityClickListener = ActivityClickHandler()
adapter.pagedListListener = this::onPagedListChanged
adapter.loadMoreSupportedPosition = if (isStandalone) {
LoadMorePosition.NONE
} else {
@ -235,26 +241,7 @@ abstract class AbsActivitiesFragment : AbsContentRecyclerViewFragment<Parcelable
}
override fun onLoadMoreContents(position: Int) {
if (isStandalone || !refreshEnabled || position != LoadMorePosition.END) return
val started = getActivities(object : ContentRefreshParam {
override val accountKeys by lazy {
this@AbsActivitiesFragment.accountKeys
}
override val pagination by lazy {
val keys = accountKeys.toNulls()
val maxIds = DataStoreUtils.getRefreshOldestActivityMaxPositions(context!!, contentUri, keys)
val maxSortIds = DataStoreUtils.getRefreshOldestActivityMaxSortPositions(context!!, contentUri, keys)
return@lazy Array(keys.size) { idx ->
SinceMaxPagination.maxId(maxIds[idx], maxSortIds[idx])
}
}
override val tabId: Long
get() = this@AbsActivitiesFragment.tabId
})
if (!started) return
super.onLoadMoreContents(position)
// No-op
}
override fun scrollToPositionWithOffset(position: Int, offset: Int) {
@ -269,6 +256,9 @@ abstract class AbsActivitiesFragment : AbsContentRecyclerViewFragment<Parcelable
}
}
/**
* Scroll to start of the timeline. This also updates read position
*/
override fun scrollToStart(): Boolean {
val result = super.scrollToStart()
if (result) saveReadPosition(0)
@ -288,7 +278,7 @@ abstract class AbsActivitiesFragment : AbsContentRecyclerViewFragment<Parcelable
protected open fun onDataLoaded(data: PagedList<ParcelableActivity>?) {
val firstVisiblePosition = layoutManager.firstVisibleItemPosition
val firstVisiblePosition = layoutManager.firstVisibleItemPositionWithOffset
adapter.showAccountsColor = accountKeys.size > 1
adapter.activities = data
when {
@ -299,12 +289,15 @@ abstract class AbsActivitiesFragment : AbsContentRecyclerViewFragment<Parcelable
showContent()
}
}
if (firstVisiblePosition == 0 && !preferences[readFromBottomKey]) {
val weakThis by weak(this)
recyclerView.post {
val f = weakThis?.takeIf { !it.isDetached } ?: return@post
f.scrollToStart()
positionBackup.set(firstVisiblePosition)
}
protected open fun onPagedListChanged(data: PagedList<ParcelableActivity>?) {
val firstVisiblePosition = positionBackup.getAndSet(null) ?: return
if (firstVisiblePosition.position == 0 && !preferences[readFromBottomKey]) {
scrollToPositionWithOffset(0, 0)
} else {
scrollToPositionWithOffset(firstVisiblePosition.position, firstVisiblePosition.offset)
}
}
@ -533,6 +526,31 @@ abstract class AbsActivitiesFragment : AbsContentRecyclerViewFragment<Parcelable
}
}
private inner class ActivitiesBoundaryCallback : PagedList.BoundaryCallback<ParcelableStatus>() {
override fun onItemAtEndLoaded(itemAtEnd: ParcelableStatus) {
val started = getActivities(object : ContentRefreshParam {
override val accountKeys by lazy {
this@AbsActivitiesFragment.accountKeys
}
override val pagination by lazy {
val keys = accountKeys.toNulls()
val maxIds = DataStoreUtils.getRefreshOldestActivityMaxPositions(context!!, contentUri, keys)
val maxSortIds = DataStoreUtils.getRefreshOldestActivityMaxSortPositions(context!!, contentUri, keys)
return@lazy Array(keys.size) { idx ->
SinceMaxPagination.maxId(maxIds[idx], maxSortIds[idx])
}
}
override val tabId: Long
get() = this@AbsActivitiesFragment.tabId
})
if (started) {
adapter.loadMoreIndicatorPosition = LoadMorePosition.END
}
}
}
companion object {
val activityColumnsLite = Activities.COLUMNS - arrayOf(Activities.SOURCES, Activities.TARGETS,
Activities.TARGET_OBJECTS, Activities.MENTIONS_JSON, Activities.CARD,

View File

@ -70,6 +70,7 @@ import org.mariotaku.twidere.data.fetcher.StatusesFetcher
import org.mariotaku.twidere.extension.*
import org.mariotaku.twidere.extension.adapter.removeStatuses
import org.mariotaku.twidere.extension.data.observe
import org.mariotaku.twidere.extension.view.PositionWithOffset
import org.mariotaku.twidere.extension.view.firstVisibleItemPosition
import org.mariotaku.twidere.extension.view.firstVisibleItemPositionWithOffset
import org.mariotaku.twidere.extension.view.lastVisibleItemPosition
@ -99,6 +100,7 @@ import org.mariotaku.twidere.view.holder.GapViewHolder
import org.mariotaku.twidere.view.holder.TimelineFilterHeaderViewHolder
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder
import org.mariotaku.twidere.view.holder.status.StatusViewHolder
import java.util.concurrent.atomic.AtomicReference
abstract class AbsTimelineFragment : AbsContentRecyclerViewFragment<ParcelableStatusesAdapter, LayoutManager>(),
IFloatingActionButtonFragment {
@ -154,12 +156,14 @@ abstract class AbsTimelineFragment : AbsContentRecyclerViewFragment<ParcelableSt
private val busEventHandler = BusEventHandler()
private val scrollHandler = ScrollHandler()
private val timelineBoundaryCallback = StatusesBoundaryCallback()
private val positionBackup: AtomicReference<PositionWithOffset> = AtomicReference()
private var dataController: ExtendedPagedListProvider.DataController? = null
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
registerForContextMenu(recyclerView)
adapter.statusClickListener = StatusClickHandler()
adapter.pagedListListener = this::onPagedListChanged
adapter.loadMoreSupportedPosition = if (isStandalone) {
LoadMorePosition.NONE
} else {
@ -285,6 +289,9 @@ abstract class AbsTimelineFragment : AbsContentRecyclerViewFragment<ParcelableSt
}
}
/**
* Scroll to start of the timeline. This also updates read position
*/
override fun scrollToStart(): Boolean {
val result = super.scrollToStart()
if (result) saveReadPosition(0)
@ -317,7 +324,6 @@ abstract class AbsTimelineFragment : AbsContentRecyclerViewFragment<ParcelableSt
showProgress()
}
protected open fun onDataLoaded(data: PagedList<ParcelableStatus>?) {
val firstVisiblePosition = layoutManager.firstVisibleItemPositionWithOffset
adapter.showAccountsColor = accountKeys.size > 1
@ -331,18 +337,15 @@ abstract class AbsTimelineFragment : AbsContentRecyclerViewFragment<ParcelableSt
showContent()
}
}
if (firstVisiblePosition == null) return
positionBackup.set(firstVisiblePosition)
}
protected open fun onPagedListChanged(data: PagedList<ParcelableStatus>?) {
val firstVisiblePosition = positionBackup.getAndSet(null) ?: return
if (firstVisiblePosition.position == 0 && !preferences[readFromBottomKey]) {
val weakThis by weak(this)
recyclerView.post {
val f = weakThis?.takeIf { !it.isDetached && it.view != null } ?: return@post
f.scrollToStart()
}
scrollToPositionWithOffset(0, 0)
} else {
val lm = loaderManager
if (lm is LinearLayoutManager) {
lm.scrollToPositionWithOffset(firstVisiblePosition.position, firstVisiblePosition.offset)
}
scrollToPositionWithOffset(firstVisiblePosition.position, firstVisiblePosition.offset)
}
}
@ -400,7 +403,7 @@ abstract class AbsTimelineFragment : AbsContentRecyclerViewFragment<ParcelableSt
private fun setupLiveData() {
statuses = if (isStandalone) onCreateStandaloneLiveData() else onCreateDatabaseLiveData()
statuses?.observe(this, success = { onDataLoaded(it) }, fail = {
statuses?.observe(this, success = this::onDataLoaded, fail = {
showError(R.drawable.ic_info_error_generic, it.getErrorMessage(context!!))
})
}
@ -487,6 +490,19 @@ abstract class AbsTimelineFragment : AbsContentRecyclerViewFragment<ParcelableSt
}
}
class DefaultOnLikedListener(
private val context: Context,
private val status: ParcelableStatus,
private val accountKey: UserKey? = null
) : LikeAnimationDrawable.OnLikedListener {
override fun onLiked(): Boolean {
if (status.is_favorite) return false
CreateFavoriteTask(context, accountKey ?: status.account_key, status).promise()
return true
}
}
internal inner class BusEventHandler {
@Subscribe
@ -634,19 +650,6 @@ abstract class AbsTimelineFragment : AbsContentRecyclerViewFragment<ParcelableSt
}
}
class DefaultOnLikedListener(
private val context: Context,
private val status: ParcelableStatus,
private val accountKey: UserKey? = null
) : LikeAnimationDrawable.OnLikedListener {
override fun onLiked(): Boolean {
if (status.is_favorite) return false
CreateFavoriteTask(context, accountKey ?: status.account_key, status).promise()
return true
}
}
companion object {
const val REQUEST_FAVORITE_SELECT_ACCOUNT = 101

View File

@ -0,0 +1,35 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* 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.
*
* This program 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 <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.model.placeholder
import org.mariotaku.twidere.model.ParcelableActivity
import org.mariotaku.twidere.model.UserKey
object ParcelableActivityPlaceholder : ParcelableActivity() {
init {
id = "none"
account_key = UserKey.INVALID
user_key = UserKey.INVALID
}
override fun hashCode(): Int {
return -1
}
}