mirror of https://github.com/M66B/FairEmail.git
Extravatars
This commit is contained in:
parent
63238d0019
commit
ac77c2c248
10
FAQ.md
10
FAQ.md
|
@ -334,7 +334,6 @@ Fonts, sizes, colors, etc should be material design whenever possible.
|
|||
* [(177) What does 'Sensitivity' mean?](#user-content-faq177)
|
||||
* [(178) Why are widgets not updating?](#user-content-faq178)
|
||||
* [(179) What are reply templates?](#user-content-faq179)
|
||||
* [(180) Can you add support for Gravatar / Libravatar?](#user-content-faq180)
|
||||
|
||||
[I have another question.](#user-content-get-support)
|
||||
|
||||
|
@ -4689,15 +4688,6 @@ Templates can have the following options:
|
|||
|
||||
<br />
|
||||
|
||||
<a name="faq180"></a>
|
||||
**(180) Can you add support for Gravatar / Libravatar?**
|
||||
|
||||
FairEmail supported both Gravatar and Libravatar until version 1.1883,
|
||||
but [Google forced removal](https://forum.xda-developers.com/t/app-5-0-fairemail-fully-featured-open-source-privacy-oriented-email-app.3824168/post-86800867) of these features,
|
||||
even though they were never present in the Play store version of the app.
|
||||
|
||||
<br />
|
||||
|
||||
<h2><a name="get-support"></a>Get support</h2>
|
||||
|
||||
🌎 [Google Translate](https://translate.google.com/translate?sl=en&u=https://github.com/M66B/FairEmail/blob/master/FAQ.md%23user-content-get-support)
|
||||
|
|
|
@ -48,16 +48,16 @@ android {
|
|||
|
||||
sourceSets {
|
||||
github {
|
||||
java.srcDirs = ['src/main/java', 'src/play/java']
|
||||
java.srcDirs = ['src/main/java', 'src/play/java', 'src/extra/java']
|
||||
}
|
||||
fdroid {
|
||||
java.srcDirs = ['src/main/java', 'src/fdroid/java']
|
||||
java.srcDirs = ['src/main/java', 'src/fdroid/java', 'src/extra/java']
|
||||
}
|
||||
play {
|
||||
java.srcDirs = ['src/main/java', 'src/play/java']
|
||||
java.srcDirs = ['src/main/java', 'src/play/java', 'src/dummy/java']
|
||||
}
|
||||
amazon {
|
||||
java.srcDirs = ['src/main/java', 'src/amazon/java']
|
||||
java.srcDirs = ['src/main/java', 'src/amazon/java', 'src/extra/java']
|
||||
}
|
||||
main.res.srcDirs += 'src/main/resExtra'
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of FairEmail.
|
||||
|
||||
FairEmail is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FairEmail is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018-2022 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.OperationCanceledException;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class Extra {
|
||||
static Callable<ContactInfo.Favicon> getG(String email, int scaleToPixels, Context context) {
|
||||
return new Callable<ContactInfo.Favicon>() {
|
||||
@Override
|
||||
public ContactInfo.Favicon call() throws Exception {
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static Callable<ContactInfo.Favicon> getL(String email, int scaleToPixels, Context context) {
|
||||
return new Callable<ContactInfo.Favicon>() {
|
||||
@Override
|
||||
public ContactInfo.Favicon call() throws Exception {
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of FairEmail.
|
||||
|
||||
FairEmail is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FairEmail is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018-2021 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class Extra {
|
||||
private static final String GRAVATAR_URI = "https://www.gravatar.com/avatar/";
|
||||
private static final int GRAVATAR_CONNECT_TIMEOUT = 5 * 1000; // milliseconds
|
||||
private static final int GRAVATAR_READ_TIMEOUT = 10 * 1000; // milliseconds
|
||||
private static final int LIBRAVATAR_CONNECT_TIMEOUT = 5 * 1000; // milliseconds
|
||||
private static final int LIBRAVATAR_READ_TIMEOUT = 10 * 1000; // milliseconds
|
||||
private static final String LIBRAVATAR_DNS = "_avatars-sec._tcp,_avatars._tcp";
|
||||
private static final String LIBRAVATAR_URI = "https://seccdn.libravatar.org/avatar/";
|
||||
|
||||
static Callable<ContactInfo.Favicon> getG(String email, int scaleToPixels, Context context) {
|
||||
return new Callable<ContactInfo.Favicon>() {
|
||||
@Override
|
||||
public ContactInfo.Favicon call() throws Exception {
|
||||
String hash = Helper.md5(email.getBytes());
|
||||
URL url = new URL(GRAVATAR_URI + hash + "?d=404");
|
||||
Log.i("Gravatar key=" + email + " url=" + url);
|
||||
|
||||
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
||||
urlConnection.setRequestMethod("GET");
|
||||
urlConnection.setReadTimeout(GRAVATAR_READ_TIMEOUT);
|
||||
urlConnection.setConnectTimeout(GRAVATAR_CONNECT_TIMEOUT);
|
||||
urlConnection.setRequestProperty("User-Agent", WebViewEx.getUserAgent(context));
|
||||
urlConnection.connect();
|
||||
|
||||
try {
|
||||
int status = urlConnection.getResponseCode();
|
||||
if (status == HttpURLConnection.HTTP_OK) {
|
||||
// Positive reply
|
||||
Bitmap bitmap = ImageHelper.getScaledBitmap(urlConnection.getInputStream(), url.toString(), null, scaleToPixels);
|
||||
return (bitmap == null ? null : new ContactInfo.Favicon(bitmap, "extra", false));
|
||||
} else if (status == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
// Negative reply
|
||||
return null;
|
||||
} else
|
||||
throw new IOException("Error " + status + ": " + urlConnection.getResponseMessage());
|
||||
} finally {
|
||||
urlConnection.disconnect();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static Callable<ContactInfo.Favicon> getL(String email, int scaleToPixels, Context context) {
|
||||
return new Callable<ContactInfo.Favicon>() {
|
||||
@Override
|
||||
public ContactInfo.Favicon call() throws Exception {
|
||||
String domain = UriHelper.getEmailDomain(email);
|
||||
|
||||
// https://wiki.libravatar.org/api/
|
||||
String baseUrl = LIBRAVATAR_URI;
|
||||
for (String dns : LIBRAVATAR_DNS.split(",")) {
|
||||
DnsHelper.DnsRecord[] records = DnsHelper.lookup(context, dns + "." + domain, "srv");
|
||||
if (records.length > 0) {
|
||||
baseUrl = (records[0].port == 443 ? "https" : "http") + "://" + records[0].name + "/avatar/";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String hash = Helper.md5(email.getBytes());
|
||||
|
||||
URL url = new URL(baseUrl + hash + "?d=404");
|
||||
Log.i("Libravatar key=" + email + " url=" + url);
|
||||
|
||||
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
||||
urlConnection.setRequestMethod("GET");
|
||||
urlConnection.setReadTimeout(LIBRAVATAR_READ_TIMEOUT);
|
||||
urlConnection.setConnectTimeout(LIBRAVATAR_CONNECT_TIMEOUT);
|
||||
urlConnection.setRequestProperty("User-Agent", WebViewEx.getUserAgent(context));
|
||||
urlConnection.connect();
|
||||
|
||||
try {
|
||||
int status = urlConnection.getResponseCode();
|
||||
if (status == HttpURLConnection.HTTP_OK) {
|
||||
// Positive reply
|
||||
Bitmap bitmap = ImageHelper.getScaledBitmap(urlConnection.getInputStream(), url.toString(), null, scaleToPixels);
|
||||
return (bitmap == null ? null : new ContactInfo.Favicon(bitmap, "extra", false));
|
||||
} else if (status == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
// Negative reply
|
||||
return null;
|
||||
} else
|
||||
throw new IOException("Error " + status + ": " + urlConnection.getResponseMessage());
|
||||
} finally {
|
||||
urlConnection.disconnect();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -6900,6 +6900,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
boolean contacts = Helper.hasPermission(context, Manifest.permission.READ_CONTACTS);
|
||||
boolean avatars = prefs.getBoolean("avatars", true);
|
||||
boolean bimi = prefs.getBoolean("bimi", false);
|
||||
boolean efavicons = (prefs.getBoolean("efavicons", false) && !BuildConfig.PLAY_STORE_RELEASE);
|
||||
boolean favicons = prefs.getBoolean("favicons", false);
|
||||
boolean generated = prefs.getBoolean("generated_icons", true);
|
||||
|
||||
|
@ -6911,7 +6912,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
this.threading_unread = threading && prefs.getBoolean("threading_unread", false);
|
||||
this.indentation = prefs.getBoolean("indentation", false);
|
||||
|
||||
this.avatars = (contacts && avatars) || (bimi || favicons || generated);
|
||||
this.avatars = (contacts && avatars) || (bimi || efavicons || favicons || generated);
|
||||
this.color_stripe = prefs.getBoolean("color_stripe", true);
|
||||
this.check_authentication = prefs.getBoolean("check_authentication", true);
|
||||
this.check_tls = prefs.getBoolean("check_tls", true);
|
||||
|
|
|
@ -253,6 +253,7 @@ public class ContactInfo {
|
|||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean avatars = prefs.getBoolean("avatars", true);
|
||||
boolean bimi = prefs.getBoolean("bimi", false);
|
||||
boolean efavicons = (prefs.getBoolean("efavicons", false) && !BuildConfig.PLAY_STORE_RELEASE);
|
||||
boolean favicons = prefs.getBoolean("favicons", false);
|
||||
boolean generated = prefs.getBoolean("generated_icons", true);
|
||||
boolean identicons = prefs.getBoolean("identicons", false);
|
||||
|
@ -302,7 +303,7 @@ public class ContactInfo {
|
|||
|
||||
// Favicon
|
||||
if (info.bitmap == null &&
|
||||
!EntityFolder.JUNK.equals(folderType) && (bimi || favicons)) {
|
||||
!EntityFolder.JUNK.equals(folderType) && (bimi || efavicons || favicons)) {
|
||||
String d = UriHelper.getEmailDomain(info.email);
|
||||
if (d != null) {
|
||||
// Prevent using Doodles
|
||||
|
@ -328,6 +329,11 @@ public class ContactInfo {
|
|||
try {
|
||||
// check cache
|
||||
File[] files = null;
|
||||
if (efavicons) {
|
||||
File f = new File(dir, email + ".extra");
|
||||
if (f.exists())
|
||||
files = new File[]{f};
|
||||
}
|
||||
if (files == null)
|
||||
files = dir.listFiles(new FilenameFilter() {
|
||||
@Override
|
||||
|
@ -363,6 +369,11 @@ public class ContactInfo {
|
|||
}
|
||||
}));
|
||||
|
||||
if (efavicons) {
|
||||
futures.add(executorFavicon.submit(Extra.getG(email, scaleToPixels, context)));
|
||||
futures.add(executorFavicon.submit(Extra.getL(email, scaleToPixels, context)));
|
||||
}
|
||||
|
||||
if (favicons) {
|
||||
String host = domain;
|
||||
while (host.indexOf('.') > 0) {
|
||||
|
@ -451,7 +462,7 @@ public class ContactInfo {
|
|||
|
||||
// Add to cache
|
||||
File output = new File(dir,
|
||||
domain +
|
||||
("extra".equals(info.type) ? email : domain) +
|
||||
"." + info.type +
|
||||
(info.verified ? "_verified" : ""));
|
||||
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(output))) {
|
||||
|
@ -966,7 +977,7 @@ public class ContactInfo {
|
|||
String displayName;
|
||||
}
|
||||
|
||||
private static class Favicon {
|
||||
static class Favicon {
|
||||
private Bitmap bitmap;
|
||||
private String type;
|
||||
private boolean verified;
|
||||
|
@ -977,7 +988,7 @@ public class ContactInfo {
|
|||
this.source = source;
|
||||
}
|
||||
|
||||
private Favicon(@NonNull Bitmap bitmap, String type, boolean verified) {
|
||||
Favicon(@NonNull Bitmap bitmap, String type, boolean verified) {
|
||||
this.bitmap = bitmap;
|
||||
this.type = type;
|
||||
this.verified = verified;
|
||||
|
|
|
@ -107,6 +107,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
|
|||
private TextView tvBimiHint;
|
||||
private TextView tvBimiUnverified;
|
||||
private SwitchCompat swBimi;
|
||||
private SwitchCompat swEFavicons;
|
||||
private SwitchCompat swFavicons;
|
||||
private SwitchCompat swFaviconsPartial;
|
||||
private TextView tvFaviconsHint;
|
||||
|
@ -187,7 +188,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
|
|||
"nav_options", "nav_categories", "nav_count", "nav_unseen_drafts", "nav_count_pinned", "navbar_colorize",
|
||||
"threading", "threading_unread", "indentation", "seekbar", "actionbar", "actionbar_color", "group_category",
|
||||
"highlight_unread", "highlight_color", "color_stripe", "color_stripe_wide",
|
||||
"avatars", "bimi", "favicons", "favicons_partial", "generated_icons", "identicons",
|
||||
"avatars", "bimi", "efavicons", "favicons", "favicons_partial", "generated_icons", "identicons",
|
||||
"circular", "saturation", "brightness", "threshold",
|
||||
"email_format", "prefer_contact", "only_contact", "distinguish_contacts", "show_recipients",
|
||||
"font_size_sender", "sender_ellipsize",
|
||||
|
@ -257,6 +258,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
|
|||
tvBimiHint = view.findViewById(R.id.tvBimiHint);
|
||||
tvBimiUnverified = view.findViewById(R.id.tvBimiUnverified);
|
||||
ibBimi = view.findViewById(R.id.ibBimi);
|
||||
swEFavicons = view.findViewById(R.id.swEFavicons);
|
||||
swFavicons = view.findViewById(R.id.swFavicons);
|
||||
swFaviconsPartial = view.findViewById(R.id.swFaviconsPartial);
|
||||
tvFaviconsHint = view.findViewById(R.id.tvFaviconsHint);
|
||||
|
@ -713,6 +715,15 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
|
|||
}
|
||||
});
|
||||
|
||||
swEFavicons.setVisibility(BuildConfig.PLAY_STORE_RELEASE ? View.GONE : View.VISIBLE);
|
||||
swEFavicons.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
|
||||
prefs.edit().putBoolean("efavicons", checked).apply();
|
||||
ContactInfo.clearCache(compoundButton.getContext());
|
||||
}
|
||||
});
|
||||
|
||||
swFavicons.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
|
||||
|
@ -1315,6 +1326,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
|
|||
//swColorStripeWide.setEnabled(swColorStripe.isChecked());
|
||||
swAvatars.setChecked(prefs.getBoolean("avatars", true));
|
||||
swBimi.setChecked(prefs.getBoolean("bimi", false));
|
||||
swEFavicons.setChecked(prefs.getBoolean("efavicons", false));
|
||||
swFavicons.setChecked(prefs.getBoolean("favicons", false));
|
||||
swFaviconsPartial.setChecked(prefs.getBoolean("favicons_partial", true));
|
||||
swFaviconsPartial.setEnabled(swFavicons.isChecked());
|
||||
|
|
|
@ -828,6 +828,17 @@
|
|||
app:layout_constraintTop_toBottomOf="@+id/tvBimiVerified"
|
||||
app:srcCompat="@drawable/twotone_info_24" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/swEFavicons"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_advanced_efavicons"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/ibBimi"
|
||||
app:switchPadding="12dp" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/swFavicons"
|
||||
android:layout_width="0dp"
|
||||
|
@ -836,7 +847,7 @@
|
|||
android:text="@string/title_advanced_favicons"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/ibBimi"
|
||||
app:layout_constraintTop_toBottomOf="@id/swEFavicons"
|
||||
app:switchPadding="12dp" />
|
||||
|
||||
<eu.faircode.email.FixedTextView
|
||||
|
|
|
@ -485,6 +485,7 @@
|
|||
<string name="title_advanced_bimi" translatable="false">Show Brand Indicators for Message Identification (BIMI)</string>
|
||||
<string name="title_advanced_bimi_unverified">Unverified sender</string>
|
||||
<string name="title_advanced_bimi_verified">Verified sender</string>
|
||||
<string name="title_advanced_efavicons">Show extravatars</string>
|
||||
<string name="title_advanced_favicons">Show favicons</string>
|
||||
<string name="title_advanced_favicons_partial">Scan only the first %1$s of the web page</string>
|
||||
<string name="title_advanced_generated_icons">Show generated icons</string>
|
||||
|
|
Loading…
Reference in New Issue