feat: 新增删除好友功能

1.新增删除好友功能
2.更新CustomItem显示样式
This commit is contained in:
糕小菜 2024-11-21 23:09:39 +08:00
parent 92e622792a
commit a07711d993
50 changed files with 1194 additions and 636 deletions

View File

@ -4,10 +4,10 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2024-11-13T03:55:58.129272100Z">
<DropdownSelection timestamp="2024-11-19T05:46:34.362744600Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="Default" identifier="serial=10.160.86.233:5555;connection=a4032080" />
<DeviceId pluginId="Default" identifier="serial=10.127.185.244:5555;connection=5b2cc3eb" />
</handle>
</Target>
</DropdownSelection>

View File

@ -172,7 +172,7 @@
},
{
"id": "6:411582187056789368",
"lastPropertyId": "7:1280810032716163450",
"lastPropertyId": "8:2020630799900991467",
"name": "Conversation",
"properties": [
{
@ -210,6 +210,11 @@
"id": "7:1280810032716163450",
"name": "unreadCount",
"type": 5
},
{
"id": "8:2020630799900991467",
"name": "show",
"type": 1
}
],
"relations": []

View File

@ -57,50 +57,6 @@
],
"relations": []
},
{
"id": "2:6854189850259048168",
"lastPropertyId": "10:8705063061921345729",
"name": "ChatLists",
"properties": [
{
"id": "1:5279270693453549140",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "3:7775198743178107108",
"name": "nickname",
"type": 9
},
{
"id": "4:7079852095664590440",
"name": "avatarUrl",
"type": 9
},
{
"id": "5:3457793996580594247",
"name": "lastContent",
"type": 9
},
{
"id": "6:6315401035981995789",
"name": "timestamp",
"type": 6
},
{
"id": "9:2123413060720974577",
"name": "talkerId",
"type": 9
},
{
"id": "10:8705063061921345729",
"name": "unreadCount",
"type": 5
}
],
"relations": []
},
{
"id": "4:6179749773128044271",
"lastPropertyId": "13:6446821128426983596",
@ -213,16 +169,61 @@
}
],
"relations": []
},
{
"id": "6:411582187056789368",
"lastPropertyId": "7:1280810032716163450",
"name": "Conversation",
"properties": [
{
"id": "1:8070670082991740155",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:62500236312534806",
"name": "talkerId",
"type": 9
},
{
"id": "3:1809723298214930621",
"name": "nickname",
"type": 9
},
{
"id": "4:3806556063168110787",
"name": "avatarUrl",
"type": 9
},
{
"id": "5:184921755262649486",
"name": "lastContent",
"type": 9
},
{
"id": "6:2348518020158588886",
"name": "timestamp",
"type": 6
},
{
"id": "7:1280810032716163450",
"name": "unreadCount",
"type": 5
}
],
"relations": []
}
],
"lastEntityId": "5:2885532406154205395",
"lastEntityId": "6:411582187056789368",
"lastIndexId": "0:0",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
"modelVersion": 5,
"modelVersionParserMinimum": 5,
"retiredEntityUids": [
4156940900268054047
4156940900268054047,
6854189850259048168
],
"retiredIndexUids": [],
"retiredPropertyUids": [
@ -238,7 +239,14 @@
1118829668259721786,
1490263641107250384,
2701112806559265255,
7345071619836824250
7345071619836824250,
5279270693453549140,
7775198743178107108,
7079852095664590440,
3457793996580594247,
6315401035981995789,
2123413060720974577,
8705063061921345729
],
"retiredRelationUids": [],
"version": 1

View File

@ -2,10 +2,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
@ -14,7 +18,6 @@
android:maxSdkVersion="32"
tools:ignore="ScopedStorage" />
<application
android:name=".KchatApplication"
android:allowBackup="true"
@ -29,6 +32,12 @@
android:theme="@style/Theme.KChatAndroid"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".ui.activity.SetRemarkAndLabelActivity"
android:exported="false" />
<activity
android:name=".ui.activity.ContactRequestListActivity"
android:exported="false" />
<activity
android:name=".ui.activity.RenameActivity"
android:exported="false" />

View File

@ -18,4 +18,5 @@ data class Conversation(
var lastContent: String,
var timestamp: Long,
var unreadCount: Int = 0,
var show: Boolean = true
)

View File

@ -1,10 +1,11 @@
package com.kaixed.kchat.model.friend
import com.kaixed.kchat.data.objectbox.entity.Contact
import kotlinx.serialization.Serializable
@Serializable
data class AcceptContactRequest(
val code: String,
val `data`: String,
val msg: String
val msg: String,
val `data`: Contact?
)

View File

@ -5,6 +5,6 @@ import kotlinx.serialization.Serializable
@Serializable
data class ContactRequestResponse(
val code: String,
val msg: String,
val `data`: List<FriendRequestItem>,
val msg: String
)

View File

@ -0,0 +1,7 @@
package com.kaixed.kchat.model.response.friend
data class DeleteContact(
val code: String,
val `data`: String?,
val msg: String
)

View File

