diff --git a/app/src/main/java/eu/faircode/email/ActivityView.java b/app/src/main/java/eu/faircode/email/ActivityView.java index 359ec22e..91106e70 100644 --- a/app/src/main/java/eu/faircode/email/ActivityView.java +++ b/app/src/main/java/eu/faircode/email/ActivityView.java @@ -99,6 +99,7 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack static final String ACTION_VIEW_MESSAGES = BuildConfig.APPLICATION_ID + ".VIEW_MESSAGES"; static final String ACTION_VIEW_MESSAGE = BuildConfig.APPLICATION_ID + ".VIEW_MESSAGE"; static final String ACTION_EDIT_FOLDER = BuildConfig.APPLICATION_ID + ".EDIT_FOLDER"; + static final String ACTION_EDIT_ANSWER = BuildConfig.APPLICATION_ID + ".EDIT_ANSWER"; static final String ACTION_STORE_ATTACHMENT = BuildConfig.APPLICATION_ID + ".STORE_ATTACHMENT"; static final String ACTION_PURCHASE = BuildConfig.APPLICATION_ID + ".ACTION_PURCHASE"; static final String ACTION_ACTIVATE_PRO = BuildConfig.APPLICATION_ID + ".ACTIVATE_PRO"; @@ -362,6 +363,7 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack iff.addAction(ACTION_VIEW_MESSAGES); iff.addAction(ACTION_VIEW_MESSAGE); iff.addAction(ACTION_EDIT_FOLDER); + iff.addAction(ACTION_EDIT_ANSWER); iff.addAction(ACTION_STORE_ATTACHMENT); iff.addAction(ACTION_PURCHASE); iff.addAction(ACTION_ACTIVATE_PRO); @@ -636,6 +638,8 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack onViewMessage(intent); else if (ACTION_EDIT_FOLDER.equals(intent.getAction())) onEditFolder(intent); + else if (ACTION_EDIT_ANSWER.equals(intent.getAction())) + onEditAnswer(intent); else if (ACTION_STORE_ATTACHMENT.equals(intent.getAction())) onStoreAttachment(intent); else if (ACTION_PURCHASE.equals(intent.getAction())) @@ -702,6 +706,14 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack fragmentTransaction.commit(); } + private void onEditAnswer(Intent intent) { + FragmentAnswer fragment = new FragmentAnswer(); + fragment.setArguments(intent.getExtras()); + FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); + fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("answer"); + fragmentTransaction.commit(); + } + private void onStoreAttachment(Intent intent) { attachment = intent.getLongExtra("id", -1); Intent create = new Intent(Intent.ACTION_CREATE_DOCUMENT); diff --git a/app/src/main/java/eu/faircode/email/AdapterAnswer.java b/app/src/main/java/eu/faircode/email/AdapterAnswer.java index 0efd8d84..ebc8f7c1 100644 --- a/app/src/main/java/eu/faircode/email/AdapterAnswer.java +++ b/app/src/main/java/eu/faircode/email/AdapterAnswer.java @@ -20,6 +20,7 @@ package eu.faircode.email; */ import android.content.Context; +import android.content.Intent; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -34,6 +35,7 @@ import java.util.List; import java.util.Locale; import androidx.annotation.NonNull; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.ListUpdateCallback; import androidx.recyclerview.widget.RecyclerView; @@ -44,7 +46,7 @@ public class AdapterAnswer extends RecyclerView.Adapter all = new ArrayList<>(); private List filtered = new ArrayList<>(); - public class ViewHolder extends RecyclerView.ViewHolder { + public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { View itemView; TextView tvName; @@ -55,9 +57,31 @@ public class AdapterAnswer extends RecyclerView.Adapter> liveAnswers(); diff --git a/app/src/main/java/eu/faircode/email/FragmentAnswer.java b/app/src/main/java/eu/faircode/email/FragmentAnswer.java index 1bc87054..15e552cf 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAnswer.java +++ b/app/src/main/java/eu/faircode/email/FragmentAnswer.java @@ -19,12 +19,17 @@ package eu.faircode.email; Copyright 2018 by Marcel Bokhorst (M66B) */ +import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ProgressBar; import android.widget.TextView; +import android.widget.Toast; + +import com.google.android.material.bottomnavigation.BottomNavigationView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -35,9 +40,21 @@ public class FragmentAnswer extends FragmentEx { private ViewGroup view; private TextView etName; private TextView etText; + private BottomNavigationView bottom_navigation; private ProgressBar pbWait; private Group grpReady; + private long id = -1; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Get arguments + Bundle args = getArguments(); + id = (args == null ? -1 : args.getLong("id", -1)); + } + @Override @Nullable public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { @@ -46,9 +63,27 @@ public class FragmentAnswer extends FragmentEx { // Get controls etName = view.findViewById(R.id.etName); etText = view.findViewById(R.id.etText); + etText = view.findViewById(R.id.etText); + bottom_navigation = view.findViewById(R.id.bottom_navigation); pbWait = view.findViewById(R.id.pbWait); grpReady = view.findViewById(R.id.grpReady); + bottom_navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() { + @Override + public boolean onNavigationItemSelected(MenuItem menuItem) { + switch (menuItem.getItemId()) { + case R.id.action_trash: + onActionTrash(); + return true; + case R.id.action_save: + onActionSave(); + return true; + } + return false; + } + }); + + // Initialize grpReady.setVisibility(View.GONE); pbWait.setVisibility(View.VISIBLE); @@ -59,17 +94,87 @@ public class FragmentAnswer extends FragmentEx { public void onActivityCreated(@Nullable final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - // Get arguments - Bundle args = getArguments(); - long id = (args == null ? -1 : args.getLong("id")); - DB.getInstance(getContext()).answer().liveAnswer(id).observe(getViewLifecycleOwner(), new Observer() { @Override - public void onChanged(EntityAnswer entityAnswer) { + public void onChanged(EntityAnswer answer) { + etName.setText(answer == null ? null : answer.name); + etText.setText(answer == null ? null : answer.text); + bottom_navigation.findViewById(R.id.action_trash).setVisibility(answer == null ? View.GONE : View.VISIBLE); pbWait.setVisibility(View.GONE); grpReady.setVisibility(View.VISIBLE); } }); } + + private void onActionTrash() { + Helper.setViewsEnabled(view, false); + + Bundle args = new Bundle(); + args.putLong("id", id); + + new SimpleTask() { + @Override + protected Void onLoad(Context context, Bundle args) throws Throwable { + long id = args.getLong("id"); + DB.getInstance(context).answer().deleteAnswer(id); + return null; + } + + @Override + protected void onLoaded(Bundle args, Void data) { + finish(); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.setViewsEnabled(view, true); + Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show(); + } + }.load(this, args); + } + + private void onActionSave() { + Helper.setViewsEnabled(view, false); + + Bundle args = new Bundle(); + args.putLong("id", id); + args.putString("name", etName.getText().toString()); + args.putString("text", etText.getText().toString()); + + new SimpleTask() { + @Override + protected Void onLoad(Context context, Bundle args) throws Throwable { + long id = args.getLong("id"); + String name = args.getString("name"); + String text = args.getString("text"); + + DB db = DB.getInstance(context); + if (id < 0) { + EntityAnswer answer = new EntityAnswer(); + answer.name = name; + answer.text = text; + answer.id = db.answer().insertAnswer(answer); + } else { + EntityAnswer answer = db.answer().getAnswer(id); + answer.name = name; + answer.text = text; + db.answer().updateAnswer(answer); + } + + return null; + } + + @Override + protected void onLoaded(Bundle args, Void data) { + finish(); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.setViewsEnabled(view, true); + Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show(); + } + }.load(this, args); + } } diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index 00819eb4..d4190a25 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -357,6 +357,7 @@ public class FragmentCompose extends FragmentEx { args.putLong("id", getArguments().getLong("id", -1)); args.putLong("account", getArguments().getLong("account", -1)); args.putLong("reference", getArguments().getLong("reference", -1)); + args.putLong("answer", getArguments().getLong("answer", -1)); args.putString("to", getArguments().getString("to")); args.putString("cc", getArguments().getString("cc")); args.putString("bcc", getArguments().getString("bcc")); @@ -372,6 +373,7 @@ public class FragmentCompose extends FragmentEx { args.putLong("id", savedInstanceState.getLong("working")); args.putLong("account", -1); args.putLong("reference", -1); + args.putLong("answer", -1); draftLoader.load(this, args); } } @@ -697,6 +699,7 @@ public class FragmentCompose extends FragmentEx { long id = args.getLong("id", -1); long account = args.getLong("account", -1); long reference = args.getLong("reference", -1); + long answer = args.getLong("answer", -1); Log.i(Helper.TAG, "Load draft action=" + action + " id=" + id + " account=" + account + " reference=" + reference); @@ -796,8 +799,12 @@ public class FragmentCompose extends FragmentEx { } if ("reply".equals(action) || "reply_all".equals(action)) { + String text = ""; + if (answer > 0) + text = db.answer().getAnswer(answer).text; draft.subject = context.getString(R.string.title_subject_reply, ref.subject); - body = String.format("

%s %s:

%s", + body = String.format("%s

%s %s:

%s", + Html.escapeHtml(text).replaceAll("\\r?\\n", "
"), Html.escapeHtml(new Date().toString()), Html.escapeHtml(MessageHelper.getFormattedAddresses(draft.to, true)), HtmlHelper.sanitize(context, ref.read(context), true)); diff --git a/app/src/main/java/eu/faircode/email/FragmentMessage.java b/app/src/main/java/eu/faircode/email/FragmentMessage.java index 49dbe4de..1dc33b02 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessage.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessage.java @@ -99,6 +99,7 @@ import static android.app.Activity.RESULT_OK; public class FragmentMessage extends FragmentEx { private ViewGroup view; + private View vwAnswerAnchor; private TextView tvFrom; private TextView tvSize; private TextView tvTime; @@ -166,6 +167,7 @@ public class FragmentMessage extends FragmentEx { debug = prefs.getBoolean("debug", false); // Get controls + vwAnswerAnchor = view.findViewById(R.id.vwAnswerAnchor); tvFrom = view.findViewById(R.id.tvFrom); tvSize = view.findViewById(R.id.tvSize); tvTime = view.findViewById(R.id.tvTime); @@ -668,6 +670,9 @@ public class FragmentMessage extends FragmentEx { case R.id.menu_reply_all: onMenuReplyAll(); return true; + case R.id.menu_answer: + onMenuAnswer(); + return true; case R.id.menu_decrypt: onMenuDecrypt(); return true; @@ -749,6 +754,43 @@ public class FragmentMessage extends FragmentEx { .putExtra("reference", message.id)); } + private void onMenuAnswer() { + DB.getInstance(getContext()).answer().liveAnswers().observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(List answers) { + final Collator collator = Collator.getInstance(Locale.getDefault()); + collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc + + Collections.sort(answers, new Comparator() { + @Override + public int compare(EntityAnswer a1, EntityAnswer a2) { + return collator.compare(a1.name, a2.name); + } + }); + + PopupMenu popupMenu = new PopupMenu(getContext(), vwAnswerAnchor); + + int order = 0; + for (EntityAnswer answer : answers) + popupMenu.getMenu().add(Menu.NONE, answer.id.intValue(), order++, + Helper.localizeFolderName(getContext(), answer.name)); + + popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem target) { + startActivity(new Intent(getContext(), ActivityCompose.class) + .putExtra("action", "reply") + .putExtra("reference", message.id) + .putExtra("answer", (long) target.getItemId())); + return true; + } + }); + + popupMenu.show(); + } + }); + } + private void onMenuDecrypt() { Log.i(Helper.TAG, "On decrypt"); try { diff --git a/app/src/main/res/layout/fragment_answer.xml b/app/src/main/res/layout/fragment_answer.xml index 05a2595b..c5af2505 100644 --- a/app/src/main/res/layout/fragment_answer.xml +++ b/app/src/main/res/layout/fragment_answer.xml @@ -8,10 +8,10 @@ android:id="@+id/etName" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginTop="6dp" android:layout_marginEnd="6dp" android:layout_marginStart="6dp" - android:background="@null" - android:hint="@string/title_body_hint" + android:hint="@string/title_answer_name" android:inputType="textCapSentences" android:textAppearance="@style/TextAppearance.AppCompat.Small" app:layout_constraintStart_toStartOf="parent" @@ -34,7 +34,7 @@ android:layout_marginStart="6dp" android:fillViewport="true" android:orientation="vertical" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@+id/bottom_navigation" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/vSeparator"> @@ -45,11 +45,24 @@ android:background="@null" android:fontFamily="monospace" android:gravity="top" - android:hint="@string/title_body_hint" + android:hint="@string/title_answer_text" android:inputType="textCapSentences|textMultiLine" android:textAppearance="@style/TextAppearance.AppCompat.Small" /> + + + app:constraint_referenced_ids="etName,vSeparator,scroll,bottom_navigation" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_message.xml b/app/src/main/res/layout/fragment_message.xml index cba22235..c0a0155b 100644 --- a/app/src/main/res/layout/fragment_message.xml +++ b/app/src/main/res/layout/fragment_message.xml @@ -7,6 +7,13 @@ android:orientation="vertical" tools:context=".ActivityView"> + + + app:menu="@menu/action_view" /> - - + app:layout_constraintTop_toTopOf="parent" /> + + \ No newline at end of file diff --git a/app/src/main/res/menu/action_answer.xml b/app/src/main/res/menu/action_answer.xml new file mode 100644 index 00000000..322812f0 --- /dev/null +++ b/app/src/main/res/menu/action_answer.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/menu/action_list_bottom.xml b/app/src/main/res/menu/action_list_bottom.xml deleted file mode 100644 index eb756728..00000000 --- a/app/src/main/res/menu/action_list_bottom.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/app/src/main/res/menu/action_view_bottom.xml b/app/src/main/res/menu/action_view.xml similarity index 100% rename from app/src/main/res/menu/action_view_bottom.xml rename to app/src/main/res/menu/action_view.xml diff --git a/app/src/main/res/menu/menu_view.xml b/app/src/main/res/menu/menu_view.xml index 4f72d7b3..925a6a8d 100644 --- a/app/src/main/res/menu/menu_view.xml +++ b/app/src/main/res/menu/menu_view.xml @@ -32,6 +32,12 @@ android:title="@string/title_reply_all" app:showAsAction="never" /> + + \'%1$s\' failed Setup - Standard answers + Answers Operations Legend FAQ @@ -176,6 +176,10 @@ Search on server Searching \'%1$s\' + Standard answer + Answer name + Answer text + CC/BCC Attachment Synchronize