feat(好友模块): 添加好友申请功能

1.添加好友申请功能
2.添加接受好友申请功能
3.使用kotlinx-serialization-json代替部分gson
This commit is contained in:
糕小菜 2024-10-22 22:20:18 +08:00
parent 0a2a7ec3cd
commit fea67b33ce
41 changed files with 1125 additions and 114 deletions

View File

@ -0,0 +1,123 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@ -8,6 +8,6 @@
<option name="languageVersion" value="1.9" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.0" />
<option name="version" value="1.9.23" />
</component>
</project>

View File

@ -1,6 +1,9 @@
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsKotlinAndroid)
id("com.google.devtools.ksp")
// id("therouter")
id("org.jetbrains.kotlin.plugin.serialization")
}
android {
@ -51,29 +54,20 @@ dependencies {
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
implementation(libs.window)
implementation(libs.emoji2)
implementation(libs.preference)
implementation(libs.okhttp)
implementation(libs.mmkv)
implementation(libs.gson)
implementation(libs.window)
implementation(libs.emoji2)
implementation(libs.preference)
//noinspection UseTomlInstead
debugImplementation("io.objectbox:objectbox-android-objectbrowser:4.0.2")
//noinspection UseTomlInstead
releaseImplementation("io.objectbox:objectbox-android:4.0.2")
debugImplementation(libs.objectbox.android.objectbrowser)
releaseImplementation(libs.objectbox.android)
implementation(libs.glide)
// ksp ("cn.therouter:apt:1.2.2")
// implementation ("cn.therouter:router:1.2.2")
implementation("com.airbnb.android:lottie:6.5.2")
implementation(libs.lottie)
implementation(libs.kotlinx.serialization.json)
// implementation(libs.therouter)
// ksp(libs.therouter.ksp)
}
apply(plugin = "io.objectbox")

View File

@ -21,8 +21,25 @@
#-renamesourcefileattribute SourceFile
# TheRouter
# need add for Fragment page route
# -keep public class * extends android.app.Fragment
# -keep public class * extends androidx.fragment.app.Fragment
# -keep public class * extends android.support.v4.app.Fragment
-keep class androidx.annotation.Keep
-keep @androidx.annotation.Keep class * {*;}
-keepclassmembers class * {
@androidx.annotation.Keep *;
}
-keepclasseswithmembers class * {
@androidx.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
@androidx.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
@androidx.annotation.Keep <init>(...);
}
-keepclasseswithmembers class * {
@com.therouter.router.Autowired <fields>;
}

View File

@ -20,6 +20,15 @@
android:theme="@style/Theme.KChatAndroid"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".view.activity.ApproveDetailActivity"
android:exported="false" />
<activity
android:name=".view.activity.ApproveContactRequestActivity"
android:exported="false" />
<activity
android:name=".view.activity.MessageActivity"
android:exported="false" />
<activity
android:name=".view.activity.ApplyAddFriendActivity"
android:exported="false" />

View File

@ -1 +0,0 @@
[]

View File

@ -34,7 +34,6 @@ public class KchatApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
// TheRouter.setDebug(true);
super.attachBaseContext(base);
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,56 @@
package com.kaixed.kchat.model.friend
import android.os.Parcel
import android.os.Parcelable
import kotlinx.serialization.Serializable
/**
* @Author: kaixed
* @Date: 2024/10/18 15:11
* @Description: TODO
*/
@Serializable
data class FriendRequestItem(
val nickname: String,
val username: String,
val message: String,
val status: String,
val signature: String,
val requestTime: String,
val avatarUrl: String,
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readString() ?: "",
parcel.readString() ?: "",
parcel.readString() ?: "",
parcel.readString() ?: "",
parcel.readString() ?: "",
parcel.readString() ?: ""
) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(nickname)
parcel.writeString(username)
parcel.writeString(message)
parcel.writeString(status)
parcel.writeString(signature)
parcel.writeString(requestTime)
parcel.writeString(avatarUrl)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<FriendRequestItem> {
override fun createFromParcel(parcel: Parcel): FriendRequestItem {
return FriendRequestItem(parcel)
}
override fun newArray(size: Int): Array<FriendRequestItem?> {
return arrayOfNulls(size)
}
}
}

View File

