improved debug preview
This commit is contained in:
parent
0e8fc18801
commit
d5d5132354
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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.data
|
||||
|
||||
import android.arch.paging.PageKeyedDataSource
|
||||
import android.content.ContentResolver
|
||||
import android.database.ContentObserver
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import org.mariotaku.ktextension.weak
|
||||
import org.mariotaku.twidere.data.processor.DataSourceItemProcessor
|
||||
import org.mariotaku.twidere.extension.queryAll
|
||||
import org.mariotaku.twidere.util.DebugLog
|
||||
|
||||
internal class ContiguousCursorObjectDataSource<T : Any>(
|
||||
val resolver: ContentResolver,
|
||||
val uri: Uri,
|
||||
val projection: Array<String>? = null,
|
||||
val selection: String? = null,
|
||||
val selectionArgs: Array<String>? = null,
|
||||
val sortOrder: String? = null,
|
||||
val cls: Class<T>,
|
||||
val processor: DataSourceItemProcessor<T>?
|
||||
) : PageKeyedDataSource<Int, T>() {
|
||||
|
||||
init {
|
||||
val weakThis by weak(this)
|
||||
val observer: ContentObserver = object : ContentObserver(MainHandler) {
|
||||
override fun onChange(selfChange: Boolean) {
|
||||
resolver.unregisterContentObserver(this)
|
||||
weakThis?.invalidate()
|
||||
}
|
||||
}
|
||||
resolver.registerContentObserver(uri, false, observer)
|
||||
processor?.init(resolver)
|
||||
}
|
||||
|
||||
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, T>) {
|
||||
val result = loadRange(0, params.requestedLoadSize)
|
||||
if (result == null) {
|
||||
invalidate()
|
||||
return
|
||||
}
|
||||
callback.onResult(result.data, null, params.requestedLoadSize)
|
||||
}
|
||||
|
||||
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, T>) {
|
||||
val result = loadRange(params.key, params.requestedLoadSize)
|
||||
if (result == null) {
|
||||
invalidate()
|
||||
return
|
||||
}
|
||||
if (params.key == 0) {
|
||||
callback.onResult(result.data, null)
|
||||
} else {
|
||||
callback.onResult(result.data, (params.key - params.requestedLoadSize).coerceAtLeast(0))
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, T>) {
|
||||
val result = loadRange(params.key, params.requestedLoadSize)
|
||||
if (result == null) {
|
||||
invalidate()
|
||||
return
|
||||
}
|
||||
callback.onResult(result.data, params.key + params.requestedLoadSize)
|
||||
}
|
||||
|
||||
private fun loadRange(startPosition: Int, count: Int): RangeResult<T>? {
|
||||
val start = System.currentTimeMillis()
|
||||
val result = resolver.queryAll(uri, projection, selection, selectionArgs, sortOrder,
|
||||
"$startPosition,$count", cls) ?: return null
|
||||
DebugLog.d(msg = "Querying $uri:$startPosition,$count took ${System.currentTimeMillis() - start} ms.")
|
||||
if (processor != null) {
|
||||
return RangeResult(result.mapNotNull(processor::process), result.size)
|
||||
}
|
||||
return RangeResult(result, result.size)
|
||||
}
|
||||
|
||||
override fun invalidate() {
|
||||
processor?.invalidate()
|
||||
super.invalidate()
|
||||
}
|
||||
|
||||
private object MainHandler : Handler(Looper.getMainLooper())
|
||||
|
||||
private data class RangeResult<out T>(val data: List<T>, val unfilteredCount: Int)
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
package org.mariotaku.twidere.data
|
||||
|
||||
import android.arch.paging.DataSource
|
||||
import android.arch.paging.PageKeyedDataSource
|
||||
import android.arch.paging.PositionalDataSource
|
||||
import android.content.ContentResolver
|
||||
import android.database.ContentObserver
|
||||
import android.net.Uri
|
||||
|
@ -59,7 +59,7 @@ class CursorObjectDataSourceFactory<T : Any>(
|
|||
val sortOrder: String? = null,
|
||||
val cls: Class<T>,
|
||||
val processor: DataSourceItemProcessor<T>?
|
||||
) : PageKeyedDataSource<Int, T>() {
|
||||
) : PositionalDataSource<T>() {
|
||||
|
||||
private val totalCount: Int by lazy { resolver.queryCount(uri, selection, selectionArgs) }
|
||||
private val filterStates: BooleanArray by lazy { BooleanArray(totalCount) }
|
||||
|
@ -76,55 +76,31 @@ class CursorObjectDataSourceFactory<T : Any>(
|
|||
processor?.init(resolver)
|
||||
}
|
||||
|
||||
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, T>) {
|
||||
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>) {
|
||||
val totalCount = this.totalCount
|
||||
val data = loadRange(0, params.requestedLoadSize)
|
||||
if (data == null) {
|
||||
val firstLoadPosition = computeInitialLoadPosition(params, totalCount)
|
||||
val firstLoadSize = computeInitialLoadSize(params, firstLoadPosition, totalCount)
|
||||
val valid = loadRange(firstLoadPosition, firstLoadSize) { data ->
|
||||
callback.onResult(data, firstLoadPosition, totalCount)
|
||||
}
|
||||
if (!valid) {
|
||||
invalidate()
|
||||
return
|
||||
}
|
||||
callback.onResult(data, 0, totalCount, null, params.requestedLoadSize)
|
||||
}
|
||||
|
||||
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, T>) {
|
||||
val data = loadRange(params.key, params.requestedLoadSize)
|
||||
if (data == null) {
|
||||
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>) {
|
||||
val valid = loadRange(params.startPosition, params.loadSize, callback::onResult)
|
||||
if (!valid) {
|
||||
invalidate()
|
||||
return
|
||||
}
|
||||
if (params.key == 0) {
|
||||
callback.onResult(data, null)
|
||||
} else {
|
||||
callback.onResult(data, (params.key - params.requestedLoadSize).coerceAtLeast(0))
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, T>) {
|
||||
val data = loadRange(params.key, params.requestedLoadSize)
|
||||
if (data == null) {
|
||||
invalidate()
|
||||
return
|
||||
}
|
||||
val nextKey = params.key + params.requestedLoadSize
|
||||
if (nextKey >= totalCount) {
|
||||
callback.onResult(data, null)
|
||||
} else {
|
||||
callback.onResult(data, nextKey)
|
||||
}
|
||||
override fun invalidate() {
|
||||
processor?.invalidate()
|
||||
super.invalidate()
|
||||
}
|
||||
|
||||
private fun loadRange(startPosition: Int, count: Int): List<T>? {
|
||||
val start = System.currentTimeMillis()
|
||||
val result = resolver.queryAll(uri, projection, selection, selectionArgs, sortOrder,
|
||||
"$startPosition,$count", cls) ?: return null
|
||||
DebugLog.d(msg = "Querying $uri:$startPosition,$count took ${System.currentTimeMillis() - start} ms.")
|
||||
if (processor != null) {
|
||||
return result.mapNotNull(processor::process)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun loadRangePositional(startPosition: Int, count: Int, callback: (List<T>) -> Unit): Boolean {
|
||||
private fun loadRange(startPosition: Int, count: Int, callback: (List<T>) -> Unit): Boolean {
|
||||
if (processor == null) {
|
||||
val start = System.currentTimeMillis()
|
||||
val result = resolver.queryAll(uri, projection, selection, selectionArgs, sortOrder,
|
||||
|
@ -155,11 +131,6 @@ class CursorObjectDataSourceFactory<T : Any>(
|
|||
return true
|
||||
}
|
||||
|
||||
override fun invalidate() {
|
||||
processor?.invalidate()
|
||||
super.invalidate()
|
||||
}
|
||||
|
||||
private fun BooleanArray.actualIndex(index: Int, def: Int): Int {
|
||||
var actual = -1
|
||||
forEachIndexed { i, v ->
|
||||
|
|
|
@ -53,9 +53,31 @@ val RecyclerView.LayoutManager.firstVisibleItemPosition: Int
|
|||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
|
||||
val RecyclerView.LayoutManager.firstVisibleItemPositionWithOffset: PositionWithOffset?
|
||||
get() {
|
||||
when (this) {
|
||||
is LinearLayoutManager -> {
|
||||
val pos = findFirstVisibleItemPosition()
|
||||
if (pos < 0) return null
|
||||
val offset = findViewByPosition(pos).top
|
||||
return PositionWithOffset(pos, offset)
|
||||
}
|
||||
is StaggeredGridLayoutManager -> {
|
||||
val pos = findFirstVisibleItemPositions(null).firstOrNull() ?: return null
|
||||
if (pos < 0) return null
|
||||
val offset = findViewByPosition(pos).top
|
||||
return PositionWithOffset(pos, offset)
|
||||
}
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
||||
val RecyclerView.LayoutManager.lastVisibleItemPosition: Int
|
||||
get() = when (this) {
|
||||
is LinearLayoutManager -> findLastVisibleItemPosition()
|
||||
is StaggeredGridLayoutManager -> findLastVisibleItemPositions(null).lastOrNull() ?: -1
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
||||
data class PositionWithOffset(val position: Int, val offset: Int)
|
|
@ -71,6 +71,7 @@ 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.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.BaseFragment
|
||||
|
@ -318,7 +319,7 @@ abstract class AbsTimelineFragment : AbsContentRecyclerViewFragment<ParcelableSt
|
|||
|
||||
|
||||
protected open fun onDataLoaded(data: PagedList<ParcelableStatus>?) {
|
||||
val firstVisiblePosition = layoutManager.firstVisibleItemPosition
|
||||
val firstVisiblePosition = layoutManager.firstVisibleItemPositionWithOffset
|
||||
adapter.showAccountsColor = accountKeys.size > 1
|
||||
adapter.statuses = data
|
||||
adapter.timelineFilter = timelineFilter
|
||||
|
@ -330,12 +331,18 @@ abstract class AbsTimelineFragment : AbsContentRecyclerViewFragment<ParcelableSt
|
|||
showContent()
|
||||
}
|
||||
}
|
||||
if (firstVisiblePosition == 0 && !preferences[readFromBottomKey]) {
|
||||
if (firstVisiblePosition == null) return
|
||||
if (firstVisiblePosition.position == 0 && !preferences[readFromBottomKey]) {
|
||||
val weakThis by weak(this)
|
||||
recyclerView.post {
|
||||
val f = weakThis?.takeIf { !it.isDetached } ?: return@post
|
||||
val f = weakThis?.takeIf { !it.isDetached && it.view != null } ?: return@post
|
||||
f.scrollToStart()
|
||||
}
|
||||
} else {
|
||||
val lm = loaderManager
|
||||
if (lm is LinearLayoutManager) {
|
||||
lm.scrollToPositionWithOffset(firstVisiblePosition.position, firstVisiblePosition.offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -603,25 +610,27 @@ abstract class AbsTimelineFragment : AbsContentRecyclerViewFragment<ParcelableSt
|
|||
|
||||
private inner class StatusesBoundaryCallback : PagedList.BoundaryCallback<ParcelableStatus>() {
|
||||
override fun onItemAtEndLoaded(itemAtEnd: ParcelableStatus) {
|
||||
adapter.loadMoreIndicatorPosition = LoadMorePosition.END
|
||||
// val started = getStatuses(object : ContentRefreshParam {
|
||||
// override val accountKeys by lazy {
|
||||
// this@AbsTimelineFragment.accountKeys
|
||||
// }
|
||||
// override val pagination by lazy {
|
||||
// val context = context!!
|
||||
// val keys = accountKeys.toNulls()
|
||||
// val maxIds = DataStoreUtils.getOldestStatusIds(context, contentUri, keys)
|
||||
// val maxSortIds = DataStoreUtils.getOldestStatusSortIds(context, contentUri, keys)
|
||||
// return@lazy Array(keys.size) { idx ->
|
||||
// SinceMaxPagination.maxId(maxIds[idx], maxSortIds[idx])
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override val tabId: Long
|
||||
// get() = this@AbsTimelineFragment.tabId
|
||||
//
|
||||
// })
|
||||
val started = getStatuses(object : ContentRefreshParam {
|
||||
override val accountKeys by lazy {
|
||||
this@AbsTimelineFragment.accountKeys
|
||||
}
|
||||
override val pagination by lazy {
|
||||
val context = context!!
|
||||
val keys = accountKeys.toNulls()
|
||||
val maxIds = DataStoreUtils.getOldestStatusIds(context, contentUri, keys)
|
||||
val maxSortIds = DataStoreUtils.getOldestStatusSortIds(context, contentUri, keys)
|
||||
return@lazy Array(keys.size) { idx ->
|
||||
SinceMaxPagination.maxId(maxIds[idx], maxSortIds[idx])
|
||||
}
|
||||
}
|
||||
|
||||
override val tabId: Long
|
||||
get() = this@AbsTimelineFragment.tabId
|
||||
|
||||
})
|
||||
if (started) {
|
||||
adapter.loadMoreIndicatorPosition = LoadMorePosition.END
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -817,7 +826,8 @@ abstract class AbsTimelineFragment : AbsContentRecyclerViewFragment<ParcelableSt
|
|||
val databasePagedListConfig: PagedList.Config = PagedList.Config.Builder()
|
||||
.setPageSize(50)
|
||||
.setInitialLoadSizeHint(50)
|
||||
.setEnablePlaceholders(false)
|
||||
.setPrefetchDistance(15)
|
||||
.setEnablePlaceholders(true)
|
||||
.build()
|
||||
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ class NameView(context: Context, attrs: AttributeSet? = null) : TwoLineTextView(
|
|||
val a = context.obtainStyledAttributes(attrs, R.styleable.NameView, 0, 0)
|
||||
nameFirst = a.getBoolean(R.styleable.NameView_nvNameFirst, true)
|
||||
a.recycle()
|
||||
if (isInEditMode && text.isNullOrEmpty()) {
|
||||
if (isInEditMode) {
|
||||
name = "Name"
|
||||
screenName = "@screenname"
|
||||
updateText()
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
package org.mariotaku.twidere.view
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.SystemClock
|
||||
import android.support.v7.widget.AppCompatTextView
|
||||
import android.text.format.DateUtils
|
||||
|
@ -37,6 +39,13 @@ class ShortTimeView(
|
|||
|
||||
private val ticker = TickerRunnable(this)
|
||||
|
||||
private val invalidateTimeRunnable = Runnable {
|
||||
val label = getTimeLabel(context, time, showAbsoluteTime)
|
||||
post {
|
||||
setTextIfChanged(label)
|
||||
}
|
||||
}
|
||||
|
||||
var showAbsoluteTime: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
|
@ -60,7 +69,7 @@ class ShortTimeView(
|
|||
}
|
||||
|
||||
private fun invalidateTime() {
|
||||
setTextIfChanged(getTimeLabel(context, time, showAbsoluteTime))
|
||||
updateHandler.post(invalidateTimeRunnable)
|
||||
}
|
||||
|
||||
private fun setTextIfChanged(text: CharSequence?) {
|
||||
|
@ -87,6 +96,12 @@ class ShortTimeView(
|
|||
private const val TICKER_DURATION = 5000L
|
||||
private val ONE_MINUTE_MILLIS = TimeUnit.MINUTES.toMillis(1)
|
||||
|
||||
// TODO: Use an universal executor across app
|
||||
private val updateThread = HandlerThread("ShortTimeUpdate").apply {
|
||||
start()
|
||||
}
|
||||
private val updateHandler = Handler(updateThread.looper)
|
||||
|
||||
fun getTimeLabel(context: Context, time: Long, showAbsoluteTime: Boolean): CharSequence {
|
||||
if (showAbsoluteTime) return formatSameDayTime(context, time)
|
||||
return if (Math.abs(System.currentTimeMillis() - time) > ONE_MINUTE_MILLIS) {
|
||||
|
|
|
@ -80,6 +80,7 @@ open class TwoLineTextView(context: Context, attrs: AttributeSet? = null) : Fixe
|
|||
|
||||
primaryTextSpan = primaryTextAppearance.toSpan()
|
||||
secondaryTextSpan = secondaryTextAppearance.toSpan()
|
||||
|
||||
updateText()
|
||||
}
|
||||
|
||||
|
|
|
@ -155,7 +155,6 @@ class StatusViewHolder(private val adapter: IStatusesAdapter, itemView: View) :
|
|||
val linkify = adapter.twidereLinkify
|
||||
val formatter = adapter.bidiFormatter
|
||||
val colorNameManager = adapter.userColorNameManager
|
||||
val nameFirst = adapter.nameFirst
|
||||
val showCardActions = isCardActionsShown
|
||||
|
||||
actionButtons.visibility = if (showCardActions) View.VISIBLE else View.GONE
|
||||
|
|
|
@ -231,7 +231,6 @@
|
|||
app:ignorePadding="true"
|
||||
tools:visibility="visible">
|
||||
|
||||
|
||||
<org.mariotaku.twidere.view.NameView
|
||||
android:id="@+id/quotedName"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -291,7 +290,7 @@
|
|||
android:textStyle="bold"
|
||||
app:drawableTint="?android:textColorSecondary"
|
||||
tools:text="@string/label_media"
|
||||
tools:visibility="gone"/>
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<org.mariotaku.twidere.view.CardMediaContainer
|
||||
android:id="@+id/quotedMediaPreview"
|
||||
|
@ -304,12 +303,7 @@
|
|||
android:layout_below="@+id/quotedMediaLabel"
|
||||
android:horizontalSpacing="@dimen/element_spacing_xsmall"
|
||||
android:verticalSpacing="@dimen/element_spacing_xsmall"
|
||||
tools:visibility="visible">
|
||||
|
||||
<!-- Child views will be inflated if media preview enabled in ViewHolder -->
|
||||
<include layout="@layout/layout_card_media_preview"/>
|
||||
|
||||
</org.mariotaku.twidere.view.CardMediaContainer>
|
||||
tools:visibility="gone"/>
|
||||
</org.mariotaku.twidere.view.ColorLabelRelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
Loading…
Reference in New Issue