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:
parent
04c84eb86f
commit
af9dadb142
81
build.gradle
81
build.gradle
@ -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',
|
||||||
]
|
]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
@ -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']}"
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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/>.
|
* 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)
|
|
||||||
|
|
||||||
}
|
}
|
@ -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?,
|
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()
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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() {
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user