@ -1,10 +1,14 @@
package com.kaixed.kchat.model.friend
import kotlinx.serialization.Serializable
/**
* @Author: kaixed
* @Date: 2024/10/17 22:00
* @Description: TODO
*/
@Serializable
data class FriendResponse(
val code: String,
val message: String,

View File

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

View File

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

View File

@ -1,10 +1,14 @@
package com.kaixed.kchat.model.user
import kotlinx.serialization.Serializable
/**
* @Author: kaixed
* @Date: 2024/10/16 13:33
* @Description: TODO
*/
@Serializable
data class User(
var nickname: String,
val avatarUrl: String,

View File

@ -1,10 +1,14 @@
package com.kaixed.kchat.model.user
import kotlinx.serialization.Serializable
/**
* @Author: kaixed
* @Date: 2024/10/16 13:34
* @Description: TODO
*/
@Serializable
data class UserList(
val code: String,
val msg: String,

View File

@ -21,4 +21,6 @@ object NetworkInterface {
const val ADD_FRIEND = "/friend/request"
const val FRIEND_LIST = "/friend/list"
const val FRIEND_REQUEST_LIST = "/friend/request/list"
const val ACCEPT_CONTACT_REQUEST = "/friend/accept"
}

View File

@ -0,0 +1,89 @@
package com.kaixed.kchat.repository
import androidx.lifecycle.MutableLiveData
import com.google.gson.Gson
import com.kaixed.kchat.model.friend.AcceptContactRequest
import com.kaixed.kchat.model.friend.ContactRequestResponse
import com.kaixed.kchat.network.NetworkInterface.ACCEPT_CONTACT_REQUEST
import com.kaixed.kchat.network.NetworkInterface.FRIEND_REQUEST_LIST
import com.kaixed.kchat.network.NetworkInterface.SERVER_URL
import com.kaixed.kchat.network.NetworkRequest
import kotlinx.serialization.json.Json
import okhttp3.Call
import okhttp3.Callback
import okhttp3.FormBody
import okhttp3.Response
import java.io.IOException
/**
* @Author: kaixed
* @Date: 2024/10/20 17:32
* @Description: 联系人相关模块网络请求Repo类
*/
class ContactRepo {
fun getContactRequestList(
username: String,
): MutableLiveData<ContactRequestResponse?> {
val applyFriendMutableLiveData = MutableLiveData<ContactRequestResponse?>()
val requestBody = FormBody.Builder()
.add("userId", username)
.build()
NetworkRequest().postAsync(
SERVER_URL + FRIEND_REQUEST_LIST,
requestBody,
object : Callback {
override fun onFailure(call: Call, e: IOException) {
applyFriendMutableLiveData.postValue(null)
}
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful && response.body != null) {
val responseBody = response.body!!.string()
val contactResponse =
Json.decodeFromString<ContactRequestResponse>(responseBody)
applyFriendMutableLiveData.postValue(contactResponse)
}
}
}
)
return applyFriendMutableLiveData
}
fun acceptContactRequest(
username: String,
contactId: String,
): MutableLiveData<AcceptContactRequest?> {
val acceptFriendMutableLiveData = MutableLiveData<AcceptContactRequest?>()
val requestBody = FormBody.Builder()
.add("requestId", contactId)
.add("receiverId", username)
.build()
NetworkRequest().postAsync(
SERVER_URL + ACCEPT_CONTACT_REQUEST,
requestBody,
object : Callback {
override fun onFailure(call: Call, e: IOException) {
acceptFriendMutableLiveData.postValue(null)
}
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful && response.body != null) {
val responseBody = response.body!!.string()
val contactResponse =
Json.decodeFromString<AcceptContactRequest>(responseBody)
acceptFriendMutableLiveData.postValue(contactResponse)
}
}
}
)
return acceptFriendMutableLiveData
}
}

View File

