Brighten light theme

1) Brighten background color

2) Add unread indicator in conversation list

3) Eliminate some conversation list overdraw
This commit is contained in:
Moxie Marlinspike 2017-11-13 18:01:05 -08:00
parent 03573df00f
commit 534dec282f
26 changed files with 151 additions and 156 deletions

View File

@ -5,7 +5,6 @@
<item> <item>
<selector> <selector>
<item android:drawable="@color/textsecure_primary_alpha33" android:state_selected="true" /> <item android:drawable="@color/textsecure_primary_alpha33" android:state_selected="true" />
<item android:drawable="@color/conversation_list_item_background_read_dark" />
</selector> </selector>
</item> </item>
</ripple> </ripple>

View File

@ -5,7 +5,6 @@
<item> <item>
<selector> <selector>
<item android:drawable="@color/textsecure_primary_alpha33" android:state_selected="true" /> <item android:drawable="@color/textsecure_primary_alpha33" android:state_selected="true" />
<item android:drawable="@color/conversation_list_item_background_read_light" />
</selector> </selector>
</item> </item>
</ripple> </ripple>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/textsecure_primary">
<item android:id="@android:id/mask" android:drawable="@android:color/black" />
<item>
<selector>
<item android:drawable="@color/textsecure_primary_alpha33" android:state_selected="true" />
<item android:drawable="@color/conversation_list_item_background_unread_light" />
</selector>
</item>
</ripple>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/textsecure_primary">
<item android:id="@android:id/mask" android:drawable="@android:color/black" />
<item>
<selector>
<item android:drawable="@color/textsecure_primary_alpha33" android:state_selected="true" />
<item android:drawable="@color/conversation_list_item_background_unread_dark" />
</selector>
</item>
</ripple>

View File

@ -3,5 +3,4 @@
<item android:drawable="@color/textsecure_primary_alpha33" android:state_selected="true" /> <item android:drawable="@color/textsecure_primary_alpha33" android:state_selected="true" />
<item android:drawable="@color/textsecure_primary_alpha33" android:state_pressed="true" /> <item android:drawable="@color/textsecure_primary_alpha33" android:state_pressed="true" />
<item android:drawable="@color/signal_primary_alpha_focus" android:state_focused="true" /> <item android:drawable="@color/signal_primary_alpha_focus" android:state_focused="true" />
<item android:drawable="@color/conversation_list_item_background_read_dark" />
</selector> </selector>

View File

@ -3,5 +3,4 @@
<item android:drawable="@color/textsecure_primary_alpha33" android:state_selected="true" /> <item android:drawable="@color/textsecure_primary_alpha33" android:state_selected="true" />
<item android:drawable="@color/textsecure_primary_alpha33" android:state_pressed="true" /> <item android:drawable="@color/textsecure_primary_alpha33" android:state_pressed="true" />
<item android:drawable="@color/signal_primary_alpha_focus" android:state_focused="true" /> <item android:drawable="@color/signal_primary_alpha_focus" android:state_focused="true" />
<item android:drawable="@color/conversation_list_item_background_read_light" />
</selector> </selector>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/textsecure_primary_alpha33" android:state_selected="true" />
<item android:drawable="@color/textsecure_primary_alpha33" android:state_pressed="true" />
<item android:drawable="@color/signal_primary_alpha_focus" android:state_focused="true" />
<item android:drawable="@color/conversation_list_item_background_unread_light" />
</selector>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/textsecure_primary_alpha33" android:state_selected="true" />
<item android:drawable="@color/textsecure_primary_alpha33" android:state_pressed="true" />
<item android:drawable="@color/signal_primary_alpha_focus" android:state_focused="true" />
<item android:drawable="@color/conversation_list_item_background_unread_dark" />
</selector>

View File

@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<android.support.v7.widget.RecyclerView <android.support.v7.widget.RecyclerView
android:id="@android:id/list" android:id="@android:id/list"

View File

@ -10,7 +10,6 @@
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:clickable="true" android:clickable="true"
android:background="?android:windowBackground"
android:padding="5dp" android:padding="5dp"
android:clipChildren="false" android:clipChildren="false"
android:clipToPadding="false"> android:clipToPadding="false">

View File

@ -3,6 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:background="?attr/conversation_list_item_background"
android:layout_width="match_parent" android:layout_width="match_parent"
android:focusable="true" android:focusable="true"
android:nextFocusRight="@+id/fab" android:nextFocusRight="@+id/fab"
@ -144,5 +145,13 @@
android:layout_alignWithParentIfMissing="true" android:layout_alignWithParentIfMissing="true"
app:iconColor="?attr/conversation_list_item_subject_color"/> app:iconColor="?attr/conversation_list_item_subject_color"/>
<ImageView android:id="@+id/unread_indicator"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_below="@id/date"
android:layout_toLeftOf="@id/archived"
android:layout_toStartOf="@id/archived"
android:layout_alignWithParentIfMissing="true"/>
</RelativeLayout> </RelativeLayout>
</org.thoughtcrime.securesms.ConversationListItem> </org.thoughtcrime.securesms.ConversationListItem>

