refactor: 更改view层为ui层

1.更改view层为ui层
2.将全部java类更改为kotlin编写
This commit is contained in:
糕小菜 2024-10-27 18:52:04 +08:00
parent 7c2a9f0619
commit b81419b40f
77 changed files with 1632 additions and 1946 deletions

View File

@ -21,67 +21,13 @@
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".view.activity.ContactUpdatesActivity"
android:name=".ui.activity.SearchActivity"
android:exported="false" />
<activity
android:name=".view.activity.ApproveDetailActivity"
android:name=".ui.activity.MainActivity"
android:exported="false" />
<activity
android:name=".view.activity.ApproveContactRequestActivity"
android:exported="false" />
<activity
android:name=".view.activity.MessageActivity"
android:exported="false" />
<activity
android:name=".view.activity.ApplyAddFriendActivity"
android:exported="false" />
<activity
android:name=".view.activity.FriendListActivity"
android:exported="false" />
<activity
android:name=".view.activity.ServiceDetailActivity"
android:exported="false" />
<activity
android:name=".view.activity.SettingActivity"
android:exported="false" />
<activity
android:name=".view.activity.ProfileDetailActivity"
android:exported="false" />
<activity
android:name=".view.activity.SetRemarkActivity"
android:exported="false" />
<activity
android:name=".view.activity.DataSettingActivity"
android:exported="false" />
<activity
android:name=".view.activity.ApplyFriendsDetailActivity"
android:exported="false" />
<activity
android:name=".view.activity.SearchFriendsActivity"
android:exported="false" />
<activity
android:name=".view.activity.MainActivity2"
android:exported="false" />
<activity
android:name=".view.activity.AddFriendsActivity"
android:exported="false" />
<activity
android:name=".view.activity.TestActivity"
android:exported="false" />
<activity
android:name=".view.activity.ProfileActivity"
android:exported="false" />
<activity
android:name=".view.activity.SearchActivity"
android:exported="false" />
<activity
android:name=".view.activity.ChatDetailActivity"
android:exported="false" />
<activity
android:name=".view.activity.ContactsDetailActivity"
android:exported="false" />
<activity
android:name=".view.activity.LoginActivity"
android:name=".ui.activity.LoginActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -90,12 +36,62 @@
</intent-filter>
</activity>
<activity
android:name=".view.activity.ChatActivity"
android:exported="false"
android:windowSoftInputMode="adjustResize" />
android:name=".ui.activity.ContactsDetailActivity"
android:exported="false" />
<activity
android:name=".view.activity.MainActivity"
android:exported="true" />
android:name=".ui.activity.ChatDetailActivity"
android:exported="false" />
<activity
android:name=".ui.activity.ChatActivity"
android:exported="false" />
<activity
android:name=".ui.activity.ContactUpdatesActivity"
android:exported="false" />
<activity
android:name=".ui.activity.ApproveDetailActivity"
android:exported="false" />
<activity
android:name=".ui.activity.ApproveContactRequestActivity"
android:exported="false" />
<activity
android:name=".ui.activity.MessageActivity"
android:exported="false" />
<activity
android:name=".ui.activity.ApplyAddFriendActivity"
android:exported="false" />
<activity
android:name=".ui.activity.FriendListActivity"
android:exported="false" />
<activity
android:name=".ui.activity.ServiceDetailActivity"
android:exported="false" />
<activity
android:name=".ui.activity.SettingActivity"
android:exported="false" />
<activity
android:name=".ui.activity.ProfileDetailActivity"
android:exported="false" />
<activity
android:name=".ui.activity.SetRemarkActivity"
android:exported="false" />
<activity
android:name=".ui.activity.DataSettingActivity"
android:exported="false" />
<activity
android:name=".ui.activity.ApplyFriendsDetailActivity"
android:exported="false" />
<activity
android:name=".ui.activity.SearchFriendsActivity"
android:exported="false" />
<activity
android:name=".ui.activity.AddFriendsActivity"
android:exported="false" />
<activity
android:name=".ui.activity.TestActivity"
android:exported="false" />
<activity
android:name=".ui.activity.ProfileActivity"
android:exported="false" />
<service android:name=".service.WebSocketService" />
</application>

View File

@ -1,9 +1,8 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import com.kaixed.kchat.databinding.ActivityAddFriendsBinding

View File

@ -1,17 +1,12 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.kaixed.kchat.R
import com.kaixed.kchat.databinding.ActivityApplyAddFriendBinding
import com.kaixed.kchat.viewmodel.ApplyFriendViewModel
import com.kaixed.kchat.viewmodel.SearchFriendsViewModel
class ApplyAddFriendActivity : BaseActivity() {
private lateinit var binding: ActivityApplyAddFriendBinding
@ -31,7 +26,7 @@ class ApplyAddFriendActivity : BaseActivity() {
if (contactId != null) {
applyAddFriendViewModel.addContact(contactId, binding.etMessage.text.toString())
?.observe(this) { value ->
.observe(this) { value ->
if (value != null) {
runOnUiThread {
if (value.code == "200") {

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.content.Intent
import android.os.Build

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.annotation.SuppressLint
import android.app.TaskStackBuilder

View File

@ -1,13 +1,10 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.content.Intent
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.bumptech.glide.Glide
import com.kaixed.kchat.R
import com.kaixed.kchat.databinding.ActivityApproveDetailBinding
import com.kaixed.kchat.model.friend.FriendRequestItem

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.os.Bundle
import android.widget.Toast

View File

@ -0,0 +1,525 @@
package com.kaixed.kchat.ui.activity
import android.annotation.SuppressLint
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.graphics.Color
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.os.IBinder
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.LinearLayout
import androidx.activity.OnBackPressedCallback
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.kaixed.kchat.database.ObjectBox.get
import com.kaixed.kchat.database.UserManager.getUsername
import com.kaixed.kchat.database.entity.Messages
import com.kaixed.kchat.database.entity.Messages_
import com.kaixed.kchat.databinding.ActivityChatBinding
import com.kaixed.kchat.service.WebSocketService
import com.kaixed.kchat.service.WebSocketService.LocalBinder
import com.kaixed.kchat.ui.adapter.ChatAdapter
import com.kaixed.kchat.ui.adapter.EmojiAdapter
import com.kaixed.kchat.ui.adapter.FunctionPanelAdapter
import com.kaixed.kchat.utils.ConstantsUtil.Companion.getKeyboardHeight
import com.kaixed.kchat.utils.ImageSpanUtil.insertEmoji
import com.kaixed.kchat.ui.i.OnItemClickListener
import io.objectbox.Box
import io.objectbox.query.QueryBuilder
import org.json.JSONException
import org.json.JSONObject
import java.util.LinkedList
class ChatActivity : AppCompatActivity(), OnItemClickListener {
private lateinit var binding: ActivityChatBinding
private var chatAdapter: ChatAdapter? = null
private var webSocketService: WebSocketService? = null
private val messagesList = LinkedList<Messages>()
private lateinit var messagesBox: Box<Messages>
private var friendId: String? = null
private val limit: Long = 50L
private var isLoading = false
private var isHasHistory = false
private var isBound = false
private var username: String? = null
private var tempIndex: Long = 0
private val mContext: Context = this
private var strings: MutableList<String>? = null
private var imm: InputMethodManager? = null
private var softKeyboardHeight = 0
private var isKeyboardShown = false
private var contactId: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityChatBinding.inflate(layoutInflater)
setContentView(binding.root)
initData()
initView()
firstLoadData()
setListener()
handleIntent(intent)
bindWebSocketService()
imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
setPanelChange()
setBackPressListener()
getKeyBoardVisibility()
}
private val connection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as LocalBinder
webSocketService = binder.getService()
isBound = true
observeLiveData()
}
override fun onServiceDisconnected(arg0: ComponentName) {
isBound = false
}
}
private fun getKeyBoardVisibility() {
val rootView = binding.root
rootView.viewTreeObserver.addOnGlobalLayoutListener {
val r = Rect()
rootView.getWindowVisibleDisplayFrame(r)
val screenHeight = rootView.rootView.height
val keypadHeight = screenHeight - r.bottom
isKeyboardShown = keypadHeight > screenHeight * 0.15
}
}
private fun setBackPressListener() {
onBackPressedDispatcher.addCallback(
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (binding.clBottomPanel.isShown) {
hidePanel(false)
} else {
isEnabled = false
onBackPressedDispatcher.onBackPressed()
}
}
},
)
}
@SuppressLint("ClickableViewAccessibility")
private fun setPanelChange() {
binding.etInput.setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_UP && binding.clBottomPanel.isShown) {
lockContentViewHeight()
hidePanel(true)
unlockContentViewHeight()
}
false
}
binding.ivFunctionPanel.setOnClickListener { v ->
if (binding.clBottomPanel.isShown) {
if (binding.rvEmoji.isShown) {
handlePanelSwitch(false)
} else {
hideBottomPanel()
}
} else {
switchPanel(false)
}
}
binding.ivEmoji.setOnClickListener { v ->
if (binding.clBottomPanel.isShown) {
if (binding.gvFunctionPanel.isShown) {
handlePanelSwitch(true)
} else {
hideBottomPanel()
}
} else {
switchPanel(true)
}
}
}
private fun switchPanel(isSwitchToEmoji: Boolean) {
if (isKeyboardShown) {
lockContentViewHeight()
handlePanelSwitch(isSwitchToEmoji)
unlockContentViewHeight()
} else {
handlePanelSwitch(isSwitchToEmoji)
}
}
private fun hideBottomPanel() {
lockContentViewHeight()
hidePanel(true)
unlockContentViewHeight()
}
private fun lockContentViewHeight() {
val layoutParams = binding.recycleChatList.layoutParams as LinearLayout.LayoutParams
layoutParams.height = binding.recycleChatList.height
layoutParams.weight = 0f
binding.recycleChatList.layoutParams = layoutParams
}
private fun unlockContentViewHeight() {
binding.recycleChatList.postDelayed({
val layoutParams = binding.recycleChatList.layoutParams as LinearLayout.LayoutParams
layoutParams.weight = 1f
binding.recycleChatList.layoutParams = layoutParams
}, 200)
}
private fun setSoftKeyboardVisibility(isShowKeyboard: Boolean) {
if (isShowKeyboard) {
binding.etInput.requestFocus()
imm!!.showSoftInput(binding.etInput, 0)
} else {
imm!!.hideSoftInputFromWindow(binding.etInput.windowToken, 0)
}
}
private fun handlePanelSwitch(isEmojiPanel: Boolean) {
setSoftKeyboardVisibility(false)
binding.clBottomPanel.visibility = View.VISIBLE
binding.rvEmoji.visibility = if (isEmojiPanel) View.VISIBLE else View.INVISIBLE
binding.gvFunctionPanel.visibility = if (isEmojiPanel) View.INVISIBLE else View.VISIBLE
}
private fun hidePanel(showSoftKeyboard: Boolean) {
if (binding.clBottomPanel.isShown) {
binding.clBottomPanel.visibility = View.GONE
if (showSoftKeyboard) {
setSoftKeyboardVisibility(true)
}
}
}
private fun handleIntent(intent: Intent?) {
if (intent != null) {
if (intent.hasExtra("friendId")) {
contactId = intent.getStringExtra("friendId")
binding.tvContactName.text = contactId
}
}
}
private fun setListener() {
binding.tvContactName.setOnLongClickListener { v ->
val intent =
Intent(mContext, TestActivity::class.java)
startActivity(intent)
false
}
binding.gvFunctionPanel.selector = ColorDrawable(Color.TRANSPARENT)
binding.tvSend.setOnClickListener { v -> sendMessage() }
binding.recycleChatList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManager = checkNotNull(recyclerView.layoutManager as LinearLayoutManager?)
val firstVisiblePosition = layoutManager.findLastVisibleItemPosition()
if (tempIndex == messagesList[firstVisiblePosition].msgLocalId && isHasHistory && !isLoading) {
loadMoreMessages()
}
}
})
binding.ivBack.setOnClickListener { v -> finish() }
binding.tvContactName.setOnClickListener { v ->
val intentContactsDetail = Intent(
mContext,
ContactsDetailActivity::class.java
)
startActivity(intentContactsDetail)
}
binding.ivMore.setOnClickListener { v ->
val intentChatDetail = Intent(
mContext,
ChatDetailActivity::class.java
)
startActivity(intentChatDetail)
}
binding.etInput.addTextChangedListener(
onTextChanged = { _, _, _, _ ->
val isInputEmpty = binding.etInput.text.toString().isEmpty()
binding.ivFunctionPanel.visibility = if (!isInputEmpty) View.GONE else View.VISIBLE
binding.tvSend.visibility = if (isInputEmpty) View.GONE else View.VISIBLE
}
)
binding.etInput.setOnEditorActionListener { _, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_DONE ||
(event != null && event.keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN)
) {
sendMessage()
return@setOnEditorActionListener true
}
false
}
}
private fun setupFunctionPanel() {
strings?.add("[委屈]")
val mEmojiAdapter = EmojiAdapter(strings)
mEmojiAdapter.setOnItemClickListener(this)
binding.rvEmoji.adapter = mEmojiAdapter
val gridLayoutManager = GridLayoutManager(this, 7)
binding.rvEmoji.layoutManager = gridLayoutManager
val strings1: MutableList<String> = ArrayList()
strings1.add("haha")
strings1.add("haha")
strings1.add("haha")
strings1.add("haha")
strings1.add("haha")
val functionPanelAdapter = FunctionPanelAdapter(strings1, this)
binding.gvFunctionPanel.adapter = functionPanelAdapter
}
override fun onItemClick(position: Int) {
runOnUiThread {
val editable = binding.etInput.text
// 获取当前光标位置
val index = binding.etInput.selectionStart
// 获取将要插入的表情符号
val emoji = strings!![position]
// 使用 ImageSpanUtil 插入表情符号
insertEmoji(
mContext, editable,
binding.etInput.textSize.toInt(), emoji, index
)
// 设置新的光标位置
binding.etInput.setSelection(index + emoji.length)
}
}
private fun initView() {
setupFunctionPanel()
setRecycleView()
if (contactId != null) {
binding.tvContactName.text = contactId
}
setPanel()
}
private fun setPanel() {
val layoutParams = binding.clBottomPanel.layoutParams
layoutParams.height = softKeyboardHeight
binding.clBottomPanel.layoutParams = layoutParams
}
private fun initData() {
messagesBox = get().boxFor(Messages::class.java)
username = getUsername()
val intent = intent
friendId = intent.getStringExtra("friendId")
softKeyboardHeight = getKeyboardHeight()
}
private fun setRecycleView() {
val layoutManager = LinearLayoutManager(this)
layoutManager.reverseLayout = true
binding.recycleChatList.layoutManager = layoutManager
chatAdapter = ChatAdapter(this, messagesList)
binding.recycleChatList.adapter = chatAdapter
}
/**
* 初次进入进行加载历史数据
*/
private fun firstLoadData() {
val messages = queryData(0, limit + 1)
if (!messages.isEmpty()) {
val size = messages.size
if (size >= limit) {
isHasHistory = true
}
if (isHasHistory) {
val messages1 = messages.subList(0, limit.toInt())
messagesList.addAll(messages1)
tempIndex = messagesList[messagesList.size - 1].msgLocalId
} else {
messagesList.addAll(messages)
}
}
}
private fun loadMoreMessages() {
if (isLoading) {
return
}
isLoading = true
val newMessages = queryDataById(tempIndex, limit + 1)
val size = newMessages.size
tempIndex = newMessages[size - 1].msgLocalId
isHasHistory = size > limit
if (newMessages.isNotEmpty()) {
val messages1 = if (isHasHistory) {
newMessages.subList(1, limit.toInt() + 1).toMutableList()
} else {
newMessages.subList(1, newMessages.size).toMutableList()
}
val messagesSize = messagesList.size
messagesList.addAll(messagesSize, messages1)
chatAdapter!!.notifyItemRangeInserted(messagesSize, newMessages.size)
}
isLoading = false
}
private fun observeLiveData() {
if (webSocketService == null) {
return
}
webSocketService!!.liveData.observe(
this
) { messages: Messages ->
if ("ack" != messages.type && username != messages.senderId) {
if (messages.msgLocalId != 0L) {
return@observe
}
messagesList.addFirst(messages)
runOnUiThread {
chatAdapter!!.notifyItemInserted(0)
binding.recycleChatList.smoothScrollToPosition(0)
}
}
}
}
private fun sendMessage() {
val message = binding.etInput.text.toString().trim()
val messages = Messages(
0,
0,
message,
System.currentTimeMillis(),
"normal",
username!!,
friendId!!,
"0",
true
)
addData(messages)
val id = messagesBox.put(messages)
val jsonObject = JSONObject()
val jsonObject2 = JSONObject()
try {
jsonObject2.put("receiverId", friendId)
jsonObject2.put("content", message)
jsonObject2.put("msgLocalId", id)
jsonObject.put("type", "single")
jsonObject.put("body", jsonObject2)
} catch (e: JSONException) {
throw RuntimeException(e)
}
webSocketService!!.sendMessage(jsonObject.toString(), id)
binding.etInput.setText("")
}
private fun addData(messages: Messages) {
messagesList.addFirst(messages)
chatAdapter!!.notifyItemInserted(0)
binding.recycleChatList.smoothScrollToPosition(0)
}
private fun queryData(offset: Long, limit: Long): List<Messages> {
val query = messagesBox
.query(Messages_.takerId.equal(friendId))
.order(Messages_.timestamp, QueryBuilder.DESCENDING)
.build()
return query.find(offset, limit)
}
private fun queryDataById(msgLocalId: Long, limit: Long): List<Messages> {
val query = messagesBox
.query(Messages_.takerId.equal(friendId))
.lessOrEqual(Messages_.msgLocalId, msgLocalId)
.order(Messages_.timestamp, QueryBuilder.DESCENDING)
.build()
val offset = 0
return query.find(offset.toLong(), limit)
}
private fun bindWebSocketService() {
val serviceIntent = Intent(
this,
WebSocketService::class.java
)
bindService(serviceIntent, connection, BIND_AUTO_CREATE)
}
override fun onDestroy() {
super.onDestroy()
if (isBound) {
unbindService(connection)
isBound = false
}
}
}

