1
0
mirror of https://github.com/TwidereProject/Twidere-Android synced 2025-02-13 02:00:39 +01:00

started to use paged list

This commit is contained in:
Mariotaku Lee 2017-10-13 19:33:14 +08:00
parent 04c84eb86f
commit af9dadb142
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
18 changed files with 322 additions and 360 deletions

View File

@ -41,46 +41,47 @@ subprojects {
PlayServices : '3.1.1', PlayServices : '3.1.1',
] ]
libVersions = [ libVersions = [
Kotlin : "${kotlinVersion}", Kotlin : "${kotlinVersion}",
SupportLib : '26.1.0', SupportLib : '26.1.0',
SupportTest : '1.0.0', SupportTest : '1.0.0',
MariotakuCommons : '0.9.20', MariotakuCommons : '0.9.20',
RestFu : '0.9.60', RestFu : '0.9.60',
ObjectCursor : '0.9.21', ObjectCursor : '0.9.21',
PlayServices : '11.4.0', PlayServices : '11.4.0',
MapsUtils : '0.5', MapsUtils : '0.5',
DropboxCoreSdk : '3.0.3', DropboxCoreSdk : '3.0.3',
GoogleDriveApi : 'v3-rev81-1.22.0', GoogleDriveApi : 'v3-rev81-1.22.0',
Exoplayer : 'r2.2.0', Exoplayer : 'r2.2.0',
Toro : '2.1.0', Toro : '2.1.0',
LoganSquare : '1.3.7', LoganSquare : '1.3.7',
IABv3 : '1.0.38', IABv3 : '1.0.38',
Mime4J : '0.7.2', Mime4J : '0.7.2',
OkHttp : '3.8.1', OkHttp : '3.8.1',
Stetho : '1.5.0', Stetho : '1.5.0',
OSMDroid : '5.6.5', OSMDroid : '5.6.5',
LeakCanary : '1.5.1', LeakCanary : '1.5.1',
TwitterText : '1.14.7', TwitterText : '1.14.7',
MediaViewerLibrary : '0.9.23', MediaViewerLibrary : '0.9.23',
MultiValueSwitch : '0.9.8', MultiValueSwitch : '0.9.8',
PickNCrop : '0.9.27', PickNCrop : '0.9.27',
AndroidGIFDrawable : '1.2.6', AndroidGIFDrawable : '1.2.6',
KPreferences : '0.9.7', KPreferences : '0.9.7',
Kovenant : '3.3.0', Kovenant : '3.3.0',
ParcelablePlease : '1.0.2', ParcelablePlease : '1.0.2',
Chameleon : '0.9.22', Chameleon : '0.9.22',
UniqR : '0.9.4', UniqR : '0.9.4',
SQLiteQB : '0.9.15', SQLiteQB : '0.9.15',
Glide : '3.7.0', Glide : '3.7.0',
GlideOkHttp3 : '1.4.0', GlideOkHttp3 : '1.4.0',
GlideTransformations : '2.0.2', GlideTransformations : '2.0.2',
AndroidImageCropper : '2.4.6', AndroidImageCropper : '2.4.6',
ExportablePreferences : '0.9.7', ExportablePreferences : '0.9.7',
ACRA : '4.9.2', ACRA : '4.9.2',
AbstractTask : '0.9.5', AbstractTask : '0.9.5',
Dagger : '2.11', Dagger : '2.11',
StethoBeanShellREPL : '0.3', StethoBeanShellREPL : '0.3',
SupportLifecycleExtensions: '1.0.0-beta2', ArchLifecycleExtensions: '1.0.0-beta2',
ArchPaging : '1.0.0-alpha3',
] ]
} }

View File

