Add ability to scroll to message selected in search results

Signed-off-by: Álvaro Brey <alvaro.brey@nextcloud.com>
This commit is contained in:
Álvaro Brey 2022-05-27 17:17:07 +02:00
parent b5d8f6ee95
commit dd55ab5741
No known key found for this signature in database
GPG Key ID: 2585783189A62105
4 changed files with 158 additions and 92 deletions

View File

@ -1346,92 +1346,21 @@ class ChatController(args: Bundle) :
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
if (resultCode != RESULT_OK) { if (resultCode != RESULT_OK && (requestCode != REQUEST_CODE_MESSAGE_SEARCH)) {
// TODO for message search, CANCELED is fine
Log.e(TAG, "resultCode for received intent was != ok") Log.e(TAG, "resultCode for received intent was != ok")
return return
} }
if (requestCode == REQUEST_CODE_CHOOSE_FILE) { when (requestCode) {
try { REQUEST_CODE_CHOOSE_FILE -> {
checkNotNull(intent)
filesToUpload.clear()
intent.clipData?.let {
for (index in 0 until it.itemCount) {
filesToUpload.add(it.getItemAt(index).uri.toString())
}
} ?: run {
checkNotNull(intent.data)
intent.data.let {
filesToUpload.add(intent.data.toString())
}
}
require(filesToUpload.isNotEmpty())
val filenamesWithLinebreaks = StringBuilder("\n")
for (file in filesToUpload) {
val filename = UriUtils.getFileName(Uri.parse(file), context)
filenamesWithLinebreaks.append(filename).append("\n")
}
val confirmationQuestion = when (filesToUpload.size) {
1 -> context?.resources?.getString(R.string.nc_upload_confirm_send_single)?.let {
String.format(it, title)
}
else -> context?.resources?.getString(R.string.nc_upload_confirm_send_multiple)?.let {
String.format(it, title)
}
}
LovelyStandardDialog(activity)
.setPositiveButtonColorRes(R.color.nc_darkGreen)
.setTitle(confirmationQuestion)
.setMessage(filenamesWithLinebreaks.toString())
.setPositiveButton(R.string.nc_yes) { v ->
if (UploadAndShareFilesWorker.isStoragePermissionGranted(context!!)) {
uploadFiles(filesToUpload, false)
} else {
UploadAndShareFilesWorker.requestStoragePermission(this)
}
}
.setNegativeButton(R.string.nc_no) {
// unused atm
}
.show()
} catch (e: IllegalStateException) {
Toast.makeText(context, context?.resources?.getString(R.string.nc_upload_failed), Toast.LENGTH_LONG)
.show()
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
} catch (e: IllegalArgumentException) {
Toast.makeText(context, context?.resources?.getString(R.string.nc_upload_failed), Toast.LENGTH_LONG)
.show()
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
}
} else if (requestCode == REQUEST_CODE_SELECT_CONTACT) {
val contactUri = intent?.data ?: return
val cursor: Cursor? = activity?.contentResolver!!.query(contactUri, null, null, null, null)
if (cursor != null && cursor.moveToFirst()) {
val id = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts._ID))
val fileName = ContactUtils.getDisplayNameFromDeviceContact(context!!, id) + ".vcf"
val file = File(context?.cacheDir, fileName)
writeContactToVcfFile(cursor, file)
val shareUri = FileProvider.getUriForFile(
activity!!,
BuildConfig.APPLICATION_ID,
File(file.absolutePath)
)
uploadFiles(mutableListOf(shareUri.toString()), false)
}
cursor?.close()
} else if (requestCode == REQUEST_CODE_PICK_CAMERA) {
if (resultCode == RESULT_OK) {
try { try {
checkNotNull(intent) checkNotNull(intent)
filesToUpload.clear() filesToUpload.clear()
run { intent.clipData?.let {
for (index in 0 until it.itemCount) {
filesToUpload.add(it.getItemAt(index).uri.toString())
}
} ?: run {
checkNotNull(intent.data) checkNotNull(intent.data)
intent.data.let { intent.data.let {
filesToUpload.add(intent.data.toString()) filesToUpload.add(intent.data.toString())
@ -1439,11 +1368,37 @@ class ChatController(args: Bundle) :
} }
require(filesToUpload.isNotEmpty()) require(filesToUpload.isNotEmpty())
if (UploadAndShareFilesWorker.isStoragePermissionGranted(context!!)) { val filenamesWithLinebreaks = StringBuilder("\n")
uploadFiles(filesToUpload, false)
} else { for (file in filesToUpload) {
UploadAndShareFilesWorker.requestStoragePermission(this) val filename = UriUtils.getFileName(Uri.parse(file), context)
filenamesWithLinebreaks.append(filename).append("\n")
} }
val confirmationQuestion = when (filesToUpload.size) {
1 -> context?.resources?.getString(R.string.nc_upload_confirm_send_single)?.let {
String.format(it, title)
}
else -> context?.resources?.getString(R.string.nc_upload_confirm_send_multiple)?.let {
String.format(it, title)
}
}
LovelyStandardDialog(activity)
.setPositiveButtonColorRes(R.color.nc_darkGreen)
.setTitle(confirmationQuestion)
.setMessage(filenamesWithLinebreaks.toString())
.setPositiveButton(R.string.nc_yes) { v ->
if (UploadAndShareFilesWorker.isStoragePermissionGranted(context!!)) {
uploadFiles(filesToUpload, false)
} else {
UploadAndShareFilesWorker.requestStoragePermission(this)
}
}
.setNegativeButton(R.string.nc_no) {
// unused atm
}
.show()
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
Toast.makeText(context, context?.resources?.getString(R.string.nc_upload_failed), Toast.LENGTH_LONG) Toast.makeText(context, context?.resources?.getString(R.string.nc_upload_failed), Toast.LENGTH_LONG)
.show() .show()
@ -1454,8 +1409,79 @@ class ChatController(args: Bundle) :
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e) Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
} }
} }
} else if (requestCode == REQUEST_CODE_MESSAGE_SEARCH) { REQUEST_CODE_SELECT_CONTACT -> {
TODO() val contactUri = intent?.data ?: return
val cursor: Cursor? = activity?.contentResolver!!.query(contactUri, null, null, null, null)
if (cursor != null && cursor.moveToFirst()) {
val id = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts._ID))
val fileName = ContactUtils.getDisplayNameFromDeviceContact(context!!, id) + ".vcf"
val file = File(context?.cacheDir, fileName)
writeContactToVcfFile(cursor, file)
val shareUri = FileProvider.getUriForFile(
activity!!,
BuildConfig.APPLICATION_ID,
File(file.absolutePath)
)
uploadFiles(mutableListOf(shareUri.toString()), false)
}
cursor?.close()
}
REQUEST_CODE_PICK_CAMERA -> {
if (resultCode == RESULT_OK) {
try {
checkNotNull(intent)
filesToUpload.clear()
run {
checkNotNull(intent.data)
intent.data.let {
filesToUpload.add(intent.data.toString())
}
}
require(filesToUpload.isNotEmpty())
if (UploadAndShareFilesWorker.isStoragePermissionGranted(context!!)) {
uploadFiles(filesToUpload, false)
} else {
UploadAndShareFilesWorker.requestStoragePermission(this)
}
} catch (e: IllegalStateException) {
Toast.makeText(
context,
context?.resources?.getString(R.string.nc_upload_failed),
Toast.LENGTH_LONG
)
.show()
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
} catch (e: IllegalArgumentException) {
Toast.makeText(
context,
context?.resources?.getString(R.string.nc_upload_failed),
Toast.LENGTH_LONG
)
.show()
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
}
}
}
REQUEST_CODE_MESSAGE_SEARCH -> {
val messageId = intent?.getStringExtra(MessageSearchActivity.RESULT_KEY_MESSAGE_ID)
messageId?.let { id ->
scrollToMessageWithId(id)
}
}
}
}
private fun scrollToMessageWithId(messageId: String) {
val position = adapter?.items?.indexOfFirst {
it.item is ChatMessage && (it.item as ChatMessage).id == messageId
}
if (position != null && position >= 0) {
binding.messagesListView.smoothScrollToPosition(position)
} else {
// TODO show error that we don't have that message?
} }
} }
@ -2283,6 +2309,7 @@ class ChatController(args: Bundle) :
if (adapter != null) { if (adapter != null) {
adapter?.addToEnd(chatMessageList, false) adapter?.addToEnd(chatMessageList, false)
} }
scrollToRequestedMessageIfNeeded()
} else { } else {
var chatMessage: ChatMessage var chatMessage: ChatMessage
@ -2398,6 +2425,12 @@ class ChatController(args: Bundle) :
} }
} }
private fun scrollToRequestedMessageIfNeeded() {
args.getString(BundleKeys.KEY_MESSAGE_ID)?.let {
scrollToMessageWithId(it)
}
}
private fun isSameDayNonSystemMessages(messageLeft: ChatMessage, messageRight: ChatMessage): Boolean { private fun isSameDayNonSystemMessages(messageLeft: ChatMessage, messageRight: ChatMessage): Boolean {
return TextUtils.isEmpty(messageLeft.systemMessage) && return TextUtils.isEmpty(messageLeft.systemMessage) &&
TextUtils.isEmpty(messageRight.systemMessage) && TextUtils.isEmpty(messageRight.systemMessage) &&
@ -2515,7 +2548,7 @@ class ChatController(args: Bundle) :
intent.putExtra(KEY_CONVERSATION_NAME, currentConversation?.displayName) intent.putExtra(KEY_CONVERSATION_NAME, currentConversation?.displayName)
intent.putExtra(KEY_ROOM_TOKEN, roomToken) intent.putExtra(KEY_ROOM_TOKEN, roomToken)
intent.putExtra(KEY_USER_ENTITY, conversationUser as Parcelable) intent.putExtra(KEY_USER_ENTITY, conversationUser as Parcelable)
activity!!.startActivityForResult(intent, REQUEST_CODE_MESSAGE_SEARCH) startActivityForResult(intent, REQUEST_CODE_MESSAGE_SEARCH)
} }
private fun handleSystemMessages(chatMessageList: List<ChatMessage>): List<ChatMessage> { private fun handleSystemMessages(chatMessageList: List<ChatMessage>): List<ChatMessage> {

View File

@ -74,7 +74,6 @@ import com.nextcloud.talk.adapters.items.MessagesTextHeaderItem;
import com.nextcloud.talk.api.NcApi; import com.nextcloud.talk.api.NcApi;
import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.controllers.base.BaseController; import com.nextcloud.talk.controllers.base.BaseController;
import com.nextcloud.talk.messagesearch.MessageSearchHelper;
import com.nextcloud.talk.events.ConversationsListFetchDataEvent; import com.nextcloud.talk.events.ConversationsListFetchDataEvent;
import com.nextcloud.talk.events.EventStatus; import com.nextcloud.talk.events.EventStatus;
import com.nextcloud.talk.interfaces.ConversationMenuInterface; import com.nextcloud.talk.interfaces.ConversationMenuInterface;
@ -82,6 +81,7 @@ import com.nextcloud.talk.jobs.AccountRemovalWorker;
import com.nextcloud.talk.jobs.ContactAddressBookWorker; import com.nextcloud.talk.jobs.ContactAddressBookWorker;
import com.nextcloud.talk.jobs.DeleteConversationWorker; import com.nextcloud.talk.jobs.DeleteConversationWorker;
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker; import com.nextcloud.talk.jobs.UploadAndShareFilesWorker;
import com.nextcloud.talk.messagesearch.MessageSearchHelper;
import com.nextcloud.talk.models.database.CapabilitiesUtil; import com.nextcloud.talk.models.database.CapabilitiesUtil;
import com.nextcloud.talk.models.database.UserEntity; import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.domain.SearchMessageEntry; import com.nextcloud.talk.models.domain.SearchMessageEntry;
@ -223,6 +223,7 @@ public class ConversationsListController extends BaseController implements Flexi
private Conversation selectedConversation; private Conversation selectedConversation;
private String textToPaste = ""; private String textToPaste = "";
private String selectedMessageId = null;
private boolean forwardMessage; private boolean forwardMessage;
@ -921,7 +922,9 @@ public class ConversationsListController extends BaseController implements Flexi
@SuppressLint("CheckResult") // handled by helper @SuppressLint("CheckResult") // handled by helper
private void startMessageSearch(final String search) { private void startMessageSearch(final String search) {
swipeRefreshLayout.setRefreshing(true); if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(true);
}
searchHelper searchHelper
.startMessageSearch(search) .startMessageSearch(search)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
@ -958,6 +961,7 @@ public class ConversationsListController extends BaseController implements Flexi
} else if (item instanceof MessageResultItem) { } else if (item instanceof MessageResultItem) {
MessageResultItem messageItem = (MessageResultItem) item; MessageResultItem messageItem = (MessageResultItem) item;
String conversationToken = messageItem.getMessageEntry().getConversationToken(); String conversationToken = messageItem.getMessageEntry().getConversationToken();
selectedMessageId = messageItem.getMessageEntry().getMessageId();
showConversationByToken(conversationToken); showConversationByToken(conversationToken);
} else if (item instanceof LoadMoreResultsItem) { } else if (item instanceof LoadMoreResultsItem) {
loadMoreMessages(); loadMoreMessages();
@ -1187,6 +1191,10 @@ public class ConversationsListController extends BaseController implements Flexi
bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), selectedConversation.getToken()); bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), selectedConversation.getToken());
bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), selectedConversation.getRoomId()); bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), selectedConversation.getRoomId());
bundle.putString(BundleKeys.INSTANCE.getKEY_SHARED_TEXT(), textToPaste); bundle.putString(BundleKeys.INSTANCE.getKEY_SHARED_TEXT(), textToPaste);
if (selectedMessageId != null) {
bundle.putString(BundleKeys.KEY_MESSAGE_ID, selectedMessageId);
selectedMessageId = null;
}
ConductorRemapping.INSTANCE.remapChatController(getRouter(), currentUser.getId(), ConductorRemapping.INSTANCE.remapChatController(getRouter(), currentUser.getId(),
selectedConversation.getToken(), bundle, false); selectedConversation.getToken(), bundle, false);
@ -1394,11 +1402,15 @@ public class ConversationsListController extends BaseController implements Flexi
recyclerView.scrollToPosition(0); recyclerView.scrollToPosition(0);
} }
} }
swipeRefreshLayout.setRefreshing(false); if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(false);
}
} }
public void onMessageSearchError(@NonNull Throwable throwable) { public void onMessageSearchError(@NonNull Throwable throwable) {
handleHttpExceptions(throwable); handleHttpExceptions(throwable);
swipeRefreshLayout.setRefreshing(false); if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(false);
}
} }
} }