View File

@ -0,0 +1,25 @@
package com.kaixed.kchat.ui.activity
import android.content.Intent
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import com.kaixed.kchat.databinding.ActivityChatDetailBinding
class ChatDetailActivity : AppCompatActivity() {
private lateinit var binding: ActivityChatDetailBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityChatDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.ifvAvatar.setOnClickListener { v ->
val intent =
Intent(this@ChatDetailActivity, ContactsDetailActivity::class.java)
intent.putExtra("friendId", "kaixed")
startActivity(intent)
}
}
}

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.os.Bundle
import androidx.activity.enableEdgeToEdge

View File

@ -0,0 +1,16 @@
package com.kaixed.kchat.ui.activity
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import com.kaixed.kchat.databinding.ActivityContactsDetailBinding
class ContactsDetailActivity : AppCompatActivity() {
private lateinit var binding: ActivityContactsDetailBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityContactsDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}

View File

@ -1,12 +1,9 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.content.Intent
import android.os.Bundle
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 com.kaixed.kchat.databinding.ActivityDataSettingBinding
class DataSettingActivity : AppCompatActivity() {

View File

@ -1,7 +1,6 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
@ -10,8 +9,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.kaixed.kchat.database.UserManager
import com.kaixed.kchat.databinding.ActivityFriendListBinding
import com.kaixed.kchat.model.friend.FriendItem
import com.kaixed.kchat.view.adapter.FriendListAdapter
import com.kaixed.kchat.viewmodel.ApplyFriendViewModel
import com.kaixed.kchat.ui.adapter.FriendListAdapter
import com.kaixed.kchat.viewmodel.FriendListViewModel
class FriendListActivity : AppCompatActivity() {

View File

@ -0,0 +1,139 @@
package com.kaixed.kchat.ui.activity
import android.content.Intent
import android.graphics.Color
import android.graphics.Rect
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.kaixed.kchat.R
import com.kaixed.kchat.databinding.ActivityLoginBinding
import com.kaixed.kchat.utils.Constants.MMKV_COMMON_DATA
import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION
import com.kaixed.kchat.utils.Constants.USER_LOGIN_STATUS
import com.kaixed.kchat.utils.DensityUtil.dpToPx
import com.kaixed.kchat.utils.DrawableUtil.createDrawable
import com.kaixed.kchat.viewmodel.LoginViewModel
import com.tencent.mmkv.MMKV
class LoginActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginBinding
private val mViewModel: LoginViewModel by viewModels()
private var mmkv: MMKV? = null
private var previousKeyboardHeight = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
mmkv = MMKV.mmkvWithID(MMKV_USER_SESSION)
if (mmkv!!.decodeBool(USER_LOGIN_STATUS)) {
navigateToMain()
}
binding.etUsername.addTextChangedListener(textWatcher)
binding.etPassword.addTextChangedListener(textWatcher)
binding.tvLogin.setOnClickListener { v ->
val username = binding.etUsername.text.toString().trim()
val password = binding.etPassword.text.toString().trim()
if (username.isEmpty() || password.isEmpty()) {
Toast.makeText(this, "请输入用户名或密码", Toast.LENGTH_SHORT).show()
}
mViewModel.login(username, password).observe(this) { loginResult ->
if (loginResult == null) {
runOnUiThread {
Toast.makeText(
this@LoginActivity,
"登录异常",
Toast.LENGTH_SHORT
)
.show()
}
} else if ("200" == loginResult.code) {
mmkv!!.encode("username", loginResult.data.username)
mmkv!!.encode("nickname", loginResult.data.nickname)
mmkv!!.encode("nickname", loginResult.data.avatarUrl)
mmkv!!.encode("userLoginStatus", true)
Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show()
navigateToMain()
}
}
}
getKeyboardHeight()
}
private fun getKeyboardHeight() {
val rootLayout = binding.root
rootLayout.viewTreeObserver.addOnGlobalLayoutListener {
val rect = Rect()
rootLayout.getWindowVisibleDisplayFrame(rect)
val screenHeight = rootLayout.rootView.height
val keypadHeight = screenHeight - rect.bottom
// 通过15%的屏幕高度差来判断是否为键盘
if (keypadHeight > (screenHeight * 0.15)) {
// 键盘弹出
if (keypadHeight != previousKeyboardHeight) {
previousKeyboardHeight = keypadHeight
val kv = MMKV.mmkvWithID(MMKV_COMMON_DATA)
kv.encode("keyboardHeight", keypadHeight)
}
}
}
}
private var textWatcher: TextWatcher = object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (s.isNotEmpty()) {
if (binding.etUsername.text.isNotEmpty() && binding.etPassword.text.isNotEmpty()) {
binding.tvLogin.setTextColor(
ContextCompat.getColor(
this@LoginActivity,
R.color.white
)
)
binding.tvLogin.background =
createDrawable(
ContextCompat.getColor(this@LoginActivity, R.color.green),
dpToPx(this@LoginActivity, 8)
)
} else {
binding.tvLogin.setTextColor(Color.parseColor("#B4B4B4"))
binding.tvLogin.background =
createDrawable(Color.parseColor("#E1E1E1"), dpToPx(this@LoginActivity, 8))
}
}
}
override fun afterTextChanged(s: Editable) {
}
}
private fun navigateToMain() {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
}

