mirror of https://github.com/M66B/FairEmail.git
Added text snippets
This commit is contained in:
parent
88605fd842
commit
0da04b81d1
File diff suppressed because it is too large
Load Diff
|
@ -75,6 +75,7 @@ public class AdapterAnswer extends RecyclerView.Adapter<AdapterAnswer.ViewHolder
|
|||
private TextView tvName;
|
||||
private ImageView ivExternal;
|
||||
private ImageView ivStandard;
|
||||
private ImageView ivSnippet;
|
||||
private ImageView ivFavorite;
|
||||
private ImageView ivReceipt;
|
||||
private TextView tvLastApplied;
|
||||
|
@ -90,6 +91,7 @@ public class AdapterAnswer extends RecyclerView.Adapter<AdapterAnswer.ViewHolder
|
|||
tvName = itemView.findViewById(R.id.tvName);
|
||||
ivExternal = itemView.findViewById(R.id.ivExternal);
|
||||
ivStandard = itemView.findViewById(R.id.ivStandard);
|
||||
ivSnippet = itemView.findViewById(R.id.ivSnippet);
|
||||
ivFavorite = itemView.findViewById(R.id.ivFavorite);
|
||||
ivReceipt = itemView.findViewById(R.id.ivReceipt);
|
||||
tvLastApplied = itemView.findViewById(R.id.tvLastApplied);
|
||||
|
@ -112,6 +114,7 @@ public class AdapterAnswer extends RecyclerView.Adapter<AdapterAnswer.ViewHolder
|
|||
tvName.setText(answer.name);
|
||||
ivExternal.setVisibility(answer.external ? View.VISIBLE : View.GONE);
|
||||
ivStandard.setVisibility(answer.standard ? View.VISIBLE : View.GONE);
|
||||
ivSnippet.setVisibility(answer.snippet ? View.VISIBLE : View.GONE);
|
||||
ivFavorite.setVisibility(answer.favorite ? View.VISIBLE : View.GONE);
|
||||
ivReceipt.setVisibility(answer.receipt ? View.VISIBLE : View.GONE);
|
||||
tvLastApplied.setText(answer.last_applied == null ? null : DF.format(answer.last_applied));
|
||||
|
|
|
@ -71,7 +71,7 @@ import io.requery.android.database.sqlite.SQLiteDatabase;
|
|||
// https://developer.android.com/topic/libraries/architecture/room.html
|
||||
|
||||
@Database(
|
||||
version = 225,
|
||||
version = 226,
|
||||
entities = {
|
||||
EntityIdentity.class,
|
||||
EntityAccount.class,
|
||||
|
@ -2285,6 +2285,12 @@ public abstract class DB extends RoomDatabase {
|
|||
" SET auto_delete = 0" +
|
||||
" WHERE type ='" + EntityFolder.JUNK + "'");
|
||||
}
|
||||
}).addMigrations(new Migration(225, 226) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase db) {
|
||||
logMigration(startVersion, endVersion);
|
||||
db.execSQL("ALTER TABLE `answer` ADD COLUMN `snippet` INTEGER NOT NULL DEFAULT 0");
|
||||
}
|
||||
}).addMigrations(new Migration(998, 999) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase db) {
|
||||
|
|
|
@ -40,6 +40,12 @@ public interface DaoAnswer {
|
|||
" ORDER BY name COLLATE NOCASE")
|
||||
List<EntityAnswer> getAnswersByFavorite(boolean favorite);
|
||||
|
||||
@Query("SELECT * FROM answer" +
|
||||
" WHERE snippet" +
|
||||
" AND NOT hide" +
|
||||
" ORDER BY name COLLATE NOCASE")
|
||||
List<EntityAnswer> getSnippets();
|
||||
|
||||
@Query("SELECT * FROM answer" +
|
||||
" WHERE external" +
|
||||
" AND NOT hide" +
|
||||
|
|
|
@ -22,6 +22,7 @@ package eu.faircode.email;
|
|||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
@ -50,9 +51,11 @@ import androidx.core.view.inputmethod.EditorInfoCompat;
|
|||
import androidx.core.view.inputmethod.InputConnectionCompat;
|
||||
import androidx.core.view.inputmethod.InputContentInfoCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.room.EntityInsertionAdapter;
|
||||
|
||||
import org.jsoup.nodes.Document;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class EditTextCompose extends FixedEditText {
|
||||
|
@ -62,6 +65,12 @@ public class EditTextCompose extends FixedEditText {
|
|||
|
||||
private Boolean canUndo = null;
|
||||
private Boolean canRedo = null;
|
||||
private List<EntityAnswer> snippets;
|
||||
|
||||
private int colorPrimary;
|
||||
private int colorBlockquote;
|
||||
private int quoteGap;
|
||||
private int quoteStripe;
|
||||
|
||||
private static final ExecutorService executor =
|
||||
Helper.getBackgroundExecutor(1, "paste");
|
||||
|
@ -84,6 +93,11 @@ public class EditTextCompose extends FixedEditText {
|
|||
void init(Context context) {
|
||||
Helper.setKeyboardIncognitoMode(this, context);
|
||||
|
||||
colorPrimary = Helper.resolveColor(context, R.attr.colorPrimary);
|
||||
colorBlockquote = Helper.resolveColor(context, R.attr.colorBlockquote, colorPrimary);
|
||||
quoteGap = context.getResources().getDimensionPixelSize(R.dimen.quote_gap_size);
|
||||
quoteStripe = context.getResources().getDimensionPixelSize(R.dimen.quote_stripe_width);
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean undo_manager = prefs.getBoolean("undo_manager", false);
|
||||
|
||||
|
@ -92,13 +106,20 @@ public class EditTextCompose extends FixedEditText {
|
|||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
try {
|
||||
int order = 1000;
|
||||
if (undo_manager && can(android.R.id.undo))
|
||||
menu.add(Menu.CATEGORY_SECONDARY, R.string.title_undo, 1001, getTitle(R.string.title_undo));
|
||||
menu.add(Menu.CATEGORY_SECONDARY, R.string.title_undo, order++, getTitle(R.string.title_undo));
|
||||
if (undo_manager && can(android.R.id.redo))
|
||||
menu.add(Menu.CATEGORY_SECONDARY, R.string.title_redo, 1002, getTitle(R.string.title_redo));
|
||||
menu.add(Menu.CATEGORY_SECONDARY, R.string.title_insert_line, 1003, context.getString(R.string.title_insert_line));
|
||||
menu.add(Menu.CATEGORY_SECONDARY, R.string.title_redo, order++, getTitle(R.string.title_redo));
|
||||
menu.add(Menu.CATEGORY_SECONDARY, R.string.title_insert_line, order++, context.getString(R.string.title_insert_line));
|
||||
if (BuildConfig.DEBUG)
|
||||
menu.add(Menu.CATEGORY_SECONDARY, R.string.title_insert_arrow, 1004, context.getString(R.string.title_insert_arrow));
|
||||
menu.add(Menu.CATEGORY_SECONDARY, R.string.title_insert_arrow, order++, context.getString(R.string.title_insert_arrow));
|
||||
if (snippets != null)
|
||||
for (EntityAnswer snippet : snippets) {
|
||||
menu.add(Menu.CATEGORY_SECONDARY, order, order, snippet.name).
|
||||
setIntent(new Intent().putExtra("id", snippet.id));
|
||||
order++;
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(ex);
|
||||
}
|
||||
|
@ -128,6 +149,12 @@ public class EditTextCompose extends FixedEditText {
|
|||
return insertLine();
|
||||
else if (id == R.string.title_insert_arrow)
|
||||
return insertArrow();
|
||||
else {
|
||||
Intent intent = item.getIntent();
|
||||
if (intent == null)
|
||||
return false;
|
||||
return insertSnippet(intent.getLongExtra("id", -1L));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -183,6 +210,61 @@ public class EditTextCompose extends FixedEditText {
|
|||
edit.insert(start, " \u27f6 ");
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean insertSnippet(long id) {
|
||||
if (snippets == null)
|
||||
return false;
|
||||
|
||||
for (EntityAnswer snippet : snippets)
|
||||
if (snippet.id.equals(id)) {
|
||||
String html = snippet.getHtml(null);
|
||||
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
SpannableStringBuilder ssb = getSpanned(context, html);
|
||||
int len = ssb.length();
|
||||
if (len > 0 && ssb.charAt(len - 1) == '\n')
|
||||
ssb.replace(len - 1, len, " ");
|
||||
|
||||
EditTextCompose.this.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
int start = getSelectionStart();
|
||||
if (start < 0)
|
||||
start = 0;
|
||||
getText().insert(start, ssb);
|
||||
setSelection(start + ssb.length());
|
||||
} catch (Throwable ex) {
|
||||
Log.e(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable ex) {
|
||||
Log.e(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
DB db = DB.getInstance(context);
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
snippets = db.answer().getSnippets();
|
||||
} catch (Throwable ex) {
|
||||
Log.e(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -286,45 +368,15 @@ public class EditTextCompose extends FixedEditText {
|
|||
} else
|
||||
html = h;
|
||||
|
||||
final int colorPrimary = Helper.resolveColor(context, R.attr.colorPrimary);
|
||||
final int colorBlockquote = Helper.resolveColor(context, R.attr.colorBlockquote, colorPrimary);
|
||||
final int quoteGap = context.getResources().getDimensionPixelSize(R.dimen.quote_gap_size);
|
||||
final int quoteStripe = context.getResources().getDimensionPixelSize(R.dimen.quote_stripe_width);
|
||||
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
SpannableStringBuilder ssb;
|
||||
if (raw)
|
||||
ssb = new SpannableStringBuilderEx(html);
|
||||
else {
|
||||
Document document = HtmlHelper.sanitizeCompose(context, html, false);
|
||||
Spanned paste = HtmlHelper.fromDocument(context, document, new Html.ImageGetter() {
|
||||
@Override
|
||||
public Drawable getDrawable(String source) {
|
||||
return ImageHelper.decodeImage(context,
|
||||
-1, source, true, 0, 1.0f, EditTextCompose.this);
|
||||
}
|
||||
}, null);
|
||||
SpannableStringBuilder ssb = (raw)
|
||||
? new SpannableStringBuilderEx(html)
|
||||
: getSpanned(context, html);
|
||||
|
||||
ssb = new SpannableStringBuilderEx(paste);
|
||||
QuoteSpan[] spans = ssb.getSpans(0, ssb.length(), QuoteSpan.class);
|
||||
for (QuoteSpan span : spans) {
|
||||
QuoteSpan q;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
|
||||
q = new QuoteSpan(colorBlockquote);
|
||||
else
|
||||
q = new QuoteSpan(colorBlockquote, quoteStripe, quoteGap);
|
||||
ssb.setSpan(q,
|
||||
ssb.getSpanStart(span),
|
||||
ssb.getSpanEnd(span),
|
||||
ssb.getSpanFlags(span));
|
||||
ssb.removeSpan(span);
|
||||
}
|
||||
}
|
||||
|
||||
ApplicationEx.getMainHandler().post(new Runnable() {
|
||||
EditTextCompose.this.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
|
@ -383,6 +435,34 @@ public class EditTextCompose extends FixedEditText {
|
|||
}
|
||||
}
|
||||
|
||||
private SpannableStringBuilder getSpanned(Context context, String html) {
|
||||
Document document = HtmlHelper.sanitizeCompose(context, html, false);
|
||||
Spanned paste = HtmlHelper.fromDocument(context, document, new Html.ImageGetter() {
|
||||
@Override
|
||||
public Drawable getDrawable(String source) {
|
||||
return ImageHelper.decodeImage(context,
|
||||
-1, source, true, 0, 1.0f, EditTextCompose.this);
|
||||
}
|
||||
}, null);
|
||||
|
||||
SpannableStringBuilder ssb = new SpannableStringBuilderEx(paste);
|
||||
QuoteSpan[] spans = ssb.getSpans(0, ssb.length(), QuoteSpan.class);
|
||||
for (QuoteSpan span : spans) {
|
||||
QuoteSpan q;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
|
||||
q = new QuoteSpan(colorBlockquote);
|
||||
else
|
||||
q = new QuoteSpan(colorBlockquote, quoteStripe, quoteGap);
|
||||
ssb.setSpan(q,
|
||||
ssb.getSpanStart(span),
|
||||
ssb.getSpanEnd(span),
|
||||
ssb.getSpanFlags(span));
|
||||
ssb.removeSpan(span);
|
||||
}
|
||||
|
||||
return ssb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
|
||||
//https://developer.android.com/guide/topics/text/image-keyboard
|
||||
|
|
|
@ -84,6 +84,8 @@ public class EntityAnswer implements Serializable {
|
|||
@NonNull
|
||||
public Boolean favorite;
|
||||
@NonNull
|
||||
public Boolean snippet;
|
||||
@NonNull
|
||||
public Boolean hide;
|
||||
@NonNull
|
||||
public Boolean external;
|
||||
|
@ -409,6 +411,7 @@ public class EntityAnswer implements Serializable {
|
|||
this.standard.equals(other.standard) &&
|
||||
this.receipt.equals(other.receipt) &&
|
||||
this.favorite.equals(other.favorite) &&
|
||||
this.snippet.equals(other.snippet) &&
|
||||
this.hide.equals(other.hide) &&
|
||||
this.external.equals(other.external) &&
|
||||
this.text.equals(other.text) &&
|
||||
|
|
|
@ -63,6 +63,7 @@ public class FragmentAnswer extends FragmentBase {
|
|||
private CheckBox cbStandard;
|
||||
private CheckBox cbReceipt;
|
||||
private CheckBox cbFavorite;
|
||||
private CheckBox cbSnippet;
|
||||
private CheckBox cbHide;
|
||||
private CheckBox cbExternal;
|
||||
private ViewButtonColor btnColor;
|
||||
|
@ -114,6 +115,7 @@ public class FragmentAnswer extends FragmentBase {
|
|||
cbStandard = view.findViewById(R.id.cbStandard);
|
||||
cbReceipt = view.findViewById(R.id.cbReceipt);
|
||||
cbFavorite = view.findViewById(R.id.cbFavorite);
|
||||
cbSnippet = view.findViewById(R.id.cbSnippet);
|
||||
cbHide = view.findViewById(R.id.cbHide);
|
||||
cbExternal = view.findViewById(R.id.cbExternal);
|
||||
btnColor = view.findViewById(R.id.btnColor);
|
||||
|
@ -234,6 +236,7 @@ public class FragmentAnswer extends FragmentBase {
|
|||
cbStandard.setChecked(answer == null ? false : answer.standard);
|
||||
cbReceipt.setChecked(answer == null ? false : answer.receipt);
|
||||
cbFavorite.setChecked(answer == null ? false : answer.favorite);
|
||||
cbSnippet.setChecked(answer == null ? false : answer.snippet);
|
||||
cbHide.setChecked(answer == null ? false : answer.hide);
|
||||
cbExternal.setChecked(answer == null ? false : answer.external);
|
||||
btnColor.setColor(answer == null ? null : answer.color);
|
||||
|
@ -359,6 +362,7 @@ public class FragmentAnswer extends FragmentBase {
|
|||
args.putBoolean("standard", cbStandard.isChecked());
|
||||
args.putBoolean("receipt", cbReceipt.isChecked());
|
||||
args.putBoolean("favorite", cbFavorite.isChecked());
|
||||
args.putBoolean("snippet", cbSnippet.isChecked());
|
||||
args.putBoolean("hide", cbHide.isChecked());
|
||||
args.putBoolean("external", cbExternal.isChecked());
|
||||
args.putInt("color", btnColor.getColor());
|
||||
|
@ -383,6 +387,7 @@ public class FragmentAnswer extends FragmentBase {
|
|||
boolean standard = args.getBoolean("standard");
|
||||
boolean receipt = args.getBoolean("receipt");
|
||||
boolean favorite = args.getBoolean("favorite");
|
||||
boolean snippet = args.getBoolean("snippet");
|
||||
boolean hide = args.getBoolean("hide");
|
||||
boolean external = args.getBoolean("external");
|
||||
Integer color = args.getInt("color");
|
||||
|
@ -417,6 +422,7 @@ public class FragmentAnswer extends FragmentBase {
|
|||
answer.standard = standard;
|
||||
answer.receipt = receipt;
|
||||
answer.favorite = favorite;
|
||||
answer.snippet = snippet;
|
||||
answer.hide = hide;
|
||||
answer.external = external;
|
||||
answer.color = color;
|
||||
|
|
|
@ -82,6 +82,16 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/cbReceipt" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbSnippet"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/title_answer_snippet"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/cbFavorite" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbHide"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -90,7 +100,7 @@
|
|||
android:layout_marginTop="6dp"
|
||||
android:text="@string/title_answer_hide"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/cbFavorite" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/cbSnippet" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbExternal"
|
||||
|
@ -145,7 +155,7 @@
|
|||
android:id="@+id/grpReady"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="etName,etGroup,cbStandard,cbReceipt,cbFavorite,cbHide,btnColor,vSeparator,etText" />
|
||||
app:constraint_referenced_ids="etName,etGroup,cbStandard,cbReceipt,cbFavorite,cbSnippet,cbHide,btnColor,vSeparator,etText" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</eu.faircode.email.ScrollViewEx>
|
||||
|
||||
|
|
|
@ -83,10 +83,19 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="12dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/ivFavorite"
|
||||
app:layout_constraintEnd_toStartOf="@id/ivSnippet"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/twotone_quickreply_24" />
|
||||
|
||||
<eu.faircode.email.FixedImageView
|
||||
android:id="@+id/ivSnippet"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="12dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/ivFavorite"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/twotone_text_snippet_24" />
|
||||
|
||||
<eu.faircode.email.FixedImageView
|
||||
android:id="@+id/ivFavorite"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -1520,6 +1520,7 @@
|
|||
<string name="title_answer_group">Template group (optional)</string>
|
||||
<string name="title_answer_standard">Default</string>
|
||||
<string name="title_answer_favorite">Favorite</string>
|
||||
<string name="title_answer_snippet">Snippet</string>
|
||||
<string name="title_answer_receipt">Use as read receipt</string>
|
||||
<string name="title_answer_hide">Hide from menus</string>
|
||||
<string name="title_answer_external" translatable="false">External</string>
|
||||
|
|
Loading…
Reference in New Issue