feat: 新增联系人列表、修改昵称功能

1.新增联系人列表
2.新增修改昵称界面
This commit is contained in:
糕小菜 2024-11-06 19:18:46 +08:00
parent d4dc9a3b24
commit 53bbc029b9
87 changed files with 1088 additions and 1231 deletions

View File

@ -2,7 +2,19 @@
<project version="4">
<component name="GitCommitMessageStorage">
<option name="messageStorage">
<MessageStorage />
<MessageStorage>
<option name="commitTemplate">
<CommitTemplate>
<option name="body" value="" />
<option name="changes" value="" />
<option name="closes" value="" />
<option name="scope" value="" />
<option name="skipCi" value="" />
<option name="subject" value="" />
<option name="type" value="refactor" />
</CommitTemplate>
</option>
</MessageStorage>
</option>
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RenderSettings">
<option name="showDecorations" value="true" />
</component>
</project>

View File

@ -72,6 +72,8 @@ dependencies {
implementation(libs.shapeview)
implementation(libs.shapedrawable)
implementation(libs.pinyin4j)
// implementation(libs.therouter)
// ksp(libs.therouter.ksp)
}

View File

@ -100,9 +100,58 @@
}
],
"relations": []
},
{
"id": "4:6179749773128044271",
"lastPropertyId": "9:468674632316468263",
"name": "Contact",
"properties": [
{
"id": "1:21644744643871861",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:3202277046871450743",
"name": "username",
"type": 9
},
{
"id": "3:8037315472776382017",
"name": "nickname",
"type": 9
},
{
"id": "4:4442492673513561150",
"name": "avatarUrl",
"type": 9
},
{
"id": "5:5924281870400961702",
"name": "signature",
"type": 9
},
{
"id": "6:4419123739192764234",
"name": "remark",
"type": 9
},
{
"id": "7:2091607227595340484",
"name": "quanpin",
"type": 9
},
{
"id": "9:468674632316468263",
"name": "showHeader",
"type": 1
}
],
"relations": []
}
],
"lastEntityId": "3:4156940900268054047",
"lastEntityId": "4:6179749773128044271",
"lastIndexId": "0:0",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
@ -123,7 +172,8 @@
3687999935415577905,
5421937431301885220,
1118829668259721786,
1490263641107250384
1490263641107250384,
2701112806559265255
],
"retiredRelationUids": [],
"version": 1

View File

@ -102,59 +102,77 @@
"relations": []
},
{
"id": "3:4156940900268054047",
"lastPropertyId": "6:1490263641107250384",
"name": "UserInfo",
"id": "4:6179749773128044271",
"lastPropertyId": "8:2701112806559265255",
"name": "Contact",
"properties": [
{
"id": "1:5682400492130737484",
"id": "1:21644744643871861",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:404167074012121135",
"name": "avatarUrl",
"id": "2:3202277046871450743",
"name": "username",
"type": 9
},
{
"id": "3:3687999935415577905",
"id": "3:8037315472776382017",
"name": "nickname",
"type": 9
},
{
"id": "4:5421937431301885220",
"id": "4:4442492673513561150",
"name": "avatarUrl",
"type": 9
},
{
"id": "5:5924281870400961702",
"name": "signature",
"type": 9
},
{
"id": "5:1118829668259721786",
"name": "status",
"type": 13
"id": "6:4419123739192764234",
"name": "remark",
"type": 9
},
{
"id": "6:1490263641107250384",
"name": "username",
"id": "7:2091607227595340484",
"name": "quanpin",
"type": 9
},
{
"id": "8:2701112806559265255",
"name": "showHead",
"type": 1
}
],
"relations": []
}
],
"lastEntityId": "3:4156940900268054047",
"lastEntityId": "4:6179749773128044271",
"lastIndexId": "0:0",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
"modelVersion": 5,
"modelVersionParserMinimum": 5,
"retiredEntityUids": [],
"retiredEntityUids": [
4156940900268054047
],
"retiredIndexUids": [],
"retiredPropertyUids": [
840045868537717781,
4549217622013661678,
4829352141114779787,
5444531588574559639,
4896943870878862074
4896943870878862074,
5682400492130737484,
404167074012121135,
3687999935415577905,
5421937431301885220,
1118829668259721786,
1490263641107250384
],
"retiredRelationUids": [],
"version": 1

View File

@ -20,6 +20,9 @@
android:theme="@style/Theme.KChatAndroid"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".ui.activity.RenameActivity"
android:exported="false" />
<activity
android:name=".ui.activity.LaunchActivity"
android:exported="true">
@ -73,9 +76,6 @@
<activity
android:name=".ui.activity.ServiceDetailActivity"
android:exported="false" />
<activity
android:name=".ui.activity.SettingActivity"
android:exported="false" />
<activity
android:name=".ui.activity.ProfileDetailActivity"
android:exported="false" />

View File

@ -0,0 +1 @@
{"v":"5.9.0","fr":29.9700012207031,"ip":0,"op":79.000003217736,"w":500,"h":500,"nm":"loading-project-003","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"line","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[37.981]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":28,"s":[360]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[0]},{"t":66.0000026882351,"s":[360]}],"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[5,3,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[258,258],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"타원 패스 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":24,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"선 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5,3],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[127.163,127.163],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"변형"}],"nm":"타원 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":28,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[0.1]},{"t":66.0000026882351,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":28,"s":[99.7]},{"t":29.0000011811942,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"패스 다듬어 자르기 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":80.0000032584668,"st":-25.0000010182709,"bm":0}],"markers":[]}

View File

@ -1,7 +1,6 @@
package com.kaixed.kchat
import android.app.Application
import android.util.Log
import com.kaixed.kchat.data.objectbox.ObjectBox.get
import com.kaixed.kchat.data.objectbox.ObjectBox.init
import com.tencent.mmkv.MMKV
@ -15,10 +14,10 @@ class KchatApplication : Application() {
override fun onCreate() {
super.onCreate()
val rootDir = MMKV.initialize(this)
Log.d("mmkv root: ", rootDir)
// Log.d("mmkv root: ", rootDir)
init(this)
val started = Admin(get()).start(this)
Log.i("ObjectBoxAdmin", "Started: $started")
// Log.i("ObjectBoxAdmin", "Started: $started")
}
}

View File

@ -0,0 +1,21 @@
package com.kaixed.kchat.data.objectbox.entity
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
/**
* @Author: kaixed
* @Date: 2024/11/4 13:39
*/
@Entity
data class Contact(
@Id
var id: Long = 0L,
var username: String?,
var nickname: String?,
var avatarUrl: String?,
var signature: String?,
var remark: String?,
var quanpin: String?,
var showHeader: Boolean?
)

View File

@ -2,6 +2,7 @@ package com.kaixed.kchat.data.objectbox.entity
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
import kotlinx.serialization.Serializable
/**
* @Author: kaixed
@ -9,6 +10,7 @@ import io.objectbox.annotation.Id
*/
@Entity
@Serializable
data class Messages(
@Id
var msgLocalId: Long = 0L,
@ -20,4 +22,5 @@ data class Messages(
var takerId: String,
var type: String,
var show: Boolean = true,
// var isShowTimer: Boolean
)

View File

@ -0,0 +1,14 @@
package com.kaixed.kchat.model.item
/**
* @Author: kaixed
* @Date: 2024/11/2 11:14
*/
data class FriendItem(
val nickname: String,
val username: String,
val remark: String?,
var avatarUrl: String? = "",
var firstInSection: Boolean = false,
var pinyin: String? = ""
)

View File

@ -1,9 +0,0 @@
package com.kaixed.kchat.model.login
data class Data(
val avatarUrl: String,
val nickname: String,
val signature: String,
val status: Any,
val username: String,
)

View File

@ -1,7 +0,0 @@
package com.kaixed.kchat.model.login
data class Login(
val code: String,
val `data`: Data,
val msg: String,
)

View File

@ -1,4 +1,4 @@
package com.kaixed.kchat.model.register
package com.kaixed.kchat.model.request
import kotlinx.serialization.Serializable
@ -6,6 +6,7 @@ import kotlinx.serialization.Serializable
data class RegisterRequest(
val avatarUrl: String,
val nickname: String,
val telephone: String,
val password: String,
val signature: String,
val username: String

View File

@ -1,5 +1,6 @@
package com.kaixed.kchat.model.friend
package com.kaixed.kchat.model.response.friend
import com.kaixed.kchat.model.friend.FriendItem
import kotlinx.serialization.Serializable
/**
@ -8,8 +9,8 @@ import kotlinx.serialization.Serializable
*/
@Serializable
data class FriendResponse(
data class FriendList(
val code: String,
val message: String,
val data: List<FriendItem>,
)
val data: List<FriendItem>?,
)

View File

@ -1,5 +1,6 @@
package com.kaixed.kchat.model.search
package com.kaixed.kchat.model.response.friend
import com.kaixed.kchat.model.search.User
import kotlinx.serialization.Serializable
/**
@ -10,5 +11,5 @@ import kotlinx.serialization.Serializable
data class SearchFriends(
val code: String,
val msg: String,
val `data`: User
val `data`: User?
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +0,0 @@
package com.kaixed.kchat.model.setting
/**
* @Author: kaixed
* @Date: 2024/10/9 20:35
*/
data class FunctionItem(
val itemName: String,
val remark: String,
)

View File

@ -1,10 +0,0 @@
package com.kaixed.kchat.model.setting
/**
* @Author: kaixed
* @Date: 2024/10/9 20:33
*/
data class NormalItem(
val name: String,
val group: String,
)

View File

@ -1,19 +0,0 @@
package com.kaixed.kchat.model.setting
/**
* @Author: kaixed
* @Date: 2024/10/8 13:53
*/
sealed class SettingItem {
data class NormalItem(
val name: String,
val group: String,
) : SettingItem()
data class FunctionItem(
val itemName: String,
val remark: String,
) : SettingItem()
data class TitleItem(val title: String) : SettingItem()
}

View File

@ -11,7 +11,8 @@ object NetworkInterface {
const val WEBSOCKET_SERVER_URL = "wss://$URL"
const val WEBSOCKET = "/websocket/single/"
const val USER_INFO = "/users/info/"
const val USER_LOGIN = "/users/login"
const val USER_LOGIN_BY_USERNAME = "/users/login/username"
const val USER_LOGIN_BY_TELEPHONE = "/users/login/telephone"
const val USER_REGISTER = "/users/register"
const val USER_MESSAGES_COUNT = "/users/%s/%s/msgCounts"

View File

@ -1,18 +1,24 @@
package com.kaixed.kchat.network
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
/**
* @Author: kaixed
* @Date: 2024/5/27 8:58
*/
object OkhttpHelper {
private var client: OkHttpClient? = null
private val client: OkHttpClient by lazy {
OkHttpClient.Builder()
// .connectTimeout(10, TimeUnit.SECONDS)
// .writeTimeout(10, TimeUnit.SECONDS)
// .readTimeout(30, TimeUnit.SECONDS)
.pingInterval(15, TimeUnit.SECONDS)
.build()
}
fun getInstance(): OkHttpClient {
if (client == null) {
client = OkHttpClient()
}
return client!!
return client
}
}

View File

@ -5,9 +5,9 @@ import com.google.gson.Gson
import com.kaixed.kchat.model.friend.AcceptContactRequest
import com.kaixed.kchat.model.friend.ContactRequestResponse
import com.kaixed.kchat.model.friend.FriendItem
import com.kaixed.kchat.model.friend.FriendResponse
import com.kaixed.kchat.model.response.ApplyFriend
import com.kaixed.kchat.model.search.SearchFriends
import com.kaixed.kchat.model.response.friend.FriendList
import com.kaixed.kchat.model.response.friend.SearchFriends
import com.kaixed.kchat.network.NetworkInterface.ACCEPT_CONTACT_REQUEST
import com.kaixed.kchat.network.NetworkInterface.ADD_FRIEND
import com.kaixed.kchat.network.NetworkInterface.FRIEND_LIST
@ -81,9 +81,7 @@ class ContactRepo {
val contactResponse =
Json.decodeFromString<AcceptContactRequest>(responseBody)
acceptFriendMutableLiveData.postValue(contactResponse)
} ?: run {
acceptFriendMutableLiveData.postValue(null)
}
} ?: acceptFriendMutableLiveData.postValue(null)
}
}
)
@ -107,9 +105,7 @@ class ContactRepo {
val applyFriend =
Json.decodeFromString<ApplyFriend>(responseBody)
mutableLiveData.postValue(applyFriend)
} ?: run {
mutableLiveData.postValue(null)
}
} ?: mutableLiveData.postValue(null)
}
override fun onFailure(call: Call, e: IOException) {
@ -135,9 +131,7 @@ class ContactRepo {
response.body?.string()?.let { responseBody ->
val searchFriends = Json.decodeFromString<SearchFriends>(responseBody)
listMutableLiveData.postValue(searchFriends)
} ?: run {
listMutableLiveData.postValue(null)
}
} ?: listMutableLiveData.postValue(null)
}
}
)
@ -161,11 +155,9 @@ class ContactRepo {
override fun onResponse(call: Call, response: Response) {
response.body?.string()?.let { responseBody ->
val friendList = Gson().fromJson(responseBody, FriendResponse::class.java)
val friendList = Gson().fromJson(responseBody, FriendList::class.java)
applyFriendMutableLiveData.postValue(friendList.data)
} ?: run {
applyFriendMutableLiveData.postValue(null)
}
} ?: applyFriendMutableLiveData.postValue(null)
}
}
)

