diff --git a/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/base/BaseMessagesFragment.kt b/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/base/BaseMessagesFragment.kt index af760564..3e7b626f 100644 --- a/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/base/BaseMessagesFragment.kt +++ b/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/base/BaseMessagesFragment.kt @@ -46,6 +46,7 @@ abstract class BaseMessagesFragment : BaseFragment() @Inject lateinit var viewModelFactory: BaseMessagesViewModel.Factory protected lateinit var viewModel: VM + var stopSearch:Boolean = false protected val adapter by lazy { MessagesAdapter( @@ -67,6 +68,8 @@ abstract class BaseMessagesFragment : BaseFragment() abstract fun getAdapterSettings(): MessagesAdapter.Settings + abstract fun getSearchMessageId(): Int + protected open fun prepareViewModel() {} /** @@ -85,6 +88,7 @@ abstract class BaseMessagesFragment : BaseFragment() viewModel = ViewModelProviders.of(this, viewModelFactory)[getViewModelClass()] prepareViewModel() adapter.startLoading() + stopSearch = false progressBar.show() xviiToolbar.isLifted = true @@ -112,26 +116,34 @@ abstract class BaseMessagesFragment : BaseFragment() } val interaction = data.data ?: return + val searchMsgId = getSearchMessageId() + try { when (interaction.type) { Interaction.Type.CLEAR -> { adapter.clear() } Interaction.Type.ADD -> { - val firstLoad = adapter.isEmpty + val firstLoad = adapter.isEmpty || searchMsgId > 0 val isAtEnd = adapter.isAtBottom(rvChatList.layoutManager as? LinearLayoutManager) adapter.addAll(interaction.messages.toMutableList(), interaction.position) adapter.stopLoading(interaction.messages.isEmpty()) when { firstLoad -> { - var unreadPos = adapter.itemCount - 1 // default last item + var unreadPos = if(searchMsgId>0) 0 else adapter.itemCount - 1 // default last item for (index in interaction.messages.indices) { val message = interaction.messages[index].message - if (!message.read && !message.isOut()) { + if (searchMsgId == 0 && !message.read && !message.isOut() || + searchMsgId == message.id) { unreadPos = index + stopSearch = true break } } + if(searchMsgId > 0 && !stopSearch){ + unreadPos = 0 + loadMore(adapter.itemCount) + } rvChatList.scrollToPosition(unreadPos) } isAtEnd -> { diff --git a/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/chat/base/BaseChatMessagesFragment.kt b/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/chat/base/BaseChatMessagesFragment.kt index e3f33b6b..eb53b831 100644 --- a/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/chat/base/BaseChatMessagesFragment.kt +++ b/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/chat/base/BaseChatMessagesFragment.kt @@ -70,6 +70,7 @@ import kotlinx.android.synthetic.main.view_chat_multiselect.* abstract class BaseChatMessagesFragment : BaseMessagesFragment() { protected val peerId by lazy { arguments?.getInt(ARG_PEER_ID) ?: 0 } + protected val messageId by lazy { arguments?.getInt(ARG_MESSAGE_ID) ?: 0 } protected val title by lazy { arguments?.getString(ARG_TITLE) ?: "" } protected val photo by lazy { arguments?.getString(ARG_PHOTO) ?: "" } private val forwardedMessages by lazy { arguments?.getString(ARG_FORWARDED) } @@ -423,6 +424,8 @@ abstract class BaseChatMessagesFragment : BaseMe isImportant = false ) + override fun getSearchMessageId() = messageId + override fun getAdapterCallback() = MessageCallback() override fun getAttachmentsCallback() = AttachmentsCallback(requireContext()) @@ -432,6 +435,7 @@ abstract class BaseChatMessagesFragment : BaseMe const val MEMBERS_MAX = 5 const val ARG_PEER_ID = "peerId" + const val ARG_MESSAGE_ID = "messageId" const val ARG_TITLE = "title" const val ARG_FORWARDED = "forwarded" const val ARG_PHOTO = "photo" diff --git a/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/chat/usual/ChatActivity.kt b/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/chat/usual/ChatActivity.kt index 9d5edce0..55f7e7ca 100644 --- a/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/chat/usual/ChatActivity.kt +++ b/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/chat/usual/ChatActivity.kt @@ -39,10 +39,12 @@ class ChatActivity : ContentActivity() { val shareImages = args?.getStringArrayList(SHARE_IMAGE) ?: emptyList() val dialog = args?.getParcelable(DIALOG) ?: Dialog( peerId = args?.getInt(PEER_ID) ?: 0, + messageId = args?.getInt(MESSAGE_ID) ?: 0, title = args?.getString(TITLE) ?: "", photo = args?.getString(AVATAR) ) - return ChatMessagesFragment.newInstance(dialog, forwarded, shareText, shareImages) + val search = args?.getBoolean(SEARCH)?: false + return ChatMessagesFragment.newInstance(dialog, forwarded, shareText, shareImages, search) } override fun getDraggableBottomMargin(): Int = 200 @@ -55,8 +57,10 @@ class ChatActivity : ContentActivity() { const val SHARE_TEXT = "shareText" const val SHARE_IMAGE = "shareImage" const val PEER_ID = "peerId" + const val MESSAGE_ID = "messageId" const val TITLE = "title" const val AVATAR = "avatar" + const val SEARCH = "search" fun launch(context: Context?, chatOwner: ChatOwner) { launch(context, Dialog( @@ -102,5 +106,14 @@ class ChatActivity : ContentActivity() { photo = user.photo100 )) } + fun launch(context: Context?, dialog: Dialog, search:Boolean = false) { + context ?: return + + context.startActivity(Intent(context, ChatActivity::class.java).apply { + putExtra(DIALOG, dialog) + putExtra(SEARCH, search) + flags = flags or Intent.FLAG_ACTIVITY_CLEAR_TOP + }) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/chat/usual/ChatMessagesFragment.kt b/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/chat/usual/ChatMessagesFragment.kt index bded7f07..263c9d2e 100644 --- a/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/chat/usual/ChatMessagesFragment.kt +++ b/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/chat/usual/ChatMessagesFragment.kt @@ -101,7 +101,8 @@ class ChatMessagesFragment : BaseChatMessagesFragment() { companion object { fun newInstance(dialog: Dialog, forwarded: String? = null, - shareText: String? = null, shareImages: List = emptyList()): ChatMessagesFragment { + shareText: String? = null, shareImages: List = emptyList(), + search:Boolean = false): ChatMessagesFragment { val fragment = ChatMessagesFragment() fragment.arguments = Bundle().apply { putInt(ARG_PEER_ID, dialog.peerId) @@ -116,6 +117,9 @@ class ChatMessagesFragment : BaseChatMessagesFragment() { if (shareImages.isNotEmpty()) { putStringArrayList(ARG_SHARE_IMAGE, ArrayList(shareImages)) } + if (search){ + putInt(ARG_MESSAGE_ID, dialog.messageId); + } } return fragment } diff --git a/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/starred/StarredMessagesFragment.kt b/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/starred/StarredMessagesFragment.kt index 31ae7cb4..aefa74e2 100644 --- a/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/starred/StarredMessagesFragment.kt +++ b/app/src/main/java/com/twoeightnine/root/xvii/chats/messages/starred/StarredMessagesFragment.kt @@ -83,6 +83,8 @@ class StarredMessagesFragment : BaseMessagesFragment() isImportant = true ) + override fun getSearchMessageId() = 0 + override fun getAdapterCallback() = object : MessagesAdapter.Callback { override fun onClicked(message: Message) { diff --git a/app/src/main/java/com/twoeightnine/root/xvii/main/MainActivity.kt b/app/src/main/java/com/twoeightnine/root/xvii/main/MainActivity.kt index c7663115..e256bcc4 100644 --- a/app/src/main/java/com/twoeightnine/root/xvii/main/MainActivity.kt +++ b/app/src/main/java/com/twoeightnine/root/xvii/main/MainActivity.kt @@ -19,6 +19,7 @@ package com.twoeightnine.root.xvii.main import android.content.Context +import android.content.Intent import android.graphics.Color import android.net.Uri import android.os.Bundle @@ -32,6 +33,7 @@ import com.twoeightnine.root.xvii.R import com.twoeightnine.root.xvii.base.BaseActivity import com.twoeightnine.root.xvii.base.FragmentPlacementActivity.Companion.startFragment import com.twoeightnine.root.xvii.chatowner.ChatOwnerFactory +import com.twoeightnine.root.xvii.chats.attachments.base.BaseAttachmentsFragment import com.twoeightnine.root.xvii.chats.messages.chat.usual.ChatActivity import com.twoeightnine.root.xvii.dialogs.fragments.DialogsForwardFragment import com.twoeightnine.root.xvii.dialogs.fragments.DialogsFragment @@ -75,7 +77,10 @@ class MainActivity : BaseActivity() { StatTool.get()?.incLaunch() ivSearch.setOnClickListener { - startFragment() + var arguments = Bundle().apply { + putBoolean(SELECTED_FRIENDS, bottomNavView.selectedItemId==R.id.menu_friends) + } + startFragment(arguments) } ivSearch.paint(Munch.color.color) ivSearch.applyTopInsetMargin() @@ -88,6 +93,17 @@ class MainActivity : BaseActivity() { } insets } + + intent.extras?.let{ + it.getString(SEARCH_TEXT)?.let{ + val search = it; + var arguments = Bundle().apply { + putBoolean(SELECTED_FRIENDS, bottomNavView.selectedItemId==R.id.menu_friends) + putString(SEARCH_TEXT, search) + } + startFragment(arguments) + } + } } override fun onResume() { @@ -135,8 +151,17 @@ class MainActivity : BaseActivity() { companion object { - fun launch(context: Context?) { - launchActivity(context, MainActivity::class.java) + const val SEARCH_TEXT = "searchText" + const val SELECTED_FRIENDS = "selectedFriends" + + fun launch(context: Context?, search:String?= null) { + if(search==null) { + launchActivity(context, MainActivity::class.java) + }else{ + context?.startActivity(Intent(context, MainActivity::class.java).apply { + putExtra(MainActivity.SEARCH_TEXT, search) + }) + } } } diff --git a/app/src/main/java/com/twoeightnine/root/xvii/model/messages/Message.kt b/app/src/main/java/com/twoeightnine/root/xvii/model/messages/Message.kt index 9f20e28a..036a5d1b 100644 --- a/app/src/main/java/com/twoeightnine/root/xvii/model/messages/Message.kt +++ b/app/src/main/java/com/twoeightnine/root/xvii/model/messages/Message.kt @@ -80,6 +80,10 @@ data class Message( @Expose val attachments: ArrayList? = arrayListOf(), + @SerializedName("conversation_message_id") + @Expose + val conversationMessageId: Int = 0, + @SerializedName("reply_message") @Expose var replyMessage: Message? = null, @@ -108,6 +112,7 @@ data class Message( constructor(event: BaseMessageEvent, prepareText: (String) -> String = { it }) : this( id = event.id, peerId = event.peerId, + //conversationMessageId = event.peerId, date = event.timeStamp, fromId = event.info.from, text = prepareText(event.text), diff --git a/app/src/main/java/com/twoeightnine/root/xvii/network/ApiService.kt b/app/src/main/java/com/twoeightnine/root/xvii/network/ApiService.kt index 50ea8a31..06c96ab3 100644 --- a/app/src/main/java/com/twoeightnine/root/xvii/network/ApiService.kt +++ b/app/src/main/java/com/twoeightnine/root/xvii/network/ApiService.kt @@ -146,6 +146,13 @@ interface ApiService { @Query("start_from") startFrom: String? ): Flowable> + @GET("messages.search?extended=1") + fun search( + @Query("q") q: String, + @Query("count") count: Int, + @Query("offset") offset: Int, + @Query("fields") fields: String = User.FIELDS + ): Flowable> @GET("messages.searchConversations?extended=1") fun searchConversations( diff --git a/app/src/main/java/com/twoeightnine/root/xvii/network/response/SearchResponse.kt b/app/src/main/java/com/twoeightnine/root/xvii/network/response/SearchResponse.kt new file mode 100644 index 00000000..2a44d22d --- /dev/null +++ b/app/src/main/java/com/twoeightnine/root/xvii/network/response/SearchResponse.kt @@ -0,0 +1,76 @@ +/* + * xvii - messenger for vk + * Copyright (C) 2021 TwoEightNine + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.twoeightnine.root.xvii.network.response + +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName +import com.twoeightnine.root.xvii.model.Conversation +import com.twoeightnine.root.xvii.model.Group +import com.twoeightnine.root.xvii.model.User +import com.twoeightnine.root.xvii.model.messages.Message +import com.twoeightnine.root.xvii.utils.matchesChatId +import com.twoeightnine.root.xvii.utils.matchesGroupId +import com.twoeightnine.root.xvii.utils.matchesUserId + +data class SearchResponse( + + @SerializedName("items") + @Expose + val items: ArrayList = arrayListOf(), + + @SerializedName("profiles") + @Expose + val profiles: ArrayList, + + @SerializedName("groups") + @Expose + val groups: ArrayList, + + @SerializedName("conversations") + @Expose + val conversations: ArrayList +) { + fun getProfileById(id: Int) = profiles.find { it.id == id } + + fun getGroupById(id: Int) = groups.find { it.id == id } + + fun getConversationById(id: Int) = conversations.find { it.peer?.id == id } + + fun getTitleFor(message: Message) = when { + message.peerId.matchesUserId() -> getProfileById(message.peerId)?.fullName + message.peerId.matchesGroupId() -> getGroupById(-message.peerId)?.name + message.peerId.matchesChatId() -> getConversationById(message.peerId)?.chatSettings?.title + else -> null + } + + fun getPhotoFor(message: Message) = when { + message.peerId.matchesUserId() -> getProfileById(message.peerId)?.photo100 + message.peerId.matchesGroupId() -> getGroupById(-message.peerId)?.photo100 + message.peerId.matchesChatId() -> getConversationById(message.peerId)?.chatSettings?.photo?.photo100 + else -> null + } + + + fun isOnline(message: Message): Boolean { + return when { + message.peerId.matchesUserId() -> getProfileById(message.fromId)?.isOnline == true + else -> false + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/twoeightnine/root/xvii/search/SearchAdapter.kt b/app/src/main/java/com/twoeightnine/root/xvii/search/SearchAdapter.kt index 84114682..df035873 100644 --- a/app/src/main/java/com/twoeightnine/root/xvii/search/SearchAdapter.kt +++ b/app/src/main/java/com/twoeightnine/root/xvii/search/SearchAdapter.kt @@ -19,23 +19,33 @@ package com.twoeightnine.root.xvii.search import android.content.Context +import android.text.Html +import android.text.SpannableStringBuilder import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.twoeightnine.root.xvii.R import com.twoeightnine.root.xvii.extensions.load import com.twoeightnine.root.xvii.managers.Prefs +import com.twoeightnine.root.xvii.utils.EmojiHelper +import com.twoeightnine.root.xvii.utils.wrapMentions import global.msnthrp.xvii.data.dialogs.Dialog import global.msnthrp.xvii.uikit.base.adapters.BaseAdapter import global.msnthrp.xvii.uikit.extensions.hide import global.msnthrp.xvii.uikit.extensions.lowerIf +import kotlinx.android.synthetic.main.item_dialog.view.* import kotlinx.android.synthetic.main.item_dialog_search.view.* +import kotlinx.android.synthetic.main.item_dialog_search.view.civPhoto +import kotlinx.android.synthetic.main.item_dialog_search.view.ivOnlineDot +import kotlinx.android.synthetic.main.item_dialog_search.view.rlItemContainer +import kotlinx.android.synthetic.main.item_dialog_search.view.tvBody +import kotlinx.android.synthetic.main.item_dialog_search.view.tvTitle class SearchAdapter( context: Context, - private val onClick: (Dialog) -> Unit, - private val onLongClick: (Dialog) -> Unit -) : BaseAdapter(context) { + private val onClick: (SearchDialog) -> Unit, + private val onLongClick: (SearchDialog) -> Unit +) : BaseAdapter(context) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = SearchViewHolder(inflater.inflate(R.layout.item_dialog_search, null)) @@ -43,13 +53,34 @@ class SearchAdapter( holder.bind(items[position]) } + private fun getMessageBody(context: Context, dialog: SearchDialog): String { + if (dialog.text.isNotEmpty()) { + return wrapMentions(context, dialog.text).toString() + } + return context.getString(R.string.error_message) + } + inner class SearchViewHolder(view: View) : RecyclerView.ViewHolder(view) { - fun bind(dialog: Dialog) { + fun bind(dialog: SearchDialog) { with(itemView) { civPhoto.load(dialog.photo) - tvTitle.text = dialog.title - tvTitle.lowerIf(Prefs.lowerTexts) + if (dialog.isChat == true) { + tvTitle.text = dialog.title + tvTitle.lowerIf(Prefs.lowerTexts) + tvBody.text = if (EmojiHelper.hasEmojis(dialog.text)) { + EmojiHelper.getEmojied( + context, + dialog.text, + Html.fromHtml(getMessageBody(context, dialog)) as SpannableStringBuilder + ) + } else { + Html.fromHtml(getMessageBody(context, dialog)) + } + }else{ + tvTitleSingle.text = dialog.title + tvTitleSingle.lowerIf(Prefs.lowerTexts) + } ivOnlineDot.hide() // due to this list is not autorefreshable rlItemContainer.setOnClickListener { @@ -64,4 +95,5 @@ class SearchAdapter( } } } + } \ No newline at end of file diff --git a/app/src/main/java/com/twoeightnine/root/xvii/search/SearchDialog.kt b/app/src/main/java/com/twoeightnine/root/xvii/search/SearchDialog.kt new file mode 100644 index 00000000..5a6a023c --- /dev/null +++ b/app/src/main/java/com/twoeightnine/root/xvii/search/SearchDialog.kt @@ -0,0 +1,21 @@ +package com.twoeightnine.root.xvii.search + +data class SearchDialog( + + var peerId: Int, + + var messageId: Int, + + var title: String, + + var text: String = "", + + var photo: String?, + + var isOnline: Boolean, + + var isOut: Boolean = true, + + var isChat: Boolean = false + +) \ No newline at end of file diff --git a/app/src/main/java/com/twoeightnine/root/xvii/search/SearchFragment.kt b/app/src/main/java/com/twoeightnine/root/xvii/search/SearchFragment.kt index afa09355..61a11339 100644 --- a/app/src/main/java/com/twoeightnine/root/xvii/search/SearchFragment.kt +++ b/app/src/main/java/com/twoeightnine/root/xvii/search/SearchFragment.kt @@ -22,11 +22,14 @@ import android.os.Bundle import android.view.View import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.twoeightnine.root.xvii.App import com.twoeightnine.root.xvii.BuildConfig import com.twoeightnine.root.xvii.R import com.twoeightnine.root.xvii.base.BaseFragment import com.twoeightnine.root.xvii.chatowner.ChatOwnerFactory +import com.twoeightnine.root.xvii.chats.messages.chat.usual.ChatActivity +import com.twoeightnine.root.xvii.main.MainActivity import com.twoeightnine.root.xvii.model.Wrapper import com.twoeightnine.root.xvii.uikit.Munch import com.twoeightnine.root.xvii.uikit.paint @@ -46,6 +49,14 @@ class SearchFragment : BaseFragment() { @Inject lateinit var viewModelFactory: SearchViewModel.Factory private lateinit var viewModel: SearchViewModel + var lastAdded: Int = 0 + + private val selectedFriends by lazy { + arguments?.getBoolean(MainActivity.SELECTED_FRIENDS)?: false + } + private val searchString by lazy { + arguments?.getString(MainActivity.SEARCH_TEXT) + } private val adapter by lazy { SearchAdapter(requireContext(), ::onClick, ::onLongClick) @@ -59,7 +70,12 @@ class SearchFragment : BaseFragment() { App.appComponent?.inject(this) viewModel = ViewModelProviders.of(this, viewModelFactory)[SearchViewModel::class.java] + viewModel.setFrom(selectedFriends) etSearch.subscribeSearch(true, viewModel::search) + searchString?.let { + etSearch.setText(it.toString()) + } + ivDelete.setOnClickListener { etSearch.setText("") } ivEmptyView.paint(Munch.color.color50) @@ -77,7 +93,7 @@ class SearchFragment : BaseFragment() { viewModel.getResult().observe(viewLifecycleOwner, ::updateResults) } - private fun updateResults(data: Wrapper>) { + private fun updateResults(data: Wrapper>) { if (data.data != null) { adapter.update(data.data) } else { @@ -85,12 +101,34 @@ class SearchFragment : BaseFragment() { } } - private fun onClick(dialog: Dialog) { - ChatOwnerFactory.launch(context, dialog.peerId) + private fun onClick(sDialog: SearchDialog) { + var dialog = Dialog( + peerId = sDialog.peerId, + messageId = sDialog.messageId, + title = sDialog.title, + text = sDialog.text, + photo = sDialog.photo, + isOnline = sDialog.isOnline, + isOut = sDialog.isOut + ) + if (sDialog.isChat){ + ChatActivity.launch(context, dialog, true) + }else { + ChatOwnerFactory.launch(context, dialog.peerId) + } } - private fun onLongClick(dialog: Dialog) { + private fun onLongClick(sDialog: SearchDialog) { if (BuildConfig.DEBUG) { + var dialog = Dialog( + peerId = sDialog.peerId, + messageId = sDialog.messageId, + title = sDialog.title, + text = sDialog.text, + photo = sDialog.photo, + isOnline = sDialog.isOnline, + isOut = sDialog.isOut + ) NotificationUtils.showTestMessageNotification(requireContext(), dialog) } } @@ -102,6 +140,7 @@ class SearchFragment : BaseFragment() { activity?.let { hideKeyboard(it) } false } + rvSearch.addOnScrollListener(SearchScrollListener()) adapter.emptyView = llEmptyView } @@ -109,4 +148,17 @@ class SearchFragment : BaseFragment() { fun newInstance() = SearchFragment() } + + private inner class SearchScrollListener : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + + if (rvSearch!=null && lastAdded < adapter.itemCount - 1 && + adapter.lastVisiblePosition(rvSearch.layoutManager) == adapter.itemCount - 1) { + viewModel.search(etSearch.text.toString(), adapter.itemCount) + lastAdded = adapter.itemCount - 1 + } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/twoeightnine/root/xvii/search/SearchViewModel.kt b/app/src/main/java/com/twoeightnine/root/xvii/search/SearchViewModel.kt index b31c2e96..dc7114dc 100644 --- a/app/src/main/java/com/twoeightnine/root/xvii/search/SearchViewModel.kt +++ b/app/src/main/java/com/twoeightnine/root/xvii/search/SearchViewModel.kt @@ -20,6 +20,7 @@ package com.twoeightnine.root.xvii.search import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import com.twoeightnine.root.xvii.App import com.twoeightnine.root.xvii.managers.Prefs import com.twoeightnine.root.xvii.model.User import com.twoeightnine.root.xvii.model.WrappedLiveData @@ -29,19 +30,27 @@ import com.twoeightnine.root.xvii.network.ApiService import com.twoeightnine.root.xvii.network.response.BaseResponse import com.twoeightnine.root.xvii.network.response.ListResponse import com.twoeightnine.root.xvii.network.response.SearchConversationsResponse +import com.twoeightnine.root.xvii.network.response.SearchResponse import com.twoeightnine.root.xvii.utils.subscribeSmart import global.msnthrp.xvii.data.dialogs.Dialog import io.reactivex.Flowable import io.reactivex.functions.Function3 +import java.lang.StrictMath.min import javax.inject.Inject class SearchViewModel(private val api: ApiService) : ViewModel() { - private val resultLiveData = WrappedMutableLiveData>() + var fromFriendsPage = false - fun getResult() = resultLiveData as WrappedLiveData> + private val resultLiveData = WrappedMutableLiveData>() - fun search(q: String) { + fun getResult() = resultLiveData as WrappedLiveData> + + fun setFrom(fromFriends:Boolean){ + fromFriendsPage = fromFriends + } + + fun search(q: String, offset: Int =0) { if (q.isEmpty()) { if (Prefs.suggestPeople) { api.searchUsers(q, User.FIELDS, COUNT, 0) @@ -54,22 +63,59 @@ class SearchViewModel(private val api: ApiService) : ViewModel() { resultLiveData.value = Wrapper(arrayListOf()) } } else { - Flowable.zip( - api.searchFriends(q, User.FIELDS, COUNT, 0), - api.searchUsers(q, User.FIELDS, COUNT, 0), + if (!fromFriendsPage) { + api.search(q, COUNT, offset).subscribeSmart({ response -> + val dialogs = arrayListOf() + val mResp = response + mResp?.items?.forEach { msg -> + var dlg = SearchDialog( + peerId = msg.peerId ?: 0, + messageId = msg.id ?: 0, + text = msg.text ?: "", + title = mResp.getTitleFor(msg) ?: "", + photo = mResp.getPhotoFor(msg) ?: "", + isOnline = mResp.isOnline(msg), + isOut = msg.isOut(), + isChat = true + ) + dialogs.add(dlg) + } + if(offset > 0) { + resultLiveData.value?.data?.addAll(ArrayList(dialogs.distinctBy { it.messageId })) + resultLiveData.value = Wrapper(resultLiveData.value?.data) + }else { + resultLiveData.value = + Wrapper(ArrayList(dialogs.distinctBy { it.messageId })) + } + }, { error -> + resultLiveData.value = Wrapper(error = error) + }) + }else{ + Flowable.zip( + api.searchFriends(q, User.FIELDS, COUNT, offset), + api.searchUsers(q, User.FIELDS, COUNT, offset), api.searchConversations(q, COUNT), ResponseCombinerFunction() - ) + ) .subscribeSmart({ response -> - resultLiveData.value = Wrapper(ArrayList(response.distinctBy { it.peerId })) + if(offset > 0) { + resultLiveData.value?.data?.addAll(ArrayList(response.distinctBy { it.peerId })) + resultLiveData.value = Wrapper(resultLiveData.value?.data) + }else { + resultLiveData.value = Wrapper(ArrayList(response.distinctBy { it.peerId })) + } + }, { error -> resultLiveData.value = Wrapper(error = error) }) + + } } } - private fun createFromUser(user: User) = Dialog( + private fun createFromUser(user: User) = SearchDialog( peerId = user.id, + messageId = user.id, title = user.fullName, photo = user.photo100, isOnline = user.isOnline @@ -84,27 +130,29 @@ class SearchViewModel(private val api: ApiService) : ViewModel() { Function3>, BaseResponse>, BaseResponse, - BaseResponse>> { + BaseResponse>> { override fun apply( friends: BaseResponse>, users: BaseResponse>, conversations: BaseResponse - ): BaseResponse> { - val dialogs = arrayListOf() + ): BaseResponse> { + val dialogs = arrayListOf() - val cResp = conversations.response + // @TODO: может, убрать conversation? В диалогах и так работает + // поиск по имени беседы, а на вкладке пользователей это не вполне надо + val cResp = conversations.response friends.response?.items?.forEach { dialogs.add(createFromUser(it)) } cResp?.items?.forEach { conversation -> - dialogs.add(Dialog( + dialogs.add(SearchDialog( peerId = conversation.peer?.id ?: 0, + messageId = conversation.peer?.id ?: 0, title = cResp.getTitleFor(conversation) ?: "", photo = cResp.getPhotoFor(conversation) ?: "", isOnline = cResp.isOnline(conversation) )) } users.response?.items?.forEach { dialogs.add(createFromUser(it)) } - return BaseResponse(dialogs) } } diff --git a/app/src/main/java/com/twoeightnine/root/xvii/utils/Utils.kt b/app/src/main/java/com/twoeightnine/root/xvii/utils/Utils.kt index 5ff7d6c9..ca36a410 100644 --- a/app/src/main/java/com/twoeightnine/root/xvii/utils/Utils.kt +++ b/app/src/main/java/com/twoeightnine/root/xvii/utils/Utils.kt @@ -29,6 +29,7 @@ import android.content.res.Resources import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Canvas +import com.twoeightnine.root.xvii.uikit.Munch import android.net.ConnectivityManager import android.net.Uri import android.os.BatteryManager @@ -39,6 +40,7 @@ import android.provider.MediaStore import android.text.Spannable import android.text.SpannableStringBuilder import android.text.style.ClickableSpan +import android.text.style.ForegroundColorSpan import android.util.DisplayMetrics import android.view.View import android.view.inputmethod.InputMethodManager @@ -55,6 +57,7 @@ import com.twoeightnine.root.xvii.background.longpoll.services.NotificationServi import com.twoeightnine.root.xvii.chatowner.ChatOwnerFactory import com.twoeightnine.root.xvii.crypto.md5 import com.twoeightnine.root.xvii.lg.L +import com.twoeightnine.root.xvii.main.MainActivity import global.msnthrp.xvii.uikit.extensions.SimpleBitmapTarget import global.msnthrp.xvii.uikit.extensions.load import io.reactivex.Completable @@ -67,7 +70,7 @@ import java.text.DecimalFormat import java.util.regex.Pattern -private const val REGEX_MENTION = "(\\[id\\d{1,9}\\|[^\\]]+\\])" +private const val REGEX_MENTION = "(\\[id\\d{1,9}\\|[^\\]]+\\]|#+[a-zA-Z0-9а-яА-ЯёЁ_@]{1,})" fun isOnline(): Boolean { val cm = App.context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager @@ -270,19 +273,36 @@ fun wrapMentions(context: Context, text: CharSequence, addClickable: Boolean = f val start = matcher.start() val end = matcher.end() - val divider = mention.indexOf('|') - val mentionUi = mention.substring(divider + 1, mention.length - 1) - val userId = mention.substring(3, divider).toIntOrNull() + if(mention.indexOf('#')==0){ + // если тег + ssb.append(text.substring(globalStart, start)) + .append(mention) + val tmp = ssb.toString() + if (addClickable) { + ssb.setSpan(object : ClickableSpan() { + override fun onClick(widget: View) { + MainActivity.launch(context, mention) + } + }, tmp.length - mention.length, tmp.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + }else{ + ssb.setSpan(object : ForegroundColorSpan(Munch.color.color){}, tmp.length - mention.length, tmp.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + + }else{ + val divider = mention.indexOf('|') + val mentionUi = mention.substring(divider + 1, mention.length - 1) + val userId = mention.substring(3, divider).toIntOrNull() - ssb.append(text.substring(globalStart, start)) + ssb.append(text.substring(globalStart, start)) .append(mentionUi) - val tmp = ssb.toString() - if (userId != null && addClickable) { - ssb.setSpan(object : ClickableSpan() { - override fun onClick(widget: View) { - ChatOwnerFactory.launch(context, userId) - } - }, tmp.length - mentionUi.length, tmp.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + val tmp = ssb.toString() + if (userId != null && addClickable) { + ssb.setSpan(object : ClickableSpan() { + override fun onClick(widget: View) { + ChatOwnerFactory.launch(context, userId) + } + }, tmp.length - mentionUi.length, tmp.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } } globalStart = end } diff --git a/app/src/main/res/layout/item_dialog_search.xml b/app/src/main/res/layout/item_dialog_search.xml index 490e125d..af5bca4d 100644 --- a/app/src/main/res/layout/item_dialog_search.xml +++ b/app/src/main/res/layout/item_dialog_search.xml @@ -37,7 +37,7 @@ app:civ_border_color="@color/icons" /> + + + + +