@ -4,7 +4,6 @@ import androidx.lifecycle.MutableLiveData
import com.google.gson.Gson
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.network.NetworkInterface.FRIEND_LIST
import com.kaixed.kchat.network.NetworkInterface.SERVER_URL
import com.kaixed.kchat.network.NetworkRequest
@ -24,7 +23,7 @@ class FriendListRepo {
val applyFriendMutableLiveData = MutableLiveData<List<FriendItem>?>()
val requestBody = FormBody.Builder()
.add("userid", username)
.add("userId", username)
.build()
NetworkRequest().postAsync(SERVER_URL + FRIEND_LIST, requestBody, object : Callback {

View File

@ -198,7 +198,7 @@ public class WebSocketService extends Service {
@Override
public void onFailure(@NonNull WebSocket webSocket, @NonNull Throwable t, @Nullable Response response) {
Log.d(TAG, "WebSocket onFailure: " + t.getMessage());
// Log.d(TAG, "WebSocket onFailure: " + t.getMessage());
establishConnection();
}
}

View File

@ -5,15 +5,19 @@ import android.os.Build
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.bumptech.glide.Glide
import com.kaixed.kchat.databinding.ActivityApplyFriendsDetailBinding
import com.kaixed.kchat.model.friend.FriendRequestItem
import com.kaixed.kchat.model.search.User
class ApplyFriendsDetailActivity : AppCompatActivity() {
class ApplyFriendsDetailActivity : BaseActivity() {
private lateinit var binding: ActivityApplyFriendsDetailBinding
private lateinit var user: User
private var request: FriendRequestItem? = null
private var user: User? = null
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onCreate(savedInstanceState: Bundle?) {
@ -22,7 +26,8 @@ class ApplyFriendsDetailActivity : AppCompatActivity() {
binding = ActivityApplyFriendsDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
user = (intent.getParcelableExtra("user") as? User)!!
user = intent.getParcelableExtra("user") as? User
request = intent.getParcelableExtra("request") as? FriendRequestItem
setContent()
@ -30,19 +35,27 @@ class ApplyFriendsDetailActivity : AppCompatActivity() {
}
private fun setContent() {
binding.tvContactName.text = user.nickname
binding.tvContactSignature.text = user.signature
binding.tvSignatureContent.text = user.signature
Glide.with(this).load(user.avatarUrl).into(binding.ifvAvatar)
// 使用 Elvis 操作符 ?:,如果 request 不为空则使用 request否则使用 user
val nickname = request?.nickname ?: user?.nickname
val signature = request?.signature ?: user?.signature
val avatarUrl = request?.avatarUrl ?: user?.avatarUrl
// 设置到 UI 上
binding.tvContactName.text = nickname
binding.tvContactSignature.text = signature
binding.tvSignatureContent.text = signature
Glide.with(this).load(avatarUrl).into(binding.ifvAvatar)
}
private fun setOnClick() {
binding.rlSendMessage.setOnClickListener {
val contactId = request?.username ?: user?.username
contactId?.let {
val intent = Intent(this, ApplyAddFriendActivity::class.java)
intent.putExtra("contactId", user.username)
intent.putExtra("contactId", it)
startActivity(intent)
}
}
binding.ctl.setOnSettingClickListener {
val intent = Intent(this, DataSettingActivity::class.java)

View File

@ -0,0 +1,68 @@
package com.kaixed.kchat.view.activity
import android.annotation.SuppressLint
import android.app.TaskStackBuilder
import android.content.Intent
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import com.kaixed.kchat.database.UserManager
import com.kaixed.kchat.databinding.ActivityApproveContactRequestBinding
import com.kaixed.kchat.viewmodel.ContactViewModel
class ApproveContactRequestActivity : BaseActivity() {
private lateinit var binding: ActivityApproveContactRequestBinding
private val contactViewModel: ContactViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityApproveContactRequestBinding.inflate(layoutInflater)
setContentView(binding.root)
setContent()
setOnClickListener()
}
@SuppressLint("SetTextI18n")
private fun setContent() {
val nickname = intent.getStringExtra("nickname")
val message = intent.getStringExtra("message")
binding.etRemark.setText(nickname)
binding.tvMessage.text = "$message"
}
private fun setOnClickListener() {
binding.tvFinish.setOnClickListener {
val contactId = intent.getStringExtra("contactId")
contactViewModel.acceptContactRequest(UserManager.getInstance().username, contactId!!)
.observe(this) { value ->
value?.let {
runOnUiThread {
toast(it.data)
}
val stackBuilder: TaskStackBuilder = TaskStackBuilder.create(this)
stackBuilder.addNextIntentWithParentStack(
Intent(
this,
MainActivity::class.java
)
)
stackBuilder.addNextIntent(
Intent(
this,
ChatActivity::class.java
).putExtra("friendId", contactId)
)
stackBuilder.startActivities()
}
}
}
}
}

View File

@ -0,0 +1,54 @@
package com.kaixed.kchat.view.activity
import android.content.Intent
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.bumptech.glide.Glide
import com.kaixed.kchat.R
import com.kaixed.kchat.databinding.ActivityApproveDetailBinding
import com.kaixed.kchat.model.friend.FriendRequestItem
class ApproveDetailActivity : AppCompatActivity() {
private lateinit var binding: ActivityApproveDetailBinding
private var request: FriendRequestItem? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityApproveDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
request = intent.getParcelableExtra("request") as? FriendRequestItem
setContent()
setOnClick()
}
private fun setContent() {
val nickname = request?.nickname
val signature = request?.signature
val avatarUrl = request?.avatarUrl
binding.tvContactName.text = nickname
binding.tvContactSignature.text = signature
binding.tvSignatureContent.text = signature
Glide.with(this).load(avatarUrl).into(binding.ifvAvatar)
}
private fun setOnClick() {
binding.ctl.setOnSettingClickListener {
val intent = Intent(this, DataSettingActivity::class.java)
startActivity(intent)
}
binding.rlGotoVerify.setOnClickListener {
val intent = Intent(this, ApproveContactRequestActivity::class.java)
intent.putExtra("nickname", request?.nickname)
intent.putExtra("message", request?.message)
startActivity(intent)
}
}
}

View File

@ -0,0 +1,21 @@
package com.kaixed.kchat.view.activity
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
/**
* @Author: kaixed
* @Date: 2024/10/21 22:32
* @Description: TODO
*/
open class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// TheRouter.inject(this)
}
fun toast(msg: String) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}
}

View File

@ -1,8 +1,5 @@
package com.kaixed.kchat.view.activity;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
@ -20,18 +17,14 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;

View File

@ -0,0 +1,47 @@
package com.kaixed.kchat.view.activity
import android.app.ActivityManager
import android.os.Bundle
import android.util.Log
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.kaixed.kchat.database.UserManager
import com.kaixed.kchat.databinding.ActivityMessageBinding
import com.kaixed.kchat.model.friend.FriendRequestItem
import com.kaixed.kchat.view.adapter.MessageListAdapter
import com.kaixed.kchat.viewmodel.ContactViewModel
class MessageActivity : AppCompatActivity() {
private lateinit var binding: ActivityMessageBinding
private var items = mutableListOf<FriendRequestItem>()
private val contactViewModel: ContactViewModel by viewModels()
private lateinit var messageListAdapter: MessageListAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityMessageBinding.inflate(layoutInflater)
setContentView(binding.root)
getItems()
messageListAdapter = MessageListAdapter(items, this)
binding.recycleFriendRequestList.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.recycleFriendRequestList.adapter = messageListAdapter
}
private fun getItems() {
contactViewModel.getContactRequestList(UserManager.getInstance().username)
.observe(this) { value ->
if (value != null) {
items.addAll(value.data.toMutableList())
messageListAdapter.notifyDataSetChanged()
}
}
}
}

View File

@ -0,0 +1,64 @@
package com.kaixed.kchat.view.adapter
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.bumptech.glide.Glide
import com.kaixed.kchat.databinding.FriendRecycleItemAddRequestBinding
import com.kaixed.kchat.model.friend.FriendRequestItem
import com.kaixed.kchat.view.activity.ApplyFriendsDetailActivity
import com.kaixed.kchat.view.activity.ApproveContactRequestActivity
import com.kaixed.kchat.view.activity.ApproveDetailActivity
/**
* @Author: kaixed
* @Date: 2024/10/18 15:08
* @Description: 好友通知列表的适配器类
*/
class MessageListAdapter(private val items: MutableList<FriendRequestItem>, val context: Context) :
RecyclerView.Adapter<MessageListAdapter.MyViewHolder>() {
class MyViewHolder(val binding: FriendRecycleItemAddRequestBinding) : ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding = FriendRecycleItemAddRequestBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return MyViewHolder(binding)
}
override fun getItemCount(): Int = items.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.binding.root.setOnClickListener {
val intent = Intent(context, ApproveDetailActivity::class.java)
intent.putExtra("request", items[position])
context.startActivity(intent)
}
holder.binding.tvAdd.setOnClickListener {
val intent = Intent(context, ApproveContactRequestActivity::class.java)
intent.putExtra("nickname", items[position].nickname)
intent.putExtra("contactId", items[position].username)
intent.putExtra("message", items[position].message)
context.startActivity(intent)
}
holder.binding.top.visibility = if (position == 0) View.GONE else View.VISIBLE
holder.binding.bottom.visibility =
if (position == items.size - 1) View.GONE else View.VISIBLE
Glide.with(context).load(items[position].avatarUrl).into(holder.binding.ifvAvatar)
holder.binding.tvMessage.text = items[position].message
holder.binding.tvNickname.text = items[position].nickname
}
}

View File

@ -1,64 +0,0 @@
package com.kaixed.kchat.view.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.kaixed.kchat.R;
import com.kaixed.kchat.model.user.User;
import java.util.List;
/**
* @Author: kaixed
* @Date: 2024/6/1 20:28
* @Description: TODO
*/
public class UserListAdapter extends RecyclerView.Adapter<UserListAdapter.MyViewHolder> {
private List<User> userList;
public UserListAdapter(List<User> userList) {
this.userList = userList;
}
public void setData(List<User> userList) {
this.userList = userList;
notifyDataSetChanged();
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.user_recycle_item, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
User user = userList.get(position);
holder.bindData(user);
}
@Override
public int getItemCount() {
return userList.isEmpty() ? 0 : userList.size();
}
public static class MyViewHolder extends RecyclerView.ViewHolder {
private final TextView mTvNickname;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
mTvNickname = itemView.findViewById(R.id.tv_nickname);
}
public void bindData(User user) {
mTvNickname.setText(user.getNickname());
}
}
}

