Fix Search

This commit is contained in:
bemusementpark 2024-07-04 14:48:31 +09:30
parent ca66d115a3
commit a4ee521ee0
6 changed files with 65 additions and 124 deletions

View File

@ -8,30 +8,9 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Telephony.Mms.Addr
import android.widget.Toast import android.widget.Toast
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.width
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
@ -56,11 +35,9 @@ import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.AppTextSecurePreferences
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.ProfilePictureModifiedEvent import org.session.libsession.utilities.ProfilePictureModifiedEvent
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.groupByNotNull
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.ThreadUtils
@ -87,25 +64,12 @@ import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.notifications.PushRegistry import org.thoughtcrime.securesms.notifications.PushRegistry
import org.thoughtcrime.securesms.recoverypassword.RecoveryPasswordActivity
import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.preferences.SettingsActivity import org.thoughtcrime.securesms.preferences.SettingsActivity
import org.thoughtcrime.securesms.recoverypassword.RecoveryPasswordActivity
import org.thoughtcrime.securesms.showMuteDialog import org.thoughtcrime.securesms.showMuteDialog
import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.showSessionDialog
import org.thoughtcrime.securesms.ui.Divider
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.color.Colors
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.SessionShieldIcon
import org.thoughtcrime.securesms.ui.base
import org.thoughtcrime.securesms.ui.components.SlimOutlineButton
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.h4
import org.thoughtcrime.securesms.ui.h8
import org.thoughtcrime.securesms.ui.setThemedContent import org.thoughtcrime.securesms.ui.setThemedContent
import org.thoughtcrime.securesms.ui.small
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import org.thoughtcrime.securesms.util.IP2Country import org.thoughtcrime.securesms.util.IP2Country
import org.thoughtcrime.securesms.util.disableClipping import org.thoughtcrime.securesms.util.disableClipping
@ -147,41 +111,27 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
private val globalSearchAdapter = GlobalSearchAdapter { model -> private val globalSearchAdapter = GlobalSearchAdapter { model ->
when (model) { when (model) {
is GlobalSearchAdapter.Model.Message -> { is GlobalSearchAdapter.Model.Message -> push<ConversationActivityV2> {
val threadId = model.messageResult.threadId model.messageResult.run {
val timestamp = model.messageResult.sentTimestampMs putExtra(ConversationActivityV2.THREAD_ID, threadId)
val author = model.messageResult.messageRecipient.address putExtra(ConversationActivityV2.SCROLL_MESSAGE_ID, sentTimestampMs)
putExtra(ConversationActivityV2.SCROLL_MESSAGE_AUTHOR, messageRecipient.address)
val intent = Intent(this, ConversationActivityV2::class.java)
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId)
intent.putExtra(ConversationActivityV2.SCROLL_MESSAGE_ID, timestamp)
intent.putExtra(ConversationActivityV2.SCROLL_MESSAGE_AUTHOR, author)
push(intent)
}
is GlobalSearchAdapter.Model.SavedMessages -> {
val intent = Intent(this, ConversationActivityV2::class.java)
intent.putExtra(ConversationActivityV2.ADDRESS, Address.fromSerialized(model.currentUserPublicKey))
push(intent)
}
is GlobalSearchAdapter.Model.Contact -> {
val address = model.contact.accountID
val intent = Intent(this, ConversationActivityV2::class.java)
intent.putExtra(ConversationActivityV2.ADDRESS, Address.fromSerialized(address))
push(intent)
}
is GlobalSearchAdapter.Model.GroupConversation -> {
val groupAddress = Address.fromSerialized(model.groupRecord.encodedId)
val threadId = threadDb.getThreadIdIfExistsFor(Recipient.from(this, groupAddress, false))
if (threadId >= 0) {
val intent = Intent(this, ConversationActivityV2::class.java)
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId)
push(intent)
} }
} }
else -> { is GlobalSearchAdapter.Model.SavedMessages -> push<ConversationActivityV2> {
Log.d("Loki", "callback with model: $model") putExtra(ConversationActivityV2.ADDRESS, Address.fromSerialized(model.currentUserPublicKey))
} }
is GlobalSearchAdapter.Model.Contact -> push<ConversationActivityV2> {
putExtra(ConversationActivityV2.ADDRESS, model.contact.accountID.let(Address::fromSerialized))
}
is GlobalSearchAdapter.Model.GroupConversation -> model.groupRecord.encodedId
.let { Recipient.from(this, Address.fromSerialized(it), false) }
.let(threadDb::getThreadIdIfExistsFor)
.takeIf { it >= 0 }
?.let {
push<ConversationActivityV2> { putExtra(ConversationActivityV2.THREAD_ID, it) }
}
else -> Log.d("Loki", "callback with model: $model")
} }
} }
@ -321,24 +271,14 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
.flatMap { (key, contacts) -> .flatMap { (key, contacts) ->
listOf( listOf(
GlobalSearchAdapter.Model.SubHeader(key) GlobalSearchAdapter.Model.SubHeader(key)
) + contacts.sortedBy { it.name ?: it.value.accountID }.map { it.value }.map(GlobalSearchAdapter.Model::Contact) ) + contacts.sortedBy { it.name ?: it.value.accountID }.map { it.value }.map { GlobalSearchAdapter.Model.Contact(it, it.accountID == publicKey) }
} }
} else { } else {
val currentUserPublicKey = publicKey val contactAndGroupList = result.contacts.map { GlobalSearchAdapter.Model.Contact(it, it.accountID == publicKey) } +
val contactAndGroupList = result.contacts.map(GlobalSearchAdapter.Model::Contact) +
result.threads.map(GlobalSearchAdapter.Model::GroupConversation) result.threads.map(GlobalSearchAdapter.Model::GroupConversation)
val contactResults = contactAndGroupList.toMutableList() val contactResults = contactAndGroupList.toMutableList()
if (contactResults.isEmpty()) {
contactResults.add(GlobalSearchAdapter.Model.SavedMessages(currentUserPublicKey))
}
val userIndex = contactResults.indexOfFirst { it is GlobalSearchAdapter.Model.Contact && it.contact.accountID == currentUserPublicKey }
if (userIndex >= 0) {
contactResults[userIndex] = GlobalSearchAdapter.Model.SavedMessages(currentUserPublicKey)
}
if (contactResults.isNotEmpty()) { if (contactResults.isNotEmpty()) {
contactResults.add(0, GlobalSearchAdapter.Model.Header(R.string.conversations)) contactResults.add(0, GlobalSearchAdapter.Model.Header(R.string.conversations))
} }
@ -348,7 +288,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
.associateWith { mmsSmsDatabase.getUnreadCount(it) } .associateWith { mmsSmsDatabase.getUnreadCount(it) }
val messageResults: MutableList<GlobalSearchAdapter.Model> = result.messages val messageResults: MutableList<GlobalSearchAdapter.Model> = result.messages
.map { GlobalSearchAdapter.Model.Message(it, unreadThreadMap[it.threadId] ?: 0) } .map { GlobalSearchAdapter.Model.Message(it, unreadThreadMap[it.threadId] ?: 0, it.conversationRecipient.isLocalNumber) }
.toMutableList() .toMutableList()
if (messageResults.isNotEmpty()) { if (messageResults.isNotEmpty()) {

View File

@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.ui.GetString
import java.security.InvalidParameterException import java.security.InvalidParameterException
import org.session.libsession.messaging.contacts.Contact as ContactModel import org.session.libsession.messaging.contacts.Contact as ContactModel
class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerView.Adapter<RecyclerView.ViewHolder>() { class GlobalSearchAdapter(private val modelCallback: (Model)->Unit): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object { companion object {
const val HEADER_VIEW_TYPE = 0 const val HEADER_VIEW_TYPE = 0
@ -122,15 +122,8 @@ class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerVi
} }
binding.root.setOnClickListener { modelCallback(model) } binding.root.setOnClickListener { modelCallback(model) }
} }
} }
data class MessageModel(
val threadRecipient: Recipient,
val messageRecipient: Recipient,
val messageSnippet: String
)
sealed class Model { sealed class Model {
data class Header(val title: GetString): Model() { data class Header(val title: GetString): Model() {
constructor(@StringRes title: Int): this(GetString(title)) constructor(@StringRes title: Int): this(GetString(title))
@ -141,8 +134,8 @@ class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerVi
constructor(title: String): this(GetString(title)) constructor(title: String): this(GetString(title))
} }
data class SavedMessages(val currentUserPublicKey: String): Model() data class SavedMessages(val currentUserPublicKey: String): Model()
data class Contact(val contact: ContactModel): Model() data class Contact(val contact: ContactModel, val isSelf: Boolean): Model()
data class GroupConversation(val groupRecord: GroupRecord): Model() data class GroupConversation(val groupRecord: GroupRecord): Model()
data class Message(val messageResult: MessageResult, val unread: Int): Model() data class Message(val messageResult: MessageResult, val unread: Int, val isSelf: Boolean): Model()
} }
} }

