diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/data/ContiguousCursorObjectDataSource.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/data/ContiguousCursorObjectDataSource.kt new file mode 100644 index 000000000..c62c70387 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/data/ContiguousCursorObjectDataSource.kt @@ -0,0 +1,107 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +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( + val resolver: ContentResolver, + val uri: Uri, + val projection: Array? = null, + val selection: String? = null, + val selectionArgs: Array? = null, + val sortOrder: String? = null, + val cls: Class, + val processor: DataSourceItemProcessor? +) : PageKeyedDataSource() { + + 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, callback: LoadInitialCallback) { + val result = loadRange(0, params.requestedLoadSize) + if (result == null) { + invalidate() + return + } + callback.onResult(result.data, null, params.requestedLoadSize) + } + + override fun loadBefore(params: LoadParams, callback: LoadCallback) { + 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, callback: LoadCallback) { + 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? { + 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(val data: List, val unfilteredCount: Int) +} + diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/data/CursorObjectDataSourceFactory.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/data/CursorObjectDataSourceFactory.kt index 6f75eb090..be24d07ec 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/data/CursorObjectDataSourceFactory.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/data/CursorObjectDataSourceFactory.kt @@ -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( val sortOrder: String? = null, val cls: Class, val processor: DataSourceItemProcessor? - ) : PageKeyedDataSource() { + ) : PositionalDataSource() { 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( processor?.init(resolver) } - override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) { + override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) { 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, callback: LoadCallback) { - val data = loadRange(params.key, params.requestedLoadSize) - if (data == null) { + override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback) { + 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, callback: LoadCallback) { - 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? { - 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) -> Unit): Boolean { + private fun loadRange(startPosition: Int, count: Int, callback: (List) -> Unit): Boolean { if (processor == null) { val start = System.currentTimeMillis() val result = resolver.queryAll(uri, projection, selection, selectionArgs, sortOrder, @@ -155,11 +131,6 @@ class CursorObjectDataSourceFactory( return true } - override fun invalidate() { - processor?.invalidate() - super.invalidate() - } - private fun BooleanArray.actualIndex(index: Int, def: Int): Int { var actual = -1 forEachIndexed { i, v -> diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/view/RecyclerViewExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/view/RecyclerViewExtensions.kt index dbe17f978..117891c62 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/view/RecyclerViewExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/view/RecyclerViewExtensions.kt @@ -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() - } \ No newline at end of file + } + +data class PositionWithOffset(val position: Int, val offset: Int) \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/timeline/AbsTimelineFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/timeline/AbsTimelineFragment.kt index c9171c93f..fced853ad 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/timeline/AbsTimelineFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/timeline/AbsTimelineFragment.kt @@ -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?) { - 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() { 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 ONE_MINUTE_MILLIS) { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/TwoLineTextView.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/TwoLineTextView.kt index dae3cbfaf..60700dbb7 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/TwoLineTextView.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/TwoLineTextView.kt @@ -80,6 +80,7 @@ open class TwoLineTextView(context: Context, attrs: AttributeSet? = null) : Fixe primaryTextSpan = primaryTextAppearance.toSpan() secondaryTextSpan = secondaryTextAppearance.toSpan() + updateText() } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/StatusViewHolder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/StatusViewHolder.kt index 9447f1196..cb6bb943e 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/StatusViewHolder.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/StatusViewHolder.kt @@ -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 diff --git a/twidere/src/main/res/layout/list_item_status.xml b/twidere/src/main/res/layout/list_item_status.xml index d5aca2f0b..d22790547 100644 --- a/twidere/src/main/res/layout/list_item_status.xml +++ b/twidere/src/main/res/layout/list_item_status.xml @@ -231,7 +231,6 @@ app:ignorePadding="true" tools:visibility="visible"> - + tools:visibility="visible"/> - - - - - + tools:visibility="gone"/>