From 9e551f9b9f9e33a696ba9ded00a60c820a85ee28 Mon Sep 17 00:00:00 2001 From: M66B Date: Sun, 26 Aug 2018 12:17:09 +0000 Subject: [PATCH] Basic search --- .../java/eu/faircode/email/ActivityView.java | 9 +- .../eu/faircode/email/AdapterMessage.java | 2 +- .../eu/faircode/email/FragmentMessages.java | 295 ++++++++++++++---- .../java/eu/faircode/email/MessageHelper.java | 6 + .../eu/faircode/email/ServiceSynchronize.java | 7 +- .../main/res/drawable/baseline_search_24.xml | 10 + app/src/main/res/menu/menu_list.xml | 7 + app/src/main/res/values/strings.xml | 3 + 8 files changed, 273 insertions(+), 66 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_search_24.xml diff --git a/app/src/main/java/eu/faircode/email/ActivityView.java b/app/src/main/java/eu/faircode/email/ActivityView.java index c7899bdd..6c6aaa4f 100644 --- a/app/src/main/java/eu/faircode/email/ActivityView.java +++ b/app/src/main/java/eu/faircode/email/ActivityView.java @@ -654,10 +654,11 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack db.beginTransaction(); EntityMessage message = db.message().getMessage(id); - for (EntityMessage tmessage : db.message().getMessageByThread(message.account, message.thread)) { - db.message().setMessageUiSeen(tmessage.id, true); - EntityOperation.queue(db, tmessage, EntityOperation.SEEN, true); - } + if (message != null) // Searched messages are not stored in the database + for (EntityMessage tmessage : db.message().getMessageByThread(message.account, message.thread)) { + db.message().setMessageUiSeen(tmessage.id, true); + EntityOperation.queue(db, tmessage, EntityOperation.SEEN, true); + } db.setTransactionSuccessful(); } finally { diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index eb0074b9..f1143403 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -52,7 +52,7 @@ public class AdapterMessage extends PagedListAdapter() { @Override @@ -136,48 +164,171 @@ public class FragmentMessages extends FragmentEx { } }); - // Observe folder/messages + // Observe folder/messages/search LiveData> messages; - boolean debug = PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("debug", false); - if (thread < 0) - if (folder < 0) { - db.folder().liveUnified().observe(getViewLifecycleOwner(), new Observer>() { - @Override - public void onChanged(List folders) { - int unseen = 0; - if (folders != null) - for (TupleFolderEx folder : folders) - unseen += folder.unseen; - String name = getString(R.string.title_folder_unified); - if (unseen > 0) - setSubtitle(getString(R.string.title_folder_unseen, name, unseen)); - else - setSubtitle(name); - } - }); - - messages = new LivePagedListBuilder<>(db.message().pagedUnifiedInbox(debug), PAGE_SIZE).build(); - } else { - db.folder().liveFolderEx(folder).observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(@Nullable TupleFolderEx folder) { - if (folder == null) - setSubtitle(null); - else { - String name = Helper.localizeFolderName(getContext(), folder.name); - if (folder.unseen > 0) - setSubtitle(getString(R.string.title_folder_unseen, name, folder.unseen)); + if (TextUtils.isEmpty(search)) { + boolean debug = PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("debug", false); + if (thread < 0) + if (folder < 0) { + db.folder().liveUnified().observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(List folders) { + int unseen = 0; + if (folders != null) + for (TupleFolderEx folder : folders) + unseen += folder.unseen; + String name = getString(R.string.title_folder_unified); + if (unseen > 0) + setSubtitle(getString(R.string.title_folder_unseen, name, unseen)); else setSubtitle(name); } - } - }); + }); + + messages = new LivePagedListBuilder<>(db.message().pagedUnifiedInbox(debug), PAGE_SIZE).build(); + } else { + db.folder().liveFolderEx(folder).observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(@Nullable TupleFolderEx folder) { + if (folder == null) + setSubtitle(null); + else { + String name = Helper.localizeFolderName(getContext(), folder.name); + if (folder.unseen > 0) + setSubtitle(getString(R.string.title_folder_unseen, name, folder.unseen)); + else + setSubtitle(name); + } + } + }); - messages = new LivePagedListBuilder<>(db.message().pagedFolder(folder, debug), PAGE_SIZE).build(); + messages = new LivePagedListBuilder<>(db.message().pagedFolder(folder, debug), PAGE_SIZE).build(); + } + else { + setSubtitle(R.string.title_folder_thread); + messages = new LivePagedListBuilder<>(db.message().pagedThread(thread, debug), PAGE_SIZE).build(); } - else { - setSubtitle(R.string.title_folder_thread); - messages = new LivePagedListBuilder<>(db.message().pagedThread(thread, debug), PAGE_SIZE).build(); + } else { + setSubtitle(getString(R.string.title_searching, search)); + + DataSource.Factory dsf = new DataSource.Factory() { + @Override + public DataSource create() { + return new PositionalDataSource() { + @Override + public void loadInitial(LoadInitialParams params, LoadInitialCallback callback) { + Log.i(Helper.TAG, "loadInitial(" + params.requestedStartPosition + ", " + params.requestedLoadSize + ")"); + callback.onResult(search(search, params.requestedStartPosition, params.requestedLoadSize), params.requestedStartPosition); + } + + @Override + public void loadRange(LoadRangeParams params, LoadRangeCallback callback) { + Log.i(Helper.TAG, "loadRange(" + params.startPosition + ", " + params.loadSize + ")"); + callback.onResult(search(search, params.startPosition, params.loadSize)); + } + + private List search(String term, int from, int count) { + List list = new ArrayList<>(); + IMAPStore istore = null; + try { + DB db = DB.getInstance(getContext()); + + EntityFolder f = db.folder().getFolder(folder); + EntityAccount account = db.account().getAccount(f.account); + + Properties props = MessageHelper.getSessionProperties(); + Session isession = Session.getInstance(props, null); + Log.i(Helper.TAG, "Connecting to account=" + account.name); + istore = (IMAPStore) isession.getStore("imaps"); + istore.connect(account.host, account.port, account.user, account.password); + + Log.i(Helper.TAG, "Opening folder=" + f.name); + IMAPFolder ifolder = (IMAPFolder) istore.getFolder(f.name); + ifolder.open(Folder.READ_WRITE); + + Log.i(Helper.TAG, "Search for term=" + term); + Message[] imessages = ifolder.search( + new OrTerm( + new SubjectTerm(term), + new BodyTerm(term))); + Log.i(Helper.TAG, "Found messages=" + imessages.length); + + List selected = new ArrayList<>(); + int base = imessages.length - 1 - from; + for (int i = base; i >= 0 && i >= base - count - 1; i--) + selected.add(imessages[i]); + Log.i(Helper.TAG, "Selected messages=" + selected.size()); + + FetchProfile fp = new FetchProfile(); + fp.add(UIDFolder.FetchProfileItem.UID); + fp.add(IMAPFolder.FetchProfileItem.FLAGS); + fp.add(FetchProfile.Item.ENVELOPE); + fp.add(FetchProfile.Item.CONTENT_INFO); + fp.add(IMAPFolder.FetchProfileItem.HEADERS); + fp.add(IMAPFolder.FetchProfileItem.MESSAGE); + ifolder.fetch(selected.toArray(new Message[0]), fp); + + for (Message imessage : selected) { + long uid = ifolder.getUID(imessage); + Log.i(Helper.TAG, "Get uid=" + uid); + + MessageHelper helper = new MessageHelper((MimeMessage) imessage); + boolean seen = helper.getSeen(); + + TupleMessageEx message = new TupleMessageEx(); + message.id = uid; + message.account = f.account; + message.folder = f.id; + message.uid = uid; + message.msgid = helper.getMessageID(); + message.references = TextUtils.join(" ", helper.getReferences()); + message.inreplyto = helper.getInReplyTo(); + message.thread = helper.getThreadId(uid); + message.from = helper.getFrom(); + message.to = helper.getTo(); + message.cc = helper.getCc(); + message.bcc = helper.getBcc(); + message.reply = helper.getReply(); + message.subject = imessage.getSubject(); + message.received = imessage.getReceivedDate().getTime(); + message.sent = (imessage.getSentDate() == null ? null : imessage.getSentDate().getTime()); + message.seen = seen; + message.ui_seen = seen; + message.ui_hide = false; + + message.accountName = account.name; + message.folderName = f.name; + message.folderType = f.type; + message.count = 1; + message.unseen = (seen ? 0 : 1); + message.attachments = 0; + + list.add(message); + } + } catch (Throwable ex) { + Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); + } finally { + if (istore != null) + try { + istore.close(); + } catch (MessagingException ex) { + Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); + } + } + + return list; + } + + }; + } + }; + + PagedList.Config.Builder plcb = new PagedList.Config.Builder() + .setEnablePlaceholders(false) + .setInitialLoadSizeHint(10) + .setPageSize(PAGE_SIZE); + + messages = new LivePagedListBuilder<>(dsf, plcb.build()).build(); } messages.observe(getViewLifecycleOwner(), new Observer>() { @@ -204,11 +355,15 @@ public class FragmentMessages extends FragmentEx { } }); + Bundle args = new Bundle(); + args.putLong("folder", folder); + args.putLong("thread", thread); + new SimpleTask() { @Override protected Long onLoad(Context context, Bundle args) { - long folder = (args == null ? -1 : args.getLong("folder", -1)); - long thread = (args == null ? -1 : args.getLong("thread", -1)); // message ID + long folder = args.getLong("folder", -1); + long thread = args.getLong("thread", -1); // message ID DB db = DB.getInstance(context); @@ -241,17 +396,47 @@ public class FragmentMessages extends FragmentEx { protected void onException(Bundle args, Throwable ex) { Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show(); } - }.load(this, getArguments()); + }.load(this, args); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.menu_list, menu); + + // TODO: search hint + final MenuItem menuSearch = menu.findItem(R.id.menu_search); + final SearchView searchView = (SearchView) menuSearch.getActionView(); + searchView.setSubmitButtonEnabled(true); + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + menuSearch.collapseActionView(); + + Intent intent = new Intent(); + intent.putExtra("folder", folder); + intent.putExtra("search", query); + + FragmentMessages fragment = new FragmentMessages(); + fragment.setArguments(intent.getExtras()); + FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); + fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("search"); + fragmentTransaction.commit(); + + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { + return false; + } + }); + super.onCreateOptionsMenu(menu, inflater); } @Override public void onPrepareOptionsMenu(Menu menu) { + menu.findItem(R.id.menu_search).setVisible(folder >= 0); menu.findItem(R.id.menu_folders).setVisible(primary >= 0); super.onPrepareOptionsMenu(menu); } diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index 948bfd67..857c316c 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -91,6 +91,12 @@ public class MessageHelper { props.put("mail.smtp.writetimeout", "20000"); // one thread overhead props.put("mail.smtp.timeout", "20000"); + props.put("mail.imaps.peek", "true"); + //props.put("mail.imaps.minidletime", "5000"); + + props.put("mail.mime.address.strict", "false"); + props.put("mail.mime.decodetext.strict", "false"); + return props; } diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index c0b54d2a..7c0be6f9 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -386,16 +386,11 @@ public class ServiceSynchronize extends LifecycleService { final DB db = DB.getInstance(this); final ExecutorService executor = Executors.newSingleThreadExecutor(); - Properties props = MessageHelper.getSessionProperties(); - props.setProperty("mail.imaps.peek", "true"); - props.setProperty("mail.mime.address.strict", "false"); - props.setProperty("mail.mime.decodetext.strict", "false"); - //props.put("mail.imaps.minidletime", "5000"); - boolean debug = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("debug", false); if (debug) System.setProperty("mail.socket.debug", "true"); + Properties props = MessageHelper.getSessionProperties(); final Session isession = Session.getInstance(props, null); isession.setDebug(debug); // adb -t 1 logcat | grep "fairemail\|System.out" diff --git a/app/src/main/res/drawable/baseline_search_24.xml b/app/src/main/res/drawable/baseline_search_24.xml new file mode 100644 index 00000000..083d6364 --- /dev/null +++ b/app/src/main/res/drawable/baseline_search_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/menu/menu_list.xml b/app/src/main/res/menu/menu_list.xml index 91b7ce4f..5339b089 100644 --- a/app/src/main/res/menu/menu_list.xml +++ b/app/src/main/res/menu/menu_list.xml @@ -2,6 +2,13 @@ + + Draft saved Sending message + Search + Searching \'%1$s\' + CC/BCC Attachment Synchronize