View File

@ -0,0 +1,359 @@
package com.kaixed.kchat.ui.activity
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.os.IBinder
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.kaixed.kchat.R
import com.kaixed.kchat.database.ObjectBox.get
import com.kaixed.kchat.database.entity.ChatLists
import com.kaixed.kchat.database.entity.ChatLists_
import com.kaixed.kchat.database.entity.Messages
import com.kaixed.kchat.databinding.ActivityMainBinding
import com.kaixed.kchat.model.HomeItem
import com.kaixed.kchat.service.WebSocketService
import com.kaixed.kchat.service.WebSocketService.LocalBinder
import com.kaixed.kchat.ui.adapter.ChatListAdapter
import com.kaixed.kchat.ui.adapter.MyGridAdapter
import com.kaixed.kchat.ui.i.OnChatListItemClickListener
import com.kaixed.kchat.utils.Constants.MMKV_COMMON_DATA
import com.tencent.mmkv.MMKV
import io.objectbox.Box
/**
* @Author: kaixed
* @Date: 2024/5/26 10:02
*/
class MainActivity : AppCompatActivity(), OnChatListItemClickListener {
private var binding: ActivityMainBinding? = null
private var chatLists: MutableList<ChatLists> = ArrayList()
private var chatListAdapter: ChatListAdapter? = null
private var chatListsBox: Box<ChatLists>? = null
private var messagesBox: Box<Messages>? = null
private var updateUsername: String? = null
private val items: MutableList<HomeItem> = ArrayList()
private var webSocketService: WebSocketService? = null
private var isFlipped = false
private var isBound = false
private val mContext: Context = this
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.enableEdgeToEdge()
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding!!.root)
chatListsBox = get().boxFor(ChatLists::class.java)
messagesBox = get().boxFor(Messages::class.java)
initView()
startService()
setOnClick()
}
private fun setOnClick() {
binding!!.ivMessage.setOnClickListener { v ->
val intent = Intent(
this,
MessageActivity::class.java
)
startActivity(intent)
}
binding!!.ivSearch.setOnClickListener { v ->
val intent =
Intent(mContext, SearchActivity::class.java)
startActivity(intent)
}
binding!!.ifvAvatar.setOnClickListener { v ->
val intent =
Intent(mContext, ProfileActivity::class.java)
startActivity(intent)
}
}
private fun flipImage(v: View) {
val flipAnimation = if (isFlipped) {
ObjectAnimator.ofFloat(v, "rotationX", 180f, 0f)
} else {
ObjectAnimator.ofFloat(v, "rotationX", 0f, 180f)
}
flipAnimation.setDuration(0)
flipAnimation.start()
isFlipped = !isFlipped
}
@SuppressLint("NotifyDataSetChanged")
fun notifyData() {
chatListAdapter!!.notifyDataSetChanged()
}
private fun startService() {
val intent = Intent(
this@MainActivity,
WebSocketService::class.java
)
startService(intent)
}
private fun observeLiveData() {
if (webSocketService == null) {
return
}
webSocketService!!.liveData.observe(
this
) { messages: Messages ->
if ("0" == messages.type) {
processMessage(messages)
}
}
}
private fun processMessage(messages: Messages) {
val talkerId = messages.takerId
val content = messages.content
val timestamp = messages.timestamp
val chatList = createChatList(talkerId, content, timestamp)
val index = findChatListIndex(talkerId)
if (index == -1) {
chatLists.add(chatList)
} else {
updateChatList(chatLists[index], content, timestamp)
}
runOnUiThread { this.notifyData() }
isInDb(chatList)
}
private fun createChatList(talkerId: String, content: String, timestamp: Long): ChatLists {
return ChatLists(
0L,
talkerId,
talkerId,
"1",
content,
timestamp,
1
)
}
private fun findChatListIndex(talkerId: String): Int {
for (i in chatLists.indices) {
if (chatLists[i].talkerId == talkerId) {
return i
}
}
return -1
}
private fun updateChatList(chatList: ChatLists, content: String, timestamp: Long) {
chatList.lastContent = content
chatList.timestamp = timestamp
}
private fun isInDb(chatList: ChatLists) {
// 查询数据库中是否已经存在该talkerId的记录
val existingChatList = chatListsBox
?.query(ChatLists_.talkerId.equal(chatList.talkerId))
?.build()
?.findFirst()
if (existingChatList != null) {
// 如果存在,更新内容和时间戳
existingChatList.lastContent = chatList.lastContent
existingChatList.timestamp = chatList.timestamp
chatListsBox!!.put(existingChatList)
} else {
// 如果不存在,添加新的记录
chatListsBox!!.put(chatList)
}
}
private fun updateChatLists() {
val kv = MMKV.mmkvWithID(MMKV_COMMON_DATA)
val msgLocalId = kv.getLong("msgLocalId", -1L)
if (msgLocalId != -1L) {
val messages = messagesBox!![msgLocalId]
processMessage(messages)
kv.remove("msgLocalId")
}
}
var connection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as LocalBinder
webSocketService = binder.getService()
isBound = true
observeLiveData()
}
override fun onServiceDisconnected(arg0: ComponentName) {
isBound = false
}
}
private fun initView() {
setupGridView()
setupRecycleView()
setupMoreOptionsToggle()
}
private fun setupMoreOptionsToggle() {
binding!!.ivMore.setOnClickListener { v ->
flipImage(v)
val gridViewHeight = binding!!.gridView.height
val startAt: Int
val endAt: Int
if (isFlipped) {
binding!!.gridView.visibility = View.VISIBLE
startAt = 0
endAt = gridViewHeight
} else {
startAt = gridViewHeight
endAt = 0
}
createAnimator(startAt, endAt).start()
}
}
private fun createAnimator(start: Int, end: Int): ObjectAnimator {
val animator = ObjectAnimator.ofInt(binding!!.linearlayout, "top", start, end)
val duration: Long = 500
animator.setDuration(duration)
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator) {
super.onAnimationStart(animation)
binding!!.ivMore.isEnabled = false
}
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
if (!isFlipped) {
binding!!.gridView.visibility = View.INVISIBLE
}
binding!!.ivMore.isEnabled = true
}
})
return animator
}
private fun setupRecycleView() {
if (chatListsBox!!.all != null) {
chatLists = chatListsBox!!.all
}
chatListAdapter = ChatListAdapter(chatLists, this)
binding!!.recycleChatList.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding!!.recycleChatList.adapter = chatListAdapter
chatListAdapter!!.setItemListener(this)
}
private fun setupGridView() {
initMenuData()
val myGridAdapter = MyGridAdapter(this, items)
binding!!.gridView.adapter = myGridAdapter
binding!!.gridView.selector = ColorDrawable(Color.TRANSPARENT)
binding!!.gridView.setOnItemClickListener { _, _, position, _ ->
when (position) {
0 -> {}
2 -> {
val intent =
Intent(
this@MainActivity,
AddFriendsActivity::class.java
)
startActivity(intent)
}
4 -> {
val intent2 =
Intent(
this@MainActivity,
ContactUpdatesActivity::class.java
)
startActivity(intent2)
}
6 -> {
val intent1 =
Intent(
this@MainActivity,
FriendListActivity::class.java
)
startActivity(intent1)
}
else -> finish()
}
}
}
private fun initMenuData() {
items.add(HomeItem("更换背景", true, R.drawable.ic_switch))
items.add(HomeItem("创建群聊", false, R.drawable.ic_troop))
items.add(HomeItem("新朋友", true, R.drawable.ic_friend))
items.add(HomeItem("夜间模式", true, R.drawable.ic_night))
items.add(HomeItem("好友动态", false, R.drawable.ic_qzone))
items.add(HomeItem("扫一扫", false, R.drawable.ic_scan))
items.add(HomeItem("通讯录", true, R.drawable.ic_clock_in))
items.add(HomeItem("关闭应用", false, R.drawable.ic_exit))
}
override fun onRestart() {
super.onRestart()
updateChatLists()
}
override fun onStart() {
super.onStart()
val intent = Intent(
this,
WebSocketService::class.java
)
bindService(intent, connection, BIND_AUTO_CREATE)
}
override fun onStop() {
super.onStop()
if (isBound) {
unbindService(connection)
isBound = false
}
}
override fun onDestroy() {
super.onDestroy()
val intent = Intent(
this@MainActivity,
WebSocketService::class.java
)
stopService(intent)
}
override fun onItemClick(username: String?) {
this.updateUsername = username
}
}

View File

@ -1,8 +1,6 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.app.ActivityManager
import android.os.Bundle
import android.util.Log
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
@ -10,7 +8,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.kaixed.kchat.database.UserManager
import com.kaixed.kchat.databinding.ActivityMessageBinding
import com.kaixed.kchat.model.friend.FriendRequestItem
import com.kaixed.kchat.view.adapter.MessageListAdapter
import com.kaixed.kchat.ui.adapter.MessageListAdapter
import com.kaixed.kchat.viewmodel.ContactViewModel
class MessageActivity : AppCompatActivity() {

View File

@ -1,13 +1,10 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.R
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import com.kaixed.kchat.databinding.ActivityProfileBinding
import com.kaixed.kchat.view.fragment.SettingsFragment
class ProfileActivity : AppCompatActivity() {

View File

@ -1,11 +1,8 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.os.Bundle
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 com.kaixed.kchat.databinding.ActivityProfileDetailBinding
class ProfileDetailActivity : AppCompatActivity() {

View File

@ -0,0 +1,110 @@
package com.kaixed.kchat.ui.activity
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.widget.EdgeEffect
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory
import com.kaixed.kchat.model.search.SearchItem
import com.kaixed.kchat.databinding.ActivitySearchBinding
import com.kaixed.kchat.ui.adapter.SearchResultAdapter
class SearchActivity : AppCompatActivity() {
private var binding: ActivitySearchBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.enableEdgeToEdge()
binding = ActivitySearchBinding.inflate(layoutInflater)
setContentView(binding!!.root)
setOnClickListener()
setupRecycleView()
binding!!.etSearch.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable) {
setupViewVisibility(s.length)
}
})
}
private fun setupViewVisibility(length: Int) {
binding!!.rvSearchResult.visibility = if (length > 0) View.VISIBLE else View.INVISIBLE
binding!!.tvPageSetting.visibility = if (length > 0) View.INVISIBLE else View.VISIBLE
}
private fun setupRecycleView() {
binding!!.rvSearchResult.edgeEffectFactory = object : EdgeEffectFactory() {
override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect {
return object : EdgeEffect(view.context) {
override fun onPull(deltaDistance: Float, displacement: Float) {
// 禁止下拉时的效果
}
override fun onRelease() {
// 禁止释放时的效果
}
override fun onAbsorb(velocity: Int) {
// 禁止吸收时的效果
}
}
}
}
val searchResultAdapter: SearchResultAdapter = getAdapter()
binding!!.rvSearchResult.adapter = searchResultAdapter
val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding!!.rvSearchResult.layoutManager = layoutManager
}
private fun setOnClickListener() {
binding!!.tvCancel.setOnClickListener { v ->
finish()
}
}
private fun getAdapter(): SearchResultAdapter {
val contactItem = SearchItem("sa", "kaixed", "as", false, "联系人")
val chatHistoryItem = SearchItem("sa", "kaixed", "as", false, "群聊")
val groupItem = SearchItem("sa", "kaixed", "as", false, "聊天记录")
val searchItem1 = SearchItem("sa", "kaixed", "as", true, "联系人")
val searchItem2 = SearchItem("sa", "kaixed", "as", true, "群聊")
val searchItem3 = SearchItem("sa", "kaixed", "as", true, "聊天记录")
val objects: MutableList<Any> = ArrayList(12)
objects.add("联系人")
objects.add(contactItem)
objects.add(contactItem)
objects.add(searchItem1)
objects.add("群聊")
objects.add(groupItem)
objects.add(groupItem)
objects.add(searchItem2)
objects.add("聊天记录")
objects.add(chatHistoryItem)
objects.add(chatHistoryItem)
objects.add(searchItem3)
val searchResultAdapter = SearchResultAdapter(objects)
return searchResultAdapter
}
}

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.app.Dialog
import android.content.Context

View File

@ -1,11 +1,8 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.os.Bundle
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 com.kaixed.kchat.databinding.ActivityServiceDetailBinding
class ServiceDetailActivity : AppCompatActivity() {

View File

@ -1,11 +1,8 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.os.Bundle
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 com.kaixed.kchat.databinding.ActivitySetRemarkBinding
class SetRemarkActivity : AppCompatActivity() {

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
@ -6,7 +6,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.kaixed.kchat.databinding.ActivitySettingBinding
import com.kaixed.kchat.model.setting.SettingItem
import com.kaixed.kchat.view.adapter.SettingListAdapter
import com.kaixed.kchat.ui.adapter.SettingListAdapter
class SettingActivity : AppCompatActivity() {
private lateinit var binding: ActivitySettingBinding

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.view.activity
package com.kaixed.kchat.ui.activity
import android.annotation.SuppressLint
import android.os.Bundle

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.view.adapter
package com.kaixed.kchat.ui.adapter
import android.content.Context
import android.graphics.drawable.ColorDrawable

View File

@ -0,0 +1,72 @@
package com.kaixed.kchat.ui.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.kaixed.kchat.database.entity.ChatLists
import com.kaixed.kchat.databinding.ChatMainItemBinding
import com.kaixed.kchat.ui.activity.ChatActivity
import com.kaixed.kchat.ui.i.OnChatListItemClickListener
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
/**
* @Author: kaixed
* @Date: 2024/10/27 18:22
*/
class ChatListAdapter(
private val chatLists: List<ChatLists>,
private val mContext: Context
) :
RecyclerView.Adapter<ChatListAdapter.MyViewHolder?>() {
private var onChatListItemClickListener: OnChatListItemClickListener? = null
class MyViewHolder(val binding: ChatMainItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bindData(chatList: ChatLists) {
binding.tvContent.text = chatList.lastContent
binding.tvNickname.text = chatList.talkerId
val instant = Instant.ofEpochMilli(chatList.timestamp)
// 将时间戳转换为当前系统默认时区的时间
val zdt = instant.atZone(ZoneId.systemDefault())
// 格式化成小时:分钟格式
val formatter = DateTimeFormatter.ofPattern("HH:mm")
val formattedTime = zdt.format(formatter)
binding.tvTimestamp.text = formattedTime
}
}
fun setItemListener(i: OnChatListItemClickListener?) {
this.onChatListItemClickListener = i
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding = ChatMainItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return MyViewHolder(binding)
}
override fun onBindViewHolder(
holder: MyViewHolder,
@SuppressLint("RecyclerView") position: Int
) {
holder.bindData(chatLists[position])
holder.itemView.setOnClickListener { v: View? ->
onChatListItemClickListener!!.onItemClick(chatLists[position].talkerId)
val intent =
Intent(mContext, ChatActivity::class.java)
intent.putExtra("friendId", chatLists[position].talkerId)
mContext.startActivity(intent)
}
}
override fun getItemCount(): Int = chatLists.size
}

View File

@ -0,0 +1,48 @@
package com.kaixed.kchat.ui.adapter
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.kaixed.kchat.databinding.EmojiRecycleItemBinding
import com.kaixed.kchat.ui.i.OnItemClickListener
/**
* @Author: kaixed
* @Date: 2024/10/27 18:27
*/
class EmojiAdapter(private val strings: MutableList<String>?) :
RecyclerView.Adapter<EmojiAdapter.MyViewHolder?>() {
private var onItemClickListener: OnItemClickListener? = null
fun setOnItemClickListener(onItemClickListener: OnItemClickListener?) {
this.onItemClickListener = onItemClickListener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding: EmojiRecycleItemBinding = EmojiRecycleItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return MyViewHolder(binding)
}
override fun onBindViewHolder(
holder: MyViewHolder,
@SuppressLint("RecyclerView") position: Int
) {
holder.itemView.setOnClickListener { v: View? ->
onItemClickListener!!.onItemClick(position)
}
}
override fun getItemCount(): Int = strings?.size ?: 0
class MyViewHolder(private val binding: EmojiRecycleItemBinding) :
RecyclerView.ViewHolder(binding.root)
}

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.view.adapter
package com.kaixed.kchat.ui.adapter
import android.content.Context
import android.content.Intent
@ -9,7 +9,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.bumptech.glide.Glide
import com.kaixed.kchat.databinding.FriendRecycleItemBinding
import com.kaixed.kchat.model.friend.FriendItem
import com.kaixed.kchat.view.activity.ChatActivity
import com.kaixed.kchat.ui.activity.ChatActivity
/**
* @Author: kaixed

View File

@ -0,0 +1,45 @@
package com.kaixed.kchat.ui.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import com.kaixed.kchat.databinding.FunctionGridItemBinding
/**
* @Author: kaixed
* @Date: 2024/10/27 18:29
*/
class FunctionPanelAdapter(
private val strings: List<String>?,
private val context: Context
) :
BaseAdapter() {
override fun getCount(): Int {
return strings?.size ?: 0
}
override fun getItem(position: Int): Any? {
return null
}
override fun getItemId(position: Int): Long {
return 0
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
var convertView = convertView
val binding: FunctionGridItemBinding
if (convertView == null) {
binding = FunctionGridItemBinding.inflate(LayoutInflater.from(context), parent, false)
convertView = binding.getRoot()
convertView.tag = binding
} else {
binding = convertView.tag as FunctionGridItemBinding
}
return convertView
}
}

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.view.adapter
package com.kaixed.kchat.ui.adapter
import android.content.Context
import android.content.Intent
@ -10,9 +10,8 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.bumptech.glide.Glide
import com.kaixed.kchat.databinding.FriendRecycleItemAddRequestBinding
import com.kaixed.kchat.model.friend.FriendRequestItem
import com.kaixed.kchat.view.activity.ApplyFriendsDetailActivity
import com.kaixed.kchat.view.activity.ApproveContactRequestActivity
import com.kaixed.kchat.view.activity.ApproveDetailActivity
import com.kaixed.kchat.ui.activity.ApproveContactRequestActivity
import com.kaixed.kchat.ui.activity.ApproveDetailActivity
/**
* @Author: kaixed

View File

@ -0,0 +1,51 @@
package com.kaixed.kchat.ui.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import com.kaixed.kchat.databinding.ItemGridBinding
import com.kaixed.kchat.model.HomeItem
/**
* @Author: kaixed
* @Date: 2024/10/27 18:30
*/
class MyGridAdapter(
private val context: Context,
private val items: List<HomeItem>
) : BaseAdapter() {
override fun getCount(): Int {
return items.size
}
override fun getItem(position: Int): Any {
return items[position]
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
// 使用 View Binding
val binding: ItemGridBinding = if (convertView == null) {
ItemGridBinding.inflate(LayoutInflater.from(context), parent, false)
} else {
ItemGridBinding.bind(convertView)
}
val item = items[position]
// 设置视图内容
binding.textview.text = item.name
binding.imageview.setImageResource(item.img)
// 控制更多按钮的可见性
binding.ivMore.visibility = if (item.more) View.VISIBLE else View.GONE
return binding.root
}
}

View File

@ -0,0 +1,80 @@
package com.kaixed.kchat.ui.adapter
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.NonNull
import androidx.recyclerview.widget.RecyclerView
import com.kaixed.kchat.databinding.SearchRecycleItemContactsBinding
import com.kaixed.kchat.databinding.SearchRecycleItemDetailsBinding
import com.kaixed.kchat.model.search.SearchItem
/**
* @Author: kaixed
* @Date: 2024/10/27 18:34
*/
class SearchResultAdapter(
private val displayedItems: List<Any>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == 0) {
val binding = SearchRecycleItemContactsBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
GroupViewHolder(binding)
} else {
val binding = SearchRecycleItemDetailsBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
DetailsViewHolder(binding)
}
}
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder.itemViewType) {
0 -> {
val groupViewHolder = holder as GroupViewHolder
groupViewHolder.binding.tvName.text = displayedItems[position].toString()
}
1 -> {
val detailsViewHolder = holder as DetailsViewHolder
val item = displayedItems[position] as? SearchItem
item?.let {
detailsViewHolder.binding.tvName.text = it.name
if (it.hasMore) {
detailsViewHolder.binding.tvHasMore.text = "更多${it.type}"
detailsViewHolder.binding.rlHasMore.visibility = View.VISIBLE
detailsViewHolder.binding.view.visibility = View.INVISIBLE
} else {
detailsViewHolder.binding.rlHasMore.visibility = View.GONE
detailsViewHolder.binding.view.visibility = View.VISIBLE
}
}
}
}
}
override fun getItemViewType(position: Int): Int {
return when (displayedItems[position]) {
is String -> 0
is SearchItem -> 1
else -> -1 // 处理意外情况
}
}
override fun getItemCount(): Int {
return displayedItems.size
}
// ViewHolder for Group Items
class GroupViewHolder(val binding: SearchRecycleItemContactsBinding) :
RecyclerView.ViewHolder(binding.root)
// ViewHolder for Detail Items
class DetailsViewHolder(val binding: SearchRecycleItemDetailsBinding) :
RecyclerView.ViewHolder(binding.root)
}

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.view.adapter
package com.kaixed.kchat.ui.adapter
import android.view.LayoutInflater
import android.view.View

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.view.fragment
package com.kaixed.kchat.ui.fragment
import android.graphics.Color
import android.os.Bundle

View File

@ -0,0 +1,9 @@
package com.kaixed.kchat.ui.i
/**
* @Author: kaixed
* @Date: 2024/10/27 18:35
*/
interface OnChatListItemClickListener {
fun onItemClick(username: String?)
}

View File

@ -0,0 +1,9 @@
package com.kaixed.kchat.ui.i
/**
* @Author: kaixed
* @Date: 2024/10/27 18:36
*/
interface OnItemClickListener {
fun onItemClick(position: Int)
}

View File

@ -1,18 +1,15 @@
package com.kaixed.kchat.view.widget
package com.kaixed.kchat.ui.widget
import android.content.Context
import android.content.Intent
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.Toast
import androidx.constraintlayout.widget.ConstraintLayout
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
import com.kaixed.kchat.R
import com.kaixed.kchat.databinding.ItemCustomBinding
import com.kaixed.kchat.view.activity.ProfileDetailActivity
/**
* @Author: kaixed

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.view.widget
package com.kaixed.kchat.ui.widget
import android.content.Context
import android.util.AttributeSet

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.view.widget
package com.kaixed.kchat.ui.widget
import android.content.Context
import android.util.AttributeSet

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.view.widget
package com.kaixed.kchat.ui.widget
import android.app.Activity
import android.content.Context

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.view.widget
package com.kaixed.kchat.ui.widget
import android.animation.ValueAnimator
import android.animation.ValueAnimator.AnimatorUpdateListener

View File

@ -1,557 +0,0 @@
package com.kaixed.kchat.view.activity;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.IBinder;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
import androidx.activity.EdgeToEdge;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.kaixed.kchat.database.ObjectBox;
import com.kaixed.kchat.database.UserManager;
import com.kaixed.kchat.database.entity.Messages;
import com.kaixed.kchat.database.entity.Messages_;
import com.kaixed.kchat.databinding.ActivityChatBinding;
import com.kaixed.kchat.service.WebSocketService;
import com.kaixed.kchat.utils.ConstantsUtil;
import com.kaixed.kchat.utils.ImageSpanUtil;
import com.kaixed.kchat.view.adapter.ChatAdapter;
import com.kaixed.kchat.view.adapter.EmojiAdapter;
import com.kaixed.kchat.view.adapter.FunctionPanelAdapter;
import com.kaixed.kchat.view.i.OnItemClickListener;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import io.objectbox.Box;
import io.objectbox.query.Query;
import io.objectbox.query.QueryBuilder;
/**
* @author hui
*/
public class ChatActivity extends AppCompatActivity implements OnItemClickListener {
private ChatAdapter chatAdapter;
private WebSocketService webSocketService;
private final LinkedList<Messages> messagesList = new LinkedList<>();
public static final String TAG = "ChatActivity";
private Box<Messages> messagesBox;
private String friendId;
private static final long LIMIT = 50L;
private boolean isLoading = false;
private boolean isHasHistory = false;
private boolean isBound = false;
private String username;
private long tempIndex;
private final Context mContext = this;
private List<String> strings;
private InputMethodManager imm;
private ActivityChatBinding binding;
private int softKeyboardHeight;
private String contactId;
private final ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
WebSocketService.LocalBinder binder = (WebSocketService.LocalBinder) service;
webSocketService = binder.getService();
isBound = true;
observeLiveData();
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
isBound = false;
}
};
private boolean isKeyboardShown = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
binding = ActivityChatBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
initData();
initView();
firstLoadData();
setListener();
handleIntent(getIntent());
bindWebSocketService();
imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
setPanelChange();
setBackPressListener();
getKeyBoardVisibility();
}
private void getKeyBoardVisibility() {
final View rootView = findViewById(android.R.id.content);
rootView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
Rect r = new Rect();
rootView.getWindowVisibleDisplayFrame(r);
int screenHeight = rootView.getRootView().getHeight();
int keypadHeight = screenHeight - r.bottom;
isKeyboardShown = keypadHeight > screenHeight * 0.15;
});
}
private void setBackPressListener() {
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (binding.clBottomPanel.isShown()) {
hidePanel(false);
} else {
setEnabled(false);
onBackPressed();
}
}
});
}
@SuppressLint("ClickableViewAccessibility")
private void setPanelChange() {
binding.etInput.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_UP && binding.clBottomPanel.isShown()) {
lockContentViewHeight();
hidePanel(true);
unlockContentViewHeight();
}
return false;
});
binding.ivFunctionPanel.setOnClickListener(v -> {
if (binding.clBottomPanel.isShown()) {
if (binding.rvEmoji.isShown()) {
handlePanelSwitch(false);
} else {
hideBottomPanel();
}
} else {
switchPanel(false);
}
});
binding.ivEmoji.setOnClickListener(v -> {
if (binding.clBottomPanel.isShown()) {
if (binding.gvFunctionPanel.isShown()) {
handlePanelSwitch(true);
} else {
hideBottomPanel();
}
} else {
switchPanel(true);
}
});
}
private void switchPanel(boolean isSwitchToEmoji) {
if (isKeyboardShown) {
lockContentViewHeight();
handlePanelSwitch(isSwitchToEmoji);
unlockContentViewHeight();
} else {
handlePanelSwitch(isSwitchToEmoji);
}
}
private void hideBottomPanel() {
lockContentViewHeight();
hidePanel(true);
unlockContentViewHeight();
}
private void lockContentViewHeight() {
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) binding.recycleChatList.getLayoutParams();
layoutParams.height = binding.recycleChatList.getHeight();
layoutParams.weight = 0f;
binding.recycleChatList.setLayoutParams(layoutParams);
}
private void unlockContentViewHeight() {
binding.recycleChatList.postDelayed(() -> {
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) binding.recycleChatList.getLayoutParams();
layoutParams.weight = 1f;
binding.recycleChatList.setLayoutParams(layoutParams);
}, 200);
}
private void setSoftKeyboardVisibility(boolean isShowKeyboard) {
if (isShowKeyboard) {
binding.etInput.requestFocus();
imm.showSoftInput(binding.etInput, 0);
} else {
imm.hideSoftInputFromWindow(binding.etInput.getWindowToken(), 0);
}
}
private void handlePanelSwitch(boolean isEmojiPanel) {
setSoftKeyboardVisibility(false);
binding.clBottomPanel.setVisibility(View.VISIBLE);
binding.rvEmoji.setVisibility(isEmojiPanel ? View.VISIBLE : View.INVISIBLE);
binding.gvFunctionPanel.setVisibility(isEmojiPanel ? View.INVISIBLE : View.VISIBLE);
}
private void hidePanel(boolean showSoftKeyboard) {
if (binding.clBottomPanel.isShown()) {
binding.clBottomPanel.setVisibility(View.GONE);
if (showSoftKeyboard) {
setSoftKeyboardVisibility(true);
}
}
}
private void handleIntent(Intent intent) {
if (intent != null) {
if (intent.hasExtra("friendId")) {
contactId = intent.getStringExtra("friendId");
binding.tvContactName.setText(contactId);
}
}
}
private void setListener() {
binding.tvContactName.setOnLongClickListener(v -> {
Intent intent = new Intent(mContext, TestActivity.class);
startActivity(intent);
return false;
});
binding.gvFunctionPanel.setSelector(new ColorDrawable(Color.TRANSPARENT));
binding.tvSend.setOnClickListener(v -> sendMessage());
binding.recycleChatList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
assert layoutManager != null;
int firstVisiblePosition = layoutManager.findLastVisibleItemPosition();
if (tempIndex == messagesList.get(firstVisiblePosition).getMsgLocalId() && isHasHistory && !isLoading) {
Log.d(TAG, "加载历史消息");
loadMoreMessages();
}
}
});
binding.ivBack.setOnClickListener(v -> finish());
binding.tvContactName.setOnClickListener(v -> {
Intent intentContactsDetail = new Intent(mContext, ContactsDetailActivity.class);
startActivity(intentContactsDetail);
});
binding.ivMore.setOnClickListener(v -> {
Intent intentChatDetail = new Intent(mContext, ChatDetailActivity.class);
startActivity(intentChatDetail);
});
binding.etInput.addTextChangedListener(textWatcher);
binding.etInput.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_DONE ||
(event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_DOWN)) {
sendMessage();
return true;
}
return false;
});
}
private final TextWatcher textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
boolean isInputEmpty = binding.etInput.getText().toString().isEmpty();
binding.ivFunctionPanel.setVisibility(!isInputEmpty ? View.GONE : View.VISIBLE);
binding.tvSend.setVisibility(isInputEmpty ? View.GONE : View.VISIBLE);
}
@Override
public void afterTextChanged(Editable s) {
}
};
private void setupFunctionPanel() {
strings = new ArrayList<>();
strings.add("[委屈]");
EmojiAdapter mEmojiAdapter = new EmojiAdapter(strings);
mEmojiAdapter.setOnItemClickListener(this);
binding.rvEmoji.setAdapter(mEmojiAdapter);
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 7);
binding.rvEmoji.setLayoutManager(gridLayoutManager);
List<String> strings1 = new ArrayList<>();
strings1.add("haha");
strings1.add("haha");
strings1.add("haha");
strings1.add("haha");
strings1.add("haha");
FunctionPanelAdapter functionPanelAdapter = new FunctionPanelAdapter(strings1, this);
binding.gvFunctionPanel.setAdapter(functionPanelAdapter);
}
@Override
public void onItemClick(int position) {
runOnUiThread(() -> {
Editable editable = binding.etInput.getText();
// 获取当前光标位置
int index = binding.etInput.getSelectionStart();
// 获取将要插入的表情符号
String emoji = strings.get(position);
// 使用 ImageSpanUtil 插入表情符号
ImageSpanUtil.INSTANCE.insertEmoji(mContext, editable, (int) binding.etInput.getTextSize(), emoji, index);
// 设置新的光标位置
binding.etInput.setSelection(index + emoji.length());
});
}
private void initView() {
setupFunctionPanel();
setRecycleView();
if (contactId != null) {
binding.tvContactName.setText(contactId);
}
setPanel();
}
private void setPanel() {
ViewGroup.LayoutParams layoutParams = binding.clBottomPanel.getLayoutParams();
layoutParams.height = softKeyboardHeight;
binding.clBottomPanel.setLayoutParams(layoutParams);
}
private void initData() {
messagesBox = ObjectBox.INSTANCE.get().boxFor(Messages.class);
username = UserManager.INSTANCE.getUsername();
Intent intent = getIntent();
friendId = intent.getStringExtra("friendId");
softKeyboardHeight = ConstantsUtil.Companion.getKeyboardHeight();
}
private void setRecycleView() {
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setReverseLayout(true);
binding.recycleChatList.setLayoutManager(layoutManager);
chatAdapter = new ChatAdapter(this, messagesList);
binding.recycleChatList.setAdapter(chatAdapter);
}
/**
* 初次进入进行加载历史数据
*/
private void firstLoadData() {
List<Messages> messages = queryData(0, LIMIT + 1);
if (!messages.isEmpty()) {
int size = messages.size();
if (size >= LIMIT) {
isHasHistory = true;
}
if (isHasHistory) {
List<Messages> messages1 = messages.subList(0, (int) LIMIT);
messagesList.addAll(messages1);
tempIndex = messagesList.get(messagesList.size() - 1).getMsgLocalId();
} else {
messagesList.addAll(messages);
}
}
}
private void loadMoreMessages() {
if (isLoading) {
return;
}
isLoading = true;
List<Messages> newMessages = queryDataById(tempIndex, LIMIT + 1);
int size = newMessages.size();
tempIndex = newMessages.get(size - 1).getMsgLocalId();
isHasHistory = size > LIMIT;
if (!newMessages.isEmpty()) {
List<Messages> messages;
if (isHasHistory) {
messages = newMessages.subList(1, (int) LIMIT + 1);
} else {
messages = newMessages.subList(1, newMessages.size());
}
int messagesSize = messagesList.size();
messagesList.addAll(messagesSize, messages);
chatAdapter.notifyItemRangeInserted(messagesSize, newMessages.size());
}
isLoading = false;
}
private void observeLiveData() {
if (webSocketService == null) {
return;
}
webSocketService.getLiveData().observe(this, new Observer<Messages>() {
@Override
public void onChanged(Messages messages) {
if (!"ack".equals(messages.getType()) && !username.equals(messages.getSenderId())) {
if (messages.getMsgLocalId() != 0) {
return;
}
messagesList.addFirst(messages);
runOnUiThread(() -> {
chatAdapter.notifyItemInserted(0);
binding.recycleChatList.smoothScrollToPosition(0);
});
}
}
});
}
private void sendMessage() {
String message = binding.etInput.getText().toString().trim();
Messages messages = new Messages(
0,
0,
message,
System.currentTimeMillis(),
"normal",
username,
friendId,
"0",
true
);
addData(messages);
Long id = messagesBox.put(messages);
JSONObject jsonObject = new JSONObject();
JSONObject jsonObject2 = new JSONObject();
try {
jsonObject2.put("receiverId", friendId);
jsonObject2.put("content", message);
jsonObject2.put("msgLocalId", id);
jsonObject.put("type", "single");
jsonObject.put("body", jsonObject2);
} catch (JSONException e) {
throw new RuntimeException(e);
}
webSocketService.sendMessage(jsonObject.toString(), id);
binding.etInput.setText("");
}
private void addData(Messages messages) {
messagesList.addFirst(messages);
chatAdapter.notifyItemInserted(0);
binding.recycleChatList.smoothScrollToPosition(0);
}
@NonNull
private List<Messages> queryData(long offset, long limit) {
Query<Messages> query = messagesBox
.query(Messages_.takerId.equal(friendId))
.order(Messages_.timestamp, QueryBuilder.DESCENDING)
.build();
return query.find(offset, limit);
}
@NonNull
private List<Messages> queryDataById(long msgLocalId, long limit) {
Query<Messages> query = messagesBox
.query(Messages_.takerId.equal(friendId))
.lessOrEqual(Messages_.msgLocalId, msgLocalId)
.order(Messages_.timestamp, QueryBuilder.DESCENDING)
.build();
int offset = 0;
return query.find(offset, limit);
}
private void bindWebSocketService() {
Intent serviceIntent = new Intent(this, WebSocketService.class);
bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (isBound) {
unbindService(connection);
isBound = false;
}
}
}

