mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-21 19:48:29 +00:00
Add search and arbitrary jump support to CFV2.
This commit is contained in:
committed by
Cody Henthorne
parent
290b0fe46f
commit
f3a0a059ea
@@ -31,11 +31,11 @@ public class ConversationSearchViewModel extends ViewModel {
|
|||||||
searchRepository = new SearchRepository(noteToSelfTitle);
|
searchRepository = new SearchRepository(noteToSelfTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveData<SearchResult> getSearchResults() {
|
public @NonNull LiveData<SearchResult> getSearchResults() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onQueryUpdated(@NonNull String query, long threadId, boolean forced) {
|
public void onQueryUpdated(@NonNull String query, long threadId, boolean forced) {
|
||||||
if (firstSearch && query.length() < 2) {
|
if (firstSearch && query.length() < 2) {
|
||||||
result.postValue(new SearchResult(Collections.emptyList(), 0));
|
result.postValue(new SearchResult(Collections.emptyList(), 0));
|
||||||
return;
|
return;
|
||||||
@@ -48,13 +48,13 @@ public class ConversationSearchViewModel extends ViewModel {
|
|||||||
updateQuery(query, threadId);
|
updateQuery(query, threadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onMissingResult() {
|
public void onMissingResult() {
|
||||||
if (activeQuery != null) {
|
if (activeQuery != null) {
|
||||||
updateQuery(activeQuery, activeThreadId);
|
updateQuery(activeQuery, activeThreadId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onMoveUp() {
|
public void onMoveUp() {
|
||||||
if (result.getValue() == null) {
|
if (result.getValue() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ public class ConversationSearchViewModel extends ViewModel {
|
|||||||
result.setValue(new SearchResult(messages, position));
|
result.setValue(new SearchResult(messages, position));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onMoveDown() {
|
public void onMoveDown() {
|
||||||
if (result.getValue() == null) {
|
if (result.getValue() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -81,12 +81,12 @@ public class ConversationSearchViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void onSearchOpened() {
|
public void onSearchOpened() {
|
||||||
searchOpen = true;
|
searchOpen = true;
|
||||||
firstSearch = true;
|
firstSearch = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSearchClosed() {
|
public void onSearchClosed() {
|
||||||
searchOpen = false;
|
searchOpen = false;
|
||||||
debouncer.clear();
|
debouncer.clear();
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ public class ConversationSearchViewModel extends ViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static class SearchResult {
|
public static class SearchResult {
|
||||||
|
|
||||||
private final List<MessageResult> results;
|
private final List<MessageResult> results;
|
||||||
private final int position;
|
private final int position;
|
||||||
|
|||||||
@@ -124,6 +124,11 @@ class ConversationAdapterV2(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateSearchQuery(searchQuery: String?) {
|
||||||
|
this.searchQuery = searchQuery
|
||||||
|
notifyItemRangeChanged(0, itemCount)
|
||||||
|
}
|
||||||
|
|
||||||
/** [messagePosition] is one-based index and adapter is zero-based. */
|
/** [messagePosition] is one-based index and adapter is zero-based. */
|
||||||
fun getAdapterPositionForMessagePosition(messagePosition: Int): Int {
|
fun getAdapterPositionForMessagePosition(messagePosition: Int): Int {
|
||||||
return messagePosition - 1
|
return messagePosition - 1
|
||||||
@@ -151,7 +156,12 @@ class ConversationAdapterV2(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return isRangeAvailable(position - 10, position + 5)
|
if (!isRangeAvailable(position - 10, position + 5)) {
|
||||||
|
getItem(absolutePosition)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun playInlineContent(conversationMessage: ConversationMessage?) {
|
fun playInlineContent(conversationMessage: ConversationMessage?) {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import androidx.activity.result.ActivityResultLauncher
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.app.ActivityOptionsCompat
|
import androidx.core.app.ActivityOptionsCompat
|
||||||
@@ -79,6 +80,7 @@ import org.signal.core.util.concurrent.addTo
|
|||||||
import org.signal.core.util.dp
|
import org.signal.core.util.dp
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.signal.core.util.orNull
|
import org.signal.core.util.orNull
|
||||||
|
import org.signal.core.util.setActionItemTint
|
||||||
import org.signal.libsignal.protocol.InvalidMessageException
|
import org.signal.libsignal.protocol.InvalidMessageException
|
||||||
import org.signal.ringrtc.CallLinkRootKey
|
import org.signal.ringrtc.CallLinkRootKey
|
||||||
import org.thoughtcrime.securesms.BlockUnblockDialog
|
import org.thoughtcrime.securesms.BlockUnblockDialog
|
||||||
@@ -94,6 +96,7 @@ import org.thoughtcrime.securesms.badges.gifts.viewgift.received.ViewReceivedGif
|
|||||||
import org.thoughtcrime.securesms.badges.gifts.viewgift.sent.ViewSentGiftBottomSheet
|
import org.thoughtcrime.securesms.badges.gifts.viewgift.sent.ViewSentGiftBottomSheet
|
||||||
import org.thoughtcrime.securesms.components.AnimatingToggle
|
import org.thoughtcrime.securesms.components.AnimatingToggle
|
||||||
import org.thoughtcrime.securesms.components.ComposeText
|
import org.thoughtcrime.securesms.components.ComposeText
|
||||||
|
import org.thoughtcrime.securesms.components.ConversationSearchBottomBar
|
||||||
import org.thoughtcrime.securesms.components.HidingLinearLayout
|
import org.thoughtcrime.securesms.components.HidingLinearLayout
|
||||||
import org.thoughtcrime.securesms.components.InputAwareConstraintLayout
|
import org.thoughtcrime.securesms.components.InputAwareConstraintLayout
|
||||||
import org.thoughtcrime.securesms.components.InputPanel
|
import org.thoughtcrime.securesms.components.InputPanel
|
||||||
@@ -129,6 +132,7 @@ import org.thoughtcrime.securesms.conversation.ConversationReactionDelegate
|
|||||||
import org.thoughtcrime.securesms.conversation.ConversationReactionOverlay
|
import org.thoughtcrime.securesms.conversation.ConversationReactionOverlay
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationReactionOverlay.OnActionSelectedListener
|
import org.thoughtcrime.securesms.conversation.ConversationReactionOverlay.OnActionSelectedListener
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationReactionOverlay.OnHideListener
|
import org.thoughtcrime.securesms.conversation.ConversationReactionOverlay.OnHideListener
|
||||||
|
import org.thoughtcrime.securesms.conversation.ConversationSearchViewModel
|
||||||
import org.thoughtcrime.securesms.conversation.MarkReadHelper
|
import org.thoughtcrime.securesms.conversation.MarkReadHelper
|
||||||
import org.thoughtcrime.securesms.conversation.MenuState
|
import org.thoughtcrime.securesms.conversation.MenuState
|
||||||
import org.thoughtcrime.securesms.conversation.MessageSendType
|
import org.thoughtcrime.securesms.conversation.MessageSendType
|
||||||
@@ -267,6 +271,7 @@ class ConversationFragment :
|
|||||||
companion object {
|
companion object {
|
||||||
private val TAG = Log.tag(ConversationFragment::class.java)
|
private val TAG = Log.tag(ConversationFragment::class.java)
|
||||||
private const val ACTION_PINNED_SHORTCUT = "action_pinned_shortcut"
|
private const val ACTION_PINNED_SHORTCUT = "action_pinned_shortcut"
|
||||||
|
private const val SAVED_STATE_IS_SEARCH_REQUESTED = "is_search_requested"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val args: ConversationIntents.Args by lazy {
|
private val args: ConversationIntents.Args by lazy {
|
||||||
@@ -313,6 +318,10 @@ class ConversationFragment :
|
|||||||
DraftViewModel(threadId = args.threadId, repository = DraftRepository(conversationArguments = args))
|
DraftViewModel(threadId = args.threadId, repository = DraftRepository(conversationArguments = args))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val searchViewModel: ConversationSearchViewModel by viewModel {
|
||||||
|
ConversationSearchViewModel(getString(R.string.note_to_self))
|
||||||
|
}
|
||||||
|
|
||||||
private val conversationTooltips = ConversationTooltips(this)
|
private val conversationTooltips = ConversationTooltips(this)
|
||||||
private val colorizer = Colorizer()
|
private val colorizer = Colorizer()
|
||||||
private val textDraftSaveDebouncer = Debouncer(500)
|
private val textDraftSaveDebouncer = Debouncer(500)
|
||||||
@@ -335,6 +344,8 @@ class ConversationFragment :
|
|||||||
private var animationsAllowed = false
|
private var animationsAllowed = false
|
||||||
private var actionMode: ActionMode? = null
|
private var actionMode: ActionMode? = null
|
||||||
private var pinnedShortcutReceiver: BroadcastReceiver? = null
|
private var pinnedShortcutReceiver: BroadcastReceiver? = null
|
||||||
|
private var searchMenuItem: MenuItem? = null
|
||||||
|
private var isSearchRequested: Boolean = false
|
||||||
|
|
||||||
private val jumpAndPulseScrollStrategy = object : ScrollToPositionDelegate.ScrollStrategy {
|
private val jumpAndPulseScrollStrategy = object : ScrollToPositionDelegate.ScrollStrategy {
|
||||||
override fun performScroll(recyclerView: RecyclerView, layoutManager: LinearLayoutManager, position: Int, smooth: Boolean) {
|
override fun performScroll(recyclerView: RecyclerView, layoutManager: LinearLayoutManager, position: Int, smooth: Boolean) {
|
||||||
@@ -365,6 +376,9 @@ class ConversationFragment :
|
|||||||
private val bottomActionBar: SignalBottomActionBar
|
private val bottomActionBar: SignalBottomActionBar
|
||||||
get() = binding.conversationBottomActionBar
|
get() = binding.conversationBottomActionBar
|
||||||
|
|
||||||
|
private val searchNav: ConversationSearchBottomBar
|
||||||
|
get() = binding.conversationSearchBottomBar.root
|
||||||
|
|
||||||
private lateinit var reactionDelegate: ConversationReactionDelegate
|
private lateinit var reactionDelegate: ConversationReactionDelegate
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -408,6 +422,18 @@ class ConversationFragment :
|
|||||||
ToolbarDependentMarginListener(binding.toolbar)
|
ToolbarDependentMarginListener(binding.toolbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||||
|
super.onViewStateRestored(savedInstanceState)
|
||||||
|
|
||||||
|
isSearchRequested = savedInstanceState?.getBoolean(SAVED_STATE_IS_SEARCH_REQUESTED, false) ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
|
||||||
|
outState.putBoolean(SAVED_STATE_IS_SEARCH_REQUESTED, isSearchRequested)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
@@ -630,6 +656,8 @@ class ConversationFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.addTo(disposables)
|
.addTo(disposables)
|
||||||
|
|
||||||
|
initializeSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun presentInputReadyState(inputReadyState: InputReadyState) {
|
private fun presentInputReadyState(inputReadyState: InputReadyState) {
|
||||||
@@ -736,8 +764,9 @@ class ConversationFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun invalidateOptionsMenu() {
|
private fun invalidateOptionsMenu() {
|
||||||
// TODO [cfv2] -- Handle search... is there a better way to manage this state? Maybe an event system?
|
if (!isSearchRequested && activity != null) {
|
||||||
conversationOptionsMenuProvider.onCreateMenu(binding.toolbar.menu, requireActivity().menuInflater)
|
conversationOptionsMenuProvider.onCreateMenu(binding.toolbar.menu, requireActivity().menuInflater)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun presentActionBarMenu() {
|
private fun presentActionBarMenu() {
|
||||||
@@ -796,6 +825,18 @@ class ConversationFragment :
|
|||||||
binding.conversationWallpaperDim.visible = false
|
binding.conversationWallpaperDim.visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val toolbarTint = ContextCompat.getColor(
|
||||||
|
requireContext(),
|
||||||
|
if (chatWallpaper != null) {
|
||||||
|
R.color.signal_colorNeutralInverse
|
||||||
|
} else {
|
||||||
|
R.color.signal_colorOnSurface
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.toolbar.setTitleTextColor(toolbarTint)
|
||||||
|
binding.toolbar.setActionItemTint(toolbarTint)
|
||||||
|
|
||||||
val wallpaperEnabled = chatWallpaper != null
|
val wallpaperEnabled = chatWallpaper != null
|
||||||
binding.conversationWallpaper.visible = wallpaperEnabled
|
binding.conversationWallpaper.visible = wallpaperEnabled
|
||||||
binding.scrollToBottom.setWallpaperEnabled(wallpaperEnabled)
|
binding.scrollToBottom.setWallpaperEnabled(wallpaperEnabled)
|
||||||
@@ -953,6 +994,32 @@ class ConversationFragment :
|
|||||||
return callback
|
return callback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initializeSearch() {
|
||||||
|
searchViewModel.searchResults.observe(viewLifecycleOwner) { result ->
|
||||||
|
if (result == null) {
|
||||||
|
return@observe
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.results.isNotEmpty()) {
|
||||||
|
val messageResult = result.results[result.position]
|
||||||
|
disposables += viewModel
|
||||||
|
.moveToSearchResult(messageResult)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribeBy {
|
||||||
|
moveToPosition(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
searchNav.setData(result.position, result.results.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
searchNav.setEventListener(SearchEventListener())
|
||||||
|
|
||||||
|
disposables += viewModel.searchQuery.subscribeBy {
|
||||||
|
adapter.updateSearchQuery(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateToggleButtonState() {
|
private fun updateToggleButtonState() {
|
||||||
val buttonToggle: AnimatingToggle = binding.conversationInputPanel.buttonToggle
|
val buttonToggle: AnimatingToggle = binding.conversationInputPanel.buttonToggle
|
||||||
val quickAttachment: HidingLinearLayout = binding.conversationInputPanel.quickAttachmentToggle
|
val quickAttachment: HidingLinearLayout = binding.conversationInputPanel.quickAttachmentToggle
|
||||||
@@ -1290,12 +1357,9 @@ class ConversationFragment :
|
|||||||
//region Message action handling
|
//region Message action handling
|
||||||
|
|
||||||
private fun handleReplyToMessage(conversationMessage: ConversationMessage) {
|
private fun handleReplyToMessage(conversationMessage: ConversationMessage) {
|
||||||
/*
|
|
||||||
TODO [cfv2]
|
|
||||||
if (isSearchRequested) {
|
if (isSearchRequested) {
|
||||||
searchViewItem.collapseActionView();
|
searchMenuItem?.collapseActionView()
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
if (inputPanel.inEditMessageMode()) {
|
if (inputPanel.inEditMessageMode()) {
|
||||||
inputPanel.exitEditMessageMode()
|
inputPanel.exitEditMessageMode()
|
||||||
@@ -1321,12 +1385,9 @@ class ConversationFragment :
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if (isSearchRequested) {
|
||||||
TODO [cfv2]
|
searchMenuItem?.collapseActionView()
|
||||||
if (isSearchRequested) {
|
|
||||||
searchViewItem.collapseActionView();
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
viewModel.resolveMessageToEdit(conversationMessage)
|
viewModel.resolveMessageToEdit(conversationMessage)
|
||||||
.subscribeBy { updatedMessage ->
|
.subscribeBy { updatedMessage ->
|
||||||
@@ -1982,6 +2043,7 @@ class ConversationFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private inner class ConversationOptionsMenuCallback : ConversationOptionsMenu.Callback {
|
private inner class ConversationOptionsMenuCallback : ConversationOptionsMenu.Callback {
|
||||||
|
|
||||||
override fun getSnapshot(): ConversationOptionsMenu.Snapshot {
|
override fun getSnapshot(): ConversationOptionsMenu.Snapshot {
|
||||||
val recipient: Recipient? = viewModel.recipientSnapshot
|
val recipient: Recipient? = viewModel.recipientSnapshot
|
||||||
return ConversationOptionsMenu.Snapshot(
|
return ConversationOptionsMenu.Snapshot(
|
||||||
@@ -2000,7 +2062,73 @@ class ConversationFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsMenuCreated(menu: Menu) {
|
override fun onOptionsMenuCreated(menu: Menu) {
|
||||||
// TODO [cfv2]
|
searchMenuItem = menu.findItem(R.id.menu_search)
|
||||||
|
|
||||||
|
val searchView: SearchView = searchMenuItem!!.actionView as SearchView
|
||||||
|
val queryListener: SearchView.OnQueryTextListener = object : SearchView.OnQueryTextListener {
|
||||||
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
|
searchViewModel.onQueryUpdated(query, args.threadId, true)
|
||||||
|
searchNav.showLoading()
|
||||||
|
viewModel.setSearchQuery(query)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQueryTextChange(newText: String): Boolean {
|
||||||
|
searchViewModel.onQueryUpdated(newText, args.threadId, false)
|
||||||
|
searchNav.showLoading()
|
||||||
|
viewModel.setSearchQuery(newText)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
searchMenuItem!!.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
|
||||||
|
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||||
|
searchView.setOnQueryTextListener(queryListener)
|
||||||
|
isSearchRequested = true
|
||||||
|
searchViewModel.onSearchOpened()
|
||||||
|
searchNav.visible = true
|
||||||
|
searchNav.setData(0, 0)
|
||||||
|
inputPanel.setHideForSearch(true)
|
||||||
|
|
||||||
|
(0 until menu.size()).forEach {
|
||||||
|
if (menu.getItem(it) != searchMenuItem) {
|
||||||
|
menu.getItem(it).isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||||
|
searchView.setOnQueryTextListener(null)
|
||||||
|
isSearchRequested = false
|
||||||
|
searchViewModel.onSearchClosed()
|
||||||
|
searchNav.visible = false
|
||||||
|
inputPanel.setHideForSearch(false)
|
||||||
|
viewModel.setSearchQuery(null)
|
||||||
|
invalidateOptionsMenu()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
searchView.maxWidth = Integer.MAX_VALUE
|
||||||
|
|
||||||
|
if (isSearchRequested) {
|
||||||
|
if (searchMenuItem!!.expandActionView()) {
|
||||||
|
searchViewModel.onSearchOpened()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val toolbarTextAndIconColor = ContextCompat.getColor(
|
||||||
|
requireContext(),
|
||||||
|
if (viewModel.wallpaperSnapshot != null) {
|
||||||
|
R.color.signal_colorNeutralInverse
|
||||||
|
} else {
|
||||||
|
R.color.signal_colorOnSurface
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.toolbar.setActionItemTint(toolbarTextAndIconColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleVideo() {
|
override fun handleVideo() {
|
||||||
@@ -2704,6 +2832,16 @@ class ConversationFragment :
|
|||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
private inner class SearchEventListener : ConversationSearchBottomBar.EventListener {
|
||||||
|
override fun onSearchMoveUpPressed() {
|
||||||
|
searchViewModel.onMoveUp()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSearchMoveDownPressed() {
|
||||||
|
searchViewModel.onMoveDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private inner class ToolbarDependentMarginListener(private val toolbar: Toolbar) : ViewTreeObserver.OnGlobalLayoutListener {
|
private inner class ToolbarDependentMarginListener(private val toolbar: Toolbar) : ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ import org.thoughtcrime.securesms.providers.BlobProvider
|
|||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException
|
import org.thoughtcrime.securesms.recipients.RecipientFormattingException
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
|
import org.thoughtcrime.securesms.search.MessageResult
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender
|
import org.thoughtcrime.securesms.sms.MessageSender
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil
|
import org.thoughtcrime.securesms.util.BitmapUtil
|
||||||
import org.thoughtcrime.securesms.util.DrawableUtil
|
import org.thoughtcrime.securesms.util.DrawableUtil
|
||||||
@@ -250,6 +251,12 @@ class ConversationRepository(
|
|||||||
}.subscribeOn(Schedulers.io())
|
}.subscribeOn(Schedulers.io())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getMessageResultPosition(threadId: Long, messageResult: MessageResult): Single<Int> {
|
||||||
|
return Single.fromCallable {
|
||||||
|
SignalDatabase.messages.getMessagePositionInConversation(threadId, messageResult.receivedTimestampMs) + 1
|
||||||
|
}.subscribeOn(Schedulers.io())
|
||||||
|
}
|
||||||
|
|
||||||
fun getNextMentionPosition(threadId: Long): Single<Int> {
|
fun getNextMentionPosition(threadId: Long): Single<Int> {
|
||||||
return Single.fromCallable {
|
return Single.fromCallable {
|
||||||
val details = SignalDatabase.messages.getOldestUnreadMentionDetails(threadId)
|
val details = SignalDatabase.messages.getOldestUnreadMentionDetails(threadId)
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import org.thoughtcrime.securesms.mms.QuoteModel
|
|||||||
import org.thoughtcrime.securesms.mms.SlideDeck
|
import org.thoughtcrime.securesms.mms.SlideDeck
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
|
import org.thoughtcrime.securesms.search.MessageResult
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.thoughtcrime.securesms.util.hasGiftBadge
|
import org.thoughtcrime.securesms.util.hasGiftBadge
|
||||||
import org.thoughtcrime.securesms.util.rx.RxStore
|
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||||
@@ -114,6 +115,9 @@ class ConversationViewModel(
|
|||||||
private val refreshIdentityRecords: Subject<Unit> = PublishSubject.create()
|
private val refreshIdentityRecords: Subject<Unit> = PublishSubject.create()
|
||||||
val identityRecords: Observable<IdentityRecordsState>
|
val identityRecords: Observable<IdentityRecordsState>
|
||||||
|
|
||||||
|
private val _searchQuery = BehaviorSubject.createDefault("")
|
||||||
|
val searchQuery: Observable<String> = _searchQuery
|
||||||
|
|
||||||
init {
|
init {
|
||||||
disposables += recipient
|
disposables += recipient
|
||||||
.subscribeBy {
|
.subscribeBy {
|
||||||
@@ -204,6 +208,10 @@ class ConversationViewModel(
|
|||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setSearchQuery(query: String?) {
|
||||||
|
_searchQuery.onNext(query ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
disposables.clear()
|
disposables.clear()
|
||||||
}
|
}
|
||||||
@@ -218,6 +226,10 @@ class ConversationViewModel(
|
|||||||
return repository.getQuotedMessagePosition(threadId, quote)
|
return repository.getQuotedMessagePosition(threadId, quote)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun moveToSearchResult(messageResult: MessageResult): Single<Int> {
|
||||||
|
return repository.getMessageResultPosition(threadId, messageResult)
|
||||||
|
}
|
||||||
|
|
||||||
fun getNextMentionPosition(): Single<Int> {
|
fun getNextMentionPosition(): Single<Int> {
|
||||||
return repository.getNextMentionPosition(threadId)
|
return repository.getNextMentionPosition(threadId)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<org.thoughtcrime.securesms.components.ConversationSearchBottomBar
|
<org.thoughtcrime.securesms.components.ConversationSearchBottomBar
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
tools:viewBindingIgnore="true"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/conversation_search_nav"
|
android:id="@+id/conversation_search_nav"
|
||||||
|
|||||||
@@ -190,6 +190,16 @@
|
|||||||
app:layout_constraintEnd_toEndOf="@id/parent_end_guideline"
|
app:layout_constraintEnd_toEndOf="@id/parent_end_guideline"
|
||||||
app:layout_constraintStart_toStartOf="@id/parent_start_guideline" />
|
app:layout_constraintStart_toStartOf="@id/parent_start_guideline" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/conversation_search_bottom_bar"
|
||||||
|
layout="@layout/conversation_search_nav"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/keyboard_guideline"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/parent_end_guideline"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/parent_start_guideline" />
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.DisabledInputView
|
<org.thoughtcrime.securesms.conversation.v2.DisabledInputView
|
||||||
android:id="@+id/conversation_disabled_input"
|
android:id="@+id/conversation_disabled_input"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.core.util
|
||||||
|
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.graphics.PorterDuffColorFilter
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.core.view.MenuItemCompat
|
||||||
|
import androidx.core.view.forEach
|
||||||
|
|
||||||
|
fun Toolbar.setActionItemTint(@ColorInt tint: Int) {
|
||||||
|
menu.forEach {
|
||||||
|
MenuItemCompat.setIconTintList(it, ColorStateList.valueOf(tint))
|
||||||
|
}
|
||||||
|
|
||||||
|
navigationIcon?.colorFilter = PorterDuffColorFilter(tint, PorterDuff.Mode.SRC_ATOP)
|
||||||
|
overflowIcon?.colorFilter = PorterDuffColorFilter(tint, PorterDuff.Mode.SRC_ATOP)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user