View File

@ -1,14 +1,14 @@
package com.kaixed.kchat.repository
import androidx.lifecycle.MutableLiveData
import com.google.gson.Gson
import com.kaixed.kchat.model.login.Login
import com.kaixed.kchat.model.register.Register
import com.kaixed.kchat.model.register.RegisterRequest
import com.kaixed.kchat.model.user.UserList
import com.kaixed.kchat.model.request.RegisterRequest
import com.kaixed.kchat.model.response.login.Login
import com.kaixed.kchat.model.response.register.Register
import com.kaixed.kchat.model.response.search.UserList
import com.kaixed.kchat.network.NetworkInterface.SERVER_URL
import com.kaixed.kchat.network.NetworkInterface.USER_LIST
import com.kaixed.kchat.network.NetworkInterface.USER_LOGIN
import com.kaixed.kchat.network.NetworkInterface.USER_LOGIN_BY_TELEPHONE
import com.kaixed.kchat.network.NetworkInterface.USER_LOGIN_BY_USERNAME
import com.kaixed.kchat.network.NetworkInterface.USER_REGISTER
import com.kaixed.kchat.network.NetworkRequest
import kotlinx.serialization.json.Json
@ -25,23 +25,25 @@ import java.io.IOException
class UserRepo {
fun register(
username: String,
password: String,
avatarUrl: String,
signature: String,
nickname: String
nickname: String,
telephone: String
): MutableLiveData<Register?> {
val registerMutableLiveData = MutableLiveData<Register?>()
val registerRequest = RegisterRequest(
username = username,
username = "",
password = password,
avatarUrl = avatarUrl,
signature = signature,
nickname = nickname
nickname = nickname,
telephone = telephone
)
val json = Json.encodeToString(RegisterRequest.serializer(), registerRequest)
val json =
Json.encodeToString(RegisterRequest.serializer(), registerRequest)
NetworkRequest().postAsync(
"$SERVER_URL$USER_REGISTER",
@ -62,16 +64,25 @@ class UserRepo {
return registerMutableLiveData
}
fun login(username: String, password: String): MutableLiveData<Login?> {
fun login(
username: String,
password: String,
loginByUsername: Boolean
): MutableLiveData<Login?> {
val loginMutableLiveData = MutableLiveData<Login?>()
val url = "$SERVER_URL${
if (loginByUsername) USER_LOGIN_BY_USERNAME else USER_LOGIN_BY_TELEPHONE
}"
val requestBody = FormBody.Builder()
.add("username", username)
.add(if (loginByUsername) "username" else "telephone", username)
.add("password", password)
.build()
NetworkRequest().postAsync(
"$SERVER_URL$USER_LOGIN",
url,
requestBody,
object : Callback {
override fun onFailure(call: Call, e: IOException) {
@ -80,7 +91,7 @@ class UserRepo {
override fun onResponse(call: Call, response: Response) {
response.body?.string()?.let { responseBody ->
val login = Gson().fromJson(responseBody, Login::class.java)
val login = Json.decodeFromString<Login?>(responseBody)
loginMutableLiveData.postValue(login)
}
}
@ -100,7 +111,7 @@ class UserRepo {
override fun onResponse(call: Call, response: Response) {
response.body?.string()?.let { responseBody ->
val userList = Gson().fromJson(responseBody, UserList::class.java)
val userList = Json.decodeFromString<UserList?>(responseBody)
listMutableLiveData.postValue(userList)
}
}
@ -108,4 +119,4 @@ class UserRepo {
)
return listMutableLiveData
}
}
}

View File

@ -20,8 +20,6 @@ import io.objectbox.Box
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import okhttp3.Request
import okhttp3.Response
@ -30,13 +28,15 @@ import okhttp3.WebSocketListener
class WebSocketService : Service() {
private val TAG = "WebSocketService"
private val HEARTBEAT_ACK = "heartbeat_ack"
companion object {
private const val TAG = "WebSocketService"
private const val WEBSOCKET_CLOSE_CODE = 1000
}
private lateinit var messagesBox: Box<Messages>
private lateinit var username: String
private val binder = LocalBinder()
private val messagesMutableLiveData = MutableLiveData<Messages>()
private val HEART_BEAT_RATE = 3000L
private var webSocket: WebSocket? = null
private var heartbeatJob: Job? = null
@ -75,12 +75,13 @@ class WebSocketService : Service() {
}
private fun establishConnection() {
val request = Request.Builder().url("$WEBSOCKET_SERVER_URL$WEBSOCKET$username").build()
val listener = EchoWebSocketListener()
val client = OkhttpHelper.getInstance()
webSocket = client.newWebSocket(request, listener)
startHeartbeat()
if (webSocket == null) {
val request = Request.Builder()
.url("$WEBSOCKET_SERVER_URL$WEBSOCKET$username").build()
val listener = EchoWebSocketListener()
val client = OkhttpHelper.getInstance()
webSocket = client.newWebSocket(request, listener)
}
}
private inner class EchoWebSocketListener : WebSocketListener() {
@ -89,10 +90,6 @@ class WebSocketService : Service() {
}
override fun onMessage(webSocket: WebSocket, text: String) {
if (HEARTBEAT_ACK == text) {
return
}
val messages = Gson().fromJson(text, Messages::class.java)
messages.takerId = messages.senderId
messagesMutableLiveData.postValue(messages)
@ -115,7 +112,7 @@ class WebSocketService : Service() {
}
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
webSocket.close(1000, null)
webSocket.close(WEBSOCKET_CLOSE_CODE, null)
Log.d(TAG, "WebSocket closing: $reason")
establishConnection()
}
@ -125,19 +122,10 @@ class WebSocketService : Service() {
}
}
private fun startHeartbeat() {
heartbeatJob = CoroutineScope(IO).launch {
while (isActive) {
webSocket?.send("heartbeat")
delay(HEART_BEAT_RATE)
}
}
}
override fun onDestroy() {
super.onDestroy()
serviceJob.cancel()
heartbeatJob?.cancel()
webSocket?.close(1000, "App exited")
webSocket?.close(WEBSOCKET_CLOSE_CODE, "App exited")
}
}

View File

@ -5,18 +5,20 @@ import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import com.kaixed.kchat.databinding.ActivityApplyAddFriendBinding
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.viewmodel.ApplyFriendViewModel
class ApplyAddFriendActivity : BaseActivity() {
private lateinit var binding: ActivityApplyAddFriendBinding
class ApplyAddFriendActivity : BaseActivity<ActivityApplyAddFriendBinding>() {
private val applyAddFriendViewModel: ApplyFriendViewModel by viewModels()
override fun inflateBinding(): ActivityApplyAddFriendBinding =
ActivityApplyAddFriendBinding.inflate(layoutInflater)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityApplyAddFriendBinding.inflate(layoutInflater)
setContentView(binding.root)
val contactId = intent.getStringExtra("contactId")

View File

@ -9,10 +9,13 @@ import com.bumptech.glide.Glide
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
class ApplyFriendsDetailActivity : BaseActivity() {
class ApplyFriendsDetailActivity : BaseActivity<ActivityApplyFriendsDetailBinding>() {
private lateinit var binding: ActivityApplyFriendsDetailBinding
override fun inflateBinding(): ActivityApplyFriendsDetailBinding {
return ActivityApplyFriendsDetailBinding.inflate(layoutInflater)
}
private var request: FriendRequestItem? = null
@ -22,8 +25,6 @@ class ApplyFriendsDetailActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityApplyFriendsDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
user = intent.getParcelableExtra("user", User::class.java)
request = intent.getParcelableExtra("request", FriendRequestItem::class.java)

View File

@ -7,19 +7,22 @@ import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import com.kaixed.kchat.databinding.ActivityApproveContactRequestBinding
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.kaixed.kchat.viewmodel.ContactViewModel
class ApproveContactRequestActivity : BaseActivity() {
class ApproveContactRequestActivity : BaseActivity<ActivityApproveContactRequestBinding>() {
private lateinit var binding: ActivityApproveContactRequestBinding
private val contactViewModel: ContactViewModel by viewModels()
override fun inflateBinding(): ActivityApproveContactRequestBinding {
return ActivityApproveContactRequestBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityApproveContactRequestBinding.inflate(layoutInflater)
setContentView(binding.root)
setContent()

View File

@ -1,15 +0,0 @@
package com.kaixed.kchat.ui.activity
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
/**
* @Author: kaixed
* @Date: 2024/10/21 22:32
*/
open class BaseActivity : AppCompatActivity() {
fun toast(msg: String) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}
}

View File

@ -34,6 +34,7 @@ import com.kaixed.kchat.service.WebSocketService.LocalBinder
import com.kaixed.kchat.ui.adapter.ChatAdapter
import com.kaixed.kchat.ui.adapter.EmojiAdapter
import com.kaixed.kchat.ui.adapter.FunctionPanelAdapter
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.ui.i.OnItemClickListener
import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT_RATIO
import com.kaixed.kchat.utils.ConstantsUtil.getKeyboardHeight
@ -44,9 +45,7 @@ import io.objectbox.query.QueryBuilder
import org.json.JSONObject
import java.util.LinkedList
class ChatActivity : BaseActivity(), OnItemClickListener {
private lateinit var binding: ActivityChatBinding
class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener {
private var chatAdapter: ChatAdapter? = null
@ -86,11 +85,13 @@ class ChatActivity : BaseActivity(), OnItemClickListener {
private const val UNBLOCK_DELAY_TIME = 200L
}
override fun inflateBinding(): ActivityChatBinding {
return ActivityChatBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityChatBinding.inflate(layoutInflater)
setContentView(binding.root)
initData()
@ -271,7 +272,9 @@ class ChatActivity : BaseActivity(), OnItemClickListener {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (recyclerView.canScrollVertically(-1) && hasHistory && !loading) {
loadMoreMessages()
}
// val layoutManager = checkNotNull(recyclerView.layoutManager as LinearLayoutManager?)
// val firstVisiblePosition = layoutManager.findLastVisibleItemPosition()
@ -443,7 +446,9 @@ class ChatActivity : BaseActivity(), OnItemClickListener {
val messagesSize = messagesList.size
messagesList.addAll(messagesSize, messages1)
chatAdapter!!.notifyItemRangeInserted(messagesSize, newMessages.size)
binding.recycleChatList.post {
chatAdapter!!.notifyItemRangeInserted(messagesSize, newMessages.size)
}
}
loading = false

View File

@ -1,52 +0,0 @@
package com.kaixed.kchat.ui.activity
import android.os.Bundle
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.kaixed.kchat.databinding.ActivityFriendListBinding
import com.kaixed.kchat.model.friend.FriendItem
import com.kaixed.kchat.ui.adapter.FriendListAdapter
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.kaixed.kchat.viewmodel.FriendListViewModel
class FriendListActivity : BaseActivity() {
private lateinit var binding: ActivityFriendListBinding
private val friendListViewModel: FriendListViewModel by viewModels()
private var friendList: MutableList<FriendItem> = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityFriendListBinding.inflate(layoutInflater)
setContentView(binding.root)
getFriendList(getUsername())
if (friendList.isEmpty()) {
binding.tvNothing.visibility = View.VISIBLE
} else {
binding.tvNothing.visibility = View.GONE
}
binding.recycleFriendList.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.recycleFriendList.adapter = FriendListAdapter(friendList, this)
}
private fun getFriendList(username: String) {
friendListViewModel.getFriendList(username).observe(this) { value ->
value?.let {
friendList.addAll(value)
binding.recycleFriendList.adapter?.notifyDataSetChanged()
if (friendList.isEmpty()) {
binding.tvNothing.visibility = View.VISIBLE
} else {
binding.tvNothing.visibility = View.GONE
}
}
}
}
}

View File

@ -5,6 +5,7 @@ import android.content.Intent
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import com.kaixed.kchat.databinding.ActivityLaunchBinding
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.utils.Constants.MMKV_COMMON_DATA
import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION
import com.kaixed.kchat.utils.Constants.STATUS_BAR_HEIGHT
@ -12,20 +13,20 @@ import com.kaixed.kchat.utils.Constants.USER_LOGIN_STATUS
import com.tencent.mmkv.MMKV
class LaunchActivity : BaseActivity() {
class LaunchActivity : BaseActivity<ActivityLaunchBinding>() {
private lateinit var binding: ActivityLaunchBinding
override fun inflateBinding(): ActivityLaunchBinding {
return ActivityLaunchBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityLaunchBinding.inflate(layoutInflater)
setContentView(binding.root)
val mmkvData: MMKV = MMKV.mmkvWithID(MMKV_COMMON_DATA)
mmkvData.putInt(STATUS_BAR_HEIGHT, getStatusBarHeight())
val mmkv = MMKV.mmkvWithID(MMKV_USER_SESSION)
val mmkv = MMKV.defaultMMKV()
if (mmkv.decodeBool(USER_LOGIN_STATUS)) {
navigateToMain()

View File

@ -9,10 +9,10 @@ import android.text.TextWatcher
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.kaixed.kchat.R
import com.kaixed.kchat.databinding.ActivityLoginBinding
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT_RATIO
import com.kaixed.kchat.utils.Constants.MMKV_COMMON_DATA
import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION
@ -21,27 +21,29 @@ import com.kaixed.kchat.utils.DrawableUtil.createDrawable
import com.kaixed.kchat.viewmodel.LoginViewModel
import com.tencent.mmkv.MMKV
class LoginActivity : AppCompatActivity() {
class LoginActivity : BaseActivity<ActivityLoginBinding>() {
private lateinit var binding: ActivityLoginBinding
override fun inflateBinding(): ActivityLoginBinding {
return ActivityLoginBinding.inflate(layoutInflater)
}
private val mViewModel: LoginViewModel by viewModels()
private var mmkv: MMKV? = null
private val mmkv by lazy { MMKV.mmkvWithID(MMKV_USER_SESSION) }
private var previousKeyboardHeight = 0
private var loginByUsername = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
mmkv = MMKV.mmkvWithID(MMKV_USER_SESSION)
binding.etUsername.addTextChangedListener(textWatcher)
binding.etPassword.addTextChangedListener(textWatcher)
initView()
setListener()
binding.tvLogin.setOnClickListener {
val username = binding.etUsername.text.toString().trim()
val password = binding.etPassword.text.toString().trim()
@ -49,30 +51,51 @@ class LoginActivity : AppCompatActivity() {
if (username.isEmpty() || password.isEmpty()) {
Toast.makeText(this, "请输入用户名或密码", Toast.LENGTH_SHORT).show()
}
mViewModel.login(username, password).observe(this) { loginResult ->
if (loginResult == null) {
runOnUiThread {
Toast.makeText(
this@LoginActivity,
"登录异常",
Toast.LENGTH_SHORT
)
.show()
mViewModel.login(username, password, loginByUsername).observe(this) { loginResult ->
loginResult?.let {
if (it.code == "200") {
mmkv.apply {
encode("username", loginResult.data.username)
encode("nickname", loginResult.data.nickname)
encode("avatarUrl", loginResult.data.avatarUrl)
}
MMKV.defaultMMKV().encode("userLoginStatus", true)
Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show()
navigateToMain()
} else {
Toast.makeText(this, "登录异常", Toast.LENGTH_SHORT).show()
}
} else if ("200" == loginResult.code) {
mmkv!!.encode("username", loginResult.data.username)
mmkv!!.encode("nickname", loginResult.data.nickname)
mmkv!!.encode("nickname", loginResult.data.avatarUrl)
mmkv!!.encode("userLoginStatus", true)
Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show()
navigateToMain()
}
} ?: Toast.makeText(this, "登录异常", Toast.LENGTH_SHORT).show()
}
}
getKeyboardHeight()
}
private fun initView() {
setView()
}
private fun setListener() {
binding.ivClose.setOnClickListener {
finish()
}
binding.tvChangeLoginWay.setOnClickListener {
loginByUsername = !loginByUsername
setView()
}
}
private fun setView() {
binding.tvTitle.text = if (loginByUsername) "用户名登录" else "手机号登录"
binding.etUsername.hint = if (loginByUsername) "请填写用户名" else "请填写手机号"
binding.tvUsername.text = if (loginByUsername) "用户名" else "手机号"
binding.tvChangeLoginWay.text = if (loginByUsername) "手机号登录" else "用户名登录"
binding.tvTip.text =
if (loginByUsername) "上述账号仅用于登陆验证" else "上述手机号仅用于登陆验证"
}
private fun getKeyboardHeight() {
val rootLayout = binding.root
@ -95,34 +118,36 @@ class LoginActivity : AppCompatActivity() {
}
}
abstract class SimpleTextWatcher : TextWatcher {
private val textWatcher = object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) {}
}
private var textWatcher: TextWatcher = object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) {
super.afterTextChanged(s)
if (s.isNotEmpty()) {
if (binding.etUsername.text.isNotEmpty() && binding.etPassword.text.isNotEmpty()) {
binding.tvLogin.setTextColor(
val isInputValid =
binding.etUsername.text.isNotEmpty() && binding.etPassword.text.isNotEmpty()
binding.tvLogin.apply {
setTextColor(
if (isInputValid) {
ContextCompat.getColor(
this@LoginActivity,
R.color.white
)
)
binding.tvLogin.background =
createDrawable(
ContextCompat.getColor(this@LoginActivity, R.color.green),
dpToPx(this@LoginActivity, 8)
} else {
Color.parseColor("#B4B4B4")
}
)
background = createDrawable(
if (isInputValid) {
ContextCompat.getColor(
this@LoginActivity,
R.color.green
)
} else {
binding.tvLogin.setTextColor(Color.parseColor("#B4B4B4"))
binding.tvLogin.background =
createDrawable(Color.parseColor("#E1E1E1"), dpToPx(this@LoginActivity, 8))
}
} else {
Color.parseColor("#E1E1E1")
},
dpToPx(this@LoginActivity, 8)
)
}
}
}

View File

@ -8,13 +8,13 @@ import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment
import com.kaixed.kchat.R
import com.kaixed.kchat.databinding.ActivityMainBinding
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.ui.fragment.ContactFragment
import com.kaixed.kchat.ui.fragment.DiscoveryFragment
import com.kaixed.kchat.ui.fragment.HomeFragment
import com.kaixed.kchat.ui.fragment.MineFragment
class MainActivity : BaseActivity(), View.OnClickListener {
private lateinit var binding: ActivityMainBinding
class MainActivity : BaseActivity<ActivityMainBinding>(), View.OnClickListener {
private var colorMain = 0
private var colorBlack = 0
@ -27,13 +27,15 @@ class MainActivity : BaseActivity(), View.OnClickListener {
private val fragments = mutableMapOf<Int, Fragment>()
override fun inflateBinding(): ActivityMainBinding {
return ActivityMainBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
enableEdgeToEdge()
// 设置透明状态栏
WindowCompat.setDecorFitsSystemWindows(window, false)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
colorMain = ContextCompat.getColor(this, R.color.green)
colorBlack = ContextCompat.getColor(this, R.color.black)
@ -73,7 +75,6 @@ class MainActivity : BaseActivity(), View.OnClickListener {
}
}
private fun initView() {
binding.clHome.setOnClickListener(this)
binding.clHome.tag = KEY_HOME_FRAGMENT
@ -88,7 +89,6 @@ class MainActivity : BaseActivity(), View.OnClickListener {
binding.clMine.tag = KEY_MINE_FRAGMENT
}
override fun onClick(view: View) {
val key = view.tag as Int
showFragment(key)

View File

@ -1,28 +0,0 @@
package com.kaixed.kchat.ui.activity
import android.content.Intent
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import com.kaixed.kchat.databinding.ActivityProfileBinding
import com.kaixed.kchat.ui.fragment.MyBottomSheetFragment
class ProfileActivity : AppCompatActivity() {
private lateinit var binding: ActivityProfileBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityProfileBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.clProfile.setOnClickListener {
startActivity(Intent(this@ProfileActivity, ProfileDetailActivity::class.java))
}
binding.rlSetting.setOnClickListener {
val bottomSheetFragment = MyBottomSheetFragment()
bottomSheetFragment.show(supportFragmentManager, bottomSheetFragment.tag)
}
}
}

View File

@ -1,20 +1,36 @@
package com.kaixed.kchat.ui.activity
import android.content.Intent
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import com.kaixed.kchat.databinding.ActivityProfileDetailBinding
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.utils.ConstantsUtil.getNickName
class ProfileDetailActivity : BaseActivity<ActivityProfileDetailBinding>() {
override fun inflateBinding(): ActivityProfileDetailBinding {
return ActivityProfileDetailBinding.inflate(layoutInflater)
}
class ProfileDetailActivity : AppCompatActivity() {
private lateinit var binding: ActivityProfileDetailBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityProfileDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.ciAvatar.setOnItemClickListener {
finish()
}
setListener()
binding.ciNickname.setItemDesc(getNickName())
}
private fun setListener() {
binding.ciNickname.setOnClickListener {
startActivity(
Intent(this, RenameActivity::class.java)
)
}
}
}

View File

@ -17,13 +17,16 @@ import androidx.core.content.ContextCompat
import androidx.core.widget.addTextChangedListener
import com.kaixed.kchat.R
import com.kaixed.kchat.databinding.ActivityRegisterBinding
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.utils.DensityUtil.dpToPx
import com.kaixed.kchat.utils.DrawableUtil.createDrawable
import com.kaixed.kchat.viewmodel.UserViewModel
class RegisterActivity : BaseActivity() {
class RegisterActivity : BaseActivity<ActivityRegisterBinding>() {
private lateinit var binding: ActivityRegisterBinding
override fun inflateBinding(): ActivityRegisterBinding {
return ActivityRegisterBinding.inflate(layoutInflater)
}
private var tvContinueEnable: Boolean = false
@ -32,8 +35,6 @@ class RegisterActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityRegisterBinding.inflate(layoutInflater)
setContentView(binding.root)
initView()
@ -47,13 +48,13 @@ class RegisterActivity : BaseActivity() {
binding.tvContinue.setOnClickListener {
if (tvContinueEnable) {
val username = binding.etUsername.text.toString().trim()
val telephone = binding.etUsername.text.toString().trim()
val nickname = binding.etNickname.text.toString().trim()
val password = binding.etPassword.text.toString().trim()
val avatarUrl = ""
val signature = ""
mViewModel.register(username, nickname, password, avatarUrl, signature)
mViewModel.register(nickname, password, avatarUrl, signature, telephone)
.observe(this) { registerResult ->
Log.d("haha", registerResult.toString())
when (registerResult?.code) {

View File

@ -0,0 +1,40 @@
package com.kaixed.kchat.ui.activity
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.core.widget.addTextChangedListener
import com.kaixed.kchat.databinding.ActivityRenameBinding
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.utils.ConstantsUtil.getNickName
import com.kaixed.kchat.utils.WidgetUtil
class RenameActivity : BaseActivity<ActivityRenameBinding>() {
override fun inflateBinding(): ActivityRenameBinding {
return ActivityRenameBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
val nickname = getNickName()
var enable = false
binding.etNickname.setText(nickname)
binding.etNickname.addTextChangedListener(
afterTextChanged = { et ->
enable = et.toString() != nickname
binding.ctb.setBtnEnable(et.toString() != nickname)
}
)
binding.ctb.setOnBtnClickListener {
if (enable) {
toast("保存成功")
WidgetUtil.showLoadingDialog(this, "正在保存")
}
}
}
}

View File

@ -4,6 +4,7 @@ import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.text.SpannableString
import android.text.Spanned
@ -14,27 +15,28 @@ import android.view.Window
import android.view.inputmethod.InputMethodManager
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.addTextChangedListener
import com.bumptech.glide.Glide
import com.kaixed.kchat.databinding.ActivitySearchFriendsBinding
import com.kaixed.kchat.databinding.DialogLoadingBinding
import com.kaixed.kchat.model.search.User
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.viewmodel.SearchFriendsViewModel
class SearchFriendsActivity : AppCompatActivity() {
private lateinit var binding: ActivitySearchFriendsBinding
class SearchFriendsActivity : BaseActivity<ActivitySearchFriendsBinding>() {
private var isSearching = false
private lateinit var userItem: User
private lateinit var loadingDialog: Dialog
override fun inflateBinding(): ActivitySearchFriendsBinding {
return ActivitySearchFriendsBinding.inflate(layoutInflater)
}
private val searchFriendsViewModel: SearchFriendsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivitySearchFriendsBinding.inflate(layoutInflater)
setContentView(binding.root)
initView()
@ -62,6 +64,7 @@ class SearchFriendsActivity : AppCompatActivity() {
binding.clSearchUser.visibility = View.GONE
} else {
binding.clSearchUser.visibility = View.VISIBLE
binding.tvNothing.visibility = View.INVISIBLE
val spannableString = SpannableString("搜索:$it")
val colorSpan = ForegroundColorSpan(Color.parseColor("#2BA245"))
@ -90,11 +93,20 @@ class SearchFriendsActivity : AppCompatActivity() {
searchFriendsViewModel.searchFriends(username).observe(this) { value ->
loadingDialog.dismiss()
value?.let {
userItem = value.data
if (::userItem.isInitialized) {
setVisibility(true)
setContent()
if (value.code == "200") {
value.data?.let {
userItem = value.data
if (::userItem.isInitialized) {
setVisibility(true)
setContent()
}
} ?: run {
setVisibility(false)
binding.tvNothing.visibility = View.VISIBLE
}
}
}
}
}
@ -110,6 +122,7 @@ class SearchFriendsActivity : AppCompatActivity() {
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialog.setCancelable(false)
dialog.setContentView(binding.root)
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
dialog.show()
return dialog
}

View File

@ -1,52 +0,0 @@
package com.kaixed.kchat.ui.activity
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.kaixed.kchat.databinding.ActivitySettingBinding
import com.kaixed.kchat.model.setting.SettingItem
import com.kaixed.kchat.ui.adapter.SettingListAdapter
class SettingActivity : AppCompatActivity() {
private lateinit var binding: ActivitySettingBinding
private lateinit var settingItems: MutableList<SettingItem>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySettingBinding.inflate(layoutInflater)
enableEdgeToEdge()
setContentView(binding.root)
settingItems = getSettingItems()
binding.rvSetting.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.rvSetting.adapter = SettingListAdapter(settingItems)
}
private fun getSettingItems(): MutableList<SettingItem> {
return mutableListOf(
SettingItem.NormalItem("账号与安全", "安全"),
SettingItem.TitleItem(""),
SettingItem.NormalItem("青少年模式", "模式"),
SettingItem.NormalItem("关怀模式", "模式"),
SettingItem.TitleItem(""),
SettingItem.NormalItem("新消息通知", ""),
SettingItem.NormalItem("聊天", ""),
SettingItem.NormalItem("设备", ""),
SettingItem.NormalItem("通用", ""),
SettingItem.TitleItem("隐私"),
SettingItem.NormalItem("朋友权限", "隐私"),
SettingItem.NormalItem("个人信息与权限", "隐私"),
SettingItem.NormalItem("个人信息收集清单", "隐私"),
SettingItem.NormalItem("第三方信息共享清单", "隐私"),
SettingItem.TitleItem(""),
SettingItem.NormalItem("插件", "插件"),
SettingItem.TitleItem(""),
SettingItem.NormalItem("关于", "帮助"),
SettingItem.NormalItem("帮助与反馈", "帮助"),
SettingItem.TitleItem(""),
SettingItem.NormalItem("切换账户", "账户"),
SettingItem.NormalItem("退出", "账户")
)
}
}

View File

@ -2,7 +2,6 @@ package com.kaixed.kchat.ui.adapter
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -142,8 +141,6 @@ class ChatAdapter(
is RedPacketViewHolder -> {
holder.bindData(singleMessage)
}
}
holder.itemView.setOnLongClickListener {
@ -152,19 +149,18 @@ class ChatAdapter(
}
}
class TipViewHolder(val binding: ChatRecycleItemTipBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindData(message: Messages) {
if (message.show)
if (message.show) {
binding.tvTip.text = binding.root.context.getString(
R.string.withdraw_message_format,
message.senderId
)
}
}
}
class CustomViewHolder(val binding: ChatRecycleItemCustomBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindData(message: Messages, context: Context) {
@ -176,7 +172,6 @@ class ChatAdapter(
message.content,
DensityUtil.dpToPx(context, 18)
)
}
private fun setViewPosition(
@ -198,14 +193,18 @@ class ChatAdapter(
}
constraintSet.connect(
avatarId, avatarConnectionStart,
ConstraintSet.PARENT_ID, avatarConnectionStart,
avatarId,
avatarConnectionStart,
ConstraintSet.PARENT_ID,
avatarConnectionStart,
margin
)
constraintSet.connect(
messageId, avatarConnectionStart,
avatarId, avatarConnectionEnd,
messageId,
avatarConnectionStart,
avatarId,
avatarConnectionEnd,
margin
)
@ -213,43 +212,36 @@ class ChatAdapter(
}
}
class ImageViewHolder(val binding: ChatRecycleItemImageBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindData(message: Messages) {
}
}
class VoiceViewHolder(val binding: ChatRecycleItemVoiceBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindData(message: Messages) {
}
}
class LocationViewHolder(val binding: ChatRecycleItemLocationBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindData(message: Messages) {
}
}
class EmojiViewHolder(val binding: ChatRecycleItemEmojiBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindData(message: Messages) {
}
}
class RedPacketViewHolder(val binding: ChatRecycleItemRedPacketBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindData(message: Messages) {
}
}
override fun getItemViewType(position: Int): Int {
val message = messages[position]
return when (MessageType.fromValue(message.type.toInt())) {

View File

@ -5,28 +5,45 @@ import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.bumptech.glide.Glide
import com.kaixed.kchat.databinding.ContactRecycleFooterItemBinding
import com.kaixed.kchat.databinding.FriendRecycleFooterItemBinding
import com.kaixed.kchat.databinding.FriendRecycleItemBinding
import com.kaixed.kchat.model.friend.FriendItem
import com.kaixed.kchat.model.item.FriendItem
import com.kaixed.kchat.ui.activity.ChatActivity
import com.kaixed.kchat.utils.Pinyin4jUtil
/**
* @Author: kaixed
* @Date: 2024/10/14 14:47
*/
class FriendListAdapter(private val items: MutableList<FriendItem>, private val context: Context) :
class FriendListAdapter(private var items: MutableList<FriendItem>, private val context: Context) :
RecyclerView.Adapter<ViewHolder>() {
class ItemViewHolder(val binding: FriendRecycleItemBinding) : ViewHolder(binding.root)
class FooterViewHolder(val binding: ContactRecycleFooterItemBinding) : ViewHolder(binding.root)
class FooterViewHolder(val binding: FriendRecycleFooterItemBinding) : ViewHolder(binding.root)
companion object {
const val TYPE_ITEM: Int = 0
const val TYPE_FOOTER: Int = 1
private const val TAG = "FriendListAdapter"
}
fun updateItems(newItems: List<FriendItem>) {
val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize() = items.size
override fun getNewListSize() = newItems.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
items[oldItemPosition].username == newItems[newItemPosition].username
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
items[oldItemPosition] == newItems[newItemPosition]
})
items = newItems.toMutableList()
diffResult.dispatchUpdatesTo(this)
}
private val defaultItems = listOf(
@ -40,7 +57,7 @@ class FriendListAdapter(private val items: MutableList<FriendItem>, private val
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return when (viewType) {
TYPE_FOOTER -> {
val binding = ContactRecycleFooterItemBinding.inflate(
val binding = FriendRecycleFooterItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
@ -68,8 +85,17 @@ class FriendListAdapter(private val items: MutableList<FriendItem>, private val
is ItemViewHolder -> {
val item = items[position]
// Log.d(TAG, "${item.nickname} ${item.firstInSection}")
holder.binding.tvNickname.text = item.nickname
if (item.firstInSection) {
holder.binding.tvLetter.text =
Pinyin4jUtil.toPinyin(item.nickname)[0].toString().uppercase()
holder.binding.tvLetter.visibility = ViewGroup.VISIBLE
} else {
holder.binding.tvLetter.visibility = ViewGroup.GONE
}
if (position <= defaultItems.size - 1) {
holder.binding.ifvAvatar.setImageResource(defaultItems[position])
holder.binding.ifvAvatar.setBackgroundColor(context.getColor(com.kaixed.kchat.R.color.gray))

View File

@ -1,99 +0,0 @@
package com.kaixed.kchat.ui.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.View.VISIBLE
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.kaixed.kchat.databinding.ItemSettingFunctionBinding
import com.kaixed.kchat.databinding.ItemSettingNormalBinding
import com.kaixed.kchat.databinding.ItemSettingTitleBinding
import com.kaixed.kchat.model.setting.SettingItem
import com.kaixed.kchat.utils.Constants.MESSAGE_TYPE_FUNCTION
import com.kaixed.kchat.utils.Constants.MESSAGE_TYPE_NORMAL
import com.kaixed.kchat.utils.Constants.MESSAGE_TYPE_TITLE
/**
* @Author: kaixed
* @Date: 2024/10/8 13:53
*/
class SettingListAdapter(private val items: MutableList<SettingItem>) :
RecyclerView.Adapter<ViewHolder>() {
class NormalViewHolder(val binding: ItemSettingNormalBinding) : ViewHolder(binding.root)
class TitleViewHolder(val binding: ItemSettingTitleBinding) : ViewHolder(binding.root)
class FunctionViewHolder(val binding: ItemSettingFunctionBinding) : ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return when (viewType) {
MESSAGE_TYPE_TITLE -> {
val binding = ItemSettingTitleBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
TitleViewHolder(binding)
}
MESSAGE_TYPE_NORMAL -> {
val binding = ItemSettingNormalBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
NormalViewHolder(binding)
}
MESSAGE_TYPE_FUNCTION -> {
val binding = ItemSettingFunctionBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
FunctionViewHolder(binding)
}
else -> throw IllegalArgumentException("Invalid view type")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is TitleViewHolder -> bindTitleViewHolder(holder, position)
is NormalViewHolder -> bindNormalViewHolder(holder, position)
is FunctionViewHolder -> bindFunctionViewHolder(holder, position)
}
}
private fun bindTitleViewHolder(holder: TitleViewHolder, position: Int) {
val item = items[position] as SettingItem.TitleItem
holder.binding.tvItemName.text = item.title
holder.binding.tvItemName.visibility = if (item.title.isEmpty()) View.GONE else VISIBLE
holder.binding.view.visibility = if (item.title.isEmpty()) VISIBLE else View.GONE
}
private fun bindNormalViewHolder(holder: NormalViewHolder, position: Int) {
val item = items[position] as SettingItem.NormalItem
holder.binding.tvItemName.text = item.name
holder.binding.viewItemDecoration.visibility =
if (position != items.size - 1 && items[position + 1] is SettingItem.TitleItem) View.GONE else VISIBLE
}
private fun bindFunctionViewHolder(holder: FunctionViewHolder, position: Int) {
}
override fun getItemViewType(position: Int): Int {
return when (items[position]) {
is SettingItem.TitleItem -> MESSAGE_TYPE_TITLE
is SettingItem.NormalItem -> MESSAGE_TYPE_NORMAL
is SettingItem.FunctionItem -> MESSAGE_TYPE_FUNCTION
}
}
override fun getItemCount(): Int = items.size
}

View File

@ -0,0 +1,27 @@
package com.kaixed.kchat.ui.base
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding
/**
* @Author: kaixed
* @Date: 2024/11/4 10:43
*/
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
protected lateinit var binding: VB
abstract fun inflateBinding(): VB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = inflateBinding()
setContentView(binding.root)
}
fun toast(msg: String) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}
}

View File

@ -6,19 +6,21 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.kaixed.kchat.R
import com.kaixed.kchat.databinding.FragmentContactBinding
import com.kaixed.kchat.model.friend.FriendItem
import com.kaixed.kchat.model.item.FriendItem
import com.kaixed.kchat.ui.adapter.FriendListAdapter
import com.kaixed.kchat.ui.base.BaseFragment
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.kaixed.kchat.viewmodel.FriendListViewModel
class ContactFragment : BaseFragment<FragmentContactBinding>() {
private val friendListViewModel: FriendListViewModel by viewModels()
private val friendAdapter: FriendListAdapter by lazy {
FriendListAdapter(friendList, requireContext())
}
private var friendList: MutableList<FriendItem> = mutableListOf()
companion object {
@ -35,68 +37,27 @@ class ContactFragment : BaseFragment<FragmentContactBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
getDefaultItem()
getFriendList(getUsername())
binding.recycleFriendList.layoutManager =
LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
binding.recycleFriendList.adapter = FriendListAdapter(friendList, requireContext())
binding.recycleFriendList.adapter = friendAdapter
getFriendList()
}
private fun getFriendList(username: String) {
friendListViewModel.getFriendList(username).observe(requireActivity()) { value ->
value?.let {
friendList.addAll(value)
binding.recycleFriendList.adapter?.notifyDataSetChanged()
private fun getFriendList() {
friendListViewModel.friendListLiveData.observe(viewLifecycleOwner) { friends ->
friends?.let {
friendList.addAll(friends)
friendAdapter.updateItems(friendList)
}
}
friendListViewModel.loadFriendList(getUsername())
}
private fun getDefaultItem() {
friendList.add(
FriendItem(
username = "",
avatarUrl = "",
nickname = "新的朋友",
remark = "",
signature = ""
)
)
friendList.add(
FriendItem(
username = "",
avatarUrl = "",
nickname = "群聊",
remark = "",
signature = ""
)
)
friendList.add(
FriendItem(
username = "",
avatarUrl = "",
nickname = "标签",
remark = "",
signature = ""
)
)
friendList.add(
FriendItem(
username = "",
avatarUrl = "",
nickname = "公众号",
remark = "",
signature = ""
)
)
friendList.add(
FriendItem(
username = "",
avatarUrl = "",
nickname = "服务号",
remark = "",
signature = ""
)
)
friendList.add(FriendItem("新的朋友", "", "新的朋友", ""))
friendList.add(FriendItem("群聊", "", "群聊", ""))
friendList.add(FriendItem("标签", "", "标签", ""))
friendList.add(FriendItem("公众号", "", "公众号", ""))
friendList.add(FriendItem("服务号", "", "服务号", ""))
}
}

View File

@ -1,6 +1,7 @@
package com.kaixed.kchat.ui.fragment
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -9,6 +10,10 @@ import com.kaixed.kchat.ui.base.BaseFragment
class DiscoveryFragment : BaseFragment<FragmentDiscoveryBinding>() {
companion object {
private const val TAG = "DiscoveryFragment"
}
override fun inflateBinding(
inflater: LayoutInflater,
container: ViewGroup?
@ -18,6 +23,7 @@ class DiscoveryFragment : BaseFragment<FragmentDiscoveryBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d(TAG,"我加载了")
}
}

View File

@ -28,9 +28,7 @@ import com.kaixed.kchat.service.WebSocketService
import com.kaixed.kchat.service.WebSocketService.LocalBinder
import com.kaixed.kchat.ui.activity.AddFriendsActivity
import com.kaixed.kchat.ui.activity.ContactUpdatesActivity
import com.kaixed.kchat.ui.activity.FriendListActivity
import com.kaixed.kchat.ui.activity.MessageActivity
import com.kaixed.kchat.ui.activity.ProfileActivity
import com.kaixed.kchat.ui.activity.SearchActivity
import com.kaixed.kchat.ui.adapter.ChatListAdapter
import com.kaixed.kchat.ui.adapter.MyGridAdapter
@ -94,11 +92,6 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnChatListItemClickLis
startActivity(intent)
}
binding.ifvAvatar.setOnClickListener {
val intent =
Intent(context, ProfileActivity::class.java)
startActivity(intent)
}
}
private fun flipImage(v: View) {
@ -310,12 +303,12 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnChatListItemClickLis
}
6 -> {
val intent1 =
Intent(
context,
FriendListActivity::class.java
)
startActivity(intent1)
// val intent1 =
// Intent(
// context,
// FriendListActivity::class.java
// )
// startActivity(intent1)
}
}
}

View File

@ -1,31 +0,0 @@
package com.kaixed.kchat.ui.fragment
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceFragmentCompat
import com.kaixed.kchat.R
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
val view = super.onCreateView(inflater, container, savedInstanceState)
view.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.white))
return view
}
}

View File

@ -1,6 +1,7 @@
package com.kaixed.kchat.ui.widget
import android.content.Context
import android.content.res.TypedArray
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
@ -25,9 +26,10 @@ class CustomItem @JvmOverloads constructor(
ItemCustomBinding.inflate(LayoutInflater.from(context), this, true)
init {
attrs?.let {
val typedArray = context.obtainStyledAttributes(it, R.styleable.CustomItem, 0, 0)
// 获取自定义属性
val itemName = typedArray.getString(R.styleable.CustomItem_itemName) ?: ""
val itemIconSize = typedArray.getDimension(R.styleable.CustomItem_iconSize, 0f)
val itemIcon = typedArray.getResourceId(R.styleable.CustomItem_itemIcon, -1)
@ -36,72 +38,96 @@ class CustomItem @JvmOverloads constructor(
val itemDesc = typedArray.getString(R.styleable.CustomItem_itemDesc) ?: ""
val itemRedTip = typedArray.getBoolean(R.styleable.CustomItem_itemRedTip, false)
val isShowTopDivider =
typedArray.getBoolean(R.styleable.CustomItem_isTopDividerVisible, false)
val isShowBottomDivider =
typedArray.getBoolean(R.styleable.CustomItem_isBottomDividerVisible, false)
binding.viewRedTip.visibility = if (itemRedTip) View.VISIBLE else View.GONE
if (itemDesc.isNotEmpty()) {
binding.tvItemDesc.visibility = View.VISIBLE
binding.tvItemDesc.text = itemDesc
} else {
binding.tvItemDesc.visibility = View.GONE
}
if (itemLeftIcon != -1) {
binding.ivItemLeftIcon.visibility = View.VISIBLE
binding.ivItemLeftIcon.setImageResource(itemLeftIcon)
} else {
binding.ivItemIcon.visibility = View.GONE
}
if (itemIcon != -1) {
binding.ivItemIcon.visibility = View.VISIBLE
binding.ivItemIcon.setImageResource(itemIcon)
val displayMetrics = context.resources.displayMetrics
if (itemIconRound.toInt() != 0) {
val requestOptions =
RequestOptions().transform(RoundedCorners(itemIconRound.toInt()))
Glide.with(context).load(itemIcon).apply(requestOptions)
.into(binding.ivItemIcon)
binding.ivItemIcon.clipToOutline = true
}
if (itemIconSize.toInt() != 0) {
binding.ivItemIcon.layoutParams.width =
(itemIconSize * displayMetrics.density).toInt()
binding.ivItemIcon.layoutParams.height =
(itemIconSize * displayMetrics.density).toInt()
} else {
binding.ivItemIcon.visibility = View.GONE
}
} else {
binding.ivItemIcon.visibility = View.GONE
}
if (itemName.isNotEmpty()) {
binding.tvItemName.text = itemName
}
binding.decorationTop.visibility =
if (isShowTopDivider) View.VISIBLE else View.GONE
binding.decorationBottom.visibility =
if (isShowBottomDivider) View.VISIBLE else View.GONE
// 设置视图
setupRedTip(itemRedTip)
setupItemDesc(itemDesc)
setupLeftIcon(itemLeftIcon)
setupItemIcon(itemIcon, itemIconSize, itemIconRound)
setupItemName(itemName)
setupDividers(typedArray)
typedArray.recycle()
}
}
// 设置红点显示
private fun setupRedTip(itemRedTip: Boolean) {
binding.viewRedTip.visibility = if (itemRedTip) View.VISIBLE else View.GONE
}
// 设置描述
private fun setupItemDesc(itemDesc: String) {
if (itemDesc.isNotEmpty()) {
binding.tvItemDesc.visibility = View.VISIBLE
binding.tvItemDesc.text = itemDesc
} else {
binding.tvItemDesc.visibility = View.GONE
}
}
// 设置左侧图标
private fun setupLeftIcon(itemLeftIcon: Int) {
if (itemLeftIcon != -1) {
binding.ivItemLeftIcon.visibility = View.VISIBLE
binding.ivItemLeftIcon.setImageResource(itemLeftIcon)
} else {
binding.ivItemLeftIcon.visibility = View.GONE
}
}
// 设置主图标
private fun setupItemIcon(itemIcon: Int, itemIconSize: Float, itemIconRound: Float) {
if (itemIcon != -1) {
binding.ivItemIcon.visibility = View.VISIBLE
binding.ivItemIcon.setImageResource(itemIcon)
if (itemIconSize > 0) {
binding.ivItemIcon.layoutParams.width = itemIconSize.toInt()
binding.ivItemIcon.layoutParams.height = itemIconSize.toInt()
}
if (itemIconRound > 0) {
val requestOptions =
RequestOptions().transform(RoundedCorners(itemIconRound.toInt()))
Glide.with(context).load(itemIcon).apply(requestOptions)
.into(binding.ivItemIcon)
binding.ivItemIcon.clipToOutline = true
}
} else {
binding.ivItemIcon.visibility = View.GONE
}
}
// 设置项名称
private fun setupItemName(itemName: String) {
if (itemName.isNotEmpty()) {
binding.tvItemName.text = itemName
}
}
// 设置分割线
private fun setupDividers(typedArray: TypedArray) {
val isShowTopDivider =
typedArray.getBoolean(R.styleable.CustomItem_isTopDividerVisible, false)
val isShowBottomDivider =
typedArray.getBoolean(R.styleable.CustomItem_isBottomDividerVisible, false)
binding.decorationTop.visibility = if (isShowTopDivider) View.VISIBLE else View.GONE
binding.decorationBottom.visibility = if (isShowBottomDivider) View.VISIBLE else View.GONE
}
// 设置点击事件
fun setOnItemClickListener(listener: OnClickListener) {
binding.root.setOnClickListener(listener)
}
// 设置红点的可见性
fun setRedTipVisibility(visible: Boolean) {
binding.viewRedTip.visibility = if (visible) View.VISIBLE else View.GONE
}
// 设置描述
fun setItemDesc(str: String) {
binding.tvItemDesc.text = str
}
}

View File

@ -1,61 +0,0 @@
package com.kaixed.kchat.ui.widget
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import com.kaixed.kchat.R
import com.kaixed.kchat.databinding.PreferenceCustomLayoutBinding
class CustomPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : Preference(context, attrs, defStyleAttr) {
private var isShowTopDivider = false
private var isShowBottomDivider = false
init {
layoutResource = R.layout.preference_custom_layout
attrs?.let {
val typedArray = context.obtainStyledAttributes(it, R.styleable.CustomPreference, 0, 0)
val isShowTopDivider =
typedArray.getBoolean(R.styleable.CustomPreference_isShowTopDivider, false)
val isShowBottomDivider =
typedArray.getBoolean(R.styleable.CustomPreference_isShowBottomDivider, false)
typedArray.recycle()
this.isShowTopDivider = isShowTopDivider
this.isShowBottomDivider = isShowBottomDivider
}
}
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
holder.isDividerAllowedAbove = false
holder.isDividerAllowedBelow = false
val binding = PreferenceCustomLayoutBinding.bind(holder.itemView)
binding.tvItemName.text = title
binding.decorationTop.visibility = if (isShowTopDivider) {
View.VISIBLE
} else {
View.GONE
}
binding.decorationBottom.visibility = if (isShowBottomDivider) {
View.VISIBLE
} else {
View.GONE
}
}
}

View File

@ -1,55 +0,0 @@
package com.kaixed.kchat.ui.widget
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.View
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceViewHolder
import com.kaixed.kchat.R
import com.kaixed.kchat.databinding.PreferenceCategoryCustomLayoutBinding
/**
* @Author: kaixed
* @Date: 2024/10/10 14:45
*/
class CustomPreferenceCategory @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : PreferenceCategory(context, attrs, defStyleAttr) {
private var title = ""
init {
layoutResource = R.layout.preference_category_custom_layout
attrs?.let {
val typedArray =
context.obtainStyledAttributes(it, R.styleable.CustomPreferenceCategory, 0, 0)
val title = typedArray.getString(R.styleable.CustomPreferenceCategory_itemTitle)
typedArray.recycle()
if (title != null) {
Log.d("haha", title)
this.title = title
}
}
}
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
val binding = PreferenceCategoryCustomLayoutBinding.bind(holder.itemView)
Log.d("haha1", title)
if (title.isNotEmpty()) {
binding.tvTitle.text = title
binding.decoration.visibility = View.GONE
binding.tvTitle.visibility = View.VISIBLE
} else {
binding.decoration.visibility = View.VISIBLE
binding.tvTitle.visibility = View.GONE
}
}
}

View File

@ -2,10 +2,12 @@ package com.kaixed.kchat.ui.widget
import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import com.kaixed.kchat.R
import com.kaixed.kchat.databinding.ViewTitleBarBinding
@ -27,6 +29,8 @@ class CustomTitleBar @JvmOverloads constructor(
val typedArray = context.obtainStyledAttributes(it, R.styleable.CustomTitleBar, 0, 0)
val titleName = typedArray.getString(R.styleable.CustomTitleBar_titleName) ?: ""
val titleIcon = typedArray.getResourceId(R.styleable.CustomTitleBar_titleIcon, -1)
val btnName = typedArray.getString(R.styleable.CustomTitleBar_btnName) ?: ""
if (titleIcon != -1) {
binding.ivSetting.visibility = View.VISIBLE
@ -41,6 +45,9 @@ class CustomTitleBar @JvmOverloads constructor(
} else {
binding.tvTitleName.visibility = View.GONE
}
binding.tvSave.visibility = if (btnName.isNotEmpty()) View.VISIBLE else View.INVISIBLE
typedArray.recycle()
}
@ -52,4 +59,15 @@ class CustomTitleBar @JvmOverloads constructor(
fun setOnSettingClickListener(listener: OnClickListener?) {
binding.ivSetting.setOnClickListener(listener)
}
fun setBtnEnable(enable: Boolean) {
val colorEnable = ContextCompat.getColor(context, R.color.white)
val color = Color.parseColor("#BFBFBF")
binding.tvSave.setTextColor(if (enable) colorEnable else color)
binding.tvSave.setBackgroundResource(if (enable) R.drawable.bac_ctb_save_enable else R.drawable.bac_ctb_save)
}
fun setOnBtnClickListener(listener: OnClickListener?) {
binding.tvSave.setOnClickListener(listener)
}
}

View File

@ -8,14 +8,11 @@ object Constants {
// mmkv
const val MMKV_USER_SESSION: String = "userSession"
const val USER_LOGIN_STATUS: String = "userLoginStatus"
const val MMKV_COMMON_DATA: String = "commonData"
const val USERNAME_KEY = "username"
const val MESSAGE_TYPE_NORMAL: Int = 1
const val MESSAGE_TYPE_FUNCTION: Int = 2
const val MESSAGE_TYPE_TITLE: Int = 2
const val NICKNAME_KEY = "nickname"
const val USER_LOGIN_STATUS: String = "userLoginStatus"
const val STATUS_BAR_HEIGHT = "status_bar_height"
const val KEYBOARD_HEIGHT = "keyboardHeight"

View File

@ -4,6 +4,7 @@ import com.kaixed.kchat.utils.Constants.KEYBOARD_DEFAULT_HEIGHT
import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT
import com.kaixed.kchat.utils.Constants.MMKV_COMMON_DATA
import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION
import com.kaixed.kchat.utils.Constants.NICKNAME_KEY
import com.kaixed.kchat.utils.Constants.STATUS_BAR_DEFAULT_HEIGHT
import com.kaixed.kchat.utils.Constants.STATUS_BAR_HEIGHT
import com.kaixed.kchat.utils.Constants.USERNAME_KEY
@ -15,12 +16,18 @@ import com.tencent.mmkv.MMKV
*/
object ConstantsUtil {
fun getKeyboardHeight(): Int = MMKV.mmkvWithID(MMKV_COMMON_DATA)
.getInt(KEYBOARD_HEIGHT, KEYBOARD_DEFAULT_HEIGHT)
private val commonDataMMKV by lazy { MMKV.mmkvWithID(MMKV_COMMON_DATA) }
private val userSessionMMKV by lazy { MMKV.mmkvWithID(MMKV_USER_SESSION) }
fun getUsername(): String = MMKV.mmkvWithID(MMKV_USER_SESSION)
.getString(USERNAME_KEY, "").toString()
fun getKeyboardHeight(): Int =
commonDataMMKV.getInt(KEYBOARD_HEIGHT, KEYBOARD_DEFAULT_HEIGHT)
fun getStatusBarHeight(): Int = MMKV.mmkvWithID(MMKV_COMMON_DATA)
.getInt(STATUS_BAR_HEIGHT, STATUS_BAR_DEFAULT_HEIGHT)
fun getUsername(): String =
userSessionMMKV.getString(USERNAME_KEY, "") ?: ""
fun getStatusBarHeight(): Int =
commonDataMMKV.getInt(STATUS_BAR_HEIGHT, STATUS_BAR_DEFAULT_HEIGHT)
fun getNickName(): String =
userSessionMMKV.getString(NICKNAME_KEY, "") ?: ""
}

View File

@ -9,8 +9,8 @@ import android.content.Context
object DensityUtil {
// 防止实例化,使用 object 即单例
fun dpToPx(mContext: Context, dp: Int): Int =
(mContext.resources.displayMetrics.density * dp).toInt()
fun dpToPx(context: Context, dp: Int): Int =
(context.resources.displayMetrics.density * dp).toInt()
fun pxToDp(mContext: Context, px: Int): Int =
(px / mContext.resources.displayMetrics.density).toInt()

View File

@ -0,0 +1,51 @@
package com.kaixed.kchat.utils
import com.kaixed.kchat.utils.Utils.isChinese
import com.kaixed.kchat.utils.Utils.isEnglish
import net.sourceforge.pinyin4j.PinyinHelper
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType
/**
* @Author: kaixed
* @Date: 2024/11/2 14:30
*/
object Pinyin4jUtil {
fun toPinyin(chineseText: String): String {
val pinyinBuilder = StringBuilder()
val format = HanyuPinyinOutputFormat()
format.toneType = HanyuPinyinToneType.WITHOUT_TONE
for (char in chineseText) {
val pinyinArray = PinyinHelper.toHanyuPinyinStringArray(char, format)
if (pinyinArray != null && pinyinArray.isNotEmpty()) {
val pinyinWithoutTone = pinyinArray[0]
pinyinBuilder.append(pinyinWithoutTone)
} else {
pinyinBuilder.append(char)
}
}
return pinyinBuilder.toString()
}
fun compare(name1: String, name2: String): Int {
val nickname1 = toPinyin(name1)
val nickname2 = toPinyin(name2)
if (nickname1 == nickname2) {
return 0
}
if (!isEnglishOrChinese(nickname1[0].toString()) && isEnglishOrChinese(nickname2[0].toString())) {
return 1
}
if (isEnglishOrChinese(nickname1[0].toString()) && !isEnglishOrChinese(nickname2[0].toString())) {
return -1
}
return if (toPinyin(nickname1) > toPinyin(nickname2)) 1 else -1
}
private fun isEnglishOrChinese(str: String): Boolean {
return isChinese(str) || isEnglish(str)
}
}

View File

@ -0,0 +1,16 @@
package com.kaixed.kchat.utils
/**
* @Author: kaixed
* @Date: 2024/11/2 13:21
*/
object Utils {
fun isChinese(str: String?): Boolean {
return str?.matches(".*[\\u4E00-\\u9FFF].*".toRegex()) ?: false
}
fun isEnglish(str: String?): Boolean {
return str != null && str.matches("^[a-zA-Z]+$".toRegex())
}
}

View File

@ -0,0 +1,31 @@
package com.kaixed.kchat.utils
import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater
import android.view.Window
import com.kaixed.kchat.databinding.DialogLoadingBinding
/**
* @Author: kaixed
* @Date: 2024/11/5 21:26
*/
object WidgetUtil {
fun showLoadingDialog(context: Context, str: String): Dialog {
val binding = DialogLoadingBinding.inflate(LayoutInflater.from(context))
val dialog = Dialog(context)
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialog.setCancelable(false)
dialog.setContentView(binding.root)
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
val params = binding.root.layoutParams
params.height = DensityUtil.dpToPx(context, 150)
params.width = DensityUtil.dpToPx(context, 150)
binding.root.layoutParams = params
binding.tvLoading.text = str
dialog.show()
return dialog
}
}

View File

@ -4,7 +4,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.kaixed.kchat.model.friend.AcceptContactRequest
import com.kaixed.kchat.model.friend.ContactRequestResponse
import com.kaixed.kchat.model.search.SearchFriends
import com.kaixed.kchat.model.response.friend.SearchFriends
import com.kaixed.kchat.repository.ContactRepo
/**

View File

@ -1,9 +1,12 @@
package com.kaixed.kchat.viewmodel
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.kaixed.kchat.model.friend.FriendItem
import com.kaixed.kchat.model.item.FriendItem
import com.kaixed.kchat.repository.ContactRepo
import com.kaixed.kchat.utils.Pinyin4jUtil
/**
* @Author: kaixed
@ -12,6 +15,44 @@ import com.kaixed.kchat.repository.ContactRepo
class FriendListViewModel : ViewModel() {
private val contactRepo = ContactRepo()
fun getFriendList(username: String): MutableLiveData<List<FriendItem>?> =
contactRepo.getContactList(username)
companion object {
private const val TAG = "FriendListViewModel"
}
private val _friendListLiveData = MutableLiveData<List<FriendItem>?>()
val friendListLiveData: LiveData<List<FriendItem>?> get() = _friendListLiveData
fun loadFriendList(username: String) {
contactRepo.getContactList(username).observeForever { friendList ->
val uiFriendList = friendList?.map { networkItem ->
mapToFriendItem(networkItem)
}?.sortedWith { f1, f2 ->
Pinyin4jUtil.compare(f1.nickname, f2.nickname)
}?.toMutableList() // 确保是可变列表,以便后续更新 firstInSection
val startTime = System.currentTimeMillis()
uiFriendList?.forEachIndexed { index, item ->
item.firstInSection =
(index == 0 || item.pinyin?.get(index) != uiFriendList[index - 1].pinyin?.get(
index - 1
))
}
val duration = System.currentTimeMillis() - startTime
Log.d(TAG, "Execution time: ${duration}ms")
_friendListLiveData.value = uiFriendList
}
}
private fun mapToFriendItem(response: com.kaixed.kchat.model.friend.FriendItem): FriendItem {
val pinyin = Pinyin4jUtil.toPinyin(response.nickname)
return FriendItem(
nickname = response.nickname,
username = response.username,
remark = response.remark,
avatarUrl = response.avatarUrl,
pinyin = pinyin
)
}
}

View File

@ -2,7 +2,7 @@ package com.kaixed.kchat.viewmodel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.kaixed.kchat.model.login.Login
import com.kaixed.kchat.model.response.login.Login
import com.kaixed.kchat.repository.UserRepo
/**
@ -12,6 +12,6 @@ import com.kaixed.kchat.repository.UserRepo
class LoginViewModel : ViewModel() {
private val userRepo: UserRepo = UserRepo()
fun login(username: String, password: String): MutableLiveData<Login?> =
userRepo.login(username, password)
fun login(username: String, password: String, loginByUsername :Boolean): MutableLiveData<Login?> =
userRepo.login(username, password, loginByUsername)
}

View File

@ -2,7 +2,7 @@ package com.kaixed.kchat.viewmodel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.kaixed.kchat.model.search.SearchFriends
import com.kaixed.kchat.model.response.friend.SearchFriends
import com.kaixed.kchat.repository.ContactRepo
/**

View File

@ -2,8 +2,8 @@ package com.kaixed.kchat.viewmodel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.kaixed.kchat.model.register.Register
import com.kaixed.kchat.model.user.UserList
import com.kaixed.kchat.model.response.register.Register
import com.kaixed.kchat.model.response.search.UserList
import com.kaixed.kchat.repository.UserRepo
/**
@ -17,12 +17,12 @@ class UserViewModel : ViewModel() {
userRepo.getUserListByNickname(username)
fun register(
username: String,
password: String,
nickname: String,
avatarUrl: String,
signature: String,
telephone: String
): MutableLiveData<Register?> =
userRepo.register(username, password, nickname, avatarUrl, signature)
userRepo.register( password, nickname, avatarUrl, signature, telephone)
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#E1E1E1" />
<corners android:radius="6dp" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#07C160" />
<corners android:radius="6dp" />
</shape>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size android:width="2dp" />
<size android:width="1dp" />
<!-- <solid android:color="#1772F6"/>-->
<solid android:color="@color/green" />
</shape>

View File

@ -9,22 +9,32 @@
android:fitsSystemWindows="true"
tools:context=".ui.activity.LoginActivity">
<ImageView
android:id="@+id/iv_close"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:src="@drawable/ic_close"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:text="手机号/用户名登录"
android:text="手机号登录"
android:textColor="@color/black"
android:textSize="24sp"
android:textSize="21sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@id/iv_close" />
<View
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_height="0.2dp"
android:layout_marginTop="50dp"
app:layout_constraintTop_toBottomOf="@id/tv_title" />
@ -34,9 +44,9 @@
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginTop="15dp"
android:text="号"
android:text="手机号"
android:textColor="@color/black"
android:textSize="19sp"
android:textSize="17sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/view" />
@ -49,6 +59,7 @@
android:background="@null"
android:hint="请填写手机号或用户名"
android:textCursorDrawable="@drawable/cursor"
android:textSize="17sp"
app:layout_constraintBottom_toTopOf="@id/view1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_username"
@ -57,7 +68,7 @@
<View
android:id="@+id/view1"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_height="0.2dp"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="15dp"
android:background="#D8D8D8"
@ -71,7 +82,7 @@
android:layout_marginTop="15dp"
android:text="密码"
android:textColor="@color/black"
android:textSize="19sp"
android:textSize="17sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/view1" />
@ -79,28 +90,38 @@
android:id="@+id/et_password"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginHorizontal="20dp"
android:layout_marginStart="35dp"
android:background="@null"
android:hint="请填写密码"
android:inputType="textPassword"
android:textCursorDrawable="@drawable/cursor"
android:textSize="17sp"
app:layout_constraintBottom_toTopOf="@id/view2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_password"
app:layout_constraintStart_toStartOf="@id/et_username"
app:layout_constraintTop_toBottomOf="@id/view1" />
<View
android:id="@+id/view2"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_height="0.2dp"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="15dp"
android:background="#D8D8D8"
app:layout_constraintTop_toBottomOf="@id/tv_password" />
<TextView
android:id="@+id/tv_change_login_way"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:text="用户名登录"
android:textColor="@color/normal"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="@id/tv_username"
app:layout_constraintTop_toBottomOf="@id/view2" />
<TextView
android:id="@+id/tv_tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"

View File

@ -20,7 +20,6 @@
android:background="@color/white"
android:paddingBottom="4dp">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
@ -53,7 +52,7 @@
android:layout_height="wrap_content"
android:text="主页"
android:textColor="@color/black"
android:textSize="10sp"
android:textSize="11sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_home" />
@ -86,7 +85,7 @@
android:layout_height="wrap_content"
android:text="联系人"
android:textColor="@color/black"
android:textSize="10sp"
android:textSize="11sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_contact" />
@ -112,13 +111,23 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="8dp"
android:layout_height="8dp"
android:background="@drawable/icon_red_dot"
app:layout_constraintCircle="@id/iv_discovery"
app:layout_constraintCircleAngle="45"
app:layout_constraintCircleRadius="15dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_discovery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发现"
android:textColor="@color/black"
android:textSize="10sp"
android:textSize="11sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_discovery" />
@ -152,14 +161,11 @@
android:layout_height="wrap_content"
android:text="我的"
android:textColor="@color/black"
android:textSize="10sp"
android:textSize="11sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_mine" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -1,334 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ui.activity.ProfileActivity">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_profile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="15dp"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv_avatar"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="20dp"
android:layout_marginTop="55dp"
android:src="@drawable/ic_avatar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:roundPercent="0.3" />
<TextView
android:id="@+id/tv_nickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="糕菜菜"
android:textColor="@color/black"
android:textSize="15sp"
app:layout_constraintStart_toEndOf="@id/ifv_avatar"
app:layout_constraintTop_toTopOf="@id/ifv_avatar" />
<TextView
android:id="@+id/tv_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="kid: kaixed"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@id/ifv_avatar"
app:layout_constraintStart_toStartOf="@id/tv_nickname"
app:layout_constraintTop_toBottomOf="@id/tv_nickname" />
<ImageView
android:id="@+id/cl_profile_right_arrow"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_arrow_right"
app:layout_constraintBottom_toBottomOf="@id/ifv_avatar"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_qrcode"
app:layout_constraintBottom_toBottomOf="@id/cl_profile_right_arrow"
app:layout_constraintEnd_toStartOf="@id/cl_profile_right_arrow"
app:layout_constraintTop_toTopOf="@id/cl_profile_right_arrow" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/ll_setting_items"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/cl_profile">
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="#E5E5E5" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingStart="15dp">
<ImageView
android:id="@+id/iv_service"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerVertical="true"
android:src="@drawable/mine_icon_service" />
<TextView
android:id="@+id/tv_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:layout_toEndOf="@id/iv_service"
android:text="服务"
android:textColor="@color/black"
android:textSize="16sp" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_arrow_right" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="#E5E5E5" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingStart="15dp"
tools:ignore="RtlSymmetry">
<ImageView
android:id="@+id/iv_collection"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerVertical="true"
android:src="@drawable/mine_icon_collection" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:layout_toEndOf="@id/iv_collection"
android:text="收藏"
android:textColor="@color/black"
android:textSize="16sp" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_arrow_right" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="55dp"
android:background="#E5E5E5" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingStart="15dp"
tools:ignore="RtlSymmetry">
<ImageView
android:id="@+id/iv_friend_circle"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerVertical="true"
android:src="@drawable/mine_icon_friend_circle" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:layout_toEndOf="@id/iv_friend_circle"
android:text="朋友圈"
android:textColor="@color/black"
android:textSize="16sp" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_arrow_right" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="55dp"
android:background="#E5E5E5" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingStart="15dp"
tools:ignore="RtlSymmetry">
<ImageView
android:id="@+id/iv_wallet"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerVertical="true"
android:src="@drawable/mine_icon_wallet" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:layout_toEndOf="@id/iv_wallet"
android:text="卡包"
android:textColor="@color/black"
android:textSize="16sp" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_arrow_right" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="55dp"
android:background="#E5E5E5" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingStart="15dp"
tools:ignore="RtlSymmetry">
<ImageView
android:id="@+id/iv_emoji"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerVertical="true"
android:src="@drawable/mine_icon_emoji" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:layout_toEndOf="@id/iv_emoji"
android:text="表情"
android:textColor="@color/black"
android:textSize="16sp" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_arrow_right" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="#E5E5E5" />
<RelativeLayout
android:id="@+id/rl_setting"
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingStart="15dp"
tools:ignore="RtlSymmetry">
<ImageView
android:id="@+id/iv_setting"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerVertical="true"
android:src="@drawable/mine_icon_setting" />
<TextView
android:id="@+id/tv_setting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:layout_toEndOf="@id/iv_setting"
android:text="设置"
android:textColor="@color/black"
android:textSize="16sp" />
<View
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:layout_toEndOf="@id/tv_setting"
android:background="@drawable/icon_red_dot" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_arrow_right" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#E5E5E5" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -28,8 +28,8 @@
android:id="@+id/ci_avatar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:iconRound="5dp"
app:iconSize="30dp"
app:iconRound="4dp"
app:iconSize="45dp"
app:itemIcon="@drawable/ic_avatar"
app:itemName="头像" />
@ -58,7 +58,7 @@
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:iconSize="10dp"
app:iconSize="21dp"
app:isTopDividerVisible="true"
app:itemIcon="@drawable/icon_qrcode"
app:itemName="二维码名片" />

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".ui.activity.RenameActivity">
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:id="@+id/ctb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:btnName="保存" />
<EditText
android:id="@+id/et_nickname"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginHorizontal="15dp"
android:layout_marginTop="15dp"
android:background="@null"
android:inputType="text"
android:maxWidth="200dp"
android:maxLines="1"
android:paddingHorizontal="8dp"
android:textColor="@color/black"
android:textCursorDrawable="@drawable/cursor" />
<View
android:layout_width="match_parent"
android:layout_height="0.7dp"
android:layout_marginHorizontal="15dp"
android:background="@color/green" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginTop="10dp"
android:text="好的名字可以让你的好友更容易记住你。"
android:textColor="#A5A5A5"
android:textSize="12sp" />
</LinearLayout>

View File

@ -14,7 +14,7 @@
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_margin="15dp"
app:cardCornerRadius="8dp"
app:cardCornerRadius="7dp"
app:cardElevation="0dp"
app:cardMaxElevation="0dp"
app:layout_constraintEnd_toStartOf="@id/tv_cancel"
@ -64,31 +64,39 @@
app:layout_constraintStart_toEndOf="@id/cv_search"
app:layout_constraintTop_toTopOf="@id/cv_search" />
<View
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="0.2dp"
android:layout_marginTop="15dp"
android:background="@color/gray"
app:layout_constraintTop_toBottomOf="@id/cv_search" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_search_user"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:background="@color/white"
android:orientation="horizontal"
android:paddingHorizontal="15dp"
android:paddingVertical="10dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/cv_search">
app:layout_constraintTop_toBottomOf="@id/view">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="#2BA245"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:roundPercent="0.1" />
<ImageView
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/search_add_friends"
app:layout_constraintBottom_toBottomOf="@id/ifv"
app:layout_constraintEnd_toEndOf="@id/ifv"
@ -100,16 +108,28 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="15dp"
android:layout_marginStart="12dp"
android:text="搜索:"
android:textColor="@color/black"
android:textSize="18sp"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/ifv"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/tv_nothing"
android:layout_width="match_parent"
android:layout_height="120dp"
android:background="@color/white"
android:gravity="center"
android:text="该用户不存在"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/view" />
<TextView
android:id="@+id/tv_title"

View File

@ -1,29 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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="wrap_content"
android:layout_height="wrap_content"
android:background="@color/white"
android:gravity="center"
android:orientation="vertical"
android:paddingHorizontal="25dp"
android:paddingVertical="25dp">
android:paddingVertical="25dp"
app:cardCornerRadius="5dp"
app:cardElevation="0dp">
<com.airbnb.lottie.LottieAnimationView
android:layout_width="120dp"
android:layout_height="120dp"
android:scaleType="centerCrop"
app:lottie_autoPlay="true"
app:lottie_fileName="loading.json"
app:lottie_loop="true" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#353434"
android:orientation="vertical">
<TextView
android:id="@+id/tv_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:maxWidth="100dp"
android:text="正在查找联系人..."
android:textColor="@color/black"
android:textSize="17sp" />
</LinearLayout>
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/lottie"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_marginTop="15dp"
android:scaleType="centerCrop"
app:layout_constraintBottom_toTopOf="@id/tv_loading"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:lottie_autoPlay="true"
app:lottie_fileName="loading1.json"
app:lottie_loop="true" />
<TextView
android:id="@+id/tv_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="15dp"
android:layout_marginBottom="@dimen/margin"
android:gravity="center_horizontal"
android:maxWidth="100dp"
android:text="正在查找联系人..."
android:textColor="@color/white"
android:textSize="17sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/lottie" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

View File

@ -15,7 +15,6 @@
android:id="@+id/cl_profile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="15dp"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.utils.widget.ImageFilterView
@ -33,10 +32,11 @@
android:id="@+id/tv_nickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginStart="15dp"
android:text="糕菜菜"
android:textColor="@color/black"
android:textSize="15sp"
android:textSize="19sp"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="@id/ifv_avatar"
app:layout_constraintTop_toTopOf="@id/ifv_avatar" />
@ -70,6 +70,14 @@
app:layout_constraintEnd_toStartOf="@id/cl_profile_right_arrow"
app:layout_constraintTop_toTopOf="@id/cl_profile_right_arrow" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:layout_marginTop="15dp"
android:background="#D5D5D5"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/ifv_avatar" />
</androidx.constraintlayout.widget.ConstraintLayout>
@ -82,18 +90,33 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/cl_profile">
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:layout_marginTop="8dp"
android:background="#D5D5D5" />
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:itemLeftIcon="@drawable/ic_mine_pay"
app:itemName="服务" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:background="#D5D5D5" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:layout_marginTop="8dp"
android:background="#D5D5D5" />
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:itemLeftIcon="@drawable/ic_mine_favorites"
app:itemName="收藏" />
@ -121,14 +144,29 @@
app:itemLeftIcon="@drawable/ic_mine_emoji"
app:itemName="表情" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:background="#D5D5D5" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:layout_marginTop="8dp"
android:background="#D5D5D5" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/ci_setting"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:isTopDividerVisible="false"
app:itemLeftIcon="@drawable/ic_mine_setting"
app:itemName="设置" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:background="#D5D5D5" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -3,8 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:paddingStart="15dp">
android:background="@color/white">
<View
android:id="@+id/view_top"
@ -14,6 +13,18 @@
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/tv_nickname"
app:layout_constraintTop_toBottomOf="@id/tv_letter" />
<TextView
android:id="@+id/tv_letter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#EDEDED"
android:paddingVertical="8dp"
android:paddingStart="15dp"
android:text="A"
android:textSize="12sp"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.utils.widget.ImageFilterView
@ -21,10 +32,11 @@
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginVertical="10dp"
android:layout_marginStart="15dp"
android:src="@drawable/ic_avatar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@id/view_top"
app:roundPercent="0.2" />
<TextView
@ -35,9 +47,9 @@
android:text="kaixed"
android:textColor="@color/black"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toBottomOf="@id/ifv_avatar"
app:layout_constraintStart_toEndOf="@+id/ifv_avatar"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="@id/ifv_avatar" />
<View
android:id="@+id/view_bottom"

View File

@ -9,7 +9,7 @@
<View
android:id="@+id/decoration_top"
android:layout_width="0dp"
android:layout_height="0.5dp"
android:layout_height="0.2dp"
android:background="#E5E5E5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/tv_item_name"
@ -56,10 +56,10 @@
android:id="@+id/tv_item_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_marginEnd="8dp"
android:text="aaaa"
android:textColor="#797979"
android:textSize="17sp"
android:textSize="15sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/iv_arrow_right"
@ -91,7 +91,7 @@
<View
android:id="@+id/decoration_bottom"
android:layout_width="0dp"
android:layout_height="0.5dp"
android:layout_height="0.2dp"
android:background="#E5E5E5"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"

View File

@ -32,9 +32,24 @@
android:layout_height="22dp"
android:layout_marginEnd="15dp"
android:src="@drawable/ic_setting"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginEnd="15dp" />
<TextView
android:id="@+id/tv_save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="15dp"
android:background="@drawable/bac_ctb_save"
android:paddingHorizontal="13dp"
android:paddingVertical="6dp"
android:text="保存"
android:textColor="#BFBFBF"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -26,6 +26,7 @@
<declare-styleable name="CustomTitleBar">
<attr name="titleName" format="string" />
<attr name="titleIcon" format="reference" />
<attr name="btnName" format="string" />
</declare-styleable>
<declare-styleable name="ShSwitchView">

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="margin">15dp</dimen>
<dimen name="divider_height">0.2dp</dimen>
</resources>

View File

@ -12,9 +12,10 @@ kotlinxSerializationJson = "1.6.3"
lottie = "6.5.2"
material = "1.12.0"
activity = "1.9.3"
constraintlayout = "2.1.4"
constraintlayout = "2.2.0"
mmkv = "1.3.9"
okhttp = "4.12.0"
pinyin4j = "2.5.1"
preference = "1.2.1"
shapedrawable = "3.2"
shapeview = "9.2"
@ -29,6 +30,7 @@ objectbox = "4.0.2"
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesCore" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
pinyin4j = { module = "com.belerweb:pinyin4j", version.ref = "pinyin4j" }
shapedrawable = { module = "com.github.getActivity:ShapeDrawable", version.ref = "shapedrawable" }
shapeview = { module = "com.github.getActivity:ShapeView", version.ref = "shapeview" }
therouter-ksp = { module = "cn.therouter:apt", version.ref = "therouter" }