View File

@ -65,7 +65,7 @@ fun ContentView.bindQuery(query: String, model: GlobalSearchAdapter.Model) {
)) ))
binding.searchResultSubtitle.text = textSpannable binding.searchResultSubtitle.text = textSpannable
binding.searchResultSubtitle.isVisible = true binding.searchResultSubtitle.isVisible = true
binding.searchResultTitle.text = model.messageResult.conversationRecipient.toShortString() binding.searchResultTitle.text = model.messageResult.conversationRecipient.getSearchName()
} }
is GroupConversation -> { is GroupConversation -> {
binding.searchResultTitle.text = getHighlight( binding.searchResultTitle.text = getHighlight(
@ -74,8 +74,7 @@ fun ContentView.bindQuery(query: String, model: GlobalSearchAdapter.Model) {
) )
val membersString = model.groupRecord.members.joinToString { address -> val membersString = model.groupRecord.members.joinToString { address ->
val recipient = Recipient.from(binding.root.context, address, false) Recipient.from(binding.root.context, address, false).getSearchName()
recipient.name ?: "${address.serialize().take(4)}...${address.serialize().takeLast(4)}"
} }
binding.searchResultSubtitle.text = getHighlight(query, membersString) binding.searchResultSubtitle.text = getHighlight(query, membersString)
} }
@ -106,16 +105,16 @@ fun ContentView.bindModel(query: String?, model: GroupConversation) {
} }
} }
fun ContentView.bindModel(query: String?, model: ContactModel) { fun ContentView.bindModel(query: String?, model: ContactModel) = binding.run {
binding.searchResultProfilePicture.isVisible = true searchResultProfilePicture.isVisible = true
binding.searchResultSubtitle.isVisible = false searchResultSubtitle.isVisible = false
binding.searchResultTimestamp.isVisible = false searchResultTimestamp.isVisible = false
binding.searchResultSubtitle.text = null searchResultSubtitle.text = null
val recipient = val recipient = Recipient.from(root.context, Address.fromSerialized(model.contact.accountID), false)
Recipient.from(binding.root.context, Address.fromSerialized(model.contact.accountID), false) searchResultProfilePicture.update(recipient)
binding.searchResultProfilePicture.update(recipient) val nameString = if (model.isSelf) root.context.getString(R.string.note_to_self)
val nameString = model.contact.getSearchName() else model.contact.getSearchName()
binding.searchResultTitle.text = getHighlight(query, nameString) searchResultTitle.text = getHighlight(query, nameString)
} }
fun ContentView.bindModel(model: SavedMessages) { fun ContentView.bindModel(model: SavedMessages) {
@ -126,32 +125,39 @@ fun ContentView.bindModel(model: SavedMessages) {
binding.searchResultProfilePicture.isVisible = true binding.searchResultProfilePicture.isVisible = true
} }
fun ContentView.bindModel(query: String?, model: Message) { fun ContentView.bindModel(query: String?, model: Message) = binding.apply {
binding.searchResultProfilePicture.isVisible = true searchResultProfilePicture.isVisible = true
binding.searchResultTimestamp.isVisible = true searchResultTimestamp.isVisible = true
// val hasUnreads = model.unread > 0 // val hasUnreads = model.unread > 0
// binding.unreadCountIndicator.isVisible = hasUnreads // unreadCountIndicator.isVisible = hasUnreads
// if (hasUnreads) { // if (hasUnreads) {
// binding.unreadCountTextView.text = model.unread.toString() // unreadCountTextView.text = model.unread.toString()
// } // }
binding.searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(binding.root.context, Locale.getDefault(), model.messageResult.sentTimestampMs) searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(root.context, Locale.getDefault(), model.messageResult.sentTimestampMs)
binding.searchResultProfilePicture.update(model.messageResult.conversationRecipient) searchResultProfilePicture.update(model.messageResult.conversationRecipient)
val textSpannable = SpannableStringBuilder() val textSpannable = SpannableStringBuilder()
if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) { if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) {
// group chat, bind // group chat, bind
val text = "${model.messageResult.messageRecipient.getSearchName()}: " val text = "${model.messageResult.messageRecipient.toShortString()}: "
textSpannable.append(text) textSpannable.append(text)
} }
textSpannable.append(getHighlight( textSpannable.append(getHighlight(
query, query,
model.messageResult.bodySnippet model.messageResult.bodySnippet
)) ))
binding.searchResultSubtitle.text = textSpannable searchResultSubtitle.text = textSpannable
binding.searchResultTitle.text = model.messageResult.conversationRecipient.toShortString() searchResultTitle.text = if (model.isSelf) root.context.getString(R.string.note_to_self)
binding.searchResultSubtitle.isVisible = true else model.messageResult.conversationRecipient.getSearchName()
searchResultSubtitle.isVisible = true
} }
fun Recipient.getSearchName(): String = name ?: address.serialize().let(::truncateIdForDisplay) fun Recipient.getSearchName(): String =
name?.takeIf { it.isNotEmpty() && !it.looksLikeAccountId }
?: address.serialize().let(::truncateIdForDisplay)
fun Contact.getSearchName(): String = nickname?.takeIf { it.isNotEmpty() } fun Contact.getSearchName(): String =
?: name?.takeIf { it.isNotEmpty() } ?: truncateIdForDisplay(accountID) nickname?.takeIf { it.isNotEmpty() && !it.looksLikeAccountId }
?: name?.takeIf { it.isNotEmpty() && !it.looksLikeAccountId }
?: truncateIdForDisplay(accountID)
private val String.looksLikeAccountId: Boolean get() = length > 60 && all { it.isDigit() || it.isLetter() }

