updated paging library
This commit is contained in:
parent
77e997b6f2
commit
0be3d766f3
|
@ -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',
|
||||
]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
|
@ -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() {
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
|
@ -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 {
|
||||
|
|
|
@ -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>?) {
|
||||
|
|
|
@ -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>?) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue