properly cancel pending searchs and scroll to bottom after refresh
This commit is contained in:
parent
35020702fb
commit
e6feb91390
|
@ -74,6 +74,7 @@ public final class Config {
|
||||||
|
|
||||||
public static final int PAGE_SIZE = 50;
|
public static final int PAGE_SIZE = 50;
|
||||||
public static final int MAX_NUM_PAGES = 3;
|
public static final int MAX_NUM_PAGES = 3;
|
||||||
|
public static final int MAX_SEARCH_RESULTS = 300;
|
||||||
|
|
||||||
public static final int REFRESH_UI_INTERVAL = 500;
|
public static final int REFRESH_UI_INTERVAL = 500;
|
||||||
|
|
||||||
|
|
|
@ -706,7 +706,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
|
|
||||||
public Cursor getMessageSearchCursor(String term) {
|
public Cursor getMessageSearchCursor(String term) {
|
||||||
SQLiteDatabase db = this.getReadableDatabase();
|
SQLiteDatabase db = this.getReadableDatabase();
|
||||||
String SQL = "SELECT "+Message.TABLENAME+".*,"+Conversation.TABLENAME+'.'+Conversation.CONTACTJID+','+Conversation.TABLENAME+'.'+Conversation.ACCOUNT+','+Conversation.TABLENAME+'.'+Conversation.MODE+" FROM "+Message.TABLENAME +" join "+Conversation.TABLENAME+" on "+Message.TABLENAME+'.'+Message.CONVERSATION+'='+Conversation.TABLENAME+'.'+Conversation.UUID+" where "+Message.BODY +" LIKE ? limit 200";
|
String SQL = "SELECT "+Message.TABLENAME+".*,"+Conversation.TABLENAME+'.'+Conversation.CONTACTJID+','+Conversation.TABLENAME+'.'+Conversation.ACCOUNT+','+Conversation.TABLENAME+'.'+Conversation.MODE+" FROM "+Message.TABLENAME +" join "+Conversation.TABLENAME+" on "+Message.TABLENAME+'.'+Message.CONVERSATION+'='+Conversation.TABLENAME+'.'+Conversation.UUID+" where "+Message.ENCRYPTION+" NOT IN("+Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE+','+Message.ENCRYPTION_PGP+','+Message.ENCRYPTION_DECRYPTION_FAILED+") AND "+Message.BODY +" LIKE ? ORDER BY "+Message.TIME_SENT+" DESC limit "+Config.MAX_SEARCH_RESULTS;
|
||||||
return db.rawQuery(SQL,new String[]{'%'+term+'%'});
|
return db.rawQuery(SQL,new String[]{'%'+term+'%'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,14 @@ public class MessageSearchTask implements Runnable, Cancellable {
|
||||||
this.onSearchResultsAvailable = onSearchResultsAvailable;
|
this.onSearchResultsAvailable = onSearchResultsAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void search(XmppConnectionService xmppConnectionService, String term, OnSearchResultsAvailable onSearchResultsAvailable) {
|
||||||
|
new MessageSearchTask(xmppConnectionService, term, onSearchResultsAvailable).executeInBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void cancelRunningTasks() {
|
||||||
|
EXECUTOR.cancelRunningTasks();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
this.isCancelled = true;
|
this.isCancelled = true;
|
||||||
|
@ -76,10 +84,20 @@ public class MessageSearchTask implements Runnable, Cancellable {
|
||||||
long startTimestamp = SystemClock.elapsedRealtime();
|
long startTimestamp = SystemClock.elapsedRealtime();
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
final HashMap<String,Conversational> conversationCache = new HashMap<>();
|
final HashMap<String, Conversational> conversationCache = new HashMap<>();
|
||||||
final List<Message> result = new ArrayList<>();
|
final List<Message> result = new ArrayList<>();
|
||||||
cursor = xmppConnectionService.databaseBackend.getMessageSearchCursor(term);
|
cursor = xmppConnectionService.databaseBackend.getMessageSearchCursor(term);
|
||||||
while(cursor.moveToNext()) {
|
if (isCancelled) {
|
||||||
|
Log.d(Config.LOGTAG, "canceled search task");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cursor != null && cursor.getCount() > 0) {
|
||||||
|
cursor.moveToLast();
|
||||||
|
do {
|
||||||
|
if (isCancelled) {
|
||||||
|
Log.d(Config.LOGTAG, "canceled search task");
|
||||||
|
return;
|
||||||
|
}
|
||||||
final String conversationUuid = cursor.getString(cursor.getColumnIndex(Message.CONVERSATION));
|
final String conversationUuid = cursor.getString(cursor.getColumnIndex(Message.CONVERSATION));
|
||||||
Conversational conversation;
|
Conversational conversation;
|
||||||
if (conversationCache.containsKey(conversationUuid)) {
|
if (conversationCache.containsKey(conversationUuid)) {
|
||||||
|
@ -93,12 +111,13 @@ public class MessageSearchTask implements Runnable, Cancellable {
|
||||||
}
|
}
|
||||||
Message message = IndividualMessage.fromCursor(cursor, conversation);
|
Message message = IndividualMessage.fromCursor(cursor, conversation);
|
||||||
result.add(message);
|
result.add(message);
|
||||||
|
} while (cursor.moveToPrevious());
|
||||||
}
|
}
|
||||||
long stopTimestamp = SystemClock.elapsedRealtime();
|
long stopTimestamp = SystemClock.elapsedRealtime();
|
||||||
Log.d(Config.LOGTAG,"found "+result.size()+" messages in "+(stopTimestamp - startTimestamp)+"ms");
|
Log.d(Config.LOGTAG, "found " + result.size() + " messages in " + (stopTimestamp - startTimestamp) + "ms");
|
||||||
onSearchResultsAvailable.onSearchResultsAvailable(term, result);
|
onSearchResultsAvailable.onSearchResultsAvailable(term, result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.d(Config.LOGTAG,"exception while searching ",e);
|
Log.d(Config.LOGTAG, "exception while searching ", e);
|
||||||
} finally {
|
} finally {
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
cursor.close();
|
cursor.close();
|
||||||
|
@ -116,14 +135,10 @@ public class MessageSearchTask implements Runnable, Cancellable {
|
||||||
if (account != null && jid != null) {
|
if (account != null && jid != null) {
|
||||||
return new StubConversation(account, conversationUuid, jid.asBareJid(), mode);
|
return new StubConversation(account, conversationUuid, jid.asBareJid(), mode);
|
||||||
}
|
}
|
||||||
throw new Exception("Unable to generate stub for "+contactJid);
|
throw new Exception("Unable to generate stub for " + contactJid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void executeInBackground() {
|
private void executeInBackground() {
|
||||||
EXECUTOR.execute(this);
|
EXECUTOR.execute(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void search(XmppConnectionService xmppConnectionService, String term, OnSearchResultsAvailable onSearchResultsAvailable) {
|
|
||||||
new MessageSearchTask(xmppConnectionService, term, onSearchResultsAvailable).executeInBackground();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,7 @@ import eu.siacs.conversations.ui.adapter.MessageAdapter;
|
||||||
import eu.siacs.conversations.ui.util.ActivityResult;
|
import eu.siacs.conversations.ui.util.ActivityResult;
|
||||||
import eu.siacs.conversations.ui.util.AttachmentTool;
|
import eu.siacs.conversations.ui.util.AttachmentTool;
|
||||||
import eu.siacs.conversations.ui.util.ConversationMenuConfigurator;
|
import eu.siacs.conversations.ui.util.ConversationMenuConfigurator;
|
||||||
|
import eu.siacs.conversations.ui.util.ListViewUtils;
|
||||||
import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
|
import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
|
||||||
import eu.siacs.conversations.ui.util.PendingItem;
|
import eu.siacs.conversations.ui.util.PendingItem;
|
||||||
import eu.siacs.conversations.ui.util.PresenceSelector;
|
import eu.siacs.conversations.ui.util.PresenceSelector;
|
||||||
|
@ -1940,21 +1941,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSelection(int pos, boolean jumpToBottom) {
|
private void setSelection(int pos, boolean jumpToBottom) {
|
||||||
setSelection(this.binding.messagesView, pos, jumpToBottom);
|
ListViewUtils.setSelection(this.binding.messagesView, pos, jumpToBottom);
|
||||||
this.binding.messagesView.post(() -> setSelection(this.binding.messagesView, pos, jumpToBottom));
|
this.binding.messagesView.post(() -> ListViewUtils.setSelection(this.binding.messagesView, pos, jumpToBottom));
|
||||||
this.binding.messagesView.post(this::fireReadEvent);
|
this.binding.messagesView.post(this::fireReadEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setSelection(final ListView listView, int pos, boolean jumpToBottom) {
|
|
||||||
if (jumpToBottom) {
|
|
||||||
final View lastChild = listView.getChildAt(listView.getChildCount() - 1);
|
|
||||||
if (lastChild != null) {
|
|
||||||
listView.setSelectionFromTop(pos, -lastChild.getHeight());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
listView.setSelection(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean scrolledToBottom() {
|
private boolean scrolledToBottom() {
|
||||||
return this.binding != null && scrolledToBottom(this.binding.messagesView);
|
return this.binding != null && scrolledToBottom(this.binding.messagesView);
|
||||||
|
|
|
@ -33,6 +33,7 @@ import android.databinding.DataBindingUtil;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
|
import android.text.InputType;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
|
@ -46,10 +47,12 @@ import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.databinding.ActivitySearchBinding;
|
import eu.siacs.conversations.databinding.ActivitySearchBinding;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
|
import eu.siacs.conversations.services.MessageSearchTask;
|
||||||
import eu.siacs.conversations.ui.adapter.MessageAdapter;
|
import eu.siacs.conversations.ui.adapter.MessageAdapter;
|
||||||
import eu.siacs.conversations.ui.interfaces.OnSearchResultsAvailable;
|
import eu.siacs.conversations.ui.interfaces.OnSearchResultsAvailable;
|
||||||
import eu.siacs.conversations.ui.util.Color;
|
import eu.siacs.conversations.ui.util.Color;
|
||||||
import eu.siacs.conversations.ui.util.Drawable;
|
import eu.siacs.conversations.ui.util.Drawable;
|
||||||
|
import eu.siacs.conversations.ui.util.ListViewUtils;
|
||||||
|
|
||||||
import static eu.siacs.conversations.ui.util.SoftKeyboardUtils.hideSoftKeyboard;
|
import static eu.siacs.conversations.ui.util.SoftKeyboardUtils.hideSoftKeyboard;
|
||||||
import static eu.siacs.conversations.ui.util.SoftKeyboardUtils.showKeyboard;
|
import static eu.siacs.conversations.ui.util.SoftKeyboardUtils.showKeyboard;
|
||||||
|
@ -77,6 +80,7 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc
|
||||||
EditText searchField = searchActionMenuItem.getActionView().findViewById(R.id.search_field);
|
EditText searchField = searchActionMenuItem.getActionView().findViewById(R.id.search_field);
|
||||||
searchField.addTextChangedListener(this);
|
searchField.addTextChangedListener(this);
|
||||||
searchField.setHint(R.string.search_messages);
|
searchField.setHint(R.string.search_messages);
|
||||||
|
searchField.setInputType(InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
|
||||||
showKeyboard(searchField);
|
showKeyboard(searchField);
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
@ -127,6 +131,7 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc
|
||||||
if (term.length() > 0) {
|
if (term.length() > 0) {
|
||||||
xmppConnectionService.search(s.toString().trim(), this);
|
xmppConnectionService.search(s.toString().trim(), this);
|
||||||
} else {
|
} else {
|
||||||
|
MessageSearchTask.cancelRunningTasks();
|
||||||
this.messages.clear();
|
this.messages.clear();
|
||||||
messageListAdapter.notifyDataSetChanged();
|
messageListAdapter.notifyDataSetChanged();
|
||||||
changeBackground(false, false);
|
changeBackground(false, false);
|
||||||
|
@ -135,11 +140,12 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSearchResultsAvailable(String term, List<Message> messages) {
|
public void onSearchResultsAvailable(String term, List<Message> messages) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
this.messages.clear();
|
this.messages.clear();
|
||||||
this.messages.addAll(messages);
|
this.messages.addAll(messages);
|
||||||
runOnUiThread(() -> {
|
|
||||||
messageListAdapter.notifyDataSetChanged();
|
messageListAdapter.notifyDataSetChanged();
|
||||||
changeBackground(true, messages.size() > 0);
|
changeBackground(true, messages.size() > 0);
|
||||||
|
ListViewUtils.scrollToBottom(this.binding.searchResults);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Daniel Gultsch All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation and/or
|
||||||
|
* other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* 3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package eu.siacs.conversations.ui.util;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ListView;
|
||||||
|
|
||||||
|
|
||||||
|
public class ListViewUtils {
|
||||||
|
|
||||||
|
public static void scrollToBottom(final ListView listView) {
|
||||||
|
int count = listView.getAdapter().getCount();
|
||||||
|
if (count > 0) {
|
||||||
|
setSelection(listView, count - 1, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setSelection(final ListView listView, int pos, boolean jumpToBottom) {
|
||||||
|
if (jumpToBottom) {
|
||||||
|
final View lastChild = listView.getChildAt(listView.getChildCount() - 1);
|
||||||
|
if (lastChild != null) {
|
||||||
|
listView.setSelectionFromTop(pos, -lastChild.getHeight());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listView.setSelection(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -18,4 +18,11 @@ public class ReplacingSerialSingleThreadExecutor extends SerialSingleThreadExecu
|
||||||
}
|
}
|
||||||
super.execute(r);
|
super.execute(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void cancelRunningTasks() {
|
||||||
|
tasks.clear();
|
||||||
|
if (active != null && active instanceof Cancellable) {
|
||||||
|
((Cancellable) active).cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,13 +30,7 @@ public class SerialSingleThreadExecutor implements Executor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void execute(final Runnable r) {
|
public synchronized void execute(final Runnable r) {
|
||||||
tasks.offer(() -> {
|
tasks.offer(new Runner(r));
|
||||||
try {
|
|
||||||
r.run();
|
|
||||||
} finally {
|
|
||||||
scheduleNext();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (active == null) {
|
if (active == null) {
|
||||||
scheduleNext();
|
scheduleNext();
|
||||||
}
|
}
|
||||||
|
@ -51,4 +45,29 @@ public class SerialSingleThreadExecutor implements Executor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class Runner implements Runnable, Cancellable {
|
||||||
|
|
||||||
|
private final Runnable runnable;
|
||||||
|
|
||||||
|
private Runner(Runnable runnable) {
|
||||||
|
this.runnable = runnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
if (runnable instanceof Cancellable) {
|
||||||
|
((Cancellable) runnable).cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
runnable.run();
|
||||||
|
} finally {
|
||||||
|
scheduleNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue