improved debug preview

This commit is contained in:
Mariotaku Lee 2017-12-29 01:22:16 +08:00
parent 0e8fc18801
commit d5d5132354
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
9 changed files with 199 additions and 80 deletions

View File

@ -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)
}

View File

@ -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 ->

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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) {

View File

@ -80,6 +80,7 @@ open class TwoLineTextView(context: Context, attrs: AttributeSet? = null) : Fixe
primaryTextSpan = primaryTextAppearance.toSpan()
secondaryTextSpan = secondaryTextAppearance.toSpan()
updateText()
}

View File

@ -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

View File

@ -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>