Simple email application for Android. Original source code: https://framagit.org/dystopia-project/simple-email
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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