@ -72,7 +72,7 @@ public class ParcelableStatus implements Parcelable, Comparable<ParcelableStatus
} }
}; };
@CursorField(value = Statuses._ID, excludeWrite = true, type = TwidereDataStore.TYPE_PRIMARY_KEY) @CursorField(value = Statuses._ID, excludeWrite = true, type = TwidereDataStore.TYPE_PRIMARY_KEY)
public long _id; public long _id = -1;
@SuppressWarnings("NullableProblems") @SuppressWarnings("NullableProblems")
@JsonField(name = "id") @JsonField(name = "id")

View File

@ -193,7 +193,8 @@ dependencies {
// https://g.co/androidstudio/app-test-app-conflict // https://g.co/androidstudio/app-test-app-conflict
androidTestImplementation "com.google.code.findbugs:jsr305:3.0.1" 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:multidex:1.0.2'
implementation "com.android.support:support-annotations:${libVersions['SupportLib']}" implementation "com.android.support:support-annotations:${libVersions['SupportLib']}"
implementation "com.android.support:support-compat:${libVersions['SupportLib']}" implementation "com.android.support:support-compat:${libVersions['SupportLib']}"

View File

@ -839,7 +839,9 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
toolbar.setContentInsetsRelative(toolbarStyle.getDimensionPixelSize(0, 0), toolbar.setContentInsetsRelative(toolbarStyle.getDimensionPixelSize(0, 0),
toolbarStyle.getDimensionPixelSize(1, 0)) toolbarStyle.getDimensionPixelSize(1, 0))
toolbarStyle.recycle() toolbarStyle.recycle()
toolbar.setOnClickListener {
(currentVisibleFragment as? RefreshScrollTopInterface)?.scrollToStart()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
} }
} }

View File

