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

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