View File

@ -0,0 +1,34 @@
package com.kaixed.kchat.viewmodel
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.friend.FriendRequestItem
import com.kaixed.kchat.model.friend.FriendResponse
import com.kaixed.kchat.model.search.SearchFriends
import com.kaixed.kchat.repository.ContactRepo
import com.kaixed.kchat.repository.SearchFriendsRepository
/**
* @Author: kaixed
* @Date: 2024/10/20 17:40
* @Description: TODO
*/
class ContactViewModel : ViewModel() {
private val contactRepo = ContactRepo()
private val searchFriendsRepo = SearchFriendsRepository()
fun getContactRequestList(username: String): MutableLiveData<ContactRequestResponse?> =
contactRepo.getContactRequestList(username)
fun searchFriends(username: String): MutableLiveData<SearchFriends>? =
searchFriendsRepo.searchFriends(username)
fun acceptContactRequest(username: String, contactId: String): MutableLiveData<AcceptContactRequest?> =
contactRepo.acceptContactRequest(username, contactId)
}

View File

@ -13,7 +13,7 @@ import com.kaixed.kchat.repository.SearchFriendsRepository
class SearchFriendsViewModel : ViewModel() {
private var searchFriendsRepo: SearchFriendsRepository = SearchFriendsRepository()
fun searchFriends(username: String): MutableLiveData<SearchFriends>? {
return searchFriendsRepo.searchFriends(username)
}
fun searchFriends(username: String): MutableLiveData<SearchFriends>? =
searchFriendsRepo.searchFriends(username)
}

