mirror of
https://github.com/TwidereProject/Twidere-Android
synced 2025-02-08 15:58:40 +01:00
started to use paged list
This commit is contained in:
parent
04c84eb86f
commit
af9dadb142
81
build.gradle
81
build.gradle
@ -41,46 +41,47 @@ subprojects {
|
||||
PlayServices : '3.1.1',
|
||||
]
|
||||
libVersions = [
|
||||
Kotlin : "${kotlinVersion}",
|
||||
SupportLib : '26.1.0',
|
||||
SupportTest : '1.0.0',
|
||||
MariotakuCommons : '0.9.20',
|
||||
RestFu : '0.9.60',
|
||||
ObjectCursor : '0.9.21',
|
||||
PlayServices : '11.4.0',
|
||||
MapsUtils : '0.5',
|
||||
DropboxCoreSdk : '3.0.3',
|
||||
GoogleDriveApi : 'v3-rev81-1.22.0',
|
||||
Exoplayer : 'r2.2.0',
|
||||
Toro : '2.1.0',
|
||||
LoganSquare : '1.3.7',
|
||||
IABv3 : '1.0.38',
|
||||
Mime4J : '0.7.2',
|
||||
OkHttp : '3.8.1',
|
||||
Stetho : '1.5.0',
|
||||
OSMDroid : '5.6.5',
|
||||
LeakCanary : '1.5.1',
|
||||
TwitterText : '1.14.7',
|
||||
MediaViewerLibrary : '0.9.23',
|
||||
MultiValueSwitch : '0.9.8',
|
||||
PickNCrop : '0.9.27',
|
||||
AndroidGIFDrawable : '1.2.6',
|
||||
KPreferences : '0.9.7',
|
||||
Kovenant : '3.3.0',
|
||||
ParcelablePlease : '1.0.2',
|
||||
Chameleon : '0.9.22',
|
||||
UniqR : '0.9.4',
|
||||
SQLiteQB : '0.9.15',
|
||||
Glide : '3.7.0',
|
||||
GlideOkHttp3 : '1.4.0',
|
||||
GlideTransformations : '2.0.2',
|
||||
AndroidImageCropper : '2.4.6',
|
||||
ExportablePreferences : '0.9.7',
|
||||
ACRA : '4.9.2',
|
||||
AbstractTask : '0.9.5',
|
||||
Dagger : '2.11',
|
||||
StethoBeanShellREPL : '0.3',
|
||||
SupportLifecycleExtensions: '1.0.0-beta2',
|
||||
Kotlin : "${kotlinVersion}",
|
||||
SupportLib : '26.1.0',
|
||||
SupportTest : '1.0.0',
|
||||
MariotakuCommons : '0.9.20',
|
||||
RestFu : '0.9.60',
|
||||
ObjectCursor : '0.9.21',
|
||||
PlayServices : '11.4.0',
|
||||
MapsUtils : '0.5',
|
||||
DropboxCoreSdk : '3.0.3',
|
||||
GoogleDriveApi : 'v3-rev81-1.22.0',
|
||||
Exoplayer : 'r2.2.0',
|
||||
Toro : '2.1.0',
|
||||
LoganSquare : '1.3.7',
|
||||
IABv3 : '1.0.38',
|
||||
Mime4J : '0.7.2',
|
||||
OkHttp : '3.8.1',
|
||||
Stetho : '1.5.0',
|
||||
OSMDroid : '5.6.5',
|
||||
LeakCanary : '1.5.1',
|
||||
TwitterText : '1.14.7',
|
||||
MediaViewerLibrary : '0.9.23',
|
||||
MultiValueSwitch : '0.9.8',
|
||||
PickNCrop : '0.9.27',
|
||||
AndroidGIFDrawable : '1.2.6',
|
||||
KPreferences : '0.9.7',
|
||||
Kovenant : '3.3.0',
|
||||
ParcelablePlease : '1.0.2',
|
||||
Chameleon : '0.9.22',
|
||||
UniqR : '0.9.4',
|
||||
SQLiteQB : '0.9.15',
|
||||
Glide : '3.7.0',
|
||||
GlideOkHttp3 : '1.4.0',
|
||||
GlideTransformations : '2.0.2',
|
||||
AndroidImageCropper : '2.4.6',
|
||||
ExportablePreferences : '0.9.7',
|
||||
ACRA : '4.9.2',
|
||||
AbstractTask : '0.9.5',
|
||||
Dagger : '2.11',
|
||||
StethoBeanShellREPL : '0.3',
|
||||
ArchLifecycleExtensions: '1.0.0-beta2',
|
||||
ArchPaging : '1.0.0-alpha3',
|
||||
]
|
||||
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ public class ParcelableStatus implements Parcelable, Comparable<ParcelableStatus
|
||||
}
|
||||
};
|
||||
@CursorField(value = Statuses._ID, excludeWrite = true, type = TwidereDataStore.TYPE_PRIMARY_KEY)
|
||||
public long _id;
|
||||
public long _id = -1;
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@JsonField(name = "id")
|
||||
|
@ -193,7 +193,8 @@ dependencies {
|
||||
// https://g.co/androidstudio/app-test-app-conflict
|
||||
androidTestImplementation "com.google.code.findbugs:jsr305:3.0.1"
|
||||
|
||||
implementation "android.arch.lifecycle:extensions:${libVersions['SupportLifecycleExtensions']}"
|
||||
implementation "android.arch.lifecycle:extensions:${libVersions['ArchLifecycleExtensions']}"
|
||||
implementation "android.arch.paging:runtime:${libVersions['ArchPaging']}"
|
||||
implementation 'com.android.support:multidex:1.0.2'
|
||||
implementation "com.android.support:support-annotations:${libVersions['SupportLib']}"
|
||||
implementation "com.android.support:support-compat:${libVersions['SupportLib']}"
|
||||
|
@ -839,7 +839,9 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
|
||||
toolbar.setContentInsetsRelative(toolbarStyle.getDimensionPixelSize(0, 0),
|
||||
toolbarStyle.getDimensionPixelSize(1, 0))
|
||||
toolbarStyle.recycle()
|
||||
|
||||
toolbar.setOnClickListener {
|
||||
(currentVisibleFragment as? RefreshScrollTopInterface)?.scrollToStart()
|
||||
}
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
}
|
||||
|
@ -19,17 +19,20 @@
|
||||
|
||||
package org.mariotaku.twidere.adapter
|
||||
|
||||
import android.arch.paging.PagedList
|
||||
import android.arch.paging.PagedListAdapterHelper
|
||||
import android.content.Context
|
||||
import android.database.CursorIndexOutOfBoundsException
|
||||
import android.support.v4.widget.Space
|
||||
import android.support.v7.recyclerview.extensions.DiffCallback
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.util.SparseBooleanArray
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import com.bumptech.glide.RequestManager
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.ktextension.*
|
||||
import org.mariotaku.library.objectcursor.ObjectCursor
|
||||
import org.mariotaku.ktextension.contains
|
||||
import org.mariotaku.ktextension.findPositionByItemId
|
||||
import org.mariotaku.ktextension.rangeOfSize
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.adapter.iface.IGapSupportedAdapter
|
||||
import org.mariotaku.twidere.adapter.iface.IGapSupportedAdapter.Companion.ITEM_VIEW_TYPE_GAP
|
||||
@ -47,7 +50,6 @@ import org.mariotaku.twidere.model.ObjectId
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.timeline.TimelineFilter
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
|
||||
import org.mariotaku.twidere.util.StatusAdapterLinkClickHandler
|
||||
import org.mariotaku.twidere.util.TwidereLinkify
|
||||
import org.mariotaku.twidere.util.Utils
|
||||
@ -62,9 +64,8 @@ class ParcelableStatusesAdapter(
|
||||
) : LoadMoreSupportAdapter<RecyclerView.ViewHolder>(context, requestManager),
|
||||
IStatusesAdapter, IItemCountsAdapter {
|
||||
|
||||
protected val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
|
||||
override val twidereLinkify: TwidereLinkify
|
||||
|
||||
@PreviewStyle
|
||||
override val mediaPreviewStyle: Int = preferences[mediaPreviewStyleKey]
|
||||
override val nameFirst: Boolean = preferences[nameFirstKey]
|
||||
@ -74,11 +75,6 @@ class ParcelableStatusesAdapter(
|
||||
override val lightFont: Boolean = preferences[lightFontKey]
|
||||
override val mediaPreviewEnabled: Boolean = Utils.isMediaPreviewEnabled(context, preferences)
|
||||
override val sensitiveContentEnabled: Boolean = preferences.getBoolean(KEY_DISPLAY_SENSITIVE_CONTENTS, false)
|
||||
private val showCardActions: Boolean = !preferences[hideCardActionsKey]
|
||||
|
||||
private val gapLoadingIds: MutableSet<ObjectId> = HashSet()
|
||||
|
||||
override var statusClickListener: IStatusViewHolder.StatusClickListener? = null
|
||||
|
||||
override val gapClickListener: IGapSupportedAdapter.GapClickListener?
|
||||
get() = statusClickListener
|
||||
@ -90,6 +86,10 @@ class ParcelableStatusesAdapter(
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override var statusClickListener: IStatusViewHolder.StatusClickListener? = null
|
||||
|
||||
override val itemCounts = ItemCounts(5)
|
||||
|
||||
var isShowInReplyTo: Boolean = false
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
@ -111,47 +111,35 @@ class ParcelableStatusesAdapter(
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private var displayPositions: IntArray? = null
|
||||
private var displayDataCount: Int = 0
|
||||
private var showingActionCardId = RecyclerView.NO_ID
|
||||
private val showingFullTextStates = SparseBooleanArray()
|
||||
private val reuseStatus = ParcelableStatus()
|
||||
private var infoCache: Array<StatusInfo?>? = null
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
|
||||
var data: List<ParcelableStatus>? = null
|
||||
set(data) {
|
||||
when (data) {
|
||||
null -> {
|
||||
displayPositions = null
|
||||
displayDataCount = 0
|
||||
}
|
||||
is ObjectCursor -> {
|
||||
displayPositions = null
|
||||
displayDataCount = data.size
|
||||
}
|
||||
else -> {
|
||||
var filteredCount = 0
|
||||
displayPositions = IntArray(data.size).apply {
|
||||
data.forEachIndexed { i, item ->
|
||||
if (!item.is_gap && item.is_filtered) {
|
||||
filteredCount++
|
||||
} else {
|
||||
this[i - filteredCount] = i
|
||||
}
|
||||
}
|
||||
}
|
||||
displayDataCount = data.size - filteredCount
|
||||
}
|
||||
}
|
||||
field = data
|
||||
this.infoCache = if (data != null) arrayOfNulls(data.size) else null
|
||||
private val showCardActions: Boolean = !preferences[hideCardActionsKey]
|
||||
|
||||
private val gapLoadingIds: MutableSet<ObjectId> = HashSet()
|
||||
private var showingActionCardId = RecyclerView.NO_ID
|
||||
|
||||
private val showingFullTextStates = SparseBooleanArray()
|
||||
|
||||
private var pagedStatusesHelper = PagedListAdapterHelper<ParcelableStatus>(this, object : DiffCallback<ParcelableStatus>() {
|
||||
override fun areContentsTheSame(oldItem: ParcelableStatus, newItem: ParcelableStatus): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areItemsTheSame(oldItem: ParcelableStatus, newItem: ParcelableStatus): Boolean {
|
||||
return oldItem._id == newItem._id || oldItem === newItem
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
var statuses: PagedList<ParcelableStatus>?
|
||||
get() = pagedStatusesHelper.currentList
|
||||
set(value) {
|
||||
pagedStatusesHelper.setList(value)
|
||||
gapLoadingIds.clear()
|
||||
updateItemCount()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override val itemCounts = ItemCounts(5)
|
||||
|
||||
val statusStartIndex: Int
|
||||
get() = getItemStartPosition(ITEM_INDEX_STATUS)
|
||||
|
||||
@ -178,20 +166,16 @@ class ParcelableStatusesAdapter(
|
||||
}
|
||||
|
||||
override fun isGapItem(position: Int): Boolean {
|
||||
return getFieldValue(position, { info ->
|
||||
return@getFieldValue info.gap
|
||||
}, { status ->
|
||||
return@getFieldValue status.is_gap
|
||||
}, false)
|
||||
return getStatus(position).is_gap
|
||||
}
|
||||
|
||||
override fun getStatus(position: Int, raw: Boolean): ParcelableStatus {
|
||||
return getStatusInternal(position, getItemCountIndex(position, raw), raw, reuse = false)
|
||||
return getStatusInternal(position, getItemCountIndex(position, raw), raw)
|
||||
}
|
||||
|
||||
override fun getStatusCount(raw: Boolean): Int {
|
||||
if (raw) return data?.size ?: 0
|
||||
return displayDataCount
|
||||
if (raw) return statuses?.size ?: 0
|
||||
return pagedStatusesHelper.itemCount
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
@ -201,57 +185,29 @@ class ParcelableStatusesAdapter(
|
||||
val status = pinnedStatuses!![position - getItemStartPosition(ITEM_INDEX_PINNED_STATUS)]
|
||||
return status.hashCode().toLong()
|
||||
}
|
||||
ITEM_INDEX_STATUS -> getFieldValue(position, { (_, accountKey, id) ->
|
||||
return@getFieldValue ParcelableStatus.calculateHashCode(accountKey, id)
|
||||
}, { status ->
|
||||
return@getFieldValue status.hashCode()
|
||||
}, -1).toLong()
|
||||
ITEM_INDEX_STATUS -> return getStatus(position, false).hashCode().toLong()
|
||||
else -> position.toLong()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getStatusId(position: Int, raw: Boolean): String {
|
||||
return getFieldValue(position, { info ->
|
||||
return@getFieldValue info.id
|
||||
}, { status ->
|
||||
return@getFieldValue status.id
|
||||
}, "")
|
||||
return getStatus(position, raw).id
|
||||
}
|
||||
|
||||
fun getStatusSortId(position: Int, raw: Boolean): Long {
|
||||
return getFieldValue(position, { info ->
|
||||
return@getFieldValue info.sortId
|
||||
}, { status ->
|
||||
return@getFieldValue status.sort_id
|
||||
}, -1L, raw)
|
||||
return getStatus(position, raw).sort_id
|
||||
}
|
||||
|
||||
override fun getStatusTimestamp(position: Int, raw: Boolean): Long {
|
||||
return getFieldValue(position, { info ->
|
||||
return@getFieldValue info.timestamp
|
||||
}, { status ->
|
||||
return@getFieldValue status.timestamp
|
||||
}, -1L)
|
||||
return getStatus(position, raw).timestamp
|
||||
}
|
||||
|
||||
override fun getStatusPositionKey(position: Int, raw: Boolean): Long {
|
||||
return getFieldValue(position, { info ->
|
||||
if (info.positionKey > 0) return@getFieldValue info.positionKey
|
||||
return@getFieldValue info.timestamp
|
||||
}, { status ->
|
||||
val positionKey = status.position_key
|
||||
if (positionKey > 0) return@getFieldValue positionKey
|
||||
return@getFieldValue status.timestamp
|
||||
}, -1L)
|
||||
return getStatus(position, raw).position_key
|
||||
}
|
||||
|
||||
override fun getAccountKey(position: Int, raw: Boolean): UserKey {
|
||||
val def: UserKey? = null
|
||||
return getFieldValue(position, { info ->
|
||||
return@getFieldValue info.accountKey
|
||||
}, { status ->
|
||||
return@getFieldValue status.account_key
|
||||
}, def, raw)!!
|
||||
return getStatus(position, raw).account_key
|
||||
}
|
||||
|
||||
override fun isCardActionsShown(position: Int): Boolean {
|
||||
@ -313,7 +269,7 @@ class ParcelableStatusesAdapter(
|
||||
when (holder.itemViewType) {
|
||||
VIEW_TYPE_STATUS -> {
|
||||
val countIndex: Int = getItemCountIndex(position)
|
||||
val status = getStatusInternal(position, countIndex = countIndex, reuse = true)
|
||||
val status = getStatusInternal(position, countIndex = countIndex)
|
||||
(holder as IStatusViewHolder).display(status, displayInReplyTo = isShowInReplyTo,
|
||||
displayPinned = countIndex == ITEM_INDEX_PINNED_STATUS)
|
||||
}
|
||||
@ -321,7 +277,7 @@ class ParcelableStatusesAdapter(
|
||||
(holder as TimelineFilterHeaderViewHolder).display(timelineFilter!!)
|
||||
}
|
||||
ITEM_VIEW_TYPE_GAP -> {
|
||||
val status = getStatusInternal(position, reuse = true)
|
||||
val status = getStatusInternal(position)
|
||||
val loading = gapLoadingIds.any { it.accountKey == status.account_key && it.id == status.id }
|
||||
(holder as GapViewHolder).display(loading)
|
||||
}
|
||||
@ -378,11 +334,8 @@ class ParcelableStatusesAdapter(
|
||||
}
|
||||
|
||||
fun getRowId(adapterPosition: Int, raw: Boolean = false): Long {
|
||||
return getFieldValue(adapterPosition, readInfoValueAction = {
|
||||
it._id
|
||||
}, readStatusValueAction = { status ->
|
||||
status.hashCode().toLong()
|
||||
}, defValue = -1L, raw = raw)
|
||||
val status = getStatus(adapterPosition, raw)
|
||||
return if (status._id < 0) status.hashCode().toLong() else status._id
|
||||
}
|
||||
|
||||
fun findPositionByPositionKey(positionKey: Long, raw: Boolean = false): Int {
|
||||
@ -414,7 +367,7 @@ class ParcelableStatusesAdapter(
|
||||
var sum = 0
|
||||
for (i in 0 until itemCounts.size) {
|
||||
sum += when (i) {
|
||||
ITEM_INDEX_STATUS -> data?.size ?: 0
|
||||
ITEM_INDEX_STATUS -> statuses?.size ?: 0
|
||||
else -> itemCounts[i]
|
||||
}
|
||||
if (position < sum) {
|
||||
@ -424,57 +377,15 @@ class ParcelableStatusesAdapter(
|
||||
return -1
|
||||
}
|
||||
|
||||
private inline fun <T> getFieldValue(position: Int,
|
||||
readInfoValueAction: (StatusInfo) -> T,
|
||||
readStatusValueAction: (status: ParcelableStatus) -> T,
|
||||
defValue: T, raw: Boolean = false): T {
|
||||
val data = this.data
|
||||
if (data is ObjectCursor) {
|
||||
val dataPosition = position - statusStartIndex
|
||||
if (dataPosition < 0 || dataPosition >= getStatusCount(true)) {
|
||||
throw CursorIndexOutOfBoundsException("index: $position, valid range is $0..${getStatusCount(true)}")
|
||||
}
|
||||
val info = infoCache?.get(dataPosition) ?: run {
|
||||
val cursor = data.cursor
|
||||
if (!cursor.safeMoveToPosition(dataPosition)) return defValue
|
||||
val indices = data.indices
|
||||
val _id = cursor.safeGetLong(indices[Statuses._ID])
|
||||
val accountKey = UserKey.valueOf(cursor.safeGetString(indices[Statuses.ACCOUNT_KEY]))
|
||||
val id = cursor.safeGetString(indices[Statuses.ID])
|
||||
val timestamp = cursor.safeGetLong(indices[Statuses.TIMESTAMP])
|
||||
val sortId = cursor.safeGetLong(indices[Statuses.SORT_ID])
|
||||
val positionKey = cursor.safeGetLong(indices[Statuses.POSITION_KEY])
|
||||
val gap = cursor.getInt(indices[Statuses.IS_GAP]) == 1
|
||||
val newInfo = StatusInfo(_id, accountKey, id, timestamp, sortId, positionKey, gap)
|
||||
infoCache?.set(dataPosition, newInfo)
|
||||
return@run newInfo
|
||||
}
|
||||
return readInfoValueAction(info)
|
||||
}
|
||||
return readStatusValueAction(getStatus(position, raw))
|
||||
}
|
||||
|
||||
private fun getStatusInternal(position: Int, countIndex: Int = getItemCountIndex(position),
|
||||
raw: Boolean = false, reuse: Boolean): ParcelableStatus {
|
||||
raw: Boolean = false): ParcelableStatus {
|
||||
when (countIndex) {
|
||||
ITEM_INDEX_PINNED_STATUS -> {
|
||||
return pinnedStatuses!![position - getItemStartPosition(ITEM_INDEX_PINNED_STATUS)]
|
||||
}
|
||||
ITEM_INDEX_STATUS -> {
|
||||
val data = this.data!!
|
||||
val dataPosition = position - statusStartIndex
|
||||
val positions = displayPositions
|
||||
val listPosition = if (positions != null && !raw) {
|
||||
positions[dataPosition]
|
||||
} else {
|
||||
dataPosition
|
||||
}
|
||||
if (reuse && data is ObjectCursor) {
|
||||
reuseStatus.is_filtered = false
|
||||
return data.setInto(listPosition, reuseStatus)
|
||||
} else {
|
||||
return data[listPosition]
|
||||
}
|
||||
return pagedStatusesHelper.getItem(dataPosition)!!
|
||||
}
|
||||
}
|
||||
val validStart = getItemStartPosition(ITEM_INDEX_PINNED_STATUS)
|
||||
|
@ -1,79 +0,0 @@
|
||||
/*
|
||||
* 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.content.ContentResolver
|
||||
import android.database.ContentObserver
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import nl.komponents.kovenant.task
|
||||
import nl.komponents.kovenant.ui.successUi
|
||||
import org.mariotaku.ktextension.weak
|
||||
import org.mariotaku.library.objectcursor.ObjectCursor
|
||||
import org.mariotaku.twidere.extension.queryReference
|
||||
|
||||
class ObjectCursorLiveData<T>(
|
||||
val resolver: ContentResolver,
|
||||
val uri: Uri,
|
||||
val projection: Array<String>? = null,
|
||||
val selection: String? = null,
|
||||
val selectionArgs: Array<String>? = null,
|
||||
val orderBy: String? = null,
|
||||
val cls: Class<out T>
|
||||
) : ReloadableLiveData<List<T>?>() {
|
||||
|
||||
private val reloadObserver = object : ContentObserver(MainThreadHandler) {
|
||||
override fun onChange(selfChange: Boolean) {
|
||||
if (hasActiveObservers()) {
|
||||
loadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLoadData(callback: (List<T>?) -> Unit) {
|
||||
val weakThis = weak()
|
||||
task {
|
||||
val (c) = resolver.queryReference(uri, projection, selection, selectionArgs) ?:
|
||||
throw NullPointerException()
|
||||
c.registerContentObserver(reloadObserver)
|
||||
val i = ObjectCursor.indicesFrom(c, cls)
|
||||
return@task ObjectCursor(c, i)
|
||||
}.successUi { data ->
|
||||
val ld = weakThis.get()
|
||||
val oldValue = ld?.value
|
||||
if (oldValue is ObjectCursor<*>) {
|
||||
oldValue.close()
|
||||
}
|
||||
callback(data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInactive() {
|
||||
val oldValue = this.value
|
||||
if (oldValue is ObjectCursor<*>) {
|
||||
oldValue.cursor.unregisterContentObserver(reloadObserver)
|
||||
oldValue.close()
|
||||
}
|
||||
value = null
|
||||
}
|
||||
|
||||
object MainThreadHandler : Handler(Looper.getMainLooper())
|
||||
}
|
@ -17,21 +17,27 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.data
|
||||
package org.mariotaku.twidere.data.source
|
||||
|
||||
import android.arch.lifecycle.LiveData
|
||||
import android.arch.paging.LivePagedListProvider
|
||||
import android.content.ContentResolver
|
||||
import android.net.Uri
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/10/13.
|
||||
*/
|
||||
|
||||
abstract class ReloadableLiveData<T> : LiveData<T>() {
|
||||
class CursorObjectLivePagedListProvider<T>(
|
||||
private 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>
|
||||
) : LivePagedListProvider<Int, T>() {
|
||||
|
||||
fun loadData() {
|
||||
onLoadData(this::setValue)
|
||||
}
|
||||
|
||||
override fun onActive() {
|
||||
loadData()
|
||||
}
|
||||
|
||||
protected abstract fun onLoadData(callback: (T) -> Unit)
|
||||
override fun createDataSource() = CursorObjectTiledDataSource(resolver, uri, projection,
|
||||
selection, selectionArgs, sortOrder, cls)
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.source
|
||||
|
||||
import android.arch.paging.TiledDataSource
|
||||
import android.content.ContentResolver
|
||||
import android.database.ContentObserver
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import org.mariotaku.twidere.extension.queryAll
|
||||
import org.mariotaku.twidere.extension.queryCount
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/10/13.
|
||||
*/
|
||||
|
||||
class CursorObjectTiledDataSource<T>(
|
||||
private 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>
|
||||
) : TiledDataSource<T>() {
|
||||
|
||||
init {
|
||||
val observer = object : ContentObserver(MainHandler) {
|
||||
override fun onChange(selfChange: Boolean) {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
resolver.registerContentObserver(uri, false, observer)
|
||||
removeInvalidatedCallback {
|
||||
resolver.unregisterContentObserver(observer)
|
||||
}
|
||||
}
|
||||
|
||||
override fun countItems() = resolver.queryCount(uri, selection, selectionArgs)
|
||||
|
||||
override fun loadRange(startPosition: Int, count: Int): List<T> {
|
||||
return resolver.queryAll(uri, projection, selection, selectionArgs, sortOrder,
|
||||
"$startPosition,$count", cls)
|
||||
}
|
||||
|
||||
private object MainHandler : Handler(Looper.getMainLooper())
|
||||
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
/*
|
||||
* 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.status
|
||||
|
||||
import org.mariotaku.twidere.data.ReloadableLiveData
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
|
||||
|
||||
class StatusesLiveData : ReloadableLiveData<List<ParcelableStatus>>() {
|
||||
override fun onLoadData(callback: (List<ParcelableStatus>) -> Unit) {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -71,8 +71,9 @@ fun <T> ContentResolver.queryOne(uri: Uri, projection: Array<String>?, selection
|
||||
}
|
||||
|
||||
fun <T> ContentResolver.queryAll(uri: Uri, projection: Array<String>?, selection: String?,
|
||||
selectionArgs: Array<String>?, sortOrder: String? = null, cls: Class<T>): List<T> {
|
||||
return queryReference(uri, projection, selection, selectionArgs, sortOrder)?.use { (cur) ->
|
||||
selectionArgs: Array<String>?, sortOrder: String? = null, limit: String? = null,
|
||||
cls: Class<T>): List<T> {
|
||||
return queryReference(uri, projection, selection, selectionArgs, sortOrder, limit)?.use { (cur) ->
|
||||
return@use cur.map(ObjectCursor.indicesFrom(cur, cls))
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
@ -25,12 +25,12 @@ fun FiltersData.read(cr: ContentResolver, loadSubscription: Boolean = false) {
|
||||
fun readBaseItems(uri: Uri): List<FiltersData.BaseItem>? {
|
||||
val where = if (loadSubscription) null else Expression.lesserThan(Filters.SOURCE, 0).sql
|
||||
return cr.queryAll(uri, Filters.COLUMNS, where, null, null,
|
||||
FiltersData.BaseItem::class.java)
|
||||
cls = FiltersData.BaseItem::class.java)
|
||||
}
|
||||
this.users = run {
|
||||
val where = if (loadSubscription) null else Expression.lesserThan(Filters.Users.SOURCE, 0).sql
|
||||
return@run cr.queryAll(Filters.Users.CONTENT_URI, Filters.Users.COLUMNS, where, null,
|
||||
null, FiltersData.UserItem::class.java)
|
||||
null, cls = FiltersData.UserItem::class.java)
|
||||
}
|
||||
this.keywords = readBaseItems(Filters.Keywords.CONTENT_URI)
|
||||
this.sources = readBaseItems(Filters.Sources.CONTENT_URI)
|
||||
|
@ -43,7 +43,6 @@ import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.activity.AccountSelectorActivity
|
||||
import org.mariotaku.twidere.activity.ComposeActivity
|
||||
import org.mariotaku.twidere.adapter.ParcelableStatusesAdapter
|
||||
import org.mariotaku.twidere.adapter.decorator.ExtendedDividerItemDecoration
|
||||
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
|
||||
import org.mariotaku.twidere.annotation.ReadPositionTag
|
||||
import org.mariotaku.twidere.constant.*
|
||||
@ -52,6 +51,7 @@ import org.mariotaku.twidere.constant.KeyboardShortcutConstants.*
|
||||
import org.mariotaku.twidere.extension.model.getAccountType
|
||||
import org.mariotaku.twidere.fragment.status.FavoriteConfirmDialogFragment
|
||||
import org.mariotaku.twidere.fragment.status.RetweetQuoteDialogFragment
|
||||
import org.mariotaku.twidere.fragment.timeline.AbsTimelineFragment
|
||||
import org.mariotaku.twidere.graphic.like.LikeAnimationDrawable
|
||||
import org.mariotaku.twidere.loader.iface.IExtendedLoader
|
||||
import org.mariotaku.twidere.model.ObjectId
|
||||
@ -100,9 +100,9 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
|
||||
protected abstract val accountKeys: Array<UserKey>
|
||||
|
||||
protected var adapterData: List<ParcelableStatus>?
|
||||
get() = adapter.data
|
||||
get() = adapter.statuses
|
||||
set(data) {
|
||||
adapter.data = data
|
||||
// adapter.statuses = data
|
||||
}
|
||||
|
||||
@ReadPositionTag
|
||||
@ -400,27 +400,9 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
|
||||
return handleActionLongClick(this, status, adapter.getItemId(position), id)
|
||||
}
|
||||
|
||||
override fun onCreateItemDecoration(context: Context, recyclerView: RecyclerView, layoutManager: LinearLayoutManager): RecyclerView.ItemDecoration? {
|
||||
val itemDecoration = ExtendedDividerItemDecoration(context, (recyclerView.layoutManager as LinearLayoutManager).orientation)
|
||||
val res = context.resources
|
||||
if (adapter.profileImageEnabled) {
|
||||
val decorPaddingLeft = res.getDimensionPixelSize(R.dimen.element_spacing_normal) * 2 + res.getDimensionPixelSize(R.dimen.icon_size_status_profile_image)
|
||||
itemDecoration.setPadding { position, rect ->
|
||||
val itemViewType = adapter.getItemViewType(position)
|
||||
var nextItemIsStatus = false
|
||||
if (position < adapter.itemCount - 1) {
|
||||
nextItemIsStatus = adapter.getItemViewType(position + 1) == ParcelableStatusesAdapter.VIEW_TYPE_STATUS
|
||||
}
|
||||
if (nextItemIsStatus && itemViewType == ParcelableStatusesAdapter.VIEW_TYPE_STATUS) {
|
||||
rect.left = decorPaddingLeft
|
||||
} else {
|
||||
rect.left = 0
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
itemDecoration.setDecorationEndOffset(1)
|
||||
return itemDecoration
|
||||
override fun onCreateItemDecoration(context: Context, recyclerView: RecyclerView,
|
||||
layoutManager: LinearLayoutManager): RecyclerView.ItemDecoration? {
|
||||
return AbsTimelineFragment.createStatusesListItemDecoration(context, recyclerView, adapter)
|
||||
}
|
||||
|
||||
protected fun saveReadPosition() {
|
||||
|
@ -149,7 +149,7 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() {
|
||||
}
|
||||
|
||||
override fun onLoaderReset(loader: Loader<List<ParcelableStatus>?>) {
|
||||
adapter.data = null
|
||||
adapter.statuses = null
|
||||
}
|
||||
|
||||
override fun onLoadMoreContents(@IndicatorPosition position: Long) {
|
||||
|
@ -41,7 +41,7 @@ class MediaStatusesSearchFragment : ParcelableStatusesFragment() {
|
||||
val tabPosition = args.getInt(EXTRA_TAB_POSITION, -1)
|
||||
val makeGap = args.getBoolean(EXTRA_MAKE_GAP, true)
|
||||
val loadingMore = args.getBoolean(EXTRA_LOADING_MORE, false)
|
||||
return MediaStatusesSearchLoader(activity, accountKey, query, adapter.data, fromUser, makeGap,
|
||||
return MediaStatusesSearchLoader(activity, accountKey, query, adapter.statuses, fromUser, makeGap,
|
||||
loadingMore)
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ class UserMediaTimelineFragment : ParcelableStatusesFragment() {
|
||||
val userKey = args.getParcelable<UserKey?>(EXTRA_USER_KEY)
|
||||
val screenName = args.getString(EXTRA_SCREEN_NAME)
|
||||
val loadingMore = args.getBoolean(EXTRA_LOADING_MORE, false)
|
||||
return MediaTimelineLoader(context, accountKey, userKey, screenName, adapter.data,
|
||||
return MediaTimelineLoader(context, accountKey, userKey, screenName, adapter.statuses,
|
||||
fromUser, loadingMore)
|
||||
}
|
||||
}
|
||||
|
@ -21,29 +21,39 @@ package org.mariotaku.twidere.fragment.timeline
|
||||
|
||||
import android.arch.lifecycle.LiveData
|
||||
import android.arch.lifecycle.Observer
|
||||
import android.arch.paging.PagedList
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.support.v7.widget.FixedLinearLayoutManager
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.support.v7.widget.RecyclerView.LayoutManager
|
||||
import android.support.v7.widget.StaggeredGridLayoutManager
|
||||
import android.widget.Toast
|
||||
import com.bumptech.glide.RequestManager
|
||||
import com.squareup.otto.Subscribe
|
||||
import kotlinx.android.synthetic.main.fragment_content_listview.*
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.adapter.ParcelableStatusesAdapter
|
||||
import org.mariotaku.twidere.adapter.decorator.ExtendedDividerItemDecoration
|
||||
import org.mariotaku.twidere.adapter.iface.IContentAdapter
|
||||
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
|
||||
import org.mariotaku.twidere.annotation.FilterScope
|
||||
import org.mariotaku.twidere.annotation.TimelineStyle
|
||||
import org.mariotaku.twidere.data.ObjectCursorLiveData
|
||||
import org.mariotaku.twidere.data.source.CursorObjectLivePagedListProvider
|
||||
import org.mariotaku.twidere.fragment.AbsContentRecyclerViewFragment
|
||||
import org.mariotaku.twidere.fragment.CursorStatusesFragment
|
||||
import org.mariotaku.twidere.model.ExceptionResponseList
|
||||
import org.mariotaku.twidere.model.ObjectId
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.event.GetStatusesTaskEvent
|
||||
import org.mariotaku.twidere.model.pagination.Pagination
|
||||
import org.mariotaku.twidere.model.pagination.SinceMaxPagination
|
||||
import org.mariotaku.twidere.model.refresh.BaseRefreshTaskParam
|
||||
import org.mariotaku.twidere.model.refresh.RefreshTaskParam
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
|
||||
import org.mariotaku.twidere.task.statuses.GetStatusesTask
|
||||
import org.mariotaku.twidere.util.DataStoreUtils
|
||||
import org.mariotaku.twidere.util.Utils
|
||||
|
||||
@ -60,7 +70,7 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
|
||||
protected open val timelineStyle: Int
|
||||
get() = TimelineStyle.PLAIN
|
||||
|
||||
protected open val isLive: Boolean
|
||||
protected open val isStandalone: Boolean
|
||||
get() = tabId <= 0
|
||||
|
||||
@FilterScope
|
||||
@ -71,16 +81,18 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
|
||||
*/
|
||||
protected abstract val contentUri: Uri
|
||||
|
||||
protected lateinit var statuses: LiveData<List<ParcelableStatus>?>
|
||||
protected lateinit var statuses: LiveData<PagedList<ParcelableStatus>?>
|
||||
private set
|
||||
|
||||
protected val accountKeys: Array<UserKey>
|
||||
get() = Utils.getAccountKeys(context, arguments) ?: if (isLive) {
|
||||
get() = Utils.getAccountKeys(context, arguments) ?: if (isStandalone) {
|
||||
emptyArray()
|
||||
} else {
|
||||
DataStoreUtils.getActivatedAccountKeys(context)
|
||||
}
|
||||
|
||||
private val busEventHandler = BusEventHandler()
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
statuses = createLiveData()
|
||||
@ -89,6 +101,16 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
|
||||
showProgress()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
bus.register(busEventHandler)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
bus.unregister(busEventHandler)
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onCreateLayoutManager(context: Context): LayoutManager = when (timelineStyle) {
|
||||
TimelineStyle.STAGGERED -> StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
|
||||
else -> FixedLinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||
@ -98,15 +120,29 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
|
||||
return ParcelableStatusesAdapter(context, requestManager, timelineStyle)
|
||||
}
|
||||
|
||||
override fun onCreateItemDecoration(context: Context, recyclerView: RecyclerView,
|
||||
layoutManager: LayoutManager): RecyclerView.ItemDecoration? {
|
||||
return when (timelineStyle) {
|
||||
TimelineStyle.PLAIN -> {
|
||||
createStatusesListItemDecoration(context, recyclerView, adapter)
|
||||
}
|
||||
else -> {
|
||||
super.onCreateItemDecoration(context, recyclerView, layoutManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun triggerRefresh(): Boolean {
|
||||
val param = onCreateRefreshParam(REFRESH_POSITION_START)
|
||||
val param = onCreateRefreshParam(REFRESH_POSITION_START) ?: return false
|
||||
return getStatuses(param)
|
||||
}
|
||||
|
||||
override fun onLoadMoreContents(position: Long) {
|
||||
if (position != ILoadMoreSupportAdapter.END) return
|
||||
val param = onCreateRefreshParam(REFRESH_POSITION_END)
|
||||
getStatuses(param)
|
||||
val param = onCreateRefreshParam(REFRESH_POSITION_END) ?: return
|
||||
if (getStatuses(param)) {
|
||||
adapter.loadMoreIndicatorPosition = position
|
||||
}
|
||||
}
|
||||
|
||||
override fun scrollToPositionWithOffset(position: Int, offset: Int) {
|
||||
@ -121,12 +157,12 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onDataLoaded(data: List<ParcelableStatus>?) {
|
||||
adapter.data = data
|
||||
protected open fun onDataLoaded(data: PagedList<ParcelableStatus>?) {
|
||||
adapter.statuses = data
|
||||
when {
|
||||
data is ExceptionResponseList -> {
|
||||
showEmpty(R.drawable.ic_info_error_generic, data.exception.toString())
|
||||
}
|
||||
// data is ExceptionResponseList -> {
|
||||
// showEmpty(R.drawable.ic_info_error_generic, data.exception.toString())
|
||||
// }
|
||||
data == null || data.isEmpty() -> {
|
||||
showEmpty(R.drawable.ic_info_refresh, getString(R.string.swipe_down_to_refresh))
|
||||
}
|
||||
@ -138,32 +174,72 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
|
||||
|
||||
protected abstract fun getStatuses(param: RefreshParam): Boolean
|
||||
|
||||
protected abstract fun onCreateRefreshParam(position: Int): RefreshParam
|
||||
protected abstract fun onCreateRefreshParam(position: Int): RefreshParam?
|
||||
|
||||
protected open fun onCreateRealTimeLiveData(): LiveData<List<ParcelableStatus>?> {
|
||||
protected open fun onCreateStandaloneLiveData(): LiveData<PagedList<ParcelableStatus>?> {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
private fun createLiveData(): LiveData<List<ParcelableStatus>?> {
|
||||
if (isLive) return onCreateRealTimeLiveData()
|
||||
return ObjectCursorLiveData(context.contentResolver, contentUri,
|
||||
CursorStatusesFragment.statusColumnsLite, cls = ParcelableStatus::class.java)
|
||||
private fun createLiveData(): LiveData<PagedList<ParcelableStatus>?> {
|
||||
if (isStandalone) return onCreateStandaloneLiveData()
|
||||
val provider = CursorObjectLivePagedListProvider(context.contentResolver, contentUri,
|
||||
CursorStatusesFragment.statusColumnsLite, sortOrder = Statuses.DEFAULT_SORT_ORDER,
|
||||
cls = ParcelableStatus::class.java)
|
||||
return provider.create(null, 20)
|
||||
}
|
||||
|
||||
private inner class BusEventHandler {
|
||||
|
||||
@Subscribe
|
||||
fun notifyGetStatusesTaskChanged(event: GetStatusesTaskEvent) {
|
||||
if (event.uri != contentUri) return
|
||||
refreshing = event.running
|
||||
if (!event.running) {
|
||||
setLoadMoreIndicatorPosition(ILoadMoreSupportAdapter.NONE)
|
||||
refreshEnabled = true
|
||||
// TODO: showContentOrError()
|
||||
|
||||
val exception = event.exception
|
||||
if (exception is GetStatusesTask.GetTimelineException && userVisibleHint) {
|
||||
Toast.makeText(context, exception.getToastMessage(context), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val REFRESH_POSITION_START = -1
|
||||
const val REFRESH_POSITION_END = -2
|
||||
|
||||
fun getBaseRefreshTaskParam(fragment: AbsTimelineFragment<*>, position: Int): BaseRefreshTaskParam {
|
||||
fun getBaseRefreshTaskParam(fragment: AbsTimelineFragment<*>, position: Int): BaseRefreshTaskParam? {
|
||||
when (position) {
|
||||
REFRESH_POSITION_START -> {
|
||||
return getRefreshBaseRefreshTaskParam(fragment)
|
||||
}
|
||||
REFRESH_POSITION_END -> {
|
||||
|
||||
val adapter = fragment.adapter
|
||||
// Get last raw status
|
||||
val startIdx = adapter.statusStartIndex
|
||||
if (startIdx < 0) return null
|
||||
val statusCount = adapter.getStatusCount(true)
|
||||
if (statusCount <= 0) return null
|
||||
val status = adapter.getStatus(startIdx + statusCount - 1, true)
|
||||
val accountKeys = arrayOf(status.account_key)
|
||||
val pagination = arrayOf<Pagination?>(SinceMaxPagination.maxId(status.id, -1))
|
||||
val param = BaseRefreshTaskParam(accountKeys, pagination)
|
||||
param.isLoadingMore = true
|
||||
return param
|
||||
}
|
||||
else -> {
|
||||
val adapter = fragment.adapter
|
||||
val status = adapter.getStatus(position)
|
||||
adapter.addGapLoadingId(ObjectId(status.account_key, status.id))
|
||||
val accountKeys = arrayOf(status.account_key)
|
||||
val pagination = arrayOf(SinceMaxPagination.maxId(status.id, status.sort_id))
|
||||
return BaseRefreshTaskParam(accountKeys, pagination)
|
||||
}
|
||||
}
|
||||
TODO()
|
||||
}
|
||||
|
||||
private fun getRefreshBaseRefreshTaskParam(fragment: AbsTimelineFragment<*>): BaseRefreshTaskParam {
|
||||
@ -184,5 +260,31 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
|
||||
return BaseRefreshTaskParam(accountKeys, null)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun createStatusesListItemDecoration(context: Context, recyclerView: RecyclerView,
|
||||
adapter: IContentAdapter): RecyclerView.ItemDecoration {
|
||||
adapter as RecyclerView.Adapter<*>
|
||||
val itemDecoration = ExtendedDividerItemDecoration(context, (recyclerView.layoutManager as LinearLayoutManager).orientation)
|
||||
val res = context.resources
|
||||
if (adapter.profileImageEnabled) {
|
||||
val decorPaddingLeft = res.getDimensionPixelSize(R.dimen.element_spacing_normal) * 2 + res.getDimensionPixelSize(R.dimen.icon_size_status_profile_image)
|
||||
itemDecoration.setPadding { position, rect ->
|
||||
val itemViewType = adapter.getItemViewType(position)
|
||||
var nextItemIsStatus = false
|
||||
if (position < adapter.itemCount - 1) {
|
||||
nextItemIsStatus = adapter.getItemViewType(position + 1) == ParcelableStatusesAdapter.VIEW_TYPE_STATUS
|
||||
}
|
||||
if (nextItemIsStatus && itemViewType == ParcelableStatusesAdapter.VIEW_TYPE_STATUS) {
|
||||
rect.left = decorPaddingLeft
|
||||
} else {
|
||||
rect.left = 0
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
itemDecoration.setDecorationEndOffset(1)
|
||||
return itemDecoration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ class HomeTimelineFragment : AbsTimelineFragment<RefreshTaskParam>() {
|
||||
|
||||
override val contentUri: Uri = Statuses.CONTENT_URI
|
||||
|
||||
override fun onCreateRefreshParam(position: Int): RefreshTaskParam {
|
||||
override fun onCreateRefreshParam(position: Int): RefreshTaskParam? {
|
||||
return getBaseRefreshTaskParam(this, position)
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ class CacheTimelineResultTask(
|
||||
val localRelationships = cr.queryAll(CachedRelationships.CONTENT_URI, CachedRelationships.COLUMNS,
|
||||
Expression.and(Expression.equalsArgs(CachedRelationships.ACCOUNT_KEY),
|
||||
Expression.inArgs(CachedRelationships.USER_KEY, users.size)).sql,
|
||||
selectionArgsList.toTypedArray(), null, ParcelableRelationship::class.java)
|
||||
selectionArgsList.toTypedArray(), cls = ParcelableRelationship::class.java)
|
||||
val relationships = users.mapTo(ArraySet<ParcelableRelationship>()) { user ->
|
||||
val userKey = user.key
|
||||
return@mapTo localRelationships.find {
|
||||
|
Loading…
x
Reference in New Issue
Block a user