Fully colorize conversations.

1. Switch from 300 to 500 colors.

2. Colorize incoming conversation bubbles.

3. Colorize recipeint preference activity toolbar.

4. Support inverted colors in avatars.

5. Make status bar icons tint according to secondary color.

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2015-06-29 15:33:36 -07:00
parent 99c9c73c9d
commit 78289ded8f
40 changed files with 270 additions and 128 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

View File

@ -6,7 +6,6 @@
android:orientation="vertical">
<ListView android:id="@android:id/list"
style="?android:attr/listViewWhiteStyle"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
@ -19,6 +18,6 @@
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:layout_marginBottom="1dip"
android:cacheColorHint="?android:attr/windowBackground" />
android:cacheColorHint="?conversation_background" />
</LinearLayout>

View File

@ -65,7 +65,7 @@
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?conversation_received_text_primary_color"
android:textColor="?conversation_item_received_text_primary_color"
android:textSize="16sp"
android:autoLink="all"
android:linksClickable="true" />
@ -117,9 +117,11 @@
android:layout_gravity="center_vertical"
android:paddingRight="4dp"
android:paddingTop="2dp"
android:src="?menu_lock_icon_small_received"
android:src="?menu_lock_icon_small"
android:contentDescription="@string/conversation_item__secure_message_description"
android:visibility="gone" />
android:visibility="gone"
android:tint="?conversation_item_received_text_secondary_color"
android:tintMode="multiply"/>
<FrameLayout android:id="@+id/pending_indicator_stub"
android:layout_width="wrap_content"
@ -131,7 +133,7 @@
android:layout_gravity="left"
android:paddingTop="1dip"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?conversation_received_text_secondary_color"
android:textColor="?conversation_item_received_text_secondary_color"
android:textSize="@dimen/conversation_item_date_text_size"
android:fontFamily="sans-serif-light"
android:autoLink="none"

View File

@ -147,20 +147,24 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:src="@drawable/ic_done_grey600_18dp"
android:src="@drawable/ic_done_white_18dp"
android:paddingLeft="2dp"
android:paddingBottom="2dp"
android:visibility="gone"
android:tint="?conversation_item_sent_text_secondary_color"
android:tintMode="multiply"
android:contentDescription="@string/conversation_item_sent__delivered_description" />
<ImageView android:id="@+id/delivered_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:src="@drawable/ic_done_all_grey600_18dp"
android:src="@drawable/ic_done_all_white_18dp"
android:paddingLeft="2dp"
android:paddingBottom="2dp"
android:visibility="gone"
android:tint="?conversation_item_sent_text_secondary_color"
android:tintMode="multiply"
android:contentDescription="@string/conversation_item_sent__delivered_description" />
<ImageView android:id="@+id/secure_indicator"
@ -171,6 +175,8 @@
android:layout_gravity="center_vertical|end"
android:paddingLeft="2dp"
android:paddingBottom="3dp"
android:tint="?conversation_item_sent_text_secondary_color"
android:tintMode="multiply"
android:contentDescription="@string/conversation_item__secure_message_description" />
</LinearLayout>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -25,7 +26,8 @@
android:layout_height="50dp"
android:cropToPadding="true"
android:layout_marginLeft="0dp"
android:layout_alignParentLeft="true"/>
android:layout_alignParentLeft="true"
app:inverted="true"/>
<TextView android:id="@+id/name"
android:layout_width="wrap_content"

View File

@ -39,20 +39,6 @@
<item>Ukrainian Український</item>
</string-array>
<array name="avatar_colors">
<item>#6dcaec</item>
<item>#cf9fe7</item>
<item>#b6db49</item>
<item>#ffd060</item>
<item>#ff7979</item>
<item>#2cb1e1</item>
<item>#c182e0</item>
<item>#92c500</item>
<item>#ffb61c</item>
<item>#f83a3a</item>
</array>
<string-array name="language_values">
<item>zz</item>
<item>en</item>
@ -191,23 +177,23 @@
</string-array>
<string-array name="default_color_choice_values" translatable="false">
<item>#ffE57373</item>
<item>#ffF06292</item>
<item>#ffBA68C8</item>
<item>#ff9575CD</item>
<item>#ff7986CB</item>
<item>#ff64B5F6</item>
<item>#ff4FC3F7</item>
<item>#ff4DD0E1</item>
<item>#FF4DB6AC</item>
<item>#FF81C784</item>
<item>#FFAED581</item>
<item>#FFDCE775</item>
<item>#FFFFD54F</item>
<item>#FFFFB74D</item>
<item>#FFFF8A65</item>
<item>#FFA1887F</item>
<item>#FF90A4AE</item>
<item>#ffF44336</item>
<item>#ffE91E63</item>
<item>#ff9C27B0</item>
<item>#ff673AB7</item>
<item>#ff3F51B5</item>
<item>#ff2196F3</item>
<item>#ff03A9F4</item>
<item>#ff00BCD4</item>
<item>#ff009688</item>
<item>#ff4CAF50</item>
<item>#ff8BC34A</item>
<!--<item>#FFCDDC39</item>-->
<item>#FFFFC107</item>
<item>#ffFF9800</item>
<item>#ffFF5722</item>
<item>#ff795548</item>
<item>#ff607D8B</item>
</string-array>
</resources>