@ -19,17 +19,20 @@
package org.mariotaku.twidere.adapter package org.mariotaku.twidere.adapter
import android.arch.paging.PagedList
import android.arch.paging.PagedListAdapterHelper
import android.content.Context import android.content.Context
import android.database.CursorIndexOutOfBoundsException
import android.support.v4.widget.Space import android.support.v4.widget.Space
import android.support.v7.recyclerview.extensions.DiffCallback
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.util.SparseBooleanArray import android.util.SparseBooleanArray
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import com.bumptech.glide.RequestManager import com.bumptech.glide.RequestManager
import org.mariotaku.kpreferences.get import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.* import org.mariotaku.ktextension.contains
import org.mariotaku.library.objectcursor.ObjectCursor import org.mariotaku.ktextension.findPositionByItemId
import org.mariotaku.ktextension.rangeOfSize
import org.mariotaku.twidere.R import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.IGapSupportedAdapter import org.mariotaku.twidere.adapter.iface.IGapSupportedAdapter
import org.mariotaku.twidere.adapter.iface.IGapSupportedAdapter.Companion.ITEM_VIEW_TYPE_GAP 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.ParcelableStatus
import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.timeline.TimelineFilter 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.StatusAdapterLinkClickHandler
import org.mariotaku.twidere.util.TwidereLinkify import org.mariotaku.twidere.util.TwidereLinkify
import org.mariotaku.twidere.util.Utils import org.mariotaku.twidere.util.Utils
@ -62,9 +64,8 @@ class ParcelableStatusesAdapter(
) : LoadMoreSupportAdapter<RecyclerView.ViewHolder>(context, requestManager), ) : LoadMoreSupportAdapter<RecyclerView.ViewHolder>(context, requestManager),
IStatusesAdapter, IItemCountsAdapter { IStatusesAdapter, IItemCountsAdapter {
protected val inflater: LayoutInflater = LayoutInflater.from(context)
override val twidereLinkify: TwidereLinkify override val twidereLinkify: TwidereLinkify
@PreviewStyle @PreviewStyle
override val mediaPreviewStyle: Int = preferences[mediaPreviewStyleKey] override val mediaPreviewStyle: Int = preferences[mediaPreviewStyleKey]
override val nameFirst: Boolean = preferences[nameFirstKey] override val nameFirst: Boolean = preferences[nameFirstKey]
@ -74,11 +75,6 @@ class ParcelableStatusesAdapter(
override val lightFont: Boolean = preferences[lightFontKey] override val lightFont: Boolean = preferences[lightFontKey]
override val mediaPreviewEnabled: Boolean = Utils.isMediaPreviewEnabled(context, preferences) override val mediaPreviewEnabled: Boolean = Utils.isMediaPreviewEnabled(context, preferences)
override val sensitiveContentEnabled: Boolean = preferences.getBoolean(KEY_DISPLAY_SENSITIVE_CONTENTS, false) 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? override val gapClickListener: IGapSupportedAdapter.GapClickListener?
get() = statusClickListener get() = statusClickListener
@ -90,6 +86,10 @@ class ParcelableStatusesAdapter(
notifyDataSetChanged() notifyDataSetChanged()
} }
override var statusClickListener: IStatusViewHolder.StatusClickListener? = null
override val itemCounts = ItemCounts(5)
var isShowInReplyTo: Boolean = false var isShowInReplyTo: Boolean = false
set(value) { set(value) {
if (field == value) return if (field == value) return
@ -111,47 +111,35 @@ class ParcelableStatusesAdapter(
notifyDataSetChanged() notifyDataSetChanged()
} }
private var displayPositions: IntArray? = null private val inflater: LayoutInflater = LayoutInflater.from(context)
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
var data: List<ParcelableStatus>? = null private val showCardActions: Boolean = !preferences[hideCardActionsKey]
set(data) {
when (data) { private val gapLoadingIds: MutableSet<ObjectId> = HashSet()
null -> { private var showingActionCardId = RecyclerView.NO_ID
displayPositions = null
displayDataCount = 0 private val showingFullTextStates = SparseBooleanArray()
}
is ObjectCursor -> { private var pagedStatusesHelper = PagedListAdapterHelper<ParcelableStatus>(this, object : DiffCallback<ParcelableStatus>() {
displayPositions = null override fun areContentsTheSame(oldItem: ParcelableStatus, newItem: ParcelableStatus): Boolean {
displayDataCount = data.size return oldItem == newItem
} }
else -> {
var filteredCount = 0 override fun areItemsTheSame(oldItem: ParcelableStatus, newItem: ParcelableStatus): Boolean {
displayPositions = IntArray(data.size).apply { return oldItem._id == newItem._id || oldItem === newItem
data.forEachIndexed { i, item -> }
if (!item.is_gap && item.is_filtered) {
filteredCount++ })
} else {
this[i - filteredCount] = i var statuses: PagedList<ParcelableStatus>?
} get() = pagedStatusesHelper.currentList
} set(value) {
} pagedStatusesHelper.setList(value)
displayDataCount = data.size - filteredCount
}
}
field = data
this.infoCache = if (data != null) arrayOfNulls(data.size) else null
gapLoadingIds.clear() gapLoadingIds.clear()
updateItemCount() updateItemCount()
notifyDataSetChanged() notifyDataSetChanged()
} }
override val itemCounts = ItemCounts(5)
val statusStartIndex: Int val statusStartIndex: Int
get() = getItemStartPosition(ITEM_INDEX_STATUS) get() = getItemStartPosition(ITEM_INDEX_STATUS)
@ -178,20 +166,16 @@ class ParcelableStatusesAdapter(
} }
override fun isGapItem(position: Int): Boolean { override fun isGapItem(position: Int): Boolean {
return getFieldValue(position, { info -> return getStatus(position).is_gap
return@getFieldValue info.gap
}, { status ->
return@getFieldValue status.is_gap
}, false)
} }
override fun getStatus(position: Int, raw: Boolean): ParcelableStatus { 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 { override fun getStatusCount(raw: Boolean): Int {
if (raw) return data?.size ?: 0 if (raw) return statuses?.size ?: 0
return displayDataCount return pagedStatusesHelper.itemCount
} }
override fun getItemId(position: Int): Long { override fun getItemId(position: Int): Long {
@ -201,57 +185,29 @@ class ParcelableStatusesAdapter(
val status = pinnedStatuses!![position - getItemStartPosition(ITEM_INDEX_PINNED_STATUS)] val status = pinnedStatuses!![position - getItemStartPosition(ITEM_INDEX_PINNED_STATUS)]
return status.hashCode().toLong() return status.hashCode().toLong()
} }
ITEM_INDEX_STATUS -> getFieldValue(position, { (_, accountKey, id) -> ITEM_INDEX_STATUS -> return getStatus(position, false).hashCode().toLong()
return@getFieldValue ParcelableStatus.calculateHashCode(accountKey, id)
}, { status ->
return@getFieldValue status.hashCode()
}, -1).toLong()
else -> position.toLong() else -> position.toLong()
} }
} }
override fun getStatusId(position: Int, raw: Boolean): String { override fun getStatusId(position: Int, raw: Boolean): String {
return getFieldValue(position, { info -> return getStatus(position, raw).id
return@getFieldValue info.id
}, { status ->
return@getFieldValue status.id
}, "")
} }
fun getStatusSortId(position: Int, raw: Boolean): Long { fun getStatusSortId(position: Int, raw: Boolean): Long {
return getFieldValue(position, { info -> return getStatus(position, raw).sort_id
return@getFieldValue info.sortId
}, { status ->
return@getFieldValue status.sort_id
}, -1L, raw)
} }
override fun getStatusTimestamp(position: Int, raw: Boolean): Long { override fun getStatusTimestamp(position: Int, raw: Boolean): Long {
return getFieldValue(position, { info -> return getStatus(position, raw).timestamp
return@getFieldValue info.timestamp
}, { status ->
return@getFieldValue status.timestamp
}, -1L)
} }
override fun getStatusPositionKey(position: Int, raw: Boolean): Long { override fun getStatusPositionKey(position: Int, raw: Boolean): Long {
return getFieldValue(position, { info -> return getStatus(position, raw).position_key
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)
} }
override fun getAccountKey(position: Int, raw: Boolean): UserKey { override fun getAccountKey(position: Int, raw: Boolean): UserKey {
val def: UserKey? = null return getStatus(position, raw).account_key
return getFieldValue(position, { info ->
return@getFieldValue info.accountKey
}, { status ->
return@getFieldValue status.account_key
}, def, raw)!!
} }
override fun isCardActionsShown(position: Int): Boolean { override fun isCardActionsShown(position: Int): Boolean {
@ -313,7 +269,7 @@ class ParcelableStatusesAdapter(
when (holder.itemViewType) { when (holder.itemViewType) {
VIEW_TYPE_STATUS -> { VIEW_TYPE_STATUS -> {
val countIndex: Int = getItemCountIndex(position) 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, (holder as IStatusViewHolder).display(status, displayInReplyTo = isShowInReplyTo,
displayPinned = countIndex == ITEM_INDEX_PINNED_STATUS) displayPinned = countIndex == ITEM_INDEX_PINNED_STATUS)
} }
@ -321,7 +277,7 @@ class ParcelableStatusesAdapter(
(holder as TimelineFilterHeaderViewHolder).display(timelineFilter!!) (holder as TimelineFilterHeaderViewHolder).display(timelineFilter!!)
} }
ITEM_VIEW_TYPE_GAP -> { 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 } val loading = gapLoadingIds.any { it.accountKey == status.account_key && it.id == status.id }
(holder as GapViewHolder).display(loading) (holder as GapViewHolder).display(loading)
} }
@ -378,11 +334,8 @@ class ParcelableStatusesAdapter(
} }
fun getRowId(adapterPosition: Int, raw: Boolean = false): Long { fun getRowId(adapterPosition: Int, raw: Boolean = false): Long {
return getFieldValue(adapterPosition, readInfoValueAction = { val status = getStatus(adapterPosition, raw)
it._id return if (status._id < 0) status.hashCode().toLong() else status._id
}, readStatusValueAction = { status ->
status.hashCode().toLong()
}, defValue = -1L, raw = raw)
} }
fun findPositionByPositionKey(positionKey: Long, raw: Boolean = false): Int { fun findPositionByPositionKey(positionKey: Long, raw: Boolean = false): Int {
@ -414,7 +367,7 @@ class ParcelableStatusesAdapter(
var sum = 0 var sum = 0
for (i in 0 until itemCounts.size) { for (i in 0 until itemCounts.size) {
sum += when (i) { sum += when (i) {
ITEM_INDEX_STATUS -> data?.size ?: 0 ITEM_INDEX_STATUS -> statuses?.size ?: 0
else -> itemCounts[i] else -> itemCounts[i]
} }
if (position < sum) { if (position < sum) {
@ -424,57 +377,15 @@ class ParcelableStatusesAdapter(
return -1 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), private fun getStatusInternal(position: Int, countIndex: Int = getItemCountIndex(position),
raw: Boolean = false, reuse: Boolean): ParcelableStatus { raw: Boolean = false): ParcelableStatus {
when (countIndex) { when (countIndex) {
ITEM_INDEX_PINNED_STATUS -> { ITEM_INDEX_PINNED_STATUS -> {
return pinnedStatuses!![position - getItemStartPosition(ITEM_INDEX_PINNED_STATUS)] return pinnedStatuses!![position - getItemStartPosition(ITEM_INDEX_PINNED_STATUS)]
} }
ITEM_INDEX_STATUS -> { ITEM_INDEX_STATUS -> {
val data = this.data!!
val dataPosition = position - statusStartIndex val dataPosition = position - statusStartIndex
val positions = displayPositions return pagedStatusesHelper.getItem(dataPosition)!!
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]
}
} }
} }
val validStart = getItemStartPosition(ITEM_INDEX_PINNED_STATUS) val validStart = getItemStartPosition(ITEM_INDEX_PINNED_STATUS)

View File

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

View File

@ -17,21 +17,27 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * 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() { override fun createDataSource() = CursorObjectTiledDataSource(resolver, uri, projection,
onLoadData(this::setValue) selection, selectionArgs, sortOrder, cls)
}
override fun onActive() {
loadData()
}
protected abstract fun onLoadData(callback: (T) -> Unit)
} }

View File

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

View File

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

View File

@ -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?, fun <T> ContentResolver.queryAll(uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String? = null, cls: Class<T>): List<T> { selectionArgs: Array<String>?, sortOrder: String? = null, limit: String? = null,
return queryReference(uri, projection, selection, selectionArgs, sortOrder)?.use { (cur) -> cls: Class<T>): List<T> {
return queryReference(uri, projection, selection, selectionArgs, sortOrder, limit)?.use { (cur) ->
return@use cur.map(ObjectCursor.indicesFrom(cur, cls)) return@use cur.map(ObjectCursor.indicesFrom(cur, cls))
} ?: emptyList() } ?: emptyList()
} }

View File

@ -25,12 +25,12 @@ fun FiltersData.read(cr: ContentResolver, loadSubscription: Boolean = false) {
fun readBaseItems(uri: Uri): List<FiltersData.BaseItem>? { fun readBaseItems(uri: Uri): List<FiltersData.BaseItem>? {
val where = if (loadSubscription) null else Expression.lesserThan(Filters.SOURCE, 0).sql val where = if (loadSubscription) null else Expression.lesserThan(Filters.SOURCE, 0).sql
return cr.queryAll(uri, Filters.COLUMNS, where, null, null, return cr.queryAll(uri, Filters.COLUMNS, where, null, null,
FiltersData.BaseItem::class.java) cls = FiltersData.BaseItem::class.java)
} }
this.users = run { this.users = run {
val where = if (loadSubscription) null else Expression.lesserThan(Filters.Users.SOURCE, 0).sql 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, 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.keywords = readBaseItems(Filters.Keywords.CONTENT_URI)
this.sources = readBaseItems(Filters.Sources.CONTENT_URI) this.sources = readBaseItems(Filters.Sources.CONTENT_URI)

View File

@ -43,7 +43,6 @@ import org.mariotaku.twidere.R
import org.mariotaku.twidere.activity.AccountSelectorActivity import org.mariotaku.twidere.activity.AccountSelectorActivity
import org.mariotaku.twidere.activity.ComposeActivity import org.mariotaku.twidere.activity.ComposeActivity
import org.mariotaku.twidere.adapter.ParcelableStatusesAdapter import org.mariotaku.twidere.adapter.ParcelableStatusesAdapter
import org.mariotaku.twidere.adapter.decorator.ExtendedDividerItemDecoration
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.annotation.ReadPositionTag import org.mariotaku.twidere.annotation.ReadPositionTag
import org.mariotaku.twidere.constant.* 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.extension.model.getAccountType
import org.mariotaku.twidere.fragment.status.FavoriteConfirmDialogFragment import org.mariotaku.twidere.fragment.status.FavoriteConfirmDialogFragment
import org.mariotaku.twidere.fragment.status.RetweetQuoteDialogFragment 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.graphic.like.LikeAnimationDrawable
import org.mariotaku.twidere.loader.iface.IExtendedLoader import org.mariotaku.twidere.loader.iface.IExtendedLoader
import org.mariotaku.twidere.model.ObjectId import org.mariotaku.twidere.model.ObjectId
@ -100,9 +100,9 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
protected abstract val accountKeys: Array<UserKey> protected abstract val accountKeys: Array<UserKey>
protected var adapterData: List<ParcelableStatus>? protected var adapterData: List<ParcelableStatus>?
get() = adapter.data get() = adapter.statuses
set(data) { set(data) {
adapter.data = data // adapter.statuses = data
} }
@ReadPositionTag @ReadPositionTag
@ -400,27 +400,9 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
return handleActionLongClick(this, status, adapter.getItemId(position), id) return handleActionLongClick(this, status, adapter.getItemId(position), id)
} }
override fun onCreateItemDecoration(context: Context, recyclerView: RecyclerView, layoutManager: LinearLayoutManager): RecyclerView.ItemDecoration? { override fun onCreateItemDecoration(context: Context, recyclerView: RecyclerView,
val itemDecoration = ExtendedDividerItemDecoration(context, (recyclerView.layoutManager as LinearLayoutManager).orientation) layoutManager: LinearLayoutManager): RecyclerView.ItemDecoration? {
val res = context.resources return AbsTimelineFragment.createStatusesListItemDecoration(context, recyclerView, adapter)
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
} }
protected fun saveReadPosition() { protected fun saveReadPosition() {

View File

@ -149,7 +149,7 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() {
} }
override fun onLoaderReset(loader: Loader<List<ParcelableStatus>?>) { override fun onLoaderReset(loader: Loader<List<ParcelableStatus>?>) {
adapter.data = null adapter.statuses = null
} }
override fun onLoadMoreContents(@IndicatorPosition position: Long) { override fun onLoadMoreContents(@IndicatorPosition position: Long) {

View File

@ -41,7 +41,7 @@ class MediaStatusesSearchFragment : ParcelableStatusesFragment() {
val tabPosition = args.getInt(EXTRA_TAB_POSITION, -1) val tabPosition = args.getInt(EXTRA_TAB_POSITION, -1)
val makeGap = args.getBoolean(EXTRA_MAKE_GAP, true) val makeGap = args.getBoolean(EXTRA_MAKE_GAP, true)
val loadingMore = args.getBoolean(EXTRA_LOADING_MORE, false) 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) loadingMore)
} }

View File

@ -43,7 +43,7 @@ class UserMediaTimelineFragment : ParcelableStatusesFragment() {
val userKey = args.getParcelable<UserKey?>(EXTRA_USER_KEY) val userKey = args.getParcelable<UserKey?>(EXTRA_USER_KEY)
val screenName = args.getString(EXTRA_SCREEN_NAME) val screenName = args.getString(EXTRA_SCREEN_NAME)
val loadingMore = args.getBoolean(EXTRA_LOADING_MORE, false) 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) fromUser, loadingMore)
} }
} }