View File

@ -2,10 +2,7 @@
<resources> <resources>
<attr name="theme_type" format="string"/> <attr name="theme_type" format="string"/>
<attr name="attachment_type_selector_background" format="color"/> <attr name="attachment_type_selector_background" format="color"/>
<attr name="conversation_list_item_background_selected" format="reference"/> <attr name="conversation_list_item_background" format="reference"/>
<attr name="conversation_list_item_background_read" format="reference"/>
<attr name="conversation_list_item_background_unread" format="reference"/>
<attr name="conversation_list_item_count_color" format="reference|color"/>
<attr name="conversation_list_item_contact_color" format="reference|color"/> <attr name="conversation_list_item_contact_color" format="reference|color"/>
<attr name="conversation_list_item_subject_color" format="reference|color"/> <attr name="conversation_list_item_subject_color" format="reference|color"/>
<attr name="conversation_list_item_date_color" format="reference|color"/> <attr name="conversation_list_item_date_color" format="reference|color"/>

View File

@ -27,14 +27,6 @@
<color name="conversation_compose_divider">#32000000</color> <color name="conversation_compose_divider">#32000000</color>
<color name="conversation_list_item_background_read_light">@color/gray5</color>
<color name="conversation_list_item_background_unread_light">#ffffffff</color>
<color name="conversation_list_item_background_read_dark">#ff000000</color>
<color name="conversation_list_item_background_unread_dark">#ff333333</color>
<!--<color name="conversation_list_divider_light">#15000000</color>-->
<!--<color name="conversation_list_divider_dark">#22ffffff</color>-->
<color name="textsecure_holo_blue_light">#ff33b5e5</color> <color name="textsecure_holo_blue_light">#ff33b5e5</color>
<color name="dark_action_bar">#ff111111</color> <color name="dark_action_bar">#ff111111</color>

View File

@ -110,15 +110,12 @@
<item name="colorAccent">@color/textsecure_primary_dark</item> <item name="colorAccent">@color/textsecure_primary_dark</item>
<item name="colorControlActivated">@color/signal_primary</item> <item name="colorControlActivated">@color/signal_primary</item>
<item name="colorControlHighlight">@color/signal_primary</item> <item name="colorControlHighlight">@color/signal_primary</item>
<item name="android:windowBackground">@color/gray5</item> <item name="android:windowBackground">@color/white</item>
<item name="alertDialogTheme">@style/AppCompatAlertDialogStyleLight</item> <item name="alertDialogTheme">@style/AppCompatAlertDialogStyleLight</item>
<item name="android:alertDialogTheme">@style/AppCompatDialogStyleLight</item> <item name="android:alertDialogTheme">@style/AppCompatDialogStyleLight</item>
<!--<item name="android:windowContentOverlay">@drawable/compat_actionbar_shadow_background</item>--> <!--<item name="android:windowContentOverlay">@drawable/compat_actionbar_shadow_background</item>-->
<item name="attachment_type_selector_background">@color/white</item> <item name="attachment_type_selector_background">@color/white</item>
<item name="conversation_list_item_background_selected">@drawable/list_selected_holo_light</item> <item name="conversation_list_item_background">@drawable/conversation_list_item_background</item>
<item name="conversation_list_item_background_unread">@drawable/conversation_list_item_unread_background</item>
<item name="conversation_list_item_background_read">@drawable/conversation_list_item_read_background</item>
<item name="conversation_list_item_count_color">#66333333</item>
<item name="conversation_list_item_contact_color">#FF333333</item> <item name="conversation_list_item_contact_color">#FF333333</item>
<item name="conversation_list_item_subject_color">#FF444444</item> <item name="conversation_list_item_subject_color">#FF444444</item>
<item name="conversation_list_item_date_color">#ff999999</item> <item name="conversation_list_item_date_color">#ff999999</item>
@ -129,7 +126,7 @@
<item name="conversation_group_member_name">#99000000</item> <item name="conversation_group_member_name">#99000000</item>
<item name="conversation_background">#eeeeee</item> <item name="conversation_background">@color/gray5</item>
<item name="conversation_editor_background">#22000000</item> <item name="conversation_editor_background">#22000000</item>
<item name="conversation_editor_text_color">#ff111111</item> <item name="conversation_editor_text_color">#ff111111</item>
<item name="conversation_transport_sms_indicator">@drawable/ic_send_sms_insecure</item> <item name="conversation_transport_sms_indicator">@drawable/ic_send_sms_insecure</item>
@ -240,10 +237,7 @@
<item name="android:windowBackground">@color/black</item> <item name="android:windowBackground">@color/black</item>
<item name="alertDialogTheme">@style/AppCompatAlertDialogStyleDark</item> <item name="alertDialogTheme">@style/AppCompatAlertDialogStyleDark</item>
<item name="android:alertDialogTheme">@style/AppCompatDialogStyleDark</item> <item name="android:alertDialogTheme">@style/AppCompatDialogStyleDark</item>
<item name="conversation_list_item_background_selected">@drawable/list_selected_holo_dark</item> <item name="conversation_list_item_background">@drawable/conversation_list_item_background_dark</item>
<item name="conversation_list_item_background_unread">@drawable/conversation_list_item_unread_background_dark</item>
<item name="conversation_list_item_background_read">@drawable/conversation_list_item_read_background_dark</item>
<item name="conversation_list_item_count_color">#66dddddd</item>
<item name="conversation_list_item_contact_color">#ffdddddd</item> <item name="conversation_list_item_contact_color">#ffdddddd</item>
<item name="conversation_list_item_subject_color">#ffdddddd</item> <item name="conversation_list_item_subject_color">#ffdddddd</item>
<item name="conversation_list_item_date_color">#ffdddddd</item> <item name="conversation_list_item_date_color">#ffdddddd</item>
@ -277,7 +271,7 @@
<item name="fab_color">@color/textsecure_primary_dark</item> <item name="fab_color">@color/textsecure_primary_dark</item>
<item name="lower_right_divet">@drawable/divet_lower_right_light</item> <item name="lower_right_divet">@drawable/divet_lower_right_light</item>
<item name="conversation_background">#22ffffff</item> <item name="conversation_background">@color/black</item>
<item name="conversation_editor_background">#22ffffff</item> <item name="conversation_editor_background">#22ffffff</item>
<item name="conversation_editor_text_color">#ffeeeeee</item> <item name="conversation_editor_text_color">#ffeeeeee</item>
<item name="conversation_transport_sms_indicator">@drawable/ic_send_sms_insecure_dark</item> <item name="conversation_transport_sms_indicator">@drawable/ic_send_sms_insecure_dark</item>