View File

@ -14,8 +14,7 @@
<attr name="conversation_sent_card_background" format="reference|color"/>
<attr name="conversation_group_member_name" format="reference|color"/>
<attr name="conversation_received_card_background" format="reference|color"/>
<attr name="conversation_received_text_primary_color" format="reference|color"/>
<attr name="conversation_received_text_secondary_color" format="reference|color"/>
<attr name="fab_color" format="reference|color" />
<attr name="lower_right_divet" format="reference" />
@ -57,6 +56,8 @@
<attr name="conversation_item_bubble_background" format="reference|color"/>
<attr name="conversation_item_sent_text_primary_color" format="reference|color"/>
<attr name="conversation_item_sent_text_secondary_color" format="reference|color"/>
<attr name="conversation_item_received_text_primary_color" format="reference|color"/>
<attr name="conversation_item_received_text_secondary_color" format="reference|color"/>
<attr name="conversation_item_sent_text_indicator_tab_color" format="reference|color"/>
<attr name="conversation_item_sent_indicator_text_background" format="reference" />
@ -87,7 +88,6 @@
<attr name="menu_unlock_icon" format="reference" />
<attr name="menu_lock_icon" format="reference" />
<attr name="menu_lock_icon_small" format="reference" />
<attr name="menu_lock_icon_small_received" format="reference" />
<attr name="menu_trash_icon" format="reference" />
<attr name="menu_selectall_icon" format="reference" />
<attr name="menu_group_icon" format="reference" />
@ -130,4 +130,8 @@
<attr name="numColumns" format="integer" />
</declare-styleable>
<declare-styleable name="AvatarImageView">
<attr name="inverted" format="boolean"/>
</declare-styleable>
</resources>

View File

@ -41,7 +41,7 @@
<item name="titleTextStyle">@style/TextSecure.TitleTextStyle</item>
<item name="subtitleTextStyle">@style/TextSecure.SubtitleTextStyle</item>
<item name="android:textColorPrimary">@color/white</item>
<item name="android:textColorSecondary">#99ffffff</item>
<item name="android:textColorSecondary">#BFffffff</item>
</style>
<style name="TextSecure.DarkActionBar.TabBar"
@ -61,7 +61,7 @@
</style>
<style name="TextSecure.SubtitleTextStyle" parent="TextAppearance.AppCompat.Widget.ActionBar.Subtitle">
<item name="android:textColor">#99ffffff</item>
<item name="android:textColor">#BFffffff</item>
</style>
<style name="TextSecure.IntroActionBar" parent="Widget.AppCompat.Light.ActionBar.Solid.Inverse">

View File

