diff --git a/build.gradle b/build.gradle
index 77917e2267..24e95ae23d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -57,6 +57,10 @@ dependencies {
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
exclude module: 'support-v4'
}
+ compile ('com.android.support:gridlayout-v7:22.2.0') {
+ exclude module: 'support-v4'
+ }
+
compile 'com.squareup.dagger:dagger:1.2.2'
compile ("com.doomonafireball.betterpickers:library:1.5.3") {
exclude group: 'com.android.support', module: 'support-v4'
diff --git a/res/layout/color_preference_item.xml b/res/layout/color_preference_item.xml
new file mode 100644
index 0000000000..fac3e5b219
--- /dev/null
+++ b/res/layout/color_preference_item.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/color_preference_items.xml b/res/layout/color_preference_items.xml
new file mode 100644
index 0000000000..db1a7f8d93
--- /dev/null
+++ b/res/layout/color_preference_items.xml
@@ -0,0 +1,21 @@
+
+
+
\ No newline at end of file
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 79156fab2c..97cf1c9f9a 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -190,4 +190,24 @@
- 2
+
+ - #ffE57373
+ - #ffF06292
+ - #ffBA68C8
+ - #ff9575CD
+ - #ff7986CB
+ - #ff64B5F6
+ - #ff4FC3F7
+ - #ff4DD0E1
+ - #FF4DB6AC
+ - #FF81C784
+ - #FFAED581
+ - #FFDCE775
+ - #FFFFD54F
+ - #FFFFB74D
+ - #FFFF8A65
+ - #FFA1887F
+ - #FF90A4AE
+
+
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 1c3d9b7639..bf69ebc9c2 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -135,4 +135,11 @@
+
+
+
+
+
+
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 1b1865093f..ca2e62ef97 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -24,4 +24,7 @@
3
10dp
+
+ 32dp
+ 48dp
diff --git a/res/values/strings.xml b/res/values/strings.xml
index eada056c18..164bd40268 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -618,6 +618,8 @@
Ringtone
Vibrate
Block
+ Color
+ Color for this contact
diff --git a/res/xml/recipient_preferences.xml b/res/xml/recipient_preferences.xml
index 4f5dd54cad..8b205d8647 100644
--- a/res/xml/recipient_preferences.xml
+++ b/res/xml/recipient_preferences.xml
@@ -1,5 +1,6 @@
-
+
+
+
+
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index bbfbdff129..9959f2cb84 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -43,7 +43,6 @@ import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnKeyListener;
import android.view.ViewStub;
-import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
@@ -53,7 +52,6 @@ import android.widget.Toast;
import com.afollestad.materialdialogs.AlertDialogWrapper;
import com.google.protobuf.ByteString;
-import com.readystatesoftware.systembartint.SystemBarTintManager;
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
import org.thoughtcrime.securesms.components.AnimatingToggle;
@@ -806,7 +804,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void onModified(final Recipients recipients) {
- Log.w(TAG, "onModified()");
titleView.post(new Runnable() {
@Override
public void run() {
diff --git a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java
index 0e26da1d7f..cb96cdfec3 100644
--- a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java
+++ b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java
@@ -18,6 +18,7 @@ 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;
@@ -28,11 +29,13 @@ import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState;
+import org.thoughtcrime.securesms.preferences.ColorPreference;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
+import org.whispersystems.libaxolotl.util.guava.Optional;
public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActivity implements Recipients.RecipientsModifiedListener
{
@@ -44,6 +47,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
private static final String PREFERENCE_TONE = "pref_key_recipient_ringtone";
private static final String PREFERENCE_VIBRATE = "pref_key_recipient_vibrate";
private static final String PREFERENCE_BLOCK = "pref_key_recipient_block";
+ private static final String PREFERENCE_COLOR = "pref_key_recipient_color";
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
@@ -156,6 +160,8 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
.setOnPreferenceClickListener(new MuteClickedListener());
this.findPreference(PREFERENCE_BLOCK)
.setOnPreferenceClickListener(new BlockClickedListener());
+ this.findPreference(PREFERENCE_COLOR)
+ .setOnPreferenceChangeListener(new ColorChangeListener());
}
@Override
@@ -174,6 +180,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
CheckBoxPreference mutePreference = (CheckBoxPreference) this.findPreference(PREFERENCE_MUTED);
RingtonePreference ringtonePreference = (RingtonePreference) this.findPreference(PREFERENCE_TONE);
ListPreference vibratePreference = (ListPreference) this.findPreference(PREFERENCE_VIBRATE);
+ ColorPreference colorPreference = (ColorPreference) this.findPreference(PREFERENCE_COLOR);
Preference blockPreference = this.findPreference(PREFERENCE_BLOCK);
mutePreference.setChecked(recipients.isMuted());
@@ -199,6 +206,14 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
vibratePreference.setValueIndex(2);
}
+ if (recipients.getColor().isPresent()) {
+ colorPreference.setValue(recipients.getColor().get());
+ colorPreference.setEnabled(true);
+ } else {
+ colorPreference.setValue(getResources().getColor(R.color.textsecure_primary));
+ colorPreference.setEnabled(false);
+ }
+
if (!recipients.isSingleRecipient() || recipients.isGroupRecipient()) {
blockPreference.setEnabled(false);
} else {
@@ -267,6 +282,28 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
}
}
+ private class ColorChangeListener implements Preference.OnPreferenceChangeListener {
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final int value = (Integer)newValue;
+
+ if (value != recipients.getColor().get()) {
+ recipients.setColor(Optional.of(value));
+
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ DatabaseFactory.getRecipientPreferenceDatabase(getActivity())
+ .setColor(recipients, value);
+ return null;
+ }
+ }.execute();
+ }
+ return true;
+ }
+ }
+
private class MuteClickedListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
diff --git a/src/org/thoughtcrime/securesms/preferences/ColorPreference.java b/src/org/thoughtcrime/securesms/preferences/ColorPreference.java
new file mode 100644
index 0000000000..9b3597bc94
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/preferences/ColorPreference.java
@@ -0,0 +1,292 @@
+package org.thoughtcrime.securesms.preferences;
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.GridLayout;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.thoughtcrime.securesms.R;
+
+/**
+ * A preference that allows the user to choose an application or shortcut.
+ */
+public class ColorPreference extends Preference {
+ private int[] mColorChoices = {};
+ private int mValue = 0;
+ private int mItemLayoutId = R.layout.color_preference_item;
+ private int mNumColumns = 5;
+ private View mPreviewView;
+
+ public ColorPreference(Context context) {
+ super(context);
+ initAttrs(null, 0);
+ }
+
+ public ColorPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initAttrs(attrs, 0);
+ }
+
+ public ColorPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ initAttrs(attrs, defStyle);
+ }
+
+ private void initAttrs(AttributeSet attrs, int defStyle) {
+ TypedArray a = getContext().getTheme().obtainStyledAttributes(
+ attrs, R.styleable.ColorPreference, defStyle, defStyle);
+
+ try {
+ mItemLayoutId = a.getResourceId(R.styleable.ColorPreference_itemLayout, mItemLayoutId);
+ mNumColumns = a.getInteger(R.styleable.ColorPreference_numColumns, mNumColumns);
+ int choicesResId = a.getResourceId(R.styleable.ColorPreference_choices,
+ R.array.default_color_choice_values);
+ if (choicesResId > 0) {
+ String[] choices = a.getResources().getStringArray(choicesResId);
+ mColorChoices = new int[choices.length];
+ for (int i = 0; i < choices.length; i++) {
+ mColorChoices[i] = Color.parseColor(choices[i]);
+ }
+ }
+
+ } finally {
+ a.recycle();
+ }
+
+ setWidgetLayoutResource(mItemLayoutId);
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+ mPreviewView = view.findViewById(R.id.color_view);
+ setColorViewValue(mPreviewView, mValue, false);
+ }
+
+ public void setValue(int value) {
+ if (callChangeListener(value)) {
+ mValue = value;
+ persistInt(value);
+ notifyChanged();
+ }
+ }
+
+ @Override
+ protected void onClick() {
+ super.onClick();
+
+ ColorDialogFragment fragment = ColorDialogFragment.newInstance();
+ fragment.setPreference(this);
+
+ ((AppCompatActivity) getContext()).getSupportFragmentManager().beginTransaction()
+ .add(fragment, getFragmentTag())
+ .commit();
+ }
+
+ @Override
+ protected void onAttachedToActivity() {
+ super.onAttachedToActivity();
+
+ AppCompatActivity activity = (AppCompatActivity) getContext();
+ ColorDialogFragment fragment = (ColorDialogFragment) activity
+ .getSupportFragmentManager().findFragmentByTag(getFragmentTag());
+ if (fragment != null) {
+ // re-bind preference to fragment
+ fragment.setPreference(this);
+ }
+ }
+
+ @Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ return a.getInt(index, 0);
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+ setValue(restoreValue ? getPersistedInt(0) : (Integer) defaultValue);
+ }
+
+ public String getFragmentTag() {
+ return "color_" + getKey();
+ }
+
+ public int getValue() {
+ return mValue;
+ }
+
+ public static class ColorDialogFragment extends android.support.v4.app.DialogFragment {
+ private ColorPreference mPreference;
+ private GridLayout mColorGrid;
+
+ public ColorDialogFragment() {
+ }
+
+ public static ColorDialogFragment newInstance() {
+ return new ColorDialogFragment();
+ }
+
+ public void setPreference(ColorPreference preference) {
+ mPreference = preference;
+ repopulateItems();
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ repopulateItems();
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
+ View rootView = layoutInflater.inflate(R.layout.color_preference_items, null);
+
+ mColorGrid = (GridLayout) rootView.findViewById(R.id.color_grid);
+ mColorGrid.setColumnCount(mPreference.mNumColumns);
+ repopulateItems();
+
+ return new AlertDialog.Builder(getActivity())
+ .setView(rootView)
+ .create();
+ }
+
+ private void repopulateItems() {
+ if (mPreference == null || mColorGrid == null) {
+ return;
+ }
+
+ Context context = mColorGrid.getContext();
+ mColorGrid.removeAllViews();
+ for (final int color : mPreference.mColorChoices) {
+ View itemView = LayoutInflater.from(context)
+ .inflate(R.layout.color_preference_item, mColorGrid, false);
+
+ setColorViewValue(itemView.findViewById(R.id.color_view), color,
+ color == mPreference.getValue());
+ itemView.setClickable(true);
+ itemView.setFocusable(true);
+ itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mPreference.setValue(color);
+ dismiss();
+ }
+ });
+
+ mColorGrid.addView(itemView);
+ }
+
+ sizeDialog();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ sizeDialog();
+ }
+
+ private void sizeDialog() {
+ if (mPreference == null || mColorGrid == null) {
+ return;
+ }
+
+ Dialog dialog = getDialog();
+ if (dialog == null) {
+ return;
+ }
+
+ final Resources res = mColorGrid.getContext().getResources();
+ DisplayMetrics dm = res.getDisplayMetrics();
+
+ // Can't use Integer.MAX_VALUE here (weird issue observed otherwise on 4.2)
+ mColorGrid.measure(
+ View.MeasureSpec.makeMeasureSpec(dm.widthPixels, View.MeasureSpec.AT_MOST),
+ View.MeasureSpec.makeMeasureSpec(dm.heightPixels, View.MeasureSpec.AT_MOST));
+ int width = mColorGrid.getMeasuredWidth();
+ int height = mColorGrid.getMeasuredHeight();
+
+ int extraPadding = res.getDimensionPixelSize(R.dimen.color_grid_extra_padding);
+
+ width += extraPadding;
+ height += extraPadding;
+
+ dialog.getWindow().setLayout(width, height);
+ }
+ }
+
+ private static void setColorViewValue(View view, int color, boolean selected) {
+ if (view instanceof ImageView) {
+ ImageView imageView = (ImageView) view;
+ Resources res = imageView.getContext().getResources();
+
+ Drawable currentDrawable = imageView.getDrawable();
+ GradientDrawable colorChoiceDrawable;
+ if (currentDrawable instanceof GradientDrawable) {
+ // Reuse drawable
+ colorChoiceDrawable = (GradientDrawable) currentDrawable;
+ } else {
+ colorChoiceDrawable = new GradientDrawable();
+ colorChoiceDrawable.setShape(GradientDrawable.OVAL);
+ }
+
+ // Set stroke to dark version of color
+// int darkenedColor = Color.rgb(
+// Color.red(color) * 192 / 256,
+// Color.green(color) * 192 / 256,
+// Color.blue(color) * 192 / 256);
+
+ colorChoiceDrawable.setColor(color);
+// colorChoiceDrawable.setStroke((int) TypedValue.applyDimension(
+// TypedValue.COMPLEX_UNIT_DIP, 2, res.getDisplayMetrics()), darkenedColor);
+
+ Drawable drawable = colorChoiceDrawable;
+ if (selected) {
+ BitmapDrawable checkmark = (BitmapDrawable) res.getDrawable(R.drawable.check);
+ checkmark.setGravity(Gravity.CENTER);
+ drawable = new LayerDrawable(new Drawable[]{
+ colorChoiceDrawable,
+ checkmark});
+ }
+
+ imageView.setImageDrawable(drawable);
+
+ } else if (view instanceof TextView) {
+ ((TextView) view).setTextColor(color);
+ }
+ }
+}