View File

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

View File

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

View File

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="70dp"
android:height="70dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M512,160a304.9,304.9 0,0 1,304.6 294.6l0.2,10.2 -0,200.2 43.9,88.7a32,32 0,0 1,-10.7 40.7l-3.8,2.2a32,32 0,0 1,-40.7 -10.7l-2.2,-3.8 -47.2,-95.4a32,32 0,0 1,-2.9 -9.3l-0.4,-4.9v-207.8a240.9,240.9 0,0 0,-481.5 -9.4l-0.2,9.4v207.8a32,32 0,0 1,-1.5 9.6l-1.8,4.6 -47.2,95.4a32,32 0,0 1,-59 -24.3l1.7,-4 43.8,-88.8v-200.2a304.9,304.9 0,0 1,294.6 -304.7L512,160z"
android:fillColor="#000000"/>
<path
android:pathData="M832,736a32,32 0,0 1,4.4 63.7l-4.4,0.3h-640a32,32 0,0 1,-4.4 -63.7l4.4,-0.3h640zM512,98.9a32,32 0,0 1,31.7 27.6l0.3,4.4V192a32,32 0,0 1,-63.7 4.4L480,192V130.9a32,32 0,0 1,32 -32z"
android:fillColor="#000000"/>
<path
android:pathData="M608,768a32,32 0,0 1,32 32,128 128,0 0,1 -256,0 32,32 0,1 1,64 0,64 64,0 0,0 127.7,6.1l0.3,-6.1a32,32 0,0 1,32 -32z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="72.9dp"
android:height="70dp"
android:viewportWidth="1066"
android:viewportHeight="1024">
<path
android:pathData="M537.6,224a32,32 0,0 1,4.4 63.7l-4.4,0.3h-384a32,32 0,0 1,-4.4 -63.7L153.6,224h384zM537.6,480a32,32 0,0 1,4.4 63.7l-4.4,0.3h-384a32,32 0,0 1,-4.4 -63.7l4.4,-0.3h384zM537.6,736a32,32 0,0 1,4.4 63.7l-4.4,0.3h-384a32,32 0,0 1,-4.4 -63.7l4.4,-0.3h384zM669.9,810.7a32,32 0,0 0,63.6 4.7l0.4,-4.7V290.6l119.6,119.6a32,32 0,0 0,42 2.9l3.3,-2.9a32,32 0,0 0,2.9 -42l-2.9,-3.3L724.5,190.7a32,32 0,0 0,-54.4 18.3L669.9,213.3v597.3z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".view.activity.ApproveContactRequestActivity">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.kaixed.kchat.view.widget.CustomTitleBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleName="通过朋友验证" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:layout_marginTop="15dp"
android:text="设置备注"
android:textSize="13sp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginHorizontal="15dp"
android:layout_marginTop="5dp"
android:background="#F7F7F7"
app:cardCornerRadius="8dp"
app:cardElevation="0dp"
app:cardMaxElevation="0dp">
<EditText
android:id="@+id/et_remark"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F7F7F7"
android:paddingHorizontal="12dp" />
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/tv_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:layout_marginTop="5dp"
android:text="我是万"
android:textSize="13sp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<TextView
android:id="@+id/tv_finish"
android:layout_width="150dp"
android:layout_height="48dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="40dp"
android:background="@drawable/add_contact_request_send_btn_bac"
android:gravity="center"
android:text="完成"
android:textColor="@color/white"
android:textSize="19sp" />
</LinearLayout>