@ -78,8 +78,6 @@
<item name="lower_right_divet">@drawable/divet_lower_right_dark</item>
<item name="conversation_group_member_name">#99000000</item>
<item name="conversation_received_text_primary_color">#ff333333</item>
<item name="conversation_received_text_secondary_color">#99333333</item>
<item name="contact_selection_push_user">#ff000000</item>
<item name="contact_selection_lay_user">#a0000000</item>
@ -116,10 +114,13 @@
<item name="emoji_category_symbol">@drawable/emoji_category_symbol_light</item>
<item name="emoji_category_emoticons">@drawable/emoji_category_emoticons_light</item>
<item name="conversation_item_sent_text_primary_color">#99000000</item>
<item name="conversation_item_bubble_background">@color/white</item>
<item name="conversation_item_sent_text_primary_color">#99000000</item>
<item name="conversation_item_sent_text_secondary_color">#bb000000</item>
<item name="conversation_item_sent_text_indicator_tab_color">#99000000</item>
<item name="conversation_item_received_text_primary_color">@color/white</item>
<item name="conversation_item_received_text_secondary_color">#BFffffff</item>
<item name="conversation_item_background">@drawable/conversation_item_background</item>
<item name="conversation_item_sent_indicator_text_background">@drawable/conversation_item_sent_indicator_text_shape</item>
@ -140,8 +141,7 @@
<item name="menu_popup_expand">@drawable/ic_launch_white_24dp</item>
<item name="menu_unlock_icon">@drawable/ic_unlocked_white_24dp</item>
<item name="menu_lock_icon">@drawable/ic_lock_white_24dp</item>
<item name="menu_lock_icon_small">@drawable/ic_lock_black_18dp</item>
<item name="menu_lock_icon_small_received">@drawable/ic_lock_black_18dp</item>
<item name="menu_lock_icon_small">@drawable/ic_lock_white_18dp</item>
<item name="menu_trash_icon">@drawable/ic_delete_white_24dp</item>
<item name="menu_selectall_icon">@drawable/ic_select_all_white_24dp</item>
<item name="menu_split_icon">@drawable/ic_call_split_white_24dp</item>
@ -188,8 +188,6 @@
<item name="share_list_item_divider">@drawable/share_list_divider_shape_dark</item>
<item name="conversation_group_member_name">#99ffffff</item>
<item name="conversation_received_text_primary_color">#ffeeeeee</item>
<item name="conversation_received_text_secondary_color">#99eeeeee</item>
<item name="contact_selection_push_user">#ffeeeeee</item>
<item name="contact_selection_lay_user">#afeeeeee</item>
@ -201,6 +199,8 @@
<item name="conversation_item_sent_text_primary_color">#ffffffff</item>
<item name="conversation_item_sent_text_secondary_color">#aaeeeeee</item>
<item name="conversation_item_sent_text_indicator_tab_color">#99ffffff</item>
<item name="conversation_item_received_text_primary_color">@color/white</item>
<item name="conversation_item_received_text_secondary_color">#BFffffff</item>
<item name="conversation_item_sent_indicator_text_background">@drawable/conversation_item_sent_indicator_text_shape_dark</item>
<item name="dialog_info_icon">@drawable/ic_info_outline_dark</item>
@ -254,7 +254,6 @@
<item name="menu_unlock_icon">@drawable/ic_unlocked_white_24dp</item>
<item name="menu_lock_icon">@drawable/ic_lock_white_24dp</item>
<item name="menu_lock_icon_small">@drawable/ic_lock_white_18dp</item>
<item name="menu_lock_icon_small_received">@drawable/ic_lock_white_18dp</item>
<item name="menu_trash_icon">@drawable/ic_delete_white_24dp</item>
<item name="menu_selectall_icon">@drawable/ic_select_all_white_24dp</item>
<item name="menu_split_icon">@drawable/ic_call_split_white_24dp</item>

View File