View File

@ -265,6 +265,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY); supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY);
setContentView(R.layout.conversation_activity); setContentView(R.layout.conversation_activity);
TypedArray typedArray = obtainStyledAttributes(new int[] {R.attr.conversation_background});
int color = typedArray.getColor(0, Color.WHITE);
typedArray.recycle();
getWindow().getDecorView().setBackgroundColor(color);
fragment = initFragment(R.id.fragment_content, new ConversationFragment(), fragment = initFragment(R.id.fragment_content, new ConversationFragment(),
masterSecret, dynamicLanguage.getCurrentLocale()); masterSecret, dynamicLanguage.getCurrentLocale());

View File

@ -240,9 +240,7 @@ public class ConversationItem extends LinearLayout
} }
private void initializeAttributes() { private void initializeAttributes() {
final int[] attributes = new int[] {R.attr.conversation_item_bubble_background, final int[] attributes = new int[] {R.attr.conversation_item_bubble_background};
R.attr.conversation_list_item_background_selected,
R.attr.conversation_item_background};
final TypedArray attrs = context.obtainStyledAttributes(attributes); final TypedArray attrs = context.obtainStyledAttributes(attributes);
defaultBubbleColor = attrs.getColor(0, Color.WHITE); defaultBubbleColor = attrs.getColor(0, Color.WHITE);

View File

@ -1,5 +1,5 @@
/** /*
* Copyright (C) 2014 Open Whisper Systems * Copyright (C) 2014-2017 Open Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -31,7 +31,6 @@ import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.Toast; import android.widget.Toast;
@ -117,9 +116,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
} }
private void initializeSearchListener() { private void initializeSearchListener() {
searchAction.setOnClickListener(v -> { searchAction.setOnClickListener(v -> searchToolbar.display(searchAction.getX() + (searchAction.getWidth() / 2),
searchToolbar.display(searchAction.getX() + (searchAction.getWidth() / 2), searchAction.getY() + (searchAction.getHeight() / 2)); searchAction.getY() + (searchAction.getHeight() / 2)));
});
searchToolbar.setListener(new SearchToolbar.SearchListener() { searchToolbar.setListener(new SearchToolbar.SearchListener() {
@Override @Override
@ -235,12 +233,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
super.onChange(selfChange); super.onChange(selfChange);
Log.w(TAG, "Detected android contact data changed, refreshing cache"); Log.w(TAG, "Detected android contact data changed, refreshing cache");
Recipient.clearCache(ConversationListActivity.this); Recipient.clearCache(ConversationListActivity.this);
ConversationListActivity.this.runOnUiThread(new Runnable() { ConversationListActivity.this.runOnUiThread(() -> fragment.getListAdapter().notifyDataSetChanged());
@Override
public void run() {
fragment.getListAdapter().notifyDataSetChanged();
}
});
} }
}; };

View File

@ -130,7 +130,7 @@ public class ConversationListFragment extends Fragment
TypedArray typedArray = getContext().obtainStyledAttributes(new int[]{R.attr.conversation_list_item_divider}); TypedArray typedArray = getContext().obtainStyledAttributes(new int[]{R.attr.conversation_list_item_divider});
Drawable itemDrawable = typedArray.getDrawable(0); Drawable itemDrawable = typedArray.getDrawable(0);
DividerItemDecoration itemDecoration = new DividerItemDecoration(getActivity(), LinearLayoutManager.VERTICAL); DividerItemDecoration itemDecoration = new DividerItemDecoration(getActivity(), LinearLayoutManager.VERTICAL);
itemDecoration.setDrawable(itemDrawable); if (itemDrawable != null) itemDecoration.setDrawable(itemDrawable);
list.addItemDecoration(itemDecoration); list.addItemDecoration(itemDecoration);
typedArray.recycle(); typedArray.recycle();
@ -473,7 +473,7 @@ public class ConversationListFragment extends Fragment
@Override @Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
final long threadId = ((ConversationListItem)viewHolder.itemView).getThreadId(); final long threadId = ((ConversationListItem)viewHolder.itemView).getThreadId();
final boolean read = ((ConversationListItem)viewHolder.itemView).getRead(); final int unreadCount = ((ConversationListItem)viewHolder.itemView).getUnreadCount();
if (archive) { if (archive) {
new SnackbarAsyncTask<Long>(getView(), new SnackbarAsyncTask<Long>(getView(),
@ -503,7 +503,7 @@ public class ConversationListFragment extends Fragment
protected void executeAction(@Nullable Long parameter) { protected void executeAction(@Nullable Long parameter) {
DatabaseFactory.getThreadDatabase(getActivity()).archiveConversation(threadId); DatabaseFactory.getThreadDatabase(getActivity()).archiveConversation(threadId);
if (!read) { if (unreadCount > 0) {
List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(getActivity()).setRead(threadId, false); List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(getActivity()).setRead(threadId, false);
MessageNotifier.updateNotification(getActivity(), masterSecret); MessageNotifier.updateNotification(getActivity(), masterSecret);
MarkReadReceiver.process(getActivity(), messageIds); MarkReadReceiver.process(getActivity(), messageIds);
@ -514,8 +514,8 @@ public class ConversationListFragment extends Fragment
protected void reverseAction(@Nullable Long parameter) { protected void reverseAction(@Nullable Long parameter) {
DatabaseFactory.getThreadDatabase(getActivity()).unarchiveConversation(threadId); DatabaseFactory.getThreadDatabase(getActivity()).unarchiveConversation(threadId);
if (!read) { if (unreadCount > 0) {
DatabaseFactory.getThreadDatabase(getActivity()).setUnread(threadId); DatabaseFactory.getThreadDatabase(getActivity()).incrementUnread(threadId, unreadCount);
MessageNotifier.updateNotification(getActivity(), masterSecret); MessageNotifier.updateNotification(getActivity(), masterSecret);
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2011 Whisper Systems * Copyright (C) 2014-2017 Open Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -19,17 +19,20 @@ package org.thoughtcrime.securesms;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.graphics.drawable.RippleDrawable; import android.graphics.drawable.RippleDrawable;
import android.os.Build.VERSION; import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES; import android.os.Build.VERSION_CODES;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import com.amulyakhare.textdrawable.TextDrawable;
import org.thoughtcrime.securesms.components.AlertView; import org.thoughtcrime.securesms.components.AlertView;
import org.thoughtcrime.securesms.components.AvatarImageView; import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.DeliveryStatusView; import org.thoughtcrime.securesms.components.DeliveryStatusView;
@ -41,7 +44,6 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.ResUtil;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
@ -50,17 +52,11 @@ import java.util.Set;
import static org.thoughtcrime.securesms.util.SpanUtil.color; import static org.thoughtcrime.securesms.util.SpanUtil.color;
/**
* A view that displays the element in a list of multiple conversation threads.
* Used by SecureSMS's ListActivity via a ConversationListAdapter.
*
* @author Moxie Marlinspike
*/
public class ConversationListItem extends RelativeLayout public class ConversationListItem extends RelativeLayout
implements RecipientModifiedListener, implements RecipientModifiedListener,
BindableConversationListItem, Unbindable BindableConversationListItem, Unbindable
{ {
@SuppressWarnings("unused")
private final static String TAG = ConversationListItem.class.getSimpleName(); private final static String TAG = ConversationListItem.class.getSimpleName();
private final static Typeface BOLD_TYPEFACE = Typeface.create("sans-serif", Typeface.BOLD); private final static Typeface BOLD_TYPEFACE = Typeface.create("sans-serif", Typeface.BOLD);
@ -76,15 +72,13 @@ public class ConversationListItem extends RelativeLayout
private TextView archivedView; private TextView archivedView;
private DeliveryStatusView deliveryStatusIndicator; private DeliveryStatusView deliveryStatusIndicator;
private AlertView alertView; private AlertView alertView;
private ImageView unreadIndicator;
private long lastSeen; private long lastSeen;
private boolean read; private int unreadCount;
private AvatarImageView contactPhotoImage; private AvatarImageView contactPhotoImage;
private ThumbnailView thumbnailView; private ThumbnailView thumbnailView;
private final @DrawableRes int readBackground;
private final @DrawableRes int unreadBackround;
private int distributionType; private int distributionType;
public ConversationListItem(Context context) { public ConversationListItem(Context context) {
@ -93,8 +87,6 @@ public class ConversationListItem extends RelativeLayout
public ConversationListItem(Context context, AttributeSet attrs) { public ConversationListItem(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
readBackground = ResUtil.getDrawableRes(context, R.attr.conversation_list_item_background_read);
unreadBackround = ResUtil.getDrawableRes(context, R.attr.conversation_list_item_background_unread);
} }
@Override @Override
@ -107,7 +99,8 @@ public class ConversationListItem extends RelativeLayout
this.alertView = findViewById(R.id.indicators_parent); this.alertView = findViewById(R.id.indicators_parent);
this.contactPhotoImage = findViewById(R.id.contact_photo_image); this.contactPhotoImage = findViewById(R.id.contact_photo_image);
this.thumbnailView = findViewById(R.id.thumbnail); this.thumbnailView = findViewById(R.id.thumbnail);
this.archivedView = ViewUtil.findById(this, R.id.archived); this.archivedView = findViewById(R.id.archived);
this.unreadIndicator = findViewById(R.id.unread_indicator);
thumbnailView.setClickable(false); thumbnailView.setClickable(false);
ViewUtil.setTextViewGravityStart(this.fromView, getContext()); ViewUtil.setTextViewGravityStart(this.fromView, getContext());
@ -123,20 +116,20 @@ public class ConversationListItem extends RelativeLayout
this.recipient = thread.getRecipient(); this.recipient = thread.getRecipient();
this.threadId = thread.getThreadId(); this.threadId = thread.getThreadId();
this.glideRequests = glideRequests; this.glideRequests = glideRequests;
this.read = thread.isRead(); this.unreadCount = thread.getUnreadCount();
this.distributionType = thread.getDistributionType(); this.distributionType = thread.getDistributionType();
this.lastSeen = thread.getLastSeen(); this.lastSeen = thread.getLastSeen();
this.recipient.addListener(this); this.recipient.addListener(this);
this.fromView.setText(recipient, read); this.fromView.setText(recipient, unreadCount == 0);
this.subjectView.setText(thread.getDisplayBody()); this.subjectView.setText(thread.getDisplayBody());
this.subjectView.setTypeface(read ? LIGHT_TYPEFACE : BOLD_TYPEFACE); // this.subjectView.setTypeface(read ? LIGHT_TYPEFACE : BOLD_TYPEFACE);
if (thread.getDate() > 0) { if (thread.getDate() > 0) {
CharSequence date = DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, thread.getDate()); CharSequence date = DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, thread.getDate());
dateView.setText(read ? date : color(getResources().getColor(R.color.textsecure_primary), date)); dateView.setText(unreadCount == 0 ? date : color(getResources().getColor(R.color.textsecure_primary_dark), date));
dateView.setTypeface(read ? LIGHT_TYPEFACE : BOLD_TYPEFACE); dateView.setTypeface(unreadCount == 0 ? LIGHT_TYPEFACE : BOLD_TYPEFACE);
} }
if (thread.isArchived()) { if (thread.isArchived()) {
@ -148,8 +141,8 @@ public class ConversationListItem extends RelativeLayout
setStatusIcons(thread); setStatusIcons(thread);
setThumbnailSnippet(masterSecret, thread); setThumbnailSnippet(masterSecret, thread);
setBatchState(batchMode); setBatchState(batchMode);
setBackground(thread);
setRippleColor(recipient); setRippleColor(recipient);
setUnreadIndicator(thread);
this.contactPhotoImage.setAvatar(glideRequests, recipient, true); this.contactPhotoImage.setAvatar(glideRequests, recipient, true);
} }
@ -170,8 +163,8 @@ public class ConversationListItem extends RelativeLayout
return threadId; return threadId;
} }
public boolean getRead() { public int getUnreadCount() {
return read; return unreadCount;
} }
public int getDistributionType() { public int getDistributionType() {
@ -226,12 +219,6 @@ public class ConversationListItem extends RelativeLayout
} }
} }
private void setBackground(ThreadRecord thread) {
if (thread.isRead()) setBackgroundResource(readBackground);
else setBackgroundResource(unreadBackround);
}
@TargetApi(VERSION_CODES.LOLLIPOP)
private void setRippleColor(Recipient recipient) { private void setRippleColor(Recipient recipient) {
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
((RippleDrawable)(getBackground()).mutate()) ((RippleDrawable)(getBackground()).mutate())
@ -239,10 +226,27 @@ public class ConversationListItem extends RelativeLayout
} }
} }
private void setUnreadIndicator(ThreadRecord thread) {
if (thread.isOutgoing() || thread.getUnreadCount() == 0) {
unreadIndicator.setVisibility(View.GONE);
return;
}
unreadIndicator.setImageDrawable(TextDrawable.builder()
.beginConfig()
.width(ViewUtil.dpToPx(getContext(), 24))
.height(ViewUtil.dpToPx(getContext(), 24))
.textColor(Color.WHITE)
.bold()
.endConfig()
.buildRound(String.valueOf(thread.getUnreadCount()), getResources().getColor(R.color.textsecure_primary_dark)));
unreadIndicator.setVisibility(View.VISIBLE);
}
@Override @Override
public void onModified(final Recipient recipient) { public void onModified(final Recipient recipient) {
Util.runOnMain(() -> { Util.runOnMain(() -> {
fromView.setText(recipient, read); fromView.setText(recipient, unreadCount == 0);
contactPhotoImage.setAvatar(glideRequests, recipient, true); contactPhotoImage.setAvatar(glideRequests, recipient, true);
setRippleColor(recipient); setRippleColor(recipient);
}); });

View File

@ -85,7 +85,7 @@ public class ShareListItem extends RelativeLayout
} }
private void setBackground() { private void setBackground() {
int[] attributes = new int[]{R.attr.conversation_list_item_background_read}; int[] attributes = new int[]{R.attr.conversation_list_item_background};
TypedArray drawables = context.obtainStyledAttributes(attributes); TypedArray drawables = context.obtainStyledAttributes(attributes);
setBackgroundDrawable(drawables.getDrawable(0)); setBackgroundDrawable(drawables.getDrawable(0));

View File

@ -109,7 +109,8 @@ public class DatabaseFactory {
private static final int UNSEEN_NUMBER_OFFER = 43; private static final int UNSEEN_NUMBER_OFFER = 43;
private static final int READ_RECEIPTS = 44; private static final int READ_RECEIPTS = 44;
private static final int GROUP_RECEIPT_TRACKING = 45; private static final int GROUP_RECEIPT_TRACKING = 45;
private static final int DATABASE_VERSION = 45; private static final int UNREAD_COUNT_VERSION = 46;
private static final int DATABASE_VERSION = 46;
private static final String DATABASE_NAME = "messages.db"; private static final String DATABASE_NAME = "messages.db";
private static final Object lock = new Object(); private static final Object lock = new Object();
@ -1346,6 +1347,33 @@ public class DatabaseFactory {
db.execSQL("CREATE INDEX IF NOT EXISTS group_receipt_mms_id_index ON group_receipts (mms_id)"); db.execSQL("CREATE INDEX IF NOT EXISTS group_receipt_mms_id_index ON group_receipts (mms_id)");
} }
if (oldVersion < UNREAD_COUNT_VERSION) {
db.execSQL("ALTER TABLE thread ADD COLUMN unread_count INTEGER DEFAULT 0");
try (Cursor cursor = db.query("thread", new String[] {"_id"}, "read = 0", null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
long threadId = cursor.getLong(0);
int unreadCount = 0;
try (Cursor smsCursor = db.rawQuery("SELECT COUNT(*) FROM sms WHERE thread_id = ? AND read = '0'", new String[] {String.valueOf(threadId)})) {
if (smsCursor != null && smsCursor.moveToFirst()) {
unreadCount += smsCursor.getInt(0);
}
}
try (Cursor mmsCursor = db.rawQuery("SELECT COUNT(*) FROM mms WHERE thread_id = ? AND read = '0'", new String[] {String.valueOf(threadId)})) {
if (mmsCursor != null && mmsCursor.moveToFirst()) {
unreadCount += mmsCursor.getInt(0);
}
}
db.execSQL("UPDATE thread SET unread_count = ? WHERE _id = ?",
new String[] {String.valueOf(unreadCount),
String.valueOf(threadId)});
}
}
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
db.endTransaction(); db.endTransaction();
} }

View File

@ -677,7 +677,7 @@ public class MmsDatabase extends MessagingDatabase {
long messageId = insertMediaMessage(masterSecret, retrieved.getBody(), retrieved.getAttachments(), contentValues, null); long messageId = insertMediaMessage(masterSecret, retrieved.getBody(), retrieved.getAttachments(), contentValues, null);
if (!Types.isExpirationTimerUpdate(mailbox)) { if (!Types.isExpirationTimerUpdate(mailbox)) {
DatabaseFactory.getThreadDatabase(context).setUnread(threadId); DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1);
DatabaseFactory.getThreadDatabase(context).update(threadId, true); DatabaseFactory.getThreadDatabase(context).update(threadId, true);
} }
@ -775,7 +775,7 @@ public class MmsDatabase extends MessagingDatabase {
DatabaseFactory.getThreadDatabase(context).update(threadId, true); DatabaseFactory.getThreadDatabase(context).update(threadId, true);
if (org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context)) { if (org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context)) {
DatabaseFactory.getThreadDatabase(context).setUnread(threadId); DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1);
} }
jobManager.add(new TrimThreadJob(context, threadId)); jobManager.add(new TrimThreadJob(context, threadId));