View File

@ -0,0 +1,203 @@
<?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=".view.activity.ApproveDetailActivity">
<com.kaixed.kchat.view.widget.CustomTitleBar
android:id="@+id/ctl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:titleIcon="@drawable/ic_more" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv_avatar"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_marginStart="15dp"
android:layout_marginTop="15dp"
android:src="@drawable/ic_avatar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ctl"
app:round="8dp" />
<TextView
android:id="@+id/tv_contact_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:text="糕小菜菜"
android:textColor="@color/black"
android:textSize="17sp"
app:layout_constraintStart_toEndOf="@id/ifv_avatar"
app:layout_constraintTop_toTopOf="@id/ifv_avatar" />
<TextView
android:id="@+id/tv_contact_signature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="平安顺遂,喜乐无忧"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="@id/tv_contact_name"
app:layout_constraintTop_toBottomOf="@id/tv_contact_name" />
<TextView
android:id="@+id/tv_set_remark_tag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="设置备注和标签"
android:textColor="#546D99"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="@id/tv_contact_signature"
app:layout_constraintTop_toBottomOf="@id/tv_contact_signature" />
<TextView
android:id="@+id/tv_contact_permission"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:text="朋友权限"
android:textColor="#546D99"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@id/tv_set_remark_tag"
app:layout_constraintStart_toEndOf="@id/tv_set_remark_tag"
app:layout_constraintTop_toTopOf="@id/tv_set_remark_tag" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="ifv_avatar,tv_set_remark_tag" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="15dp"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/barrier">
<View
android:layout_width="match_parent"
android:layout_height="0.7dp"
android:layout_marginStart="15dp"
android:background="#E5E5E5" />
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="#E5E5E5" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_signature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:paddingVertical="15dp"
android:text="个性签名"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="15dp"
android:background="#E5E5E5"
app:layout_constraintBottom_toTopOf="@id/tv_origin"
app:layout_constraintTop_toBottomOf="@id/tv_signature" />
<TextView
android:id="@+id/tv_origin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:paddingVertical="15dp"
android:text="来源"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_signature" />
<TextView
android:id="@+id/tv_signature_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:ellipsize="end"
android:maxWidth="250dp"
android:maxLines="1"
android:text="平安顺遂,喜乐无忧。"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@id/tv_signature"
app:layout_constraintStart_toEndOf="@id/tv_signature"
app:layout_constraintTop_toTopOf="@id/tv_signature" />
<TextView
android:id="@+id/tv_origin_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxWidth="250dp"
android:maxLines="1"
android:text="来自账号搜索"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@id/tv_origin"
app:layout_constraintStart_toStartOf="@id/tv_signature_content"
app:layout_constraintTop_toTopOf="@id/tv_origin" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="#E5E5E5" />
<RelativeLayout
android:id="@+id/rl_goto_verify"
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingStart="15dp"
tools:ignore="RtlSymmetry">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="前往验证"
android:textColor="@color/normal"
android:textSize="16sp"
android:textStyle="bold" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.7dp"
android:background="#E5E5E5" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#E5E5E5" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/gray"
android:fitsSystemWindows="true"
tools:context=".view.activity.MessageActivity">
<com.kaixed.kchat.view.widget.CustomTitleBar
android:id="@+id/ctl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:titleName="新的朋友" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycle_friend_request_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:overScrollMode="never"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/ctl" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:minHeight="80dp">
<View
android:id="@+id/top"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="@color/gray"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/tv_nickname"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/bottom"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="@color/gray"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/tv_nickname" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv_avatar"
android:layout_width="50dp"
android:layout_height="50dp"
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:roundPercent="0.2" />
<TextView
android:id="@+id/tv_nickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:text="kaixed"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/tv_message"
app:layout_constraintStart_toEndOf="@id/ifv_avatar"
app:layout_constraintTop_toTopOf="@id/ifv_avatar" />
<TextView
android:id="@+id/tv_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我11"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@id/ifv_avatar"
app:layout_constraintStart_toStartOf="@id/tv_nickname"
app:layout_constraintTop_toBottomOf="@id/tv_nickname" />
<TextView
android:id="@+id/tv_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="15dp"
android:background="@drawable/bac_message_agree_request_btn"
android:paddingHorizontal="14dp"
android:paddingVertical="5dp"
android:text="添加"
android:textColor="@color/black"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -2,6 +2,9 @@
plugins {
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
id("com.google.devtools.ksp") version "1.9.23-1.0.19" apply false
// id ("cn.therouter.agp8") version "1.2.1" apply false
id ("org.jetbrains.kotlin.plugin.serialization") version "1.9.23"
}
buildscript {

View File

@ -7,18 +7,26 @@ junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
appcompat = "1.7.0"
kotlinxSerializationJson = "1.6.3"
lottie = "6.5.2"
material = "1.12.0"
activity = "1.9.0"
activity = "1.9.3"
constraintlayout = "2.1.4"
mmkv = "1.3.9"
okhttp = "4.12.0"
preference = "1.2.1"
therouter = "1.2.2"
window = "1.3.0"
kotlin = "1.9.0"
#noinspection GradleDependency
kotlin = "1.9.23"
coreKtx = "1.13.1"
objectbox = "4.0.2"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
therouter-ksp = { module = "cn.therouter:apt", version.ref = "therouter" }
objectbox-android-objectbrowser = { group = "io.objectbox", name = "objectbox-android-objectbrowser", version.ref = "objectbox" }
objectbox-android = { group = "io.objectbox", name = "objectbox-android", version.ref = "objectbox" }
emoji2 = { module = "androidx.emoji2:emoji2", version.ref = "emoji2" }
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
@ -26,12 +34,15 @@ junit = { group = "junit", name = "junit", version.ref = "junit" }
ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
lottie = { module = "com.airbnb.android:lottie", version.ref = "lottie" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
mmkv = { module = "com.tencent:mmkv", version.ref = "mmkv" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
preference = { module = "androidx.preference:preference", version.ref = "preference" }
therouter = { module = "cn.therouter:router", version.ref = "therouter" }
window = { module = "androidx.window:window", version.ref = "window" }
[plugins]