diff --git a/app/src/main/java/eu/faircode/email/ContactInfo.java b/app/src/main/java/eu/faircode/email/ContactInfo.java index 2d138db05f..204bd3cecf 100644 --- a/app/src/main/java/eu/faircode/email/ContactInfo.java +++ b/app/src/main/java/eu/faircode/email/ContactInfo.java @@ -27,13 +27,6 @@ import android.database.ContentObserver; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.RectF; import android.net.Uri; import android.os.Handler; import android.os.Looper; @@ -168,54 +161,20 @@ public class ContactInfo { boolean identicon = false; if (info.bitmap == null) { int dp = Helper.dp2pixels(context, 96); - boolean dark = Helper.isDarkTheme(context); boolean generated = prefs.getBoolean("generated_icons", true); if (generated) { boolean identicons = prefs.getBoolean("identicons", false); if (identicons) { identicon = true; - info.bitmap = ImageHelper.generateIdenticon(key, dp, 5, dark); + info.bitmap = ImageHelper.generateIdenticon(key, dp, 5, context); } else - info.bitmap = ImageHelper.generateLetterIcon(key, dp, dark); + info.bitmap = ImageHelper.generateLetterIcon(key, dp, context); } } boolean circular = prefs.getBoolean("circular", true); - if (info.bitmap != null) { - int w = info.bitmap.getWidth(); - int h = info.bitmap.getHeight(); - - Rect source; - if (w > h) { - int off = (w - h) / 2; - source = new Rect(off, 0, w - off, h); - } else if (w < h) { - int off = (h - w) / 2; - source = new Rect(0, off, w, h - off); - } else - source = new Rect(0, 0, w, h); - - Rect dest = new Rect(0, 0, source.width(), source.height()); - - Bitmap round = Bitmap.createBitmap(source.width(), source.height(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(round); - - Paint paint = new Paint(); - paint.setAntiAlias(true); - canvas.drawARGB(0, 0, 0, 0); - paint.setColor(Color.GRAY); - if (circular && !identicon) - canvas.drawOval(new RectF(dest), paint); - else { - float radius = Helper.dp2pixels(context, 3); - canvas.drawRoundRect(new RectF(dest), radius, radius, paint); - } - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); - canvas.drawBitmap(info.bitmap, source, dest, paint); - - info.bitmap.recycle(); - info.bitmap = round; - } + info.bitmap = ImageHelper.makeCircular(info.bitmap, + circular && !identicon ? null : Helper.dp2pixels(context, 3)); if (info.displayName == null) info.displayName = address.getPersonal(); diff --git a/app/src/main/java/eu/faircode/email/FragmentOptions.java b/app/src/main/java/eu/faircode/email/FragmentOptions.java index 025a8717e1..d9a2808eed 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptions.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptions.java @@ -40,7 +40,7 @@ public class FragmentOptions extends FragmentBase { static String[] OPTIONS_RESTART = new String[]{ "subscriptions", "startup", "cards", "date", "threading", "indentation", "highlight_unread", - "avatars", "generated_icons", "identicons", "circular", + "avatars", "generated_icons", "identicons", "circular", "saturation", "brightness", "name_email", "distinguish_contacts", "authentication", "subject_top", "subject_italic", "subject_ellipsize", "flags", "flags_background", "preview", "preview_italic", diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java b/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java index eac0b11ff8..612dcba758 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java @@ -20,7 +20,9 @@ package eu.faircode.email; */ import android.app.Dialog; +import android.content.Context; import android.content.SharedPreferences; +import android.graphics.Bitmap; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; @@ -31,7 +33,9 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.Button; import android.widget.CompoundButton; +import android.widget.ImageView; import android.widget.RadioGroup; +import android.widget.SeekBar; import android.widget.Spinner; import android.widget.Toast; @@ -54,6 +58,11 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer private SwitchCompat swGeneratedIcons; private SwitchCompat swIdenticons; private SwitchCompat swCircular; + private ImageView ivRed; + private ImageView ivGreen; + private ImageView ivBlue; + private SeekBar sbSaturation; + private SeekBar sbBrightness; private SwitchCompat swNameEmail; private SwitchCompat swDistinguishContacts; private SwitchCompat swAuthentication; @@ -77,7 +86,8 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer private final static String[] RESET_OPTIONS = new String[]{ "theme", "startup", "cards", "date", "threading", "indentation", "highlight_unread", - "avatars", "generated_icons", "identicons", "circular", "name_email", "distinguish_contacts", "authentication", + "avatars", "generated_icons", "identicons", "circular", "saturation", "brightness", + "name_email", "distinguish_contacts", "authentication", "subject_top", "subject_italic", "subject_ellipsize", "flags", "flags_background", "preview", "preview_italic", "addresses", "attachments_alt", "contrast", "monospaced", "text_color", @@ -105,6 +115,11 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer swGeneratedIcons = view.findViewById(R.id.swGeneratedIcons); swIdenticons = view.findViewById(R.id.swIdenticons); swCircular = view.findViewById(R.id.swCircular); + ivRed = view.findViewById(R.id.ivRed); + ivGreen = view.findViewById(R.id.ivGreen); + ivBlue = view.findViewById(R.id.ivBlue); + sbSaturation = view.findViewById(R.id.sbSaturation); + sbBrightness = view.findViewById(R.id.sbBrightness); swNameEmail = view.findViewById(R.id.swNameEmail); swDistinguishContacts = view.findViewById(R.id.swDistinguishContacts); swAuthentication = view.findViewById(R.id.swAuthentication); @@ -216,10 +231,49 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { prefs.edit().putBoolean("circular", checked).apply(); + updateColor(); ContactInfo.clearCache(); } }); + sbSaturation.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + prefs.edit().putInt("saturation", progress).apply(); + updateColor(); + ContactInfo.clearCache(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do nothing + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do nothing + } + }); + + sbBrightness.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + prefs.edit().putInt("brightness", progress).apply(); + updateColor(); + ContactInfo.clearCache(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do nothing + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do nothing + } + }); + swNameEmail.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { @@ -426,6 +480,8 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer swIdenticons.setChecked(prefs.getBoolean("identicons", false)); swIdenticons.setEnabled(swGeneratedIcons.isChecked()); swCircular.setChecked(prefs.getBoolean("circular", true)); + sbSaturation.setProgress(prefs.getInt("saturation", 100)); + sbBrightness.setProgress(prefs.getInt("brightness", 100)); swNameEmail.setChecked(prefs.getBoolean("name_email", false)); swDistinguishContacts.setChecked(prefs.getBoolean("distinguish_contacts", false)); swAuthentication.setChecked(prefs.getBoolean("authentication", true)); @@ -454,6 +510,29 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer swImagesInline.setChecked(prefs.getBoolean("inline_images", false)); swSeekbar.setChecked(prefs.getBoolean("seekbar", false)); swActionbar.setChecked(prefs.getBoolean("actionbar", true)); + + updateColor(); + } + + private void updateColor() { + Context context = getContext(); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean circular = prefs.getBoolean("circular", true); + + int size = Helper.dp2pixels(context, 36); + Integer radius = (circular ? null : Helper.dp2pixels(context, 3)); + + Bitmap red = ImageHelper.generateLetterIcon(0f, "A", size, context); + Bitmap green = ImageHelper.generateLetterIcon(120f, "B", size, context); + Bitmap blue = ImageHelper.generateLetterIcon(240f, "C", size, context); + + red = ImageHelper.makeCircular(red, radius); + green = ImageHelper.makeCircular(green, radius); + blue = ImageHelper.makeCircular(blue, radius); + + ivRed.setImageBitmap(red); + ivGreen.setImageBitmap(green); + ivBlue.setImageBitmap(blue); } public static class FragmentDialogTheme extends FragmentDialogBase { diff --git a/app/src/main/java/eu/faircode/email/ImageHelper.java b/app/src/main/java/eu/faircode/email/ImageHelper.java index 96ea86e33e..37ddaedc65 100644 --- a/app/src/main/java/eu/faircode/email/ImageHelper.java +++ b/app/src/main/java/eu/faircode/email/ImageHelper.java @@ -28,6 +28,8 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ImageDecoder; import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; @@ -64,10 +66,17 @@ class ImageHelper { private static final ExecutorService executor = Helper.getBackgroundExecutor(1, "image"); - static Bitmap generateIdenticon(@NonNull String email, int size, int pixels, boolean dark) { + static Bitmap generateIdenticon(@NonNull String email, int size, int pixels, Context context) { byte[] hash = getHash(email); - int color = Color.HSVToColor(127, new float[]{Math.abs(email.hashCode()) % 360, 1, 1}); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + int saturation = prefs.getInt("saturation", 100); + int brightness = prefs.getInt("brightness", 100); + + int color = Color.HSVToColor(new float[]{ + Math.abs(email.hashCode()) % 360, + saturation / 100f, + brightness / 100f}); Paint paint = new Paint(); paint.setColor(color); @@ -91,34 +100,43 @@ class ImageHelper { return bitmap; } - static Bitmap generateLetterIcon(@NonNull String email, int size, boolean dark) { - String text = null; + static Bitmap generateLetterIcon(@NonNull String email, int size, Context context) { + String letter = null; for (int i = 0; i < email.length(); i++) { char kar = email.charAt(i); if (Character.isAlphabetic(kar)) { - text = email.substring(i, i + 1).toUpperCase(); + letter = email.substring(i, i + 1).toUpperCase(); break; } } - if (text == null) + if (letter == null) return null; - int color = Color.HSVToColor(127, new float[]{Math.abs(email.hashCode()) % 360, 1, 1}); + float h = Math.abs(email.hashCode()) % 360f; + return generateLetterIcon(h, letter, size, context); + } + + static Bitmap generateLetterIcon(float h, String letter, int size, Context context) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + float s = prefs.getInt("saturation", 100) / 100f; + float v = prefs.getInt("brightness", 100) / 100f; + + int bg = Color.HSVToColor(new float[]{h, s, v}); + + double lum = ColorUtils.calculateLuminance(bg); + int fg = Color.HSVToColor(new float[]{0, 0, lum < 0.5 ? v : 0}); Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); - canvas.drawColor(color); - - double lum = ColorUtils.calculateLuminance(color); + canvas.drawColor(bg); Paint paint = new Paint(); - paint.setColor(lum < 0.5 ? Color.WHITE : Color.BLACK); + paint.setColor(fg); paint.setTextSize(size / 2f); paint.setTypeface(Typeface.DEFAULT_BOLD); - canvas.drawText( - text, - size / 2f - paint.measureText(text) / 2, + canvas.drawText(letter, + size / 2f - paint.measureText(letter) / 2, size / 2f - (paint.descent() + paint.ascent()) / 2, paint); return bitmap; @@ -132,6 +150,43 @@ class ImageHelper { } } + static Bitmap makeCircular(Bitmap bitmap, Integer radius) { + if (bitmap == null) + return null; + + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + + Rect source; + if (w > h) { + int off = (w - h) / 2; + source = new Rect(off, 0, w - off, h); + } else if (w < h) { + int off = (h - w) / 2; + source = new Rect(0, off, w, h - off); + } else + source = new Rect(0, 0, w, h); + + Rect dest = new Rect(0, 0, source.width(), source.height()); + + Bitmap round = Bitmap.createBitmap(source.width(), source.height(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(round); + + Paint paint = new Paint(); + paint.setAntiAlias(true); + canvas.drawARGB(0, 0, 0, 0); + paint.setColor(Color.GRAY); + if (radius == null) + canvas.drawOval(new RectF(dest), paint); // round + else + canvas.drawRoundRect(new RectF(dest), radius, radius, paint); // rounded + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(bitmap, source, dest, paint); + + bitmap.recycle(); + return round; + } + static Drawable decodeImage(final Context context, final long id, String source, boolean show, final TextView view) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); boolean compact = prefs.getBoolean("compact", false); diff --git a/app/src/main/res/layout/fragment_options_display.xml b/app/src/main/res/layout/fragment_options_display.xml index 8d334d8df1..d267e8f5e0 100644 --- a/app/src/main/res/layout/fragment_options_display.xml +++ b/app/src/main/res/layout/fragment_options_display.xml @@ -187,6 +187,81 @@ app:layout_constraintTop_toBottomOf="@id/swIdenticons" app:switchPadding="12dp" /> + + + + + + + + + + + + + + Show generated icons Show identicons Show round icons + Saturation + Brightness Show names and email addresses Show a warning when the receiving server could not authenticate the message Show subject above sender