View File

@ -1,38 +0,0 @@
package com.kaixed.kchat.view.activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.kaixed.kchat.R;
import com.kaixed.kchat.databinding.ActivityChatDetailBinding;
public class ChatDetailActivity extends AppCompatActivity {
private ActivityChatDetailBinding binding;
private final Context mContext = this;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
binding = ActivityChatDetailBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.ifvAvatar.setOnClickListener(v -> {
Intent intent = new Intent(mContext, ContactsDetailActivity.class);
intent.putExtra("friendId", "kaixed");
startActivity(intent);
});
binding.ivBack.setOnClickListener(v -> {
finish();
});
}
}

View File

@ -1,27 +0,0 @@
package com.kaixed.kchat.view.activity;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import com.kaixed.kchat.databinding.ActivityContactsDetailBinding;
public class ContactsDetailActivity extends AppCompatActivity {
private ActivityContactsDetailBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
binding = ActivityContactsDetailBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.ivBack.setOnClickListener(v -> {
finish();
});
}
}

View File

@ -1,146 +0,0 @@
package com.kaixed.kchat.view.activity;
import static com.kaixed.kchat.utils.Constants.MMKV_COMMON_DATA;
import static com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION;
import static com.kaixed.kchat.utils.Constants.USER_LOGIN_STATUS;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.FrameLayout;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import com.kaixed.kchat.R;
import com.kaixed.kchat.databinding.ActivityLoginBinding;
import com.kaixed.kchat.utils.DensityUtil;
import com.kaixed.kchat.utils.DrawableUtil;
import com.kaixed.kchat.viewmodel.LoginViewModel;
import com.tencent.mmkv.MMKV;
/**
* @author kaixed
*/
public class LoginActivity extends AppCompatActivity {
private LoginViewModel mViewModel;
private ActivityLoginBinding binding;
private MMKV mmkv;
private final Context mContext = this;
private int previousKeyboardHeight = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
binding = ActivityLoginBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
mmkv = MMKV.mmkvWithID(MMKV_USER_SESSION);
if (mmkv.decodeBool(USER_LOGIN_STATUS)) {
navigateToMain();
}
binding.etUsername.addTextChangedListener(textWatcher);
binding.etPassword.addTextChangedListener(textWatcher);
mViewModel = new ViewModelProvider(this).get(LoginViewModel.class);
binding.tvLogin.setOnClickListener(v -> {
String username = binding.etUsername.getText().toString().trim();
String password = binding.etPassword.getText().toString().trim();
if (username.isEmpty() || password.isEmpty()) {
Toast.makeText(this, "请输入用户名或密码", Toast.LENGTH_SHORT).show();
}
mViewModel.login(username, password).observe(this, loginResult -> {
if (loginResult == null) {
runOnUiThread(() -> {
Toast.makeText(LoginActivity.this, "登录异常", Toast.LENGTH_SHORT).show();
});
} else if ("200".equals(loginResult.getCode())) {
mmkv.encode("username", loginResult.getData().getUsername());
mmkv.encode("nickname", loginResult.getData().getNickname());
mmkv.encode("nickname", loginResult.getData().getAvatarUrl());
mmkv.encode("userLoginStatus", true);
Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
navigateToMain();
}
});
});
getKeyboardHeight();
}
private void getKeyboardHeight() {
final FrameLayout rootLayout = findViewById(android.R.id.content);
rootLayout.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
Rect rect = new Rect();
rootLayout.getWindowVisibleDisplayFrame(rect);
int screenHeight = rootLayout.getRootView().getHeight();
int keypadHeight = screenHeight - rect.bottom;
// 通过15%的屏幕高度差来判断是否为键盘
if (keypadHeight > (screenHeight * 0.15)) {
// 键盘弹出
if (keypadHeight != previousKeyboardHeight) {
previousKeyboardHeight = keypadHeight;
MMKV kv = MMKV.mmkvWithID(MMKV_COMMON_DATA);
kv.encode("keyboardHeight", keypadHeight);
}
}
});
}
TextWatcher textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.length() != 0) {
if (binding.etUsername.getText().length() != 0 && binding.etPassword.getText().length() != 0) {
binding.tvLogin.setTextColor(ContextCompat.getColor(mContext, R.color.white));
binding.tvLogin.setBackground(DrawableUtil.INSTANCE.createDrawable(ContextCompat.getColor(mContext, R.color.green), DensityUtil.INSTANCE.dpToPx(mContext, 8)));
} else {
binding.tvLogin.setTextColor(Color.parseColor("#B4B4B4"));
binding.tvLogin.setBackground(DrawableUtil.INSTANCE.createDrawable(Color.parseColor("#E1E1E1"), DensityUtil.INSTANCE.dpToPx(mContext, 8)));
}
}
}
@Override
public void afterTextChanged(Editable s) {
}
};
private void navigateToMain() {
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
finish();
}
}

View File

@ -1,350 +0,0 @@
package com.kaixed.kchat.view.activity;
import static com.kaixed.kchat.utils.Constants.MMKV_COMMON_DATA;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.kaixed.kchat.R;
import com.kaixed.kchat.database.ObjectBox;
import com.kaixed.kchat.database.entity.ChatLists;
import com.kaixed.kchat.database.entity.ChatLists_;
import com.kaixed.kchat.database.entity.Messages;
import com.kaixed.kchat.databinding.ActivityMainBinding;
import com.kaixed.kchat.model.HomeItem;
import com.kaixed.kchat.service.WebSocketService;
import com.kaixed.kchat.view.adapter.ChatListAdapter;
import com.kaixed.kchat.view.adapter.MyGridAdapter;
import com.kaixed.kchat.view.i.OnChatListItemClickListener;
import com.tencent.mmkv.MMKV;
import java.util.ArrayList;
import java.util.List;
import io.objectbox.Box;
/**
* @Author: kaixed
* @Date: 2024/5/26 10:02
*/
public class MainActivity extends AppCompatActivity implements OnChatListItemClickListener {
private ActivityMainBinding binding;
private List<ChatLists> chatLists = new ArrayList<>();
private ChatListAdapter chatListAdapter;
private Box<ChatLists> chatListsBox;
private Box<Messages> messagesBox;
private String updateUsername;
private final List<HomeItem> items = new ArrayList<>();
private WebSocketService webSocketService;
private boolean isFlipped = false;
private boolean isBound = false;
private final Context mContext = this;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
chatListsBox = ObjectBox.INSTANCE.get().boxFor(ChatLists.class);
messagesBox = ObjectBox.INSTANCE.get().boxFor(Messages.class);
initView();
startService();
setOnClick();
}
private void setOnClick() {
binding.ivMessage.setOnClickListener(v -> {
Intent intent = new Intent(this, MessageActivity.class);
startActivity(intent);
});
binding.ivSearch.setOnClickListener(v -> {
Intent intent = new Intent(mContext, SearchActivity.class);
startActivity(intent);
});
binding.ifvAvatar.setOnClickListener(v -> {
Intent intent = new Intent(mContext, ProfileActivity.class);
startActivity(intent);
});
}
private void flipImage(View v) {
ObjectAnimator flipAnimation;
if (isFlipped) {
flipAnimation = ObjectAnimator.ofFloat(v, "rotationX", 180F, 0F);
} else {
flipAnimation = ObjectAnimator.ofFloat(v, "rotationX", 0F, 180F);
}
flipAnimation.setDuration(0);
flipAnimation.start();
isFlipped = !isFlipped;
}
@SuppressLint("NotifyDataSetChanged")
public void notifyData() {
chatListAdapter.notifyDataSetChanged();
}
private void startService() {
Intent intent = new Intent(MainActivity.this, WebSocketService.class);
startService(intent);
}
private void observeLiveData() {
if (webSocketService == null) {
return;
}
webSocketService.getLiveData().observe(this, messages -> {
if ("0".equals(messages.getType())) {
processMessage(messages);
}
});
}
private void processMessage(Messages messages) {
String talkerId = messages.getTakerId();
String content = messages.getContent();
Long timestamp = messages.getTimestamp();
ChatLists chatList = createChatList(talkerId, content, timestamp);
int index = findChatListIndex(talkerId);
if (index == -1) {
chatLists.add(chatList);
} else {
updateChatList(chatLists.get(index), content, timestamp);
}
runOnUiThread(this::notifyData);
isInDb(chatList);
}
private ChatLists createChatList(String talkerId, String content, Long timestamp) {
return new ChatLists(
0L,
talkerId,
talkerId,
"1",
content,
timestamp,
1
);
}
private int findChatListIndex(String talkerId) {
for (int i = 0; i < chatLists.size(); i++) {
if (chatLists.get(i).getTalkerId().equals(talkerId)) {
return i;
}
}
return -1;
}
private void updateChatList(ChatLists chatList, String content, Long timestamp) {
chatList.setLastContent(content);
chatList.setTimestamp(timestamp);
}
private void isInDb(ChatLists chatList) {
// 查询数据库中是否已经存在该talkerId的记录
ChatLists existingChatList = chatListsBox
.query(ChatLists_.talkerId.equal(chatList.getTalkerId()))
.build()
.findFirst();
if (existingChatList != null) {
// 如果存在更新内容和时间戳
existingChatList.setLastContent(chatList.getLastContent());
existingChatList.setTimestamp(chatList.getTimestamp());
chatListsBox.put(existingChatList);
} else {
// 如果不存在添加新的记录
chatListsBox.put(chatList);
}
}
private void updateChatLists() {
MMKV kv = MMKV.mmkvWithID(MMKV_COMMON_DATA);
long msgLocalId = kv.getLong("msgLocalId", -1L);
if (msgLocalId != -1) {
Messages messages = messagesBox.get(msgLocalId);
processMessage(messages);
kv.remove("msgLocalId");
}
}
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
WebSocketService.LocalBinder binder = (WebSocketService.LocalBinder) service;
webSocketService = binder.getService();
isBound = true;
observeLiveData();
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
isBound = false;
}
};
private void initView() {
setupGridView();
setupRecycleView();
setupMoreOptionsToggle();
}
private void setupMoreOptionsToggle() {
binding.ivMore.setOnClickListener(v -> {
flipImage(v);
int gridViewHeight = binding.gridView.getHeight();
int startAt;
int endAt;
if (isFlipped) {
binding.gridView.setVisibility(View.VISIBLE);
startAt = 0;
endAt = gridViewHeight;
} else {
startAt = gridViewHeight;
endAt = 0;
}
createAnimator(startAt, endAt).start();
});
}
private ObjectAnimator createAnimator(int start, int end) {
ObjectAnimator animator = ObjectAnimator.ofInt(binding.linearlayout, "top", start, end);
long duration = 500;
animator.setDuration(duration);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
binding.ivMore.setEnabled(false);
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (!isFlipped) {
binding.gridView.setVisibility(View.INVISIBLE);
}
binding.ivMore.setEnabled(true);
}
});
return animator;
}
private void setupRecycleView() {
if (chatListsBox.getAll() != null) {
chatLists = chatListsBox.getAll();
}
chatListAdapter = new ChatListAdapter(chatLists, this);
binding.recycleChatList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
binding.recycleChatList.setAdapter(chatListAdapter);
chatListAdapter.setItemListener(this);
}
private void setupGridView() {
initMenuData();
MyGridAdapter myGridAdapter = new MyGridAdapter(this, items);
binding.gridView.setAdapter(myGridAdapter);
binding.gridView.setSelector(new ColorDrawable(Color.TRANSPARENT));
binding.gridView.setOnItemClickListener((parent, view, position, id) -> {
switch (position) {
case 0:
break;
case 2:
Intent intent = new Intent(MainActivity.this, AddFriendsActivity.class);
startActivity(intent);
break;
case 4:
Intent intent2 = new Intent(MainActivity.this, ContactUpdatesActivity.class);
startActivity(intent2);
break;
case 6:
Intent intent1 = new Intent(MainActivity.this, FriendListActivity.class);
startActivity(intent1);
break;
default:
finish();
break;
}
});
}
private void initMenuData() {
items.add(new HomeItem("更换背景", true, R.drawable.ic_switch));
items.add(new HomeItem("创建群聊", false, R.drawable.ic_troop));
items.add(new HomeItem("新朋友", true, R.drawable.ic_friend));
items.add(new HomeItem("夜间模式", true, R.drawable.ic_night));
items.add(new HomeItem("好友动态", false, R.drawable.ic_qzone));
items.add(new HomeItem("扫一扫", false, R.drawable.ic_scan));
items.add(new HomeItem("通讯录", true, R.drawable.ic_clock_in));
items.add(new HomeItem("关闭应用", false, R.drawable.ic_exit));
}
@Override
public void onItemClick(String username) {
this.updateUsername = username;
}
@Override
protected void onRestart() {
super.onRestart();
updateChatLists();
}
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, WebSocketService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
if (isBound) {
unbindService(connection);
isBound = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
Intent intent = new Intent(MainActivity.this, WebSocketService.class);
stopService(intent);
}
}

