From 7522bf04eb4b774ca4f80a923473c609465764f0 Mon Sep 17 00:00:00 2001 From: Mariotaku Lee Date: Sat, 11 Feb 2017 02:01:31 +0800 Subject: [PATCH] improved load more gesture --- .../twidere/model/ParcelableMessage.java | 9 +++- .../model/ParcelableMessageConversation.java | 6 +++ .../twidere/provider/TwidereDataStore.java | 2 + .../java/org/mariotaku/twidere/Constants.java | 2 +- .../mariotaku/ktextension/ArrayExtensions.kt | 6 ++- .../twidere/adapter/MessagesEntriesAdapter.kt | 27 ++++++++-- .../adapter/ParcelableActivitiesAdapter.kt | 12 ++--- .../AbsContentRecyclerViewFragment.kt | 11 ++-- .../fragment/MessagesEntriesFragment.kt | 18 +++++-- .../mariotaku/twidere/task/GetMessagesTask.kt | 53 +++++++++++++++---- .../twidere/util/ContentScrollHandler.kt | 15 +++--- .../mariotaku/twidere/util/DataStoreUtils.kt | 6 +++ .../util/content/TwidereSQLiteOpenHelper.kt | 25 ++++++--- .../view/holder/LoadIndicatorViewHolder.kt | 4 ++ 14 files changed, 155 insertions(+), 41 deletions(-) diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableMessage.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableMessage.java index fd7c95162..72a1c5382 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableMessage.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableMessage.java @@ -76,6 +76,10 @@ public class ParcelableMessage { @CursorField(Messages.LOCAL_TIMESTAMP) public long local_timestamp; + @JsonField(name = "sort_id") + @CursorField(value = Messages.SORT_ID) + public long sort_id; + @JsonField(name = "text_unescaped") @CursorField(Messages.TEXT_UNESCAPED) public String text_unescaped; @@ -119,9 +123,13 @@ public class ParcelableMessage { ", conversation_id='" + conversation_id + '\'' + ", message_type='" + message_type + '\'' + ", message_timestamp=" + message_timestamp + + ", local_timestamp=" + local_timestamp + + ", sort_id=" + sort_id + ", text_unescaped='" + text_unescaped + '\'' + ", media=" + Arrays.toString(media) + ", spans=" + Arrays.toString(spans) + + ", extras=" + extras + + ", internalExtras=" + internalExtras + ", sender_key=" + sender_key + ", recipient_key=" + recipient_key + ", is_outgoing=" + is_outgoing + @@ -129,7 +137,6 @@ public class ParcelableMessage { '}'; } - @OnPreJsonSerialize void beforeJsonSerialize() { internalExtras = InternalExtras.from(extras); diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableMessageConversation.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableMessageConversation.java index 2df854f2e..3e80e4934 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableMessageConversation.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableMessageConversation.java @@ -76,6 +76,10 @@ public class ParcelableMessageConversation { @CursorField(value = Conversations.LOCAL_TIMESTAMP) public long local_timestamp; + @JsonField(name = "sort_id") + @CursorField(value = Conversations.SORT_ID) + public long sort_id; + @JsonField(name = "text_unescaped") @CursorField(Conversations.TEXT_UNESCAPED) public String text_unescaped; @@ -121,11 +125,13 @@ public class ParcelableMessageConversation { return "ParcelableMessageConversation{" + "_id=" + _id + ", account_key=" + account_key + + ", account_color=" + account_color + ", id='" + id + '\'' + ", conversation_type='" + conversation_type + '\'' + ", message_type='" + message_type + '\'' + ", message_timestamp=" + message_timestamp + ", local_timestamp=" + local_timestamp + + ", sort_id=" + sort_id + ", text_unescaped='" + text_unescaped + '\'' + ", media=" + Arrays.toString(media) + ", spans=" + Arrays.toString(spans) + diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/provider/TwidereDataStore.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/provider/TwidereDataStore.java index 4f9c072f6..318a01f3e 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/provider/TwidereDataStore.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/provider/TwidereDataStore.java @@ -361,6 +361,7 @@ public interface TwidereDataStore { String MESSAGE_TYPE = "message_type"; String MESSAGE_TIMESTAMP = "message_timestamp"; String LOCAL_TIMESTAMP = "local_timestamp"; + String SORT_ID = "sort_id"; String TEXT_UNESCAPED = "text_unescaped"; String MEDIA = "media"; String SPANS = "spans"; @@ -384,6 +385,7 @@ public interface TwidereDataStore { String CONVERSATION_TYPE = "conversation_type"; String MESSAGE_TYPE = "message_type"; String MESSAGE_TIMESTAMP = "message_timestamp"; + String SORT_ID = "sort_id"; String LOCAL_TIMESTAMP = "local_timestamp"; String TEXT_UNESCAPED = "text_unescaped"; String MEDIA = "media"; diff --git a/twidere/src/main/java/org/mariotaku/twidere/Constants.java b/twidere/src/main/java/org/mariotaku/twidere/Constants.java index 203aed783..8259505ed 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/Constants.java +++ b/twidere/src/main/java/org/mariotaku/twidere/Constants.java @@ -34,7 +34,7 @@ import static org.mariotaku.twidere.annotation.PreferenceType.STRING; public interface Constants extends TwidereConstants { String DATABASES_NAME = "twidere.sqlite"; - int DATABASES_VERSION = 169; + int DATABASES_VERSION = 170; int EXTRA_FEATURES_NOTICE_VERSION = 0; diff --git a/twidere/src/main/kotlin/org/mariotaku/ktextension/ArrayExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/ktextension/ArrayExtensions.kt index 19c4814f2..c88ebb9cc 100644 --- a/twidere/src/main/kotlin/org/mariotaku/ktextension/ArrayExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/ktextension/ArrayExtensions.kt @@ -16,4 +16,8 @@ fun Array<*>?.isNullOrEmpty(): Boolean { fun Array.toNulls(): Array { @Suppress("UNCHECKED_CAST") return this as Array -} \ No newline at end of file +} + +fun Array.toStringArray(): Array { + return Array(size) { this[it].toString() } +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/MessagesEntriesAdapter.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/MessagesEntriesAdapter.kt index 9e524bcef..e79a13b1f 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/MessagesEntriesAdapter.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/MessagesEntriesAdapter.kt @@ -5,8 +5,10 @@ import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.ViewGroup import org.mariotaku.twidere.adapter.iface.IItemCountsAdapter +import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter import org.mariotaku.twidere.model.ItemCounts import org.mariotaku.twidere.model.ParcelableMessageConversation +import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder import org.mariotaku.twidere.view.holder.message.MessageEntryViewHolder /** @@ -15,7 +17,7 @@ import org.mariotaku.twidere.view.holder.message.MessageEntryViewHolder class MessagesEntriesAdapter(context: Context) : LoadMoreSupportAdapter(context), IItemCountsAdapter { - override val itemCounts: ItemCounts = ItemCounts(1) + override val itemCounts: ItemCounts = ItemCounts(2) var conversations: List? = null set(value) { @@ -33,6 +35,7 @@ class MessagesEntriesAdapter(context: Context) : LoadMoreSupportAdapter { + val itemView = inflater.inflate(MessageEntryViewHolder.layoutResource, parent, false) + return MessageEntryViewHolder(itemView, this) + } + ITEM_VIEW_TYPE_LOAD_INDICATOR -> { + val itemView = inflater.inflate(LoadIndicatorViewHolder.layoutResource, parent, false) + return LoadIndicatorViewHolder(itemView) + + } + } + throw UnsupportedOperationException() } override fun getItemViewType(position: Int): Int { - return ITEM_TYPE_MESSAGE_ENTRY + when (itemCounts.getItemCountIndex(position)) { + 0 -> return ITEM_TYPE_MESSAGE_ENTRY + 1 -> return ITEM_VIEW_TYPE_LOAD_INDICATOR + } + throw UnsupportedOperationException() } fun getConversation(position: Int): ParcelableMessageConversation? { @@ -67,7 +84,7 @@ class MessagesEntriesAdapter(context: Context) : LoadMoreSupportAdapter(), LoaderManager.LoaderCallbacks?>, MessagesEntriesAdapter.MessageConversationClickListener { - private val accountKeys: Array - get() = Utils.getAccountKeys(context, arguments) ?: DataStoreUtils.getActivatedAccountKeys(context) + private val accountKeys: Array by lazy { + Utils.getAccountKeys(context, arguments) ?: DataStoreUtils.getActivatedAccountKeys(context) + } private val errorInfoKey: String = ErrorInfoStore.KEY_DIRECT_MESSAGES @@ -55,6 +58,8 @@ class MessagesEntriesFragment : AbsContentListRecyclerViewFragment?> { val loader = ObjectCursorLoader(context, ParcelableMessageConversationCursorIndices::class.java) loader.uri = Conversations.CONTENT_URI + loader.selection = Expression.inArgs(Conversations.ACCOUNT_KEY, accountKeys.size).sql + loader.selectionArgs = accountKeys.toStringArray() loader.projection = Conversations.COLUMNS loader.sortOrder = OrderBy(Conversations.LOCAL_TIMESTAMP, false).sql return loader @@ -67,6 +72,7 @@ class MessagesEntriesFragment : AbsContentListRecyclerViewFragment?>?, data: List?) { adapter.conversations = data adapter.drawAccountColors = accountKeys.size > 1 + setLoadMoreIndicatorPosition(ILoadMoreSupportAdapter.NONE) showContentOrError() } @@ -83,7 +89,13 @@ class MessagesEntriesFragment : AbsContentListRecyclerViewFragment Array - ) : SimpleRefreshTaskParam() { + context: Context, + getAccountKeys: () -> Array + ) : RefreshMessagesTaskParam(context, getAccountKeys) { - private val accounts by lazy { - AccountUtils.getAllAccountDetails(AccountManager.get(context), accountKeys, false) - } - - override val accountKeys: Array - get() = getAccountKeys() override val sinceIds: Array? get() { @@ -252,5 +246,46 @@ class GetMessagesTask(context: Context) : BaseAbstractTask Array + ) : RefreshMessagesTaskParam(context, getAccountKeys) { + + + override val maxIds: Array? + get() { + val keys = accounts.map { account -> + when (account?.type) { + AccountType.FANFOU -> { + return@map null + } + } + return@map account?.key + }.toTypedArray() + val incomingIds = DataStoreUtils.getOldestMessageIds(context, Messages.CONTENT_URI, + keys, false) + val outgoingIds = DataStoreUtils.getOldestMessageIds(context, Messages.CONTENT_URI, + keys, true) + return incomingIds + outgoingIds + } + + override val hasSinceIds: Boolean = false + override val hasMaxIds: Boolean = true + } + + open class RefreshMessagesTaskParam( + val context: Context, + val getAccountKeys: () -> Array + ) : SimpleRefreshTaskParam() { + + protected val accounts: Array by lazy { + AccountUtils.getAllAccountDetails(AccountManager.get(context), accountKeys, false) + } + + override final val accountKeys: Array + get() = getAccountKeys() + + } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/ContentScrollHandler.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/ContentScrollHandler.kt index 046d9b805..5aaf5778f 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/ContentScrollHandler.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/ContentScrollHandler.kt @@ -74,10 +74,10 @@ open class ContentScrollHandler( if (!contentListSupport.refreshing && adapter.loadMoreSupportedPosition != ILoadMoreSupportAdapter.NONE && adapter.loadMoreIndicatorPosition == ILoadMoreSupportAdapter.NONE) { var position: Long = 0 - if (contentListSupport.reachingEnd && scrollDirection > 0) { + if (contentListSupport.reachingEnd && scrollDirection < 0) { position = position or ILoadMoreSupportAdapter.END } - if (contentListSupport.reachingStart && scrollDirection < 0) { + if (contentListSupport.reachingStart && scrollDirection > 0) { position = position or ILoadMoreSupportAdapter.START } resetScrollDirection() @@ -133,13 +133,16 @@ open class ContentScrollHandler( lastY = Float.NaN } MotionEvent.ACTION_MOVE -> { - if (!java.lang.Float.isNaN(lastY)) { - val delta = lastY - event.rawY - listener.setScrollDirection(if (delta < 0) -1 else 1) + if (lastY.isNaN()) { + lastY = event.y } else { - lastY = event.rawY + val delta = event.y - lastY + listener.setScrollDirection(if (delta < 0) -1 else 1) } } + MotionEvent.ACTION_UP -> { + lastY = Float.NaN + } } return false } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt index 9a9ae99b9..41ed73733 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt @@ -171,6 +171,12 @@ object DataStoreUtils { OrderBy(SQLFunctions.MAX(Messages.LOCAL_TIMESTAMP)), having, null) } + fun getOldestMessageIds(context: Context, uri: Uri, accountKeys: Array, outgoing: Boolean): Array { + val having: Expression = Expression.equals(Messages.IS_OUTGOING, if (outgoing) 1 else 0) + return getStringFieldArray(context, uri, accountKeys, Messages.ACCOUNT_KEY, Messages.MESSAGE_ID, + OrderBy(SQLFunctions.MIN(Messages.LOCAL_TIMESTAMP)), having, null) + } + fun getNewestStatusSortIds(context: Context, uri: Uri, accountKeys: Array): LongArray { return getLongFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.SORT_ID, diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/content/TwidereSQLiteOpenHelper.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/content/TwidereSQLiteOpenHelper.kt index 813974cc4..3a2734814 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/content/TwidereSQLiteOpenHelper.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/content/TwidereSQLiteOpenHelper.kt @@ -37,6 +37,7 @@ import org.mariotaku.twidere.model.Tab import org.mariotaku.twidere.model.TabValuesCreator import org.mariotaku.twidere.model.tab.TabConfiguration import org.mariotaku.twidere.provider.TwidereDataStore.* +import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations import org.mariotaku.twidere.util.content.DatabaseUpgradeHelper.safeUpgrade import org.mariotaku.twidere.util.migrateAccounts import java.util.* @@ -79,9 +80,10 @@ class TwidereSQLiteOpenHelper( db.endTransaction() db.beginTransaction() - db.execSQL(createTable(Messages.TABLE_NAME, Messages.COLUMNS, Messages.TYPES, true)) - db.execSQL(createTable(Messages.Conversations.TABLE_NAME, Messages.Conversations.COLUMNS, - Messages.Conversations.TYPES, true)) + db.execSQL(createTable(Messages.TABLE_NAME, Messages.COLUMNS, Messages.TYPES, true, + messagesConstraint())) + db.execSQL(createTable(Conversations.TABLE_NAME, Conversations.COLUMNS, + Conversations.TYPES, true, messageConversationsConstraint())) db.setTransactionSuccessful() db.endTransaction() @@ -101,6 +103,7 @@ class TwidereSQLiteOpenHelper( setupDefaultTabs(db) } + private fun setupDefaultTabs(db: SQLiteDatabase) { db.beginTransaction() @CustomTabType @@ -239,9 +242,10 @@ class TwidereSQLiteOpenHelper( safeUpgrade(db, SavedSearches.TABLE_NAME, SavedSearches.COLUMNS, SavedSearches.TYPES, true, null) // DM columns - safeUpgrade(db, Messages.TABLE_NAME, Messages.COLUMNS, Messages.TYPES, true, null) - safeUpgrade(db, Messages.Conversations.TABLE_NAME, Messages.Conversations.COLUMNS, - Messages.Conversations.TYPES, true, null) + safeUpgrade(db, Messages.TABLE_NAME, Messages.COLUMNS, Messages.TYPES, true, null, + messagesConstraint()) + safeUpgrade(db, Conversations.TABLE_NAME, Conversations.COLUMNS, + Conversations.TYPES, true, null, messageConversationsConstraint()) if (oldVersion < 131) { migrateFilteredUsers(db) @@ -312,4 +316,13 @@ class TwidereSQLiteOpenHelper( return qb.buildSQL() } + private fun messagesConstraint(): Constraint { + return Constraint.unique("unique_message", Columns(Messages.ACCOUNT_KEY, Messages.CONVERSATION_ID, + Messages.MESSAGE_ID), OnConflict.REPLACE) + } + + private fun messageConversationsConstraint(): Constraint { + return Constraint.unique("unique_message_conversations", Columns(Conversations.ACCOUNT_KEY, + Conversations.CONVERSATION_ID), OnConflict.REPLACE) + } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/LoadIndicatorViewHolder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/LoadIndicatorViewHolder.kt index df79e4db2..5f66f6130 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/LoadIndicatorViewHolder.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/LoadIndicatorViewHolder.kt @@ -22,6 +22,7 @@ package org.mariotaku.twidere.view.holder import android.support.v7.widget.RecyclerView import android.view.View import kotlinx.android.synthetic.main.list_item_load_indicator.view.* +import org.mariotaku.twidere.R /** * Created by mariotaku on 14/11/19. @@ -32,4 +33,7 @@ class LoadIndicatorViewHolder(view: View) : RecyclerView.ViewHolder(view) { fun setLoadProgressVisible(visible: Boolean) { loadProgress.visibility = if (visible) View.VISIBLE else View.GONE } + companion object { + const val layoutResource = R.layout.list_item_load_indicator + } }