using telegram style conversation list ui
This commit is contained in:
parent
168abbd100
commit
c5dce143ed
|
@ -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'
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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<String>?): Cursor? {
|
||||
val rawUri = TwidereDataStore.CONTENT_URI_RAW_QUERY.buildUpon().appendPath(sql).build()
|
||||
val rawUri = TwidereQueryBuilder.rawQuery(sql)
|
||||
return query(rawUri, null, null, selectionArgs, null)
|
||||
}
|
||||
|
||||
|
|
|
@ -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<MessagesEntri
|
|||
|
||||
override fun onCreateLoader(id: Int, args: Bundle?): Loader<List<ParcelableMessageConversation>?> {
|
||||
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<MessagesEntri
|
|||
}
|
||||
}
|
||||
|
||||
private fun mapProjection(projection: String): Column = when (projection) {
|
||||
Conversations.UNREAD_COUNT -> 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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<SpanItem>?, quotedSpans: Array<SpanItem>?, source: String?, quotedSource: String?,
|
||||
retweetedByKey: UserKey?, quotedUserKey: UserKey?): Boolean {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/material_light_green"/>
|
||||
<corners android:radius="12dp"/>
|
||||
</shape>
|
|
@ -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"/>
|
||||
|
||||
|
||||
<RelativeLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_toEndOf="@+id/profileImage"
|
||||
android:layout_toRightOf="@+id/profileImage"
|
||||
android:divider="?android:dividerVertical"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/element_spacing_normal"
|
||||
|
@ -65,59 +65,92 @@
|
|||
android:paddingStart="0dp"
|
||||
android:paddingTop="@dimen/element_spacing_normal">
|
||||
|
||||
<org.mariotaku.twidere.view.NameView
|
||||
android:id="@+id/name"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_toLeftOf="@+id/time"
|
||||
android:layout_toStartOf="@+id/time"
|
||||
android:gravity="center_vertical"
|
||||
app:nv_primaryTextColor="?android:textColorPrimary"
|
||||
app:nv_primaryTextStyle="bold"
|
||||
app:nv_secondaryTextColor="?android:textColorSecondary"
|
||||
app:nv_twoLine="false"/>
|
||||
android:orientation="horizontal">
|
||||
|
||||
<org.mariotaku.twidere.view.ShortTimeView
|
||||
android:id="@+id/time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:drawablePadding="4dp"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
tools:text="12:00"/>
|
||||
<org.mariotaku.twidere.view.IconActionView
|
||||
android:id="@+id/typeIndicator"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginEnd="@dimen/element_spacing_small"
|
||||
android:layout_marginRight="@dimen/element_spacing_small"
|
||||
android:layout_weight="0"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_message_convertion_type_group"
|
||||
tools:tint="?android:textColorSecondary"/>
|
||||
|
||||
<org.mariotaku.twidere.view.TimelineContentTextView
|
||||
android:id="@+id/text"
|
||||
<org.mariotaku.twidere.view.NameView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
app:nv_primaryTextColor="?android:textColorPrimary"
|
||||
app:nv_primaryTextStyle="bold"
|
||||
app:nv_secondaryTextColor="?android:textColorSecondary"
|
||||
app:nv_twoLine="false"/>
|
||||
|
||||
<org.mariotaku.twidere.view.IconActionView
|
||||
android:id="@+id/stateIndicator"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginLeft="@dimen/element_spacing_small"
|
||||
android:layout_marginStart="@dimen/element_spacing_small"
|
||||
android:layout_weight="0"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_message_type_outgoing"
|
||||
tools:tint="?android:textColorSecondary"/>
|
||||
|
||||
<org.mariotaku.twidere.view.ShortTimeView
|
||||
android:id="@+id/time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0"
|
||||
android:drawablePadding="4dp"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
tools:text="12:00"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_below="@+id/name"
|
||||
android:layout_marginTop="@dimen/element_spacing_xsmall"
|
||||
android:layout_toEndOf="@+id/stateIndicator"
|
||||
android:layout_toRightOf="@+id/stateIndicator"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
tools:text="@string/sample_status_text"/>
|
||||
|
||||
<org.mariotaku.twidere.view.IconActionView
|
||||
android:id="@+id/stateIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/text"
|
||||
android:layout_alignLeft="@+id/name"
|
||||
android:layout_alignStart="@+id/name"
|
||||
android:layout_alignTop="@+id/text"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_activity_action_reply"/>
|
||||
</RelativeLayout>
|
||||
android:layout_marginTop="@dimen/element_spacing_small"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="24dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<org.mariotaku.twidere.view.TimelineContentTextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
tools:text="@string/sample_status_text"/>
|
||||
|
||||
<org.mariotaku.twidere.view.FixedTextView
|
||||
android:id="@+id/unreadCount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginLeft="@dimen/element_spacing_small"
|
||||
android:layout_marginStart="@dimen/element_spacing_small"
|
||||
android:layout_weight="0"
|
||||
android:background="@drawable/bg_message_unread_indicator"
|
||||
android:gravity="center"
|
||||
android:minWidth="24dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textStyle="bold"
|
||||
tools:text="9"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</org.mariotaku.twidere.view.ColorLabelRelativeLayout>
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>ic_message_convertion_type_group-mdpi</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Action-Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="ic_message_convertion_type_group-mdpi">
|
||||
<polygon id="Shape" points="0 0 16 0 16 16 0 16"></polygon>
|
||||
<path d="M10.6666667,7.33333333 C11.7733333,7.33333333 12.66,6.44 12.66,5.33333333 C12.66,4.22666667 11.7733333,3.33333333 10.6666667,3.33333333 C9.56,3.33333333 8.66666667,4.22666667 8.66666667,5.33333333 C8.66666667,6.44 9.56,7.33333333 10.6666667,7.33333333 Z M5.33333333,7.33333333 C6.44,7.33333333 7.32666667,6.44 7.32666667,5.33333333 C7.32666667,4.22666667 6.44,3.33333333 5.33333333,3.33333333 C4.22666667,3.33333333 3.33333333,4.22666667 3.33333333,5.33333333 C3.33333333,6.44 4.22666667,7.33333333 5.33333333,7.33333333 Z M5.33333333,8.66666667 C3.78,8.66666667 0.666666667,9.44666667 0.666666667,11 L0.666666667,12.6666667 L10,12.6666667 L10,11 C10,9.44666667 6.88666667,8.66666667 5.33333333,8.66666667 Z M10.6666667,8.66666667 C10.4733333,8.66666667 10.2533333,8.68 10.02,8.7 C10.7933333,9.26 11.3333333,10.0133333 11.3333333,11 L11.3333333,12.6666667 L15.3333333,12.6666667 L15.3333333,11 C15.3333333,9.44666667 12.22,8.66666667 10.6666667,8.66666667 Z" id="Shape" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>ic_message_type_outgoing-mdpi</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Action-Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="ic_message_type_outgoing-mdpi">
|
||||
<polygon id="Shape" points="0 0 16 0 16 16 0 16"></polygon>
|
||||
<polygon id="Shape" fill="#FFFFFF" fill-rule="nonzero" points="6 10.78 3.22 8 2.27333333 8.94 6 12.6666667 14 4.66666667 13.06 3.72666667"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 768 B |
Loading…
Reference in New Issue