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; | ||||||
| @ -79,7 +87,17 @@ public class MessageSearchTask implements Runnable, Cancellable { | |||||||
| 			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,6 +111,7 @@ 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"); | ||||||
| @ -122,8 +141,4 @@ public class MessageSearchTask implements Runnable, Cancellable { | |||||||
| 	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 New Issue
	
	Block a user
	 Daniel Gultsch
						Daniel Gultsch