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

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