From c5dce143eda97f842e8e691284cf38c9d49d0b84 Mon Sep 17 00:00:00 2001 From: Mariotaku Lee Date: Fri, 17 Feb 2017 16:31:29 +0800 Subject: [PATCH] using telegram style conversation list ui --- build.gradle | 3 +- twidere.component.common/build.gradle | 4 +- .../model/ParcelableMessageConversation.java | 4 + .../twidere/provider/TwidereDataStore.java | 5 + twidere/build.gradle | 4 +- .../twidere/util/TwidereQueryBuilder.java | 7 + .../extension/ContentResolverExtensions.kt | 4 +- .../message/MessagesEntriesFragment.kt | 35 ++-- .../twidere/provider/TwidereDataProvider.kt | 4 +- .../task/twitter/message/GetMessagesTask.kt | 4 +- .../util/ContentNotificationManager.kt | 165 ++++++++++++------ .../util/database/FilterQueryBuilder.kt | 8 + .../holder/message/MessageEntryViewHolder.kt | 16 +- .../drawable/bg_message_unread_indicator.xml | 25 +++ .../res/layout/list_item_message_entry.xml | 133 ++++++++------ .../ic_message_convertion_type_group-mdpi.svg | 13 ++ .../ic_message_type_outgoing-mdpi.svg | 13 ++ 17 files changed, 322 insertions(+), 125 deletions(-) create mode 100644 twidere/src/main/res/drawable/bg_message_unread_indicator.xml create mode 100644 twidere/src/main/svg/drawable/ic_message_convertion_type_group-mdpi.svg create mode 100644 twidere/src/main/svg/drawable/ic_message_type_outgoing-mdpi.svg diff --git a/build.gradle b/build.gradle index 07b586caf..6e941b112 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,8 @@ subprojects { ext.android_support_lib_version = '25.1.1' ext.mariotaku_commons_library_version = '0.9.11' ext.mariotaku_restfu_version = '0.9.35' - ext.play_services_version = '10.0.1' + ext.mariotaku_object_cursor_version = '0.9.13-SNAPSHOT' + ext.play_services_version = '10.2.0' ext.crashlyrics_version = '2.6.6' ext.fabric_plugin_version = '1.22.0' ext.dropbox_core_sdk_version = '2.1.2' diff --git a/twidere.component.common/build.gradle b/twidere.component.common/build.gradle index 6dc0d1158..af3d3b6d3 100644 --- a/twidere.component.common/build.gradle +++ b/twidere.component.common/build.gradle @@ -40,13 +40,13 @@ android { dependencies { apt 'com.bluelinelabs:logansquare-compiler:1.3.7' apt 'com.hannesdorfmann.parcelableplease:processor:1.0.2' - apt 'com.github.mariotaku.ObjectCursor:processor:0.9.12' + apt "com.github.mariotaku.ObjectCursor:processor:$mariotaku_object_cursor_version" compile "com.android.support:support-annotations:$android_support_lib_version" compile 'com.bluelinelabs:logansquare:1.3.7' compile "com.github.mariotaku.RestFu:library:$mariotaku_restfu_version" compile "com.github.mariotaku.RestFu:oauth:$mariotaku_restfu_version" compile 'com.hannesdorfmann.parcelableplease:annotation:1.0.2' - compile 'com.github.mariotaku.ObjectCursor:core:0.9.12' + compile "com.github.mariotaku.ObjectCursor:core:$mariotaku_object_cursor_version" compile "com.github.mariotaku.CommonsLibrary:objectcursor:$mariotaku_commons_library_version" compile "com.github.mariotaku.CommonsLibrary:logansquare:$mariotaku_commons_library_version" compile fileTree(dir: 'libs', include: ['*.jar']) 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 cd5616b31..22e8ce498 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 @@ -158,6 +158,10 @@ public class ParcelableMessageConversation implements Parcelable { @CursorField(value = Conversations.LAST_READ_TIMESTAMP) public long last_read_timestamp; + @JsonField(name = "unread_count") + @CursorField(value = Conversations.UNREAD_COUNT, excludeWrite = true, excludeInfo = true) + public long unread_count; + /** * True if this is a temporary conversation, i.e. Created by user but haven't send any message * yet. 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 3511786ee..ea6de3d31 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 @@ -389,6 +389,11 @@ public interface TwidereDataStore { String CONVERSATION_EXTRAS = "conversation_extras"; String CONVERSATION_EXTRAS_TYPE = "conversation_extras_type"; + /** + * This column isn't available in database, you'll need to calculate by yourself instead + */ + String UNREAD_COUNT = "unread_count"; + String[] COLUMNS = ParcelableMessageConversationTableInfo.COLUMNS; String[] TYPES = ParcelableMessageConversationTableInfo.TYPES; diff --git a/twidere/build.gradle b/twidere/build.gradle index 7968bd38e..229742202 100644 --- a/twidere/build.gradle +++ b/twidere/build.gradle @@ -92,7 +92,7 @@ dependencies { kapt 'com.bluelinelabs:logansquare-compiler:1.3.7' kapt 'com.hannesdorfmann.parcelableplease:processor:1.0.2' kapt 'com.google.dagger:dagger-compiler:2.8' - kapt 'com.github.mariotaku.ObjectCursor:processor:0.9.12' + kapt "com.github.mariotaku.ObjectCursor:processor:$mariotaku_object_cursor_version" compile project(':twidere.component.common') compile project(':twidere.component.nyan') @@ -171,7 +171,7 @@ dependencies { compile 'com.github.mariotaku.MediaViewerLibrary:base:0.9.17' compile 'com.github.mariotaku.MediaViewerLibrary:subsample-image-view:0.9.17' compile 'com.github.mariotaku:SQLiteQB:0.9.10' - compile 'com.github.mariotaku.ObjectCursor:core:0.9.12' + compile "com.github.mariotaku.ObjectCursor:core:$mariotaku_object_cursor_version" compile 'com.github.mariotaku:MultiValueSwitch:0.9.7' compile 'com.github.mariotaku:AbstractTask:0.9.4' compile "com.github.mariotaku.CommonsLibrary:parcel:$mariotaku_commons_library_version" diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/TwidereQueryBuilder.java b/twidere/src/main/java/org/mariotaku/twidere/util/TwidereQueryBuilder.java index d585fa699..1d5dfaba1 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/TwidereQueryBuilder.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/TwidereQueryBuilder.java @@ -20,6 +20,8 @@ package org.mariotaku.twidere.util; +import android.net.Uri; + import org.mariotaku.sqliteqb.library.Columns; import org.mariotaku.sqliteqb.library.Columns.Column; import org.mariotaku.sqliteqb.library.Expression; @@ -31,6 +33,7 @@ import org.mariotaku.sqliteqb.library.Table; import org.mariotaku.sqliteqb.library.Tables; import org.mariotaku.sqliteqb.library.query.SQLSelectQuery; import org.mariotaku.twidere.model.UserKey; +import org.mariotaku.twidere.provider.TwidereDataStore; import org.mariotaku.twidere.provider.TwidereDataStore.CachedRelationships; import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers; @@ -40,6 +43,10 @@ import kotlin.Pair; public class TwidereQueryBuilder { + public static Uri rawQuery(String rawQuery) { + return TwidereDataStore.CONTENT_URI_RAW_QUERY.buildUpon().appendPath(rawQuery).build(); + } + public static final class CachedUsersQueryBuilder { private CachedUsersQueryBuilder() { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/ContentResolverExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/ContentResolverExtensions.kt index ebc322052..955154d65 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/ContentResolverExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/ContentResolverExtensions.kt @@ -22,11 +22,11 @@ package org.mariotaku.twidere.extension import android.annotation.SuppressLint import android.content.ContentResolver import android.database.Cursor -import org.mariotaku.twidere.provider.TwidereDataStore +import org.mariotaku.twidere.util.TwidereQueryBuilder @SuppressLint("Recycle") fun ContentResolver.rawQuery(sql: String, selectionArgs: Array?): Cursor? { - val rawUri = TwidereDataStore.CONTENT_URI_RAW_QUERY.buildUpon().appendPath(sql).build() + val rawUri = TwidereQueryBuilder.rawQuery(sql) return query(rawUri, null, null, selectionArgs, null) } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessagesEntriesFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessagesEntriesFragment.kt index e886bfa86..a013ceb1c 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessagesEntriesFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessagesEntriesFragment.kt @@ -27,8 +27,8 @@ import android.support.v4.content.Loader import com.squareup.otto.Subscribe import org.mariotaku.kpreferences.get import org.mariotaku.ktextension.toStringArray -import org.mariotaku.sqliteqb.library.Expression -import org.mariotaku.sqliteqb.library.OrderBy +import org.mariotaku.sqliteqb.library.* +import org.mariotaku.sqliteqb.library.Columns.Column import org.mariotaku.twidere.R import org.mariotaku.twidere.TwidereConstants.EXTRA_ACCOUNT_KEYS import org.mariotaku.twidere.TwidereConstants.REQUEST_SELECT_ACCOUNT @@ -46,11 +46,10 @@ import org.mariotaku.twidere.model.ParcelableMessageConversation import org.mariotaku.twidere.model.ParcelableMessageConversationCursorIndices import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.event.GetMessagesTaskEvent +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.DataStoreUtils -import org.mariotaku.twidere.util.ErrorInfoStore -import org.mariotaku.twidere.util.IntentUtils +import org.mariotaku.twidere.util.* import org.mariotaku.twidere.util.Utils /** @@ -85,12 +84,22 @@ 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 + val projection = (Conversations.COLUMNS + Conversations.UNREAD_COUNT).map { + mapProjection(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.equals( + Column(Table(Conversations.TABLE_NAME), Conversations.CONVERSATION_ID), + Column(Table(Messages.TABLE_NAME), Messages.CONVERSATION_ID) + ) + )) + 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))) + loader.uri = TwidereQueryBuilder.rawQuery(qb.buildSQL()) loader.selectionArgs = accountKeys.toStringArray() - loader.projection = Conversations.COLUMNS - loader.sortOrder = OrderBy(arrayOf(Conversations.LOCAL_TIMESTAMP, - Conversations.SORT_ID), booleanArrayOf(false, false)).sql return loader } @@ -177,5 +186,11 @@ class MessagesEntriesFragment : AbsContentListRecyclerViewFragment Column(SQLFunctions.COUNT( + "CASE WHEN ${Messages.TABLE_NAME}.${Messages.LOCAL_TIMESTAMP} > ${Conversations.TABLE_NAME}.${Conversations.LAST_READ_TIMESTAMP} THEN 1 ELSE NULL END" + ), projection) + else -> Column(Table(Conversations.TABLE_NAME), projection, projection) + } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/provider/TwidereDataProvider.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/provider/TwidereDataProvider.kt index a109082f9..b757ca0b8 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/provider/TwidereDataProvider.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/provider/TwidereDataProvider.kt @@ -537,11 +537,11 @@ class TwidereDataProvider : ContentProvider(), LazyLoadCallback { notifyUnreadCountChanged(NOTIFICATION_ID_INTERACTIONS_TIMELINE) } } - TABLE_ID_MESSAGES -> { + TABLE_ID_MESSAGES_CONVERSATIONS -> { val prefs = AccountPreferences.getNotificationEnabledPreferences(context, DataStoreUtils.getAccountKeys(context)) prefs.filter(AccountPreferences::isDirectMessagesNotificationEnabled).forEach { - // TODO show messages notifications + contentNotificationManager.showMessages(it) } notifyUnreadCountChanged(NOTIFICATION_ID_DIRECT_MESSAGES) } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/GetMessagesTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/GetMessagesTask.kt index 0989f2b96..31ef1efeb 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/GetMessagesTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/GetMessagesTask.kt @@ -435,8 +435,10 @@ class GetMessagesTask( ContentResolverUtils.bulkDelete(resolver, Messages.CONTENT_URI, Messages.CONVERSATION_ID, false, data.deleteConversations, accountWhere, accountWhereArgs) - ContentResolverUtils.bulkInsert(resolver, Conversations.CONTENT_URI, conversationsValues) + // Don't change order! insert messages first ContentResolverUtils.bulkInsert(resolver, Messages.CONTENT_URI, messagesValues) + // Notifications will show on conversations inserted + ContentResolverUtils.bulkInsert(resolver, Conversations.CONTENT_URI, conversationsValues) if (data.conversationRequestCursor != null) { resolver.update(Conversations.CONTENT_URI, ContentValues().apply { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/ContentNotificationManager.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/ContentNotificationManager.kt index 2fdc6ff2c..99125fc39 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/ContentNotificationManager.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/ContentNotificationManager.kt @@ -19,7 +19,9 @@ package org.mariotaku.twidere.util +import android.annotation.SuppressLint import android.app.PendingIntent +import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.SharedPreferences @@ -30,6 +32,8 @@ import android.support.v4.app.NotificationCompat import android.text.TextUtils import org.apache.commons.lang3.ArrayUtils import org.mariotaku.kpreferences.get +import org.mariotaku.ktextension.isEmpty +import org.mariotaku.ktextension.useCursor import org.mariotaku.microblog.library.twitter.model.Activity import org.mariotaku.sqliteqb.library.* import org.mariotaku.twidere.R @@ -40,11 +44,14 @@ import org.mariotaku.twidere.annotation.NotificationType import org.mariotaku.twidere.constant.IntentConstants import org.mariotaku.twidere.constant.iWantMyStarsBackKey import org.mariotaku.twidere.constant.nameFirstKey +import org.mariotaku.twidere.extension.model.getConversationName +import org.mariotaku.twidere.extension.model.getSummaryText import org.mariotaku.twidere.extension.rawQuery import org.mariotaku.twidere.model.* import org.mariotaku.twidere.model.util.ParcelableActivityUtils -import org.mariotaku.twidere.provider.TwidereDataStore.Activities -import org.mariotaku.twidere.provider.TwidereDataStore.Statuses +import org.mariotaku.twidere.provider.TwidereDataStore +import org.mariotaku.twidere.provider.TwidereDataStore.* +import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations import org.mariotaku.twidere.receiver.NotificationReceiver import org.mariotaku.twidere.util.database.FilterQueryBuilder import org.oshkimaadziig.george.androidutils.SpanFormatter @@ -67,7 +74,6 @@ class ContentNotificationManager( fun showTimeline(pref: AccountPreferences, minPositionKey: Long) { val accountKey = pref.accountKey val resources = context.resources - val nm = notificationManager val selection = Expression.and(Expression.equalsArgs(Statuses.ACCOUNT_KEY), Expression.greaterThan(Statuses.POSITION_KEY, minPositionKey)) val filteredSelection = buildStatusFilterWhereClause(preferences, Statuses.TABLE_NAME, @@ -75,16 +81,20 @@ class ContentNotificationManager( val selectionArgs = arrayOf(accountKey.toString()) val userProjection = arrayOf(Statuses.USER_KEY, Statuses.USER_NAME, Statuses.USER_SCREEN_NAME) val statusProjection = arrayOf(Statuses.POSITION_KEY) - val statusCursor = context.contentResolver.query(Statuses.CONTENT_URI, statusProjection, - filteredSelection.sql, selectionArgs, Statuses.DEFAULT_SORT_ORDER) ?: return + @SuppressLint("Recycle") + val statusCursor = context.contentResolver.query(Statuses.CONTENT_URI, statusProjection, + filteredSelection.sql, selectionArgs, Statuses.DEFAULT_SORT_ORDER) + + @SuppressLint("Recycle") val userCursor = context.contentResolver.rawQuery(SQLQueryBuilder.select(Columns(*userProjection)) .from(Table(Statuses.TABLE_NAME)) .where(filteredSelection) .groupBy(Columns.Column(Statuses.USER_KEY)) - .orderBy(OrderBy(Statuses.DEFAULT_SORT_ORDER)).buildSQL(), selectionArgs) ?: return + .orderBy(OrderBy(Statuses.DEFAULT_SORT_ORDER)).buildSQL(), selectionArgs) try { + if (statusCursor == null || userCursor == null) return val usersCount = userCursor.count val statusesCount = statusCursor.count if (statusesCount == 0 || usersCount == 0) return @@ -126,15 +136,16 @@ class ContentNotificationManager( builder.setCategory(NotificationCompat.CATEGORY_SOCIAL) applyNotificationPreferences(builder, pref, pref.homeTimelineNotificationType) try { - nm.notify("home_" + accountKey, Utils.getNotificationId(NOTIFICATION_ID_HOME_TIMELINE, accountKey), builder.build()) + val notificationId = Utils.getNotificationId(NOTIFICATION_ID_HOME_TIMELINE, accountKey) + notificationManager.notify("home", notificationId, builder.build()) Utils.sendPebbleNotification(context, null, notificationContent) } catch (e: SecurityException) { // Silently ignore } } finally { - statusCursor.close() - userCursor.close() + statusCursor?.close() + userCursor?.close() } } @@ -147,6 +158,7 @@ class ContentNotificationManager( Expression.greaterThanArgs(Activities.POSITION_KEY) ).sql val whereArgs = arrayOf(accountKey.toString(), position.toString()) + @SuppressLint("Recycle") val c = cr.query(Activities.AboutMe.CONTENT_URI, Activities.COLUMNS, where, whereArgs, OrderBy(Activities.TIMESTAMP, false).sql) ?: return val builder = NotificationCompat.Builder(context) @@ -166,59 +178,52 @@ class ContentNotificationManager( builder.setAutoCancel(true) style.setSummaryText(accountName) val ci = ParcelableActivityCursorIndices(c) - var messageLines = 0 var timestamp: Long = -1 - c.moveToPosition(-1) - while (c.moveToNext()) { - if (messageLines == 5) { - style.addLine(resources.getString(R.string.and_N_more, count - c.position)) - pebbleNotificationStringBuilder.append(resources.getString(R.string.and_N_more, count - c.position)) - break - } + val filteredUserIds = DataStoreUtils.getFilteredUserIds(context) + val remaining = c.forEachRow(5) { cur, idx -> + val activity = ci.newObject(c) - if (pref.isNotificationMentionsOnly && !ArrayUtils.contains(Activity.Action.MENTION_ACTIONS, - activity.action)) { - continue + if (pref.isNotificationMentionsOnly && activity.action !in Activity.Action.MENTION_ACTIONS) { + return@forEachRow false } - if (activity.status_id != null && FilterQueryBuilder.isFiltered(cr, - activity.status_user_key, activity.status_text_plain, - activity.status_quote_text_plain, activity.status_spans, - activity.status_quote_spans, activity.status_source, - activity.status_quote_source, activity.status_retweeted_by_user_key, - activity.status_quoted_user_key)) { - continue - } - val filteredUserIds = DataStoreUtils.getFilteredUserIds(context) - if (timestamp == -1L) { - timestamp = activity.timestamp + if (activity.status_id != null && FilterQueryBuilder.isFiltered(cr, activity)) { + return@forEachRow false } ParcelableActivityUtils.initAfterFilteredSourceIds(activity, filteredUserIds, pref.isNotificationFollowingOnly) val sources = ParcelableActivityUtils.getAfterFilteredSources(activity) - if (ArrayUtils.isEmpty(sources)) continue - val message = ActivityTitleSummaryMessage.get(context, - userColorNameManager, activity, sources, - 0, useStarForLikes, nameFirst) - if (message != null) { - val summary = message.summary - if (TextUtils.isEmpty(summary)) { - style.addLine(message.title) - pebbleNotificationStringBuilder.append(message.title) - pebbleNotificationStringBuilder.append("\n") - } else { - style.addLine(SpanFormatter.format(resources.getString(R.string.title_summary_line_format), - message.title, summary)) - pebbleNotificationStringBuilder.append(message.title) - pebbleNotificationStringBuilder.append(": ") - pebbleNotificationStringBuilder.append(message.summary) - pebbleNotificationStringBuilder.append("\n") - } - messageLines++ + + if (sources.isEmpty()) return@forEachRow false + + + if (timestamp == -1L) { + timestamp = activity.timestamp } + + val message = ActivityTitleSummaryMessage.get(context, userColorNameManager, + activity, sources, 0, useStarForLikes, nameFirst) ?: return@forEachRow false + val summary = message.summary + if (summary.isNullOrEmpty()) { + style.addLine(message.title) + pebbleNotificationStringBuilder.append(message.title) + pebbleNotificationStringBuilder.append("\n") + } else { + style.addLine(SpanFormatter.format(resources.getString(R.string.title_summary_line_format), + message.title, summary)) + pebbleNotificationStringBuilder.append(message.title) + pebbleNotificationStringBuilder.append(": ") + pebbleNotificationStringBuilder.append(summary) + pebbleNotificationStringBuilder.append("\n") + } + return@forEachRow true } - if (messageLines == 0) return - val displayCount = messageLines + count - c.position + if (remaining < 0) return + if (remaining > 0) { + style.addLine(resources.getString(R.string.and_N_more, count - c.position)) + pebbleNotificationStringBuilder.append(resources.getString(R.string.and_N_more, count - c.position)) + } + val displayCount = 5 + remaining val title = resources.getQuantityString(R.plurals.N_new_interactions, displayCount, displayCount) builder.setContentTitle(title) @@ -241,7 +246,63 @@ class ContentNotificationManager( } fun showMessages(pref: AccountPreferences) { + val accountKey = pref.accountKey + val cr = context.contentResolver + val selection = Expression.and(Expression.equalsArgs(Conversations.ACCOUNT_KEY), + Expression.lesserThan(Columns.Column(Conversations.LAST_READ_TIMESTAMP), + Columns.Column(Conversations.LOCAL_TIMESTAMP))).sql + val selectionArgs = arrayOf(accountKey.toString()) + @SuppressLint("Recycle") + val cur = cr.query(Conversations.CONTENT_URI, Conversations.COLUMNS, selection, selectionArgs, + OrderBy(Conversations.LOCAL_TIMESTAMP, false).sql) ?: return + try { + if (cur.isEmpty) return + val indices = ParcelableMessageConversationCursorIndices(cur) + val builder = NotificationCompat.Builder(context) + val accountName = DataStoreUtils.getAccountDisplayName(context, accountKey, nameFirst) + applyNotificationPreferences(builder, pref, pref.directMessagesNotificationType) + builder.setSmallIcon(R.drawable.ic_stat_message) + builder.setCategory(NotificationCompat.CATEGORY_SOCIAL) + builder.setAutoCancel(true) + val style = NotificationCompat.InboxStyle(builder) + style.setSummaryText(accountName) + val remaining = cur.forEachRow(5) { cur, pos -> + val conversation = indices.newObject(cur) + val title = conversation.getConversationName(context, userColorNameManager, nameFirst) + val summary = conversation.getSummaryText(context, userColorNameManager, nameFirst) + if (pos == 0) { + builder.setTicker("New notification") + builder.setContentTitle(title.first) + builder.setContentText(summary) + } + style.addLine(SpanFormatter.format(context.getString(R.string.title_summary_line_format), + title.first, summary)) + return@forEachRow true + } + if (remaining > 0) { + style.addLine(context.getString(R.string.and_N_more, remaining)) + } + val notificationId = Utils.getNotificationId(NOTIFICATION_ID_DIRECT_MESSAGES, accountKey) + notificationManager.notify("direct_messages", notificationId, builder.build()) + } finally { + cur.close() + } + } + /** + * @return Remaining count, -1 if no rows present + */ + private inline fun Cursor.forEachRow(limit: Int, action: (cur: Cursor, pos: Int) -> Boolean): Int { + moveToFirst() + var current = 0 + while (!isAfterLast) { + if (current >= limit) break + if (action(this, position)) { + current++ + } + moveToNext() + } + return count - position } private fun applyNotificationPreferences(builder: NotificationCompat.Builder, pref: AccountPreferences, defaultFlags: Int) { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/database/FilterQueryBuilder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/database/FilterQueryBuilder.kt index a1b8de09e..d57293068 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/database/FilterQueryBuilder.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/database/FilterQueryBuilder.kt @@ -21,6 +21,7 @@ package org.mariotaku.twidere.util.database import android.content.ContentResolver import org.mariotaku.twidere.extension.rawQuery +import org.mariotaku.twidere.model.ParcelableActivity import org.mariotaku.twidere.model.SpanItem import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.provider.TwidereDataStore.Filters @@ -32,6 +33,13 @@ import java.util.* object FilterQueryBuilder { + fun isFiltered(cr: ContentResolver, activity: ParcelableActivity): Boolean { + return isFiltered(cr, activity.status_user_key, activity.status_text_plain, + activity.status_quote_text_plain, activity.status_spans, activity.status_quote_spans, + activity.status_source, activity.status_quote_source, activity.status_retweeted_by_user_key, + activity.status_quoted_user_key) + } + fun isFiltered(cr: ContentResolver, userKey: UserKey?, textPlain: String?, quotedTextPlain: String?, spans: Array?, quotedSpans: Array?, source: String?, quotedSource: String?, retweetedByKey: UserKey?, quotedUserKey: UserKey?): Boolean { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/message/MessageEntryViewHolder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/message/MessageEntryViewHolder.kt index 442274e92..288d2c54f 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/message/MessageEntryViewHolder.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/message/MessageEntryViewHolder.kt @@ -42,7 +42,9 @@ class MessageEntryViewHolder(itemView: View, val adapter: MessagesEntriesAdapter private val name by lazy { itemView.name } private val text by lazy { itemView.text } private val profileImage by lazy { itemView.profileImage } + private val typeIndicator by lazy { itemView.typeIndicator } private val stateIndicator by lazy { itemView.stateIndicator } + private val unreadCount by lazy { itemView.unreadCount } init { setup() @@ -64,7 +66,7 @@ class MessageEntryViewHolder(itemView: View, val adapter: MessagesEntriesAdapter adapter.nameFirst) if (conversation.is_outgoing) { stateIndicator.visibility = View.VISIBLE - stateIndicator.setImageResource(R.drawable.ic_activity_action_reply) + stateIndicator.setImageResource(R.drawable.ic_message_type_outgoing) } else { stateIndicator.visibility = View.GONE } @@ -76,15 +78,23 @@ class MessageEntryViewHolder(itemView: View, val adapter: MessagesEntriesAdapter adapter.mediaLoader.displayProfileImage(profileImage, null) // TODO display default profile image } + typeIndicator.visibility = View.GONE } else { adapter.mediaLoader.displayGroupConversationAvatar(profileImage, conversation.conversation_avatar) + typeIndicator.visibility = View.VISIBLE + } + if (conversation.unread_count > 0) { + unreadCount.visibility = View.VISIBLE + unreadCount.text = conversation.unread_count.toString() + } else { + unreadCount.visibility = View.GONE } } private fun setup() { val textSize = adapter.textSize - name.setPrimaryTextSize(textSize * 1.1f) - name.setSecondaryTextSize(textSize) + name.setPrimaryTextSize(textSize * 1.05f) + name.setSecondaryTextSize(textSize * 0.95f) text.textSize = textSize time.textSize = textSize * 0.85f diff --git a/twidere/src/main/res/drawable/bg_message_unread_indicator.xml b/twidere/src/main/res/drawable/bg_message_unread_indicator.xml new file mode 100644 index 000000000..9248a3558 --- /dev/null +++ b/twidere/src/main/res/drawable/bg_message_unread_indicator.xml @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file diff --git a/twidere/src/main/res/layout/list_item_message_entry.xml b/twidere/src/main/res/layout/list_item_message_entry.xml index 374eee10b..d25cf4945 100644 --- a/twidere/src/main/res/layout/list_item_message_entry.xml +++ b/twidere/src/main/res/layout/list_item_message_entry.xml @@ -46,16 +46,16 @@ android:layout_marginLeft="@dimen/element_spacing_normal" android:layout_marginRight="@dimen/element_spacing_normal" android:layout_marginTop="@dimen/element_spacing_small" - android:contentDescription="@string/profile_image"/> + android:contentDescription="@string/profile_image" + tools:src="@drawable/ic_profile_image_default_group"/> - - + android:orientation="horizontal"> - + - + + + + + + + - - - + android:layout_marginTop="@dimen/element_spacing_small" + android:gravity="center_vertical" + android:minHeight="24dp" + android:orientation="horizontal"> + + + + + + \ No newline at end of file diff --git a/twidere/src/main/svg/drawable/ic_message_convertion_type_group-mdpi.svg b/twidere/src/main/svg/drawable/ic_message_convertion_type_group-mdpi.svg new file mode 100644 index 000000000..f9e309f4e --- /dev/null +++ b/twidere/src/main/svg/drawable/ic_message_convertion_type_group-mdpi.svg @@ -0,0 +1,13 @@ + + + + ic_message_convertion_type_group-mdpi + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/twidere/src/main/svg/drawable/ic_message_type_outgoing-mdpi.svg b/twidere/src/main/svg/drawable/ic_message_type_outgoing-mdpi.svg new file mode 100644 index 000000000..33e49c63c --- /dev/null +++ b/twidere/src/main/svg/drawable/ic_message_type_outgoing-mdpi.svg @@ -0,0 +1,13 @@ + + + + ic_message_type_outgoing-mdpi + Created with Sketch. + + + + + + + + \ No newline at end of file