mirror of https://github.com/M66B/FairEmail.git
Moved password protecting to style menu
This commit is contained in:
parent
d4abad6684
commit
427721d871
|
@ -58,16 +58,9 @@ import androidx.preference.PreferenceManager;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
import javax.crypto.SecretKeyFactory;
|
|
||||||
import javax.crypto.spec.GCMParameterSpec;
|
|
||||||
import javax.crypto.spec.PBEKeySpec;
|
|
||||||
import javax.mail.internet.AddressException;
|
import javax.mail.internet.AddressException;
|
||||||
import javax.mail.internet.InternetAddress;
|
import javax.mail.internet.InternetAddress;
|
||||||
|
|
||||||
|
@ -122,7 +115,6 @@ public class EditTextCompose extends FixedEditText {
|
||||||
int order = 1000;
|
int order = 1000;
|
||||||
menu.add(Menu.CATEGORY_SECONDARY, R.string.title_insert_brackets, order++, context.getString(R.string.title_insert_brackets));
|
menu.add(Menu.CATEGORY_SECONDARY, R.string.title_insert_brackets, order++, context.getString(R.string.title_insert_brackets));
|
||||||
menu.add(Menu.CATEGORY_SECONDARY, R.string.title_insert_quotes, order++, context.getString(R.string.title_insert_quotes));
|
menu.add(Menu.CATEGORY_SECONDARY, R.string.title_insert_quotes, order++, context.getString(R.string.title_insert_quotes));
|
||||||
menu.add(Menu.CATEGORY_SECONDARY, R.string.title_protect_text, order++, context.getString(R.string.title_protect_text));
|
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
Log.e(ex);
|
Log.e(ex);
|
||||||
}
|
}
|
||||||
|
@ -147,8 +139,6 @@ public class EditTextCompose extends FixedEditText {
|
||||||
return surround("(", ")");
|
return surround("(", ")");
|
||||||
else if (id == R.string.title_insert_quotes)
|
else if (id == R.string.title_insert_quotes)
|
||||||
return surround("\"", "\"");
|
return surround("\"", "\"");
|
||||||
else if (id == R.string.title_protect_text)
|
|
||||||
return protect();
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -178,81 +168,6 @@ public class EditTextCompose extends FixedEditText {
|
||||||
}
|
}
|
||||||
return selection;
|
return selection;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean protect() {
|
|
||||||
Editable edit = getText();
|
|
||||||
int start = getSelectionStart();
|
|
||||||
int end = getSelectionEnd();
|
|
||||||
boolean selection = (edit != null && start >= 0 && start < end);
|
|
||||||
if (selection)
|
|
||||||
executor.submit(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
String password = "FairEmail";
|
|
||||||
String text = HtmlHelper.toHtml((Spanned) edit.subSequence(start, end), context);
|
|
||||||
|
|
||||||
SecureRandom random = new SecureRandom();
|
|
||||||
|
|
||||||
byte[] salt = new byte[16]; // 128 bits
|
|
||||||
random.nextBytes(salt);
|
|
||||||
|
|
||||||
byte[] iv = new byte[12]; // 96 bites
|
|
||||||
random.nextBytes(iv);
|
|
||||||
|
|
||||||
// Iterations = 120,000; Keylength = 256 bits = 32 bytes
|
|
||||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256);
|
|
||||||
|
|
||||||
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
|
|
||||||
SecretKey key = skf.generateSecret(spec);
|
|
||||||
|
|
||||||
// Authentication tag length = 128 bits = 16 bytes
|
|
||||||
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv);
|
|
||||||
|
|
||||||
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
|
|
||||||
|
|
||||||
byte[] cipherText = cipher.doFinal(text.getBytes());
|
|
||||||
|
|
||||||
ByteBuffer out = ByteBuffer.allocate(1 + salt.length + iv.length + cipherText.length);
|
|
||||||
out.put((byte) 1); //version
|
|
||||||
out.put(salt);
|
|
||||||
out.put(iv);
|
|
||||||
out.put(cipherText);
|
|
||||||
String fragment = Base64.encodeToString(out.array(), Base64.URL_SAFE | Base64.NO_WRAP);
|
|
||||||
|
|
||||||
post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
if (getSelectionStart() != start || getSelectionEnd() != end)
|
|
||||||
return;
|
|
||||||
String title = context.getString(R.string.title_decrypt);
|
|
||||||
String url = "https://email.faircode.eu/decrypt/#" + fragment;
|
|
||||||
edit.delete(start, end);
|
|
||||||
edit.insert(start, title);
|
|
||||||
edit.setSpan(new URLSpan(url), start, start + title.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
setSelection(start + title.length());
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
Log.e(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
Log.e(ex);
|
|
||||||
post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
ToastEx.makeText(context, ex.toString(), Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return selection;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setCustomInsertionActionModeCallback(new ActionMode.Callback() {
|
setCustomInsertionActionModeCallback(new ActionMode.Callback() {
|
||||||
|
|
|
@ -19,6 +19,7 @@ package eu.faircode.email;
|
||||||
Copyright 2018-2022 by Marcel Bokhorst (M66B)
|
Copyright 2018-2022 by Marcel Bokhorst (M66B)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
@ -26,6 +27,7 @@ import android.content.SharedPreferences;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import android.text.NoCopySpan;
|
import android.text.NoCopySpan;
|
||||||
|
@ -47,14 +49,20 @@ import android.text.style.StyleSpan;
|
||||||
import android.text.style.TypefaceSpan;
|
import android.text.style.TypefaceSpan;
|
||||||
import android.text.style.URLSpan;
|
import android.text.style.URLSpan;
|
||||||
import android.text.style.UnderlineSpan;
|
import android.text.style.UnderlineSpan;
|
||||||
|
import android.util.Base64;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.SubMenu;
|
import android.view.SubMenu;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.PopupMenu;
|
import androidx.appcompat.widget.PopupMenu;
|
||||||
import androidx.core.content.res.ResourcesCompat;
|
import androidx.core.content.res.ResourcesCompat;
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
@ -63,13 +71,23 @@ import androidx.preference.PreferenceManager;
|
||||||
import com.flask.colorpicker.ColorPickerView;
|
import com.flask.colorpicker.ColorPickerView;
|
||||||
import com.flask.colorpicker.builder.ColorPickerClickListener;
|
import com.flask.colorpicker.builder.ColorPickerClickListener;
|
||||||
import com.flask.colorpicker.builder.ColorPickerDialogBuilder;
|
import com.flask.colorpicker.builder.ColorPickerDialogBuilder;
|
||||||
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.SecretKeyFactory;
|
||||||
|
import javax.crypto.spec.GCMParameterSpec;
|
||||||
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
|
|
||||||
public class StyleHelper {
|
public class StyleHelper {
|
||||||
private static final List<Class> CLEAR_STYLES = Collections.unmodifiableList(Arrays.asList(
|
private static final List<Class> CLEAR_STYLES = Collections.unmodifiableList(Arrays.asList(
|
||||||
StyleSpan.class,
|
StyleSpan.class,
|
||||||
|
@ -214,6 +232,7 @@ public class StyleHelper {
|
||||||
popupMenu.getMenu().findItem(R.id.menu_style_indentation_increase).setEnabled(maxLevel == null);
|
popupMenu.getMenu().findItem(R.id.menu_style_indentation_increase).setEnabled(maxLevel == null);
|
||||||
popupMenu.getMenu().findItem(R.id.menu_style_indentation_decrease).setEnabled(indents.length > 0);
|
popupMenu.getMenu().findItem(R.id.menu_style_indentation_decrease).setEnabled(indents.length > 0);
|
||||||
|
|
||||||
|
popupMenu.getMenu().findItem(R.id.menu_style_password).setVisible(!BuildConfig.PLAY_STORE_RELEASE);
|
||||||
popupMenu.getMenu().findItem(R.id.menu_style_code).setEnabled(BuildConfig.DEBUG);
|
popupMenu.getMenu().findItem(R.id.menu_style_code).setEnabled(BuildConfig.DEBUG);
|
||||||
|
|
||||||
popupMenu.insertIcons(context);
|
popupMenu.insertIcons(context);
|
||||||
|
@ -253,6 +272,8 @@ public class StyleHelper {
|
||||||
return setSuperscript(item);
|
return setSuperscript(item);
|
||||||
} else if (groupId == R.id.group_style_strikethrough) {
|
} else if (groupId == R.id.group_style_strikethrough) {
|
||||||
return setStrikeThrough(item);
|
return setStrikeThrough(item);
|
||||||
|
} else if (groupId == R.id.group_style_password) {
|
||||||
|
return setPassword(item);
|
||||||
} else if (groupId == R.id.group_style_code) {
|
} else if (groupId == R.id.group_style_code) {
|
||||||
return setCode(item);
|
return setCode(item);
|
||||||
} else if (groupId == R.id.group_style_clear) {
|
} else if (groupId == R.id.group_style_clear) {
|
||||||
|
@ -544,6 +565,117 @@ public class StyleHelper {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean setPassword(MenuItem item) {
|
||||||
|
if (!ActivityBilling.isPro(context)) {
|
||||||
|
context.startActivity(new Intent(context, ActivityBilling.class));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
View dview = LayoutInflater.from(context).inflate(R.layout.dialog_password_protect, null);
|
||||||
|
TextInputLayout etPassword1 = dview.findViewById(R.id.tilPassword1);
|
||||||
|
TextInputLayout etPassword2 = dview.findViewById(R.id.tilPassword2);
|
||||||
|
|
||||||
|
Dialog dialog = new AlertDialog.Builder(context)
|
||||||
|
.setView(dview)
|
||||||
|
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
String password1 = etPassword1.getEditText().getText().toString();
|
||||||
|
String password2 = etPassword2.getEditText().getText().toString();
|
||||||
|
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
boolean debug = prefs.getBoolean("debug", false);
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(password1) && !(debug || BuildConfig.DEBUG))
|
||||||
|
ToastEx.makeText(context, R.string.title_setup_password_missing, Toast.LENGTH_LONG).show();
|
||||||
|
else {
|
||||||
|
if (password1.equals(password2)) {
|
||||||
|
int start = etBody.getSelectionStart();
|
||||||
|
int end = etBody.getSelectionEnd();
|
||||||
|
boolean selection = (start >= 0 && start < end);
|
||||||
|
if (selection) {
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putCharSequence("text", edit.subSequence(start, end));
|
||||||
|
args.putString("password", password1);
|
||||||
|
args.putInt("start", start);
|
||||||
|
args.putInt("end", end);
|
||||||
|
|
||||||
|
new SimpleTask<String>() {
|
||||||
|
@Override
|
||||||
|
protected String onExecute(Context context, Bundle args) throws Throwable {
|
||||||
|
Spanned text = (Spanned) args.getCharSequence("text");
|
||||||
|
String password = args.getString("password");
|
||||||
|
String html = HtmlHelper.toHtml(text, context);
|
||||||
|
|
||||||
|
SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
|
byte[] salt = new byte[16]; // 128 bits
|
||||||
|
random.nextBytes(salt);
|
||||||
|
|
||||||
|
byte[] iv = new byte[12]; // 96 bites
|
||||||
|
random.nextBytes(iv);
|
||||||
|
|
||||||
|
// Iterations = 120,000; Keylength = 256 bits = 32 bytes
|
||||||
|
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 120000, 256);
|
||||||
|
|
||||||
|
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
|
||||||
|
SecretKey key = skf.generateSecret(spec);
|
||||||
|
|
||||||
|
// Authentication tag length = 128 bits = 16 bytes
|
||||||
|
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv);
|
||||||
|
|
||||||
|
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
|
||||||
|
|
||||||
|
byte[] cipherText = cipher.doFinal(html.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
ByteBuffer out = ByteBuffer.allocate(1 + salt.length + iv.length + cipherText.length);
|
||||||
|
out.put((byte) 1); // version
|
||||||
|
out.put(salt);
|
||||||
|
out.put(iv);
|
||||||
|
out.put(cipherText);
|
||||||
|
|
||||||
|
String fragment = Base64.encodeToString(out.array(), Base64.URL_SAFE | Base64.NO_WRAP);
|
||||||
|
String url = "https://email.faircode.eu/decrypt/#" + fragment;
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onExecuted(Bundle args, String url) {
|
||||||
|
if (etBody.getSelectionStart() != start ||
|
||||||
|
etBody.getSelectionEnd() != end)
|
||||||
|
return;
|
||||||
|
|
||||||
|
String title = context.getString(R.string.title_decrypt);
|
||||||
|
edit.delete(start, end);
|
||||||
|
edit.insert(start, title);
|
||||||
|
edit.setSpan(new URLSpan(url), start, start + title.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
etBody.setSelection(start + title.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onException(Bundle args, Throwable ex) {
|
||||||
|
Log.e(ex);
|
||||||
|
ToastEx.makeText(context, ex.toString(), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}.execute(context, owner, args, "protect");
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
ToastEx.makeText(context, R.string.title_setup_password_different, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||||
|
// WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean setCode(MenuItem item) {
|
private boolean setCode(MenuItem item) {
|
||||||
Log.breadcrumb("style", "action", "code");
|
Log.breadcrumb("style", "action", "code");
|
||||||
_setSize(HtmlHelper.FONT_SMALL);
|
_setSize(HtmlHelper.FONT_SMALL);
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?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:fadeScrollbars="false"
|
||||||
|
android:padding="24dp"
|
||||||
|
android:scrollbarStyle="outsideOverlay">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvCaption"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:drawableStart="@drawable/twotone_lock_24"
|
||||||
|
android:drawablePadding="6dp"
|
||||||
|
android:text="@string/title_style_password"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Large"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/tilPassword1"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
app:endIconMode="password_toggle"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tvCaption">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:autofillHints="password"
|
||||||
|
android:hint="@string/title_setup_password"
|
||||||
|
android:imeOptions="actionNext"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Medium">
|
||||||
|
|
||||||
|
<requestFocus />
|
||||||
|
</com.google.android.material.textfield.TextInputEditText>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/tilPassword2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:endIconMode="password_toggle"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tilPassword1">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:autofillHints="password"
|
||||||
|
android:hint="@string/title_setup_password_repeat"
|
||||||
|
android:imeOptions="actionDone"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvHint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="@string/title_style_password_remark"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
|
android:textStyle="italic"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tilPassword2" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</eu.faircode.email.ScrollViewEx>
|
|
@ -178,8 +178,17 @@
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<group
|
<group
|
||||||
android:id="@+id/group_style_code"
|
android:id="@+id/group_style_password"
|
||||||
android:orderInCategory="13">
|
android:orderInCategory="13">
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_style_password"
|
||||||
|
android:icon="@drawable/twotone_lock_24"
|
||||||
|
android:title="@string/title_style_password" />
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<group
|
||||||
|
android:id="@+id/group_style_code"
|
||||||
|
android:orderInCategory="14">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/menu_style_code"
|
android:id="@+id/menu_style_code"
|
||||||
android:icon="@drawable/twotone_code_24"
|
android:icon="@drawable/twotone_code_24"
|
||||||
|
@ -188,7 +197,7 @@
|
||||||
|
|
||||||
<group
|
<group
|
||||||
android:id="@+id/group_style_clear"
|
android:id="@+id/group_style_clear"
|
||||||
android:orderInCategory="14">
|
android:orderInCategory="15">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/menu_style_clear"
|
android:id="@+id/menu_style_clear"
|
||||||
android:icon="@drawable/twotone_format_clear_24"
|
android:icon="@drawable/twotone_format_clear_24"
|
||||||
|
|
|
@ -1448,6 +1448,8 @@
|
||||||
<string name="title_style_superscript">Superscript</string>
|
<string name="title_style_superscript">Superscript</string>
|
||||||
<string name="title_style_strikethrough">Strikethrough</string>
|
<string name="title_style_strikethrough">Strikethrough</string>
|
||||||
<string name="title_style_code" translatable="false">Code</string>
|
<string name="title_style_code" translatable="false">Code</string>
|
||||||
|
<string name="title_style_password">Password protect</string>
|
||||||
|
<string name="title_style_password_remark" translatable="false">AES/GCM 256 bit PBKDF2/SHA-256/120,000</string>
|
||||||
<string name="title_style_clear">Clear formatting</string>
|
<string name="title_style_clear">Clear formatting</string>
|
||||||
<string name="title_style_link">Insert link</string>
|
<string name="title_style_link">Insert link</string>
|
||||||
<string name="title_style_link_address">Address</string>
|
<string name="title_style_link_address">Address</string>
|
||||||
|
@ -1954,7 +1956,6 @@
|
||||||
<string name="title_select_block">Select block</string>
|
<string name="title_select_block">Select block</string>
|
||||||
<string name="title_insert_brackets">Bracket</string>
|
<string name="title_insert_brackets">Bracket</string>
|
||||||
<string name="title_insert_quotes">Quote</string>
|
<string name="title_insert_quotes">Quote</string>
|
||||||
<string name="title_protect_text">Protect</string>
|
|
||||||
<string name="title_add">Add</string>
|
<string name="title_add">Add</string>
|
||||||
<string name="title_browse">Open with</string>
|
<string name="title_browse">Open with</string>
|
||||||
<string name="title_info">Info</string>
|
<string name="title_info">Info</string>
|
||||||
|
|
Loading…
Reference in New Issue