updated paging library

This commit is contained in:
Mariotaku Lee 2017-12-19 12:01:36 +08:00
parent 77e997b6f2
commit 0be3d766f3
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
17 changed files with 184 additions and 217 deletions

View File

@ -35,7 +35,7 @@ allprojects {
subprojects {
buildscript {
ext {
kotlinVersion = '1.2.0'
kotlinVersion = '1.2.10'
pluginVersions = [
AndroidSvgDrawable: '3.0.0',
PlayServices : '3.1.1',
@ -70,7 +70,7 @@ subprojects {
ParcelablePlease : '1.0.2',
Chameleon : '0.9.25',
UniqR : '0.9.6',
SQLiteQB : '0.9.16',
SQLiteQB : '0.9.18',
Glide : '3.7.0',
GlideOkHttp3 : '1.4.0',
GlideTransformations : '2.0.2',
@ -81,7 +81,7 @@ subprojects {
Dagger : '2.11',
StethoBeanShellREPL : '0.3',
ArchLifecycleExtensions: '1.0.0',
ArchPaging : '1.0.0-alpha3',
ArchPaging : '1.0.0-alpha4-1',
ConstraintLayout : '1.0.2',
MessageBubbleView : '2.1',
]

View File

@ -576,14 +576,6 @@ public class ParcelableStatus implements Parcelable, Comparable<ParcelableStatus
@Nullable
public Map<String, CustomEmoji> emojis;
@JsonField(name = "next_key")
@Nullable
public String next_key;
@JsonField(name = "prev_key")
@Nullable
public String prev_key;
@Override
public int describeContents() {
return 0;

View File

@ -398,6 +398,7 @@ public interface TwidereDataStore {
String TABLE_NAME = "messages_conversations";
String CONTENT_PATH = "messages/conversations";
Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
}
}

View File

@ -23,10 +23,6 @@ import android.os.Parcelable;
import org.mariotaku.microblog.library.twitter.model.Paging;
/**
* Created by mariotaku on 2017/4/21.
*/
public interface Pagination extends Parcelable {
void applyTo(Paging paging);

View File

@ -19,4 +19,4 @@
package android.arch.paging
val <T>NullPaddedList<T>.rawList: MutableList<T?> get() = mList
val <T>PagedList<T>.storage: MutableList<T?> get() = mStorage

View File

@ -20,20 +20,20 @@
package org.mariotaku.twidere.data
import android.arch.paging.DataSource
import android.arch.paging.TiledDataSource
import android.arch.paging.PositionalDataSource
import android.content.ContentResolver
import android.database.ContentObserver
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.support.annotation.WorkerThread
import org.mariotaku.ktextension.toWeak
import org.mariotaku.ktextension.weak
import org.mariotaku.twidere.data.processor.DataSourceItemProcessor
import org.mariotaku.twidere.extension.queryAll
import org.mariotaku.twidere.extension.queryCount
import org.mariotaku.twidere.util.DebugLog
class CursorObjectLivePagedListProvider<T : Any>(
class CursorObjectDataSourceFactory<T : Any>(
private val resolver: ContentResolver,
val uri: Uri,
val projection: Array<String>? = null,
@ -42,15 +42,15 @@ class CursorObjectLivePagedListProvider<T : Any>(
val sortOrder: String? = null,
val cls: Class<T>,
val processor: DataSourceItemProcessor<T>? = null
) : ExtendedPagedListProvider<Int, T>() {
) : DataSource.Factory<Int, T> {
@WorkerThread
override fun onCreateDataSource(): DataSource<Int, T> {
return CursorObjectTiledDataSource(resolver, uri, projection,
selection, selectionArgs, sortOrder, cls, processor)
override fun create(): DataSource<Int, T> {
return CursorObjectDataSource(resolver, uri, projection, selection, selectionArgs,
sortOrder, cls, processor)
}
private class CursorObjectTiledDataSource<T : Any>(
private class CursorObjectDataSource<T : Any>(
val resolver: ContentResolver,
val uri: Uri,
val projection: Array<String>? = null,
@ -59,32 +59,41 @@ class CursorObjectLivePagedListProvider<T : Any>(
val sortOrder: String? = null,
val cls: Class<T>,
val processor: DataSourceItemProcessor<T>?
) : TiledDataSource<T>() {
) : PositionalDataSource<T>() {
private val lazyCount: Int by lazy { resolver.queryCount(uri, selection, selectionArgs) }
private val filterStates: BooleanArray by lazy { BooleanArray(lazyCount) }
init {
val weakThis = toWeak()
val weakThis by weak(this)
val observer: ContentObserver = object : ContentObserver(MainHandler) {
override fun onChange(selfChange: Boolean) {
resolver.unregisterContentObserver(this)
weakThis.get()?.invalidate()
weakThis?.invalidate()
}
}
resolver.registerContentObserver(uri, false, observer)
processor?.init(resolver)
}
override fun countItems() = lazyCount
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>) {
loadRange(0, params.pageSize) { data ->
callback.onResult(data, 0, lazyCount)
}
}
override fun loadRange(startPosition: Int, count: Int): List<T> {
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>) {
loadRange(params.startPosition, params.loadSize, callback::onResult)
}
private fun loadRange(startPosition: Int, count: Int, callback: (List<T>) -> Unit) {
if (processor == null) {
val start = System.currentTimeMillis()
val result = resolver.queryAll(uri, projection, selection, selectionArgs, sortOrder,
"$startPosition,$count", cls)
"$startPosition,$count", cls)!!
DebugLog.d(msg = "Querying $uri took ${System.currentTimeMillis() - start} ms.")
return result.orEmpty()
callback(result)
return
}
val result = ArrayList<T>()
var offset = filterStates.actualIndex(startPosition, startPosition)
@ -104,7 +113,7 @@ class CursorObjectLivePagedListProvider<T : Any>(
offset += limit
limit = count - result.size
} while (result.size < count)
return result
callback(result)
}
override fun invalidate() {

View File

@ -22,36 +22,36 @@ package org.mariotaku.twidere.data
import android.accounts.AccountManager
import android.arch.paging.DataSource
import android.arch.paging.KeyedDataSource
import android.arch.paging.PageKeyedDataSource
import android.content.Context
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.mastodon.Mastodon
import org.mariotaku.microblog.library.twitter.model.Paging
import org.mariotaku.twidere.R
import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.data.fetcher.StatusesFetcher
import org.mariotaku.twidere.exception.APINotSupportedException
import org.mariotaku.twidere.extension.getDetails
import org.mariotaku.twidere.extension.model.api.mastodon.getLinkPagination
import org.mariotaku.twidere.extension.getDetailsOrThrow
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
import org.mariotaku.twidere.extension.model.api.toParcelable
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.pagination.PaginatedArrayList
import org.mariotaku.twidere.model.pagination.PaginatedList
import org.mariotaku.twidere.model.pagination.Pagination
import org.mariotaku.twidere.model.pagination.SinceMaxPagination
import org.mariotaku.twidere.model.timeline.TimelineFilter
class StatusesLivePagedListProvider(
class StatusesDataSourceFactory(
private val context: Context,
private val fetcher: StatusesFetcher,
private val accountKey: UserKey,
private val timelineFilter: TimelineFilter?,
private val errorHandler: (Exception) -> Unit
) : ExtendedPagedListProvider<Pagination, ParcelableStatus>() {
) : DataSource.Factory<Pagination, ParcelableStatus> {
override fun onCreateDataSource(): DataSource<Pagination, ParcelableStatus> {
override fun create(): DataSource<Pagination, ParcelableStatus> {
return StatusesDataSource(context, fetcher, accountKey, timelineFilter, errorHandler)
}
@ -61,100 +61,94 @@ class StatusesLivePagedListProvider(
private val accountKey: UserKey,
private val timelineFilter: TimelineFilter?,
val errorHandler: (Exception) -> Unit
) : KeyedDataSource<Pagination, ParcelableStatus>() {
) : PageKeyedDataSource<Pagination, ParcelableStatus>() {
private val profileImageSize = context.getString(R.string.profile_image_size)
private var lastEndKey: String? = null
override fun getKey(item: ParcelableStatus): Pagination {
val prevKey = item.extras?.prev_key ?: item.id
val nextKey = item.extras?.next_key ?: item.id
return SinceMaxPagination().apply {
sinceId = nextKey
maxId = prevKey
}
}
override fun loadInitial(pageSize: Int): List<ParcelableStatus>? {
val loaded = try {
load(Paging().count(pageSize))
} catch (e: MicroBlogException) {
override fun loadInitial(params: LoadInitialParams<Pagination>, callback: LoadInitialCallback<Pagination, ParcelableStatus>) {
val paging = Paging().count(params.requestedLoadSize)
try {
val loaded = load(paging)
val filtered = loaded.filter(::filterCheck)
callback.onResult(filtered, null, loaded.nextPage)
} catch (e: Exception) {
errorHandler(e)
return emptyList()
}
return loaded.filter {
timelineFilter?.check(it) != false
invalidate()
}
}
override fun loadBefore(currentBeginKey: Pagination, pageSize: Int): List<ParcelableStatus>? {
val sinceId = (currentBeginKey as? SinceMaxPagination)?.sinceId ?: return null
return loadOrNull(Paging().count(pageSize).sinceId(sinceId))?.filter {
it.id != sinceId && timelineFilter?.check(it) != false
override fun loadBefore(params: LoadParams<Pagination>, callback: LoadCallback<Pagination, ParcelableStatus>) {
val paging = Paging().count(params.requestedLoadSize)
params.key.applyTo(paging)
try {
val loaded = load(paging)
val filtered = loaded.filter(::filterCheck).filterNot { params.key.isFromStatus(it) }
callback.onResult(filtered, null)
} catch (e: Exception) {
errorHandler(e)
callback.onResult(emptyList(), null)
}
}
override fun loadAfter(currentEndKey: Pagination, pageSize: Int): List<ParcelableStatus>? {
val maxId = (currentEndKey as? SinceMaxPagination)?.maxId ?: return null
if (lastEndKey == maxId) {
return null
}
val loadResult = loadOrNull(Paging().count(pageSize).maxId(maxId)) ?: return null
lastEndKey = loadResult.singleOrNull()?.id
return loadResult.filter {
it.id != maxId && timelineFilter?.check(it) != false
override fun loadAfter(params: LoadParams<Pagination>, callback: LoadCallback<Pagination, ParcelableStatus>) {
val paging = Paging().count(params.requestedLoadSize)
params.key.applyTo(paging)
try {
val loaded = load(paging)
val filtered = loaded.filter(::filterCheck).filterNot { params.key.isFromStatus(it) }
callback.onResult(filtered, loaded.nextPage)
} catch (e: Exception) {
errorHandler(e)
callback.onResult(emptyList(), null)
}
}
private fun load(paging: Paging): List<ParcelableStatus> {
private fun load(paging: Paging): PaginatedList<ParcelableStatus> {
val am = AccountManager.get(context)
val account = am.getDetails(accountKey, true) ?: return emptyList()
val account = am.getDetailsOrThrow(accountKey, true)
when (account.type) {
AccountType.TWITTER -> {
val twitter = account.newMicroBlogInstance(context, MicroBlog::class.java)
val timeline = fetcher.forTwitter(account, twitter, paging, timelineFilter)
return timeline.map {
it.toParcelable(account, profileImageSize)
}
return timeline.mapToPaginated { it.toParcelable(account, profileImageSize) }
}
AccountType.STATUSNET -> {
val statusnet = account.newMicroBlogInstance(context, MicroBlog::class.java)
val timeline = fetcher.forStatusNet(account, statusnet, paging, timelineFilter)
return timeline.map {
it.toParcelable(account, profileImageSize)
}
return timeline.mapToPaginated { it.toParcelable(account, profileImageSize) }
}
AccountType.FANFOU -> {
val fanfou = account.newMicroBlogInstance(context, MicroBlog::class.java)
val timeline = fetcher.forFanfou(account, fanfou, paging, timelineFilter)
return timeline.map {
it.toParcelable(account, profileImageSize)
}
return timeline.mapToPaginated { it.toParcelable(account, profileImageSize) }
}
AccountType.MASTODON -> {
val mastodon = account.newMicroBlogInstance(context, Mastodon::class.java)
val timeline = fetcher.forMastodon(account, mastodon, paging, timelineFilter)
val prevPagination = timeline.getLinkPagination("prev") as? SinceMaxPagination
val nextPagination = timeline.getLinkPagination("next") as? SinceMaxPagination
return timeline.map {
val status = it.toParcelable(account)
status.extras?.prev_key = prevPagination?.sinceId
status.extras?.next_key = nextPagination?.maxId
return@map status
}
return timeline.mapToPaginated { it.toParcelable(account) }
}
else -> throw APINotSupportedException(platform = account.type)
}
}
private fun loadOrNull(paging: Paging): List<ParcelableStatus>? {
return try {
load(paging)
} catch (e: MicroBlogException) {
null
private fun filterCheck(item: ParcelableStatus) = timelineFilter?.check(item) != false
private fun <T> List<T>.mapToPaginated(transform: (T) -> ParcelableStatus) = mapTo(PaginatedArrayList(), transform).apply {
val first = firstOrNull()
if (first != null) {
previousPage = SinceMaxPagination.sinceId(first.id, first.sort_id)
}
val last = lastOrNull()
if (last != null) {
nextPage = SinceMaxPagination.maxId(last.id, last.sort_id)
}
}
private fun Pagination.isFromStatus(status: ParcelableStatus) = when (this) {
is SinceMaxPagination -> sinceId == status.id || maxId == status.id
else -> false
}
}
}

View File

@ -19,15 +19,9 @@
package org.mariotaku.twidere.extension.adapter
import android.arch.paging.NullPaddedList
import android.arch.paging.rawList
import org.mariotaku.twidere.adapter.ParcelableStatusesAdapter
import org.mariotaku.twidere.model.ParcelableStatus
fun ParcelableStatusesAdapter.removeStatuses(predicate: (ParcelableStatus?) -> Boolean) {
val statuses = statuses as? NullPaddedList ?: return
// FIXME: Dirty hack, may break in future versions
if (statuses.rawList.removeAll(predicate = predicate)) {
notifyDataSetChanged()
}
// TODO: It really broke as of 1.0-alpha4-1. Try make it work.
}

View File

@ -23,10 +23,6 @@ import org.mariotaku.microblog.library.mastodon.model.Results
import org.mariotaku.twidere.model.pagination.PaginatedArrayList
import org.mariotaku.twidere.model.pagination.PaginatedList
/**
* Created by mariotaku on 2017/4/23.
*/
inline fun <T, R> Results.mapToPaginated(listSelector: (Results) -> List<T>?, transform: (T) -> R): PaginatedList<R> {
val list = listSelector(this) ?: return PaginatedArrayList()
val result = list.mapTo(PaginatedArrayList(list.size), transform)

View File

@ -1,38 +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.extension.text.twitter
import com.twitter.Extractor
import com.twitter.Validator
import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.UserKey
fun Validator.getTweetLength(text: String, ignoreMentions: Boolean, inReplyTo: ParcelableStatus?,
accountKey: UserKey? = inReplyTo?.account_key): Int {
if (!ignoreMentions || inReplyTo == null || accountKey == null) {
return getTweetLength(text)
}
val (_, replyText, _, _, _) = InternalExtractor.extractReplyTextAndMentions(text, inReplyTo,
accountKey)
return getTweetLength(replyText)
}
private object InternalExtractor : Extractor()

View File

@ -21,6 +21,7 @@ package org.mariotaku.twidere.fragment.activities
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.Observer
import android.arch.paging.LivePagedListBuilder
import android.arch.paging.PagedList
import android.content.ContentValues
import android.content.Context
@ -53,7 +54,7 @@ import org.mariotaku.twidere.annotation.ReadPositionTag
import org.mariotaku.twidere.constant.displaySensitiveContentsKey
import org.mariotaku.twidere.constant.newDocumentApiKey
import org.mariotaku.twidere.constant.readFromBottomKey
import org.mariotaku.twidere.data.CursorObjectLivePagedListProvider
import org.mariotaku.twidere.data.CursorObjectDataSourceFactory
import org.mariotaku.twidere.data.ExtendedPagedListProvider
import org.mariotaku.twidere.data.processor.DataSourceItemProcessor
import org.mariotaku.twidere.extension.model.activityStatus
@ -377,13 +378,13 @@ abstract class AbsActivitiesFragment : AbsContentRecyclerViewFragment<Parcelable
extraSelection.first.addTo(expressions)
extraSelection.second?.addAllTo(expressionArgs)
}
val provider = CursorObjectLivePagedListProvider(context!!.contentResolver, contentUri,
val factory = CursorObjectDataSourceFactory(context!!.contentResolver, contentUri,
activityColumnsLite, Expression.and(*expressions.toTypedArray()).sql,
expressionArgs.toTypedArray(), Activities.DEFAULT_SORT_ORDER,
ParcelableActivity::class.java, onCreateCursorObjectProcessor())
dataController = provider.obtainDataController()
return provider.create(null, PagedList.Config.Builder()
.setPageSize(50).setEnablePlaceholders(false).build())
// dataController = factory.obtainDataController()
return LivePagedListBuilder(factory, PagedList.Config.Builder()
.setPageSize(50).setEnablePlaceholders(false).build()).build()
}
private fun getFullActivity(position: Int): ParcelableActivity {

View File

@ -23,6 +23,7 @@ import android.accounts.AccountManager
import android.app.Activity
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.Observer
import android.arch.paging.LivePagedListBuilder
import android.arch.paging.PagedList
import android.content.Context
import android.content.Intent
@ -70,7 +71,7 @@ import org.mariotaku.twidere.constant.nameFirstKey
import org.mariotaku.twidere.constant.newDocumentApiKey
import org.mariotaku.twidere.constant.profileImageStyleKey
import org.mariotaku.twidere.data.ComputableLiveData
import org.mariotaku.twidere.data.CursorObjectLivePagedListProvider
import org.mariotaku.twidere.data.CursorObjectDataSourceFactory
import org.mariotaku.twidere.extension.*
import org.mariotaku.twidere.extension.model.*
import org.mariotaku.twidere.fragment.AbsContentListRecyclerViewFragment
@ -395,11 +396,11 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
Expression.equalsArgs(Messages.CONVERSATION_ID)).sql
val selectionArgs = arrayOf(accountKey.toString(), conversationId)
val sortOrder = OrderBy(Messages.SORT_ID, false).sql
val provider = CursorObjectLivePagedListProvider(context!!.contentResolver,
val factory = CursorObjectDataSourceFactory(context!!.contentResolver,
Messages.CONTENT_URI, Messages.COLUMNS, selection, selectionArgs, sortOrder,
ParcelableMessage::class.java)
return provider.create(null, PagedList.Config.Builder()
.setPageSize(50).setEnablePlaceholders(false).build())
return LivePagedListBuilder(factory, PagedList.Config.Builder()
.setPageSize(50).setEnablePlaceholders(false).build()).build()
}
private fun onDataLoaded(messages: PagedList<ParcelableMessage>?) {

View File

@ -22,6 +22,7 @@ package org.mariotaku.twidere.fragment.message
import android.app.Activity
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.Observer
import android.arch.paging.LivePagedListBuilder
import android.arch.paging.PagedList
import android.content.Context
import android.content.Intent
@ -35,8 +36,10 @@ import com.squareup.otto.Subscribe
import kotlinx.android.synthetic.main.activity_premium_dashboard.*
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.toStringArray
import org.mariotaku.sqliteqb.library.*
import org.mariotaku.sqliteqb.library.Columns.Column
import org.mariotaku.sqliteqb.library.Columns
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.sqliteqb.library.OrderBy
import org.mariotaku.sqliteqb.library.Table
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.EXTRA_ACCOUNT_KEYS
import org.mariotaku.twidere.TwidereConstants.REQUEST_SELECT_ACCOUNT
@ -48,7 +51,7 @@ import org.mariotaku.twidere.annotation.LoadMorePosition
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_TYPES
import org.mariotaku.twidere.constant.nameFirstKey
import org.mariotaku.twidere.constant.newDocumentApiKey
import org.mariotaku.twidere.data.CursorObjectLivePagedListProvider
import org.mariotaku.twidere.data.CursorObjectDataSourceFactory
import org.mariotaku.twidere.extension.accountKey
import org.mariotaku.twidere.extension.linkHandlerTitle
import org.mariotaku.twidere.extension.model.getTitle
@ -61,11 +64,11 @@ import org.mariotaku.twidere.model.ParcelableMessageConversation
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.event.GetMessagesTaskEvent
import org.mariotaku.twidere.promise.MessagePromises
import org.mariotaku.twidere.provider.TwidereDataProvider
import org.mariotaku.twidere.provider.TwidereDataStore.Messages
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
import org.mariotaku.twidere.task.twitter.message.GetMessagesTask
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.DataStoreUtils
import org.mariotaku.twidere.util.ErrorInfoStore
import org.mariotaku.twidere.util.IntentUtils
import org.mariotaku.twidere.util.Utils
import org.mariotaku.twidere.view.ExtendedRecyclerView
@ -217,32 +220,16 @@ class MessagesEntriesFragment : AbsContentListRecyclerViewFragment<MessagesEntri
}
private fun createLiveData(): LiveData<PagedList<ParcelableMessageConversation>?> {
val projection = (Conversations.COLUMNS + Conversations.UNREAD_COUNT).map {
TwidereQueryBuilder.mapConversationsProjection(it)
}.toTypedArray()
val qb = SQLQueryBuilder.select(Columns(*projection))
qb.from(Table(Conversations.TABLE_NAME))
qb.join(Join(false, Join.Operation.LEFT_OUTER, Table(Messages.TABLE_NAME),
Expression.and(
Expression.equals(
Column(Table(Conversations.TABLE_NAME), Conversations.CONVERSATION_ID),
Column(Table(Messages.TABLE_NAME), Messages.CONVERSATION_ID)
),
Expression.equals(
Column(Table(Conversations.TABLE_NAME), Conversations.ACCOUNT_KEY),
Column(Table(Messages.TABLE_NAME), Messages.ACCOUNT_KEY))
)
))
qb.where(Expression.inArgs(Column(Table(Conversations.TABLE_NAME), Conversations.ACCOUNT_KEY), accountKeys.size))
qb.groupBy(Column(Table(Messages.TABLE_NAME), Messages.CONVERSATION_ID))
qb.orderBy(OrderBy(arrayOf(Conversations.LOCAL_TIMESTAMP, Conversations.SORT_ID), booleanArrayOf(false, false)))
qb.limit(RawSQLLang(TwidereDataProvider.PLACEHOLDER_LIMIT))
val provider = CursorObjectLivePagedListProvider(context!!.contentResolver,
TwidereQueryBuilder.rawQuery(qb.buildSQL(), Conversations.CONTENT_URI),
val projection = Conversations.COLUMNS + Conversations.UNREAD_COUNT
val factory = CursorObjectDataSourceFactory(context!!.contentResolver,
Conversations.CONTENT_URI,
projection = projection,
selection = Expression.inArgs(Columns.Column(Table(Conversations.TABLE_NAME), Conversations.ACCOUNT_KEY), accountKeys.size).sql,
selectionArgs = accountKeys.toStringArray(),
sortOrder = OrderBy(arrayOf(Conversations.LOCAL_TIMESTAMP, Conversations.SORT_ID), booleanArrayOf(false, false)).sql,
cls = ParcelableMessageConversation::class.java)
return provider.create(null, PagedList.Config.Builder()
.setPageSize(50).setEnablePlaceholders(false).build())
return LivePagedListBuilder(factory, PagedList.Config.Builder()
.setPageSize(50).setEnablePlaceholders(false).build()).build()
}
private fun onDataLoaded(data: PagedList<ParcelableMessageConversation>?) {

View File

@ -23,6 +23,7 @@ import android.app.Activity
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MediatorLiveData
import android.arch.lifecycle.MutableLiveData
import android.arch.paging.LivePagedListBuilder
import android.arch.paging.PagedList
import android.content.ContentValues
import android.content.Context
@ -60,10 +61,10 @@ import org.mariotaku.twidere.annotation.TimelineStyle
import org.mariotaku.twidere.constant.*
import org.mariotaku.twidere.constant.IntentConstants.*
import org.mariotaku.twidere.constant.KeyboardShortcutConstants.*
import org.mariotaku.twidere.data.CursorObjectLivePagedListProvider
import org.mariotaku.twidere.data.CursorObjectDataSourceFactory
import org.mariotaku.twidere.data.ExceptionLiveData
import org.mariotaku.twidere.data.ExtendedPagedListProvider
import org.mariotaku.twidere.data.StatusesLivePagedListProvider
import org.mariotaku.twidere.data.StatusesDataSourceFactory
import org.mariotaku.twidere.data.fetcher.StatusesFetcher
import org.mariotaku.twidere.extension.*
import org.mariotaku.twidere.extension.adapter.removeStatuses
@ -404,7 +405,7 @@ abstract class AbsTimelineFragment : AbsContentRecyclerViewFragment<ParcelableSt
val accountKey = accountKeys.singleOrNull()!!
val errorLiveData = MutableLiveData<SingleResponse<PagedList<ParcelableStatus>?>>()
val provider = StatusesLivePagedListProvider(context.applicationContext,
val factory = StatusesDataSourceFactory(context.applicationContext,
onCreateStatusesFetcher(), accountKey, timelineFilter) {
errorLiveData.postValue(SingleResponse(it))
}
@ -412,10 +413,11 @@ abstract class AbsTimelineFragment : AbsContentRecyclerViewFragment<ParcelableSt
val loadLimit = preferences[loadItemLimitKey]
// We don't use dataController since it's not supported
dataController = null
val apiLiveData = ExceptionLiveData.wrap(provider.create(null, PagedList.Config.Builder()
val apiLiveData = ExceptionLiveData.wrap(LivePagedListBuilder(factory, PagedList.Config.Builder()
.setPageSize(loadLimit.coerceAtMost(maxLoadLimit))
.setInitialLoadSizeHint(loadLimit.coerceAtMost(maxLoadLimit))
.build()))
.build()).build())
merger.addSource(errorLiveData) {
merger.removeSource(apiLiveData)
merger.removeSource(errorLiveData)
@ -443,13 +445,13 @@ abstract class AbsTimelineFragment : AbsContentRecyclerViewFragment<ParcelableSt
extraSelection.first.addTo(expressions)
extraSelection.second?.addAllTo(expressionArgs)
}
val provider = CursorObjectLivePagedListProvider(context.contentResolver, contentUri,
val factory = CursorObjectDataSourceFactory(context.contentResolver, contentUri,
statusColumnsLite, Expression.and(*expressions.toTypedArray()).sql,
expressionArgs.toTypedArray(), Statuses.DEFAULT_SORT_ORDER,
ParcelableStatus::class.java)
dataController = provider.obtainDataController()
return ExceptionLiveData.wrap(provider.create(null, PagedList.Config.Builder()
.setPageSize(50).setEnablePlaceholders(false).build()))
// dataController = factory.obtainDataController()
return ExceptionLiveData.wrap(LivePagedListBuilder(factory, PagedList.Config.Builder()
.setPageSize(50).setEnablePlaceholders(false).build()).build())
}
private fun getFullStatus(position: Int): ParcelableStatus {

View File

@ -36,10 +36,10 @@ import android.support.v4.text.BidiFormatter
import com.squareup.otto.Bus
import okhttp3.Dns
import org.mariotaku.ktextension.isNullOrEmpty
import org.mariotaku.ktextension.mapToArray
import org.mariotaku.ktextension.toNulls
import org.mariotaku.sqliteqb.library.*
import org.mariotaku.sqliteqb.library.Columns.Column
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.sqliteqb.library.RawItemArray
import org.mariotaku.twidere.TwidereConstants.*
import org.mariotaku.twidere.annotation.CustomTabType
import org.mariotaku.twidere.annotation.ReadPositionTag
@ -54,6 +54,7 @@ import org.mariotaku.twidere.promise.UpdateStatusPromise
import org.mariotaku.twidere.provider.TwidereDataStore.*
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.SQLiteDatabaseWrapper.LazyLoadCallback
import org.mariotaku.twidere.util.Utils
import org.mariotaku.twidere.util.content.TwidereSQLiteOpenHelper
import org.mariotaku.twidere.util.database.CachedUsersQueryBuilder
import org.mariotaku.twidere.util.database.SuggestionsCursorCreator
@ -222,16 +223,41 @@ class TwidereDataProvider : ContentProvider(), LazyLoadCallback {
if (projection != null || selection != null || sortOrder != null) {
throw IllegalArgumentException()
}
var rawQuery = uri.lastPathSegment
if (limit != null) {
rawQuery = rawQuery.replace(PLACEHOLDER_LIMIT, limit)
}
val c = databaseWrapper.rawQuery(rawQuery, selectionArgs)
val c = databaseWrapper.rawQuery(uri.lastPathSegment, selectionArgs)
uri.getQueryParameter(QUERY_PARAM_NOTIFY_URI)?.let {
c?.setNotificationUri(context.contentResolver, Uri.parse(it))
}
return c
}
TableIds.MESSAGES_CONVERSATIONS -> if (projection?.contains(Messages.Conversations.UNREAD_COUNT) == true) {
val mappedProjection = projection.mapToArray(TwidereQueryBuilder::mapConversationsProjection)
val qb = SQLQueryBuilder.select(Columns(*mappedProjection))
qb.from(Table(Messages.Conversations.TABLE_NAME))
qb.join(Join(false, Join.Operation.LEFT_OUTER, Table(Messages.TABLE_NAME),
Expression.and(
Expression.equals(
Column(Table(Messages.Conversations.TABLE_NAME), Messages.Conversations.CONVERSATION_ID),
Column(Table(Messages.TABLE_NAME), Messages.CONVERSATION_ID)
),
Expression.equals(
Column(Table(Messages.Conversations.TABLE_NAME), Messages.Conversations.ACCOUNT_KEY),
Column(Table(Messages.TABLE_NAME), Messages.ACCOUNT_KEY))
)
))
if (selection != null) {
qb.where(Expression(selection))
}
qb.groupBy(Column(Table(Messages.TABLE_NAME), Messages.CONVERSATION_ID))
if (sortOrder != null) {
qb.orderBy(RawSQLLang(sortOrder))
}
if (limit != null) {
qb.limit(RawSQLLang(limit))
}
val c = databaseWrapper.rawQuery(qb.buildSQL(), selectionArgs)
c?.setNotificationUri(context.contentResolver, uri)
return c
}
}
if (table == null) return null
val c = databaseWrapper.query(table, projection, selection, selectionArgs,
@ -518,8 +544,6 @@ class TwidereDataProvider : ContentProvider(), LazyLoadCallback {
companion object {
const val PLACEHOLDER_LIMIT = "{limit}"
private fun getConflictAlgorithm(tableId: Int): Int {
when (tableId) {
TableIds.CACHED_HASHTAGS, TableIds.CACHED_STATUSES, TableIds.CACHED_USERS,

View File

@ -22,7 +22,6 @@ package org.mariotaku.twidere.util.text
import org.mariotaku.ktextension.mapToIntArray
import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.extension.model.textLimit
import org.mariotaku.twidere.extension.text.twitter.getTweetLength
import org.mariotaku.twidere.model.AccountDetails
import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.UserKey

View File

@ -20,10 +20,15 @@
package org.mariotaku.twidere.util.text
import com.twitter.Extractor
import com.twitter.Validator
import org.mariotaku.twidere.extension.text.twitter.extractReplyTextAndMentions
import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.UserKey
import java.text.Normalizer
object TwitterValidator : Validator() {
object TwitterValidator {
const val shortUrlLength = 23
const val shortUrlLengthHttps = 23
const val maxWeightedTweetLength: Int = 280
@ -38,7 +43,7 @@ object TwitterValidator : Validator() {
private val extractor = Extractor()
override fun getTweetLength(text: String): Int {
fun getTweetLength(text: String): Int {
val normalized = Normalizer.normalize(text, Normalizer.Form.NFC)
var weightedLength = 0
val inputLength = normalized.length
@ -60,20 +65,24 @@ object TwitterValidator : Validator() {
return length
}
override fun isValidTweet(text: String?): Boolean {
if (text == null || text.isEmpty()) {
return false
fun getTweetLength(text: String, ignoreMentions: Boolean, inReplyTo: ParcelableStatus?,
accountKey: UserKey? = inReplyTo?.account_key): Int {
if (!ignoreMentions || inReplyTo == null || accountKey == null) {
return getTweetLength(text)
}
for (c in text.toCharArray()) {
if (c == '\uFFFE' || c == '\uFEFF' || // BOM
c == '\uFFFF' || // Special
c in '\u202A'..'\u202E') { // Direction change
return false
}
}
val (_, replyText, _, _, _) = extractor.extractReplyTextAndMentions(text, inReplyTo,
accountKey)
return getTweetLength(replyText)
}
return getTweetLength(text) <= maxWeightedTweetLength
fun isValidTweet(text: String): Boolean {
// BOM: uFFFE, uFEFF
// Special : uFFFF
// Direction change [u202A, u202E]
return text.none { c ->
c == '\uFFFE' || c == '\uFEFF' || c == '\uFFFF' || c in '\u202A'..'\u202E'
} && getTweetLength(text) in 0..maxWeightedTweetLength
}
private fun weightForCodePoint(codePoint: Int): Int {