@ -188,10 +188,18 @@ public class ConversationItem extends LinearLayout {
/// MessageRecord Attribute Parsers
private void setBubbleState(MessageRecord messageRecord) {
int[] attributes = new int[]{R.attr.conversation_item_bubble_background};
TypedArray colors = context.obtainStyledAttributes(attributes);
int[] attributes = new int[]{R.attr.conversation_item_bubble_background};
TypedArray colors = context.obtainStyledAttributes(attributes);
int defaultColor = colors.getColor(0, 0xFFFFFF);
bodyBubble.getBackground().setColorFilter(colors.getColor(0, 0xFFFFFFFF), PorterDuff.Mode.MULTIPLY);
if (messageRecord.isOutgoing()) {
bodyBubble.getBackground().setColorFilter(defaultColor, PorterDuff.Mode.MULTIPLY);
} else {
bodyBubble.getBackground().setColorFilter(messageRecord.getIndividualRecipient()
.getColor()
.or(defaultColor),
PorterDuff.Mode.MULTIPLY);
}
colors.recycle();
}

View File

@ -6,6 +6,7 @@ import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.CheckBoxPreference;
@ -18,7 +19,6 @@ import android.support.v4.app.Fragment;
import android.support.v4.preference.PreferenceFragment;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
@ -26,6 +26,7 @@ import android.widget.TextView;
import com.afollestad.materialdialogs.AlertDialogWrapper;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState;
@ -53,6 +54,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private AvatarImageView avatar;
private Toolbar toolbar;
private TextView title;
private TextView blockedIndicator;
@ -103,7 +105,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
}
private void initializeToolbar() {
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
this.toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
@ -115,8 +117,18 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
}
private void setHeader(Recipients recipients) {
Optional<Integer> color = recipients.getColor();
this.avatar.setAvatar(recipients, true);
this.title.setText(recipients.toShortString());
this.toolbar.setBackgroundColor(color.or(getResources().getColor(R.color.textsecure_primary)));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
int primaryDark = getResources().getColor(R.color.textsecure_primary_dark);
if (color.isPresent()) getWindow().setStatusBarColor(ContactColors.getStatusTinted(color.get()).or(primaryDark));
else getWindow().setStatusBarColor(primaryDark);
}
if (recipients.isBlocked()) this.blockedIndicator.setVisibility(View.VISIBLE);
else this.blockedIndicator.setVisibility(View.GONE);
@ -207,11 +219,11 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
}
if (recipients.getColor().isPresent()) {
colorPreference.setValue(recipients.getColor().get());
colorPreference.setEnabled(true);
colorPreference.setValue(recipients.getColor().get());
} else {
colorPreference.setValue(getResources().getColor(R.color.textsecure_primary));
colorPreference.setEnabled(false);
colorPreference.setValue(getResources().getColor(R.color.textsecure_primary));
}
if (!recipients.isSingleRecipient() || recipients.isGroupRecipient()) {
@ -288,7 +300,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
public boolean onPreferenceChange(Preference preference, Object newValue) {
final int value = (Integer)newValue;
if (value != recipients.getColor().get()) {
if (preference.isEnabled() && value != recipients.getColor().get()) {
recipients.setColor(Optional.of(value));
new AsyncTask<Void, Void, Void>() {

View File

@ -2,12 +2,14 @@ package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.provider.ContactsContract;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
@ -16,6 +18,8 @@ import org.thoughtcrime.securesms.recipients.Recipients;
public class AvatarImageView extends ImageView {
private boolean inverted;
public AvatarImageView(Context context) {
super(context);
setScaleType(ScaleType.CENTER_CROP);
@ -24,15 +28,21 @@ public class AvatarImageView extends ImageView {
public AvatarImageView(Context context, AttributeSet attrs) {
super(context, attrs);
setScaleType(ScaleType.CENTER_CROP);
if (attrs != null) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AvatarImageView, 0, 0);
inverted = typedArray.getBoolean(0, false);
typedArray.recycle();
}
}
public void setAvatar(@Nullable Recipients recipients, boolean quickContactEnabled) {
if (recipients != null) {
int backgroundColor = recipients.getColor().or(ContactColors.UNKNOWN_COLOR);
setImageDrawable(recipients.getContactPhoto().asDrawable(getContext(), backgroundColor));
setImageDrawable(recipients.getContactPhoto().asDrawable(getContext(), backgroundColor, inverted));
setAvatarClickHandler(recipients, quickContactEnabled);
} else {
setImageDrawable(ContactPhotoFactory.getDefaultContactPhoto(null).asDrawable(getContext(), ContactColors.UNKNOWN_COLOR));
setImageDrawable(ContactPhotoFactory.getDefaultContactPhoto(null).asDrawable(getContext(), ContactColors.UNKNOWN_COLOR, inverted));
setOnClickListener(null);
}
}

View File

@ -16,7 +16,12 @@ public class BitmapContactPhoto implements ContactPhoto {
}
@Override
public Drawable asDrawable(Context context, int background) {
public Drawable asDrawable(Context context, int color) {
return asDrawable(context, color, false);
}
@Override
public Drawable asDrawable(Context context, int color, boolean inverted) {
return RoundedDrawable.fromBitmap(bitmap)
.setScaleType(ImageView.ScaleType.CENTER_CROP)
.setOval(true);

View File

@ -16,40 +16,58 @@ public class ContactColors {
public static final int UNKNOWN_COLOR = 0xff9E9E9E;
private static final int RED_300 = 0xffE57373;
private static final int RED_500 = 0xffF44336;
private static final int RED_700 = 0xFFD32F2F;
private static final int PINK_300 = 0xffF06292;
private static final int PINK_500 = 0xffE91E63;
private static final int PINK_700 = 0xFFC2185B;
private static final int PURPLE_300 = 0xffBA68C8;
private static final int PURPLE_500 = 0Xff9C27B0;
private static final int PURPLE_700 = 0xFF7B1FA2;
private static final int DEEP_PURPLE_300 = 0xff9575CD;
private static final int DEEP_PURPLE_500 = 0xff673AB7;
private static final int DEEP_PURPLE_700 = 0xFF512DA8;
private static final int INDIGO_300 = 0xff7986CB;
private static final int INDIGO_700 = 0xFF303F9F;
private static final int INDIGO_500 = 0xff3F51B5;
private static final int INDIGO_700 = 0xff303F9F;
private static final int BLUE_300 = 0xff64B5F6;
private static final int BLUE_500 = 0xff2196F3;
private static final int BLUE_700 = 0xFF1976D2;
private static final int LIGHT_BLUE_300 = 0xff4FC3F7;
private static final int LIGHT_BLUE_500 = 0xff03A9F4;
private static final int LIGHT_BLUE_700 = 0xFF0288D1;
private static final int CYAN_300 = 0xff4DD0E1;
private static final int CYAN_500 = 0xff00BCD4;
private static final int CYAN_700 = 0xFF0097A7;
private static final int TEAL_300 = 0xFF4DB6AC;
private static final int TEAL_500 = 0xff009688;
private static final int TEAL_700 = 0xFF00796B;
private static final int GREEN_300 = 0xFF81C784;
private static final int GREEN_500 = 0xff4CAF50;
private static final int GREEN_700 = 0xFF388E3C;
private static final int LIGHT_GREEN_300 = 0xFFAED581;
private static final int LIGHT_GREEN_500 = 0xff8BC34A;
private static final int LIGHT_GREEN_700 = 0xFF689F38;
private static final int LIME_300 = 0xFFDCE775;
private static final int LIME_500 = 0XFFCDDC39;
private static final int LIME_700 = 0xFFAFB42B;
private static final int YELLOW_300 = 0xFFFFF176;
private static final int YELLOW_500 = 0xffFFEB3B;
private static final int YELLOW_700 = 0xFFFBC02D;
private static final int AMBER_300 = 0xFFFFD54F;
private static final int AMBER_500 = 0XFFFFC107;
private static final int AMBER_700 = 0xFFFFA000;
private static final int ORANGE_300 = 0xFFFFB74D;
private static final int ORANGE_500 = 0xffFF9800;
private static final int ORANGE_700 = 0xFFF57C00;
private static final int DEEP_ORANGE_300 = 0xFFFF8A65;
private static final int DEEP_ORANGE_500 = 0xffFF5722;
private static final int DEEP_ORANGE_700 = 0xFFE64A19;
private static final int BROWN_300 = 0xFFA1887F;
private static final int BROWN_500 = 0xff795548;
private static final int BROWN_700 = 0xFF5D4037;
private static final int BLUE_GREY_300 = 0xFF90A4AE;
private static final int BLUE_GREY_500 = 0xff607D8B;
private static final int BLUE_GREY_700 = 0xFF455A64;
private static final List<Integer> MATERIAL_300 = new ArrayList<>(Arrays.asList(
@ -72,6 +90,46 @@ public class ContactColors {
BLUE_GREY_300)
);
private static final List<Integer> MATERIAL_500 = new ArrayList<>(Arrays.asList(
RED_500,
PINK_500,
PURPLE_500,
DEEP_PURPLE_500,
INDIGO_500,
BLUE_500,
LIGHT_BLUE_500,
CYAN_500,
TEAL_500,
GREEN_500,
LIGHT_GREEN_500,
// LIME_500,
AMBER_500,
ORANGE_500,
DEEP_ORANGE_500,
BROWN_500,
BLUE_GREY_500)
);
private static final SparseIntArray MATERIAL_500_TO_700 = new SparseIntArray() {{
put(RED_500, RED_700);
put(PINK_500, PINK_700);
put(PURPLE_500, PURPLE_700);
put(DEEP_PURPLE_500, DEEP_PURPLE_700);
put(INDIGO_500, INDIGO_700);
put(BLUE_500, BLUE_700);
put(LIGHT_BLUE_500, LIGHT_BLUE_700);
put(CYAN_500, CYAN_700);
put(TEAL_500, TEAL_700);
put(GREEN_500, GREEN_700);
put(LIGHT_GREEN_500, LIGHT_GREEN_700);
// put(LIME_500, LIME_700);
put(AMBER_500, AMBER_700);
put(ORANGE_500, ORANGE_700);
put(DEEP_ORANGE_500, DEEP_ORANGE_700);
put(BROWN_500, BROWN_700);
put(BLUE_GREY_500, BLUE_GREY_700);
}};
private static final SparseIntArray MATERIAL_300_TO_700 = new SparseIntArray() {{
put(RED_300, RED_700);
put(PINK_300, PINK_700);
@ -92,15 +150,14 @@ public class ContactColors {
put(BLUE_GREY_300, BLUE_GREY_700);
}};
private static final ColorGenerator MATERIAL_GENERATOR = ColorGenerator.create(MATERIAL_300);
private static final ColorGenerator MATERIAL_GENERATOR = ColorGenerator.create(MATERIAL_500);
public static int generateFor(@NonNull String name) {
return MATERIAL_GENERATOR.getColor(name);
}
public static Optional<Integer> getStatusTinted(int color) {
int statusTinted = MATERIAL_300_TO_700.get(color, -1);
int statusTinted = MATERIAL_500_TO_700.get(color, -1);
return statusTinted == -1 ? Optional.<Integer>absent() : Optional.of(statusTinted);
}

View File

@ -5,7 +5,8 @@ import android.graphics.drawable.Drawable;
public interface ContactPhoto {
public Drawable asDrawable(Context context, int background);
public Drawable asDrawable(Context context, int color);
public Drawable asDrawable(Context context, int color, boolean inverted);
}

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
@ -18,14 +19,20 @@ public class GeneratedContactPhoto implements ContactPhoto {
}
@Override
public Drawable asDrawable(Context context, int background) {
public Drawable asDrawable(Context context, int color) {
return asDrawable(context, color, false);
}
@Override
public Drawable asDrawable(Context context, int color, boolean inverted) {
int targetSize = context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size);
return TextDrawable.builder()
.beginConfig()
.width(targetSize)
.height(targetSize)
.textColor(inverted ? color : Color.WHITE)
.endConfig()
.buildRound(String.valueOf(name.charAt(0)), background);
.buildRound(String.valueOf(name.charAt(0)), inverted ? Color.WHITE : color);
}
}

View File

@ -1,8 +1,12 @@
package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.support.v4.graphics.ColorUtils;
import android.widget.ImageView;
import com.amulyakhare.textdrawable.TextDrawable;
@ -17,11 +21,21 @@ public class ResourceContactPhoto implements ContactPhoto {
}
@Override
public Drawable asDrawable(Context context, int backgroundColor) {
Drawable background = TextDrawable.builder().buildRound(" ", backgroundColor);
public Drawable asDrawable(Context context, int color) {
return asDrawable(context, color, false);
}
@Override
public Drawable asDrawable(Context context, int color, boolean inverted) {
Drawable background = TextDrawable.builder().buildRound(" ", inverted ? Color.WHITE : color);
RoundedDrawable foreground = (RoundedDrawable) RoundedDrawable.fromDrawable(context.getResources().getDrawable(resourceId));
foreground.setScaleType(ImageView.ScaleType.CENTER);
if (inverted) {
foreground.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
}
return new ExpandingLayerDrawable(new Drawable[] {background, foreground});
}

View File

@ -10,7 +10,12 @@ public class TransparentContactPhoto implements ContactPhoto {
TransparentContactPhoto() {}
@Override
public Drawable asDrawable(Context context, int background) {
public Drawable asDrawable(Context context, int color) {
return asDrawable(context, color, false);
}
@Override
public Drawable asDrawable(Context context, int color, boolean inverted) {
return RoundedDrawable.fromDrawable(context.getResources().getDrawable(android.R.color.transparent));
}
}

View File

@ -21,12 +21,14 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoFactory;
import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails;
import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.util.Collections;
import java.util.HashSet;
@ -44,33 +46,30 @@ public class Recipient {
private String number;
private String name;
private ContactPhoto contactPhoto;
private Uri contactUri;
private ContactPhoto contactPhoto;
private Uri contactUri;
private Optional<Integer> color;
Recipient(long recipientId, String number, ListenableFutureTask<RecipientDetails> future)
{
this.recipientId = recipientId;
this.number = number;
this.contactPhoto = ContactPhotoFactory.getLoadingPhoto();
this.color = Optional.absent();
future.addListener(new FutureTaskListener<RecipientDetails>() {
@Override
public void onSuccess(RecipientDetails result) {
if (result != null) {
Set<RecipientModifiedListener> localListeners;
synchronized (Recipient.this) {
Recipient.this.name = result.name;
Recipient.this.number = result.number;
Recipient.this.contactUri = result.contactUri;
Recipient.this.contactPhoto = result.avatar;
localListeners = new HashSet<>(listeners);
listeners.clear();
Recipient.this.color = result.color;
}
for (RecipientModifiedListener listener : localListeners)
listener.onModified(Recipient.this);
notifyListeners();
}
}
@ -87,6 +86,7 @@ public class Recipient {
this.contactUri = details.contactUri;
this.name = details.name;
this.contactPhoto = details.avatar;
this.color = details.color;
}
public synchronized Uri getContactUri() {
@ -97,6 +97,20 @@ public class Recipient {
return this.name;
}
public synchronized @NonNull Optional<Integer> getColor() {
if (color.isPresent()) return color;
else if (name != null) return Optional.of(ContactColors.generateFor(name));
else return Optional.of(ContactColors.UNKNOWN_COLOR);
}
public void setColor(Optional<Integer> color) {
synchronized (this) {
this.color = color;
}
notifyListeners();
}
public String getNumber() {
return number;
}
@ -126,7 +140,9 @@ public class Recipient {
}
public static Recipient getUnknownRecipient() {
return new Recipient(-1, new RecipientDetails("Unknown", "Unknown", null, ContactPhotoFactory.getDefaultContactPhoto("Unknown")));
return new Recipient(-1, new RecipientDetails("Unknown", "Unknown", null,
ContactPhotoFactory.getDefaultContactPhoto("Unknown"),
Optional.<Integer>absent()));
}
@Override
@ -144,6 +160,17 @@ public class Recipient {
return 31 + (int)this.recipientId;
}
private void notifyListeners() {
Set<RecipientModifiedListener> localListeners;
synchronized (this) {
localListeners = new HashSet<>(listeners);
}
for (RecipientModifiedListener listener : localListeners)
listener.onModified(Recipient.this);
}
public interface RecipientModifiedListener {
public void onModified(Recipient recipient);
}

View File

@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.LRUCache;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.io.IOException;
import java.util.Arrays;
@ -67,9 +68,9 @@ public class RecipientProvider {
String number = CanonicalAddressDatabase.getInstance(context).getAddressFromId(recipientId);
if (asynchronous) {
cachedRecipient = new Recipient(recipientId, number, getRecipientDetailsAsync(context, number));
cachedRecipient = new Recipient(recipientId, number, getRecipientDetailsAsync(context, recipientId, number));
} else {
cachedRecipient = new Recipient(recipientId, getRecipientDetailsSync(context, number));
cachedRecipient = new Recipient(recipientId, getRecipientDetailsSync(context, recipientId, number));
}
recipientCache.put(recipientId, cachedRecipient);
@ -99,12 +100,13 @@ public class RecipientProvider {
}
private @NonNull ListenableFutureTask<RecipientDetails> getRecipientDetailsAsync(final Context context,
final long recipientId,
final String number)
{
Callable<RecipientDetails> task = new Callable<RecipientDetails>() {
@Override
public RecipientDetails call() throws Exception {
return getRecipientDetailsSync(context, number);
return getRecipientDetailsSync(context, recipientId, number);
}
};
@ -113,15 +115,17 @@ public class RecipientProvider {
return future;
}
private @NonNull RecipientDetails getRecipientDetailsSync(Context context, String number) {
private @NonNull RecipientDetails getRecipientDetailsSync(Context context, long recipientId, String number) {
if (GroupUtil.isEncodedGroup(number)) return getGroupRecipientDetails(context, number);
else return getIndividualRecipientDetails(context, number);
else return getIndividualRecipientDetails(context, recipientId, number);
}
private @NonNull RecipientDetails getIndividualRecipientDetails(Context context, String number) {
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
Cursor cursor = context.getContentResolver().query(uri, CALLER_ID_PROJECTION,
null, null, null);
private @NonNull RecipientDetails getIndividualRecipientDetails(Context context, long recipientId, String number) {
Optional<RecipientsPreferences> preferences = DatabaseFactory.getRecipientPreferenceDatabase(context).getRecipientsPreferences(new long[]{recipientId});
Optional<Integer> color = preferences.isPresent() ? preferences.get().getColor() : Optional.<Integer>absent();
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
Cursor cursor = context.getContentResolver().query(uri, CALLER_ID_PROJECTION,
null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
@ -131,14 +135,14 @@ public class RecipientProvider {
Uri.withAppendedPath(Contacts.CONTENT_URI, cursor.getLong(2) + ""),
name);
return new RecipientDetails(cursor.getString(0), cursor.getString(3), contactUri, contactPhoto);
return new RecipientDetails(cursor.getString(0), cursor.getString(3), contactUri, contactPhoto, color);
}
} finally {
if (cursor != null)
cursor.close();
}
return new RecipientDetails(null, number, null, ContactPhotoFactory.getDefaultContactPhoto(null));
return new RecipientDetails(null, number, null, ContactPhotoFactory.getDefaultContactPhoto(null), color);
}
private @NonNull RecipientDetails getGroupRecipientDetails(Context context, String groupId) {
@ -148,13 +152,13 @@ public class RecipientProvider {
if (record != null) {
ContactPhoto contactPhoto = ContactPhotoFactory.getGroupContactPhoto(record.getAvatar());
return new RecipientDetails(record.getTitle(), groupId, null, contactPhoto);
return new RecipientDetails(record.getTitle(), groupId, null, contactPhoto, Optional.<Integer>absent());
}
return new RecipientDetails(null, groupId, null, ContactPhotoFactory.getDefaultGroupPhoto());
return new RecipientDetails(null, groupId, null, ContactPhotoFactory.getDefaultGroupPhoto(), Optional.<Integer>absent());
} catch (IOException e) {
Log.w("RecipientProvider", e);
return new RecipientDetails(null, groupId, null, ContactPhotoFactory.getDefaultGroupPhoto());
return new RecipientDetails(null, groupId, null, ContactPhotoFactory.getDefaultGroupPhoto(), Optional.<Integer>absent());
}
}
@ -178,18 +182,21 @@ public class RecipientProvider {
}
public static class RecipientDetails {
@Nullable public final String name;
@NonNull public final String number;
@NonNull public final ContactPhoto avatar;
@Nullable public final Uri contactUri;
@Nullable public final String name;
@NonNull public final String number;
@NonNull public final ContactPhoto avatar;
@Nullable public final Uri contactUri;
@NonNull public final Optional<Integer> color;
public RecipientDetails(@Nullable String name, @NonNull String number,
@Nullable Uri contactUri, @NonNull ContactPhoto avatar)
@Nullable Uri contactUri, @NonNull ContactPhoto avatar,
@NonNull Optional<Integer> color)
{
this.name = name;
this.number = number;
this.avatar = avatar;
this.contactUri = contactUri;
this.color = color;
}
}

View File

@ -22,7 +22,6 @@ import android.support.annotation.Nullable;
import android.util.Log;
import android.util.Patterns;
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoFactory;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
@ -51,11 +50,10 @@ public class Recipients implements Iterable<Recipient>, RecipientModifiedListene
private final Set<RecipientsModifiedListener> listeners = Collections.newSetFromMap(new WeakHashMap<RecipientsModifiedListener, Boolean>());
private final List<Recipient> recipients;
private Uri ringtone = null;
private long mutedUntil = 0;
private boolean blocked = false;
private VibrateState vibrate = VibrateState.DEFAULT;
private Optional<Integer> color = Optional.absent();
private Uri ringtone = null;
private long mutedUntil = 0;
private boolean blocked = false;
private VibrateState vibrate = VibrateState.DEFAULT;
Recipients() {
this(new LinkedList<Recipient>(), (RecipientsPreferences)null);
@ -69,7 +67,6 @@ public class Recipients implements Iterable<Recipient>, RecipientModifiedListene
mutedUntil = preferences.getMuteUntil();
vibrate = preferences.getVibrateState();
blocked = preferences.isBlocked();
color = preferences.getColor();
}
}
@ -88,7 +85,6 @@ public class Recipients implements Iterable<Recipient>, RecipientModifiedListene
mutedUntil = result.getMuteUntil();
vibrate = result.getVibrateState();
blocked = result.isBlocked();
color = result.getColor();
localListeners = new HashSet<>(listeners);
}
@ -106,21 +102,6 @@ public class Recipients implements Iterable<Recipient>, RecipientModifiedListene
});
}
public synchronized Optional<Integer> getColor() {
if (color.isPresent()) return color;
else if (isGroupRecipient()) return Optional.absent();
else if (recipients.get(0).getName() == null) return Optional.absent();
else return Optional.of(ContactColors.generateFor(recipients.get(0).getName()));
}
public void setColor(Optional<Integer> color) {
synchronized (this) {
this.color = color;
}
notifyListeners();
}
public synchronized @Nullable Uri getRingtone() {
return ringtone;
}
@ -169,12 +150,22 @@ public class Recipients implements Iterable<Recipient>, RecipientModifiedListene
notifyListeners();
}
public @NonNull
ContactPhoto getContactPhoto() {
public @NonNull ContactPhoto getContactPhoto() {
if (recipients.size() == 1) return recipients.get(0).getContactPhoto();
else return ContactPhotoFactory.getDefaultGroupPhoto();
}
public synchronized @NonNull Optional<Integer> getColor() {
if (!isSingleRecipient() || isGroupRecipient()) return Optional.absent();
else if (isEmpty()) return Optional.absent();
else return recipients.get(0).getColor();
}
public synchronized void setColor(Optional<Integer> color) {
if (!isSingleRecipient() || isGroupRecipient()) throw new AssertionError("Groups don't have colors!");
else if (!isEmpty()) recipients.get(0).setColor(color);
}
public synchronized void addListener(RecipientsModifiedListener listener) {
if (listeners.isEmpty()) {
for (Recipient recipient : recipients) {