Simple email application for Android. Original source code: https://framagit.org/dystopia-project/simple-email

892 lines
37 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. package eu.faircode.email;
  2. /*
  3. This file is part of FairEmail.
  4. FairEmail is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. NetGuard is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with NetGuard. If not, see <http://www.gnu.org/licenses/>.
  14. Copyright 2018 by Marcel Bokhorst (M66B)
  15. */
  16. import android.content.Context;
  17. import android.content.DialogInterface;
  18. import android.content.Intent;
  19. import android.graphics.Typeface;
  20. import android.net.Uri;
  21. import android.os.Bundle;
  22. import android.preference.PreferenceManager;
  23. import android.text.Html;
  24. import android.text.Layout;
  25. import android.text.Spannable;
  26. import android.text.method.LinkMovementMethod;
  27. import android.text.style.URLSpan;
  28. import android.view.LayoutInflater;
  29. import android.view.Menu;
  30. import android.view.MenuInflater;
  31. import android.view.MenuItem;
  32. import android.view.MotionEvent;
  33. import android.view.View;
  34. import android.view.ViewGroup;
  35. import android.widget.ProgressBar;
  36. import android.widget.TextView;
  37. import android.widget.Toast;
  38. import com.google.android.material.bottomnavigation.BottomNavigationView;
  39. import com.google.android.material.floatingactionbutton.FloatingActionButton;
  40. import java.text.Collator;
  41. import java.text.DateFormat;
  42. import java.text.SimpleDateFormat;
  43. import java.util.ArrayList;
  44. import java.util.Collections;
  45. import java.util.Comparator;
  46. import java.util.Date;
  47. import java.util.List;
  48. import java.util.Locale;
  49. import androidx.annotation.NonNull;
  50. import androidx.annotation.Nullable;
  51. import androidx.appcompat.app.AlertDialog;
  52. import androidx.appcompat.widget.PopupMenu;
  53. import androidx.browser.customtabs.CustomTabsIntent;
  54. import androidx.constraintlayout.widget.Group;
  55. import androidx.fragment.app.FragmentManager;
  56. import androidx.fragment.app.FragmentTransaction;
  57. import androidx.lifecycle.Observer;
  58. import androidx.recyclerview.widget.LinearLayoutManager;
  59. import androidx.recyclerview.widget.RecyclerView;
  60. public class FragmentMessage extends FragmentEx {
  61. private ViewGroup view;
  62. private TextView tvFrom;
  63. private TextView tvTime;
  64. private TextView tvTo;
  65. private TextView tvSubject;
  66. private TextView tvCount;
  67. private TextView tvReplyTo;
  68. private TextView tvCc;
  69. private TextView tvBcc;
  70. private RecyclerView rvAttachment;
  71. private TextView tvError;
  72. private BottomNavigationView top_navigation;
  73. private TextView tvBody;
  74. private FloatingActionButton fab;
  75. private BottomNavigationView bottom_navigation;
  76. private ProgressBar pbWait;
  77. private Group grpHeader;
  78. private Group grpAddresses;
  79. private Group grpAttachments;
  80. private Group grpMessage;
  81. private boolean free = false;
  82. private AdapterAttachment adapter;
  83. private boolean debug;
  84. private DateFormat df = SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
  85. @Override
  86. @Nullable
  87. public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
  88. view = (ViewGroup) inflater.inflate(R.layout.fragment_message, container, false);
  89. // Get arguments
  90. Bundle args = getArguments();
  91. final long id = (args == null ? -1 : args.getLong("id"));
  92. debug = PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("debug", false);
  93. // Get controls
  94. tvFrom = view.findViewById(R.id.tvFrom);
  95. tvTime = view.findViewById(R.id.tvTime);
  96. tvTo = view.findViewById(R.id.tvTo);
  97. tvSubject = view.findViewById(R.id.tvSubject);
  98. tvCount = view.findViewById(R.id.tvCount);
  99. tvReplyTo = view.findViewById(R.id.tvReplyTo);
  100. tvCc = view.findViewById(R.id.tvCc);
  101. tvBcc = view.findViewById(R.id.tvBcc);
  102. rvAttachment = view.findViewById(R.id.rvAttachment);
  103. tvError = view.findViewById(R.id.tvError);
  104. top_navigation = view.findViewById(R.id.top_navigation);
  105. tvBody = view.findViewById(R.id.tvBody);
  106. fab = view.findViewById(R.id.fab);
  107. bottom_navigation = view.findViewById(R.id.bottom_navigation);
  108. pbWait = view.findViewById(R.id.pbWait);
  109. grpHeader = view.findViewById(R.id.grpHeader);
  110. grpAddresses = view.findViewById(R.id.grpAddresses);
  111. grpAttachments = view.findViewById(R.id.grpAttachments);
  112. grpMessage = view.findViewById(R.id.grpMessage);
  113. setHasOptionsMenu(true);
  114. tvBody.setMovementMethod(new LinkMovementMethod() {
  115. public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
  116. if (event.getAction() != MotionEvent.ACTION_UP)
  117. return super.onTouchEvent(widget, buffer, event);
  118. int x = (int) event.getX();
  119. int y = (int) event.getY();
  120. x -= widget.getTotalPaddingLeft();
  121. y -= widget.getTotalPaddingTop();
  122. x += widget.getScrollX();
  123. y += widget.getScrollY();
  124. Layout layout = widget.getLayout();
  125. int line = layout.getLineForVertical(y);
  126. int off = layout.getOffsetForHorizontal(line, x);
  127. URLSpan[] link = buffer.getSpans(off, off, URLSpan.class);
  128. if (link.length != 0) {
  129. String url = link[0].getURL();
  130. if (true) {
  131. // https://developer.chrome.com/multidevice/android/customtabs
  132. CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
  133. builder.setToolbarColor(Helper.resolveColor(getContext(), R.attr.colorPrimary));
  134. CustomTabsIntent customTabsIntent = builder.build();
  135. customTabsIntent.launchUrl(getContext(), Uri.parse(url));
  136. } else {
  137. Bundle args = new Bundle();
  138. args.putString("link", url);
  139. FragmentWebView fragment = new FragmentWebView();
  140. fragment.setArguments(args);
  141. FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
  142. fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("webview");
  143. fragmentTransaction.commit();
  144. }
  145. }
  146. return true;
  147. }
  148. });
  149. // Wire controls
  150. top_navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
  151. @Override
  152. public boolean onNavigationItemSelected(@NonNull MenuItem item) {
  153. switch (item.getItemId()) {
  154. case R.id.action_thread:
  155. onActionThread(id);
  156. return true;
  157. case R.id.action_seen:
  158. onActionSeen(id);
  159. return true;
  160. case R.id.action_edit:
  161. onActionEdit(id);
  162. return true;
  163. case R.id.action_forward:
  164. onActionForward(id);
  165. return true;
  166. case R.id.action_reply_all:
  167. onActionReplyAll(id);
  168. return true;
  169. }
  170. return false;
  171. }
  172. });
  173. fab.setOnClickListener(new View.OnClickListener() {
  174. @Override
  175. public void onClick(View view) {
  176. tvCount.setTag(tvCount.getVisibility());
  177. tvError.setTag(tvError.getVisibility());
  178. tvCc.setTag(grpAddresses.getVisibility());
  179. rvAttachment.setTag(grpAddresses.getVisibility());
  180. free = true;
  181. getActivity().invalidateOptionsMenu();
  182. grpHeader.setVisibility(free ? View.GONE : View.VISIBLE);
  183. grpAddresses.setVisibility(View.GONE);
  184. grpAttachments.setVisibility(View.GONE);
  185. tvCount.setVisibility(View.GONE);
  186. tvError.setVisibility(View.GONE);
  187. fab.setVisibility(View.GONE);
  188. }
  189. });
  190. ((ActivityBase) getActivity()).addBackPressedListener(new ActivityBase.IBackPressedListener() {
  191. @Override
  192. public boolean onBackPressed() {
  193. if (free && isVisible()) {
  194. free = false;
  195. getActivity().invalidateOptionsMenu();
  196. grpHeader.setVisibility(free ? View.GONE : View.VISIBLE);
  197. grpAddresses.setVisibility((int) tvCc.getTag());
  198. rvAttachment.setVisibility((int) rvAttachment.getTag());
  199. tvCount.setVisibility((int) tvCount.getTag());
  200. tvError.setVisibility((int) tvError.getTag());
  201. fab.setVisibility(View.VISIBLE);
  202. return true;
  203. }
  204. return false;
  205. }
  206. });
  207. bottom_navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
  208. @Override
  209. public boolean onNavigationItemSelected(@NonNull MenuItem item) {
  210. switch (item.getItemId()) {
  211. case R.id.action_spam:
  212. onActionSpam(id);
  213. return true;
  214. case R.id.action_trash:
  215. onActionDelete(id);
  216. return true;
  217. case R.id.action_move:
  218. onActionMove(id);
  219. return true;
  220. case R.id.action_archive:
  221. onActionArchive(id);
  222. return true;
  223. case R.id.action_reply:
  224. onActionReply(id);
  225. return true;
  226. }
  227. return false;
  228. }
  229. });
  230. // Initialize
  231. grpHeader.setVisibility(View.GONE);
  232. grpAddresses.setVisibility(View.GONE);
  233. grpAttachments.setVisibility(View.GONE);
  234. grpMessage.setVisibility(View.GONE);
  235. tvCount.setVisibility(View.GONE);
  236. tvError.setVisibility(View.GONE);
  237. fab.setVisibility(View.GONE);
  238. pbWait.setVisibility(View.VISIBLE);
  239. rvAttachment.setHasFixedSize(false);
  240. LinearLayoutManager llm = new LinearLayoutManager(getContext());
  241. rvAttachment.setLayoutManager(llm);
  242. adapter = new AdapterAttachment(getContext(), getViewLifecycleOwner(), true);
  243. rvAttachment.setAdapter(adapter);
  244. return view;
  245. }
  246. @Override
  247. public void onSaveInstanceState(Bundle outState) {
  248. outState.putBoolean("free", free);
  249. outState.putInt("addresses", grpAddresses.getVisibility());
  250. outState.putInt("attachments", rvAttachment.getVisibility());
  251. super.onSaveInstanceState(outState);
  252. }
  253. @Override
  254. public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
  255. super.onActivityCreated(savedInstanceState);
  256. // Get arguments
  257. Bundle args = getArguments();
  258. final long id = (args == null ? -1 : args.getLong("id"));
  259. final DB db = DB.getInstance(getContext());
  260. // Observe message
  261. db.message().liveMessage(id).observe(getViewLifecycleOwner(), new Observer<TupleMessageEx>() {
  262. @Override
  263. public void onChanged(@Nullable final TupleMessageEx message) {
  264. if (message == null || (!(debug && BuildConfig.DEBUG) && message.ui_hide)) {
  265. // Message gone (moved, deleted)
  266. if (FragmentMessage.this.isVisible())
  267. getFragmentManager().popBackStack();
  268. } else {
  269. setSubtitle(Helper.localizeFolderName(getContext(), message.folderName));
  270. tvFrom.setText(message.from == null ? null : MessageHelper.getFormattedAddresses(message.from, true));
  271. tvTime.setText(message.sent == null ? null : df.format(new Date(message.sent)));
  272. tvTo.setText(message.to == null ? null : MessageHelper.getFormattedAddresses(message.to, true));
  273. tvSubject.setText(message.subject);
  274. tvCount.setText(Integer.toString(message.count));
  275. tvReplyTo.setText(message.reply == null ? null : MessageHelper.getFormattedAddresses(message.reply, true));
  276. tvCc.setText(message.cc == null ? null : MessageHelper.getFormattedAddresses(message.cc, true));
  277. tvBcc.setText(message.bcc == null ? null : MessageHelper.getFormattedAddresses(message.bcc, true));
  278. int typeface = (message.ui_seen ? Typeface.NORMAL : Typeface.BOLD);
  279. tvFrom.setTypeface(null, typeface);
  280. tvTime.setTypeface(null, typeface);
  281. tvSubject.setTypeface(null, typeface);
  282. tvCount.setTypeface(null, typeface);
  283. int colorUnseen = Helper.resolveColor(getContext(), message.ui_seen
  284. ? android.R.attr.textColorSecondary : R.attr.colorUnread);
  285. tvFrom.setTextColor(colorUnseen);
  286. tvTime.setTextColor(colorUnseen);
  287. tvError.setText(message.error);
  288. MenuItem actionSeen = top_navigation.getMenu().findItem(R.id.action_seen);
  289. actionSeen.setIcon(message.ui_seen
  290. ? R.drawable.baseline_visibility_off_24
  291. : R.drawable.baseline_visibility_24);
  292. actionSeen.setTitle(message.ui_seen ? R.string.title_unseen : R.string.title_seen);
  293. tvBody.setText(message.body == null
  294. ? null
  295. : Html.fromHtml(HtmlHelper.sanitize(getContext(), message.body, false)));
  296. db.folder().liveFolders(message.account).removeObservers(getViewLifecycleOwner());
  297. db.folder().liveFolders(message.account).observe(getViewLifecycleOwner(), new Observer<List<TupleFolderEx>>() {
  298. @Override
  299. public void onChanged(@Nullable List<TupleFolderEx> folders) {
  300. if (folders == null)
  301. folders = new ArrayList<>();
  302. boolean inInbox = EntityFolder.INBOX.equals(message.folderType);
  303. boolean inOutbox = EntityFolder.OUTBOX.equals(message.folderType);
  304. boolean inArchive = EntityFolder.ARCHIVE.equals(message.folderType);
  305. //boolean inDafts = EntityFolder.DRAFTS.equals(message.folderType);
  306. boolean inTrash = EntityFolder.TRASH.equals(message.folderType);
  307. boolean inJunk = EntityFolder.JUNK.equals(message.folderType);
  308. //boolean inSent = EntityFolder.SENT.equals(message.folderType);
  309. boolean hasTrash = false;
  310. boolean hasJunk = false;
  311. boolean hasArchive = false;
  312. boolean hasUser = false;
  313. if (folders != null)
  314. for (EntityFolder folder : folders) {
  315. if (EntityFolder.TRASH.equals(folder.type))
  316. hasTrash = true;
  317. else if (EntityFolder.JUNK.equals(folder.type))
  318. hasJunk = true;
  319. else if (EntityFolder.ARCHIVE.equals(folder.type))
  320. hasArchive = true;
  321. else if (EntityFolder.USER.equals(folder.type))
  322. hasUser = true;
  323. }
  324. bottom_navigation.setTag(inTrash || !hasTrash);
  325. top_navigation.getMenu().findItem(R.id.action_thread).setVisible(message.count > 1);
  326. top_navigation.getMenu().findItem(R.id.action_seen).setVisible(!inOutbox);
  327. top_navigation.getMenu().findItem(R.id.action_edit).setVisible(inTrash);
  328. top_navigation.getMenu().findItem(R.id.action_forward).setVisible(!inOutbox);
  329. top_navigation.getMenu().findItem(R.id.action_reply_all).setVisible(!inOutbox && message.cc != null);
  330. top_navigation.setVisibility(View.VISIBLE);
  331. bottom_navigation.getMenu().findItem(R.id.action_spam).setVisible(message.uid != null && !inOutbox && !inArchive && !inJunk && hasJunk);
  332. bottom_navigation.getMenu().findItem(R.id.action_trash).setVisible(message.uid != null && !inOutbox && hasTrash);
  333. bottom_navigation.getMenu().findItem(R.id.action_move).setVisible(message.uid != null && !inOutbox && (!inInbox || hasUser));
  334. bottom_navigation.getMenu().findItem(R.id.action_archive).setVisible(message.uid != null && !inOutbox && !inArchive && hasArchive);
  335. bottom_navigation.getMenu().findItem(R.id.action_reply).setVisible(!inOutbox);
  336. bottom_navigation.setVisibility(View.VISIBLE);
  337. }
  338. });
  339. }
  340. if (savedInstanceState != null) {
  341. free = savedInstanceState.getBoolean("free");
  342. grpAddresses.setTag(savedInstanceState.getInt("addresses"));
  343. rvAttachment.setTag(savedInstanceState.getInt("attachments"));
  344. }
  345. pbWait.setVisibility(View.GONE);
  346. grpHeader.setVisibility(free ? View.GONE : View.VISIBLE);
  347. if (free)
  348. grpAddresses.setVisibility(View.GONE);
  349. grpMessage.setVisibility(View.VISIBLE);
  350. tvCount.setVisibility(!free && message.count > 1 ? View.VISIBLE : View.GONE);
  351. tvError.setVisibility(free || message.error == null ? View.GONE : View.VISIBLE);
  352. fab.setVisibility(free ? View.GONE : View.VISIBLE);
  353. }
  354. });
  355. // Observe attachments
  356. db.attachment().liveAttachments(id).observe(getViewLifecycleOwner(),
  357. new Observer<List<EntityAttachment>>() {
  358. @Override
  359. public void onChanged(@Nullable List<EntityAttachment> attachments) {
  360. if (attachments == null)
  361. attachments = new ArrayList<>();
  362. adapter.set(attachments);
  363. if (!free)
  364. grpAttachments.setVisibility(attachments.size() > 0 ? View.VISIBLE : View.GONE);
  365. }
  366. });
  367. }
  368. @Override
  369. public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
  370. inflater.inflate(R.menu.menu_view, menu);
  371. super.onCreateOptionsMenu(menu, inflater);
  372. }
  373. @Override
  374. public void onPrepareOptionsMenu(Menu menu) {
  375. super.onPrepareOptionsMenu(menu);
  376. menu.findItem(R.id.menu_addresses).setVisible(!free);
  377. }
  378. @Override
  379. public boolean onOptionsItemSelected(MenuItem item) {
  380. switch (item.getItemId()) {
  381. case R.id.menu_addresses:
  382. onMenuAddresses();
  383. return true;
  384. default:
  385. return super.onOptionsItemSelected(item);
  386. }
  387. }
  388. private void onMenuAddresses() {
  389. grpAddresses.setVisibility(grpAddresses.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);
  390. }
  391. private void onActionThread(long id) {
  392. getFragmentManager().popBackStack("thread", FragmentManager.POP_BACK_STACK_INCLUSIVE);
  393. Bundle args = new Bundle();
  394. args.putLong("thread", id); // message ID
  395. FragmentMessages fragment = new FragmentMessages();
  396. fragment.setArguments(args);
  397. FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
  398. fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("thread");
  399. fragmentTransaction.commit();
  400. }
  401. private void onActionSeen(long id) {
  402. Helper.setViewsEnabled(view, false);
  403. Bundle args = new Bundle();
  404. args.putLong("id", id);
  405. new SimpleTask<Void>() {
  406. @Override
  407. protected Void onLoad(Context context, Bundle args) {
  408. long id = args.getLong("id");
  409. DB db = DB.getInstance(context);
  410. try {
  411. db.beginTransaction();
  412. EntityMessage message = db.message().getMessage(id);
  413. for (EntityMessage tmessage : db.message().getMessageByThread(message.account, message.thread))
  414. if (message.uid != null) { // Skip drafts and outbox
  415. db.message().setMessageUiSeen(tmessage.id, !message.ui_seen);
  416. EntityOperation.queue(db, tmessage, EntityOperation.SEEN, !tmessage.ui_seen);
  417. }
  418. db.setTransactionSuccessful();
  419. } finally {
  420. db.endTransaction();
  421. }
  422. EntityOperation.process(context);
  423. return null;
  424. }
  425. @Override
  426. protected void onLoaded(Bundle args, Void data) {
  427. Helper.setViewsEnabled(view, true);
  428. }
  429. @Override
  430. public void onException(Bundle args, Throwable ex) {
  431. Helper.setViewsEnabled(view, true);
  432. Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
  433. }
  434. }.load(this, args);
  435. }
  436. private void onActionEdit(final long id) {
  437. Helper.setViewsEnabled(view, false);
  438. Bundle args = new Bundle();
  439. args.putLong("id", id);
  440. new SimpleTask<Void>() {
  441. @Override
  442. protected Void onLoad(Context context, Bundle args) {
  443. long id = args.getLong("id");
  444. DB db = DB.getInstance(context);
  445. try {
  446. db.beginTransaction();
  447. EntityMessage draft = db.message().getMessage(id);
  448. EntityFolder drafts = db.folder().getFolderByType(draft.account, EntityFolder.DRAFTS);
  449. draft.id = null;
  450. draft.folder = drafts.id;
  451. draft.uid = null;
  452. draft.msgid = EntityMessage.generateMessageId();
  453. draft.id = db.message().insertMessage(draft);
  454. EntityOperation.queue(db, draft, EntityOperation.ADD);
  455. db.setTransactionSuccessful();
  456. } finally {
  457. db.endTransaction();
  458. }
  459. EntityOperation.process(context);
  460. return null;
  461. }
  462. @Override
  463. protected void onLoaded(Bundle args, Void data) {
  464. Helper.setViewsEnabled(view, true);
  465. getContext().startActivity(
  466. new Intent(getContext(), ActivityCompose.class)
  467. .putExtra("action", "edit")
  468. .putExtra("id", id));
  469. }
  470. @Override
  471. public void onException(Bundle args, Throwable ex) {
  472. Helper.setViewsEnabled(view, true);
  473. Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
  474. }
  475. }.load(this, args);
  476. }
  477. private void onActionForward(long id) {
  478. startActivity(new Intent(getContext(), ActivityCompose.class)
  479. .putExtra("action", "forward")
  480. .putExtra("reference", id));
  481. }
  482. private void onActionReplyAll(long id) {
  483. startActivity(new Intent(getContext(), ActivityCompose.class)
  484. .putExtra("action", "reply_all")
  485. .putExtra("reference", id));
  486. }
  487. private void onActionSpam(final long id) {
  488. AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
  489. builder
  490. .setMessage(R.string.title_ask_spam)
  491. .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
  492. @Override
  493. public void onClick(DialogInterface dialog, int which) {
  494. Helper.setViewsEnabled(view, false);
  495. Bundle args = new Bundle();
  496. args.putLong("id", id);
  497. new SimpleTask<Void>() {
  498. @Override
  499. protected Void onLoad(Context context, Bundle args) {
  500. long id = args.getLong("id");
  501. DB db = DB.getInstance(context);
  502. try {
  503. db.beginTransaction();
  504. db.message().setMessageUiHide(id, true);
  505. EntityMessage message = db.message().getMessage(id);
  506. EntityFolder spam = db.folder().getFolderByType(message.account, EntityFolder.JUNK);
  507. EntityOperation.queue(db, message, EntityOperation.MOVE, spam.id);
  508. db.setTransactionSuccessful();
  509. } finally {
  510. db.endTransaction();
  511. }
  512. EntityOperation.process(context);
  513. return null;
  514. }
  515. @Override
  516. protected void onLoaded(Bundle args, Void result) {
  517. Helper.setViewsEnabled(view, true);
  518. }
  519. @Override
  520. protected void onException(Bundle args, Throwable ex) {
  521. Helper.setViewsEnabled(view, true);
  522. Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
  523. }
  524. }.load(FragmentMessage.this, args);
  525. }
  526. })
  527. .setNegativeButton(android.R.string.cancel, null).show();
  528. }
  529. private void onActionDelete(final long id) {
  530. boolean delete = (Boolean) bottom_navigation.getTag();
  531. if (delete) {
  532. // No trash or is trash
  533. AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
  534. builder
  535. .setMessage(R.string.title_ask_delete)
  536. .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
  537. @Override
  538. public void onClick(DialogInterface dialog, int which) {
  539. Helper.setViewsEnabled(view, false);
  540. Bundle args = new Bundle();
  541. args.putLong("id", id);
  542. new SimpleTask<Void>() {
  543. @Override
  544. protected Void onLoad(Context context, Bundle args) {
  545. long id = args.getLong("id");
  546. DB db = DB.getInstance(context);
  547. try {
  548. db.beginTransaction();
  549. db.message().setMessageUiHide(id, true);
  550. EntityMessage message = db.message().getMessage(id);
  551. EntityOperation.queue(db, message, EntityOperation.DELETE);
  552. db.setTransactionSuccessful();
  553. } finally {
  554. db.endTransaction();
  555. }
  556. EntityOperation.process(context);
  557. return null;
  558. }
  559. @Override
  560. protected void onLoaded(Bundle args, Void result) {
  561. Helper.setViewsEnabled(view, true);
  562. }
  563. @Override
  564. protected void onException(Bundle args, Throwable ex) {
  565. Helper.setViewsEnabled(view, true);
  566. Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
  567. }
  568. }.load(FragmentMessage.this, args);
  569. }
  570. })
  571. .setNegativeButton(android.R.string.cancel, null).show();
  572. } else {
  573. Helper.setViewsEnabled(view, false);
  574. Bundle args = new Bundle();
  575. args.putLong("id", id);
  576. new SimpleTask<Void>() {
  577. @Override
  578. protected Void onLoad(Context context, Bundle args) {
  579. long id = args.getLong("id");
  580. DB db = DB.getInstance(context);
  581. try {
  582. db.beginTransaction();
  583. if (debug && BuildConfig.DEBUG)
  584. db.message().deleteMessage(id);
  585. else {
  586. db.message().setMessageUiHide(id, true);
  587. EntityMessage message = db.message().getMessage(id);
  588. EntityFolder trash = db.folder().getFolderByType(message.account, EntityFolder.TRASH);
  589. EntityOperation.queue(db, message, EntityOperation.MOVE, trash.id);
  590. }
  591. db.setTransactionSuccessful();
  592. } finally {
  593. db.endTransaction();
  594. }
  595. EntityOperation.process(context);
  596. return null;
  597. }
  598. @Override
  599. protected void onLoaded(Bundle args, Void result) {
  600. Helper.setViewsEnabled(view, true);
  601. }
  602. @Override
  603. protected void onException(Bundle args, Throwable ex) {
  604. Helper.setViewsEnabled(view, true);
  605. Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
  606. }
  607. }.load(FragmentMessage.this, args);
  608. }
  609. }
  610. private void onActionMove(long id) {
  611. Bundle args = new Bundle();
  612. args.putLong("id", id);
  613. new SimpleTask<List<EntityFolder>>() {
  614. @Override
  615. protected List<EntityFolder> onLoad(Context context, Bundle args) {
  616. EntityMessage message;
  617. List<EntityFolder> folders;
  618. DB db = DB.getInstance(getContext());
  619. try {
  620. db.beginTransaction();
  621. message = db.message().getMessage(args.getLong("id"));
  622. folders = db.folder().getUserFolders(message.account);
  623. for (int i = 0; i < folders.size(); i++)
  624. if (folders.get(i).id.equals(message.folder)) {
  625. folders.remove(i);
  626. break;
  627. }
  628. db.setTransactionSuccessful();
  629. } finally {
  630. db.endTransaction();
  631. }
  632. final Collator collator = Collator.getInstance(Locale.getDefault());
  633. collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
  634. Collections.sort(folders, new Comparator<EntityFolder>() {
  635. @Override
  636. public int compare(EntityFolder f1, EntityFolder f2) {
  637. return collator.compare(f1.name, f2.name);
  638. }
  639. });
  640. EntityFolder inbox = db.folder().getFolderByType(message.account, EntityFolder.INBOX);
  641. if (!message.folder.equals(inbox.id))
  642. folders.add(0, inbox);
  643. return folders;
  644. }
  645. @Override
  646. protected void onLoaded(final Bundle args, List<EntityFolder> folders) {
  647. View anchor = bottom_navigation.findViewById(R.id.action_move);
  648. PopupMenu popupMenu = new PopupMenu(getContext(), anchor);
  649. int order = 0;
  650. for (EntityFolder folder : folders)
  651. popupMenu.getMenu().add(Menu.NONE, folder.id.intValue(), order++,
  652. Helper.localizeFolderName(getContext(), folder.name));
  653. popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
  654. @Override
  655. public boolean onMenuItemClick(final MenuItem target) {
  656. Helper.setViewsEnabled(view, false);
  657. args.putLong("target", target.getItemId());
  658. new SimpleTask<Void>() {
  659. @Override
  660. protected Void onLoad(Context context, Bundle args) {
  661. long id = args.getLong("id");
  662. long target = args.getLong("target");
  663. DB db = DB.getInstance(context);
  664. try {
  665. db.beginTransaction();
  666. EntityMessage message = db.message().getMessage(id);
  667. EntityFolder folder = db.folder().getFolder(message.folder);
  668. if (!EntityFolder.ARCHIVE.equals(folder.type))
  669. db.message().setMessageUiHide(message.id, true);
  670. EntityOperation.queue(db, message, EntityOperation.MOVE, target);
  671. db.setTransactionSuccessful();
  672. } finally {
  673. db.endTransaction();
  674. }
  675. EntityOperation.process(context);
  676. return null;
  677. }
  678. @Override
  679. protected void onLoaded(Bundle args, Void result) {
  680. Helper.setViewsEnabled(view, true);
  681. }
  682. @Override
  683. protected void onException(Bundle args, Throwable ex) {
  684. Helper.setViewsEnabled(view, true);
  685. Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
  686. }
  687. }.load(FragmentMessage.this, args);
  688. return true;
  689. }
  690. });
  691. popupMenu.show();
  692. }
  693. }.load(FragmentMessage.this, args);
  694. }
  695. private void onActionArchive(long id) {
  696. Helper.setViewsEnabled(view, false);
  697. Bundle args = new Bundle();
  698. args.putLong("id", id);
  699. new SimpleTask<Void>() {
  700. @Override
  701. protected Void onLoad(Context context, Bundle args) {
  702. long id = args.getLong("id");
  703. DB db = DB.getInstance(context);
  704. try {
  705. db.beginTransaction();
  706. db.message().setMessageUiHide(id, true);
  707. EntityMessage message = db.message().getMessage(id);
  708. EntityFolder archive = db.folder().getFolderByType(message.account, EntityFolder.ARCHIVE);
  709. EntityOperation.queue(db, message, EntityOperation.MOVE, archive.id);
  710. db.setTransactionSuccessful();
  711. } finally {
  712. db.endTransaction();
  713. }
  714. EntityOperation.process(context);
  715. return null;
  716. }
  717. @Override
  718. protected void onLoaded(Bundle args, Void result) {
  719. Helper.setViewsEnabled(view, true);
  720. }
  721. @Override
  722. protected void onException(Bundle args, Throwable ex) {
  723. Helper.setViewsEnabled(view, true);
  724. Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
  725. }
  726. }.load(FragmentMessage.this, args);
  727. }
  728. private void onActionReply(long id) {
  729. startActivity(new Intent(getContext(), ActivityCompose.class)
  730. .putExtra("action", "reply")
  731. .putExtra("reference", id));
  732. }
  733. }