View File

@ -1,28 +0,0 @@
package com.kaixed.kchat.view.activity
import android.os.Bundle
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 com.kaixed.kchat.databinding.ActivityMain2Binding
class MainActivity2 : AppCompatActivity() {
private lateinit var binding:ActivityMain2Binding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityMain2Binding.inflate(layoutInflater)
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
binding.btn.setOnClickListener {
finish()
}
}
}

View File

@ -1,133 +0,0 @@
package com.kaixed.kchat.view.activity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.EdgeEffect;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.kaixed.kchat.databinding.ActivitySearchBinding;
import com.kaixed.kchat.model.search.SearchItem;
import com.kaixed.kchat.view.adapter.SearchResultAdapter;
import java.util.ArrayList;
import java.util.List;
public class SearchActivity extends AppCompatActivity {
private ActivitySearchBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
binding = ActivitySearchBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setOnClickListener();
setupRecycleView();
binding.etSearch.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
setupViewVisibility(s.length());
}
});
}
private void setupViewVisibility(int length) {
binding.rvSearchResult.setVisibility(length > 0 ? View.VISIBLE : View.INVISIBLE);
binding.tvPageSetting.setVisibility(length > 0 ? View.INVISIBLE : View.VISIBLE);
}
private void setupRecycleView() {
binding.rvSearchResult.setEdgeEffectFactory(new RecyclerView.EdgeEffectFactory() {
@NonNull
@Override
protected EdgeEffect createEdgeEffect(@NonNull RecyclerView view, int direction) {
return new EdgeEffect(view.getContext()) {
@Override
public void onPull(float deltaDistance, float displacement) {
// 禁止下拉时的效果
}
@Override
public void onRelease() {
// 禁止释放时的效果
}
@Override
public void onAbsorb(int velocity) {
// 禁止吸收时的效果
}
};
}
});
SearchResultAdapter searchResultAdapter = getSearchResultAdapter();
binding.rvSearchResult.setAdapter(searchResultAdapter);
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
binding.rvSearchResult.setLayoutManager(layoutManager);
}
private void setOnClickListener() {
binding.tvCancel.setOnClickListener(v -> {
finish();
});
}
private static @NonNull SearchResultAdapter getSearchResultAdapter() {
SearchItem contactItem = new SearchItem("sa", "kaixed", "as", false, "联系人");
SearchItem chatHistoryItem = new SearchItem("sa", "kaixed", "as", false, "群聊");
SearchItem groupItem = new SearchItem("sa", "kaixed", "as", false, "聊天记录");
SearchItem searchItem1 = new SearchItem("sa", "kaixed", "as", true, "联系人");
SearchItem searchItem2 = new SearchItem("sa", "kaixed", "as", true, "群聊");
SearchItem searchItem3 = new SearchItem("sa", "kaixed", "as", true, "聊天记录");
List<Object> objects = new ArrayList<>(12);
objects.add("联系人");
objects.add(contactItem);
objects.add(contactItem);
objects.add(searchItem1);
objects.add("群聊");
objects.add(groupItem);
objects.add(groupItem);
objects.add(searchItem2);
objects.add("聊天记录");
objects.add(chatHistoryItem);
objects.add(chatHistoryItem);
objects.add(searchItem3);
SearchResultAdapter searchResultAdapter = new SearchResultAdapter(objects);
return searchResultAdapter;
}
}

View File

@ -1,95 +0,0 @@
package com.kaixed.kchat.view.adapter;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.kaixed.kchat.R;
import com.kaixed.kchat.database.entity.ChatLists;
import com.kaixed.kchat.view.activity.ChatActivity;
import com.kaixed.kchat.view.i.OnChatListItemClickListener;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* @Author: kaixed
* @Date: 2024/5/20 10:13
*/
public class ChatListAdapter extends RecyclerView.Adapter<ChatListAdapter.MyViewHolder> {
private final List<ChatLists> chatLists;
private final Context mContext;
private OnChatListItemClickListener onChatListItemClickListener;
public void setItemListener(OnChatListItemClickListener i) {
this.onChatListItemClickListener = i;
}
public ChatListAdapter(List<ChatLists> chatLists, Context mContext) {
this.chatLists = chatLists;
this.mContext = mContext;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.chat_main_item, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, @SuppressLint("RecyclerView") int position) {
holder.bindData(chatLists.get(position));
holder.itemView.setOnClickListener(v -> {
onChatListItemClickListener.onItemClick(chatLists.get(position).getTalkerId());
Intent intent = new Intent(mContext, ChatActivity.class);
intent.putExtra("friendId", chatLists.get(position).getTalkerId());
mContext.startActivity(intent);
});
}
@Override
public int getItemCount() {
return chatLists.isEmpty() ? 0 : chatLists.size();
}
public static class MyViewHolder extends RecyclerView.ViewHolder {
private final TextView mTvContent;
private final TextView mTvNickname;
private final TextView mTvTimestamp;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
mTvContent = itemView.findViewById(R.id.tv_content);
mTvNickname = itemView.findViewById(R.id.tv_nickname);
mTvTimestamp = itemView.findViewById(R.id.tv_timestamp);
}
private void bindData(ChatLists chatList) {
mTvContent.setText(chatList.getLastContent());
mTvNickname.setText(chatList.getTalkerId());
Instant instant = Instant.ofEpochMilli(chatList.getTimestamp());
// 将时间戳转换为当前系统默认时区的时间
ZonedDateTime zdt = instant.atZone(ZoneId.systemDefault());
// 格式化成小时分钟格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
String formattedTime = zdt.format(formatter);
mTvTimestamp.setText(formattedTime);
}
}
}

View File

@ -1,37 +0,0 @@
package com.kaixed.kchat.view.adapter;
import android.content.Context;
import android.view.View;
import androidx.recyclerview.widget.LinearLayoutManager;
public class CustomLayoutManager extends LinearLayoutManager {
public CustomLayoutManager(Context context) {
super(context);
}
@Override
public void scrollToPositionWithOffset(int position, int offset) {
if (getChildCount() == 0 || getItemCount() == 0) {
super.scrollToPositionWithOffset(position, offset);
return;
}
if (position < getItemCount()) {
View firstVisibleView = getChildAt(0);
int firstItemPosition = getPosition(firstVisibleView);
int lastItemPosition = getPosition(getChildAt(getChildCount() - 1));
if (lastItemPosition - firstItemPosition + 1 < getChildCount()) {
// 如果列表不满一屏则将列表置于顶部
super.scrollToPositionWithOffset(position, 0);
} else {
// 如果列表满一屏则将最新的消息显示在底部
super.scrollToPositionWithOffset(position, Integer.MAX_VALUE);
}
} else {
super.scrollToPositionWithOffset(position, offset);
}
}
}

View File

@ -1,61 +0,0 @@
package com.kaixed.kchat.view.adapter;
import android.annotation.SuppressLint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.constraintlayout.helper.widget.Layer;
import androidx.recyclerview.widget.RecyclerView;
import com.kaixed.kchat.R;
import com.kaixed.kchat.view.i.OnItemClickListener;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: kaixed
* @Date: 2024/5/30 16:51
*/
public class EmojiAdapter extends RecyclerView.Adapter<EmojiAdapter.MyViewHolder> {
private final List<String> strings;
private OnItemClickListener onItemClickListener;
public EmojiAdapter(List<String> strings) {
this.strings = strings;
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.emoji_recycle_item, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, @SuppressLint("RecyclerView") int position) {
holder.itemView.setOnClickListener(v -> {
onItemClickListener.onItemClick(position);
});
}
@Override
public int getItemCount() {
return strings.isEmpty() ? 0 : strings.size();
}
public static class MyViewHolder extends RecyclerView.ViewHolder {
public MyViewHolder(@NonNull View itemView) {
super(itemView);
}
}
}

View File

@ -1,57 +0,0 @@
package com.kaixed.kchat.view.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import com.kaixed.kchat.databinding.FunctionGridItemBinding;
import java.util.List;
/**
* @Author: kaixed
* @Date: 2024/8/16 20:53
*/
public class FunctionPanelAdapter extends BaseAdapter {
private List<String> strings;
private final Context context;
public FunctionPanelAdapter(List<String> strings, Context context) {
this.strings = strings;
this.context = context;
}
@Override
public int getCount() {
return strings == null ? 0 : strings.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
FunctionGridItemBinding binding;
if (convertView == null) {
binding = FunctionGridItemBinding.inflate(LayoutInflater.from(context), parent, false);
convertView = binding.getRoot();
convertView.setTag(binding);
} else {
binding = (FunctionGridItemBinding) convertView.getTag();
}
return convertView;
}
}

View File

