1
0
mirror of https://github.com/TwidereProject/Twidere-Android synced 2025-02-08 15:58:40 +01:00

started to use paged list

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

View File

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

View File

@ -193,7 +193,8 @@ dependencies {
// https://g.co/androidstudio/app-test-app-conflict
androidTestImplementation "com.google.code.findbugs:jsr305:3.0.1"
implementation "android.arch.lifecycle:extensions:${libVersions['SupportLifecycleExtensions']}"
implementation "android.arch.lifecycle:extensions:${libVersions['ArchLifecycleExtensions']}"
implementation "android.arch.paging:runtime:${libVersions['ArchPaging']}"
implementation 'com.android.support:multidex:1.0.2'
implementation "com.android.support:support-annotations:${libVersions['SupportLib']}"
implementation "com.android.support:support-compat:${libVersions['SupportLib']}"

View File

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

View File

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

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/>.
*/
package org.mariotaku.twidere.data
package org.mariotaku.twidere.data.source
import android.arch.lifecycle.LiveData
import android.arch.paging.LivePagedListProvider
import android.content.ContentResolver
import android.net.Uri
/**
* Created by mariotaku on 2017/10/13.
*/
abstract class ReloadableLiveData<T> : LiveData<T>() {
class CursorObjectLivePagedListProvider<T>(
private val resolver: ContentResolver,
val uri: Uri,
val projection: Array<String>? = null,
val selection: String? = null,
val selectionArgs: Array<String>? = null,
val sortOrder: String? = null,
val cls: Class<T>
) : LivePagedListProvider<Int, T>() {
fun loadData() {
onLoadData(this::setValue)
}
override fun onActive() {
loadData()
}
protected abstract fun onLoadData(callback: (T) -> Unit)
override fun createDataSource() = CursorObjectTiledDataSource(resolver, uri, projection,
selection, selectionArgs, sortOrder, cls)
}

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

View File

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

View File

@ -43,7 +43,6 @@ import org.mariotaku.twidere.R
import org.mariotaku.twidere.activity.AccountSelectorActivity
import org.mariotaku.twidere.activity.ComposeActivity
import org.mariotaku.twidere.adapter.ParcelableStatusesAdapter
import org.mariotaku.twidere.adapter.decorator.ExtendedDividerItemDecoration
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.annotation.ReadPositionTag
import org.mariotaku.twidere.constant.*
@ -52,6 +51,7 @@ import org.mariotaku.twidere.constant.KeyboardShortcutConstants.*
import org.mariotaku.twidere.extension.model.getAccountType
import org.mariotaku.twidere.fragment.status.FavoriteConfirmDialogFragment
import org.mariotaku.twidere.fragment.status.RetweetQuoteDialogFragment
import org.mariotaku.twidere.fragment.timeline.AbsTimelineFragment
import org.mariotaku.twidere.graphic.like.LikeAnimationDrawable
import org.mariotaku.twidere.loader.iface.IExtendedLoader
import org.mariotaku.twidere.model.ObjectId
@ -100,9 +100,9 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
protected abstract val accountKeys: Array<UserKey>
protected var adapterData: List<ParcelableStatus>?
get() = adapter.data
get() = adapter.statuses
set(data) {
adapter.data = data
// adapter.statuses = data
}
@ReadPositionTag
@ -400,27 +400,9 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
return handleActionLongClick(this, status, adapter.getItemId(position), id)
}
override fun onCreateItemDecoration(context: Context, recyclerView: RecyclerView, layoutManager: LinearLayoutManager): RecyclerView.ItemDecoration? {
val itemDecoration = ExtendedDividerItemDecoration(context, (recyclerView.layoutManager as LinearLayoutManager).orientation)
val res = context.resources
if (adapter.profileImageEnabled) {
val decorPaddingLeft = res.getDimensionPixelSize(R.dimen.element_spacing_normal) * 2 + res.getDimensionPixelSize(R.dimen.icon_size_status_profile_image)
itemDecoration.setPadding { position, rect ->
val itemViewType = adapter.getItemViewType(position)
var nextItemIsStatus = false
if (position < adapter.itemCount - 1) {
nextItemIsStatus = adapter.getItemViewType(position + 1) == ParcelableStatusesAdapter.VIEW_TYPE_STATUS
}
if (nextItemIsStatus && itemViewType == ParcelableStatusesAdapter.VIEW_TYPE_STATUS) {
rect.left = decorPaddingLeft
} else {
rect.left = 0
}
true
}
}
itemDecoration.setDecorationEndOffset(1)
return itemDecoration
override fun onCreateItemDecoration(context: Context, recyclerView: RecyclerView,
layoutManager: LinearLayoutManager): RecyclerView.ItemDecoration? {
return AbsTimelineFragment.createStatusesListItemDecoration(context, recyclerView, adapter)
}
protected fun saveReadPosition() {

View File

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

View File

@ -41,7 +41,7 @@ class MediaStatusesSearchFragment : ParcelableStatusesFragment() {
val tabPosition = args.getInt(EXTRA_TAB_POSITION, -1)
val makeGap = args.getBoolean(EXTRA_MAKE_GAP, true)
val loadingMore = args.getBoolean(EXTRA_LOADING_MORE, false)
return MediaStatusesSearchLoader(activity, accountKey, query, adapter.data, fromUser, makeGap,
return MediaStatusesSearchLoader(activity, accountKey, query, adapter.statuses, fromUser, makeGap,
loadingMore)
}

View File

@ -43,7 +43,7 @@ class UserMediaTimelineFragment : ParcelableStatusesFragment() {
val userKey = args.getParcelable<UserKey?>(EXTRA_USER_KEY)
val screenName = args.getString(EXTRA_SCREEN_NAME)
val loadingMore = args.getBoolean(EXTRA_LOADING_MORE, false)
return MediaTimelineLoader(context, accountKey, userKey, screenName, adapter.data,
return MediaTimelineLoader(context, accountKey, userKey, screenName, adapter.statuses,
fromUser, loadingMore)
}
}

View File

@ -21,29 +21,39 @@ package org.mariotaku.twidere.fragment.timeline
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.Observer
import android.arch.paging.PagedList
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.support.v7.widget.FixedLinearLayoutManager
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.RecyclerView.LayoutManager
import android.support.v7.widget.StaggeredGridLayoutManager
import android.widget.Toast
import com.bumptech.glide.RequestManager
import com.squareup.otto.Subscribe
import kotlinx.android.synthetic.main.fragment_content_listview.*
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.ParcelableStatusesAdapter
import org.mariotaku.twidere.adapter.decorator.ExtendedDividerItemDecoration
import org.mariotaku.twidere.adapter.iface.IContentAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.annotation.FilterScope
import org.mariotaku.twidere.annotation.TimelineStyle
import org.mariotaku.twidere.data.ObjectCursorLiveData
import org.mariotaku.twidere.data.source.CursorObjectLivePagedListProvider
import org.mariotaku.twidere.fragment.AbsContentRecyclerViewFragment
import org.mariotaku.twidere.fragment.CursorStatusesFragment
import org.mariotaku.twidere.model.ExceptionResponseList
import org.mariotaku.twidere.model.ObjectId
import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.event.GetStatusesTaskEvent
import org.mariotaku.twidere.model.pagination.Pagination
import org.mariotaku.twidere.model.pagination.SinceMaxPagination
import org.mariotaku.twidere.model.refresh.BaseRefreshTaskParam
import org.mariotaku.twidere.model.refresh.RefreshTaskParam
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.task.statuses.GetStatusesTask
import org.mariotaku.twidere.util.DataStoreUtils
import org.mariotaku.twidere.util.Utils
@ -60,7 +70,7 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
protected open val timelineStyle: Int
get() = TimelineStyle.PLAIN
protected open val isLive: Boolean
protected open val isStandalone: Boolean
get() = tabId <= 0
@FilterScope
@ -71,16 +81,18 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
*/
protected abstract val contentUri: Uri
protected lateinit var statuses: LiveData<List<ParcelableStatus>?>
protected lateinit var statuses: LiveData<PagedList<ParcelableStatus>?>
private set
protected val accountKeys: Array<UserKey>
get() = Utils.getAccountKeys(context, arguments) ?: if (isLive) {
get() = Utils.getAccountKeys(context, arguments) ?: if (isStandalone) {
emptyArray()
} else {
DataStoreUtils.getActivatedAccountKeys(context)
}
private val busEventHandler = BusEventHandler()
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
statuses = createLiveData()
@ -89,6 +101,16 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
showProgress()
}
override fun onStart() {
super.onStart()
bus.register(busEventHandler)
}
override fun onStop() {
bus.unregister(busEventHandler)
super.onStop()
}
override fun onCreateLayoutManager(context: Context): LayoutManager = when (timelineStyle) {
TimelineStyle.STAGGERED -> StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
else -> FixedLinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
@ -98,15 +120,29 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
return ParcelableStatusesAdapter(context, requestManager, timelineStyle)
}
override fun onCreateItemDecoration(context: Context, recyclerView: RecyclerView,
layoutManager: LayoutManager): RecyclerView.ItemDecoration? {
return when (timelineStyle) {
TimelineStyle.PLAIN -> {
createStatusesListItemDecoration(context, recyclerView, adapter)
}
else -> {
super.onCreateItemDecoration(context, recyclerView, layoutManager)
}
}
}
override fun triggerRefresh(): Boolean {
val param = onCreateRefreshParam(REFRESH_POSITION_START)
val param = onCreateRefreshParam(REFRESH_POSITION_START) ?: return false
return getStatuses(param)
}
override fun onLoadMoreContents(position: Long) {
if (position != ILoadMoreSupportAdapter.END) return
val param = onCreateRefreshParam(REFRESH_POSITION_END)
getStatuses(param)
val param = onCreateRefreshParam(REFRESH_POSITION_END) ?: return
if (getStatuses(param)) {
adapter.loadMoreIndicatorPosition = position
}
}
override fun scrollToPositionWithOffset(position: Int, offset: Int) {
@ -121,12 +157,12 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
}
}
protected open fun onDataLoaded(data: List<ParcelableStatus>?) {
adapter.data = data
protected open fun onDataLoaded(data: PagedList<ParcelableStatus>?) {
adapter.statuses = data
when {
data is ExceptionResponseList -> {
showEmpty(R.drawable.ic_info_error_generic, data.exception.toString())
}
// data is ExceptionResponseList -> {
// showEmpty(R.drawable.ic_info_error_generic, data.exception.toString())
// }
data == null || data.isEmpty() -> {
showEmpty(R.drawable.ic_info_refresh, getString(R.string.swipe_down_to_refresh))
}
@ -138,32 +174,72 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
protected abstract fun getStatuses(param: RefreshParam): Boolean
protected abstract fun onCreateRefreshParam(position: Int): RefreshParam
protected abstract fun onCreateRefreshParam(position: Int): RefreshParam?
protected open fun onCreateRealTimeLiveData(): LiveData<List<ParcelableStatus>?> {
protected open fun onCreateStandaloneLiveData(): LiveData<PagedList<ParcelableStatus>?> {
throw UnsupportedOperationException()
}
private fun createLiveData(): LiveData<List<ParcelableStatus>?> {
if (isLive) return onCreateRealTimeLiveData()
return ObjectCursorLiveData(context.contentResolver, contentUri,
CursorStatusesFragment.statusColumnsLite, cls = ParcelableStatus::class.java)
private fun createLiveData(): LiveData<PagedList<ParcelableStatus>?> {
if (isStandalone) return onCreateStandaloneLiveData()
val provider = CursorObjectLivePagedListProvider(context.contentResolver, contentUri,
CursorStatusesFragment.statusColumnsLite, sortOrder = Statuses.DEFAULT_SORT_ORDER,
cls = ParcelableStatus::class.java)
return provider.create(null, 20)
}
private inner class BusEventHandler {
@Subscribe
fun notifyGetStatusesTaskChanged(event: GetStatusesTaskEvent) {
if (event.uri != contentUri) return
refreshing = event.running
if (!event.running) {
setLoadMoreIndicatorPosition(ILoadMoreSupportAdapter.NONE)
refreshEnabled = true
// TODO: showContentOrError()
val exception = event.exception
if (exception is GetStatusesTask.GetTimelineException && userVisibleHint) {
Toast.makeText(context, exception.getToastMessage(context), Toast.LENGTH_SHORT).show()
}
}
}
}
companion object {
const val REFRESH_POSITION_START = -1
const val REFRESH_POSITION_END = -2
fun getBaseRefreshTaskParam(fragment: AbsTimelineFragment<*>, position: Int): BaseRefreshTaskParam {
fun getBaseRefreshTaskParam(fragment: AbsTimelineFragment<*>, position: Int): BaseRefreshTaskParam? {
when (position) {
REFRESH_POSITION_START -> {
return getRefreshBaseRefreshTaskParam(fragment)
}
REFRESH_POSITION_END -> {
val adapter = fragment.adapter
// Get last raw status
val startIdx = adapter.statusStartIndex
if (startIdx < 0) return null
val statusCount = adapter.getStatusCount(true)
if (statusCount <= 0) return null
val status = adapter.getStatus(startIdx + statusCount - 1, true)
val accountKeys = arrayOf(status.account_key)
val pagination = arrayOf<Pagination?>(SinceMaxPagination.maxId(status.id, -1))
val param = BaseRefreshTaskParam(accountKeys, pagination)
param.isLoadingMore = true
return param
}
else -> {
val adapter = fragment.adapter
val status = adapter.getStatus(position)
adapter.addGapLoadingId(ObjectId(status.account_key, status.id))
val accountKeys = arrayOf(status.account_key)
val pagination = arrayOf(SinceMaxPagination.maxId(status.id, status.sort_id))
return BaseRefreshTaskParam(accountKeys, pagination)
}
}
TODO()
}
private fun getRefreshBaseRefreshTaskParam(fragment: AbsTimelineFragment<*>): BaseRefreshTaskParam {
@ -184,5 +260,31 @@ abstract class AbsTimelineFragment<RefreshParam : RefreshTaskParam> :
return BaseRefreshTaskParam(accountKeys, null)
}
}
fun createStatusesListItemDecoration(context: Context, recyclerView: RecyclerView,
adapter: IContentAdapter): RecyclerView.ItemDecoration {
adapter as RecyclerView.Adapter<*>
val itemDecoration = ExtendedDividerItemDecoration(context, (recyclerView.layoutManager as LinearLayoutManager).orientation)
val res = context.resources
if (adapter.profileImageEnabled) {
val decorPaddingLeft = res.getDimensionPixelSize(R.dimen.element_spacing_normal) * 2 + res.getDimensionPixelSize(R.dimen.icon_size_status_profile_image)
itemDecoration.setPadding { position, rect ->
val itemViewType = adapter.getItemViewType(position)
var nextItemIsStatus = false
if (position < adapter.itemCount - 1) {
nextItemIsStatus = adapter.getItemViewType(position + 1) == ParcelableStatusesAdapter.VIEW_TYPE_STATUS
}
if (nextItemIsStatus && itemViewType == ParcelableStatusesAdapter.VIEW_TYPE_STATUS) {
rect.left = decorPaddingLeft
} else {
rect.left = 0
}
true
}
}
itemDecoration.setDecorationEndOffset(1)
return itemDecoration
}
}
}

View File

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

View File

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