@ -3,6 +3,7 @@ package com.kaixed.kchat.network.service
import com.kaixed.kchat.model.friend.AcceptContactRequest
import com.kaixed.kchat.model.friend.ContactRequestResponse
import com.kaixed.kchat.model.response.ApplyFriend
import com.kaixed.kchat.model.response.friend.DeleteContact
import com.kaixed.kchat.model.response.friend.FriendList
import com.kaixed.kchat.model.response.friend.SearchFriends
import retrofit2.Response
@ -26,7 +27,8 @@ interface ContactService {
@POST("friend/accept")
suspend fun acceptContactRequest(
@Field("requestId") contactId: String,
@Field("receiverId") username: String
@Field("receiverId") username: String,
@Field("remark") remark: String
): Response<AcceptContactRequest>
// 添加联系人
@ -50,4 +52,12 @@ interface ContactService {
suspend fun getContactList(
@Field("userId") username: String
): Response<FriendList>
// 删除好友
@FormUrlEncoded
@POST("friend/delete")
suspend fun deleteContact(
@Field("userId") username: String,
@Field("contactId") contactId: String,
): Response<DeleteContact>
}

View File

@ -2,6 +2,7 @@ package com.kaixed.kchat.network.service
import com.kaixed.kchat.model.request.RegisterRequest
import com.kaixed.kchat.model.request.UserRequest
import com.kaixed.kchat.model.response.friend.SearchFriends
import com.kaixed.kchat.model.response.login.Login
import com.kaixed.kchat.model.response.register.Register
import com.kaixed.kchat.model.response.search.UserList
@ -52,6 +53,12 @@ interface UserApiService {
@Path("username") username: String
): Response<UserList>
// 获取用户信息
@GET("users/{username}")
suspend fun getUserInfo(
@Path("username") username: String
): Response<SearchFriends>
// 更改昵称接口
@POST("users/info")
suspend fun changeNickname(

View File

@ -1,14 +1,18 @@
package com.kaixed.kchat.repository
import android.util.Log
import com.kaixed.kchat.data.objectbox.ObjectBox.get
import com.kaixed.kchat.data.objectbox.entity.Contact
import com.kaixed.kchat.data.objectbox.entity.Contact_
import com.kaixed.kchat.model.friend.AcceptContactRequest
import com.kaixed.kchat.model.friend.ContactRequestResponse
import com.kaixed.kchat.model.response.ApplyFriend
import com.kaixed.kchat.model.response.friend.DeleteContact
import com.kaixed.kchat.model.response.friend.FriendList
import com.kaixed.kchat.model.response.friend.SearchFriends
import com.kaixed.kchat.network.RetrofitClient
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import io.objectbox.Box
import okhttp3.ResponseBody.Companion.toResponseBody
import retrofit2.Response
@ -16,6 +20,10 @@ class ContactRepository {
private val contactApiService = RetrofitClient.contactApiService
private val contactBox: Box<Contact> by lazy {
get().boxFor(Contact::class.java)
}
// 获取联系人请求列表
suspend fun getContactRequestList(username: String): Response<ContactRequestResponse> {
return try {
@ -29,16 +37,31 @@ class ContactRepository {
// 接受联系人请求
suspend fun acceptContactRequest(
username: String,
contactId: String
contactId: String,
remark: String
): Response<AcceptContactRequest> {
return try {
contactApiService.acceptContactRequest(contactId, username)
contactApiService.acceptContactRequest(contactId, username, remark)
} catch (e: Exception) {
Log.e("ContactRepository", "Accept Contact Request failed: ${e.message}")
Response.error(500, "".toResponseBody())
}
}
// 删除联系人
suspend fun deleteContact(
username: String,
contactId: String,
): Response<DeleteContact> {
try {
contactBox.query(Contact_.username.equal(contactId))
return contactApiService.deleteContact(username, contactId)
} catch (e: Exception) {
Log.e("ContactRepository", "Delete Contact failed: ${e.message}")
return Response.error(500, "".toResponseBody())
}
}
// 添加联系人
suspend fun addContact(contactId: String, message: String): Response<ApplyFriend> {
return try {

View File

@ -3,6 +3,7 @@ package com.kaixed.kchat.repository
import android.util.Log
import com.kaixed.kchat.model.request.RegisterRequest
import com.kaixed.kchat.model.request.UserRequest
import com.kaixed.kchat.model.response.friend.SearchFriends
import com.kaixed.kchat.model.response.login.Login
import com.kaixed.kchat.model.response.register.Register
import com.kaixed.kchat.model.response.search.UserList
@ -49,6 +50,16 @@ class UserRepository {
suspend fun getUserList(username: String): Response<UserList> {
return try {
userApiService.getUserListByNickname(username)
} catch (e: Exception) {
Log.e("UserRepository", "Get User Info Failed: ${e.message}", e)
Response.error(500, "".toResponseBody())
}
}
// 获取用户信息
suspend fun getUserInfo(username: String): Response<SearchFriends> {
return try {
userApiService.getUserInfo(username)
} catch (e: Exception) {
Log.e("UserRepository", "Get User List Failed: ${e.message}", e)
Response.error(500, "".toResponseBody())

View File

@ -29,6 +29,7 @@ import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
class WebSocketService : Service() {
companion object {
@ -44,8 +45,6 @@ class WebSocketService : Service() {
private val binder = LocalBinder()
private val messagesMutableLiveData = MutableLiveData<Messages>()
private var webSocket: WebSocket? = null
private var heartbeatJob: Job? = null
@ -54,7 +53,9 @@ class WebSocketService : Service() {
private val serviceScope = CoroutineScope(IO + serviceJob)
val liveData: LiveData<Messages> get() = messagesMutableLiveData
private val _messagesMutableLiveData = MutableLiveData<Messages>()
val messageLivedata: LiveData<Messages> get() = _messagesMutableLiveData
inner class LocalBinder : Binder() {
fun getService(): WebSocketService {
@ -70,6 +71,7 @@ class WebSocketService : Service() {
super.onCreate()
messagesBox = get().boxFor(Messages::class.java)
username = getUsername()
firstLoad()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@ -85,6 +87,10 @@ class WebSocketService : Service() {
}
}
fun sendMessage(messages: Messages) {
updateConversationList(messages)
}
private fun establishConnection() {
if (webSocket == null) {
val request = Request.Builder()
@ -104,7 +110,7 @@ class WebSocketService : Service() {
conversation?.let {
conversation.unreadCount =
if (currentContactId.isNotEmpty() && conversation.talkerId == currentContactId) 0
if (conversation.talkerId == currentContactId || messages.senderId == getUsername()) 0
else conversation.unreadCount + 1
conversation.lastContent = messages.content
@ -150,13 +156,11 @@ class WebSocketService : Service() {
Log.d(TAG, "Message received: $messages")
messages.takerId = messages.senderId
messagesMutableLiveData.postValue(messages)
updateDbConversation(messages)
serviceScope.launch {
if ("ack" == messages.type) {
val existingMessage = messagesBox.get(messages.msgLocalId)
existingMessage?.let {
it.timestamp = messages.timestamp
@ -166,9 +170,10 @@ class WebSocketService : Service() {
messagesBox.put(messages)
}
} else {
_messagesMutableLiveData.postValue(messages)
messagesBox.put(messages)
updateConversationList(messages)
}
Log.d(TAG, "Message stored: $messages")
}
}
@ -183,6 +188,46 @@ class WebSocketService : Service() {
}
}
private val _conversations = MutableLiveData<List<Conversation>?>()
val conversations: LiveData<List<Conversation>?> get() = _conversations
private var conversationList = mutableListOf<Conversation>()
private fun updateConversationList(messages: Messages) {
updateDbConversation(messages)
val index = conversationList.indexOfFirst { it.talkerId == messages.takerId }
if (index != -1) {
if (messages.timestamp < conversationList[index].timestamp) {
return
}
conversationList[index].apply {
lastContent = messages.content
timestamp = messages.timestamp
unreadCount =
if (this.talkerId == getCurrentContactId() || messages.senderId == getUsername()) 0 else unreadCount + 1
}
} else {
conversationList.add(
createChatList(
talkerId = messages.takerId,
nickname = messages.takerId,
content = messages.content,
timestamp = messages.timestamp
)
)
}
_conversations.postValue(conversationList.sortedByDescending { it.timestamp })
}
private fun firstLoad() {
conversationList = conversationBox.query(Conversation_.show.equal(true))
.orderDesc(Conversation_.timestamp)
.build()
.find()
_conversations.postValue(conversationList)
}
override fun onDestroy() {
super.onDestroy()
serviceJob.cancel()

View File

@ -9,13 +9,17 @@ import androidx.activity.viewModels
import com.kaixed.kchat.databinding.ActivityApproveContactRequestBinding
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.kaixed.kchat.utils.handle.ContactUtil
import com.kaixed.kchat.viewmodel.ContactViewModel
class ApproveContactRequestActivity : BaseActivity<ActivityApproveContactRequestBinding>() {
private val contactViewModel: ContactViewModel by viewModels()
private var nickname: String? = null
private var message: String? = null
private var contactId: String? = null
override fun inflateBinding(): ActivityApproveContactRequestBinding {
return ActivityApproveContactRequestBinding.inflate(layoutInflater)
}
@ -24,28 +28,40 @@ class ApproveContactRequestActivity : BaseActivity<ActivityApproveContactRequest
super.onCreate(savedInstanceState)
enableEdgeToEdge()
handleIntent(intent)
setContent()
setOnClickListener()
}
private fun handleIntent(intent: Intent) {
nickname = intent.getStringExtra("nickname")
message = intent.getStringExtra("message")
contactId = intent.getStringExtra("contactId")
}
@SuppressLint("SetTextI18n")
private fun setContent() {
val nickname = intent.getStringExtra("nickname")
val message = intent.getStringExtra("message")
binding.etRemark.setText(nickname)
binding.tvMessage.text = "$message"
binding.tvMessage.text = "$message"
}
private fun setOnClickListener() {
binding.tvFinish.setOnClickListener {
val contactId = intent.getStringExtra("contactId")
contactViewModel.acceptContactRequestResponse
.observe(this) { value ->
value?.let {
runOnUiThread {
toast(it.data)
acceptContactRequest(contactId!!)
}
}
private fun acceptContactRequest(contactId: String) {
contactViewModel.acceptContactRequestResponse
.observe(this) { value ->
value?.let {
if (value.code != "200") {
toast(it.msg)
} else {
value.data?.let { it1 ->
ContactUtil.handleContact(it1)
}
val stackBuilder: TaskStackBuilder = TaskStackBuilder.create(this)
stackBuilder.addNextIntentWithParentStack(
@ -60,11 +76,12 @@ class ApproveContactRequestActivity : BaseActivity<ActivityApproveContactRequest
)
stackBuilder.startActivities()
}
}
}
contactViewModel.acceptContactRequest(getUsername(), contactId!!)
}
val remark = binding.etRemark.text.toString()
contactViewModel.acceptContactRequest(getUsername(), contactId, remark)
}
}

View File

@ -5,30 +5,44 @@ import android.os.Build
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.bumptech.glide.Glide
import com.kaixed.kchat.databinding.ActivityApproveDetailBinding
import com.kaixed.kchat.model.friend.FriendRequestItem
import com.kaixed.kchat.ui.base.BaseActivity
class ApproveDetailActivity : AppCompatActivity() {
private lateinit var binding: ActivityApproveDetailBinding
class ApproveDetailActivity : BaseActivity<ActivityApproveDetailBinding>() {
private var request: FriendRequestItem? = null
override fun inflateBinding(): ActivityApproveDetailBinding {
return ActivityApproveDetailBinding.inflate(layoutInflater)
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityApproveDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
request = intent.getParcelableExtra("request", FriendRequestItem::class.java)
handleIntent(intent)
setContent()
setOnClick()
}
private fun handleIntent(intent: Intent) {
request = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra("request", FriendRequestItem::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra("request") as? FriendRequestItem
}
}
private fun setContent() {
if (request == null) {
return
}
val nickname = request?.nickname
val signature = request?.signature
val avatarUrl = request?.avatarUrl
@ -50,6 +64,7 @@ class ApproveDetailActivity : AppCompatActivity() {
Intent(this, ApproveContactRequestActivity::class.java).apply {
putExtra("nickname", request?.nickname)
putExtra("message", request?.message)
putExtra("contactId", request?.username)
}
)
}

View File

@ -10,6 +10,7 @@ import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
@ -37,6 +38,8 @@ import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.ui.i.OnItemClickListener
import com.kaixed.kchat.utils.Constants.CURRENT_CONTACT_ID
import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT_RATIO
import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION
import com.kaixed.kchat.utils.ConstantsUtil.getCurrentContactId
import com.kaixed.kchat.utils.ConstantsUtil.getKeyboardHeight
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.kaixed.kchat.utils.ImageSpanUtil.insertEmoji
@ -60,7 +63,7 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
private var contactNickname: String? = null
private var username: String? = null
private val username: String by lazy { getUsername() }
private var tempIndex: Long = 0
@ -80,7 +83,7 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
private var bound = false
private val mmkv by lazy { MMKV.defaultMMKV() }
private val mmkv by lazy { MMKV.mmkvWithID(MMKV_USER_SESSION) }
companion object {
private const val TAG = "ChatActivity"
@ -110,8 +113,6 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
setPanelChange()
setBackPressListener()
getKeyBoardVisibility()
}
@ -251,6 +252,7 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
}
private fun setListener() {
setBackPressListener()
binding.tvContactName.setOnLongClickListener {
false
}
@ -386,7 +388,6 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
private fun initData() {
messagesBox = get().boxFor(Messages::class.java)
username = getUsername()
softKeyboardHeight = getKeyboardHeight()
mmkv.putString(CURRENT_CONTACT_ID, contactId)
}
@ -458,15 +459,15 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
if (webSocketService == null) {
return
}
webSocketService!!.liveData.observe(
webSocketService!!.messageLivedata.observe(
this
) { messages: Messages ->
Log.d("haha11", messages.toString())
if ("ack" != messages.type && username != messages.senderId) {
if (messages.msgLocalId != 0L) {
return@observe
}
messagesList.addFirst(messages)
runOnUiThread {
val a = getCurrentContactId()
if (messages.takerId == contactId) {
messagesList.addFirst(messages)
chatAdapter!!.notifyItemInserted(0)
binding.recycleChatList.smoothScrollToPosition(0)
}
@ -478,7 +479,7 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
val messages = Messages(
content = message,
timestamp = System.currentTimeMillis(),
senderId = username!!,
senderId = username,
takerId = talkerId,
type = type,
)
@ -495,6 +496,7 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
jsonObject.put("type", "single")
jsonObject.put("body", jsonObject2)
webSocketService!!.sendMessage(jsonObject.toString(), id)
webSocketService!!.sendMessage(messages)
binding.etInput.setText("")
}

View File

@ -3,32 +3,39 @@ package com.kaixed.kchat.ui.activity
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.kaixed.kchat.databinding.ActivityMessageBinding
import com.kaixed.kchat.databinding.ActivityContactRequestListBinding
import com.kaixed.kchat.model.friend.FriendRequestItem
import com.kaixed.kchat.ui.adapter.MessageListAdapter
import com.kaixed.kchat.ui.adapter.ContactRequestListAdapter
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.kaixed.kchat.viewmodel.ContactViewModel
class MessageActivity : AppCompatActivity() {
private lateinit var binding: ActivityMessageBinding
class ContactRequestListActivity : BaseActivity<ActivityContactRequestListBinding>() {
private var items = mutableListOf<FriendRequestItem>()
private val contactViewModel: ContactViewModel by viewModels()
private lateinit var messageListAdapter: MessageListAdapter
private lateinit var contactRequestListAdapter: ContactRequestListAdapter
override fun inflateBinding(): ActivityContactRequestListBinding {
return ActivityContactRequestListBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityMessageBinding.inflate(layoutInflater)
setContentView(binding.root)
getItems()
messageListAdapter = MessageListAdapter(items, this)
contactRequestListAdapter = ContactRequestListAdapter(items, this)
binding.recycleFriendRequestList.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.recycleFriendRequestList.adapter = messageListAdapter
binding.recycleFriendRequestList.adapter = contactRequestListAdapter
}
private fun getItems() {
@ -36,10 +43,10 @@ class MessageActivity : AppCompatActivity() {
.observe(this) { value ->
if (value != null) {
items.addAll(value.data.toMutableList())
messageListAdapter.notifyDataSetChanged()
contactRequestListAdapter.notifyDataSetChanged()
}
}
contactViewModel.getContactRequestList(getUsername())
}
}
}

View File

@ -17,6 +17,7 @@ class ContactsDetailActivity : BaseActivity<ActivityContactsDetailBinding>() {
private val contactBox: Box<Contact> by lazy { get().boxFor(Contact::class.java) }
private var contactId: String? = null
private var contactNickname: String? = null
companion object {
@ -60,6 +61,19 @@ class ContactsDetailActivity : BaseActivity<ActivityContactsDetailBinding>() {
putExtra("contactNickname", contactId)
})
}
binding.ciSetRemarkAndLabel.setOnClickListener {
startActivity(Intent(this, SetRemarkAndLabelActivity::class.java).apply {
putExtra("contactId", contactId)
putExtra("contactNickname", contactNickname)
})
}
binding.ctb.setOnSettingClickListener {
startActivity(Intent(this, DataSettingActivity::class.java).apply {
putExtra("contactId", contactId)
})
}
}
private fun getUserInfo(contactId: String): Contact? {

View File

@ -1,13 +1,42 @@
package com.kaixed.kchat.ui.activity
import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.Gravity
import android.view.LayoutInflater
import android.view.Window
import android.view.WindowManager
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.activity.viewModels
import com.kaixed.kchat.data.objectbox.ObjectBox.get
import com.kaixed.kchat.data.objectbox.entity.Contact
import com.kaixed.kchat.data.objectbox.entity.Contact_
import com.kaixed.kchat.databinding.ActivityDataSettingBinding
import com.kaixed.kchat.databinding.DialogDeleteContactBinding
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.kaixed.kchat.utils.WidgetUtil
import com.kaixed.kchat.viewmodel.ContactViewModel
import io.objectbox.Box
class DataSettingActivity : BaseActivity<ActivityDataSettingBinding>() {
private val contactViewModel by viewModels<ContactViewModel>()
private val contactBox: Box<Contact> by lazy { get().boxFor(Contact::class.java) }
private var contactId = ""
override fun inflateBinding(): ActivityDataSettingBinding {
return ActivityDataSettingBinding.inflate(layoutInflater)
}
class DataSettingActivity : AppCompatActivity() {
private lateinit var binding: ActivityDataSettingBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
@ -15,13 +44,66 @@ class DataSettingActivity : AppCompatActivity() {
setContentView(binding.root)
setOnClick()
handleIntent(intent)
}
private fun handleIntent(intent: Intent) {
contactId = intent.getStringExtra("contactId") ?: ""
}
private fun setOnClick() {
binding.tvSetRemark.setOnClickListener {
binding.ciSetRemarkAndLabel.setOnClickListener {
val intent = Intent(this, SetRemarkActivity::class.java)
startActivity(intent)
}
binding.ivBack.setOnClickListener { finish() }
binding.tvDelete.setOnClickListener {
val dialog = showDeleteDialog(this)
dialog.show()
}
}
private fun showDeleteDialog(context: Context): Dialog {
val binding = DialogDeleteContactBinding.inflate(LayoutInflater.from(context))
val dialog = Dialog(context)
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialog.setCancelable(false)
dialog.setContentView(binding.root)
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
val layoutParams = WindowManager.LayoutParams().apply {
copyFrom(dialog.window?.attributes)
width = (context.resources.displayMetrics.widthPixels * 0.8).toInt()
gravity = Gravity.CENTER
}
dialog.window?.attributes = layoutParams
binding.tvCancel.setOnClickListener {
dialog.dismiss()
}
binding.tvDelete.setOnClickListener {
deleteContact(getUsername(), contactId)
}
return dialog
}
private fun deleteContact(username: String, contactId: String) {
val dialog = WidgetUtil.showLoadingDialog(this, "删除中...")
dialog.show()
contactViewModel.deleteContact.observe(this) {
it?.let {
val con = contactBox.query(Contact_.username.equal(contactId)).build().findFirst()
contactBox.remove(con!!)
val handle = Handler(Looper.getMainLooper())
handle.postDelayed({
dialog.dismiss()
}, 1000)
toast("删除成功")
} ?: toast("删除失败")
}
contactViewModel.deleteContact(username, contactId)
}
}

View File

@ -73,9 +73,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), View.OnClickListener {
if (isFirstLaunchApp()) {
lifecycleScope.launch(Dispatchers.Main) {
val dialog = WidgetUtil.showLoadingDialog(this@MainActivity, "同步数据中")
dialog.show()
delay(200)
contactViewModel.contactListResponse.observe(this@MainActivity) { contacts ->
contactBox.put(contacts ?: emptyList())
}

View File

@ -80,6 +80,7 @@ class ProfileDetailActivity : BaseActivity<ActivityProfileDetailBinding>() {
val filePath = getPathFromUri(uri)
val dialog = WidgetUtil.showLoadingDialog(this, "正在上传")
dialog.show()
userViewModel.uploadAvatarResponse.observe(this) { value ->
value?.let {
userSessionMMKV.putString(AVATAR_URL, value)

View File

@ -0,0 +1,22 @@
package com.kaixed.kchat.ui.activity
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.kaixed.kchat.R
import com.kaixed.kchat.databinding.ActivitySetRemarkAndLabelBinding
import com.kaixed.kchat.ui.base.BaseActivity
class SetRemarkAndLabelActivity : BaseActivity<ActivitySetRemarkAndLabelBinding>() {
override fun inflateBinding(): ActivitySetRemarkAndLabelBinding {
return ActivitySetRemarkAndLabelBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
}
}

View File

@ -17,8 +17,11 @@ import com.kaixed.kchat.ui.activity.ApproveDetailActivity
* @Author: kaixed
* @Date: 2024/10/18 15:08
*/
class MessageListAdapter(private val items: MutableList<FriendRequestItem>, val context: Context) :
RecyclerView.Adapter<MessageListAdapter.MyViewHolder>() {
class ContactRequestListAdapter(
private val items: MutableList<FriendRequestItem>,
val context: Context
) :
RecyclerView.Adapter<ContactRequestListAdapter.MyViewHolder>() {
class MyViewHolder(val binding: FriendRecycleItemAddRequestBinding) : ViewHolder(binding.root)
@ -43,6 +46,7 @@ class MessageListAdapter(private val items: MutableList<FriendRequestItem>, val
holder.binding.tvAdd.setOnClickListener {
context.startActivity(
Intent(context, ApproveContactRequestActivity::class.java).apply {
putExtra("request", items[position])
putExtra("nickname", items[position].nickname)
putExtra("contactId", items[position].username)
putExtra("message", items[position].message)

View File

@ -14,20 +14,26 @@ import com.kaixed.kchat.R
import com.kaixed.kchat.data.objectbox.entity.Contact
import com.kaixed.kchat.databinding.FriendRecycleFooterItemBinding
import com.kaixed.kchat.databinding.FriendRecycleItemBinding
import com.kaixed.kchat.ui.activity.ContactRequestListActivity
import com.kaixed.kchat.ui.activity.ContactsDetailActivity
import com.kaixed.kchat.utils.Pinyin4jUtil
/**
* @Author: kaixed
* @Date: 2024/10/14 14:47
*/
class FriendListAdapter(private var items: MutableList<Contact>, private val context: Context) :
class FriendListAdapter(var items: MutableList<Contact>, private val context: Context) :
RecyclerView.Adapter<ViewHolder>() {
class ItemViewHolder(val binding: FriendRecycleItemBinding) : ViewHolder(binding.root)
class FooterViewHolder(val binding: FriendRecycleFooterItemBinding) : ViewHolder(binding.root)
fun updateData(newData: List<Contact>) {
items.clear()
items.addAll(newData)
notifyDataSetChanged()
}
companion object {
const val TYPE_ITEM: Int = 0
const val TYPE_FOOTER: Int = 1
@ -68,25 +74,62 @@ class FriendListAdapter(private var items: MutableList<Contact>, private val con
}
is ItemViewHolder -> {
if (position == 0) {
holder.binding.root.setOnClickListener {
context.startActivity(
Intent(
context,
ContactRequestListActivity::class.java
)
)
}
}
val item = items[position]
// Log.d(TAG, "onBindViewHolder: $item")
holder.binding.tvItemName.text = item.remark ?: item.nickname
if (item.showHeader == true) {
holder.binding.tvLetter.text =
Pinyin4jUtil.toPinyin(item.nickname)[0].toString().uppercase()
if (item.remarkquanpin != null) {
if (item.remarkquanpin?.substring(0, 1)?.uppercase()
?.matches(Regex("[a-zA-Z]+")) == false
) {
holder.binding.tvLetter.text =
"#"
} else {
holder.binding.tvLetter.text =
item.remarkquanpin?.substring(0, 1)?.uppercase()
}
} else {
holder.binding.tvLetter.text = item.quanpin?.substring(0, 1)?.uppercase()
}
holder.binding.tvLetter.visibility = ViewGroup.VISIBLE
} else {
holder.binding.tvLetter.visibility = ViewGroup.GONE
}
if (position <= defaultItems.size - 1) {
holder.binding.ifvAvatar.setImageResource(defaultItems[position])
holder.binding.ifvAvatar.setBackgroundColor(context.getColor(R.color.gray))
if (position == 0) {
holder.binding.rlUnreadCount.visibility =
if (item.avatarUrl != null) View.VISIBLE else View.GONE
if (item.avatarUrl != null) {
val options = RequestOptions()
.placeholder(R.drawable.ic_default_avatar)
.error(R.drawable.ic_default_avatar)
Glide.with(context).load(item.avatarUrl).apply(options)
.into(holder.binding.ifvAvatar)
} else {
holder.binding.ifvAvatar.setImageResource(defaultItems[position])
holder.binding.ifvAvatar.setBackgroundColor(context.getColor(R.color.gray))
}
} else {
holder.binding.ifvAvatar.setImageResource(defaultItems[position])
holder.binding.ifvAvatar.setBackgroundColor(context.getColor(R.color.gray))
}
if (position == defaultItems.size - 1) {
holder.binding.viewBottom.visibility =
if (items.size == 5) View.VISIBLE else View.INVISIBLE
}
} else {
val options = RequestOptions()
.placeholder(R.drawable.ic_default_avatar)

View File

@ -1,30 +1,35 @@
package com.kaixed.kchat.ui.fragment
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.kaixed.kchat.data.objectbox.ObjectBox.get
import com.kaixed.kchat.data.objectbox.entity.Contact
import com.kaixed.kchat.databinding.FragmentContactBinding
import com.kaixed.kchat.ui.adapter.FriendListAdapter
import com.kaixed.kchat.ui.base.BaseFragment
import io.objectbox.Box
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.kaixed.kchat.viewmodel.ContactViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ContactFragment : BaseFragment<FragmentContactBinding>() {
// private val friendListViewModel: FriendListViewModel by viewModels()
private val contactViewModel: ContactViewModel by viewModels()
private val contactBox: Box<Contact> by lazy { get().boxFor(Contact::class.java) }
private var loading = false
private lateinit var context: Context
private val friendAdapter: FriendListAdapter by lazy {
FriendListAdapter(friendList, requireContext())
FriendListAdapter(mutableListOf(), requireContext())
}
private var friendList: MutableList<Contact> = mutableListOf()
companion object {
private const val TAG = "ContactFragment"
}
@ -38,32 +43,67 @@ class ContactFragment : BaseFragment<FragmentContactBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
getDefaultItem()
context = requireContext()
loadData()
loadFriendRequest()
}
override fun onResume() {
super.onResume()
loadFriendRequest()
}
private fun loadFriendRequest() {
contactViewModel.contactRequestListResponse.observe(viewLifecycleOwner) { response ->
if (response?.data?.isNotEmpty() == true) {
val contactRequest = response.data[response.data.size - 1]
friendAdapter.items[0].apply {
remark =
"${contactRequest.nickname}(${contactRequest.message})"
avatarUrl = contactRequest.avatarUrl
}
friendAdapter.notifyItemChanged(0)
}
}
contactViewModel.getContactRequestList(getUsername())
}
private fun loadData() {
loading = true
binding.tvLoading.visibility = View.VISIBLE
binding.recycleFriendList.visibility = View.INVISIBLE
lifecycleScope.launch(Dispatchers.IO) {
val contacts = contactViewModel.loadFriendListInDb()
val allItems = mutableListOf<Contact>().apply {
addAll(getDefaultItems())
addAll(contacts)
}
withContext(Dispatchers.Main) {
friendAdapter.updateData(allItems)
binding.tvLoading.visibility = View.GONE
binding.recycleFriendList.visibility = View.VISIBLE
loading = false
}
}
setupRecyclerView()
}
private fun setupRecyclerView() {
binding.recycleFriendList.layoutManager =
LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
binding.recycleFriendList.adapter = friendAdapter
getFriendList()
}
private fun getFriendList() {
friendList.addAll(contactBox.all)
Log.d(TAG, "getFriendList: ${contactBox.all}")
// friendListViewModel.friendListLiveData.observe(viewLifecycleOwner) { friends ->
// friends?.let {
// friendList.addAll(friends)
// friendAdapter.notifyDataSetChanged()
// }
// }
// friendListViewModel.loadFriendList(getUsername())
}
private fun getDefaultItem() {
friendList.add(Contact(username = "", nickname = "新的朋友"))
friendList.add(Contact(username = "", nickname = "群聊"))
friendList.add(Contact(username = "", nickname = "标签"))
friendList.add(Contact(username = "", nickname = "公众号"))
friendList.add(Contact(username = "", nickname = "服务号"))
private fun getDefaultItems(): List<Contact> {
return listOf(
Contact(username = "", nickname = "新的朋友"),
Contact(username = "", nickname = "群聊"),
Contact(username = "", nickname = "标签"),
Contact(username = "", nickname = "公众号"),
Contact(username = "", nickname = "服务号")
)
}
}

View File

@ -10,11 +10,14 @@ import android.content.Intent
import android.content.ServiceConnection
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity.BIND_AUTO_CREATE
import androidx.recyclerview.widget.LinearLayoutManager
import com.kaixed.kchat.R
@ -28,7 +31,6 @@ import com.kaixed.kchat.service.WebSocketService
import com.kaixed.kchat.service.WebSocketService.LocalBinder
import com.kaixed.kchat.ui.activity.AddFriendsActivity
import com.kaixed.kchat.ui.activity.ContactUpdatesActivity
import com.kaixed.kchat.ui.activity.MessageActivity
import com.kaixed.kchat.ui.activity.SearchActivity
import com.kaixed.kchat.ui.adapter.ConversationAdapter
import com.kaixed.kchat.ui.adapter.MyGridAdapter
@ -42,6 +44,8 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnChatListItemClickLis
private var chatLists: MutableList<Conversation> = mutableListOf()
private var oldLists: MutableList<Conversation> = mutableListOf()
private val conversationAdapter: ConversationAdapter by lazy {
ConversationAdapter(chatLists, context)
}
@ -94,18 +98,11 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnChatListItemClickLis
private fun setOnClick() {
binding.ivMessage.setOnClickListener {
val intent = Intent(
context,
MessageActivity::class.java
)
startActivity(intent)
}
binding.ivSearch.setOnClickListener {
val intent =
Intent(context, SearchActivity::class.java)
val intent = Intent(context, SearchActivity::class.java)
startActivity(intent)
}
}
private fun flipImage(v: View) {
@ -125,10 +122,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnChatListItemClickLis
}
private fun startService() {
val intent = Intent(
context,
WebSocketService::class.java
)
val intent = Intent(context, WebSocketService::class.java)
context.startService(intent)
}
@ -136,19 +130,16 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnChatListItemClickLis
if (webSocketService == null) {
return
}
webSocketService!!.liveData.observe(
this
) { messages: Messages ->
if ("0" == messages.type) {
if (messages.msgSvrId != handleMsgSvrId) {
processMessage(messages)
}
webSocketService!!.conversations.observe(viewLifecycleOwner) {
it?.let {
chatLists = it.toMutableList()
conversationAdapter.updateData(chatLists)
notifyData()
}
}
}
private fun processMessage(messages: Messages) {
handleMsgSvrId = messages.msgSvrId
val talkerId = messages.takerId
@ -271,9 +262,9 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnChatListItemClickLis
}
private fun setupRecycleView() {
conversationBox.all?.let {
chatLists = conversationBox.all
}
// conversationBox.all?.let {
// chatLists = conversationBox.query(Conversation_.show.equal(true)).build().find()
// }
binding.recycleChatList.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
binding.recycleChatList.adapter = conversationAdapter
@ -321,22 +312,27 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnChatListItemClickLis
}
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_discovery_scan))
items.add(HomeItem("通讯录", true, R.drawable.ic_clock_in))
items.add(HomeItem("关闭应用", false, R.drawable.ic_exit))
val list = listOf(
HomeItem("更换背景", true, R.drawable.ic_switch),
HomeItem("创建群聊", false, R.drawable.ic_troop),
HomeItem("新朋友", true, R.drawable.ic_friend),
HomeItem("夜间模式", true, R.drawable.ic_night),
HomeItem("好友动态", false, R.drawable.ic_qzone),
HomeItem("扫一扫", false, R.drawable.ic_discovery_scan),
HomeItem("通讯录", true, R.drawable.ic_clock_in),
HomeItem("关闭应用", false, R.drawable.ic_exit)
)
items.addAll(list)
}
override fun onResume() {
super.onResume()
updateChatLists()
// updateChatLists()
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onStart() {
super.onStart()
val intent = Intent(

View File

@ -37,6 +37,17 @@ class CustomItem @JvmOverloads constructor(
val itemDesc = typedArray.getString(R.styleable.CustomItem_itemDesc) ?: ""
val itemRedTip = typedArray.getBoolean(R.styleable.CustomItem_itemRedTip, false)
val rightType = typedArray.getString(R.styleable.CustomItem_rightType) ?: ""
val background = typedArray.getDrawable(R.styleable.CustomItem_android_background)
background?.let { bg ->
this.background = bg
} ?: run {
this.setBackgroundResource(R.color.white)
}
setupRightContent(rightType)
// 设置视图
setupRedTip(itemRedTip)
setupItemDesc(itemDesc)
@ -49,6 +60,20 @@ class CustomItem @JvmOverloads constructor(
}
}
private fun setupRightContent(type: String) {
when (type) {
"switch" -> {
binding.ssvSwitch.visibility = View.VISIBLE
binding.ivArrowRight.visibility = View.GONE
}
else -> {
binding.ssvSwitch.visibility = View.GONE
binding.ivArrowRight.visibility = View.VISIBLE
}
}
}
// 设置红点显示
private fun setupRedTip(itemRedTip: Boolean) {
binding.viewRedTip.visibility = if (itemRedTip) View.VISIBLE else View.GONE
@ -132,4 +157,8 @@ class CustomItem @JvmOverloads constructor(
Glide.with(context).load(url)
.into(binding.ivItemIcon)
}
fun setSwitchChecked(checked: Boolean) {
binding.ssvSwitch.setOn(checked)
}
}

View File

@ -46,7 +46,12 @@ class CustomTitleBar @JvmOverloads constructor(
binding.tvTitleName.visibility = View.GONE
}
binding.tvSave.visibility = if (btnName.isNotEmpty()) View.VISIBLE else View.INVISIBLE
if (btnName.isNotEmpty()) {
binding.tvSave.visibility = View.VISIBLE
binding.tvSave.text = btnName
} else {
binding.tvSave.visibility = View.INVISIBLE
}
typedArray.recycle()
}

View File

@ -4,6 +4,7 @@ import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
@ -510,7 +511,7 @@ class ShSwitchView @JvmOverloads constructor(
)
//innerContent
paint.color = foregroundColor
paint.color = Color.WHITE
canvas.drawRoundRect(
innerContentBound,
innerContentBound.height() / 2,

View File

@ -4,8 +4,11 @@ import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.Gravity
import android.view.LayoutInflater
import android.view.Window
import android.view.WindowManager
import com.kaixed.kchat.databinding.DialogDeleteContactBinding
import com.kaixed.kchat.databinding.DialogLoadingBinding
import com.kaixed.kchat.utils.DensityUtil.dpToPx
@ -26,7 +29,6 @@ object WidgetUtil {
params.width = dpToPx(150)
binding.root.layoutParams = params
binding.tvLoading.text = str
dialog.show()
return dialog
}
}

View File

@ -0,0 +1,36 @@
package com.kaixed.kchat.utils.handle
import com.kaixed.kchat.data.objectbox.ObjectBox.get
import com.kaixed.kchat.data.objectbox.entity.Contact
import com.kaixed.kchat.utils.Pinyin4jUtil
import io.objectbox.Box
/**
* @Author: kaixed
* @Date: 2024/11/19 16:11
*/
object ContactUtil {
private val contactBox: Box<Contact> by lazy { get().boxFor(Contact::class.java) }
fun handleContact(contact: Contact) {
val contactLists = getDbContactLists()
contactLists.add(contact)
contact.apply {
quanpin = Pinyin4jUtil.toPinyin(nickname)
remark?.let {
remarkquanpin = Pinyin4jUtil.toPinyin(remark!!)
}
}
contactLists.forEachIndexed { index, item ->
item.showHeader =
(index == 0 || item.quanpin?.get(index) != contactLists[index - 1].quanpin?.get(
index - 1
))
}
contactBox.put(contactLists)
}
private fun getDbContactLists(): MutableList<Contact> {
return contactBox.all.toMutableList()
}
}

View File

@ -4,13 +4,16 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.kaixed.kchat.data.objectbox.ObjectBox.get
import com.kaixed.kchat.data.objectbox.entity.Contact
import com.kaixed.kchat.model.friend.AcceptContactRequest
import com.kaixed.kchat.model.friend.ContactRequestResponse
import com.kaixed.kchat.model.response.ApplyFriend
import com.kaixed.kchat.model.response.friend.DeleteContact
import com.kaixed.kchat.model.response.friend.SearchFriends
import com.kaixed.kchat.repository.ContactRepository
import com.kaixed.kchat.utils.Pinyin4jUtil
import io.objectbox.Box
import kotlinx.coroutines.launch
class ContactViewModel : ViewModel() {
@ -38,6 +41,21 @@ class ContactViewModel : ViewModel() {
private val _contactListResponse = MutableLiveData<List<Contact>?>()
val contactListResponse: LiveData<List<Contact>?> = _contactListResponse
// 删除联系人
private val _deleteContact = MutableLiveData<DeleteContact?>()
val deleteContact: LiveData<DeleteContact?> = _deleteContact
fun deleteContact(username: String, contactId: String) {
viewModelScope.launch {
val response = contactRepository.deleteContact(username, contactId)
if (response.isSuccessful) {
_deleteContact.value = response.body()
} else {
_deleteContact.value = null
}
}
}
// 获取联系人请求列表
fun getContactRequestList(username: String) {
viewModelScope.launch {
@ -51,9 +69,9 @@ class ContactViewModel : ViewModel() {
}
// 接受联系人请求
fun acceptContactRequest(username: String, contactId: String) {
fun acceptContactRequest(username: String, contactId: String, remark: String) {
viewModelScope.launch {
val response = contactRepository.acceptContactRequest(username, contactId)
val response = contactRepository.acceptContactRequest(username, contactId, remark)
if (response.isSuccessful) {
_acceptContactRequestResponse.value = response.body()
} else {
@ -86,6 +104,40 @@ class ContactViewModel : ViewModel() {
}
}
fun loadFriendListInDb(): List<Contact> {
val contactBox: Box<Contact> = get().boxFor(Contact::class.java)
val sortedContacts = contactBox.query()
.sort { contact1, contact2 ->
val str1 = contact1.remarkquanpin ?: contact1.quanpin ?: contact1.nickname
val str2 = contact2.remarkquanpin ?: contact2.quanpin ?: contact2.nickname
val type1 = when {
str1.matches(Regex("[a-zA-Z]+")) -> 1 // 字母
str1.matches(Regex("[0-9]+")) -> 2 // 数字
else -> 3 // 特殊字符
}
val type2 = when {
str2.matches(Regex("[a-zA-Z]+")) -> 1 // 字母
str2.matches(Regex("[0-9]+")) -> 2 // 数字
else -> 3 // 特殊字符
}
// 比较类型,如果相同再按字母升序排序
if (type1 != type2) {
type1 - type2 // 按类型排序
} else {
str1.compareTo(str2) // 如果类型相同,按字母顺序排序
}
}
.build()
.find()
return sortedContacts.toMutableList()
}
fun loadFriendList(username: String) {
viewModelScope.launch {
val response = contactRepository.getContactList(username)
@ -94,8 +146,27 @@ class ContactViewModel : ViewModel() {
val uiFriendList = friendListResponse?.map { networkItem ->
mapToFriendItem(networkItem) // 数据转换
}?.sortedWith { f1, f2 ->
// 拼音排序
Pinyin4jUtil.compare(f1.nickname, f2.nickname)
val str1 = f1.remarkquanpin ?: f1.quanpin ?: f1.nickname
val str2 = f2.remarkquanpin ?: f2.quanpin ?: f2.nickname
val type1 = when {
str1.matches(Regex("[a-zA-Z]+")) -> 1 // 字母
str1.matches(Regex("[0-9]+")) -> 2 // 数字
else -> 3 // 特殊字符
}
val type2 = when {
str2.matches(Regex("[a-zA-Z]+")) -> 1 // 字母
str2.matches(Regex("[0-9]+")) -> 2 // 数字
else -> 3 // 特殊字符
}
// 比较类型,如果相同再按字母升序排序
if (type1 != type2) {
type1 - type2 // 按类型排序
} else {
str1.compareTo(str2) // 如果类型相同,按字母顺序排序
}
}
// 设置是否显示分组头
@ -115,6 +186,9 @@ class ContactViewModel : ViewModel() {
private fun mapToFriendItem(response: Contact): Contact {
val pinyin = Pinyin4jUtil.toPinyin(response.nickname)
response.remark?.let {
response.remarkquanpin = Pinyin4jUtil.toPinyin(it)
}
response.quanpin = pinyin
return response
}

View File

@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.kaixed.kchat.model.request.RegisterRequest
import com.kaixed.kchat.model.request.UserRequest
import com.kaixed.kchat.model.response.friend.SearchFriends
import com.kaixed.kchat.model.response.login.Login
import com.kaixed.kchat.model.response.register.Register
import com.kaixed.kchat.model.response.search.UserList
@ -26,6 +27,10 @@ class UserViewModel : ViewModel() {
private val _userListResponse = MutableLiveData<UserList?>()
val userListResponse: LiveData<UserList?> = _userListResponse
// 获取用户信息返回
private val _userInfoResponse = MutableLiveData<SearchFriends?>()
val userInfoResponse: LiveData<SearchFriends?> = _userInfoResponse
// 修改昵称返回
private val _changeNicknameResponse = MutableLiveData<Boolean>()
val changeNicknameResponse: LiveData<Boolean> = _changeNicknameResponse
@ -89,6 +94,23 @@ class UserViewModel : ViewModel() {
}
}
// 获取用户信息
fun getUserInfo(username: String) {
viewModelScope.launch {
try {
val response = userRepo.getUserInfo(username)
if (response.isSuccessful) {
_userInfoResponse.value = response.body()
} else {
_userInfoResponse.value = null
}
} catch (e: Exception) {
Log.e("UserViewModel", "Get User failed: ${e.message}")
_userInfoResponse.value = null
}
}
}
// 修改昵称
fun changeNickname(userRequest: UserRequest) {
viewModelScope.launch {

View File

@ -57,24 +57,67 @@
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:layout_marginTop="5dp"
android:text="我是"
android:text="我是x"
android:textSize="13sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:layout_marginTop="20dp"
android:text="设置标签与备注"
android:textSize="13sp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp"
android:layout_marginTop="10dp"
app:cardCornerRadius="8dp"
app:cardElevation="0dp"
app:cardMaxElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F7F7F7"
android:orientation="vertical">
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#F7F7F7"
app:itemName="标签" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:background="#ededed" />
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#F7F7F7"
app:itemName="描述" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<TextView
android:id="@+id/tv_finish"
android:layout_width="150dp"
android:layout_height="48dp"
android:layout_width="160dp"
android:layout_height="43dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="40dp"
android:background="@drawable/add_contact_request_send_btn_bac"
android:gravity="center"
android:text="完成"
android:textColor="@color/white"
android:textSize="19sp" />
android:textSize="17sp" />
</LinearLayout>

View File

@ -77,13 +77,28 @@
app:barrierDirection="bottom"
app:constraint_referenced_ids="ifv_avatar,tv_set_remark_tag" />
<LinearLayout
android:id="@+id/cl_reply"
android:layout_width="match_parent"
android:visibility="invisible"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="15dp"
app:layout_constraintTop_toBottomOf="@id/barrier">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="沉默是金:我是万" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="15dp"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/barrier">
app:layout_constraintTop_toBottomOf="@id/cl_reply">
<View
android:layout_width="match_parent"

View File

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

View File

@ -88,10 +88,11 @@
android:background="#E5E5E5" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/ci_set_remark_and_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:isTopDividerVisible="true"
app:itemName="设置备注" />
app:itemName="设置备注和标签" />
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"

View File

@ -1,83 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout 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"
android:background="#E5E5E5"
android:background="@color/gray"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".ui.activity.DataSettingActivity">
<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:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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" />
<ImageView
android:id="@+id/iv_setting"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_marginEnd="15dp"
android:src="@drawable/ic_more"
app:layout_constraintBottom_toBottomOf="@id/iv_back"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/iv_back" />
<TextView
android:id="@+id/tv_setRemark"
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:background="@color/white"
android:paddingVertical="15dp"
android:paddingStart="15dp"
android:text="设置备注和标签"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_back" />
app:titleName="资料设置" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_arrow_right"
app:layout_constraintBottom_toBottomOf="@id/tv_setRemark"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_setRemark" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height" />
<TextView
android:id="@+id/tv_addToBlockList"
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/ci_set_remark_and_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:isBottomDividerVisible="true"
app:itemDesc="万(小)"
app:itemName="设置备注和标签" />
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemName="朋友权限" />
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin"
app:isBottomDividerVisible="true"
app:itemName="把她推荐给朋友" />
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemName="添加到桌面" />
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin"
app:itemName="设为星标朋友"
app:rightType="switch" />
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin"
app:isBottomDividerVisible="true"
app:itemName="加入黑名单"
app:rightType="switch" />
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemName="投诉" />
<TextView
android:id="@+id/tv_delete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin"
android:background="@color/white"
android:gravity="center"
android:paddingVertical="15dp"
android:paddingStart="15dp"
android:text="加入黑名单"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintTop_toBottomOf="@id/tv_setRemark" />
android:text="删除"
android:textColor="@color/red"
android:textSize="16sp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -1,40 +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"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".ui.activity.FriendListActivity">
<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="通讯录" />
<TextView
android:id="@+id/tv_nothing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="您当前还没有好友哦。"
android:textColor="@color/black"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ctb"
app:layout_constraintVertical_bias="0.3" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycle_friend_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:overScrollMode="never"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/ctb" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,310 +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"
android:background="#e5e5e5"
android:fitsSystemWindows="true"
tools:context=".ui.activity.ProfileActivity">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv_avatar"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="20dp"
android:layout_marginTop="35dp"
android:src="@drawable/ic_avatar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:roundPercent="0.3" />
<TextView
android:id="@+id/tv_nickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="糕菜菜"
android:textColor="@color/black"
android:textSize="15sp"
app:layout_constraintStart_toEndOf="@id/ifv_avatar"
app:layout_constraintTop_toTopOf="@id/ifv_avatar" />
<TextView
android:id="@+id/tv_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="kid: kaixed"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@id/ifv_avatar"
app:layout_constraintStart_toStartOf="@id/tv_nickname"
app:layout_constraintTop_toBottomOf="@id/tv_nickname" />
<ImageView
android:id="@+id/iv_service"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="15dp"
android:layout_marginTop="15dp"
android:src="@drawable/mine_icon_service"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ifv_avatar" />
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="我的服务"
android:gravity="center_vertical"
android:paddingStart="35dp"
app:layout_constraintBottom_toBottomOf="@id/iv_service"
app:layout_constraintTop_toTopOf="@id/iv_service" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="55dp"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/ifv_avatar">
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="#E5E5E5" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingStart="15dp">
<!-- <ImageView-->
<!-- android:id="@+id/iv_service"-->
<!-- android:layout_width="24dp"-->
<!-- android:layout_height="24dp"-->
<!-- android:layout_centerVertical="true"-->
<!-- android:src="@drawable/mine_icon_service" />-->
<!-- <TextView-->
<!-- android:id="@+id/tv_service"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_centerVertical="true"-->
<!-- android:layout_marginStart="15dp"-->
<!-- android:layout_toEndOf="@id/iv_service"-->
<!-- android:text="服务"-->
<!-- android:textColor="@color/black"-->
<!-- android:textSize="16sp" />-->
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_arrow_right" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="#E5E5E5" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingStart="15dp"
tools:ignore="RtlSymmetry">
<ImageView
android:id="@+id/iv_collection"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerVertical="true"
android:src="@drawable/mine_icon_collection" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:layout_toEndOf="@id/iv_collection"
android:text="收藏"
android:textColor="@color/black"
android:textSize="16sp" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_arrow_right" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="55dp"
android:background="#E5E5E5" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingStart="15dp"
tools:ignore="RtlSymmetry">
<ImageView
android:id="@+id/iv_friend_circle"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerVertical="true"
android:src="@drawable/mine_icon_friend_circle" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:layout_toEndOf="@id/iv_friend_circle"
android:text="朋友圈"
android:textColor="@color/black"
android:textSize="16sp" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_arrow_right" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="55dp"
android:background="#E5E5E5" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingStart="15dp"
tools:ignore="RtlSymmetry">
<ImageView
android:id="@+id/iv_wallet"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerVertical="true"
android:src="@drawable/mine_icon_wallet" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:layout_toEndOf="@id/iv_wallet"
android:text="卡包"
android:textColor="@color/black"
android:textSize="16sp" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_arrow_right" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="55dp"
android:background="#E5E5E5" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingStart="15dp"
tools:ignore="RtlSymmetry">
<ImageView
android:id="@+id/iv_emoji"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerVertical="true"
android:src="@drawable/mine_icon_emoji" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:layout_toEndOf="@id/iv_emoji"
android:text="表情"
android:textColor="@color/black"
android:textSize="16sp" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_arrow_right" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="#E5E5E5" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingStart="15dp"
tools:ignore="RtlSymmetry">
<ImageView
android:id="@+id/iv_setting"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerVertical="true"
android:src="@drawable/mine_icon_setting" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:layout_toEndOf="@id/iv_setting"
android:text="设置"
android:textColor="@color/black"
android:textSize="16sp" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_arrow_right" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#E5E5E5" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,172 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:background="@color/white"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".ui.activity.SetRemarkAndLabelActivity">
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:id="@+id/ctb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:btnName="完成" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="30dp"
android:text="设置备注和标签"
android:textColor="@color/black"
android:textSize="19sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="15dp"
android:text="备注"
android:textSize="13sp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginHorizontal="15dp"
android:layout_marginTop="5dp"
android:background="#F7F7F7"
app:cardCornerRadius="8dp"
app:cardElevation="0dp"
app:cardMaxElevation="0dp">
<EditText
android:id="@+id/et_remark"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F7F7F7"
android:paddingHorizontal="16dp"
android:textCursorDrawable="@drawable/cursor" />
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="15dp"
android:text="标签"
android:textSize="13sp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginHorizontal="15dp"
android:layout_marginTop="5dp"
android:background="#F7F7F7"
app:cardCornerRadius="8dp"
app:cardElevation="0dp"
app:cardMaxElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F7F7F7"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.0"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:text="添加标签"
android:textColor="@color/normal" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="10dp"
android:src="@drawable/icon_arrow_right" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="15dp"
android:text="电话"
android:textSize="13sp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginHorizontal="15dp"
android:layout_marginTop="5dp"
android:background="#F7F7F7"
app:cardCornerRadius="8dp"
app:cardElevation="0dp"
app:cardMaxElevation="0dp">
<TextView
android:id="@+id/tv_telephone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F7F7F7"
android:gravity="center_vertical"
android:paddingHorizontal="16dp"
android:text="添加电话"
android:textColor="@color/normal" />
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="15dp"
android:text="描述"
android:textSize="13sp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginHorizontal="15dp"
android:layout_marginTop="5dp"
android:background="#F7F7F7"
app:cardCornerRadius="8dp"
app:cardElevation="0dp"
app:cardMaxElevation="0dp">
<EditText
android:id="@+id/et_desc"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F7F7F7"
android:hint="添加文字"
android:paddingHorizontal="16dp"
android:textSize="15sp" />
</androidx.cardview.widget.CardView>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="25dp"
app:cardCornerRadius="8dp"
app:cardElevation="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:text="删除联系人"
android:textColor="@color/black"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginTop="15dp"
android:text="将联系人“xxx”删除将同时删除与该联系人的聊天记录"
android:textColor="@color/black"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" />
<View
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:layout_marginTop="35dp"
android:background="@color/gray"
app:layout_constraintTop_toBottomOf="@id/tv_content" />
<TextView
android:id="@+id/tv_cancel"
android:layout_width="0dp"
android:layout_height="45dp"
android:gravity="center"
android:text="取消"
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/view1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/view" />
<View
android:id="@+id/view1"
android:layout_width="@dimen/divider_height"
android:layout_height="0dp"
android:background="@color/gray"
app:layout_constraintBottom_toBottomOf="@id/tv_cancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_cancel" />
<TextView
android:id="@+id/tv_delete"
android:layout_width="0dp"
android:layout_height="45dp"
android:gravity="center"
android:text="删除"
android:textColor="@color/red"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/view1"
app:layout_constraintTop_toBottomOf="@id/view" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -8,6 +8,7 @@
tools:context=".ui.fragment.ContactFragment">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_gravity="center"
@ -21,7 +22,17 @@
android:id="@+id/recycle_friend_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/title"
android:background="@color/white"
android:overScrollMode="never" />
</LinearLayout>
<TextView
android:id="@+id/tv_loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/title"
android:gravity="center"
android:text="正在加载中..."
android:textColor="@color/black" />
</RelativeLayout>

View File

@ -23,7 +23,6 @@
android:layout_height="50dp"
android:layout_marginStart="20dp"
android:layout_marginTop="55dp"
android:src="@drawable/ic_avatar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:roundPercent="0.3" />

View File

@ -60,5 +60,28 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/tv_item_name" />
<RelativeLayout
android:id="@+id/rl_unread_count"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="15dp"
android:background="@drawable/icon_red_dot"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tv_unread_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:text="1"
android:textColor="@color/white"
android:textSize="11sp" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -4,7 +4,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:minHeight="80dp">
android:minHeight="65dp">
<View
android:id="@+id/top"
@ -26,8 +26,8 @@
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv_avatar"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_marginStart="15dp"
android:src="@drawable/ic_avatar"
app:layout_constraintBottom_toBottomOf="parent"
@ -64,7 +64,7 @@
android:layout_marginEnd="15dp"
android:background="@drawable/bac_message_agree_request_btn"
android:paddingHorizontal="14dp"
android:paddingVertical="5dp"
android:paddingVertical="6dp"
android:text="添加"
android:textColor="@color/black"
android:textSize="12sp"

View File

@ -16,8 +16,8 @@
<ImageView
android:id="@+id/iv_item"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_width="26dp"
android:layout_height="26dp"
app:layout_constraintBottom_toBottomOf="@id/iv_bg"
app:layout_constraintEnd_toEndOf="@id/iv_bg"
app:layout_constraintStart_toStartOf="@id/iv_bg"
@ -30,6 +30,7 @@
android:layout_marginTop="5dp"
android:text="哈哈"
android:textColor="#6F6F6F"
android:textSize="11sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/iv_bg"
app:layout_constraintStart_toStartOf="@id/iv_bg"

View File

@ -3,7 +3,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:minHeight="50dp">
<View
@ -56,13 +55,13 @@
android:id="@+id/tv_item_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginEnd="4dp"
android:text="aaaa"
android:textColor="#797979"
android:textSize="15sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/iv_arrow_right"
app:layout_constraintEnd_toStartOf="@id/right_layout"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.utils.widget.ImageFilterView
@ -73,21 +72,34 @@
android:layout_marginEnd="10dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/iv_arrow_right"
app:layout_constraintEnd_toStartOf="@id/right_layout"
app:layout_constraintTop_toTopOf="parent"
app:roundPercent="0.2" />
<ImageView
android:id="@+id/iv_arrow_right"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
<RelativeLayout
android:id="@+id/right_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_arrow_right"
app:layout_constraintBottom_toBottomOf="@id/tv_item_name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_item_name" />
app:layout_constraintTop_toTopOf="@id/tv_item_name">
<ImageView
android:id="@+id/iv_arrow_right"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_centerInParent="true"
android:src="@drawable/icon_arrow_right" />
<com.kaixed.kchat.ui.widget.ShSwitchView
android:id="@+id/ssv_switch"
android:layout_width="57dp"
android:layout_height="38dp"
android:layout_centerInParent="true"
android:visibility="gone"
app:tintColor="@color/green" />
</RelativeLayout>
<View
android:id="@+id/decoration_bottom"

View File

@ -15,11 +15,13 @@
<attr name="itemDesc" format="string" />
<attr name="isTopDividerVisible" format="boolean" />
<attr name="isBottomDividerVisible" format="boolean" />
<attr name="rightType" format="string" />
<attr name="iconSize" format="dimension" />
<attr name="iconRound" format="float" />
<attr name="itemIcon" format="reference" />
<attr name="itemLeftIcon" format="reference" />
<attr name="itemRedTip" format="boolean" />
<attr name="android:background" />
</declare-styleable>

View File

@ -6,6 +6,7 @@
<color name="normal">#576B95</color>
<color name="green">#07C160</color>
<color name="gray">#E5E5E5</color>
<color name="red">#FF3E3E</color>
<color name="light_blue_400">#FF29B6F6</color>
<color name="light_blue_600">#FF039BE5</color>
<color name="gray_400">#FFBDBDBD</color>