View File

@ -22,6 +22,7 @@
package com.nextcloud.talk.messagesearch package com.nextcloud.talk.messagesearch
import android.app.Activity import android.app.Activity
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.view.Menu import android.view.Menu
@ -154,14 +155,29 @@ class MessageSearchActivity : BaseActivity() {
adapter!!.addListener(object : FlexibleAdapter.OnItemClickListener { adapter!!.addListener(object : FlexibleAdapter.OnItemClickListener {
override fun onItemClick(view: View?, position: Int): Boolean { override fun onItemClick(view: View?, position: Int): Boolean {
val item = adapter!!.getItem(position) val item = adapter!!.getItem(position)
if (item?.itemViewType == LoadMoreResultsItem.VIEW_TYPE) { when (item?.itemViewType) {
viewModel.loadMore() LoadMoreResultsItem.VIEW_TYPE -> {
viewModel.loadMore()
}
MessageResultItem.VIEW_TYPE -> {
// TODO go through viewmodel
val messageItem = item as MessageResultItem
finishWithResult(messageItem.messageEntry.messageId!!)
}
} }
return false return false
} }
}) })
} }
private fun finishWithResult(messageId: String) {
val resultIntent = Intent().apply {
putExtra(RESULT_KEY_MESSAGE_ID, messageId)
}
setResult(Activity.RESULT_OK, resultIntent)
finish()
}
private fun showInitial() { private fun showInitial() {
binding.messageSearchRecycler.visibility = View.GONE binding.messageSearchRecycler.visibility = View.GONE
binding.emptyContainer.emptyListViewHeadline.text = "Start typing to search..." binding.emptyContainer.emptyListViewHeadline.text = "Start typing to search..."
@ -235,4 +251,8 @@ class MessageSearchActivity : BaseActivity() {
super.onDestroy() super.onDestroy()
searchViewDisposable?.dispose() searchViewDisposable?.dispose()
} }
companion object {
const val RESULT_KEY_MESSAGE_ID = "MessageSearchActivity.result.message"
}
} }

View File

@ -73,4 +73,5 @@ object BundleKeys {
val KEY_FORWARD_MSG_TEXT = "KEY_FORWARD_MSG_TEXT" val KEY_FORWARD_MSG_TEXT = "KEY_FORWARD_MSG_TEXT"
val KEY_FORWARD_HIDE_SOURCE_ROOM = "KEY_FORWARD_HIDE_SOURCE_ROOM" val KEY_FORWARD_HIDE_SOURCE_ROOM = "KEY_FORWARD_HIDE_SOURCE_ROOM"
val KEY_SYSTEM_NOTIFICATION_ID = "KEY_SYSTEM_NOTIFICATION_ID" val KEY_SYSTEM_NOTIFICATION_ID = "KEY_SYSTEM_NOTIFICATION_ID"
const val KEY_MESSAGE_ID = "KEY_MESSAGE_ID"
} }