@ -1,67 +0,0 @@
package com.kaixed.kchat.view.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.kaixed.kchat.R;
import com.kaixed.kchat.model.HomeItem;
import java.util.List;
/**
* @author kaixed
*/
public class MyGridAdapter extends BaseAdapter {
private Context context;
private List<HomeItem> items;
public MyGridAdapter(Context context, List<HomeItem> items) {
this.context = context;
this.items = items;
}
@Override
public int getCount() {
return items.size();
}
@Override
public Object getItem(int position) {
return items.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.item_grid, parent, false);
}
HomeItem item = items.get(position);
TextView title = convertView.findViewById(R.id.textview);
ImageView image = convertView.findViewById(R.id.imageview);
ImageView imageView = convertView.findViewById(R.id.iv_more);
if (item.getMore()) {
imageView.setVisibility(View.VISIBLE);
} else {
imageView.setVisibility(View.GONE);
}
title.setText(item.getName());
image.setImageResource(item.getImg());
return convertView;
}
}

View File

@ -1,101 +0,0 @@
package com.kaixed.kchat.view.adapter;
import android.annotation.SuppressLint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.kaixed.kchat.databinding.SearchRecycleItemContactsBinding;
import com.kaixed.kchat.databinding.SearchRecycleItemDetailsBinding;
import com.kaixed.kchat.model.search.SearchItem;
import java.util.List;
/**
* @Author: kaixed
* @Date: 2024/8/12 20:25
*/
public class SearchResultAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final List<Object> displayedItems;
public SearchResultAdapter(List<Object> objects) {
this.displayedItems = objects;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == 0) {
SearchRecycleItemContactsBinding binding = SearchRecycleItemContactsBinding.inflate(
LayoutInflater.from(parent.getContext()), parent, false);
return new GroupViewHolder(binding);
} else {
SearchRecycleItemDetailsBinding binding = SearchRecycleItemDetailsBinding.inflate(
LayoutInflater.from(parent.getContext()), parent, false);
return new DetailsViewHolder(binding);
}
}
@SuppressLint("SetTextI18n")
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == 0) {
GroupViewHolder groupViewHolder = (GroupViewHolder) holder;
groupViewHolder.binding.tvName.setText(String.valueOf(displayedItems.get(position)));
} else {
DetailsViewHolder detailsViewHolder = (DetailsViewHolder) holder;
if (displayedItems.get(position) instanceof SearchItem item) {
detailsViewHolder.binding.tvName.setText((item.getName()));
if ((item.getHasMore())) {
detailsViewHolder.binding.tvHasMore.setText("更多" + item.getType());
detailsViewHolder.binding.rlHasMore.setVisibility(View.VISIBLE);
detailsViewHolder.binding.view.setVisibility(View.INVISIBLE);
} else {
detailsViewHolder.binding.rlHasMore.setVisibility(View.GONE);
detailsViewHolder.binding.view.setVisibility(View.VISIBLE);
}
}
}
}
@Override
public int getItemViewType(int position) {
if (displayedItems.get(position) instanceof String) {
return 0;
} else {
return 1;
}
}
@Override
public int getItemCount() {
return displayedItems == null ? 0 : displayedItems.size();
}
// ViewHolder for Group Items
static class GroupViewHolder extends RecyclerView.ViewHolder {
private final SearchRecycleItemContactsBinding binding;
public GroupViewHolder(SearchRecycleItemContactsBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
// ViewHolder for Detail Items
static class DetailsViewHolder extends RecyclerView.ViewHolder {
private final SearchRecycleItemDetailsBinding binding;
public DetailsViewHolder(SearchRecycleItemDetailsBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
}

View File

@ -1,9 +0,0 @@
package com.kaixed.kchat.view.i;
/**
* @Author: kaixed
* @Date: 2024/6/16 19:28
*/
public interface OnChatListItemClickListener {
void onItemClick(String username);
}

View File

@ -1,8 +0,0 @@
package com.kaixed.kchat.view.i;
/**
* @author hui
*/
public interface OnItemClickListener {
void onItemClick(int position);
}

View File

@ -7,7 +7,7 @@
android:layout_height="match_parent"
android:background="#e5e5e5"
android:fitsSystemWindows="true"
tools:context=".view.activity.AddFriendsActivity">
tools:context=".ui.activity.AddFriendsActivity">
<ImageView
android:id="@+id/iv_back"

View File

@ -8,7 +8,7 @@
android:background="@color/white"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".view.activity.ApplyAddFriendActivity">
tools:context=".ui.activity.ApplyAddFriendActivity">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
@ -20,7 +20,7 @@
android:layout_height="match_parent"
android:orientation="vertical">
<com.kaixed.kchat.view.widget.CustomTitleBar
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleName="申请添加朋友" />

View File

@ -6,9 +6,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".view.activity.ApplyFriendsDetailActivity">
tools:context=".ui.activity.ApplyFriendsDetailActivity">
<com.kaixed.kchat.view.widget.CustomTitleBar
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:id="@+id/ctl"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -8,7 +8,7 @@
android:background="@color/white"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".view.activity.ApproveContactRequestActivity">
tools:context=".ui.activity.ApproveContactRequestActivity">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
@ -20,7 +20,7 @@
android:layout_height="match_parent"
android:orientation="vertical">
<com.kaixed.kchat.view.widget.CustomTitleBar
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleName="通过朋友验证" />

View File

@ -6,9 +6,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".view.activity.ApproveDetailActivity">
tools:context=".ui.activity.ApproveDetailActivity">
<com.kaixed.kchat.view.widget.CustomTitleBar
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:id="@+id/ctl"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -6,7 +6,14 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".view.activity.ChatDetailActivity">
tools:context=".ui.activity.ChatDetailActivity">
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:id="@+id/ctb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:titleName="聊天信息" />
<ImageView
android:layout_width="match_parent"
@ -18,28 +25,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_back"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_marginStart="15dp"
android:layout_marginTop="15dp"
android:src="@drawable/ic_left_arrow"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="7dp"
android:text="聊天信息"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@id/iv_back"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/iv_back" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv_avatar"
@ -49,7 +34,7 @@
android:layout_marginTop="15dp"
android:src="@drawable/ic_avatar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_back"
app:layout_constraintTop_toBottomOf="@id/ctb"
app:round="8dp" />
<TextView

View File

@ -6,7 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".view.activity.ContactUpdatesActivity">
tools:context=".ui.activity.ContactUpdatesActivity">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv_avatar"

View File

@ -6,38 +6,15 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".view.activity.ContactsDetailActivity">
tools:context=".ui.activity.ContactsDetailActivity">
<!-- <com.kaixed.kchat.utils.CustomToolbarView-->
<!-- app:customTitle="哈哈"-->
<!-- android:id="@+id/custom_toolbar"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:background="?attr/colorPrimary"-->
<!-- tools:ignore="MissingConstraints"-->
<!-- app:layout_constraintStart_toStartOf="parent"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- />-->
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:id="@+id/ctb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:titleIcon="@drawable/ic_setting" />
<ImageView
android:id="@+id/iv_back"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_marginStart="15dp"
android:layout_marginTop="15dp"
android:src="@drawable/ic_left_arrow"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_setting"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_marginEnd="15dp"
android:src="@drawable/ic_setting"
app:layout_constraintBottom_toBottomOf="@id/iv_back"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/iv_back" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv_avatar"
@ -47,7 +24,7 @@
android:layout_marginTop="15dp"
android:src="@drawable/ic_avatar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_back"
app:layout_constraintTop_toBottomOf="@id/ctb"
app:round="8dp" />
<TextView

View File

@ -7,7 +7,7 @@
android:layout_height="match_parent"
android:background="#E5E5E5"
android:fitsSystemWindows="true"
tools:context=".view.activity.DataSettingActivity">
tools:context=".ui.activity.DataSettingActivity">
<ImageView
android:id="@+id/iv_back"

View File

@ -7,9 +7,9 @@
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".view.activity.FriendListActivity">
tools:context=".ui.activity.FriendListActivity">
<com.kaixed.kchat.view.widget.CustomTitleBar
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:id="@+id/ctb"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -7,7 +7,7 @@
android:layout_height="match_parent"
android:background="@color/white"
android:fitsSystemWindows="true"
tools:context=".view.activity.LoginActivity">
tools:context=".ui.activity.LoginActivity">
<TextView
android:id="@+id/tv_title"

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.activity.MainActivity2">
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -7,9 +7,9 @@
android:layout_height="match_parent"
android:background="@color/gray"
android:fitsSystemWindows="true"
tools:context=".view.activity.MessageActivity">
tools:context=".ui.activity.MessageActivity">
<com.kaixed.kchat.view.widget.CustomTitleBar
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:id="@+id/ctl"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -6,7 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".view.activity.ProfileActivity">
tools:context=".ui.activity.ProfileActivity">
<FrameLayout
android:id="@+id/fragment_container"

View File

@ -8,9 +8,9 @@
android:background="@color/gray"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".view.activity.ProfileDetailActivity">
tools:context=".ui.activity.ProfileDetailActivity">
<com.kaixed.kchat.view.widget.CustomTitleBar
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleName="个人信息" />
@ -24,7 +24,7 @@
android:layout_height="match_parent"
android:orientation="vertical">
<com.kaixed.kchat.view.widget.CustomItem
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/ci_avatar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -33,7 +33,7 @@
app:itemIcon="@drawable/ic_avatar"
app:itemName="头像" />
<com.kaixed.kchat.view.widget.CustomItem
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/ci_nickname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -41,21 +41,21 @@
app:itemDesc="糕菜菜"
app:itemName="名字" />
<com.kaixed.kchat.view.widget.CustomItem
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:isTopDividerVisible="true"
app:itemDesc="的八块腹肌,说你好棒"
app:itemName="拍一拍" />
<com.kaixed.kchat.view.widget.CustomItem
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:isTopDividerVisible="true"
app:itemDesc="kaixed"
app:itemName="微信号" />
<com.kaixed.kchat.view.widget.CustomItem
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:iconSize="10dp"
@ -63,26 +63,26 @@
app:itemIcon="@drawable/icon_qrcode"
app:itemName="二维码名片" />
<com.kaixed.kchat.view.widget.CustomItem
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:isTopDividerVisible="true"
app:itemName="更多信息" />
<com.kaixed.kchat.view.widget.CustomItem
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:itemDesc="白月光与朱砂痣"
app:itemName="来电铃声" />
<com.kaixed.kchat.view.widget.CustomItem
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:itemName="蔬菜豆" />
<com.kaixed.kchat.view.widget.CustomItem
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"

View File

@ -7,7 +7,7 @@
android:layout_height="match_parent"
android:background="#e5e5e5"
android:fitsSystemWindows="true"
tools:context=".view.activity.ProfileActivity">
tools:context=".ui.activity.ProfileActivity">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv_avatar"

View File

@ -7,7 +7,7 @@
android:layout_height="match_parent"
android:background="#ededed"
android:fitsSystemWindows="true"
tools:context=".view.activity.SearchActivity">
tools:context=".ui.activity.SearchActivity">
<androidx.cardview.widget.CardView
android:id="@+id/cv_search"

View File

@ -7,7 +7,7 @@
android:layout_height="match_parent"
android:background="#ededed"
android:fitsSystemWindows="true"
tools:context=".view.activity.SearchActivity">
tools:context=".ui.activity.SearchActivity">
<androidx.cardview.widget.CardView
android:id="@+id/cv_search"

View File

@ -7,9 +7,9 @@
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".view.activity.ServiceDetailActivity">
tools:context=".ui.activity.ServiceDetailActivity">
<com.kaixed.kchat.view.widget.CustomTitleBar
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"

View File

@ -6,7 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".view.activity.SetRemarkActivity">
tools:context=".ui.activity.SetRemarkActivity">
<ImageView
android:id="@+id/iv_back"

View File

@ -7,7 +7,7 @@
android:layout_height="match_parent"
android:background="@color/gray"
android:fitsSystemWindows="true"
tools:context=".view.activity.SettingActivity">
tools:context=".ui.activity.SettingActivity">
<ImageView
android:id="@+id/iv_back"