View File

@ -57,10 +57,12 @@ class PickDisplayNameActivity : BaseActionBarActivity() {
viewModel.states.collectAsState().value, viewModel.states.collectAsState().value,
viewModel::onChange, viewModel::onChange,
viewModel::onContinue, viewModel::onContinue,
viewModel::dismissDialog viewModel::dismissDialog,
) { viewModel.dismissDialog(); finish() } quit = { viewModel.dismissDialog(); finish() }
)
} }
@Deprecated("Deprecated in Java")
override fun onBackPressed() { override fun onBackPressed() {
if (viewModel.onBackPressed()) return if (viewModel.onBackPressed()) return

View File

@ -108,5 +108,5 @@ data class ThemeState (
) )
inline fun <reified T: Activity> Activity.show() = Intent(this, T::class.java).also(::startActivity).let { overridePendingTransition(R.anim.slide_from_bottom, R.anim.fade_scale_out) } inline fun <reified T: Activity> Activity.show() = Intent(this, T::class.java).also(::startActivity).let { overridePendingTransition(R.anim.slide_from_bottom, R.anim.fade_scale_out) }
inline fun <reified T: Activity> Activity.push() = Intent(this, T::class.java).also(::startActivity).let { overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out) } inline fun <reified T: Activity> Activity.push(modify: Intent.() -> Unit = {}) = Intent(this, T::class.java).also(modify).also(::startActivity).let { overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out) }
inline fun <reified T: Activity> Context.start(modify: Intent.() -> Unit = {}) = Intent(this, T::class.java).also(modify).apply { addFlags(FLAG_ACTIVITY_SINGLE_TOP) }.let(::startActivity) inline fun <reified T: Activity> Context.start(modify: Intent.() -> Unit = {}) = Intent(this, T::class.java).also(modify).apply { addFlags(FLAG_ACTIVITY_SINGLE_TOP) }.let(::startActivity)

View File

@ -1,4 +1,4 @@
package org.session.libsession.utilities package org.session.libsession.utilities
fun truncateIdForDisplay(id: String): String = fun truncateIdForDisplay(id: String): String =
id.takeIf { it.length > 8 }?.apply{ "${take(4)}${takeLast(4)}" } ?: id id.takeIf { it.length > 8 }?.run{ "${take(4)}${takeLast(4)}" } ?: id