From d2445c70af51b54ad3548037cad37765d1b57556 Mon Sep 17 00:00:00 2001 From: M66B Date: Thu, 8 Feb 2024 10:58:45 +0100 Subject: [PATCH] Added barcode decoding --- ATTRIBUTION.md | 1 + app/build.gradle | 5 ++ app/src/main/assets/ATTRIBUTION.md | 1 + .../java/eu/faircode/email/AdapterMedia.java | 86 +++++++++++++++---- .../eu/faircode/email/FragmentOptions.java | 2 +- .../email/FragmentOptionsDisplay.java | 14 ++- .../res/layout/fragment_options_display.xml | 12 +++ app/src/main/res/values/strings.xml | 1 + 8 files changed, 103 insertions(+), 19 deletions(-) diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index b4a17b13f5..d12820378f 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -54,3 +54,4 @@ FairEmail uses parts or all of: * [Brave Lists](https://github.com/brave/adblock-lists). [Mozilla Public License Version 2.0](https://github.com/brave/adblock-lists/blob/master/LICENSE). * [AG Filters Registry](https://github.com/AdguardTeam/FiltersRegistry). [GNU Lesser General Public License Version 3](https://github.com/AdguardTeam/FiltersRegistry/blob/master/LICENSE). * [Certificate transparency for Android and JVM](https://github.com/appmattus/certificatetransparency). Copyright 2023 Appmattus Limited. [Apache License 2.0](https://github.com/appmattus/certificatetransparency/blob/main/LICENSE.md). +* [ZXing](https://github.com/zxing/zxing). Copyright (C) 2014 ZXing authors. [Apache License 2.0](https://github.com/zxing/zxing/blob/master/LICENSE). diff --git a/app/build.gradle b/app/build.gradle index 03161421c9..40001d60ea 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -572,6 +572,7 @@ dependencies { def canary_version = "2.13" def ws_version = "2.14" def tinylog_version = "2.6.2" + def zxing_version = "3.5.3" // https://mvnrepository.com/artifact/com.android.tools/desugar_jdk_libs?repo=google coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugar_version" @@ -828,4 +829,8 @@ dependencies { // https://mvnrepository.com/artifact/org.tinylog implementation "org.tinylog:tinylog-api:$tinylog_version" implementation "org.tinylog:tinylog-impl:$tinylog_version" + + // https://github.com/zxing/zxing + // https://mvnrepository.com/artifact/com.google.zxing/core + implementation "com.google.zxing:core:$zxing_version" } diff --git a/app/src/main/assets/ATTRIBUTION.md b/app/src/main/assets/ATTRIBUTION.md index b4a17b13f5..d12820378f 100644 --- a/app/src/main/assets/ATTRIBUTION.md +++ b/app/src/main/assets/ATTRIBUTION.md @@ -54,3 +54,4 @@ FairEmail uses parts or all of: * [Brave Lists](https://github.com/brave/adblock-lists). [Mozilla Public License Version 2.0](https://github.com/brave/adblock-lists/blob/master/LICENSE). * [AG Filters Registry](https://github.com/AdguardTeam/FiltersRegistry). [GNU Lesser General Public License Version 3](https://github.com/AdguardTeam/FiltersRegistry/blob/master/LICENSE). * [Certificate transparency for Android and JVM](https://github.com/appmattus/certificatetransparency). Copyright 2023 Appmattus Limited. [Apache License 2.0](https://github.com/appmattus/certificatetransparency/blob/main/LICENSE.md). +* [ZXing](https://github.com/zxing/zxing). Copyright (C) 2014 ZXing authors. [Apache License 2.0](https://github.com/zxing/zxing/blob/master/LICENSE). diff --git a/app/src/main/java/eu/faircode/email/AdapterMedia.java b/app/src/main/java/eu/faircode/email/AdapterMedia.java index 55a9f21920..9b71049d6f 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMedia.java +++ b/app/src/main/java/eu/faircode/email/AdapterMedia.java @@ -25,6 +25,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.pdf.PdfRenderer; @@ -35,7 +36,10 @@ import android.os.Build; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.provider.MediaStore; +import android.text.SpannableStringBuilder; import android.text.TextUtils; +import android.text.style.StyleSpan; +import android.text.style.URLSpan; import android.util.Pair; import android.util.Size; import android.view.LayoutInflater; @@ -45,6 +49,7 @@ import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.core.text.method.LinkMovementMethodCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; @@ -55,7 +60,16 @@ import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.ListUpdateCallback; import androidx.recyclerview.widget.RecyclerView; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.MultiFormatReader; +import com.google.zxing.NotFoundException; +import com.google.zxing.RGBLuminanceSource; +import com.google.zxing.Result; +import com.google.zxing.common.HybridBinarizer; + import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -84,6 +98,7 @@ public class AdapterMedia extends RecyclerView.Adapter ivImage = itemView.findViewById(R.id.ivImage); tvCaption = itemView.findViewById(R.id.tvCaption); tvProperties = itemView.findViewById(R.id.tvProperties); + tvProperties.setMovementMethod(LinkMovementMethodCompat.getInstance()); } private void wire() { @@ -180,6 +195,7 @@ public class AdapterMedia extends RecyclerView.Adapter } else { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); boolean webp = prefs.getBoolean("webp", true); + boolean barcode_preview = prefs.getBoolean("barcode_preview", true); if ("image/webp".equalsIgnoreCase(type) && !webp) return context.getDrawable(R.drawable.twotone_image_not_supported_24); @@ -200,6 +216,24 @@ public class AdapterMedia extends RecyclerView.Adapter Log.w(ex); } + if (barcode_preview) + try (InputStream is = new FileInputStream(file)) { + Bitmap bitmap = BitmapFactory.decodeStream(is); + int width = bitmap.getWidth(), height = bitmap.getHeight(); + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + + RGBLuminanceSource source = new RGBLuminanceSource(width, height, pixels); + BinaryBitmap bBitmap = new BinaryBitmap(new HybridBinarizer(source)); + MultiFormatReader reader = new MultiFormatReader(); + Result result = reader.decode(bBitmap); + args.putString("barcode", result.getText()); + } catch (NotFoundException ex) { + Log.w(ex); + } catch (Throwable ex) { + Log.e(ex); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !"image/svg+xml".equals(type) && !"svg".equals(Helper.getExtension(file.getName()))) @@ -233,47 +267,65 @@ public class AdapterMedia extends RecyclerView.Adapter ImageHelper.animate(context, image); } - StringBuilder sb = new StringBuilder(); + SpannableStringBuilder ssb = new SpannableStringBuilderEx(); int width = args.getInt("width"); int height = args.getInt("height"); if (width > 0 && height > 0) - sb.append(width) + ssb.append(Integer.toString(width)) .append("\u00d7") // × - .append(height); + .append(Integer.toString(height)); if (BuildConfig.DEBUG) { String color = args.getString("color"); if (color != null) { - if (sb.length() > 0) - sb.append(' '); - sb.append(color); + if (ssb.length() > 0) + ssb.append(' '); + ssb.append(color); } String config = args.getString("config"); if (config != null) { - if (sb.length() > 0) - sb.append(' '); - sb.append(config); + if (ssb.length() > 0) + ssb.append(' '); + ssb.append(config); } } long size = args.getLong("size"); if (size > 0) { - if (sb.length() > 0) - sb.append(' '); - sb.append(Helper.humanReadableByteCount(size)); + if (ssb.length() > 0) + ssb.append(' '); + ssb.append(Helper.humanReadableByteCount(size)); } int duration = args.getInt("duration"); if (duration > 0) { - if (sb.length() > 0) - sb.append(' '); - sb.append(Helper.formatDuration(duration)); + if (ssb.length() > 0) + ssb.append(' '); + ssb.append(Helper.formatDuration(duration)); + } + + String barcode = args.getString("barcode"); + if (!TextUtils.isEmpty(barcode)) { + if (ssb.length() > 0) + ssb.append('\n'); + int start = ssb.length(); + ssb.append(barcode); + + try { + Uri uri = UriHelper.guessScheme(Uri.parse(barcode)); + if (UriHelper.isHyperLink(uri) || UriHelper.isMail(uri)) + ssb.setSpan(new URLSpan(uri.toString()), start, ssb.length(), 0); + else + ssb.setSpan(new StyleSpan(Typeface.BOLD), start, ssb.length(), 0); + } catch (Throwable ex) { + Log.e(ex); + } } - if (sb.length() > 0) { - tvProperties.setText(sb); + if (ssb.length() > 0) { + tvProperties.setText(ssb); tvProperties.setVisibility(View.VISIBLE); } } diff --git a/app/src/main/java/eu/faircode/email/FragmentOptions.java b/app/src/main/java/eu/faircode/email/FragmentOptions.java index eaf464c5b0..bd515536a4 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptions.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptions.java @@ -155,7 +155,7 @@ public class FragmentOptions extends FragmentBase { "subject_top", "subject_italic", "highlight_subject", "font_size_subject", "subject_ellipsize", "keywords_header", "labels_header", "flags", "flags_background", "preview", "preview_italic", "preview_lines", "align_header", "message_zoom", "overview_mode", "addresses", "button_extra", "attachments_alt", - "thumbnails", "pdf_preview", "video_preview", "audio_preview", + "thumbnails", "pdf_preview", "video_preview", "audio_preview", "barcode_preview", "contrast", "hyphenation", "display_font", "monospaced_pre", "list_count", "bundled_fonts", "narrow_fonts", "parse_classes", "background_color", "text_color", "text_size", "text_font", "text_align", "text_titles", "text_separators", diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java b/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java index 6526e41c54..0833b9504b 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java @@ -187,6 +187,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer private SwitchCompat swPdfPreview; private SwitchCompat swVideoPreview; private SwitchCompat swAudioPreview; + private SwitchCompat swBarcodePreview; private SwitchCompat swListCount; private SwitchCompat swBundledFonts; @@ -226,7 +227,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer "hyphenation", "display_font", "contrast", "monospaced_pre", "text_separators", "collapse_quotes", "image_placeholders", "inline_images", "button_extra", - "unzip", "attachments_alt", "thumbnails", "pdf_preview", "video_preview", "audio_preview", + "unzip", "attachments_alt", "thumbnails", "pdf_preview", "video_preview", "audio_preview", "barcode_preview", "list_count", "bundled_fonts", "narrow_fonts", "parse_classes", "background_color", "text_color", "text_size", "text_font", "text_align", "text_titles", "authentication", "authentication_indicator" @@ -361,6 +362,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer swPdfPreview = view.findViewById(R.id.swPdfPreview); swVideoPreview = view.findViewById(R.id.swVideoPreview); swAudioPreview = view.findViewById(R.id.swAudioPreview); + swBarcodePreview = view.findViewById(R.id.swBarcodePreview); swListCount = view.findViewById(R.id.swListCount); swBundledFonts = view.findViewById(R.id.swBundledFonts); @@ -1324,6 +1326,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer swPdfPreview.setEnabled(checked); swVideoPreview.setEnabled(checked); swAudioPreview.setEnabled(checked); + swBarcodePreview.setEnabled(checked); } }); @@ -1348,6 +1351,13 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer } }); + swBarcodePreview.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { + prefs.edit().putBoolean("barcode_preview", checked).apply(); + } + }); + swListCount.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { @@ -1695,6 +1705,8 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer swVideoPreview.setEnabled(swThumbnails.isChecked()); swAudioPreview.setChecked(prefs.getBoolean("audio_preview", true)); swAudioPreview.setEnabled(swThumbnails.isChecked()); + swBarcodePreview.setChecked(prefs.getBoolean("barcode_preview", true)); + swBarcodePreview.setEnabled(swThumbnails.isChecked()); swListCount.setChecked(prefs.getBoolean("list_count", false)); swBundledFonts.setChecked(prefs.getBoolean("bundled_fonts", true)); diff --git a/app/src/main/res/layout/fragment_options_display.xml b/app/src/main/res/layout/fragment_options_display.xml index 61516e74b2..2ecdbee335 100644 --- a/app/src/main/res/layout/fragment_options_display.xml +++ b/app/src/main/res/layout/fragment_options_display.xml @@ -2084,6 +2084,18 @@ app:layout_constraintTop_toBottomOf="@id/swVideoPreview" app:switchPadding="12dp" /> + + Show PDF thumbnails Show video thumbnails Show mini audio player + Show barcode content Default message text zoom: %1$s %% Zoom message text also in the message editor Zoom original messages to fit the screen