View File

@ -1,5 +1,6 @@
/** /*
* Copyright (C) 2011 Whisper Systems * Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 - 2017 Open Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -486,7 +487,7 @@ public class SmsDatabase extends MessagingDatabase {
jobManager.add(new TrimThreadJob(context, threadId)); jobManager.add(new TrimThreadJob(context, threadId));
if (unread) { if (unread) {
DatabaseFactory.getThreadDatabase(context).setUnread(threadId); DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1);
} }
return new Pair<>(messageId, threadId); return new Pair<>(messageId, threadId);
@ -561,7 +562,7 @@ public class SmsDatabase extends MessagingDatabase {
long messageId = db.insert(TABLE_NAME, null, values); long messageId = db.insert(TABLE_NAME, null, values);
if (unread) { if (unread) {
DatabaseFactory.getThreadDatabase(context).setUnread(threadId); DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1);
} }
if (!message.isIdentityUpdate() && !message.isIdentityVerified() && !message.isIdentityDefault()) { if (!message.isIdentityUpdate() && !message.isIdentityVerified() && !message.isIdentityDefault()) {

View File

@ -1,5 +1,6 @@
/** /*
* Copyright (C) 2011 Whisper Systems * Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013-2017 Open Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -65,6 +66,7 @@ public class ThreadDatabase extends Database {
public static final String SNIPPET = "snippet"; public static final String SNIPPET = "snippet";
private static final String SNIPPET_CHARSET = "snippet_cs"; private static final String SNIPPET_CHARSET = "snippet_cs";
public static final String READ = "read"; public static final String READ = "read";
public static final String UNREAD_COUNT = "unread_count";
public static final String TYPE = "type"; public static final String TYPE = "type";
private static final String ERROR = "error"; private static final String ERROR = "error";
public static final String SNIPPET_TYPE = "snippet_type"; public static final String SNIPPET_TYPE = "snippet_type";
@ -86,15 +88,15 @@ public class ThreadDatabase extends Database {
ARCHIVED + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT 0, " + ARCHIVED + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT 0, " +
DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + EXPIRES_IN + " INTEGER DEFAULT 0, " + DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + EXPIRES_IN + " INTEGER DEFAULT 0, " +
LAST_SEEN + " INTEGER DEFAULT 0, " + HAS_SENT + " INTEGER DEFAULT 0, " + LAST_SEEN + " INTEGER DEFAULT 0, " + HAS_SENT + " INTEGER DEFAULT 0, " +
READ_RECEIPT_COUNT + " INTEGER DEFAULT 0);"; READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + UNREAD_COUNT + " INTEGER DEFAULT 0);";
public static final String[] CREATE_INDEXS = { static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + ADDRESS + ");", "CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + ADDRESS + ");",
"CREATE INDEX IF NOT EXISTS archived_count_index ON " + TABLE_NAME + " (" + ARCHIVED + ", " + MESSAGE_COUNT + ");", "CREATE INDEX IF NOT EXISTS archived_count_index ON " + TABLE_NAME + " (" + ARCHIVED + ", " + MESSAGE_COUNT + ");",
}; };
private static final String[] THREAD_PROJECTION = { private static final String[] THREAD_PROJECTION = {
ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, TYPE, ERROR, SNIPPET_TYPE, ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, TYPE, ERROR, SNIPPET_TYPE,
SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT
}; };
@ -248,6 +250,7 @@ public class ThreadDatabase extends Database {
SQLiteDatabase db = databaseHelper.getWritableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues(1); ContentValues contentValues = new ContentValues(1);
contentValues.put(READ, 1); contentValues.put(READ, 1);
contentValues.put(UNREAD_COUNT, 0);
db.update(TABLE_NAME, contentValues, null, null); db.update(TABLE_NAME, contentValues, null, null);
@ -265,6 +268,7 @@ public class ThreadDatabase extends Database {
public List<MarkedMessageInfo> setRead(long threadId, boolean lastSeen) { public List<MarkedMessageInfo> setRead(long threadId, boolean lastSeen) {
ContentValues contentValues = new ContentValues(1); ContentValues contentValues = new ContentValues(1);
contentValues.put(READ, 1); contentValues.put(READ, 1);
contentValues.put(UNREAD_COUNT, 0);
if (lastSeen) { if (lastSeen) {
contentValues.put(LAST_SEEN, System.currentTimeMillis()); contentValues.put(LAST_SEEN, System.currentTimeMillis());
@ -284,13 +288,22 @@ public class ThreadDatabase extends Database {
}}; }};
} }
public void setUnread(long threadId) { // public void setUnread(long threadId, int unreadCount) {
ContentValues contentValues = new ContentValues(1); // ContentValues contentValues = new ContentValues(1);
contentValues.put(READ, 0); // contentValues.put(READ, 0);
// contentValues.put(UNREAD_COUNT, unreadCount);
//
// SQLiteDatabase db = databaseHelper.getWritableDatabase();
// db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId + ""});
// notifyConversationListListeners();
// }
public void incrementUnread(long threadId, int amount) {
SQLiteDatabase db = databaseHelper.getWritableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId + ""}); db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = 0, " +
notifyConversationListListeners(); UNREAD_COUNT + " = " + UNREAD_COUNT + " + ? WHERE " + ID + " = ?",
new String[] {String.valueOf(amount),
String.valueOf(threadId)});
} }
public void setDistributionType(long threadId, int distributionType) { public void setDistributionType(long threadId, int distributionType) {
@ -530,11 +543,12 @@ public class ThreadDatabase extends Database {
notifyConversationListeners(threadId); notifyConversationListeners(threadId);
} }
public void updateReadState(long threadId) { void updateReadState(long threadId) {
int unreadCount = DatabaseFactory.getMmsSmsDatabase(context).getUnreadCount(threadId); int unreadCount = DatabaseFactory.getMmsSmsDatabase(context).getUnreadCount(threadId);
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
contentValues.put(READ, unreadCount == 0); contentValues.put(READ, unreadCount == 0);
contentValues.put(UNREAD_COUNT, unreadCount);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues,ID_WHERE, databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues,ID_WHERE,
new String[] {String.valueOf(threadId)}); new String[] {String.valueOf(threadId)});
@ -595,8 +609,8 @@ public class ThreadDatabase extends Database {
" ORDER BY " + TABLE_NAME + "." + DATE + " DESC"; " ORDER BY " + TABLE_NAME + "." + DATE + " DESC";
} }
public static interface ProgressListener { public interface ProgressListener {
public void onProgress(int complete, int total); void onProgress(int complete, int total);
} }
public Reader readerFor(Cursor cursor, MasterCipher masterCipher) { public Reader readerFor(Cursor cursor, MasterCipher masterCipher) {
@ -648,7 +662,7 @@ public class ThreadDatabase extends Database {
DisplayRecord.Body body = getPlaintextBody(cursor); DisplayRecord.Body body = getPlaintextBody(cursor);
long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE)); long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE));
long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT)); long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT));
long read = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.READ)); int unreadCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.UNREAD_COUNT));
long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE)); long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE));
boolean archived = cursor.getInt(cursor.getColumnIndex(ThreadDatabase.ARCHIVED)) != 0; boolean archived = cursor.getInt(cursor.getColumnIndex(ThreadDatabase.ARCHIVED)) != 0;
int status = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.STATUS)); int status = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.STATUS));
@ -662,9 +676,9 @@ public class ThreadDatabase extends Database {
readReceiptCount = 0; readReceiptCount = 0;
} }
return new ThreadRecord(context, body, snippetUri, recipient, date, count, read == 1, return new ThreadRecord(context, body, snippetUri, recipient, date, count,
threadId, deliveryReceiptCount, status, type, distributionType, archived, unreadCount, threadId, deliveryReceiptCount, status, type,
expiresIn, lastSeen, readReceiptCount); distributionType, archived, expiresIn, lastSeen, readReceiptCount);
} }
private DisplayRecord.Body getPlaintextBody(Cursor cursor) { private DisplayRecord.Body getPlaintextBody(Cursor cursor) {

View File

@ -1,5 +1,6 @@
/** /*
* Copyright (C) 2012 Moxie Marlinspike * Copyright (C) 2012 Moxie Marlinspike
* Copyright (C) 2013-2017 Open Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -42,14 +43,14 @@ public class ThreadRecord extends DisplayRecord {
private @NonNull final Context context; private @NonNull final Context context;
private @Nullable final Uri snippetUri; private @Nullable final Uri snippetUri;
private final long count; private final long count;
private final boolean read; private final int unreadCount;
private final int distributionType; private final int distributionType;
private final boolean archived; private final boolean archived;
private final long expiresIn; private final long expiresIn;
private final long lastSeen; private final long lastSeen;
public ThreadRecord(@NonNull Context context, @NonNull Body body, @Nullable Uri snippetUri, public ThreadRecord(@NonNull Context context, @NonNull Body body, @Nullable Uri snippetUri,
@NonNull Recipient recipient, long date, long count, boolean read, @NonNull Recipient recipient, long date, long count, int unreadCount,
long threadId, int deliveryReceiptCount, int status, long snippetType, long threadId, int deliveryReceiptCount, int status, long snippetType,
int distributionType, boolean archived, long expiresIn, long lastSeen, int distributionType, boolean archived, long expiresIn, long lastSeen,
int readReceiptCount) int readReceiptCount)
@ -58,7 +59,7 @@ public class ThreadRecord extends DisplayRecord {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.snippetUri = snippetUri; this.snippetUri = snippetUri;
this.count = count; this.count = count;
this.read = read; this.unreadCount = unreadCount;
this.distributionType = distributionType; this.distributionType = distributionType;
this.archived = archived; this.archived = archived;
this.expiresIn = expiresIn; this.expiresIn = expiresIn;
@ -134,8 +135,8 @@ public class ThreadRecord extends DisplayRecord {
return count; return count;
} }
public boolean isRead() { public int getUnreadCount() {
return read; return unreadCount;
} }
public long getDate() { public long getDate() {