View File

@ -21,29 +21,39 @@ package org.mariotaku.twidere.fragment.timeline
import android.arch.lifecycle.LiveData import android.arch.lifecycle.LiveData
import android.arch.lifecycle.Observer import android.arch.lifecycle.Observer
import android.arch.paging.PagedList
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.support.v7.widget.FixedLinearLayoutManager import android.support.v7.widget.FixedLinearLayoutManager
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.RecyclerView.LayoutManager import android.support.v7.widget.RecyclerView.LayoutManager
import android.support.v7.widget.StaggeredGridLayoutManager import android.support.v7.widget.StaggeredGridLayoutManager
import android.widget.Toast
import com.bumptech.glide.RequestManager import com.bumptech.glide.RequestManager
import com.squareup.otto.Subscribe
import kotlinx.android.synthetic.main.fragment_content_listview.* import kotlinx.android.synthetic.main.fragment_content_listview.*
import org.mariotaku.twidere.R import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.ParcelableStatusesAdapter 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.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.annotation.FilterScope import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.annotation.TimelineStyle 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.AbsContentRecyclerViewFragment
import org.mariotaku.twidere.fragment.CursorStatusesFragment 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.ParcelableStatus
import org.mariotaku.twidere.model.UserKey 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.pagination.SinceMaxPagination
import org.mariotaku.twidere.model.refresh.BaseRefreshTaskParam import org.mariotaku.twidere.model.refresh.BaseRefreshTaskParam
import org.mariotaku.twidere.model.refresh.RefreshTaskParam 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.DataStoreUtils
import org.mariotaku.twidere.util.Utils import org.mariotaku.twidere.util.Utils
@ -60,7 +70,7 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
protected open val timelineStyle: Int protected open val timelineStyle: Int
get() = TimelineStyle.PLAIN get() = TimelineStyle.PLAIN
protected open val isLive: Boolean protected open val isStandalone: Boolean
get() = tabId <= 0 get() = tabId <= 0
@FilterScope @FilterScope
@ -71,16 +81,18 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
*/ */
protected abstract val contentUri: Uri protected abstract val contentUri: Uri
protected lateinit var statuses: LiveData<List<ParcelableStatus>?> protected lateinit var statuses: LiveData<PagedList<ParcelableStatus>?>
private set private set
protected val accountKeys: Array<UserKey> protected val accountKeys: Array<UserKey>
get() = Utils.getAccountKeys(context, arguments) ?: if (isLive) { get() = Utils.getAccountKeys(context, arguments) ?: if (isStandalone) {
emptyArray() emptyArray()
} else { } else {
DataStoreUtils.getActivatedAccountKeys(context) DataStoreUtils.getActivatedAccountKeys(context)
} }
private val busEventHandler = BusEventHandler()
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
statuses = createLiveData() statuses = createLiveData()
@ -89,6 +101,16 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
showProgress() 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) { override fun onCreateLayoutManager(context: Context): LayoutManager = when (timelineStyle) {
TimelineStyle.STAGGERED -> StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL) TimelineStyle.STAGGERED -> StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
else -> FixedLinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) else -> FixedLinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
@ -98,15 +120,29 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
return ParcelableStatusesAdapter(context, requestManager, timelineStyle) 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 { override fun triggerRefresh(): Boolean {
val param = onCreateRefreshParam(REFRESH_POSITION_START) val param = onCreateRefreshParam(REFRESH_POSITION_START) ?: return false
return getStatuses(param) return getStatuses(param)
} }
override fun onLoadMoreContents(position: Long) { override fun onLoadMoreContents(position: Long) {
if (position != ILoadMoreSupportAdapter.END) return if (position != ILoadMoreSupportAdapter.END) return
val param = onCreateRefreshParam(REFRESH_POSITION_END) val param = onCreateRefreshParam(REFRESH_POSITION_END) ?: return
getStatuses(param) if (getStatuses(param)) {
adapter.loadMoreIndicatorPosition = position
}
} }
override fun scrollToPositionWithOffset(position: Int, offset: Int) { override fun scrollToPositionWithOffset(position: Int, offset: Int) {
@ -121,12 +157,12 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
} }
} }
protected open fun onDataLoaded(data: List<ParcelableStatus>?) { protected open fun onDataLoaded(data: PagedList<ParcelableStatus>?) {
adapter.data = data adapter.statuses = data
when { when {
data is ExceptionResponseList -> { // data is ExceptionResponseList -> {
showEmpty(R.drawable.ic_info_error_generic, data.exception.toString()) // showEmpty(R.drawable.ic_info_error_generic, data.exception.toString())
} // }
data == null || data.isEmpty() -> { data == null || data.isEmpty() -> {
showEmpty(R.drawable.ic_info_refresh, getString(R.string.swipe_down_to_refresh)) 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 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() throw UnsupportedOperationException()
} }
private fun createLiveData(): LiveData<List<ParcelableStatus>?> { private fun createLiveData(): LiveData<PagedList<ParcelableStatus>?> {
if (isLive) return onCreateRealTimeLiveData() if (isStandalone) return onCreateStandaloneLiveData()
return ObjectCursorLiveData(context.contentResolver, contentUri, val provider = CursorObjectLivePagedListProvider(context.contentResolver, contentUri,
CursorStatusesFragment.statusColumnsLite, cls = ParcelableStatus::class.java) 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 { companion object {
const val REFRESH_POSITION_START = -1 const val REFRESH_POSITION_START = -1
const val REFRESH_POSITION_END = -2 const val REFRESH_POSITION_END = -2
fun getBaseRefreshTaskParam(fragment: AbsTimelineFragment<*>, position: Int): BaseRefreshTaskParam { fun getBaseRefreshTaskParam(fragment: AbsTimelineFragment<*>, position: Int): BaseRefreshTaskParam? {
when (position) { when (position) {
REFRESH_POSITION_START -> { REFRESH_POSITION_START -> {
return getRefreshBaseRefreshTaskParam(fragment) return getRefreshBaseRefreshTaskParam(fragment)
} }
REFRESH_POSITION_END -> { 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 { private fun getRefreshBaseRefreshTaskParam(fragment: AbsTimelineFragment<*>): BaseRefreshTaskParam {
@ -184,5 +260,31 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
return BaseRefreshTaskParam(accountKeys, null) 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
}
} }
} }

View File

@ -29,7 +29,7 @@ class HomeTimelineFragment : AbsTimelineFragment<RefreshTaskParam>() {
override val contentUri: Uri = Statuses.CONTENT_URI override val contentUri: Uri = Statuses.CONTENT_URI
override fun onCreateRefreshParam(position: Int): RefreshTaskParam { override fun onCreateRefreshParam(position: Int): RefreshTaskParam? {
return getBaseRefreshTaskParam(this, position) return getBaseRefreshTaskParam(this, position)
} }

View File

@ -40,7 +40,7 @@ class CacheTimelineResultTask(
val localRelationships = cr.queryAll(CachedRelationships.CONTENT_URI, CachedRelationships.COLUMNS, val localRelationships = cr.queryAll(CachedRelationships.CONTENT_URI, CachedRelationships.COLUMNS,
Expression.and(Expression.equalsArgs(CachedRelationships.ACCOUNT_KEY), Expression.and(Expression.equalsArgs(CachedRelationships.ACCOUNT_KEY),
Expression.inArgs(CachedRelationships.USER_KEY, users.size)).sql, 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 relationships = users.mapTo(ArraySet<ParcelableRelationship>()) { user ->
val userKey = user.key val userKey = user.key
return@mapTo localRelationships.find { return@mapTo localRelationships.find {