From b8c80e4b00d2758f0751bcb00028e3decf8ec470 Mon Sep 17 00:00:00 2001 From: kaixed Date: Thu, 12 Dec 2024 16:14:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E6=B6=88=E6=81=AF=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增搜索历史消息功能 - 修复部分问题 --- .idea/GitCommitMessageStorage.xml | 14 +- .../com/kaixed/kchat/data/LocalDatabase.kt | 9 + .../data/model/search/ChatHistoryItem.kt | 12 + .../kaixed/kchat/manager/MessagesManager.kt | 10 +- .../kchat/processor/MessageProcessor.kt | 32 ++ .../kaixed/kchat/service/WebSocketService.kt | 7 +- .../kaixed/kchat/ui/activity/ChatActivity.kt | 31 +- .../kchat/ui/activity/ChatDetailActivity.kt | 7 +- .../ui/activity/ContactPermissionActivity.kt | 1 - .../kchat/ui/activity/SearchChatHistory.kt | 130 ++++++++- .../kaixed/kchat/ui/adapter/ChatAdapter.kt | 37 +-- .../kchat/ui/adapter/ChatHistoryAdapter.kt | 87 ++++++ .../kchat/ui/adapter/FriendListAdapter.kt | 9 +- .../kchat/ui/fragment/ContactFragment.kt | 2 - .../kaixed/kchat/ui/widget/CustomEditText.kt | 24 ++ .../com/kaixed/kchat/utils/ConstantsUtil.kt | 7 +- .../java/com/kaixed/kchat/utils/ViewUtil.kt | 5 +- .../layout/activity_search_chat_history.xml | 274 ++++++++++++++---- .../res/layout/chat_history_recycle_item.xml | 61 ++++ 19 files changed, 651 insertions(+), 108 deletions(-) create mode 100644 app/src/main/java/com/kaixed/kchat/data/model/search/ChatHistoryItem.kt create mode 100644 app/src/main/java/com/kaixed/kchat/processor/MessageProcessor.kt create mode 100644 app/src/main/java/com/kaixed/kchat/ui/adapter/ChatHistoryAdapter.kt create mode 100644 app/src/main/java/com/kaixed/kchat/ui/widget/CustomEditText.kt create mode 100644 app/src/main/res/layout/chat_history_recycle_item.xml diff --git a/.idea/GitCommitMessageStorage.xml b/.idea/GitCommitMessageStorage.xml index e4fd56a..3b56900 100644 --- a/.idea/GitCommitMessageStorage.xml +++ b/.idea/GitCommitMessageStorage.xml @@ -2,7 +2,19 @@ \ No newline at end of file diff --git a/app/src/main/java/com/kaixed/kchat/data/LocalDatabase.kt b/app/src/main/java/com/kaixed/kchat/data/LocalDatabase.kt index b7795e6..59b0993 100644 --- a/app/src/main/java/com/kaixed/kchat/data/LocalDatabase.kt +++ b/app/src/main/java/com/kaixed/kchat/data/LocalDatabase.kt @@ -52,4 +52,13 @@ object LocalDatabase { val offset = 0 return query.find(offset.toLong(), limit) } + + fun getAllHistoryMessages(contactId: String, msgLocalId: Long): List { + val query = messagesBox + .query(Messages_.takerId.equal(contactId)) + .greaterOrEqual(Messages_.msgLocalId, msgLocalId) + .order(Messages_.timestamp, QueryBuilder.DESCENDING) + .build() + return query.find() + } } \ No newline at end of file diff --git a/app/src/main/java/com/kaixed/kchat/data/model/search/ChatHistoryItem.kt b/app/src/main/java/com/kaixed/kchat/data/model/search/ChatHistoryItem.kt new file mode 100644 index 0000000..2f52f96 --- /dev/null +++ b/app/src/main/java/com/kaixed/kchat/data/model/search/ChatHistoryItem.kt @@ -0,0 +1,12 @@ +package com.kaixed.kchat.data.model.search + +/** + * @Author: kaixed + * @Date: 2024/12/11 16:47 + */ +data class ChatHistoryItem( + var msgLocalId: Long, + var content: String, + var timestamp: Long, + var isMine: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/com/kaixed/kchat/manager/MessagesManager.kt b/app/src/main/java/com/kaixed/kchat/manager/MessagesManager.kt index dbb187e..450abfa 100644 --- a/app/src/main/java/com/kaixed/kchat/manager/MessagesManager.kt +++ b/app/src/main/java/com/kaixed/kchat/manager/MessagesManager.kt @@ -1,6 +1,7 @@ package com.kaixed.kchat.manager import com.kaixed.kchat.data.LocalDatabase +import com.kaixed.kchat.data.LocalDatabase.getAllHistoryMessages import com.kaixed.kchat.data.LocalDatabase.getMessagesWithContact import com.kaixed.kchat.data.LocalDatabase.getMoreMessages import com.kaixed.kchat.data.local.entity.Messages @@ -13,7 +14,7 @@ import kotlinx.coroutines.flow.StateFlow */ object MessagesManager { - private const val LIMIT = 10L + private const val LIMIT = 20L private val _messages = MutableStateFlow>(emptyList()) @@ -24,6 +25,13 @@ object MessagesManager { private var loading = false private var tempIndex: Long = 0 + fun queryHistory(msgLocalId: Long): Int { + _messages.value = emptyList() + val msg = getAllHistoryMessages(contactId, msgLocalId) + _messages.value = msg + return msg.size + } + fun setContactId(contactId: String) { this.contactId = contactId } diff --git a/app/src/main/java/com/kaixed/kchat/processor/MessageProcessor.kt b/app/src/main/java/com/kaixed/kchat/processor/MessageProcessor.kt new file mode 100644 index 0000000..a7bb673 --- /dev/null +++ b/app/src/main/java/com/kaixed/kchat/processor/MessageProcessor.kt @@ -0,0 +1,32 @@ +package com.kaixed.kchat.processor + +import com.kaixed.kchat.data.local.box.ObjectBox.getBox +import com.kaixed.kchat.data.local.entity.Messages +import com.kaixed.kchat.data.local.entity.Messages_ +import io.objectbox.Box + +/** + * @Author: kaixed + * @Date: 2024/12/12 15:14 + */ +object MessageProcessor { + + private const val INTERVAL = 5 + + private val messagesBox: Box by lazy { getBox(Messages::class.java) } + + fun processorMsg(messages: Messages): Messages { + val curTimestamp = System.currentTimeMillis() + val localMsg = messagesBox.query(Messages_.takerId.equal(messages.takerId)) + .lessOrEqual(Messages_.timestamp, curTimestamp) + .orderDesc(Messages_.timestamp).build().findFirst() + + var showTimer = true + localMsg?.let { + showTimer = curTimestamp - it.timestamp >= INTERVAL * 1L * 60 * 1000 + } + + messages.isShowTimer = showTimer + return messages + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kaixed/kchat/service/WebSocketService.kt b/app/src/main/java/com/kaixed/kchat/service/WebSocketService.kt index a6e7f5a..6ada19c 100644 --- a/app/src/main/java/com/kaixed/kchat/service/WebSocketService.kt +++ b/app/src/main/java/com/kaixed/kchat/service/WebSocketService.kt @@ -19,9 +19,11 @@ import com.kaixed.kchat.data.local.entity.Messages_ import com.kaixed.kchat.network.NetworkInterface.WEBSOCKET import com.kaixed.kchat.network.NetworkInterface.WEBSOCKET_SERVER_URL import com.kaixed.kchat.network.OkhttpHelper +import com.kaixed.kchat.processor.MessageProcessor import com.kaixed.kchat.utils.Constants.MMKV_COMMON_DATA import com.kaixed.kchat.utils.ConstantsUtil.getCurrentContactId import com.kaixed.kchat.utils.ConstantsUtil.getUsername +import com.kaixed.kchat.utils.SingleLiveEvent import com.tencent.mmkv.MMKV import io.objectbox.Box import kotlinx.coroutines.CoroutineScope @@ -60,9 +62,9 @@ class WebSocketService : Service() { private val serviceScope = CoroutineScope(IO + serviceJob) - private val _messagesMutableLiveData = MutableLiveData() + private val _messagesMutableLiveData = SingleLiveEvent() - val messageLivedata: LiveData get() = _messagesMutableLiveData + val messageLivedata: SingleLiveEvent get() = _messagesMutableLiveData private val _conversations = MutableLiveData?>() @@ -233,6 +235,7 @@ class WebSocketService : Service() { } else { messages.takerId = messages.senderId _messagesMutableLiveData.postValue(messages) + MessageProcessor.processorMsg(messages) messagesBox.put(messages) updateConversationList(messages) } diff --git a/app/src/main/java/com/kaixed/kchat/ui/activity/ChatActivity.kt b/app/src/main/java/com/kaixed/kchat/ui/activity/ChatActivity.kt index f076869..c365850 100644 --- a/app/src/main/java/com/kaixed/kchat/ui/activity/ChatActivity.kt +++ b/app/src/main/java/com/kaixed/kchat/ui/activity/ChatActivity.kt @@ -33,6 +33,7 @@ import com.kaixed.kchat.data.local.entity.Messages import com.kaixed.kchat.data.model.FunctionItem import com.kaixed.kchat.databinding.ActivityChatBinding import com.kaixed.kchat.manager.MessagesManager +import com.kaixed.kchat.processor.MessageProcessor import com.kaixed.kchat.service.WebSocketService import com.kaixed.kchat.service.WebSocketService.LocalBinder import com.kaixed.kchat.ui.adapter.ChatAdapter @@ -45,6 +46,7 @@ import com.kaixed.kchat.ui.widget.LoadingDialogFragment import com.kaixed.kchat.utils.Constants.CURRENT_CONTACT_ID import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT_RATIO import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION +import com.kaixed.kchat.utils.ConstantsUtil.getAvatarUrl import com.kaixed.kchat.utils.ConstantsUtil.getKeyboardHeight import com.kaixed.kchat.utils.ConstantsUtil.getUsername import com.kaixed.kchat.utils.ImageEngines @@ -91,6 +93,10 @@ class ChatActivity : BaseActivity(), OnItemClickListener, private val fileViewModel: FileViewModel by viewModels() + private var isSearchHistory = false + + private var msgLocalId = 0L + companion object { private const val UNBLOCK_DELAY_TIME = 200L } @@ -109,6 +115,17 @@ class ChatActivity : BaseActivity(), OnItemClickListener, setPanelChange() getKeyBoardVisibility() observeViewModel() + if (isSearchHistory) { + val size = MessagesManager.queryHistory(msgLocalId) + binding.recycleChatList.smoothScrollToPosition(size - 1) + } + } + + override fun onResume() { + super.onResume() + if (!isSearchHistory) { + MessagesManager.firstLoadMessages() + } } private fun observeViewModel() { @@ -376,8 +393,11 @@ class ChatActivity : BaseActivity(), OnItemClickListener, } override fun initData() { - contactId = intent.getStringExtra("contactId").toString() - contactNickname = intent.getStringExtra("contactNickname") + contactId = intent?.getStringExtra("contactId").toString() + contactNickname = intent?.getStringExtra("contactNickname") + isSearchHistory = intent?.getBooleanExtra("isSearchHistory", false) == true + msgLocalId = intent.getLongExtra("msgLocalId", 0) + binding.ctb.setTitleName(contactNickname!!) softKeyboardHeight = getKeyboardHeight() @@ -414,7 +434,6 @@ class ChatActivity : BaseActivity(), OnItemClickListener, private fun handleMsg(messages: Messages) { MessagesManager.receiveMessage(messages) -// messagesViewModel.receiveMessage(messages) binding.recycleChatList.smoothScrollToPosition(0) } @@ -424,10 +443,11 @@ class ChatActivity : BaseActivity(), OnItemClickListener, timestamp = System.currentTimeMillis(), senderId = username, takerId = talkerId, + avatarUrl = getAvatarUrl(), type = type, ) - - val id: Long = messagesBox.put(messages) + val msg = MessageProcessor.processorMsg(messages) + val id: Long = messagesBox.put(msg) val jsonObject = JSONObject() val jsonObject2 = JSONObject() @@ -445,7 +465,6 @@ class ChatActivity : BaseActivity(), OnItemClickListener, binding.etInput.setText("") } - private fun bindWebSocketService() { bindService( Intent( diff --git a/app/src/main/java/com/kaixed/kchat/ui/activity/ChatDetailActivity.kt b/app/src/main/java/com/kaixed/kchat/ui/activity/ChatDetailActivity.kt index 9c7a11c..6cbf949 100644 --- a/app/src/main/java/com/kaixed/kchat/ui/activity/ChatDetailActivity.kt +++ b/app/src/main/java/com/kaixed/kchat/ui/activity/ChatDetailActivity.kt @@ -3,7 +3,6 @@ package com.kaixed.kchat.ui.activity import android.content.Intent import android.os.Bundle import androidx.activity.enableEdgeToEdge -import androidx.lifecycle.ViewModelProvider import com.bumptech.glide.Glide import com.kaixed.kchat.data.LocalDatabase.getContactByUsername import com.kaixed.kchat.databinding.ActivityChatDetailBinding @@ -54,7 +53,11 @@ class ChatDetailActivity : BaseActivity() { } binding.ciSearchChatHistory.setOnClickListener { - val intent = Intent(this, SearchChatHistory::class.java) + val intent = Intent(this, SearchChatHistory::class.java).apply { + putExtra("contactNickname", contact?.remark ?: contact?.nickname) + putExtra("contactAvatarUrl", contact?.avatarUrl) + putExtra("contactId", contactId) + } startActivity(intent) } } diff --git a/app/src/main/java/com/kaixed/kchat/ui/activity/ContactPermissionActivity.kt b/app/src/main/java/com/kaixed/kchat/ui/activity/ContactPermissionActivity.kt index 5e385f3..64d14e3 100644 --- a/app/src/main/java/com/kaixed/kchat/ui/activity/ContactPermissionActivity.kt +++ b/app/src/main/java/com/kaixed/kchat/ui/activity/ContactPermissionActivity.kt @@ -19,7 +19,6 @@ class ContactPermissionActivity : BaseActivity } override fun initData() { - TODO("Not yet implemented") } private fun initView() { diff --git a/app/src/main/java/com/kaixed/kchat/ui/activity/SearchChatHistory.kt b/app/src/main/java/com/kaixed/kchat/ui/activity/SearchChatHistory.kt index 41ee0a1..307afdd 100644 --- a/app/src/main/java/com/kaixed/kchat/ui/activity/SearchChatHistory.kt +++ b/app/src/main/java/com/kaixed/kchat/ui/activity/SearchChatHistory.kt @@ -1,16 +1,49 @@ package com.kaixed.kchat.ui.activity +import android.graphics.Color import android.os.Bundle +import android.text.style.ForegroundColorSpan +import android.util.Log +import android.view.View import androidx.activity.enableEdgeToEdge -import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import com.kaixed.kchat.R +import androidx.core.text.buildSpannedString +import androidx.core.text.inSpans +import androidx.core.widget.addTextChangedListener +import androidx.recyclerview.widget.LinearLayoutManager +import com.kaixed.kchat.data.local.box.ObjectBox.getBox +import com.kaixed.kchat.data.local.entity.Messages +import com.kaixed.kchat.data.local.entity.Messages_ +import com.kaixed.kchat.data.model.search.ChatHistoryItem import com.kaixed.kchat.databinding.ActivitySearchChatHistoryBinding +import com.kaixed.kchat.ui.adapter.ChatHistoryAdapter import com.kaixed.kchat.ui.base.BaseActivity +import com.kaixed.kchat.utils.ConstantsUtil.getUsername +import io.objectbox.Box class SearchChatHistory : BaseActivity() { + companion object { + private const val VIEW_CHAT_HISTORY = 0 + private const val VIEW_SEARCH_FAILED = 1 + private const val VIEW_SELECT = 2 + } + + private val messageBox: Box by lazy { getBox(Messages::class.java) } + + private val chatHistoryAdapter by lazy { + ChatHistoryAdapter( + contactAvatarUrl, + contactNickname, + this + ) + } + + private var contactAvatarUrl: String = "" + + private var contactNickname: String = "" + + private var contactId: String = "" + override fun inflateBinding(): ActivitySearchChatHistoryBinding { return ActivitySearchChatHistoryBinding.inflate(layoutInflater) } @@ -18,10 +51,93 @@ class SearchChatHistory : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() - setContentView(R.layout.activity_search_chat_history) + setListener() + setupRecyclerView() + showView(VIEW_SELECT) + } + + private fun setupRecyclerView() { + binding.rvChatHistory.layoutManager = LinearLayoutManager(this) + binding.rvChatHistory.adapter = chatHistoryAdapter + chatHistoryAdapter.setContactId(contactId) + + chatHistoryAdapter.let { adapter -> + val viewHolder = adapter.createViewHolder(binding.rvChatHistory, adapter.getItemViewType(0)) + val itemView = viewHolder.itemView + itemView.measure( + View.MeasureSpec.makeMeasureSpec(binding.rvChatHistory.width, View.MeasureSpec.EXACTLY), + View.MeasureSpec.UNSPECIFIED + ) + val itemHeight = itemView.measuredHeight + Log.d("RecyclerView", "单个 Item 高度: $itemHeight") + } + + } + + private fun setListener() { + binding.tvCancel.setOnClickListener { finish() } + binding.etSearch.addTextChangedListener(afterTextChanged = + { + if (it?.length == 0) { + chatHistoryAdapter.submitList(null) + showView(VIEW_SELECT) + } else { + getData(it.toString()) + } + }) + } + + private fun showView(viewToShowIndex: Int) { + val viewList = listOf(binding.rvChatHistory, binding.tvSearchFailed, binding.clSelect) + viewList.forEachIndexed { index, view -> + view.visibility = if (index == viewToShowIndex) View.VISIBLE else View.INVISIBLE + } + } + + private fun getData(matchedField: String) { + val data = loadDataFromDb(matchedField) + + val items = data.map { + ChatHistoryItem(it.msgLocalId, it.content, it.timestamp, it.senderId == getUsername()) + } + + chatHistoryAdapter.setMatchedField(matchedField) + chatHistoryAdapter.submitList(null) + chatHistoryAdapter.submitList(items) + + when (items.isEmpty()) { + true -> { + val text = createEmptyMessage(matchedField) + binding.tvSearchFailed.text = text + showView(VIEW_SEARCH_FAILED) + } + + false -> { + showView(VIEW_CHAT_HISTORY) + } + } + } + + private fun createEmptyMessage(matchedField: String): CharSequence { + return buildSpannedString { + append("没有找到与") + append("“") + inSpans(ForegroundColorSpan(Color.parseColor("#07C160"))) { + append(matchedField) + } + append("”") + append("相关的聊天记录") + } + } + + private fun loadDataFromDb(content: String): List { + return messageBox.query(Messages_.content.contains(content)).orderDesc(Messages_.timestamp) + .build().find() } override fun initData() { - + contactNickname = intent?.getStringExtra("contactNickname") ?: "" + contactAvatarUrl = intent?.getStringExtra("contactAvatarUrl") ?: "" + contactId = intent?.getStringExtra("contactId") ?: "" } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/kaixed/kchat/ui/adapter/ChatAdapter.kt b/app/src/main/java/com/kaixed/kchat/ui/adapter/ChatAdapter.kt index 026109e..4106991 100644 --- a/app/src/main/java/com/kaixed/kchat/ui/adapter/ChatAdapter.kt +++ b/app/src/main/java/com/kaixed/kchat/ui/adapter/ChatAdapter.kt @@ -21,6 +21,7 @@ import com.kaixed.kchat.databinding.ChatRecycleItemImageNormalBinding import com.kaixed.kchat.databinding.ChatRecycleItemTipBinding import com.kaixed.kchat.utils.ConstantsUtil.getUsername import com.kaixed.kchat.utils.PopWindowUtil.showPopupWindow +import com.kaixed.kchat.utils.TextUtil import com.kaixed.kchat.utils.TextUtil.extractDimensionsAndPrefix import com.kaixed.kchat.utils.ViewUtil.changeTimerVisibility import com.kaixed.kchat.utils.ViewUtil.setViewVisibility @@ -42,25 +43,12 @@ class ChatAdapter( companion object { const val CUSTOM = 0 - - // 提示消息 const val TIP = 2 - - // 图片消息 const val IMAGE = 4 - - // 语音消息 const val VOICE = 6 - - // 位置消息 const val LOCATION = 8 - - // 表情消息 const val EMOJI = 10 - - // 红包消息 const val RED_PACKET = 12 - private const val TAG = "ChatAdapter" } @@ -68,25 +56,22 @@ class ChatAdapter( return when (viewType) { CUSTOM -> { CustomViewHolder( - ChatRecycleItemCustomNormalBinding.inflate( - LayoutInflater.from(parent.context), parent, false - ) + ChatRecycleItemCustomNormalBinding + .inflate(LayoutInflater.from(parent.context), parent, false) ) } IMAGE -> { ImageViewHolder( - ChatRecycleItemImageNormalBinding.inflate( - LayoutInflater.from(parent.context), parent, false - ) + ChatRecycleItemImageNormalBinding + .inflate(LayoutInflater.from(parent.context), parent, false) ) } else -> { TipViewHolder( - ChatRecycleItemTipBinding.inflate( - LayoutInflater.from(parent.context), parent, false - ) + ChatRecycleItemTipBinding + .inflate(LayoutInflater.from(parent.context), parent, false) ) } } @@ -108,7 +93,7 @@ class ChatAdapter( } } (holder as? HasTimer)?.let { - changeTimerVisibility(position, currentList, it.getTimerView(), singleMessage) + changeTimerVisibility(it.getTimerView(), singleMessage) } handleLongClick(holder, position, singleMessage) } @@ -176,12 +161,15 @@ class ChatAdapter( contentId = binding.tvMsgContent.id, contentMineId = binding.tvMsgContentMine.id ) + Glide.with(binding.root.context).load(message.avatarUrl) + .into(if (sender) binding.ifvAvatarMine else binding.ifvAvatar) val contentView = if (sender) binding.tvMsgContentMine else binding.tvMsgContent contentView.text = message.content.replaceSpan("[委屈]") { CenterImageSpan(binding.root.context, R.drawable.emoji).setDrawableSize(55) } + } override fun getTimerView(): TextView = binding.tvTimer @@ -191,6 +179,8 @@ class ChatAdapter( RecyclerView.ViewHolder(binding.root), HasTimer { fun bindData(message: Messages) { val sender = message.senderId == getUsername() + Glide.with(binding.root.context).load(message.avatarUrl) + .into(if (sender) binding.ifvAvatarMine else binding.ifvAvatar) setViewVisibility( parentView = binding.root, sender = sender, @@ -226,7 +216,6 @@ class ChatAdapter( override fun getItemViewType(position: Int): Int { return getItem(position).type.toInt() - } private fun updateDb(message: Messages) { diff --git a/app/src/main/java/com/kaixed/kchat/ui/adapter/ChatHistoryAdapter.kt b/app/src/main/java/com/kaixed/kchat/ui/adapter/ChatHistoryAdapter.kt new file mode 100644 index 0000000..836b40e --- /dev/null +++ b/app/src/main/java/com/kaixed/kchat/ui/adapter/ChatHistoryAdapter.kt @@ -0,0 +1,87 @@ +package com.kaixed.kchat.ui.adapter + +import android.content.Context +import android.content.Intent +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.bumptech.glide.Glide +import com.drake.spannable.replaceSpanFirst +import com.drake.spannable.span.HighlightSpan +import com.kaixed.kchat.data.model.search.ChatHistoryItem +import com.kaixed.kchat.databinding.ChatHistoryRecycleItemBinding +import com.kaixed.kchat.ui.activity.ChatActivity +import com.kaixed.kchat.utils.ConstantsUtil +import com.kaixed.kchat.utils.TextUtil + +/** + * @Author: kaixed + * @Date: 2024/12/11 16:42 + */ +class ChatHistoryAdapter( + private val contactAvatarUrl: String, + private val contactNickname: String, + private val context: Context +) : + ListAdapter(DiffCallback()) { + + private var matchedField: String = "" + private val highlightSpan = HighlightSpan("#07C160") + private var contactId = "" + + fun setMatchedField(field: String) { + matchedField = field + } + + fun setContactId(contactId: String) { + this.contactId = contactId + } + + class MyViewHolder(val binding: ChatHistoryRecycleItemBinding) : ViewHolder(binding.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + return MyViewHolder( + ChatHistoryRecycleItemBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + ) + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val bind = holder.binding + val chatHistoryItem = getItem(position) + bind.tvTime.text = TextUtil.getTimestampString(chatHistoryItem.timestamp) + bind.tvContent.text = chatHistoryItem.content.replaceSpanFirst(matchedField) { + highlightSpan + } + bind.tvNickname.text = + if (chatHistoryItem.isMine) ConstantsUtil.getNickName() else contactNickname + val avatarUrl = + if (chatHistoryItem.isMine) ConstantsUtil.getAvatarUrl() else contactAvatarUrl + Glide.with(bind.root.context).load(avatarUrl).into(bind.ifvAvatar) + bind.root.postDelayed({ + val intent = Intent(context, ChatActivity::class.java).apply { + putExtra("contactId", contactId) + putExtra("contactNickname", contactNickname) + putExtra("isSearchHistory", true) + putExtra("msgLocalId", chatHistoryItem.msgLocalId) + } + context.startActivity(intent) + }, 200L) + } + + class DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: ChatHistoryItem, newItem: ChatHistoryItem): Boolean { + return oldItem.msgLocalId == newItem.msgLocalId + } + + override fun areContentsTheSame( + oldItem: ChatHistoryItem, + newItem: ChatHistoryItem + ): Boolean { + return oldItem == newItem + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kaixed/kchat/ui/adapter/FriendListAdapter.kt b/app/src/main/java/com/kaixed/kchat/ui/adapter/FriendListAdapter.kt index 1f33117..ea5a8eb 100644 --- a/app/src/main/java/com/kaixed/kchat/ui/adapter/FriendListAdapter.kt +++ b/app/src/main/java/com/kaixed/kchat/ui/adapter/FriendListAdapter.kt @@ -77,10 +77,7 @@ class FriendListAdapter(var items: MutableList, private val context: Co if (position == 0) { holder.binding.root.setOnClickListener { context.startActivity( - Intent( - context, - ContactRequestListActivity::class.java - ) + Intent(context, ContactRequestListActivity::class.java) ) } } @@ -101,9 +98,9 @@ class FriendListAdapter(var items: MutableList, private val context: Co } else { holder.binding.tvLetter.text = item.quanpin?.substring(0, 1)?.uppercase() } - holder.binding.tvLetter.visibility = ViewGroup.VISIBLE + holder.binding.tvLetter.visibility = View.VISIBLE } else { - holder.binding.tvLetter.visibility = ViewGroup.GONE + holder.binding.tvLetter.visibility = View.GONE } if (position <= defaultItems.size - 1) { diff --git a/app/src/main/java/com/kaixed/kchat/ui/fragment/ContactFragment.kt b/app/src/main/java/com/kaixed/kchat/ui/fragment/ContactFragment.kt index e8dc3b1..dee5eee 100644 --- a/app/src/main/java/com/kaixed/kchat/ui/fragment/ContactFragment.kt +++ b/app/src/main/java/com/kaixed/kchat/ui/fragment/ContactFragment.kt @@ -96,8 +96,6 @@ class ContactFragment : BaseFragment() { contactViewModel.getContactRequestList(getUsername()) } - - private fun loadData() { loading = true binding.tvLoading.visibility = View.VISIBLE diff --git a/app/src/main/java/com/kaixed/kchat/ui/widget/CustomEditText.kt b/app/src/main/java/com/kaixed/kchat/ui/widget/CustomEditText.kt new file mode 100644 index 0000000..2519ac9 --- /dev/null +++ b/app/src/main/java/com/kaixed/kchat/ui/widget/CustomEditText.kt @@ -0,0 +1,24 @@ +package com.kaixed.kchat.ui.widget + +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import androidx.annotation.RequiresApi +import androidx.appcompat.widget.AppCompatEditText +import androidx.core.content.ContextCompat +import com.kaixed.kchat.R + +/** + * @Author: kaixed + * @Date: 2024/12/12 10:09 + */ +@RequiresApi(Build.VERSION_CODES.Q) +class CustomEditText @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = androidx.appcompat.R.attr.editTextStyle +) : AppCompatEditText(context, attrs, defStyleAttr) { + init { + textCursorDrawable = ContextCompat.getDrawable(context, R.drawable.cursor) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kaixed/kchat/utils/ConstantsUtil.kt b/app/src/main/java/com/kaixed/kchat/utils/ConstantsUtil.kt index 7633b9b..bdc841a 100644 --- a/app/src/main/java/com/kaixed/kchat/utils/ConstantsUtil.kt +++ b/app/src/main/java/com/kaixed/kchat/utils/ConstantsUtil.kt @@ -29,8 +29,10 @@ object ConstantsUtil { fun getUsername(): String = userSessionMMKV.getString(USERNAME_KEY, "") ?: "" - fun getAvatarUrl(): String = - userSessionMMKV.getString(AVATAR_URL, "") ?: "" + fun getAvatarUrl(): String { + val avatarUrl = userSessionMMKV.getString(AVATAR_URL, "") ?: "" + return TextUtil.extractDimensionsAndPrefix(avatarUrl)?.first ?: "" + } fun getStatusBarHeight(): Int = commonDataMMKV.getInt(STATUS_BAR_HEIGHT, STATUS_BAR_DEFAULT_HEIGHT) @@ -49,5 +51,4 @@ object ConstantsUtil { } return isFirstLaunch } - } diff --git a/app/src/main/java/com/kaixed/kchat/utils/ViewUtil.kt b/app/src/main/java/com/kaixed/kchat/utils/ViewUtil.kt index a0de572..fac7e8c 100644 --- a/app/src/main/java/com/kaixed/kchat/utils/ViewUtil.kt +++ b/app/src/main/java/com/kaixed/kchat/utils/ViewUtil.kt @@ -101,14 +101,11 @@ object ViewUtil { fun changeTimerVisibility( - position: Int, - messages: List, tvTimer: TextView, singleMessage: Messages ) { val showTimer: Boolean = - if (position == messages.size - 1) true - else singleMessage.timestamp - messages[position + 1].timestamp >= 5 * 1L * 60 * 1000 + singleMessage.isShowTimer if (showTimer) { tvTimer.visibility = View.VISIBLE diff --git a/app/src/main/res/layout/activity_search_chat_history.xml b/app/src/main/res/layout/activity_search_chat_history.xml index 9e0b93c..88b19e3 100644 --- a/app/src/main/res/layout/activity_search_chat_history.xml +++ b/app/src/main/res/layout/activity_search_chat_history.xml @@ -9,69 +9,245 @@ android:fitsSystemWindows="true" tools:context=".ui.activity.SearchChatHistory"> - - + - - - + android:layout_height="match_parent"> - + + + + + - + + + + - + app:layout_constraintTop_toBottomOf="@id/ll_search" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_history_recycle_item.xml b/app/src/main/res/layout/chat_history_recycle_item.xml new file mode 100644 index 0000000..46c7685 --- /dev/null +++ b/app/src/main/res/layout/chat_history_recycle_item.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + \ No newline at end of file