using telegram style conversation list ui

This commit is contained in:
Mariotaku Lee 2017-02-17 16:31:29 +08:00
parent 168abbd100
commit c5dce143ed
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
17 changed files with 322 additions and 125 deletions

View File

@ -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'

View File

@ -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'])

View File

@ -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.

View File

@ -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;

View File

@ -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"

View File

@ -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() {

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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) {

View File

@ -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 {

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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