refactor: 重构部分逻辑

1.在查找conversation时使用唯一标识代替index查找
2.添加用户发送图片消息(待优化为微信发送图片样式)
3.移动model层到data层
4.重构HomeItem为枚举
5.优化WebsocketService中的发送消息代码
This commit is contained in:
糕小菜 2024-11-29 16:43:48 +08:00
parent 0d56f17f37
commit 4e3f1e914d
83 changed files with 938 additions and 549 deletions

View File

@ -61,9 +61,11 @@ dependencies {
implementation(libs.okhttp) implementation(libs.okhttp)
implementation(libs.mmkv) implementation(libs.mmkv)
implementation(libs.gson) implementation(libs.gson)
implementation(libs.objectbox.kotlin) implementation(libs.objectbox.kotlin)
debugImplementation(libs.objectbox.android.objectbrowser) debugImplementation(libs.objectbox.android.objectbrowser)
releaseImplementation(libs.objectbox.android) releaseImplementation(libs.objectbox.android)
implementation(libs.glide) implementation(libs.glide)
implementation(libs.lottie) implementation(libs.lottie)
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)

View File

@ -5,7 +5,7 @@
"entities": [ "entities": [
{ {
"id": "1:488582047102418567", "id": "1:488582047102418567",
"lastPropertyId": "12:4851920989895940582", "lastPropertyId": "15:6294917834245722650",
"name": "Messages", "name": "Messages",
"properties": [ "properties": [
{ {
@ -53,13 +53,28 @@
"id": "12:4851920989895940582", "id": "12:4851920989895940582",
"name": "show", "name": "show",
"type": 1 "type": 1
},
{
"id": "13:4063945019286198809",
"name": "isShowTimer",
"type": 1
},
{
"id": "14:3576556630733755597",
"name": "isSender",
"type": 1
},
{
"id": "15:6294917834245722650",
"name": "avatarUrl",
"type": 9
} }
], ],
"relations": [] "relations": []
}, },
{ {
"id": "4:6179749773128044271", "id": "4:6179749773128044271",
"lastPropertyId": "13:6446821128426983596", "lastPropertyId": "14:5371512009949707960",
"name": "Contact", "name": "Contact",
"properties": [ "properties": [
{ {
@ -71,7 +86,9 @@
{ {
"id": "2:3202277046871450743", "id": "2:3202277046871450743",
"name": "username", "name": "username",
"type": 9 "indexId": "1:8059396809448816057",
"type": 9,
"flags": 2048
}, },
{ {
"id": "3:8037315472776382017", "id": "3:8037315472776382017",
@ -122,6 +139,11 @@
"id": "13:6446821128426983596", "id": "13:6446821128426983596",
"name": "remarkquanpin", "name": "remarkquanpin",
"type": 9 "type": 9
},
{
"id": "14:5371512009949707960",
"name": "disturb",
"type": 1
} }
], ],
"relations": [] "relations": []
@ -140,7 +162,9 @@
{ {
"id": "2:2771882473472315", "id": "2:2771882473472315",
"name": "username", "name": "username",
"type": 9 "indexId": "3:4492440644813580438",
"type": 9,
"flags": 2048
}, },
{ {
"id": "3:1747776423604377158", "id": "3:1747776423604377158",
@ -172,7 +196,7 @@
}, },
{ {
"id": "6:411582187056789368", "id": "6:411582187056789368",
"lastPropertyId": "9:6199737871252062125", "lastPropertyId": "11:1529665629379902832",
"name": "Conversation", "name": "Conversation",
"properties": [ "properties": [
{ {
@ -184,7 +208,9 @@
{ {
"id": "2:62500236312534806", "id": "2:62500236312534806",
"name": "talkerId", "name": "talkerId",
"type": 9 "indexId": "2:8554188191509595989",
"type": 9,
"flags": 2048
}, },
{ {
"id": "3:1809723298214930621", "id": "3:1809723298214930621",
@ -212,8 +238,13 @@
"type": 5 "type": 5
}, },
{ {
"id": "8:2020630799900991467", "id": "10:1189093311778315437",
"name": "show", "name": "isShow",
"type": 1
},
{
"id": "11:1529665629379902832",
"name": "isPinned",
"type": 1 "type": 1
} }
], ],
@ -221,7 +252,7 @@
} }
], ],
"lastEntityId": "6:411582187056789368", "lastEntityId": "6:411582187056789368",
"lastIndexId": "0:0", "lastIndexId": "3:4492440644813580438",
"lastRelationId": "0:0", "lastRelationId": "0:0",
"lastSequenceId": "0:0", "lastSequenceId": "0:0",
"modelVersion": 5, "modelVersion": 5,
@ -252,7 +283,8 @@
6315401035981995789, 6315401035981995789,
2123413060720974577, 2123413060720974577,
8705063061921345729, 8705063061921345729,
6199737871252062125 6199737871252062125,
2020630799900991467
], ],
"retiredRelationUids": [], "retiredRelationUids": [],
"version": 1 "version": 1

View File

@ -5,7 +5,7 @@
"entities": [ "entities": [
{ {
"id": "1:488582047102418567", "id": "1:488582047102418567",
"lastPropertyId": "12:4851920989895940582", "lastPropertyId": "14:3576556630733755597",
"name": "Messages", "name": "Messages",
"properties": [ "properties": [
{ {
@ -53,13 +53,23 @@
"id": "12:4851920989895940582", "id": "12:4851920989895940582",
"name": "show", "name": "show",
"type": 1 "type": 1
},
{
"id": "13:4063945019286198809",
"name": "isShowTimer",
"type": 1
},
{
"id": "14:3576556630733755597",
"name": "isSender",
"type": 1
} }
], ],
"relations": [] "relations": []
}, },
{ {
"id": "4:6179749773128044271", "id": "4:6179749773128044271",
"lastPropertyId": "13:6446821128426983596", "lastPropertyId": "14:5371512009949707960",
"name": "Contact", "name": "Contact",
"properties": [ "properties": [
{ {
@ -71,7 +81,9 @@
{ {
"id": "2:3202277046871450743", "id": "2:3202277046871450743",
"name": "username", "name": "username",
"type": 9 "indexId": "1:8059396809448816057",
"type": 9,
"flags": 2048
}, },
{ {
"id": "3:8037315472776382017", "id": "3:8037315472776382017",
@ -122,6 +134,11 @@
"id": "13:6446821128426983596", "id": "13:6446821128426983596",
"name": "remarkquanpin", "name": "remarkquanpin",
"type": 9 "type": 9
},
{
"id": "14:5371512009949707960",
"name": "disturb",
"type": 1
} }
], ],
"relations": [] "relations": []
@ -140,7 +157,9 @@
{ {
"id": "2:2771882473472315", "id": "2:2771882473472315",
"name": "username", "name": "username",
"type": 9 "indexId": "3:4492440644813580438",
"type": 9,
"flags": 2048
}, },
{ {
"id": "3:1747776423604377158", "id": "3:1747776423604377158",
@ -172,7 +191,7 @@
}, },
{ {
"id": "6:411582187056789368", "id": "6:411582187056789368",
"lastPropertyId": "9:6199737871252062125", "lastPropertyId": "11:1529665629379902832",
"name": "Conversation", "name": "Conversation",
"properties": [ "properties": [
{ {
@ -184,7 +203,9 @@
{ {
"id": "2:62500236312534806", "id": "2:62500236312534806",
"name": "talkerId", "name": "talkerId",
"type": 9 "indexId": "2:8554188191509595989",
"type": 9,
"flags": 2048
}, },
{ {
"id": "3:1809723298214930621", "id": "3:1809723298214930621",
@ -212,21 +233,21 @@
"type": 5 "type": 5
}, },
{ {
"id": "8:2020630799900991467", "id": "10:1189093311778315437",
"name": "show", "name": "isShow",
"type": 1 "type": 1
}, },
{ {
"id": "9:6199737871252062125", "id": "11:1529665629379902832",
"name": "msgLocalId", "name": "isPinned",
"type": 6 "type": 1
} }
], ],
"relations": [] "relations": []
} }
], ],
"lastEntityId": "6:411582187056789368", "lastEntityId": "6:411582187056789368",
"lastIndexId": "0:0", "lastIndexId": "3:4492440644813580438",
"lastRelationId": "0:0", "lastRelationId": "0:0",
"lastSequenceId": "0:0", "lastSequenceId": "0:0",
"modelVersion": 5, "modelVersion": 5,
@ -256,7 +277,9 @@
3457793996580594247, 3457793996580594247,
6315401035981995789, 6315401035981995789,
2123413060720974577, 2123413060720974577,
8705063061921345729 8705063061921345729,
6199737871252062125,
2020630799900991467
], ],
"retiredRelationUids": [], "retiredRelationUids": [],
"version": 1 "version": 1

View File

@ -3,8 +3,10 @@ package com.kaixed.kchat.data
import com.kaixed.kchat.data.local.box.ObjectBox.getBox import com.kaixed.kchat.data.local.box.ObjectBox.getBox
import com.kaixed.kchat.data.local.entity.Contact import com.kaixed.kchat.data.local.entity.Contact
import com.kaixed.kchat.data.local.entity.Contact_ import com.kaixed.kchat.data.local.entity.Contact_
import com.kaixed.kchat.data.local.entity.Conversation import com.kaixed.kchat.data.local.entity.Messages
import com.kaixed.kchat.data.local.entity.Conversation_ import com.kaixed.kchat.data.local.entity.Messages_
import io.objectbox.Box
import io.objectbox.query.QueryBuilder
/** /**
* @Author: kaixed * @Author: kaixed
@ -13,9 +15,27 @@ import com.kaixed.kchat.data.local.entity.Conversation_
object LocalDatabase { object LocalDatabase {
private val contactBox by lazy { getBox(Contact::class.java) } private val contactBox by lazy { getBox(Contact::class.java) }
private val conversationBox by lazy { getBox(Conversation::class.java) }
fun getContactByUsername(contactId: String): Contact? { fun getContactByUsername(contactId: String): Contact? {
return contactBox.query(Contact_.username.equal(contactId)).build().findFirst() return contactBox.query(Contact_.username.equal(contactId)).build().findFirst()
} }
private val messagesBox: Box<Messages> by lazy { getBox(Messages::class.java) }
fun getMessagesWithContact(contactId: String, offset: Long, limit: Long): List<Messages> {
val query = messagesBox
.query(Messages_.takerId.equal(contactId))
.order(Messages_.timestamp, QueryBuilder.DESCENDING)
.build()
return query.find(offset, limit)
}
fun getMoreMessages(contactId: String, msgLocalId: Long, limit: Long): List<Messages> {
val query = messagesBox
.query(Messages_.takerId.equal(contactId))
.lessOrEqual(Messages_.msgLocalId, msgLocalId)
.order(Messages_.timestamp, QueryBuilder.DESCENDING)
.build()
val offset = 0
return query.find(offset.toLong(), limit)
}
} }

View File

@ -2,6 +2,7 @@ package com.kaixed.kchat.data.local.entity
import io.objectbox.annotation.Entity import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id import io.objectbox.annotation.Id
import io.objectbox.annotation.Index
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /**
@ -13,8 +14,11 @@ import kotlinx.serialization.Serializable
data class Contact( data class Contact(
@Id @Id
var id: Long = 0L, var id: Long = 0L,
@Index
var username: String, var username: String,
@Index
var nickname: String, var nickname: String,
@Index
var remark: String? = null, var remark: String? = null,
var signature: String? = null, var signature: String? = null,
var avatarUrl: String? = null, var avatarUrl: String? = null,
@ -24,4 +28,5 @@ data class Contact(
var showHeader: Boolean? = false, var showHeader: Boolean? = false,
var address: String? = null, var address: String? = null,
var gender: String? = null, var gender: String? = null,
var disturb: Boolean = true
) )

View File

@ -2,6 +2,7 @@ package com.kaixed.kchat.data.local.entity
import io.objectbox.annotation.Entity import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id import io.objectbox.annotation.Id
import io.objectbox.annotation.Index
/** /**
* @Author: kaixed * @Author: kaixed
@ -12,11 +13,13 @@ import io.objectbox.annotation.Id
data class Conversation( data class Conversation(
@Id @Id
var id: Long = 0L, var id: Long = 0L,
@Index
var talkerId: String, var talkerId: String,
var nickname: String, var nickname: String,
var avatarUrl: String, var avatarUrl: String,
var lastContent: String, var lastContent: String,
var timestamp: Long, var timestamp: Long,
var unreadCount: Int = 0, var unreadCount: Int = 0,
var show: Boolean = true var isShow: Boolean = true,
var isPinned: Boolean = false
) )

View File

@ -19,8 +19,10 @@ data class Messages(
var timestamp: Long, var timestamp: Long,
var status: String = "normal", var status: String = "normal",
var senderId: String, var senderId: String,
var avatarUrl: String = "",
var takerId: String, var takerId: String,
var type: String, var type: String,
var show: Boolean = true, var show: Boolean = true,
// var isShowTimer: Boolean = false var isShowTimer: Boolean = false,
var isSender: Boolean = false
) )

View File

@ -2,6 +2,7 @@ package com.kaixed.kchat.data.local.entity
import io.objectbox.annotation.Entity import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id import io.objectbox.annotation.Id
import io.objectbox.annotation.Index
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /**
@ -14,6 +15,7 @@ import kotlinx.serialization.Serializable
data class UserInfo( data class UserInfo(
@Id @Id
var id: Long = 0, var id: Long = 0,
@Index
var username: String, var username: String,
var nickname: String, var nickname: String,
var avatarUrl: String, var avatarUrl: String,

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.model package com.kaixed.kchat.data.model
/** /**
* @Author: kaixed * @Author: kaixed

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.model package com.kaixed.kchat.data.model
/** /**
* @Author: kaixed * @Author: kaixed

View File

@ -0,0 +1,12 @@
package com.kaixed.kchat.data.model
/**
* @Author: kaixed
* @Date: 2024/11/28 10:33
*/
data class Message(
val id: Long,
val talkerId: String,
var lastContent: String,
val timestamp: Long
)

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.model.friend package com.kaixed.kchat.data.model.friend
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.model.item package com.kaixed.kchat.data.model.item
/** /**
* @Author: kaixed * @Author: kaixed

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.model.request package com.kaixed.kchat.data.model.request
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.model.request package com.kaixed.kchat.data.model.request
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.model.response.register package com.kaixed.kchat.data.model.response.register
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.model.response.search package com.kaixed.kchat.data.model.response.search
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.model.search package com.kaixed.kchat.data.model.search
/** /**
* @Author: kaixed * @Author: kaixed
@ -10,5 +10,4 @@ data class SearchItem(
val content: String, val content: String,
val hasMore: Boolean, val hasMore: Boolean,
val type: String, val type: String,
) )

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.model.search package com.kaixed.kchat.data.model.search
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.model.search package com.kaixed.kchat.data.model.search
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable

View File

@ -3,8 +3,8 @@ package com.kaixed.kchat.data.repository
import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore
import com.kaixed.kchat.data.local.entity.Contact import com.kaixed.kchat.data.local.entity.Contact
import com.kaixed.kchat.data.local.entity.Contact_ import com.kaixed.kchat.data.local.entity.Contact_
import com.kaixed.kchat.model.friend.FriendRequestItem import com.kaixed.kchat.data.model.friend.FriendRequestItem
import com.kaixed.kchat.model.search.User import com.kaixed.kchat.data.model.search.SearchUser
import com.kaixed.kchat.network.ApiCall.apiCall import com.kaixed.kchat.network.ApiCall.apiCall
import com.kaixed.kchat.network.RetrofitClient import com.kaixed.kchat.network.RetrofitClient
import com.kaixed.kchat.utils.ConstantsUtil.getUsername import com.kaixed.kchat.utils.ConstantsUtil.getUsername
@ -26,19 +26,6 @@ class ContactRepository {
apiCall = { contactApiService.getContactRequestList(username) }, apiCall = { contactApiService.getContactRequestList(username) },
errorMessage = "获取好友申请列表失败" errorMessage = "获取好友申请列表失败"
) )
// return try {
// val response = contactApiService.getContactRequestList(username)
// if (response.isSuccess()) {
// val searchUser = response.getResponseData()
// searchUser?.let {
// Result.success(searchUser)
// } ?: Result.failure(Exception("没有好友申请"))
// } else {
// Result.failure(Exception(response.getResponseMsg()))
// }
// } catch (e: Exception) {
// Result.failure(e)
// }
} }
// 接受联系人请求 // 接受联系人请求
@ -54,20 +41,6 @@ class ContactRepository {
it?.let { it?.let {
ContactUtil.handleContact(it) ContactUtil.handleContact(it)
} }
// return try {
// val response = contactApiService.acceptContactRequest(contactId, username, remark)
// if (response.isSuccess()) {
// val searchUser = response.getResponseData()
// searchUser?.let {
// ContactUtil.handleContact(searchUser)
// Result.success(searchUser)
// } ?: Result.failure(Exception("添加好友失败"))
// } else {
// Result.failure(Exception(response.getResponseMsg()))
// }
// } catch (e: Exception) {
// Result.failure(e)
// }
} }
} }
@ -84,22 +57,6 @@ class ContactRepository {
contactBox.query(Contact_.username.equal(contactId)).build().findFirst() contactBox.query(Contact_.username.equal(contactId)).build().findFirst()
contactBox.remove(con!!) contactBox.remove(con!!)
} }
// return try {
// val response = contactApiService.deleteContact(username, contactId)
// if (response.isSuccess()) {
// val searchUser = response.getResponseData()
// searchUser?.let {
// val con =
// contactBox.query(Contact_.username.equal(contactId)).build().findFirst()
// contactBox.remove(con!!)
// Result.success(searchUser)
// } ?: Result.failure(Exception("删除好友失败"))
// } else {
// Result.failure(Exception(response.getResponseMsg()))
// }
// } catch (e: Exception) {
// Result.failure(e)
// }
} }
// 添加联系人 // 添加联系人
@ -108,44 +65,14 @@ class ContactRepository {
apiCall = { contactApiService.addContact(contactId, getUsername(), message) }, apiCall = { contactApiService.addContact(contactId, getUsername(), message) },
errorMessage = "添加联系人失败" errorMessage = "添加联系人失败"
) )
// return try {
// val response = contactApiService.addContact(
// senderId = contactId,
// receiverId = getUsername(),
// message = message
// )
// if (response.isSuccess()) {
// val searchUser = response.getResponseData()
// searchUser?.let {
// Result.success(searchUser)
// } ?: Result.failure(Exception("添加联系人失败"))
// } else {
// Result.failure(Exception(response.getResponseMsg()))
// }
// } catch (e: Exception) {
// Result.failure(e)
// }
} }
// 搜索联系人 // 搜索联系人
suspend fun searchContact(username: String): Result<User?> { suspend fun searchContact(username: String): Result<SearchUser?> {
return apiCall( return apiCall(
apiCall = { contactApiService.searchContact(username) }, apiCall = { contactApiService.searchContact(username) },
errorMessage = "搜索用户失败" errorMessage = "搜索用户失败"
) )
// return try {
// val response = contactApiService.searchContact(username)
// if (response.isSuccess()) {
// val searchUser = response.getResponseData()
// searchUser?.let {
// Result.success(searchUser)
// } ?: Result.failure(Exception("没有找到用户"))
// } else {
// Result.failure(Exception(response.getResponseMsg()))
// }
// } catch (e: Exception) {
// Result.failure(e)
// }
} }
// 获取联系人列表 // 获取联系人列表

View File

@ -0,0 +1,30 @@
package com.kaixed.kchat.data.repository
import com.kaixed.kchat.network.ApiCall.apiCall
import com.kaixed.kchat.network.RetrofitClient
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
/**
* @Author: kaixed
* @Date: 2024/11/29 15:15
*/
class FileRepository {
private val fileService = RetrofitClient.fileApiService
suspend fun uploadFile(file: File) =
apiCall(
apiCall = {
val multiFile = MultipartBody.Part.createFormData(
"file",
file.name,
file.asRequestBody("application/octet-stream".toMediaTypeOrNull())
)
fileService.uploadFile(multiFile)
},
errorMessage = "获取好友申请列表失败"
)
}

View File

@ -3,8 +3,8 @@ package com.kaixed.kchat.data.repository
import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore
import com.kaixed.kchat.data.local.entity.UserInfo import com.kaixed.kchat.data.local.entity.UserInfo
import com.kaixed.kchat.data.local.entity.UserInfo_ import com.kaixed.kchat.data.local.entity.UserInfo_
import com.kaixed.kchat.model.request.RegisterRequest import com.kaixed.kchat.data.model.request.RegisterRequest
import com.kaixed.kchat.model.response.register.Register import com.kaixed.kchat.data.model.response.register.Register
import com.kaixed.kchat.network.ApiCall.apiCall import com.kaixed.kchat.network.ApiCall.apiCall
import com.kaixed.kchat.network.RetrofitClient import com.kaixed.kchat.network.RetrofitClient
import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION
@ -33,28 +33,6 @@ class UserAuthRepository {
insertUserInfo(register, registerRequest.telephone) insertUserInfo(register, registerRequest.telephone)
} }
} }
// return try {
// val response = userApiService.register(registerRequest)
// if (response.isSuccess()) {
// val register = response.getResponseData()
// register?.let {
// val userInfo = UserInfo(
// username = register.username,
// nickname = register.nickname,
// avatarUrl = "",
// signature = "",
// telephone = registerRequest.telephone
// )
// userInfoBox.put(userInfo)
//
// Result.success(register)
// } ?: Result.failure(Exception("注册成功,但未返回用户数据"))
// } else {
// Result.failure(Exception(response.getResponseMsg()))
// }
// } catch (e: Exception) {
// Result.failure(e)
// }
} }
// 登录方法 // 登录方法
@ -67,22 +45,6 @@ class UserAuthRepository {
updateDb(userInfo) updateDb(userInfo)
} }
} }
// return try {
// val response = userApiService.loginByUsername(username, password)
// if (response.isSuccess()) {
// val userInfo = response.getResponseData()
// if (userInfo != null) {
// updateDb(userInfo)
// Result.success(userInfo)
// } else {
// Result.failure(Exception("登录成功,但未返回用户数据"))
// }
// } else {
// Result.failure(Exception(response.getResponseMsg()))
// }
// } catch (e: Exception) {
// Result.failure(e)
// }
} }
@ -95,23 +57,12 @@ class UserAuthRepository {
updateDb(userInfo) updateDb(userInfo)
} }
} }
// return try {
// val response = userApiService.loginByTelephone(telephone, password)
// if (response.isSuccess()) {
// val userInfo = response.getResponseData()
// userInfo?.let {
// updateDb(userInfo)
// Result.success(userInfo)
// } ?: Result.failure(Exception("登录成功,但未返回用户数据"))
// } else {
// Result.failure(Exception(response.getResponseMsg()))
// }
// } catch (e: Exception) {
// Result.failure(e)
// }
} }
private fun insertUserInfo(register: Register, telephone: String) { private fun insertUserInfo(
register: Register,
telephone: String
) {
val userInfo = UserInfo( val userInfo = UserInfo(
username = register.username, username = register.username,
nickname = register.nickname, nickname = register.nickname,

View File

@ -3,8 +3,8 @@ package com.kaixed.kchat.data.repository
import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore
import com.kaixed.kchat.data.local.entity.UserInfo import com.kaixed.kchat.data.local.entity.UserInfo
import com.kaixed.kchat.data.local.entity.UserInfo_ import com.kaixed.kchat.data.local.entity.UserInfo_
import com.kaixed.kchat.model.request.UserRequest import com.kaixed.kchat.data.model.request.UserRequest
import com.kaixed.kchat.model.search.SearchUser import com.kaixed.kchat.data.model.search.SearchUser
import com.kaixed.kchat.network.RetrofitClient import com.kaixed.kchat.network.RetrofitClient
import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION
import com.kaixed.kchat.utils.Constants.NICKNAME_KEY import com.kaixed.kchat.utils.Constants.NICKNAME_KEY

View File

@ -1,6 +1,6 @@
package com.kaixed.kchat.data.repository package com.kaixed.kchat.data.repository
import com.kaixed.kchat.model.response.search.User import com.kaixed.kchat.data.model.response.search.User
import com.kaixed.kchat.network.RetrofitClient import com.kaixed.kchat.network.RetrofitClient
/** /**

View File

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

View File

@ -1,10 +0,0 @@
package com.kaixed.kchat.model.friend
import kotlinx.serialization.Serializable
@Serializable
data class ContactRequestResponse(
val code: String,
val msg: String,
val `data`: List<FriendRequestItem>,
)

View File

@ -1,17 +0,0 @@
package com.kaixed.kchat.model.friend
import kotlinx.serialization.Serializable
/**
* @Author: kaixed
* @Date: 2024/10/14 14:30
*/
@Serializable
data class FriendItem(
val username: String,
val avatarUrl: String,
val nickname: String,
val remark: String,
val signature: String,
)

View File

@ -1,10 +0,0 @@
package com.kaixed.kchat.model.response
import kotlinx.serialization.Serializable
@Serializable
data class ApplyFriend(
val code: String,
val `data`: String,
val msg: String
)

View File

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

View File

@ -1,16 +0,0 @@
package com.kaixed.kchat.model.response.friend
import com.kaixed.kchat.data.local.entity.Contact
import kotlinx.serialization.Serializable
/**
* @Author: kaixed
* @Date: 2024/10/17 22:00
*/
@Serializable
data class FriendList(
val code: String,
val msg: String,
val `data`: List<Contact>?,
)

View File

@ -1,15 +0,0 @@
package com.kaixed.kchat.model.response.friend
import com.kaixed.kchat.model.search.User
import kotlinx.serialization.Serializable
/**
* @Author: kaixed
* @Date: 2024/9/22 22:59
*/
@Serializable
data class SearchFriends(
val code: String,
val msg: String,
val `data`: User?
)

View File

@ -1,14 +0,0 @@
package com.kaixed.kchat.model.response.login
import kotlinx.serialization.Serializable
@Serializable
data class Data(
var id: Long,
val avatarUrl: String,
val nickname: String,
val signature: String,
var telephone: String,
val status: String?,
val username: String,
)

View File

@ -1,11 +0,0 @@
package com.kaixed.kchat.model.response.login
import com.kaixed.kchat.data.local.entity.UserInfo
import kotlinx.serialization.Serializable
@Serializable
data class Login(
val code: String,
val msg: String,
val `data`: UserInfo?,
)

View File

@ -1,13 +0,0 @@
package com.kaixed.kchat.model.response.search
import kotlinx.serialization.Serializable
/**
* @Author: kaixed
* @Date: 2024/10/16 13:35
*/
@Serializable
data class Data(
val userLists: List<User>,
)

View File

@ -1,15 +0,0 @@
package com.kaixed.kchat.model.response.search
import kotlinx.serialization.Serializable
/**
* @Author: kaixed
* @Date: 2024/10/16 13:34
*/
@Serializable
data class UserList(
val code: String,
val msg: String,
val data: Data,
)

View File

@ -1,10 +0,0 @@
package com.kaixed.kchat.model.response.user
import kotlinx.serialization.Serializable
@Serializable
data class ChangeNickname(
val code: String,
val `data`: String?,
val msg: String
)

View File

@ -1,14 +0,0 @@
package com.kaixed.kchat.model.response.user
import kotlinx.serialization.Serializable
/**
* @Author: kaixed
* @Date: 2024/11/14 21:38
*/
@Serializable
data class UploadAvatar(
var code: String,
var msg: String,
var data: String?
)

View File

@ -1,7 +1,7 @@
package com.kaixed.kchat.network package com.kaixed.kchat.network
import com.kaixed.kchat.network.interceptor.SignInterceptor
import com.kaixed.kchat.network.service.ContactService import com.kaixed.kchat.network.service.ContactService
import com.kaixed.kchat.network.service.FileApiService
import com.kaixed.kchat.network.service.UserApiService import com.kaixed.kchat.network.service.UserApiService
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
@ -42,4 +42,8 @@ object RetrofitClient {
val contactApiService: ContactService by lazy { val contactApiService: ContactService by lazy {
retrofit.create(ContactService::class.java) retrofit.create(ContactService::class.java)
} }
val fileApiService: FileApiService by lazy {
retrofit.create(FileApiService::class.java)
}
} }

View File

@ -3,7 +3,7 @@ package com.kaixed.kchat.network
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonParser import com.google.gson.JsonParser
import com.kaixed.kchat.model.request.UserRequest import com.kaixed.kchat.data.model.request.UserRequest
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
@ -103,7 +103,12 @@ object SignUtil {
val request = Request.Builder() val request = Request.Builder()
.url("https://app.kaixed.com/kchat/users/login/username") .url("https://app.kaixed.com/kchat/users/login/username")
.post( .post(
Gson().toJson(UserRequest("username", "password")) Gson().toJson(
UserRequest(
"username",
"password"
)
)
.toRequestBody("application/json".toMediaType()) .toRequestBody("application/json".toMediaType())
) )
.build() .build()

View File

@ -1,8 +1,8 @@
package com.kaixed.kchat.network.service package com.kaixed.kchat.network.service
import com.kaixed.kchat.data.local.entity.Contact import com.kaixed.kchat.data.local.entity.Contact
import com.kaixed.kchat.model.friend.FriendRequestItem import com.kaixed.kchat.data.model.friend.FriendRequestItem
import com.kaixed.kchat.model.search.User import com.kaixed.kchat.data.model.search.SearchUser
import com.kaixed.kchat.network.ApiResponse import com.kaixed.kchat.network.ApiResponse
import retrofit2.http.Field import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded import retrofit2.http.FormUrlEncoded
@ -10,6 +10,10 @@ import retrofit2.http.GET
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Path import retrofit2.http.Path
/**
* @Author: kaixed
* @Date: 2024/11/15 11:00
*/
interface ContactService { interface ContactService {
// 获取联系人请求列表 // 获取联系人请求列表
@ -41,7 +45,7 @@ interface ContactService {
@GET("users/{username}") @GET("users/{username}")
suspend fun searchContact( suspend fun searchContact(
@Path("username") username: String @Path("username") username: String
): ApiResponse<User?> ): ApiResponse<SearchUser?>
// 获取联系人列表 // 获取联系人列表
@FormUrlEncoded @FormUrlEncoded

View File

@ -0,0 +1,20 @@
package com.kaixed.kchat.network.service
import com.kaixed.kchat.network.ApiResponse
import okhttp3.MultipartBody
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
/**
* @Author: kaixed
* @Date: 2024/11/29 15:11
*/
interface FileApiService {
@Multipart
@POST("file/upload")
suspend fun uploadFile(
@Part file: MultipartBody.Part
): ApiResponse<String>
}

View File

@ -1,11 +1,11 @@
package com.kaixed.kchat.network.service package com.kaixed.kchat.network.service
import com.kaixed.kchat.data.local.entity.UserInfo import com.kaixed.kchat.data.local.entity.UserInfo
import com.kaixed.kchat.model.request.RegisterRequest import com.kaixed.kchat.data.model.request.RegisterRequest
import com.kaixed.kchat.model.request.UserRequest import com.kaixed.kchat.data.model.request.UserRequest
import com.kaixed.kchat.model.response.register.Register import com.kaixed.kchat.data.model.response.register.Register
import com.kaixed.kchat.model.response.search.User import com.kaixed.kchat.data.model.response.search.User
import com.kaixed.kchat.model.search.SearchUser import com.kaixed.kchat.data.model.search.SearchUser
import com.kaixed.kchat.network.ApiResponse import com.kaixed.kchat.network.ApiResponse
import okhttp3.MultipartBody import okhttp3.MultipartBody
import retrofit2.http.Body import retrofit2.http.Body

View File

@ -9,9 +9,12 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.google.gson.Gson import com.google.gson.Gson
import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore
import com.kaixed.kchat.data.local.entity.Contact
import com.kaixed.kchat.data.local.entity.Contact_
import com.kaixed.kchat.data.local.entity.Conversation import com.kaixed.kchat.data.local.entity.Conversation
import com.kaixed.kchat.data.local.entity.Conversation_ import com.kaixed.kchat.data.local.entity.Conversation_
import com.kaixed.kchat.data.local.entity.Messages import com.kaixed.kchat.data.local.entity.Messages
import com.kaixed.kchat.data.local.entity.Messages_
import com.kaixed.kchat.network.NetworkInterface.WEBSOCKET import com.kaixed.kchat.network.NetworkInterface.WEBSOCKET
import com.kaixed.kchat.network.NetworkInterface.WEBSOCKET_SERVER_URL import com.kaixed.kchat.network.NetworkInterface.WEBSOCKET_SERVER_URL
import com.kaixed.kchat.network.OkhttpHelper import com.kaixed.kchat.network.OkhttpHelper
@ -88,9 +91,42 @@ class WebSocketService : Service() {
} }
fun storeOwnerMsg(messages: Messages) { fun storeOwnerMsg(messages: Messages) {
if (messages.avatarUrl == "") {
val contactBox: Box<Contact> = getBoxStore().boxFor(Contact::class.java)
val contact =
contactBox.query(Contact_.username.equal(messages.takerId)).build().findFirst()
messages.avatarUrl = contact?.avatarUrl ?: ""
}
updateConversationList(messages) updateConversationList(messages)
} }
fun deleteConversationList(con: Conversation) {
val conversation = conversationList.find { it.talkerId == con.talkerId }!!
conversationList.remove(conversation)
conversationBox.remove(conversation.id)
messagesBox.query(Messages_.takerId.equal(con.talkerId)).build().remove()
_conversations.postValue(conversationList.sortedByDescending { it.timestamp })
}
fun updateConversationList(con: Conversation) {
val conversation = conversationList.find { it.talkerId == con.talkerId }!!
if (!con.isShow) {
conversation.apply {
isShow = false
conversationList.remove(conversation)
}
}
conversation.apply {
isPinned = con.isPinned
unreadCount = con.unreadCount
}
conversationBox.put(conversation)
_conversations.postValue(conversationList.sortedByDescending { it.timestamp })
}
private fun establishConnection() { private fun establishConnection() {
if (webSocket == null) { if (webSocket == null) {
val request = Request.Builder() val request = Request.Builder()
@ -107,32 +143,38 @@ class WebSocketService : Service() {
.findFirst() .findFirst()
val currentContactId = getCurrentContactId() val currentContactId = getCurrentContactId()
if (conversation != null) {
conversation?.let { conversation.apply {
conversation.unreadCount = unreadCount =
if (conversation.talkerId == currentContactId || messages.senderId == getUsername()) 0 if (talkerId == currentContactId || messages.senderId == getUsername()) 0 else unreadCount + 1
else conversation.unreadCount + 1 lastContent = if (messages.type == "4") "[图片]" else messages.content
timestamp = messages.timestamp
conversation.lastContent = if (messages.type == "4") "[图片]" else messages.content }
conversation.timestamp = messages.timestamp
conversationBox.put(conversation) conversationBox.put(conversation)
} ?: run { } else {
conversationBox.put( val con = createChatList(messages)
createChatList( conversationBox.put(con)
}
}
private fun createChatList(
messages: Messages
): Conversation {
return Conversation(
talkerId = messages.takerId, talkerId = messages.takerId,
nickname = messages.takerId, nickname = messages.takerId,
content = messages.content, avatarUrl = messages.avatarUrl,
lastContent = if (messages.type == "4") "[图片]" else messages.content,
timestamp = messages.timestamp, timestamp = messages.timestamp,
unreadCount = if (messages.senderId == getUsername()) 0 else 1 unreadCount = if (messages.senderId == getUsername()) 0 else 1
) )
)
}
} }
private fun createChatList( private fun createChatList(
talkerId: String, talkerId: String,
nickname: String, nickname: String,
content: String, content: String,
avatarUrl: String,
timestamp: Long, timestamp: Long,
unreadCount: Int = 1 unreadCount: Int = 1
): Conversation { ): Conversation {
@ -140,7 +182,7 @@ class WebSocketService : Service() {
0L, 0L,
talkerId = talkerId, talkerId = talkerId,
nickname = nickname, nickname = nickname,
avatarUrl = "https://s3.qjqq.cn/49/660ff4a698da0.webp!color", avatarUrl = avatarUrl,
lastContent = content, lastContent = content,
timestamp = timestamp, timestamp = timestamp,
unreadCount = unreadCount unreadCount = unreadCount
@ -215,6 +257,7 @@ class WebSocketService : Service() {
nickname = messages.takerId, nickname = messages.takerId,
content = if (messages.type == "4") "[图片]" else messages.content, content = if (messages.type == "4") "[图片]" else messages.content,
timestamp = messages.timestamp, timestamp = messages.timestamp,
avatarUrl = messages.avatarUrl,
unreadCount = if (messages.senderId == getUsername()) 0 else 1 unreadCount = if (messages.senderId == getUsername()) 0 else 1
) )
) )
@ -223,7 +266,7 @@ class WebSocketService : Service() {
} }
private fun firstLoad() { private fun firstLoad() {
conversationList = conversationBox.query(Conversation_.show.equal(true)) conversationList = conversationBox.query(Conversation_.isShow.equal(true))
.orderDesc(Conversation_.timestamp) .orderDesc(Conversation_.timestamp)
.build() .build()
.find() .find()

View File

@ -6,9 +6,9 @@ import android.os.Bundle
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.kaixed.kchat.data.model.friend.FriendRequestItem
import com.kaixed.kchat.data.model.search.User
import com.kaixed.kchat.databinding.ActivityApplyFriendsDetailBinding import com.kaixed.kchat.databinding.ActivityApplyFriendsDetailBinding
import com.kaixed.kchat.model.friend.FriendRequestItem
import com.kaixed.kchat.model.search.User
import com.kaixed.kchat.ui.base.BaseActivity import com.kaixed.kchat.ui.base.BaseActivity
class ApplyFriendsDetailActivity : BaseActivity<ActivityApplyFriendsDetailBinding>() { class ApplyFriendsDetailActivity : BaseActivity<ActivityApplyFriendsDetailBinding>() {
@ -33,7 +33,10 @@ class ApplyFriendsDetailActivity : BaseActivity<ActivityApplyFriendsDetailBindin
@RequiresApi(Build.VERSION_CODES.TIRAMISU) @RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun initData() { override fun initData() {
user = intent.getParcelableExtra("user", User::class.java) user = intent.getParcelableExtra("user", User::class.java)
request = intent.getParcelableExtra("request", FriendRequestItem::class.java) request = intent.getParcelableExtra(
"request",
FriendRequestItem::class.java
)
} }
private fun setContent() { private fun setContent() {

View File

@ -7,7 +7,7 @@ import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.kaixed.kchat.databinding.ActivityApproveDetailBinding import com.kaixed.kchat.databinding.ActivityApproveDetailBinding
import com.kaixed.kchat.model.friend.FriendRequestItem import com.kaixed.kchat.data.model.friend.FriendRequestItem
import com.kaixed.kchat.ui.base.BaseActivity import com.kaixed.kchat.ui.base.BaseActivity
class ApproveDetailActivity : BaseActivity<ActivityApproveDetailBinding>() { class ApproveDetailActivity : BaseActivity<ActivityApproveDetailBinding>() {

View File

@ -9,7 +9,10 @@ import android.graphics.Color
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.IBinder import android.os.IBinder
import android.os.Looper
import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
@ -18,36 +21,47 @@ import android.view.inputmethod.InputMethodManager
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.kaixed.kchat.R import com.kaixed.kchat.R
import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore import com.kaixed.kchat.data.LocalDatabase.getMessagesWithContact
import com.kaixed.kchat.data.LocalDatabase.getMoreMessages
import com.kaixed.kchat.data.local.box.ObjectBox.getBox
import com.kaixed.kchat.data.local.entity.Messages import com.kaixed.kchat.data.local.entity.Messages
import com.kaixed.kchat.data.local.entity.Messages_ import com.kaixed.kchat.data.model.FunctionItem
import com.kaixed.kchat.databinding.ActivityChatBinding import com.kaixed.kchat.databinding.ActivityChatBinding
import com.kaixed.kchat.model.FunctionItem
import com.kaixed.kchat.service.WebSocketService import com.kaixed.kchat.service.WebSocketService
import com.kaixed.kchat.service.WebSocketService.LocalBinder import com.kaixed.kchat.service.WebSocketService.LocalBinder
import com.kaixed.kchat.ui.adapter.ChatAdapter import com.kaixed.kchat.ui.adapter.ChatAdapter
import com.kaixed.kchat.ui.adapter.EmojiAdapter import com.kaixed.kchat.ui.adapter.EmojiAdapter
import com.kaixed.kchat.ui.adapter.FunctionPanelAdapter import com.kaixed.kchat.ui.adapter.FunctionPanelAdapter
import com.kaixed.kchat.ui.base.BaseActivity import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.ui.i.IOnItemClickListener
import com.kaixed.kchat.ui.i.OnItemClickListener import com.kaixed.kchat.ui.i.OnItemClickListener
import com.kaixed.kchat.ui.widget.LoadingDialogFragment
import com.kaixed.kchat.utils.Constants.CURRENT_CONTACT_ID import com.kaixed.kchat.utils.Constants.CURRENT_CONTACT_ID
import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT_RATIO import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT_RATIO
import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION
import com.kaixed.kchat.utils.ConstantsUtil.getKeyboardHeight import com.kaixed.kchat.utils.ConstantsUtil.getKeyboardHeight
import com.kaixed.kchat.utils.ConstantsUtil.getUsername import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.kaixed.kchat.utils.ImageEngines
import com.kaixed.kchat.utils.ImageSpanUtil.insertEmoji import com.kaixed.kchat.utils.ImageSpanUtil.insertEmoji
import com.kaixed.kchat.viewmodel.FileViewModel
import com.luck.picture.lib.basic.PictureSelector
import com.luck.picture.lib.config.SelectMimeType
import com.luck.picture.lib.entity.LocalMedia
import com.luck.picture.lib.interfaces.OnResultCallbackListener
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import io.objectbox.Box import io.objectbox.Box
import io.objectbox.query.QueryBuilder
import org.json.JSONObject import org.json.JSONObject
import java.io.File
import java.util.LinkedList import java.util.LinkedList
class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener { class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener,
IOnItemClickListener {
private var chatAdapter: ChatAdapter? = null private var chatAdapter: ChatAdapter? = null
@ -55,7 +69,7 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
private val messagesList = LinkedList<Messages>() private val messagesList = LinkedList<Messages>()
private lateinit var messagesBox: Box<Messages> private val messagesBox: Box<Messages> by lazy { getBox(Messages::class.java) }
private var contactId: String = "" private var contactId: String = ""
@ -85,8 +99,9 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
private var lastMessage: Messages? = null private var lastMessage: Messages? = null
private val fileViewModel: FileViewModel by viewModels()
companion object { companion object {
private const val TAG = "ChatActivity"
private const val LIMIT: Long = 20L private const val LIMIT: Long = 20L
private const val UNBLOCK_DELAY_TIME = 200L private const val UNBLOCK_DELAY_TIME = 200L
} }
@ -164,7 +179,7 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
false false
} }
binding.ivFunctionPanel.setOnClickListener { v -> binding.ivFunctionPanel.setOnClickListener {
if (binding.clBottomPanel.isShown) { if (binding.clBottomPanel.isShown) {
if (binding.rvEmoji.isShown) { if (binding.rvEmoji.isShown) {
handlePanelSwitch(false) handlePanelSwitch(false)
@ -176,7 +191,7 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
} }
} }
binding.ivEmoji.setOnClickListener { v -> binding.ivEmoji.setOnClickListener {
if (binding.clBottomPanel.isShown) { if (binding.clBottomPanel.isShown) {
if (binding.gvFunctionPanel.isShown) { if (binding.gvFunctionPanel.isShown) {
handlePanelSwitch(true) handlePanelSwitch(true)
@ -334,6 +349,7 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
} }
val functionPanelAdapter = FunctionPanelAdapter(functionItems, this) val functionPanelAdapter = FunctionPanelAdapter(functionItems, this)
functionPanelAdapter.setOnItemClickListener(this)
binding.gvFunctionPanel.adapter = functionPanelAdapter binding.gvFunctionPanel.adapter = functionPanelAdapter
} }
@ -377,7 +393,6 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
contactNickname = intent.getStringExtra("contactNickname") contactNickname = intent.getStringExtra("contactNickname")
binding.ctb.setTitleName(contactNickname!!) binding.ctb.setTitleName(contactNickname!!)
messagesBox = getBoxStore().boxFor(Messages::class.java)
softKeyboardHeight = getKeyboardHeight() softKeyboardHeight = getKeyboardHeight()
mmkv.putString(CURRENT_CONTACT_ID, contactId) mmkv.putString(CURRENT_CONTACT_ID, contactId)
} }
@ -394,7 +409,7 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
* 初次进入进行加载历史数据 * 初次进入进行加载历史数据
*/ */
private fun firstLoadData() { private fun firstLoadData() {
val messages = queryData(0, LIMIT + 1) val messages = getMessagesWithContact(contactId, 0, LIMIT + 1)
if (messages.isNotEmpty()) { if (messages.isNotEmpty()) {
val size = messages.size val size = messages.size
hasHistory = size > LIMIT hasHistory = size > LIMIT
@ -413,7 +428,7 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
loading = true loading = true
val newMessages: List<Messages> = queryDataById(tempIndex, LIMIT + 1) val newMessages: List<Messages> = getMoreMessages(contactId, tempIndex, LIMIT + 1)
val size = newMessages.size val size = newMessages.size
tempIndex = newMessages[size - 1].msgLocalId tempIndex = newMessages[size - 1].msgLocalId
@ -480,7 +495,7 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
jsonObject2.put("receiverId", contactId) jsonObject2.put("receiverId", contactId)
jsonObject2.put("content", message) jsonObject2.put("content", message)
jsonObject2.put("msgLocalId", id) jsonObject2.put("msgLocalId", id)
jsonObject.put("type", "single") jsonObject.put("type", if (type == "4") "image" else "text")
jsonObject.put("body", jsonObject2) jsonObject.put("body", jsonObject2)
webSocketService!!.sendMessage(jsonObject.toString(), id) webSocketService!!.sendMessage(jsonObject.toString(), id)
webSocketService!!.storeOwnerMsg(messages) webSocketService!!.storeOwnerMsg(messages)
@ -494,23 +509,6 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
binding.recycleChatList.smoothScrollToPosition(0) binding.recycleChatList.smoothScrollToPosition(0)
} }
private fun queryData(offset: Long, limit: Long): List<Messages> {
val query = messagesBox
.query(Messages_.takerId.equal(contactId))
.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(contactId))
.lessOrEqual(Messages_.msgLocalId, msgLocalId)
.order(Messages_.timestamp, QueryBuilder.DESCENDING)
.build()
val offset = 0
return query.find(offset.toLong(), limit)
}
private fun bindWebSocketService() { private fun bindWebSocketService() {
bindService( bindService(
@ -531,4 +529,64 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
bound = false bound = false
} }
} }
private fun uploadFile(imagePath: String) {
val loadingDialog: LoadingDialogFragment =
LoadingDialogFragment.newInstance("正在上传...")
loadingDialog.showLoading(supportFragmentManager)
if (imagePath.isEmpty()) {
return
}
val file = File(imagePath)
fileViewModel.uploadFileResult.observe(this) { result ->
result.onSuccess {
sendMessage(contactId, it!!, "4")
toast("上传成功")
}
result.onFailure {
toast("上传失败")
}
loadingDialog.dismissLoading()
}
fileViewModel.uploadFile(file)
}
//TODO: 待优化成微信选择上传模式,当前上传模式耗费用户等待时间
private fun selectPicture() {
PictureSelector.create(this)
.openGallery(SelectMimeType.ofImage())
.setImageEngine(ImageEngines())
.forResult(object : OnResultCallbackListener<LocalMedia?> {
override fun onResult(result: ArrayList<LocalMedia?>) {
result.forEach { localMedia ->
localMedia?.let {
val imagePath = it.realPath
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({
uploadFile(imagePath)
}, 200L)
Log.d("haha", "图片路径: $imagePath")
}
}
}
override fun onCancel() {
toast("已取消")
}
})
}
override fun onFunctionItemClick(position: Int) {
when (position) {
0 -> {
selectPicture()
// 相册
}
}
}
} }

View File

@ -5,7 +5,7 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.kaixed.kchat.databinding.ActivityContactRequestListBinding import com.kaixed.kchat.databinding.ActivityContactRequestListBinding
import com.kaixed.kchat.model.friend.FriendRequestItem import com.kaixed.kchat.data.model.friend.FriendRequestItem
import com.kaixed.kchat.ui.adapter.ContactRequestListAdapter import com.kaixed.kchat.ui.adapter.ContactRequestListAdapter
import com.kaixed.kchat.ui.base.BaseActivity import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.utils.ConstantsUtil.getUsername import com.kaixed.kchat.utils.ConstantsUtil.getUsername

View File

@ -10,6 +10,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.bumptech.glide.Glide
import com.kaixed.kchat.R import com.kaixed.kchat.R
import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore
import com.kaixed.kchat.data.local.entity.Contact import com.kaixed.kchat.data.local.entity.Contact

View File

@ -13,6 +13,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.bumptech.glide.Glide
import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore
import com.kaixed.kchat.data.local.entity.UserInfo import com.kaixed.kchat.data.local.entity.UserInfo
import com.kaixed.kchat.data.local.entity.UserInfo_ import com.kaixed.kchat.data.local.entity.UserInfo_
@ -88,6 +89,7 @@ class ProfileDetailActivity : BaseActivity<ActivityProfileDetailBinding>() {
userViewModel.uploadAvatarResult.observe(this) { result -> userViewModel.uploadAvatarResult.observe(this) { result ->
result.onSuccess { result.onSuccess {
Glide.with(this).downloadOnly().load(it).submit()
userSessionMMKV.putString(AVATAR_URL, it) userSessionMMKV.putString(AVATAR_URL, it)
toast("上传成功") toast("上传成功")
binding.ciAvatar.setItemIcon(uri) binding.ciAvatar.setItemIcon(uri)

View File

@ -17,7 +17,7 @@ import com.kaixed.kchat.R
import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore
import com.kaixed.kchat.data.local.entity.UserInfo import com.kaixed.kchat.data.local.entity.UserInfo
import com.kaixed.kchat.databinding.ActivityRegisterBinding import com.kaixed.kchat.databinding.ActivityRegisterBinding
import com.kaixed.kchat.model.request.RegisterRequest import com.kaixed.kchat.data.model.request.RegisterRequest
import com.kaixed.kchat.ui.base.BaseActivity import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.utils.DensityUtil.dpToPx import com.kaixed.kchat.utils.DensityUtil.dpToPx
import com.kaixed.kchat.utils.DrawableUtil.createDrawable import com.kaixed.kchat.utils.DrawableUtil.createDrawable

View File

@ -9,7 +9,7 @@ import androidx.core.widget.addTextChangedListener
import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore
import com.kaixed.kchat.data.local.entity.UserInfo import com.kaixed.kchat.data.local.entity.UserInfo
import com.kaixed.kchat.databinding.ActivityRenameBinding import com.kaixed.kchat.databinding.ActivityRenameBinding
import com.kaixed.kchat.model.request.UserRequest import com.kaixed.kchat.data.model.request.UserRequest
import com.kaixed.kchat.ui.base.BaseActivity import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.ui.widget.LoadingDialogFragment import com.kaixed.kchat.ui.widget.LoadingDialogFragment
import com.kaixed.kchat.utils.ConstantsUtil.getNickName import com.kaixed.kchat.utils.ConstantsUtil.getNickName

View File

@ -4,13 +4,12 @@ import android.os.Bundle
import android.view.View import android.view.View
import android.widget.EdgeEffect import android.widget.EdgeEffect
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory import androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory
import com.kaixed.kchat.data.model.search.SearchItem
import com.kaixed.kchat.databinding.ActivitySearchBinding import com.kaixed.kchat.databinding.ActivitySearchBinding
import com.kaixed.kchat.model.search.SearchItem
import com.kaixed.kchat.ui.adapter.SearchResultAdapter import com.kaixed.kchat.ui.adapter.SearchResultAdapter
import com.kaixed.kchat.ui.base.BaseActivity import com.kaixed.kchat.ui.base.BaseActivity
@ -78,13 +77,19 @@ class SearchActivity : BaseActivity<ActivitySearchBinding>() {
} }
private fun getAdapter(): SearchResultAdapter { private fun getAdapter(): SearchResultAdapter {
val contactItem = SearchItem("sa", "kaixed", "as", false, "联系人") val contactItem =
val chatHistoryItem = SearchItem("sa", "kaixed", "as", false, "群聊") SearchItem("sa", "kaixed", "as", false, "联系人")
val groupItem = 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 searchItem1 =
val searchItem2 = SearchItem("sa", "kaixed", "as", true, "群聊") SearchItem("sa", "kaixed", "as", true, "联系人")
val searchItem3 = 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) val objects: MutableList<Any> = ArrayList(12)

View File

@ -17,9 +17,9 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.kaixed.kchat.data.model.search.SearchUser
import com.kaixed.kchat.databinding.ActivitySearchFriendsBinding import com.kaixed.kchat.databinding.ActivitySearchFriendsBinding
import com.kaixed.kchat.databinding.DialogLoadingBinding import com.kaixed.kchat.databinding.DialogLoadingBinding
import com.kaixed.kchat.model.search.User
import com.kaixed.kchat.ui.base.BaseActivity import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.viewmodel.ContactViewModel import com.kaixed.kchat.viewmodel.ContactViewModel
@ -27,7 +27,7 @@ class SearchFriendsActivity : BaseActivity<ActivitySearchFriendsBinding>() {
private var isSearching = false private var isSearching = false
private lateinit var userItem: User private lateinit var userItem: SearchUser
private lateinit var loadingDialog: Dialog private lateinit var loadingDialog: Dialog

View File

@ -2,13 +2,11 @@ package com.kaixed.kchat.ui.activity
import android.os.Bundle import android.os.Bundle
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.recyclerview.widget.LinearLayoutManager
import com.kaixed.kchat.data.model.Message
import com.kaixed.kchat.databinding.ActivityTestBinding import com.kaixed.kchat.databinding.ActivityTestBinding
import com.kaixed.kchat.ui.adapter.MessageAdapter
import com.kaixed.kchat.ui.base.BaseActivity import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.utils.ImageEngines
import com.luck.picture.lib.basic.PictureSelector
import com.luck.picture.lib.config.SelectMimeType
import com.luck.picture.lib.entity.LocalMedia
import com.luck.picture.lib.interfaces.OnResultCallbackListener
class TestActivity : BaseActivity<ActivityTestBinding>() { class TestActivity : BaseActivity<ActivityTestBinding>() {
@ -20,27 +18,52 @@ class TestActivity : BaseActivity<ActivityTestBinding>() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
val adapter = MessageAdapter()
binding.recyclerView.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.recyclerView.adapter = adapter
val list = mutableListOf<Message>()
val message = Message(1, "1", "1", 1)
list.add(message)
adapter.submitList(list)
binding.button.setOnClickListener {
val list1 = mutableListOf<Message>()
val message0 = Message(1, "1", "1", 1)
val message1 = Message(1, "1", "2", 1)
list1.add(message0)
list1.add(message1)
list1[0].apply {
lastContent = "222"
}
adapter.submitList(list1)
toast("添加成功")
}
} }
override fun initData() { override fun initData() {
PictureSelector.create(this) // PictureSelector.create(this)
.openGallery(SelectMimeType.ofImage()) // .openGallery(SelectMimeType.ofImage())
.setImageEngine(ImageEngines()) // .setImageEngine(ImageEngines())
.forResult(object : OnResultCallbackListener<LocalMedia?> { // .forResult(object : OnResultCallbackListener<LocalMedia?> {
override fun onResult(result: ArrayList<LocalMedia?>) { // override fun onResult(result: ArrayList<LocalMedia?>) {
result.forEach { localMedia -> // result.forEach { localMedia ->
localMedia?.let { // localMedia?.let {
val imagePath = it.realPath // val imagePath = it.realPath
toast("图片路径: $imagePath") // toast("图片路径: $imagePath")
} // }
} // }
} // }
//
override fun onCancel() { // override fun onCancel() {
toast("已取消") // toast("已取消")
} // }
}) // })
} }
} }

View File

@ -130,7 +130,8 @@ class ChatAdapter(
class ImageViewHolder(val binding: ChatRecycleItemImageNormalBinding) : class ImageViewHolder(val binding: ChatRecycleItemImageNormalBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
fun bindData(message: Messages) { fun bindData(message: Messages) {
Glide.with(binding.root.context).load(message.content).into(binding.image) Glide.with(binding.root.context).load(message.content)
.placeholder(R.drawable.bac_contacts_detail).into(binding.image)
val sender = message.senderId == getUsername() val sender = message.senderId == getUsername()
changeView( changeView(
parentView = binding.root, parentView = binding.root,

View File

@ -8,8 +8,8 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.kaixed.kchat.data.model.friend.FriendRequestItem
import com.kaixed.kchat.databinding.FriendRecycleItemAddRequestBinding import com.kaixed.kchat.databinding.FriendRecycleItemAddRequestBinding
import com.kaixed.kchat.model.friend.FriendRequestItem
import com.kaixed.kchat.ui.activity.ApproveContactRequestActivity import com.kaixed.kchat.ui.activity.ApproveContactRequestActivity
import com.kaixed.kchat.ui.activity.ApproveDetailActivity import com.kaixed.kchat.ui.activity.ApproveDetailActivity

View File

@ -3,6 +3,7 @@ package com.kaixed.kchat.ui.adapter
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Color
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -46,6 +47,9 @@ class ConversationAdapter(
.error(R.drawable.ic_default_avatar) .error(R.drawable.ic_default_avatar)
val chatList = getItem(position) val chatList = getItem(position)
holder.binding.main.setBackgroundColor(if (chatList.isPinned) Color.parseColor("#EDEDED") else Color.WHITE)
if (chatList.avatarUrl.isNotEmpty()) { if (chatList.avatarUrl.isNotEmpty()) {
Glide.with(context).load(chatList.avatarUrl).apply(options) Glide.with(context).load(chatList.avatarUrl).apply(options)
.into(holder.binding.ifvAvatar) .into(holder.binding.ifvAvatar)

View File

@ -5,8 +5,9 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.BaseAdapter import android.widget.BaseAdapter
import com.kaixed.kchat.data.model.FunctionItem
import com.kaixed.kchat.databinding.FunctionGridItemBinding import com.kaixed.kchat.databinding.FunctionGridItemBinding
import com.kaixed.kchat.model.FunctionItem import com.kaixed.kchat.ui.i.IOnItemClickListener
/** /**
* @Author: kaixed * @Author: kaixed
@ -15,33 +16,48 @@ import com.kaixed.kchat.model.FunctionItem
class FunctionPanelAdapter( class FunctionPanelAdapter(
private val items: List<FunctionItem>, private val items: List<FunctionItem>,
private val context: Context private val context: Context
) : ) : BaseAdapter() {
BaseAdapter() {
// 监听器接口,外部可以传递点击事件的处理方法
private var listener: IOnItemClickListener? = null
// 设置点击事件监听器
fun setOnItemClickListener(listener: IOnItemClickListener) {
this.listener = listener
}
override fun getCount(): Int = items.size override fun getCount(): Int = items.size
override fun getItem(position: Int): Any? {
return null
}
override fun getItemId(position: Int): Long { override fun getItem(position: Int): Any = items[position]
return 0
}
override fun getItemId(position: Int): Long = position.toLong()
// 用于设置 GridItem 的视图
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
var convertView = convertView
val binding: FunctionGridItemBinding val binding: FunctionGridItemBinding
val view: View
if (convertView == null) { if (convertView == null) {
// convertView 为空时,说明需要创建新的视图 // convertView 为空时,需要创建新的视图
binding = FunctionGridItemBinding.inflate(LayoutInflater.from(context), parent, false) binding = FunctionGridItemBinding.inflate(LayoutInflater.from(context), parent, false)
convertView = binding.root view = binding.root
// 将 binding 存入 tag以后可复用 view.tag = binding // 将 binding 存入 tag以便复用
convertView.tag = binding
} else { } else {
// convertView 不为空时,直接复用已有的 View // convertView 不为空时,直接复用已有的 View
binding = convertView.tag as FunctionGridItemBinding view = convertView
binding = view.tag as FunctionGridItemBinding
} }
binding.tvItemName.text = items[position].itemName
binding.ivItem.setImageResource(items[position].iconResId) // 设置项的名称和图标
return convertView val functionItem = items[position]
binding.tvItemName.text = functionItem.itemName
binding.ivItem.setImageResource(functionItem.iconResId)
// 设置点击事件
binding.main.setOnClickListener {
listener?.onFunctionItemClick(position)
}
return view
} }
} }

View File

@ -0,0 +1,43 @@
package com.kaixed.kchat.ui.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.kaixed.kchat.data.model.Message
import com.kaixed.kchat.databinding.ItemMessageBinding
class MessageAdapter :
ListAdapter<Message, MessageAdapter.MessageViewHolder>(MessageDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder {
val binding = ItemMessageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MessageViewHolder(binding)
}
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) {
val message = getItem(position)
holder.bind(message)
}
inner class MessageViewHolder(private val binding: ItemMessageBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(message: Message) {
binding.message.text = message.lastContent
}
}
// DiffCallback 用于高效更新数据
class MessageDiffCallback : DiffUtil.ItemCallback<Message>() {
override fun areItemsTheSame(oldItem: Message, newItem: Message): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Message, newItem: Message): Boolean {
return oldItem == newItem
}
}
}

View File

@ -6,7 +6,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.BaseAdapter import android.widget.BaseAdapter
import com.kaixed.kchat.databinding.ItemGridBinding import com.kaixed.kchat.databinding.ItemGridBinding
import com.kaixed.kchat.model.HomeItem import com.kaixed.kchat.data.model.HomeItem
/** /**
* @Author: kaixed * @Author: kaixed

View File

@ -7,7 +7,7 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.kaixed.kchat.databinding.SearchRecycleItemContactsBinding import com.kaixed.kchat.databinding.SearchRecycleItemContactsBinding
import com.kaixed.kchat.databinding.SearchRecycleItemDetailsBinding import com.kaixed.kchat.databinding.SearchRecycleItemDetailsBinding
import com.kaixed.kchat.model.search.SearchItem import com.kaixed.kchat.data.model.search.SearchItem
/** /**
* @Author: kaixed * @Author: kaixed

View File

@ -7,7 +7,6 @@ import android.view.ViewGroup
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.kaixed.kchat.model.friend.FriendItem
import com.kaixed.kchat.utils.ConstantsUtil.getStatusBarHeight import com.kaixed.kchat.utils.ConstantsUtil.getStatusBarHeight
/** /**

View File

@ -16,6 +16,7 @@ import android.os.IBinder
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity.BIND_AUTO_CREATE import androidx.appcompat.app.AppCompatActivity.BIND_AUTO_CREATE
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -23,38 +24,35 @@ import com.kaixed.kchat.R
import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore
import com.kaixed.kchat.data.local.entity.Conversation import com.kaixed.kchat.data.local.entity.Conversation
import com.kaixed.kchat.data.local.entity.Conversation_ import com.kaixed.kchat.data.local.entity.Conversation_
import com.kaixed.kchat.data.local.entity.Messages import com.kaixed.kchat.data.model.HomeItem
import com.kaixed.kchat.databinding.FragmentHomeBinding import com.kaixed.kchat.databinding.FragmentHomeBinding
import com.kaixed.kchat.model.HomeItem
import com.kaixed.kchat.service.WebSocketService import com.kaixed.kchat.service.WebSocketService
import com.kaixed.kchat.service.WebSocketService.LocalBinder import com.kaixed.kchat.service.WebSocketService.LocalBinder
import com.kaixed.kchat.ui.activity.AddFriendsActivity import com.kaixed.kchat.ui.activity.AddFriendsActivity
import com.kaixed.kchat.ui.activity.ContactUpdatesActivity
import com.kaixed.kchat.ui.activity.SearchActivity import com.kaixed.kchat.ui.activity.SearchActivity
import com.kaixed.kchat.ui.activity.TestActivity import com.kaixed.kchat.ui.activity.TestActivity
import com.kaixed.kchat.ui.adapter.ConversationAdapter import com.kaixed.kchat.ui.adapter.ConversationAdapter
import com.kaixed.kchat.ui.adapter.MyGridAdapter import com.kaixed.kchat.ui.adapter.MyGridAdapter
import com.kaixed.kchat.ui.base.BaseFragment import com.kaixed.kchat.ui.base.BaseFragment
import com.kaixed.kchat.ui.i.OnChatListItemClickListener import com.kaixed.kchat.ui.i.OnDialogFragmentClickListener
import com.kaixed.kchat.utils.Constants.MMKV_COMMON_DATA import com.kaixed.kchat.ui.i.OnItemListener
import com.tencent.mmkv.MMKV import com.kaixed.kchat.ui.widget.HomeDialogFragment
import io.objectbox.Box import io.objectbox.Box
class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnChatListItemClickListener { class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnItemListener,
OnDialogFragmentClickListener {
private var chatLists: MutableList<Conversation> = mutableListOf() private var chatLists: MutableList<Conversation> = mutableListOf()
private val conversationAdapter: ConversationAdapter by lazy { private val conversationAdapter: ConversationAdapter by lazy {
ConversationAdapter(chatLists, context) ConversationAdapter(requireContext())
} }
private val conversationBox: Box<Conversation> by lazy { getBoxStore().boxFor(Conversation::class.java) } private val conversationBox: Box<Conversation> by lazy { getBoxStore().boxFor(Conversation::class.java) }
private val messagesBox: Box<Messages> by lazy { getBoxStore().boxFor(Messages::class.java) }
private var talkerId: String? = null private var talkerId: String? = null
private val items: MutableList<HomeItem> = ArrayList() private val items: List<HomeItem> by lazy { getHomeItems() }
private var webSocketService: WebSocketService? = null private var webSocketService: WebSocketService? = null
@ -64,8 +62,6 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnChatListItemClickLis
private lateinit var context: Context private lateinit var context: Context
private var handleMsgSvrId = -1L
private var connection: ServiceConnection = object : ServiceConnection { private var connection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) { override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as LocalBinder val binder = service as LocalBinder
@ -134,7 +130,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnChatListItemClickLis
webSocketService!!.conversations.observe(viewLifecycleOwner) { webSocketService!!.conversations.observe(viewLifecycleOwner) {
it?.let { it?.let {
chatLists = it.toMutableList() chatLists = it.toMutableList()
conversationAdapter.updateData(chatLists) conversationAdapter.submitList(chatLists)
notifyData() notifyData()
} }
} }
@ -202,58 +198,52 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnChatListItemClickLis
flipped = false flipped = false
} }
enum class HomeItems(val title: String, val iconResId: Int, val isEnabled: Boolean) {
CHANGE_BACKGROUND("更换背景", R.drawable.ic_switch, true),
CREATE_GROUP("创建群聊", R.drawable.ic_troop, false),
ADD_FRIENDS("新朋友", R.drawable.ic_friend, true),
CHANGE_MODE("夜间模式", R.drawable.ic_night, true),
FRIEND_CIRCLE("朋友圈", R.drawable.ic_qzone, false),
SCAN("扫一扫", R.drawable.ic_scan, false),
FRIENDS("通讯录", R.drawable.ic_clock_in, true),
CLOSE_APP("关闭应用", R.drawable.ic_exit, false);
companion object {
fun getAllItems() = entries
}
}
private fun getHomeItems(): List<HomeItem> {
return HomeItems.getAllItems().map { homeItem ->
HomeItem(homeItem.title, homeItem.isEnabled, homeItem.iconResId)
}
}
private fun setupGridView() { private fun setupGridView() {
initMenuData()
val myGridAdapter = MyGridAdapter(context, items) val myGridAdapter = MyGridAdapter(context, items)
binding.gridView.adapter = myGridAdapter binding.gridView.adapter = myGridAdapter
binding.gridView.selector = ColorDrawable(Color.TRANSPARENT) binding.gridView.selector = ColorDrawable(Color.TRANSPARENT)
binding.gridView.setOnItemClickListener { _, _, position, _ -> binding.gridView.setOnItemClickListener { _, _, position, _ ->
when (position) { val homeItem = items[position]
0 -> {} when (HomeItems.valueOf(homeItem.name)) {
2 -> { HomeItems.ADD_FRIENDS -> {
val intent = Intent(context, AddFriendsActivity::class.java) val intent = Intent(context, AddFriendsActivity::class.java)
startActivity(intent) startActivity(intent)
} }
4 -> { HomeItems.CLOSE_APP -> {
val intent2 = requireActivity().finishAffinity()
Intent(
context,
ContactUpdatesActivity::class.java
)
startActivity(intent2)
} }
6 -> { else -> Toast.makeText(requireContext(), "暂未实现", Toast.LENGTH_SHORT).show()
// val intent1 =
// Intent(
// context,
// FriendListActivity::class.java
// )
// startActivity(intent1)
}
}
} }
} }
private fun initMenuData() {
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() { override fun onResume() {
super.onResume() super.onResume()
if (flipped) { if (flipped) {
@ -288,13 +278,17 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnChatListItemClickLis
override fun onItemClick(talkerId: String) { override fun onItemClick(talkerId: String) {
this.talkerId = talkerId this.talkerId = talkerId
// chatLists.find { it.talkerId == talkerId }?.let {
// it.unreadCount = 0
// conversationBox.put(it)
// }
for (i in chatLists.indices) { for (i in chatLists.indices) {
if (chatLists[i].talkerId == talkerId) { if (chatLists[i].talkerId == talkerId) {
chatLists[i].unreadCount = 0 chatLists[i].unreadCount = 0
} }
} }
notifyData() notifyData()
//
val conversation = val conversation =
conversationBox.query(Conversation_.talkerId.equal(talkerId)).build().findFirst() conversationBox.query(Conversation_.talkerId.equal(talkerId)).build().findFirst()
@ -303,4 +297,52 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnChatListItemClickLis
conversationBox.put(it) conversationBox.put(it)
} }
} }
private var longClickTalkerId = ""
private var con: Conversation? = null
override fun onItemLongClick(talkId: String, nickname: String) {
longClickTalkerId = talkId
con = getConversation(talkId)
val dialogFragment = HomeDialogFragment.newInstance(
contactNickname = nickname,
isPinned = con!!.isPinned,
isHasUnreadMsg = con!!.unreadCount != 0
)
dialogFragment.show(childFragmentManager, "HomeDialogFragment")
}
private fun getConversation(): Conversation =
chatLists.find { it.talkerId == longClickTalkerId }!!
private fun getConversation(talkId: String): Conversation =
chatLists.find { it.talkerId == talkId }!!
override fun onClickSetUnread() {
if (con == null) {
return
}
webSocketService?.updateConversationList(con!!.apply {
this.unreadCount = if (unreadCount != 0) 0 else 1
})
}
override fun onClickHideConversation() {
webSocketService?.updateConversationList(getConversation().apply {
this.isShow = false
})
}
override fun onClickSticky() {
webSocketService?.updateConversationList(getConversation().apply {
this.isPinned = !isPinned
})
}
override fun onClickDeleteConversation() {
webSocketService?.deleteConversationList(getConversation())
}
} }

View File

@ -10,6 +10,7 @@ import com.kaixed.kchat.R
import com.kaixed.kchat.databinding.FragmentMineBinding import com.kaixed.kchat.databinding.FragmentMineBinding
import com.kaixed.kchat.ui.activity.ProfileDetailActivity import com.kaixed.kchat.ui.activity.ProfileDetailActivity
import com.kaixed.kchat.ui.base.BaseFragment import com.kaixed.kchat.ui.base.BaseFragment
import com.kaixed.kchat.ui.widget.MyBottomSheetFragment
import com.kaixed.kchat.utils.ConstantsUtil import com.kaixed.kchat.utils.ConstantsUtil
import com.kaixed.kchat.utils.ConstantsUtil.getAvatarUrl import com.kaixed.kchat.utils.ConstantsUtil.getAvatarUrl

View File

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

View File

@ -0,0 +1,8 @@
package com.kaixed.kchat.ui.i
interface OnDialogFragmentClickListener {
fun onClickSetUnread()
fun onClickHideConversation()
fun onClickSticky()
fun onClickDeleteConversation()
}

View File

@ -4,6 +4,9 @@ package com.kaixed.kchat.ui.i
* @Author: kaixed * @Author: kaixed
* @Date: 2024/10/27 18:35 * @Date: 2024/10/27 18:35
*/ */
interface OnChatListItemClickListener { interface OnItemListener {
fun onItemClick(talkerId: String) fun onItemClick(talkerId: String)
fun onItemLongClick(talkId: String, nickname: String)
} }

View File

@ -0,0 +1,114 @@
package com.kaixed.kchat.ui.widget
import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.Gravity
import android.view.Window
import android.view.WindowManager
import androidx.fragment.app.DialogFragment
import com.kaixed.kchat.databinding.DialogHomeBinding
import com.kaixed.kchat.ui.i.OnDialogFragmentClickListener
import kotlin.properties.Delegates
class HomeDialogFragment : DialogFragment() {
private lateinit var contactNickname: String
private var isPinned by Delegates.notNull<Boolean>()
private var isHasUnreadMsg by Delegates.notNull<Boolean>()
private var listener: OnDialogFragmentClickListener? = null
companion object {
fun newInstance(
contactNickname: String,
isPinned: Boolean = false,
isHasUnreadMsg: Boolean = false
): HomeDialogFragment {
val fragment = HomeDialogFragment()
val args = Bundle()
args.putString("contactNickname", contactNickname)
args.putBoolean("isPinned", isPinned)
args.putBoolean("isHasUnreadMsg", isHasUnreadMsg)
fragment.arguments = args
return fragment
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
val parentFragment = parentFragment
listener = if (parentFragment is OnDialogFragmentClickListener) {
parentFragment
} else if (context is OnDialogFragmentClickListener) {
context
} else {
throw IllegalStateException(
"Either parent fragment or activity must implement OnDialogFragmentClickListener"
)
}
}
override fun onDetach() {
super.onDetach()
listener = null
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
contactNickname = arguments?.getString("contactNickname") ?: ""
isPinned = arguments?.getBoolean("isPinned") ?: false
isHasUnreadMsg = arguments?.getBoolean("isHasUnreadMsg") ?: false
val binding = DialogHomeBinding.inflate(layoutInflater)
val dialog = Dialog(requireContext())
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 = (requireContext().resources.displayMetrics.widthPixels * 0.8).toInt()
gravity = Gravity.CENTER
}
dialog.window?.attributes = layoutParams
setContent(binding)
setOnClickListener(binding)
return dialog
}
private fun setContent(binding: DialogHomeBinding) {
binding.tvTitle.text = contactNickname
binding.tvSticky.text = if (isPinned) "取消置顶" else "置顶"
binding.tvSetUnread.text = if (isHasUnreadMsg) "标记已读" else "标记未读"
}
private fun setOnClickListener(binding: DialogHomeBinding) {
binding.tvSetUnread.setOnClickListener {
listener?.onClickSetUnread()
dismissDialog()
}
binding.tvSticky.setOnClickListener {
listener?.onClickSticky()
dismissDialog()
}
binding.tvHideConversation.setOnClickListener {
listener?.onClickHideConversation()
dismissDialog()
}
binding.tvDeleteConversation.setOnClickListener {
listener?.onClickDeleteConversation()
dismissDialog()
}
}
private fun dismissDialog() {
if (isAdded && !isStateSaved) {
dismissAllowingStateLoss()
}
}
}

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.ui.fragment package com.kaixed.kchat.ui.widget
import android.content.Intent import android.content.Intent

View File

@ -17,6 +17,7 @@ import com.kaixed.kchat.utils.DensityUtil.dpToPx
* @Date: 2024/11/25 15:44 * @Date: 2024/11/25 15:44
*/ */
object PopWindowUtil { object PopWindowUtil {
fun showPopupWindow(context: Context, parentView: View, isMine: Boolean) { fun showPopupWindow(context: Context, parentView: View, isMine: Boolean) {
val binding: PopwindowsBinding = PopwindowsBinding.inflate(LayoutInflater.from(context)) val binding: PopwindowsBinding = PopwindowsBinding.inflate(LayoutInflater.from(context))
val popupWindow: PopupWindow = PopupWindow( val popupWindow: PopupWindow = PopupWindow(

View File

@ -6,8 +6,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.kaixed.kchat.data.local.box.ObjectBox.getBox import com.kaixed.kchat.data.local.box.ObjectBox.getBox
import com.kaixed.kchat.data.local.entity.Contact import com.kaixed.kchat.data.local.entity.Contact
import com.kaixed.kchat.model.friend.FriendRequestItem import com.kaixed.kchat.data.model.friend.FriendRequestItem
import com.kaixed.kchat.model.search.User import com.kaixed.kchat.data.model.search.SearchUser
import com.kaixed.kchat.data.repository.ContactRepository import com.kaixed.kchat.data.repository.ContactRepository
import io.objectbox.Box import io.objectbox.Box
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -17,7 +17,8 @@ class ContactViewModel : ViewModel() {
private val contactRepository = ContactRepository() private val contactRepository = ContactRepository()
// 获取联系人请求列表 // 获取联系人请求列表
private val _contactRequestListResult = MutableLiveData<Result<List<FriendRequestItem>?>>() private val _contactRequestListResult =
MutableLiveData<Result<List<FriendRequestItem>?>>()
val contactRequestListResult: LiveData<Result<List<FriendRequestItem>?>> = val contactRequestListResult: LiveData<Result<List<FriendRequestItem>?>> =
_contactRequestListResult _contactRequestListResult
@ -31,8 +32,8 @@ class ContactViewModel : ViewModel() {
val addContactResult: LiveData<Result<String?>> = _addContactResult val addContactResult: LiveData<Result<String?>> = _addContactResult
// 搜索联系人 // 搜索联系人
private val _searchContactResult = MutableLiveData<Result<User?>>() private val _searchContactResult = MutableLiveData<Result<SearchUser?>>()
val searchContactResult: LiveData<Result<User?>> = _searchContactResult val searchContactResult: LiveData<Result<SearchUser?>> = _searchContactResult
// 获取联系人列表 // 获取联系人列表
private val _contactListResult = MutableLiveData<Result<List<Contact>?>>() private val _contactListResult = MutableLiveData<Result<List<Contact>?>>()

View File

@ -0,0 +1,29 @@
package com.kaixed.kchat.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.kaixed.kchat.data.repository.FileRepository
import kotlinx.coroutines.launch
import okhttp3.MultipartBody
import java.io.File
/**
* @Author: kaixed
* @Date: 2024/11/29 15:17
*/
class FileViewModel : ViewModel() {
private val fileRepo = FileRepository()
private val _uploadFileResult = MutableLiveData<Result<String?>>()
val uploadFileResult: LiveData<Result<String?>> = _uploadFileResult
// 上传头像
fun uploadFile(file: File) {
viewModelScope.launch {
val result = fileRepo.uploadFile(file)
_uploadFileResult.postValue(result)
}
}
}

View File

@ -5,11 +5,11 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.kaixed.kchat.data.local.entity.UserInfo import com.kaixed.kchat.data.local.entity.UserInfo
import com.kaixed.kchat.model.request.RegisterRequest import com.kaixed.kchat.data.model.request.RegisterRequest
import com.kaixed.kchat.model.request.UserRequest import com.kaixed.kchat.data.model.request.UserRequest
import com.kaixed.kchat.model.response.register.Register import com.kaixed.kchat.data.model.response.register.Register
import com.kaixed.kchat.model.response.search.User import com.kaixed.kchat.data.model.response.search.User
import com.kaixed.kchat.model.search.SearchUser import com.kaixed.kchat.data.model.search.SearchUser
import com.kaixed.kchat.data.repository.UserAuthRepository import com.kaixed.kchat.data.repository.UserAuthRepository
import com.kaixed.kchat.data.repository.UserProfileRepository import com.kaixed.kchat.data.repository.UserProfileRepository
import com.kaixed.kchat.data.repository.UserSearchRepository import com.kaixed.kchat.data.repository.UserSearchRepository

View File

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="200"
android:viewportHeight="200">
<path
android:pathData="M37.61,52.64C37.58,58.99 41.36,64.16 46.05,64.18L104.92,64.5C109.62,64.52 113.45,59.4 113.48,53.05C113.52,46.69 109.74,41.53 105.05,41.5L46.18,41.18C41.48,41.16 37.65,46.29 37.61,52.64Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
<path
android:pathData="M37.64,103.33C37.68,109.68 41.51,114.81 46.2,114.79L105.07,114.5C109.76,114.48 113.54,109.31 113.51,102.96C113.48,96.61 109.65,91.48 104.96,91.5L46.09,91.79C41.39,91.81 37.61,96.98 37.64,103.33Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
<path
android:pathData="M37.68,154.02C37.77,160.37 41.65,165.46 46.35,165.39L105.22,164.5C109.91,164.43 113.64,159.22 113.54,152.87C113.45,146.52 109.57,141.43 104.87,141.5L46,142.39C41.31,142.46 37.58,147.67 37.68,154.02Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
<path
android:pathData="M160.55,114.49L160.55,50C160.55,49.88 160.54,49.75 160.54,49.63C160.54,49.51 160.52,49.38 160.5,49.26C160.49,49.14 160.48,49.02 160.45,48.9C160.43,48.78 160.41,48.66 160.38,48.54C160.35,48.42 160.31,48.3 160.27,48.18C160.24,48.06 160.19,47.94 160.15,47.82C160.1,47.7 160.05,47.59 159.99,47.47C159.94,47.35 159.89,47.24 159.83,47.13C159.77,47.02 159.71,46.9 159.64,46.79C159.57,46.68 159.5,46.57 159.42,46.46C159.35,46.35 159.27,46.25 159.2,46.14C159.12,46.03 159.04,45.93 158.95,45.83C158.87,45.73 158.77,45.63 158.67,45.53C158.58,45.43 158.49,45.33 158.4,45.24C158.3,45.15 158.19,45.05 158.09,44.96C157.99,44.87 157.87,44.79 157.76,44.7C157.65,44.61 157.54,44.52 157.43,44.44C157.31,44.36 157.19,44.28 157.07,44.2C156.95,44.12 156.83,44.05 156.71,43.98C156.58,43.91 156.45,43.83 156.32,43.76C156.19,43.69 156.06,43.63 155.93,43.57C155.8,43.51 155.66,43.45 155.52,43.39C155.39,43.33 155.25,43.27 155.1,43.22C154.96,43.17 154.82,43.12 154.67,43.07C154.53,43.02 154.39,42.98 154.24,42.94C154.09,42.9 153.95,42.86 153.8,42.82C153.65,42.78 153.49,42.75 153.34,42.72C153.19,42.69 153.04,42.66 152.88,42.64C152.73,42.62 152.58,42.6 152.42,42.58C152.27,42.56 152.12,42.55 151.97,42.54C151.81,42.53 151.65,42.51 151.5,42.51C151.34,42.51 151.18,42.5 151.03,42.5C150.87,42.5 150.71,42.51 150.56,42.51C150.4,42.51 150.24,42.53 150.09,42.54C149.93,42.55 149.78,42.56 149.63,42.58C149.48,42.6 149.32,42.62 149.17,42.64C149.02,42.66 148.87,42.69 148.71,42.72C148.56,42.75 148.41,42.78 148.26,42.82C148.11,42.86 147.96,42.9 147.81,42.94C147.67,42.98 147.52,43.02 147.38,43.07C147.24,43.12 147.09,43.17 146.95,43.22C146.81,43.27 146.67,43.33 146.53,43.39C146.39,43.45 146.26,43.51 146.12,43.57C145.99,43.63 145.86,43.69 145.73,43.76C145.6,43.83 145.47,43.91 145.35,43.98C145.22,44.05 145.1,44.12 144.98,44.2C144.86,44.28 144.74,44.36 144.63,44.44C144.51,44.52 144.4,44.61 144.29,44.7C144.18,44.79 144.07,44.87 143.96,44.96C143.86,45.05 143.76,45.15 143.66,45.24C143.56,45.33 143.47,45.43 143.38,45.53C143.29,45.63 143.19,45.73 143.1,45.83C143.01,45.93 142.94,46.03 142.86,46.14C142.78,46.25 142.7,46.35 142.63,46.46C142.56,46.57 142.48,46.68 142.41,46.79C142.35,46.9 142.28,47.02 142.22,47.13C142.16,47.24 142.11,47.35 142.06,47.47C142.01,47.59 141.95,47.7 141.91,47.82C141.86,47.94 141.82,48.06 141.78,48.18C141.74,48.3 141.71,48.42 141.68,48.54C141.65,48.66 141.62,48.78 141.6,48.9C141.58,49.02 141.57,49.14 141.55,49.26C141.54,49.38 141.51,49.51 141.51,49.63C141.51,49.75 141.5,49.88 141.5,50L141.5,150C141.72,153.44 143.86,155.8 147.91,157.09C152.12,158.05 155.65,157.23 158.52,154.63L186.47,126.63C186.79,126.3 187.08,125.95 187.33,125.58C187.59,125.21 187.81,124.83 187.98,124.44C188.15,124.05 188.29,123.65 188.37,123.24C188.46,122.83 188.5,122.41 188.5,121.99C188.5,121.96 188.5,121.94 188.5,121.91C188.5,121.64 188.48,121.37 188.44,121.1C188.31,120.27 188.01,119.47 187.55,118.71C186.83,117.65 185.95,116.8 184.92,116.14C183.32,115.11 181.34,114.56 178.97,114.49L160.55,114.49Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="200"
android:viewportHeight="200">
<path
android:pathData="M80.98,161.47C125.7,161.47 161.95,125.32 161.95,80.73C161.95,36.14 125.7,0 80.98,0C36.26,0 0,36.14 0,80.73C0,125.32 36.26,161.47 80.98,161.47ZM80.98,125.95C55.94,125.95 35.63,105.7 35.63,80.73C35.63,55.76 55.94,35.52 80.98,35.52C106.03,35.52 126.32,55.76 126.32,80.73C126.32,105.7 106.03,125.95 80.98,125.95ZM149.2,149.35C142.32,156.2 142.32,167.33 149.2,174.18L169.95,194.86C176.82,201.71 187.97,201.71 194.84,194.86C201.71,188.01 201.71,176.89 194.84,170.04L174.1,149.35C167.23,142.5 156.07,142.5 149.2,149.35Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</vector>

View File

@ -1,29 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
android:orientation="vertical"> android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
<androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:padding="10dp"> android:layout_weight="1.0" />
<TextView <Button
android:layout_width="0dp" android:id="@+id/button"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:ellipsize="end" android:layout_height="40dp"
android:maxLines="1" android:layout_gravity="center" />
android:singleLine="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="这是一个测试文案 这是一个测试文案 这是一个测试文案 这是一个测试文案" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout> </LinearLayout>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/white"> android:background="@color/white">

View File

@ -0,0 +1,62 @@
<?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"
app:cardCornerRadius="8dp"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="20dp"
android:paddingBottom="20dp"
android:paddingTop="10dp">
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="12dp"
android:text="kaixed"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_set_unread"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="12dp"
android:text="标为未读"
android:textColor="@color/black"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_sticky"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="12dp"
android:text="置顶"
android:textColor="@color/black"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_hide_conversation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="12dp"
android:text="不显示该聊天"
android:textColor="@color/black"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_delete_conversation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="12dp"
android:text="删除该聊天"
android:textColor="@color/black"
android:textSize="16sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -46,11 +46,11 @@
<ImageView <ImageView
android:id="@+id/iv_search" android:id="@+id/iv_search"
android:layout_width="36dp" android:layout_width="16dp"
android:layout_height="36dp" android:layout_height="16dp"
android:layout_marginEnd="5dp" android:layout_marginEnd="12dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:src="@drawable/ic_search" android:src="@drawable/ic_search_home"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/iv_message" app:layout_constraintEnd_toStartOf="@id/iv_message"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -61,16 +61,17 @@
android:layout_height="22dp" android:layout_height="22dp"
android:layout_marginEnd="5dp" android:layout_marginEnd="5dp"
android:src="@drawable/home_ic_message" android:src="@drawable/home_ic_message"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/iv_more" app:layout_constraintEnd_toStartOf="@id/iv_more"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<ImageView <ImageView
android:id="@+id/iv_more" android:id="@+id/iv_more"
android:layout_width="36dp" android:layout_width="24dp"
android:layout_height="36dp" android:layout_height="24dp"
android:layout_marginEnd="20dp" android:layout_marginEnd="20dp"
android:src="@drawable/home_more_indicator" android:src="@drawable/ic_more_home"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="20dp"> android:padding="20dp">

View File

@ -0,0 +1,17 @@
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:gravity="center"
app:layout_constraintTop_toTopOf="parent"
android:text="haha"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -14,7 +14,6 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
//noinspection UseTomlInstead
classpath("io.objectbox:objectbox-gradle-plugin:4.0.0") classpath("io.objectbox:objectbox-gradle-plugin:4.0.0")
} }
} }