Theme Support

1) Broke out the UI elements of the major Activites into stylable
   attributes.

2) Created a 'light' and 'dark' theme for the newly stylable attrs.

3) Touched up some of the UI spacing.

4) Implemented dynamic theme switching support.
This commit is contained in:
Moxie Marlinspike
2013-06-21 11:56:59 -07:00
parent 2ffc70a95b
commit 5263ac1f1a
62 changed files with 443 additions and 156 deletions

View File

@@ -38,8 +38,10 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.Trimmer;
import org.thoughtcrime.securesms.util.Util;
import com.actionbarsherlock.view.MenuItem;
@@ -52,7 +54,9 @@ import java.util.List;
*
*/
public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPreferenceActivity {
public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPreferenceActivity
implements SharedPreferences.OnSharedPreferenceChangeListener
{
private static final int PICK_IDENTITY_CONTACT = 1;
@@ -67,6 +71,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
public static final String PASSPHRASE_TIMEOUT_INTERVAL_PREF = "pref_timeout_interval";
public static final String PASSPHRASE_TIMEOUT_PREF = "pref_timeout_passphrase";
public static final String AUTO_KEY_EXCHANGE_PREF = "pref_auto_complete_key_exchange";
public static final String THEME_PREF = "pref_theme";
private static final String DISPLAY_CATEGORY_PREF = "pref_display_category";
@@ -93,8 +98,11 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
public static final String REGISTERED_GCM_PREF = "pref_gcm_registered";
public static final String GCM_PASSWORD_PREF = "pref_gcm_password";
private final DynamicTheme dynamicTheme = new DynamicTheme();
@Override
protected void onCreate(Bundle icicle) {
dynamicTheme.onCreate(this);
super.onCreate(icicle);
this.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
@@ -116,6 +124,24 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
.setOnPreferenceChangeListener(new TrimLengthValidationListener());
}
@Override
public void onStart() {
super.onStart();
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
@Override
public void onStop() {
super.onStop();
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onDestroy() {
MemoryCleaner.clean((MasterSecret) getIntent().getParcelableExtra("master_secret"));
@@ -203,6 +229,13 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(THEME_PREF)) {
dynamicTheme.onResume(this);
}
}
private class IdentityPreferenceClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
@@ -328,4 +361,5 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr
((PreferenceScreen)preference).getDialog().getWindow().getDecorView().setBackgroundDrawable(this.getWindow().getDecorView().getBackground().getConstantState().newDrawable());
return false;
}
}

View File

@@ -23,6 +23,7 @@ import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DynamicTheme;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.ActionBar.Tab;
@@ -41,6 +42,8 @@ import com.actionbarsherlock.view.MenuItem;
*/
public class ContactSelectionActivity extends PassphraseRequiredSherlockFragmentActivity {
private final DynamicTheme dynamicTheme = new DynamicTheme();
private ContactSelectionListFragment contactsFragment;
private ContactSelectionGroupsFragment groupsFragment;
private ContactSelectionRecentFragment recentFragment;
@@ -49,6 +52,7 @@ public class ContactSelectionActivity extends PassphraseRequiredSherlockFragment
@Override
protected void onCreate(Bundle icicle) {
dynamicTheme.onCreate(this);
super.onCreate(icicle);
ActionBar actionBar = this.getSupportActionBar();
@@ -62,6 +66,12 @@ public class ContactSelectionActivity extends PassphraseRequiredSherlockFragment
setupRecentTab();
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = this.getSupportMenuInflater();

View File

@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.TypedArray;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
@@ -75,6 +76,7 @@ import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.CharacterCalculator;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.EncryptedCharacterCalculator;
import org.thoughtcrime.securesms.util.InvalidMessageException;
import org.thoughtcrime.securesms.util.MemoryCleaner;
@@ -129,9 +131,11 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
private boolean isMmsEnabled = true;
private CharacterCalculator characterCalculator = new CharacterCalculator();
private DynamicTheme dynamicTheme = new DynamicTheme();
@Override
protected void onCreate(Bundle state) {
dynamicTheme.onCreate(this);
super.onCreate(state);
setContentView(R.layout.conversation_activity);
@@ -154,6 +158,8 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
@Override
protected void onResume() {
super.onResume();
dynamicTheme.onResume(this);
initializeSecurity();
initializeTitleBar();
initializeMmsEnabledCheck();
@@ -513,21 +519,26 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
}
private void initializeSecurity() {
int attributes[] = new int[]{R.attr.conversation_send_button,
R.attr.conversation_send_secure_button};
TypedArray drawables = obtainStyledAttributes(attributes);
if (isSingleConversation() &&
KeyUtil.isSessionFor(this, getRecipients().getPrimaryRecipient()))
{
sendButton.setImageResource(R.drawable.ic_send_encrypted_holo_light);
sendButton.setImageDrawable(drawables.getDrawable(1));
this.isEncryptedConversation = true;
this.isAuthenticatedConversation = KeyUtil.isIdentityKeyFor(this, masterSecret, getRecipients().getPrimaryRecipient());
this.characterCalculator = new EncryptedCharacterCalculator();
} else {
sendButton.setImageResource(R.drawable.ic_send_holo_light);
sendButton.setImageDrawable(drawables.getDrawable(0));
this.isEncryptedConversation = false;
this.isAuthenticatedConversation = false;
this.characterCalculator = new CharacterCalculator();
}
calculateCharactersRemaining();
drawables.recycle();
}
private void initializeMmsEnabledCheck() {

View File

@@ -84,6 +84,7 @@ public class ConversationItem extends LinearLayout {
private ImageView contactPhoto;
private ImageView deliveredImage;
private View mmsContainer;
private ImageView mmsThumbnail;
private Button mmsDownloadButton;
private TextView mmsDownloadingLabel;
@@ -115,6 +116,7 @@ public class ConversationItem extends LinearLayout {
this.secureImage = (ImageView)findViewById(R.id.sms_secure_indicator);
this.failedImage = (ImageView)findViewById(R.id.sms_failed_indicator);
this.keyImage = (ImageView)findViewById(R.id.key_exchange_indicator);
this.mmsContainer = (View) findViewById(R.id.mms_view);
this.mmsThumbnail = (ImageView)findViewById(R.id.image_view);
this.mmsDownloadButton = (Button) findViewById(R.id.mms_download_button);
this.mmsDownloadingLabel = (TextView) findViewById(R.id.mms_label_downloading);
@@ -237,9 +239,11 @@ public class ConversationItem extends LinearLayout {
private void setMediaMmsAttributes(MediaMmsMessageRecord messageRecord) {
if (messageRecord.getPartCount() > 0) {
mmsThumbnail.setVisibility(View.VISIBLE);
mmsContainer.setVisibility(View.VISIBLE);
mmsThumbnail.setImageDrawable(new ColorDrawable(Color.TRANSPARENT));
} else {
mmsThumbnail.setVisibility(View.GONE);
mmsContainer.setVisibility(View.GONE);
}
slideDeck = messageRecord.getSlideDeck();

View File

@@ -21,18 +21,22 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.MemoryCleaner;
public class ConversationListActivity extends PassphraseRequiredSherlockFragmentActivity
implements ConversationListFragment.ConversationSelectedListener
{
private final DynamicTheme dynamicTheme = new DynamicTheme();
private ConversationListFragment fragment;
private MasterSecret masterSecret;
@Override
public void onCreate(Bundle icicle) {
dynamicTheme.onCreate(this);
super.onCreate(icicle);
setContentView(R.layout.conversation_list_activity);
getSupportActionBar().setTitle("TextSecure");
@@ -41,6 +45,12 @@ public class ConversationListActivity extends PassphraseRequiredSherlockFragment
initializeContactUpdatesReceiver();
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
@Override
public void onDestroy() {
Log.w("ConversationListActivity", "onDestroy...");

View File

@@ -18,6 +18,7 @@ package org.thoughtcrime.securesms;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Typeface;
import android.net.Uri;
@@ -148,13 +149,21 @@ public class ConversationListItem extends RelativeLayout
}
private void setBackground(boolean read, boolean batch) {
int[] attributes = new int[]{R.attr.conversation_list_item_background_selected,
R.attr.conversation_list_item_background_read,
R.attr.conversation_list_item_background_unread};
TypedArray drawables = context.obtainStyledAttributes(attributes);
if (batch && selectedThreads.contains(threadId)) {
setBackgroundResource(R.drawable.list_selected_holo_light);
setBackgroundDrawable(drawables.getDrawable(0));
} else if (read) {
setBackgroundResource(R.drawable.conversation_list_item_background_read);
setBackgroundDrawable(drawables.getDrawable(1));
} else {
setBackgroundResource(R.drawable.conversation_list_item_background_unread);
setBackgroundDrawable(drawables.getDrawable(2));
}
drawables.recycle();
}
private boolean isBadgeEnabled() {
@@ -162,12 +171,15 @@ public class ConversationListItem extends RelativeLayout
}
private CharSequence formatFrom(Recipients from, long count, boolean read) {
int attributes[] = new int[] {R.attr.conversation_list_item_count_color};
TypedArray colors = context.obtainStyledAttributes(attributes);
String fromString = from.toShortString();
SpannableStringBuilder builder = new SpannableStringBuilder(fromString);
if (count > 0) {
builder.append(" " + count);
builder.setSpan(new ForegroundColorSpan(Color.parseColor("#66333333")),
builder.setSpan(new ForegroundColorSpan(colors.getColor(0,0)),
fromString.length(), builder.length(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}
@@ -177,6 +189,7 @@ public class ConversationListItem extends RelativeLayout
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}
colors.recycle();
return builder;
}

View File

@@ -5,6 +5,7 @@ import android.content.Intent;
import android.os.Bundle;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import org.thoughtcrime.securesms.util.DynamicTheme;
public class CountrySelectionActivity extends SherlockFragmentActivity
implements CountrySelectionFragment.CountrySelectedListener

View File

@@ -17,11 +17,13 @@
package org.thoughtcrime.securesms;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.SerializableKey;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.DynamicTheme;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
@@ -36,6 +38,20 @@ import com.google.zxing.integration.android.IntentResult;
*/
public abstract class KeyScanningActivity extends PassphraseRequiredSherlockActivity {
private final DynamicTheme dynamicTheme = new DynamicTheme();
@Override
protected void onCreate(Bundle bundle) {
dynamicTheme.onCreate(this);
super.onCreate(bundle);
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);

View File

@@ -20,14 +20,26 @@ import android.os.Bundle;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.MenuItem;
import org.thoughtcrime.securesms.util.DynamicTheme;
public class ReviewIdentitiesActivity extends SherlockFragmentActivity {
private final DynamicTheme dynamicTheme = new DynamicTheme();
@Override
public void onCreate(Bundle bundle) {
dynamicTheme.onCreate(this);
super.onCreate(bundle);
setContentView(R.layout.review_identities);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {

View File

@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
@@ -45,19 +46,24 @@ public class ImageDivet extends ImageView {
}
private void setDrawable() {
Resources r = getContext().getResources();
int attributes[] = new int[] {R.attr.conversation_avatar_divet_left,
R.attr.conversation_avatar_divet_right};
TypedArray drawables = getContext().obtainStyledAttributes(attributes);
switch (position) {
case 0:
drawable = r.getDrawable(R.drawable.divet_right);
drawable = drawables.getDrawable(1);
break;
case 1:
drawable = r.getDrawable(R.drawable.divet_left);
drawable = drawables.getDrawable(0);
break;
}
drawableIntrinsicWidth = drawable.getIntrinsicWidth();
drawableIntrinsicHeight = drawable.getIntrinsicHeight();
drawables.recycle();
}
@Override

View File

@@ -103,6 +103,9 @@ public class ImageSlide extends Slide {
imageView.setImageDrawable(temporaryDrawable);
if (maxWidth == 0 || maxHeight == 0)
return;
MmsDatabase.slideResolver.execute(new Runnable() {
@Override
public void run() {

View File

@@ -0,0 +1,61 @@
package org.thoughtcrime.securesms.util;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.preference.PreferenceManager;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R;
public class DynamicTheme {
private int currentTheme;
public void onCreate(Activity activity) {
currentTheme = getSelectedTheme(activity);
activity.setTheme(currentTheme);
}
public void onResume(Activity activity) {
if (currentTheme != getSelectedTheme(activity)) {
Intent intent = activity.getIntent();
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
activity.startActivity(intent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
OverridePendingTransition.invoke(activity);
}
activity.finish();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
OverridePendingTransition.invoke(activity);
}
}
}
public static int getSelectedTheme(Context context) {
String theme = PreferenceManager.getDefaultSharedPreferences(context)
.getString(ApplicationPreferencesActivity.THEME_PREF, "light");
return getSelectedTheme(theme);
}
public static int getSelectedTheme(String theme) {
if (theme.equals("light")) {
return R.style.TextSecure_LightTheme;
} else if (theme.equals("dark")) {
return R.style.TextSecure_DarkTheme;
}
return R.style.TextSecure_LightTheme;
}
private static final class OverridePendingTransition {
static void invoke(Activity activity) {
activity.overridePendingTransition(0, 0);
}
}
}

View File

@@ -184,7 +184,6 @@ public class Util {
return new String(bout.toByteArray());
}
// public static Bitmap loadScaledBitmap(InputStream src, int targetWidth, int targetHeight) {
// return BitmapFactory.decodeStream(src);
//// BitmapFactory.Options options = new BitmapFactory.Options();