mirror of https://github.com/M66B/FairEmail.git
Added translation of received messages
This commit is contained in:
parent
66fe572bb1
commit
c908bca9dc
|
@ -304,6 +304,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
private static final int MAX_RECIPIENTS_COMPACT = 3;
|
||||
private static final int MAX_RECIPIENTS_NORMAL = 7;
|
||||
private static final int MAX_QUOTE_LEVEL = 3;
|
||||
private static final int MAX_TRANSLATE = 1000; // characters
|
||||
|
||||
// https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml
|
||||
private static final List<String> IMAP_KEYWORDS_BLACKLIST = Collections.unmodifiableList(Arrays.asList(
|
||||
|
@ -443,6 +444,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
private ImageButton ibEvent;
|
||||
private ImageButton ibSearchText;
|
||||
private ImageButton ibSearch;
|
||||
private ImageButton ibTranslate;
|
||||
private ImageButton ibHide;
|
||||
private ImageButton ibSeen;
|
||||
private ImageButton ibAnswer;
|
||||
|
@ -675,6 +677,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ibEvent = vsBody.findViewById(R.id.ibEvent);
|
||||
ibSearchText = vsBody.findViewById(R.id.ibSearchText);
|
||||
ibSearch = vsBody.findViewById(R.id.ibSearch);
|
||||
ibTranslate = vsBody.findViewById(R.id.ibTranslate);
|
||||
ibHide = vsBody.findViewById(R.id.ibHide);
|
||||
ibSeen = vsBody.findViewById(R.id.ibSeen);
|
||||
ibAnswer = vsBody.findViewById(R.id.ibAnswer);
|
||||
|
@ -784,6 +787,8 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ibEvent.setOnClickListener(this);
|
||||
ibSearchText.setOnClickListener(this);
|
||||
ibSearch.setOnClickListener(this);
|
||||
ibTranslate.setOnClickListener(this);
|
||||
ibTranslate.setOnLongClickListener(this);
|
||||
ibHide.setOnClickListener(this);
|
||||
ibSeen.setOnClickListener(this);
|
||||
ibAnswer.setOnClickListener(this);
|
||||
|
@ -905,6 +910,8 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ibEvent.setOnClickListener(null);
|
||||
ibSearchText.setOnClickListener(null);
|
||||
ibSearch.setOnClickListener(null);
|
||||
ibTranslate.setOnClickListener(null);
|
||||
ibTranslate.setOnLongClickListener(null);
|
||||
ibHide.setOnClickListener(null);
|
||||
ibSeen.setOnClickListener(null);
|
||||
ibAnswer.setOnClickListener(null);
|
||||
|
@ -1382,6 +1389,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ibEvent.setVisibility(View.GONE);
|
||||
ibSearchText.setVisibility(View.GONE);
|
||||
ibSearch.setVisibility(View.GONE);
|
||||
ibTranslate.setVisibility(View.GONE);
|
||||
ibHide.setVisibility(View.GONE);
|
||||
ibSeen.setVisibility(View.GONE);
|
||||
ibAnswer.setVisibility(View.GONE);
|
||||
|
@ -1591,6 +1599,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ibEvent.setVisibility(View.GONE);
|
||||
ibSearchText.setVisibility(View.GONE);
|
||||
ibSearch.setVisibility(View.GONE);
|
||||
ibTranslate.setVisibility(View.GONE);
|
||||
ibHide.setVisibility(View.GONE);
|
||||
ibSeen.setVisibility(View.GONE);
|
||||
ibAnswer.setVisibility(View.GONE);
|
||||
|
@ -1774,6 +1783,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
boolean button_notes = prefs.getBoolean("button_notes", false);
|
||||
boolean button_seen = prefs.getBoolean("button_seen", false);
|
||||
boolean button_hide = prefs.getBoolean("button_hide", false);
|
||||
boolean button_translate = prefs.getBoolean("button_translate", false);
|
||||
boolean button_search = prefs.getBoolean("button_search", false);
|
||||
boolean button_search_text = prefs.getBoolean("button_search_text", false);
|
||||
boolean button_event = prefs.getBoolean("button_event", false);
|
||||
|
@ -1801,6 +1811,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ibEvent.setVisibility(tools && button_event && message.content ? View.VISIBLE : View.GONE);
|
||||
ibSearchText.setVisibility(tools && button_search_text && message.content && !full ? View.VISIBLE : View.GONE);
|
||||
ibSearch.setVisibility(tools && button_search && (froms > 0 || tos > 0) && !outbox ? View.VISIBLE : View.GONE);
|
||||
ibTranslate.setVisibility(tools && button_translate && DeepL.isAvailable(context) && message.content ? View.VISIBLE : View.GONE);
|
||||
ibHide.setVisibility(tools && button_hide && !outbox ? View.VISIBLE : View.GONE);
|
||||
ibSeen.setVisibility(tools && button_seen && !outbox && seen ? View.VISIBLE : View.GONE);
|
||||
ibAnswer.setVisibility(!tools || outbox || (!expand_all && expand_one) ? View.GONE : View.VISIBLE);
|
||||
|
@ -3187,6 +3198,13 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
onSearchText(message);
|
||||
} else if (id == R.id.ibSearch) {
|
||||
onSearchContact(message);
|
||||
} else if (id == R.id.ibTranslate) {
|
||||
if (DeepL.canTranslate(context))
|
||||
onActionTranslate(message);
|
||||
else {
|
||||
DeepL.FragmentDialogDeepL fragment = new DeepL.FragmentDialogDeepL();
|
||||
fragment.show(parentFragment.getParentFragmentManager(), "deepl:configure");
|
||||
}
|
||||
} else if (id == R.id.ibAnswer) {
|
||||
onActionAnswer(message, ibAnswer);
|
||||
} else if (id == R.id.ibNotes) {
|
||||
|
@ -3344,6 +3362,10 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
} else if (id == R.id.ibNotes) {
|
||||
onActionCopyNote(message);
|
||||
return true;
|
||||
} else if (id == R.id.ibTranslate) {
|
||||
DeepL.FragmentDialogDeepL fragment = new DeepL.FragmentDialogDeepL();
|
||||
fragment.show(parentFragment.getParentFragmentManager(), "deepl:configure");
|
||||
return true;
|
||||
} else if (id == R.id.ibFull) {
|
||||
onActionOpenFull(message);
|
||||
return true;
|
||||
|
@ -4831,6 +4853,15 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
fragment.show(parentFragment.getParentFragmentManager(), "edit:notes");
|
||||
}
|
||||
|
||||
private void onActionTranslate(TupleMessageEx message) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", message.id);
|
||||
|
||||
FragmentDialogTranslate fragment = new FragmentDialogTranslate();
|
||||
fragment.setArguments(args);
|
||||
fragment.show(parentFragment.getParentFragmentManager(), "message:translate");
|
||||
}
|
||||
|
||||
private void onSearchText(TupleMessageEx message) {
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
View dview = inflater.inflate(R.layout.popup_search_in_text, null, false);
|
||||
|
@ -6618,6 +6649,64 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
}
|
||||
}
|
||||
|
||||
public static class FragmentDialogTranslate extends FragmentDialogBase {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
final Context context = getContext();
|
||||
final View view = LayoutInflater.from(context).inflate(R.layout.dialog_translate, null);
|
||||
final TextView tvTranslated = view.findViewById(R.id.tvTranslated);
|
||||
final TextView tvLanguage = view.findViewById(R.id.tvLanguage);
|
||||
final ContentLoadingProgressBar pbWait = view.findViewById(R.id.pbWait);
|
||||
|
||||
tvTranslated.setText(null);
|
||||
tvLanguage.setText(null);
|
||||
|
||||
new SimpleTask<DeepL.Translation>() {
|
||||
@Override
|
||||
protected void onPreExecute(Bundle args) {
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Bundle args) {
|
||||
pbWait.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DeepL.Translation onExecute(Context context, Bundle args) throws Throwable {
|
||||
long id = args.getLong("id");
|
||||
|
||||
File file = EntityMessage.getFile(context, id);
|
||||
Document d = JsoupEx.parse(file);
|
||||
HtmlHelper.truncate(d, MAX_TRANSLATE);
|
||||
|
||||
String text = d.text();
|
||||
String language = DeepL.getCurrentLanguage(context);
|
||||
return DeepL.translate(text, language, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onExecuted(Bundle args, DeepL.Translation translation) {
|
||||
tvTranslated.setText(translation.translated_text);
|
||||
tvLanguage.setText(getString(R.string.title_from_to,
|
||||
translation.detected_language, translation.target_language));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onException(Bundle args, Throwable ex) {
|
||||
tvTranslated.setText(ex.toString());
|
||||
}
|
||||
}.execute(this, getArguments(), "message:translate");
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context)
|
||||
.setView(view)
|
||||
.setPositiveButton(android.R.string.ok, null);
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
|
||||
public static class FragmentDialogKeywordManage extends FragmentDialogBase {
|
||||
@NonNull
|
||||
@Override
|
||||
|
@ -6931,6 +7020,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
final CheckBox cbHide = dview.findViewById(R.id.cbHide);
|
||||
final CheckBox cbSearch = dview.findViewById(R.id.cbSearch);
|
||||
final CheckBox cbSearchText = dview.findViewById(R.id.cbSearchText);
|
||||
final CheckBox cbTranslate = dview.findViewById(R.id.cbTranslate);
|
||||
final CheckBox cbEvent = dview.findViewById(R.id.cbEvent);
|
||||
final CheckBox cbShare = dview.findViewById(R.id.cbShare);
|
||||
final CheckBox cbPin = dview.findViewById(R.id.cbPin);
|
||||
|
@ -6939,6 +7029,9 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
final CheckBox cbUnsubscribe = dview.findViewById(R.id.cbUnsubscribe);
|
||||
final CheckBox cbRule = dview.findViewById(R.id.cbRule);
|
||||
|
||||
cbTranslate.setVisibility(DeepL.isAvailable(context) &&
|
||||
DeepL.getCurrentLanguage(context) != null
|
||||
? View.VISIBLE : View.GONE);
|
||||
cbPin.setVisibility(Shortcuts.can(context) ? View.VISIBLE : View.GONE);
|
||||
|
||||
cbJunk.setChecked(prefs.getBoolean("button_junk", true));
|
||||
|
@ -6952,6 +7045,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
cbHide.setChecked(prefs.getBoolean("button_hide", false));
|
||||
cbSearch.setChecked(prefs.getBoolean("button_search", false));
|
||||
cbSearchText.setChecked(prefs.getBoolean("button_search_text", false));
|
||||
cbTranslate.setChecked(prefs.getBoolean("button_translate", false));
|
||||
cbEvent.setChecked(prefs.getBoolean("button_event", false));
|
||||
cbShare.setChecked(prefs.getBoolean("button_share", false));
|
||||
cbPin.setChecked(prefs.getBoolean("button_pin", false));
|
||||
|
@ -6977,6 +7071,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
editor.putBoolean("button_hide", cbHide.isChecked());
|
||||
editor.putBoolean("button_search", cbSearch.isChecked());
|
||||
editor.putBoolean("button_search_text", cbSearchText.isChecked());
|
||||
editor.putBoolean("button_translate", cbTranslate.isChecked());
|
||||
editor.putBoolean("button_event", cbEvent.isChecked());
|
||||
editor.putBoolean("button_share", cbShare.isChecked());
|
||||
editor.putBoolean("button_pin", cbPin.isChecked());
|
||||
|
|
|
@ -62,10 +62,12 @@ import javax.net.ssl.HttpsURLConnection;
|
|||
|
||||
public class DeepL {
|
||||
// https://www.deepl.com/docs-api/
|
||||
private static JSONArray jlanguages = null;
|
||||
|
||||
private static final int DEEPL_TIMEOUT = 20; // seconds
|
||||
|
||||
// curl https://api-free.deepl.com/v2/languages \
|
||||
// -d auth_key=42c191db-21ba-9b96-2464-47a9a5e81b4a:fx \
|
||||
// -d auth_key=... \
|
||||
// -d type=target
|
||||
|
||||
public static boolean isAvailable(Context context) {
|
||||
|
@ -81,17 +83,16 @@ public class DeepL {
|
|||
}
|
||||
|
||||
public static List<Language> getTargetLanguages(Context context) {
|
||||
try (InputStream is = context.getAssets().open("deepl.json")) {
|
||||
String json = Helper.readStream(is);
|
||||
JSONArray jarray = new JSONArray(json);
|
||||
try {
|
||||
ensureLanguages(context);
|
||||
|
||||
String pkg = context.getPackageName();
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
List<Language> languages = new ArrayList<>();
|
||||
Map<String, Integer> frequencies = new HashMap<>();
|
||||
for (int i = 0; i < jarray.length(); i++) {
|
||||
JSONObject jlanguage = jarray.getJSONObject(i);
|
||||
for (int i = 0; i < jlanguages.length(); i++) {
|
||||
JSONObject jlanguage = jlanguages.getJSONObject(i);
|
||||
String name = jlanguage.getString("name");
|
||||
String target = jlanguage.getString("language");
|
||||
|
||||
|
@ -132,6 +133,36 @@ public class DeepL {
|
|||
}
|
||||
}
|
||||
|
||||
public static String getCurrentLanguage(Context context) {
|
||||
try {
|
||||
ensureLanguages(context);
|
||||
|
||||
Locale locale = Locale.getDefault();
|
||||
for (int i = 0; i < jlanguages.length(); i++) {
|
||||
JSONObject jlanguage = jlanguages.getJSONObject(i);
|
||||
String language = jlanguage.getString("language");
|
||||
if (language.equalsIgnoreCase(locale.toLanguageTag()) ||
|
||||
language.equalsIgnoreCase(locale.getLanguage())) {
|
||||
return language;
|
||||
}
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void ensureLanguages(Context context) throws IOException, JSONException {
|
||||
if (jlanguages != null)
|
||||
return;
|
||||
|
||||
try (InputStream is = context.getAssets().open("deepl.json")) {
|
||||
String json = Helper.readStream(is);
|
||||
jlanguages = new JSONArray(json);
|
||||
}
|
||||
}
|
||||
|
||||
public static Pair<Integer, Integer> getParagraph(EditText etBody) {
|
||||
int start = etBody.getSelectionStart();
|
||||
int end = etBody.getSelectionEnd();
|
||||
|
@ -171,7 +202,7 @@ public class DeepL {
|
|||
return null;
|
||||
}
|
||||
|
||||
public static String translate(String text, String target, Context context) throws IOException, JSONException {
|
||||
public static Translation translate(String text, String target, Context context) throws IOException, JSONException {
|
||||
String request =
|
||||
"text=" + URLEncoder.encode(text, StandardCharsets.UTF_8.name()) +
|
||||
"&target_lang=" + URLEncoder.encode(target, StandardCharsets.UTF_8.name());
|
||||
|
@ -212,9 +243,12 @@ public class DeepL {
|
|||
if (jtranslations.length() == 0)
|
||||
throw new FileNotFoundException();
|
||||
JSONObject jtranslation = (JSONObject) jtranslations.get(0);
|
||||
String detected = jtranslation.getString("detected_source_language");
|
||||
String translated = jtranslation.getString("text");
|
||||
return translated;
|
||||
|
||||
Translation result = new Translation();
|
||||
result.target_language = target;
|
||||
result.detected_language = jtranslation.getString("detected_source_language");
|
||||
result.translated_text = jtranslation.getString("text");
|
||||
return result;
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
|
@ -273,6 +307,12 @@ public class DeepL {
|
|||
}
|
||||
}
|
||||
|
||||
public static class Translation {
|
||||
public String detected_language;
|
||||
public String target_language;
|
||||
public String translated_text;
|
||||
}
|
||||
|
||||
public static class FragmentDialogDeepL extends FragmentDialogBase {
|
||||
@NonNull
|
||||
@Override
|
||||
|
@ -324,8 +364,8 @@ public class DeepL {
|
|||
|
||||
@Override
|
||||
protected void onException(Bundle args, Throwable ex) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.unexpectedError(getParentFragmentManager(), ex);
|
||||
tvUsage.setText(Log.formatThrowable(ex, false));
|
||||
tvUsage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}.execute(this, new Bundle(), "deepl:usage");
|
||||
}
|
||||
|
|
|
@ -755,21 +755,21 @@ public class FragmentCompose extends FragmentBase {
|
|||
args.putString("target", target);
|
||||
args.putString("text", text);
|
||||
|
||||
new SimpleTask<String>() {
|
||||
new SimpleTask<DeepL.Translation>() {
|
||||
@Override
|
||||
protected void onPreExecute(Bundle args) {
|
||||
ToastEx.makeText(getContext(), R.string.title_translating, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String onExecute(Context context, Bundle args) throws Throwable {
|
||||
protected DeepL.Translation onExecute(Context context, Bundle args) throws Throwable {
|
||||
String target = args.getString("target");
|
||||
String text = args.getString("text");
|
||||
return DeepL.translate(text, target, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onExecuted(Bundle args, String translated) {
|
||||
protected void onExecuted(Bundle args, DeepL.Translation translation) {
|
||||
if (paragraph.second > edit.length())
|
||||
return;
|
||||
|
||||
|
@ -781,8 +781,8 @@ public class FragmentCompose extends FragmentBase {
|
|||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
// Insert translated text
|
||||
edit.insert(paragraph.second, "\n\n" + translated);
|
||||
etBody.setSelection(paragraph.second + 2 + translated.length());
|
||||
edit.insert(paragraph.second, "\n\n" + translation.translated_text);
|
||||
etBody.setSelection(paragraph.second + 2 + translation.translated_text.length());
|
||||
|
||||
boolean small = prefs.getBoolean("deepl_small", false);
|
||||
if (small) {
|
||||
|
|
|
@ -174,6 +174,19 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbSearch" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbTranslate"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:drawableEnd="@drawable/outline_translate_24"
|
||||
android:drawablePadding="6dp"
|
||||
android:text="@string/title_translate"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbSearchText" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbEvent"
|
||||
android:layout_width="0dp"
|
||||
|
@ -185,7 +198,7 @@
|
|||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbSearchText" />
|
||||
app:layout_constraintTop_toBottomOf="@id/cbTranslate" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbShare"
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<eu.faircode.email.ScrollViewEx xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="24dp"
|
||||
android:scrollbarStyle="outsideOverlay">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<eu.faircode.email.FixedTextView
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@+id/etKeyword"
|
||||
android:text="@string/title_translate"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Large"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<eu.faircode.email.FixedTextView
|
||||
android:id="@+id/tvTranslated"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:minHeight="60dp"
|
||||
android:text="Text"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvTitle" />
|
||||
|
||||
<eu.faircode.email.FixedTextView
|
||||
android:id="@+id/tvLanguage"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_from_to"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvTranslated" />
|
||||
|
||||
<eu.faircode.email.ContentLoadingProgressBar
|
||||
android:id="@+id/pbWait"
|
||||
style="@style/Base.Widget.AppCompat.ProgressBar"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:indeterminate="true"
|
||||
app:layout_constraintBottom_toBottomOf="@id/tvTranslated"
|
||||
app:layout_constraintEnd_toEndOf="@id/tvTranslated"
|
||||
app:layout_constraintStart_toStartOf="@id/tvTranslated"
|
||||
app:layout_constraintTop_toTopOf="@id/tvTranslated" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</eu.faircode.email.ScrollViewEx>
|
|
@ -42,7 +42,7 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="3dp"
|
||||
app:constraint_referenced_ids="ibMore,ibInbox,ibJunk,ibTrash,ibArchive,ibMove,ibCopy,ibKeywords,ibLabels,ibNotes,ibAnswer,ibSeen,ibHide,ibSearch,ibSearchText,ibEvent,ibShare,ibPin,ibPrint,ibHeaders,ibUnsubscribe,ibRule,ibUndo"
|
||||
app:constraint_referenced_ids="ibMore,ibInbox,ibJunk,ibTrash,ibArchive,ibMove,ibCopy,ibKeywords,ibLabels,ibNotes,ibAnswer,ibSeen,ibHide,ibTranslate,ibSearch,ibSearchText,ibEvent,ibShare,ibPin,ibPrint,ibHeaders,ibUnsubscribe,ibRule,ibUndo"
|
||||
app:flow_horizontalBias="0"
|
||||
app:flow_horizontalGap="3dp"
|
||||
app:flow_horizontalStyle="packed"
|
||||
|
@ -201,6 +201,18 @@
|
|||
app:srcCompat="@drawable/twotone_visibility_off_24"
|
||||
tools:ignore="MissingConstraints" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/ibTranslate"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/title_translate"
|
||||
android:padding="6dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:tooltipText="@string/title_translate"
|
||||
app:srcCompat="@drawable/outline_translate_24"
|
||||
tools:ignore="MissingConstraints" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/ibSearch"
|
||||
android:layout_width="36dp"
|
||||
|
|
Loading…
Reference in New Issue