diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 267e9e7..d9f63b2 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -72,9 +72,6 @@ dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.android)
- implementation(libs.shapeview)
- implementation(libs.shapedrawable)
-
implementation(libs.pinyin4j)
implementation(libs.retrofit)
@@ -92,6 +89,11 @@ dependencies {
// 自定义spannable
implementation(libs.spannable)
+ // Navigation Component 依赖
+ implementation("androidx.navigation:navigation-fragment-ktx:2.8.4")
+ implementation("androidx.navigation:navigation-ui-ktx:2.8.4")
+
+
// implementation(libs.therouter)
// ksp(libs.therouter.ksp)
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 83b441a..7dbfa8b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -39,6 +39,12 @@
android:theme="@style/Theme.KChatAndroid"
android:usesCleartextTraffic="true"
tools:targetApi="31">
+
+
diff --git a/app/src/main/java/com/kaixed/kchat/data/LocalDatabase.kt b/app/src/main/java/com/kaixed/kchat/data/LocalDatabase.kt
index 11b6060..0bfa817 100644
--- a/app/src/main/java/com/kaixed/kchat/data/LocalDatabase.kt
+++ b/app/src/main/java/com/kaixed/kchat/data/LocalDatabase.kt
@@ -15,6 +15,10 @@ import io.objectbox.query.QueryBuilder
object LocalDatabase {
private val contactBox by lazy { getBox(Contact::class.java) }
+ fun isMyFriend(contactId: String): Boolean {
+ return getContactByUsername(contactId) != null
+ }
+
fun getContactByUsername(contactId: String): Contact? {
return contactBox.query(Contact_.username.equal(contactId)).build().findFirst()
}
diff --git a/app/src/main/java/com/kaixed/kchat/ui/activity/ApplyFriendsDetailActivity.kt b/app/src/main/java/com/kaixed/kchat/ui/activity/ApplyFriendsDetailActivity.kt
index 141532e..de521d6 100644
--- a/app/src/main/java/com/kaixed/kchat/ui/activity/ApplyFriendsDetailActivity.kt
+++ b/app/src/main/java/com/kaixed/kchat/ui/activity/ApplyFriendsDetailActivity.kt
@@ -6,8 +6,7 @@ import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi
import com.bumptech.glide.Glide
-import com.kaixed.kchat.data.model.friend.FriendRequestItem
-import com.kaixed.kchat.data.model.search.User
+import com.kaixed.kchat.data.model.search.SearchUser
import com.kaixed.kchat.databinding.ActivityApplyFriendsDetailBinding
import com.kaixed.kchat.ui.base.BaseActivity
@@ -17,9 +16,7 @@ class ApplyFriendsDetailActivity : BaseActivity(), OnItemClickListener,
enableEdgeToEdge()
firstLoadData()
-
initView()
-
setListener()
-
bindWebSocketService()
-
setPanelChange()
-
getKeyBoardVisibility()
}
@@ -302,7 +297,6 @@ class ChatActivity : BaseActivity(), OnItemClickListener,
)
}
-
binding.etInput.addTextChangedListener(
afterTextChanged = { _ ->
val isInputEmpty = binding.etInput.text.toString().isEmpty()
@@ -362,11 +356,7 @@ class ChatActivity : BaseActivity(), OnItemClickListener,
val emoji = strings!![position]
// 使用 ImageSpanUtil 插入表情符号
insertEmoji(
- context,
- editable,
- binding.etInput.textSize.toInt(),
- emoji,
- index
+ context, editable, binding.etInput.textSize.toInt(), emoji, index
)
// 设置新的光标位置
binding.etInput.setSelection(index + emoji.length)
@@ -585,7 +575,6 @@ class ChatActivity : BaseActivity(), OnItemClickListener,
when (position) {
0 -> {
selectPicture()
- // 相册
}
}
}
diff --git a/app/src/main/java/com/kaixed/kchat/ui/activity/FriendCircleActivity.kt b/app/src/main/java/com/kaixed/kchat/ui/activity/FriendCircleActivity.kt
index ba0f155..c3b6eda 100644
--- a/app/src/main/java/com/kaixed/kchat/ui/activity/FriendCircleActivity.kt
+++ b/app/src/main/java/com/kaixed/kchat/ui/activity/FriendCircleActivity.kt
@@ -22,6 +22,7 @@ import com.kaixed.kchat.utils.ConstantsUtil
import com.kaixed.kchat.utils.ConstantsUtil.getAvatarUrl
import com.kaixed.kchat.utils.ConstantsUtil.getNickName
import com.kaixed.kchat.utils.DensityUtil.dp2Px
+import com.kaixed.kchat.utils.TextUtil
import kotlin.math.max
import kotlin.math.min
import kotlin.random.Random
@@ -129,7 +130,8 @@ class FriendCircleActivity : BaseActivity() {
private fun setContent() {
binding.tvNickname.text = getNickName()
- Glide.with(this).load(getAvatarUrl()).into(binding.ifvAvatar)
+ val avatarUrl = TextUtil.extractDimensionsAndPrefix(getAvatarUrl())?.first
+ Glide.with(this).load(avatarUrl).into(binding.ifvAvatar)
}
private fun setupRecyclerView() {
diff --git a/app/src/main/java/com/kaixed/kchat/ui/activity/LoginActivity.kt b/app/src/main/java/com/kaixed/kchat/ui/activity/LoginActivity.kt
index 593e7af..b10fd71 100644
--- a/app/src/main/java/com/kaixed/kchat/ui/activity/LoginActivity.kt
+++ b/app/src/main/java/com/kaixed/kchat/ui/activity/LoginActivity.kt
@@ -6,6 +6,8 @@ import android.graphics.Rect
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
+import android.view.View
+import android.widget.EditText
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
@@ -27,19 +29,25 @@ class LoginActivity : BaseActivity() {
private var loginByUsername = false
+ private lateinit var etUsername: EditText
+
+ private lateinit var etPassword: EditText
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
- binding.etUsername.addTextChangedListener(textWatcher)
- binding.etPassword.addTextChangedListener(textWatcher)
+ setupEdittext()
+
+ etUsername.addTextChangedListener(textWatcher)
+ etPassword.addTextChangedListener(textWatcher)
initView()
setListener()
binding.tvLogin.setOnClickListener {
- val username = binding.etUsername.text.toString().trim()
- val password = binding.etPassword.text.toString().trim()
+ val username = etUsername.text.toString().trim()
+ val password = etPassword.text.toString().trim()
login(username, password)
}
@@ -47,13 +55,18 @@ class LoginActivity : BaseActivity() {
getKeyboardHeight()
}
+ private fun setupEdittext() {
+ etUsername = if (loginByUsername) binding.etUsername else binding.etTelephone
+ etPassword = binding.etPassword
+ }
+
override fun initData() {
}
private fun login(username: String, password: String) {
if (!loginByUsername) {
- binding.etUsername.text.toString().length != 11
+ etUsername.text.toString().length != 11
toast("请输入正确的手机号码")
return
}
@@ -91,8 +104,12 @@ class LoginActivity : BaseActivity() {
}
private fun setView() {
+ setupEdittext()
+
binding.tvTitle.text = if (loginByUsername) "用户名登录" else "手机号登录"
- binding.etUsername.hint = if (loginByUsername) "请填写用户名" else "请填写手机号"
+ binding.etTelephone.visibility = if (loginByUsername) View.INVISIBLE else View.VISIBLE
+ binding.etUsername.visibility = if (loginByUsername) View.VISIBLE else View.INVISIBLE
+
binding.tvUsername.text = if (loginByUsername) "用户名" else "手机号"
binding.tvChangeLoginWay.text = if (loginByUsername) "手机号登录" else "用户名登录"
binding.tvTip.text =
@@ -127,7 +144,7 @@ class LoginActivity : BaseActivity() {
override fun afterTextChanged(s: Editable) {
val isInputValid =
- binding.etUsername.text.isNotEmpty() && binding.etPassword.text.isNotEmpty()
+ etUsername.text.isNotEmpty() && etPassword.text.isNotEmpty()
binding.tvLogin.apply {
setTextColor(
diff --git a/app/src/main/java/com/kaixed/kchat/ui/activity/MainActivity.kt b/app/src/main/java/com/kaixed/kchat/ui/activity/MainActivity.kt
index 3ae42a3..9547feb 100644
--- a/app/src/main/java/com/kaixed/kchat/ui/activity/MainActivity.kt
+++ b/app/src/main/java/com/kaixed/kchat/ui/activity/MainActivity.kt
@@ -2,6 +2,8 @@ package com.kaixed.kchat.ui.activity
import android.os.Bundle
import android.view.View
+import android.widget.ImageView
+import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
@@ -10,9 +12,8 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
-import com.bumptech.glide.Glide
import com.kaixed.kchat.R
-import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore
+import com.kaixed.kchat.data.local.box.ObjectBox.getBox
import com.kaixed.kchat.data.local.entity.Contact
import com.kaixed.kchat.databinding.ActivityMainBinding
import com.kaixed.kchat.ui.base.BaseActivity
@@ -30,22 +31,25 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
-class MainActivity : BaseActivity(), View.OnClickListener {
+class MainActivity : BaseActivity() {
private val colorPrimary: Int by lazy { ContextCompat.getColor(this, R.color.green) }
private val colorBlack: Int by lazy { ContextCompat.getColor(this, R.color.black) }
- private val contactBox: Box by lazy { getBoxStore().boxFor(Contact::class.java) }
+ private val contactBox: Box by lazy { getBox(Contact::class.java) }
private val contactViewModel: ContactViewModel by viewModels()
- companion object {
- private const val KEY_HOME_FRAGMENT = 0
- private const val KEY_CONTACT_FRAGMENT = 1
- private const val KEY_DISCOVERY_FRAGMENT = 2
- private const val KEY_MINE_FRAGMENT = 3
- private const val TAG = "MainActivity"
+ private val navBinding by lazy { binding.bottomNavContainer }
+
+ private val navItems by lazy {
+ listOf(
+ NavItem(navBinding.ivHome, navBinding.tvHome),
+ NavItem(navBinding.ivContact, navBinding.tvContact),
+ NavItem(navBinding.ivDiscovery, navBinding.tvDiscovery),
+ NavItem(navBinding.ivMine, navBinding.tvMine)
+ )
}
private val fragments = listOf(
@@ -69,9 +73,6 @@ class MainActivity : BaseActivity(), View.OnClickListener {
initView()
initViewPager()
-
- updateSelection(KEY_HOME_FRAGMENT)
-
}
override fun initData() {
@@ -94,18 +95,17 @@ class MainActivity : BaseActivity(), View.OnClickListener {
}
}
+
private fun initView() {
- binding.clHome.setOnClickListener(this)
- binding.clHome.tag = KEY_HOME_FRAGMENT
+ navItems.forEachIndexed { index, navItem ->
+ navItem.container.setOnClickListener {
+ updateSelection(index)
+ }
+ }
+ }
- binding.clContact.setOnClickListener(this)
- binding.clContact.tag = KEY_CONTACT_FRAGMENT
-
- binding.clDiscovery.setOnClickListener(this)
- binding.clDiscovery.tag = KEY_DISCOVERY_FRAGMENT
-
- binding.clMine.setOnClickListener(this)
- binding.clMine.tag = KEY_MINE_FRAGMENT
+ fun updatePosition(position: Int) {
+ updateSelection(position)
}
private fun initViewPager() {
@@ -128,46 +128,17 @@ class MainActivity : BaseActivity(), View.OnClickListener {
})
}
- override fun onClick(view: View) {
- val key = view.tag as Int
- binding.viewPager.currentItem = key
- updateSelection(key)
- }
-
- private fun updateSelection(selectedKey: Int) {
- clearSelection()
- when (selectedKey) {
- KEY_HOME_FRAGMENT -> {
- binding.ivHome.isSelected = true
- binding.tvHome.setTextColor(colorPrimary)
- }
-
- KEY_CONTACT_FRAGMENT -> {
- binding.ivContact.isSelected = true
- binding.tvContact.setTextColor(colorPrimary)
- }
-
- KEY_DISCOVERY_FRAGMENT -> {
- binding.ivDiscovery.isSelected = true
- binding.tvDiscovery.setTextColor(colorPrimary)
- }
-
- KEY_MINE_FRAGMENT -> {
- binding.ivMine.isSelected = true
- binding.tvMine.setTextColor(colorPrimary)
- }
+ private fun updateSelection(position: Int) {
+ binding.viewPager.currentItem = position
+ navItems.forEachIndexed { index, navItem ->
+ navItem.imageView.isSelected = index == position
+ navItem.textView.setTextColor(if (index == position) colorPrimary else colorBlack)
}
}
- private fun clearSelection() {
- binding.ivHome.isSelected = false
- binding.ivContact.isSelected = false
- binding.ivDiscovery.isSelected = false
- binding.ivMine.isSelected = false
-
- binding.tvHome.setTextColor(colorBlack)
- binding.tvContact.setTextColor(colorBlack)
- binding.tvDiscovery.setTextColor(colorBlack)
- binding.tvMine.setTextColor(colorBlack)
- }
+ data class NavItem(
+ val imageView: ImageView,
+ val textView: TextView,
+ val container: View = imageView.parent as View
+ )
}
diff --git a/app/src/main/java/com/kaixed/kchat/ui/activity/ProfileDetailActivity.kt b/app/src/main/java/com/kaixed/kchat/ui/activity/ProfileDetailActivity.kt
index 76358f8..49c177f 100644
--- a/app/src/main/java/com/kaixed/kchat/ui/activity/ProfileDetailActivity.kt
+++ b/app/src/main/java/com/kaixed/kchat/ui/activity/ProfileDetailActivity.kt
@@ -23,6 +23,7 @@ import com.kaixed.kchat.ui.widget.LoadingDialogFragment
import com.kaixed.kchat.utils.Constants.AVATAR_URL
import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
+import com.kaixed.kchat.utils.TextUtil.extractDimensionsAndPrefix
import com.kaixed.kchat.viewmodel.UserViewModel
import com.tencent.mmkv.MMKV
import io.objectbox.Box
@@ -89,7 +90,8 @@ class ProfileDetailActivity : BaseActivity() {
userViewModel.uploadAvatarResult.observe(this) { result ->
result.onSuccess {
- Glide.with(this).downloadOnly().load(it).submit()
+ val a = extractDimensionsAndPrefix(it)
+ Glide.with(this).downloadOnly().load(a!!.first).submit()
userSessionMMKV.putString(AVATAR_URL, it)
toast("上传成功")
binding.ciAvatar.setItemIcon(uri)
@@ -98,10 +100,10 @@ class ProfileDetailActivity : BaseActivity() {
result.onFailure {
toast("上传失败")
}
+ loadingDialogFragment.dismissLoading()
}
val file = File(filePath!!)
userViewModel.uploadAvatar(username = getUsername(), file = prepareFilePart(file))
- loadingDialogFragment.dismissLoading()
}
private fun prepareFilePart(file: File): MultipartBody.Part {
@@ -174,7 +176,9 @@ class ProfileDetailActivity : BaseActivity() {
val userInfo = userInfoBox.query(UserInfo_.username.equal(username)).build().findFirst()
if (userInfo != null) {
binding.ciNickname.setItemDesc(userInfo.nickname)
- binding.ciAvatar.setItemIcon(userInfo.avatarUrl)
+ val avatarUrl =
+ extractDimensionsAndPrefix(userInfo.avatarUrl)?.first ?: userInfo.avatarUrl
+ binding.ciAvatar.setItemIcon(avatarUrl)
}
}
}
diff --git a/app/src/main/java/com/kaixed/kchat/ui/activity/RegisterActivity.kt b/app/src/main/java/com/kaixed/kchat/ui/activity/RegisterActivity.kt
index fa1f3e4..86a9a1a 100644
--- a/app/src/main/java/com/kaixed/kchat/ui/activity/RegisterActivity.kt
+++ b/app/src/main/java/com/kaixed/kchat/ui/activity/RegisterActivity.kt
@@ -2,6 +2,7 @@ package com.kaixed.kchat.ui.activity
import android.content.Intent
import android.graphics.Color
+import android.graphics.Typeface
import android.os.Bundle
import android.text.Editable
import android.text.SpannableString
@@ -13,16 +14,15 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.core.widget.addTextChangedListener
+import com.drake.spannable.replaceSpan
+import com.drake.spannable.span.HighlightSpan
import com.kaixed.kchat.R
-import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore
-import com.kaixed.kchat.data.local.entity.UserInfo
-import com.kaixed.kchat.databinding.ActivityRegisterBinding
import com.kaixed.kchat.data.model.request.RegisterRequest
+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
-import io.objectbox.Box
class RegisterActivity : BaseActivity() {
@@ -30,8 +30,6 @@ class RegisterActivity : BaseActivity() {
private val userViewModel: UserViewModel by viewModels()
- private val userInfoBox: Box by lazy { getBoxStore().boxFor(UserInfo::class.java) }
-
override fun inflateBinding(): ActivityRegisterBinding {
return ActivityRegisterBinding.inflate(layoutInflater)
}
@@ -56,7 +54,6 @@ class RegisterActivity : BaseActivity() {
binding.tvContinue.setOnClickListener {
onContinueClick()
-
}
}
@@ -122,7 +119,7 @@ class RegisterActivity : BaseActivity() {
binding.etUsername.text.toString().isNotEmpty() &&
binding.etPassword.text.toString().isNotEmpty()
- tvContinueEnable = allFieldsFilled
+ tvContinueEnable = allFieldsFilled && binding.smoothCheckBox.isChecked
if (allFieldsFilled) {
setContinueButtonState(
@@ -157,15 +154,13 @@ class RegisterActivity : BaseActivity() {
}
}
- val spannableString =
- SpannableString("我已阅读并同意《软件许可及服务协议》\n本页面收集的信息仅用于注册账户")
- spannableString.setSpan(
- clickableSpan,
- 7,
- 18,
- SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE
- )
- binding.tvTip.text = spannableString
+ binding.tvTip.text =
+ "我已阅读并同意《软件许可及服务协议》\n本页面收集的信息仅用于注册账户".replaceSpan("《软件许可及服务协议》") {
+ HighlightSpan("#576B95", Typeface.defaultFromStyle(Typeface.NORMAL)) {
+ toast()
+ }
+ }
+
binding.tvTip.highlightColor = Color.TRANSPARENT
binding.tvTip.movementMethod = LinkMovementMethod.getInstance()
}
diff --git a/app/src/main/java/com/kaixed/kchat/ui/activity/SearchFriendsActivity.kt b/app/src/main/java/com/kaixed/kchat/ui/activity/SearchFriendsActivity.kt
index 44138a2..1df2493 100644
--- a/app/src/main/java/com/kaixed/kchat/ui/activity/SearchFriendsActivity.kt
+++ b/app/src/main/java/com/kaixed/kchat/ui/activity/SearchFriendsActivity.kt
@@ -1,26 +1,22 @@
package com.kaixed.kchat.ui.activity
-import android.app.Dialog
-import android.content.Context
import android.content.Intent
import android.graphics.Color
-import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.text.SpannableString
import android.text.Spanned
import android.text.style.ForegroundColorSpan
-import android.view.LayoutInflater
import android.view.View
-import android.view.Window
import android.view.inputmethod.InputMethodManager
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.core.widget.addTextChangedListener
import com.bumptech.glide.Glide
+import com.kaixed.kchat.data.LocalDatabase
import com.kaixed.kchat.data.model.search.SearchUser
import com.kaixed.kchat.databinding.ActivitySearchFriendsBinding
-import com.kaixed.kchat.databinding.DialogLoadingBinding
import com.kaixed.kchat.ui.base.BaseActivity
+import com.kaixed.kchat.ui.widget.LoadingDialogFragment
import com.kaixed.kchat.viewmodel.ContactViewModel
class SearchFriendsActivity : BaseActivity() {
@@ -29,7 +25,7 @@ class SearchFriendsActivity : BaseActivity() {
private lateinit var userItem: SearchUser
- private lateinit var loadingDialog: Dialog
+ private lateinit var loadingDialog: LoadingDialogFragment
override fun inflateBinding(): ActivitySearchFriendsBinding {
return ActivitySearchFriendsBinding.inflate(layoutInflater)
@@ -52,26 +48,27 @@ class SearchFriendsActivity : BaseActivity() {
private fun setOnClick() {
binding.clFriends.setOnClickListener {
- val intent = Intent(this, ApplyFriendsDetailActivity::class.java)
- intent.putExtra("user", userItem)
+ Intent(this, ApplyFriendsDetailActivity::class.java).apply {
+ putExtra("user", userItem)
+ }
+
startActivity(intent)
}
}
private fun initView() {
- binding.clFriends.visibility = View.GONE
- binding.tvTitle.visibility = View.GONE
+ binding.clFriends.visibility = View.INVISIBLE
+ binding.tvTitle.visibility = View.INVISIBLE
binding.etSearch.requestFocus()
binding.etSearch.addTextChangedListener(afterTextChanged = {
it?.let {
+ binding.tvNothing.visibility = View.INVISIBLE
if (it.isEmpty()) {
- binding.clFriends.visibility = View.GONE
- binding.tvTitle.visibility = View.GONE
- binding.clSearchUser.visibility = View.GONE
+ binding.clFriends.visibility = View.INVISIBLE
+ binding.clSearchUser.visibility = View.INVISIBLE
} else {
binding.clSearchUser.visibility = View.VISIBLE
- binding.tvNothing.visibility = View.INVISIBLE
val spannableString = SpannableString("搜索:$it")
val colorSpan = ForegroundColorSpan(Color.parseColor("#2BA245"))
@@ -94,27 +91,11 @@ class SearchFriendsActivity : BaseActivity() {
}, 200)
binding.clSearchUser.setOnClickListener {
- loadingDialog = showLoadingDialog(this)
+ loadingDialog = LoadingDialogFragment.newInstance("正在搜索中...")
+ loadingDialog.showLoading(supportFragmentManager)
val username = binding.etSearch.text.toString()
- contactViewModel.searchContactResult.observe(this) { result ->
- loadingDialog.dismiss()
- result.onSuccess {
- it?.let {
- userItem = it
- if (::userItem.isInitialized) {
- setVisibility(true)
- setContent()
- }
- }
- }
-
- result.onFailure {
- setVisibility(false)
- binding.tvNothing.visibility = View.VISIBLE
- }
- }
- contactViewModel.searchContact(username)
+ searchUser(username)
}
binding.tvCancel.setOnClickListener {
@@ -122,26 +103,43 @@ class SearchFriendsActivity : BaseActivity() {
}
}
- private fun showLoadingDialog(context: Context): 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))
- dialog.show()
- return dialog
+ private fun searchUser(username: String) {
+ contactViewModel.searchContactResult.observe(this) { result ->
+ result.onSuccess {
+ it?.let {
+ if (LocalDatabase.isMyFriend(it.username)) {
+ val intent =
+ Intent(this, ContactsDetailActivity::class.java).apply {
+ putExtra("contactId", it.username)
+ }
+ startActivity(intent)
+ } else {
+ userItem = it
+ updateView(true)
+ setContent(it)
+ }
+ }
+ }
+
+ result.onFailure {
+ updateView(false)
+ }
+
+ loadingDialog.dismissLoading()
+ }
+ contactViewModel.searchContact(username)
}
- private fun setContent() {
+ private fun setContent(userItem: SearchUser) {
binding.tvNickname.text = userItem.nickname
binding.tvSignature.text = userItem.signature
Glide.with(this).load(userItem.avatarUrl).into(binding.ivAvatar)
}
- private fun setVisibility(isSearchClick: Boolean) {
- binding.clFriends.visibility = if (isSearchClick) View.VISIBLE else View.GONE
- binding.tvTitle.visibility = if (isSearchClick) View.VISIBLE else View.GONE
- binding.clSearchUser.visibility = if (isSearchClick) View.GONE else View.VISIBLE
+ private fun updateView(isSearchedUser: Boolean) {
+ binding.tvNothing.visibility = if (isSearchedUser) View.GONE else View.VISIBLE
+ binding.clFriends.visibility = if (isSearchedUser) View.VISIBLE else View.INVISIBLE
+ binding.tvTitle.visibility = if (isSearchedUser) View.VISIBLE else View.INVISIBLE
+ binding.clSearchUser.visibility = if (isSearchedUser) View.INVISIBLE else View.VISIBLE
}
}
diff --git a/app/src/main/java/com/kaixed/kchat/ui/activity/SettingActivity.kt b/app/src/main/java/com/kaixed/kchat/ui/activity/SettingActivity.kt
new file mode 100644
index 0000000..9849530
--- /dev/null
+++ b/app/src/main/java/com/kaixed/kchat/ui/activity/SettingActivity.kt
@@ -0,0 +1,22 @@
+package com.kaixed.kchat.ui.activity
+
+import android.os.Bundle
+import androidx.activity.enableEdgeToEdge
+import com.kaixed.kchat.databinding.ActivitySettingBinding
+import com.kaixed.kchat.ui.base.BaseActivity
+
+class SettingActivity : BaseActivity() {
+
+ override fun inflateBinding(): ActivitySettingBinding {
+ return ActivitySettingBinding.inflate(layoutInflater)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ }
+
+ override fun initData() {
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kaixed/kchat/ui/activity/setting/AboutActivity.kt b/app/src/main/java/com/kaixed/kchat/ui/activity/setting/AboutActivity.kt
new file mode 100644
index 0000000..33fd3f3
--- /dev/null
+++ b/app/src/main/java/com/kaixed/kchat/ui/activity/setting/AboutActivity.kt
@@ -0,0 +1,26 @@
+package com.kaixed.kchat.ui.activity.setting
+
+import android.os.Bundle
+import androidx.activity.enableEdgeToEdge
+import com.kaixed.kchat.databinding.ActivityAboutBinding
+import com.kaixed.kchat.ui.base.BaseActivity
+import com.kaixed.kchat.ui.widget.LoadingDialogFragment
+
+class AboutActivity : BaseActivity() {
+
+ override fun inflateBinding(): ActivityAboutBinding {
+ return ActivityAboutBinding.inflate(layoutInflater)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ }
+
+ override fun initData() {
+ binding.ciCheckUpdate.setOnClickListener {
+ val loadingDialog = LoadingDialogFragment.newInstance("检查中...")
+ loadingDialog.showLoading(supportFragmentManager)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kaixed/kchat/ui/adapter/ChatAdapter.kt b/app/src/main/java/com/kaixed/kchat/ui/adapter/ChatAdapter.kt
index 3791f18..218018b 100644
--- a/app/src/main/java/com/kaixed/kchat/ui/adapter/ChatAdapter.kt
+++ b/app/src/main/java/com/kaixed/kchat/ui/adapter/ChatAdapter.kt
@@ -1,21 +1,27 @@
package com.kaixed.kchat.ui.adapter
import android.content.Context
+import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.drake.spannable.replaceSpan
import com.drake.spannable.span.CenterImageSpan
import com.kaixed.kchat.R
-import com.kaixed.kchat.data.local.box.ObjectBox
+import com.kaixed.kchat.data.local.box.ObjectBox.getBox
import com.kaixed.kchat.data.local.entity.Messages
import com.kaixed.kchat.databinding.ChatRecycleItemCustomNormalBinding
import com.kaixed.kchat.databinding.ChatRecycleItemImageNormalBinding
import com.kaixed.kchat.databinding.ChatRecycleItemTipBinding
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
+import com.kaixed.kchat.utils.PopWindowUtil.showPopupWindow
+import com.kaixed.kchat.utils.TextUtil.extractDimensionsAndPrefix
import com.kaixed.kchat.utils.ViewUtil.changeTimerVisibility
-import com.kaixed.kchat.utils.ViewUtil.changeView
+import com.kaixed.kchat.utils.ViewUtil.setViewVisibility
import io.objectbox.Box
import java.util.LinkedList
@@ -78,23 +84,62 @@ class ChatAdapter(
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val singleMessage = messages[position]
-
when (holder) {
is CustomViewHolder -> {
holder.bindData(singleMessage)
- changeTimerVisibility(position, messages, holder.binding.tvTimer, singleMessage)
}
is ImageViewHolder -> {
holder.bindData(singleMessage)
- changeTimerVisibility(position, messages, holder.binding.tvTimer, singleMessage)
}
is TipViewHolder -> {
holder.bindData(singleMessage)
}
-
}
+ (holder as? HasTimer)?.let {
+ changeTimerVisibility(position, messages, it.getTimerView(), singleMessage)
+ }
+ handleLongClick(holder, position, singleMessage)
+ }
+
+ private fun handleLongClick(holder: RecyclerView.ViewHolder, position: Int, message: Messages) {
+ val isMine = message.senderId == getUsername()
+ val contentView = when (holder) {
+ is CustomViewHolder -> if (isMine) holder.binding.tvMsgContentMine else holder.binding.tvMsgContent
+ is ImageViewHolder -> if (isMine) holder.binding.imageMine else holder.binding.image
+ else -> null
+ }
+
+ contentView?.setOnLongClickListener {
+ Log.d("haha", "长按了:${message.content} position: $position")
+ val popupWindow = showPopupWindow(context, it, message.senderId == getUsername())
+ val deleteButton = popupWindow.contentView.findViewById(R.id.tv_delete)
+ deleteButton.setOnClickListener {
+ deleteMessage(position, message)
+ Log.d("haha", "长按并删除了了:${message.content} position: $position")
+ popupWindow.dismiss()
+ }
+ true
+ }
+ }
+
+ private fun deleteMessage(position: Int, message: Messages) {
+ // 从数据库删除
+ val messagesBox: Box = getBox(Messages::class.java)
+ messagesBox.remove(message.msgLocalId)
+
+ // 更新数据源并通知 RecyclerView
+ messages.removeAt(position)
+ notifyItemRemoved(position)
+
+ if (position != messages.size) { // 如果移除的是最后一个,忽略
+ notifyItemRangeChanged(position, messages.size - position);
+ }
+ }
+
+ interface HasTimer {
+ fun getTimerView(): TextView
}
class TipViewHolder(val binding: ChatRecycleItemTipBinding) :
@@ -108,38 +153,65 @@ class ChatAdapter(
}
}
-
class CustomViewHolder(val binding: ChatRecycleItemCustomNormalBinding) :
- RecyclerView.ViewHolder(binding.root) {
+ RecyclerView.ViewHolder(binding.root), HasTimer {
fun bindData(message: Messages) {
val sender = message.senderId == getUsername()
- binding.tvMessageContent.text = message.content.replaceSpan("[委屈]") {
+
+ setViewVisibility(
+ parentView = binding.root,
+ sender = sender,
+ avatarId = binding.ifvAvatar.id,
+ avatarMineId = binding.ifvAvatarMine.id,
+ contentId = binding.tvMsgContent.id,
+ contentMineId = binding.tvMsgContentMine.id
+ )
+
+ val contentView = if (sender) binding.tvMsgContentMine else binding.tvMsgContent
+
+ contentView.text = message.content.replaceSpan("[委屈]") {
CenterImageSpan(binding.root.context, R.drawable.emoji).setDrawableSize(55)
}
-
- changeView(
- parentView = binding.root,
- sender = sender,
- avatarId = binding.ifvAvatar.id,
- contentId = binding.tvMessageContent.id
- )
}
+
+ override fun getTimerView(): TextView = binding.tvTimer
}
-
class ImageViewHolder(val binding: ChatRecycleItemImageNormalBinding) :
- RecyclerView.ViewHolder(binding.root) {
+ RecyclerView.ViewHolder(binding.root), HasTimer {
fun bindData(message: Messages) {
- Glide.with(binding.root.context).load(message.content)
- .placeholder(R.drawable.bac_contacts_detail).into(binding.image)
val sender = message.senderId == getUsername()
- changeView(
+ setViewVisibility(
parentView = binding.root,
sender = sender,
avatarId = binding.ifvAvatar.id,
- contentId = binding.image.id
+ avatarMineId = binding.ifvAvatarMine.id,
+ contentId = binding.image.id,
+ contentMineId = binding.imageMine.id
)
+ val imageView = if (sender) binding.imageMine else binding.image
+ loadImage(message, imageView)
}
+
+ private fun loadImage(message: Messages, imageView: ImageView) {
+ val result = extractDimensionsAndPrefix(message.content)
+ result?.let {
+ val (url, width, height) = it
+ imageView.apply {
+ updateLayoutParams {
+ this.height = height
+ this.width = width
+ }
+ }
+ Glide.with(binding.root.context).load(url)
+ .placeholder(R.drawable.image_loading).into(imageView)
+ } ?: run {
+ Glide.with(binding.root.context).load(message.content)
+ .placeholder(R.drawable.image_loading).into(imageView)
+ }
+ }
+
+ override fun getTimerView(): TextView = binding.tvTimer
}
override fun getItemViewType(position: Int): Int {
@@ -148,7 +220,7 @@ class ChatAdapter(
}
private fun updateDb(message: Messages) {
- val messagesBox: Box = ObjectBox.getBoxStore().boxFor(Messages::class.java)
+ val messagesBox: Box = getBox(Messages::class.java)
messagesBox.put(message)
}
diff --git a/app/src/main/java/com/kaixed/kchat/ui/base/BaseFragment.kt b/app/src/main/java/com/kaixed/kchat/ui/base/BaseFragment.kt
index daf6c58..28087c5 100644
--- a/app/src/main/java/com/kaixed/kchat/ui/base/BaseFragment.kt
+++ b/app/src/main/java/com/kaixed/kchat/ui/base/BaseFragment.kt
@@ -29,6 +29,8 @@ abstract class BaseFragment : Fragment() {
abstract fun inflateBinding(inflater: LayoutInflater, container: ViewGroup?): VB
+// abstract fun isSetStatusBarPadding(): Boolean
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupStatusBarPadding(binding.root)
diff --git a/app/src/main/java/com/kaixed/kchat/ui/fragment/HomeFragment.kt b/app/src/main/java/com/kaixed/kchat/ui/fragment/HomeFragment.kt
index 7b10a46..394ec74 100644
--- a/app/src/main/java/com/kaixed/kchat/ui/fragment/HomeFragment.kt
+++ b/app/src/main/java/com/kaixed/kchat/ui/fragment/HomeFragment.kt
@@ -22,7 +22,6 @@ import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity.BIND_AUTO_CREATE
import androidx.core.view.updateLayoutParams
-import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.kaixed.kchat.R
import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore
@@ -43,7 +42,6 @@ import com.kaixed.kchat.ui.base.BaseFragment
import com.kaixed.kchat.ui.i.OnDialogFragmentClickListener
import com.kaixed.kchat.ui.i.OnItemListener
import com.kaixed.kchat.ui.widget.HomeDialogFragment
-import com.kaixed.kchat.viewmodel.HomeViewModel
import io.objectbox.Box
class HomeFragment : BaseFragment(), OnItemListener,
@@ -59,8 +57,6 @@ class HomeFragment : BaseFragment(), OnItemListener,
private var talkerId: String? = null
- private val homeViewModel: HomeViewModel by viewModels()
-
private val items: List by lazy { getHomeItems() }
private var webSocketService: WebSocketService? = null
diff --git a/app/src/main/java/com/kaixed/kchat/ui/fragment/MineFragment.kt b/app/src/main/java/com/kaixed/kchat/ui/fragment/MineFragment.kt
index a5163b1..cf72207 100644
--- a/app/src/main/java/com/kaixed/kchat/ui/fragment/MineFragment.kt
+++ b/app/src/main/java/com/kaixed/kchat/ui/fragment/MineFragment.kt
@@ -9,10 +9,12 @@ import com.bumptech.glide.Glide
import com.kaixed.kchat.R
import com.kaixed.kchat.databinding.FragmentMineBinding
import com.kaixed.kchat.ui.activity.ProfileDetailActivity
+import com.kaixed.kchat.ui.activity.SettingActivity
import com.kaixed.kchat.ui.base.BaseFragment
-import com.kaixed.kchat.ui.widget.MyBottomSheetFragment
import com.kaixed.kchat.utils.ConstantsUtil
import com.kaixed.kchat.utils.ConstantsUtil.getAvatarUrl
+import com.kaixed.kchat.utils.ConstantsUtil.getUsername
+import com.kaixed.kchat.utils.TextUtil
class MineFragment : BaseFragment() {
@@ -32,11 +34,7 @@ class MineFragment : BaseFragment() {
}
binding.ciSetting.setOnClickListener {
- val bottomSheetFragment = MyBottomSheetFragment()
- bottomSheetFragment.show(
- requireActivity().supportFragmentManager,
- bottomSheetFragment.tag
- )
+ startActivity(Intent(requireContext(), SettingActivity::class.java))
}
binding.ciSetting.setRedTipVisibility(true)
@@ -50,7 +48,10 @@ class MineFragment : BaseFragment() {
private fun updateContent() {
val nickname = ConstantsUtil.getNickName()
binding.tvNickname.text = nickname
- Glide.with(requireContext()).load(getAvatarUrl())
+ val kid = "kid: ${getUsername()}"
+ binding.tvId.text = kid
+ val avatarUrl = TextUtil.extractDimensionsAndPrefix(getAvatarUrl())?.first
+ Glide.with(requireContext()).load(avatarUrl)
.placeholder(R.drawable.ic_default_avatar)
.error(R.drawable.ic_default_avatar)
.into(binding.ifvAvatar)
diff --git a/app/src/main/java/com/kaixed/kchat/ui/fragment/SettingsFragment.kt b/app/src/main/java/com/kaixed/kchat/ui/fragment/SettingsFragment.kt
new file mode 100644
index 0000000..2db9f2c
--- /dev/null
+++ b/app/src/main/java/com/kaixed/kchat/ui/fragment/SettingsFragment.kt
@@ -0,0 +1,148 @@
+package com.kaixed.kchat.ui.fragment
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.navigation.NavOptions
+import androidx.navigation.fragment.findNavController
+import com.kaixed.kchat.R
+import com.kaixed.kchat.databinding.FragmentSettingsBinding
+import com.kaixed.kchat.ui.activity.setting.AboutActivity
+import com.kaixed.kchat.ui.base.BaseFragment
+import com.kaixed.kchat.ui.widget.MyBottomSheetFragment
+
+
+class SettingsFragment : BaseFragment(), View.OnClickListener {
+ override fun inflateBinding(
+ inflater: LayoutInflater,
+ container: ViewGroup?
+ ): FragmentSettingsBinding {
+ return FragmentSettingsBinding.inflate(inflater, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setListener()
+ }
+
+ private fun setListener() {
+ binding.itemAccountSecurity.setOnClickListener(this)
+ binding.itemTeenMode.setOnClickListener(this)
+ binding.itemCareMode.setOnClickListener(this)
+ binding.itemNewMessageNotification.setOnClickListener(this)
+ binding.itemChat.setOnClickListener(this)
+ binding.itemDevice.setOnClickListener(this)
+ binding.itemGeneral.setOnClickListener(this)
+ binding.textPrivacy.setOnClickListener(this)
+ binding.itemFriendPermission.setOnClickListener(this)
+ binding.itemPersonalInfoPermission.setOnClickListener(this)
+ binding.itemPersonalInfoCollection.setOnClickListener(this)
+ binding.itemThirdPartyInfoSharing.setOnClickListener(this)
+ binding.itemPlugin.setOnClickListener(this)
+ binding.itemAboutKchat.setOnClickListener(this)
+ binding.itemHelpFeedback.setOnClickListener(this)
+ binding.tvSwitchAccount.setOnClickListener(this)
+ binding.tvLogout.setOnClickListener(this)
+ }
+
+ override fun onClick(v: View?) {
+ when (v?.id) {
+ R.id.item_account_security -> {
+
+ val navOptions = NavOptions.Builder()
+ .setEnterAnim(R.anim.push_in_from_right) // 从右侧推入
+ .setExitAnim(R.anim.push_out_to_left) // 从左侧推出
+ .setPopEnterAnim(R.anim.push_in_from_left) // 从左侧推入
+ .setPopExitAnim(R.anim.push_out_to_right) // 从右侧推出
+ .build()
+
+ findNavController().navigate(R.id.action_settings_to_security, null, navOptions)
+ }
+
+ R.id.item_teen_mode -> {
+ // 青少年模式点击
+ Toast.makeText(context, "青少年模式点击", Toast.LENGTH_SHORT).show()
+ }
+
+ R.id.item_care_mode -> {
+ // 关怀模式点击
+ Toast.makeText(context, "关怀模式点击", Toast.LENGTH_SHORT).show()
+ }
+
+ R.id.item_new_message_notification -> {
+ // 新消息通知点击
+ Toast.makeText(context, "新消息通知点击", Toast.LENGTH_SHORT).show()
+ }
+
+ R.id.item_chat -> {
+ // 聊天点击
+ Toast.makeText(context, "聊天点击", Toast.LENGTH_SHORT).show()
+ }
+
+ R.id.item_device -> {
+ // 设备点击
+ Toast.makeText(context, "设备点击", Toast.LENGTH_SHORT).show()
+ }
+
+ R.id.item_general -> {
+ // 通用点击
+ Toast.makeText(context, "通用点击", Toast.LENGTH_SHORT).show()
+ }
+
+ R.id.text_privacy -> {
+ // 隐私点击
+ Toast.makeText(context, "隐私点击", Toast.LENGTH_SHORT).show()
+ }
+
+ R.id.item_friend_permission -> {
+ // 朋友权限点击
+ Toast.makeText(context, "朋友权限点击", Toast.LENGTH_SHORT).show()
+ }
+
+ R.id.item_personal_info_permission -> {
+ // 个人信息与权限点击
+ Toast.makeText(context, "个人信息与权限点击", Toast.LENGTH_SHORT).show()
+ }
+
+ R.id.item_personal_info_collection -> {
+ // 个人信息收集清单点击
+ Toast.makeText(context, "个人信息收集清单点击", Toast.LENGTH_SHORT).show()
+ }
+
+ R.id.item_third_party_info_sharing -> {
+ // 第三方信息共享清单点击
+ Toast.makeText(context, "第三方信息共享清单点击", Toast.LENGTH_SHORT).show()
+ }
+
+ R.id.item_plugin -> {
+ // 插件点击
+ Toast.makeText(context, "插件点击", Toast.LENGTH_SHORT).show()
+ }
+
+ R.id.item_about_kchat -> {
+ startActivity(Intent(context, AboutActivity::class.java))
+ }
+
+ R.id.item_help_feedback -> {
+ // 帮助与反馈点击
+ Toast.makeText(context, "帮助与反馈点击", Toast.LENGTH_SHORT).show()
+ }
+
+ R.id.tv_switch_account -> {
+ // 切换账号点击
+ Toast.makeText(context, "切换账号点击", Toast.LENGTH_SHORT).show()
+ }
+
+ R.id.tv_logout -> {
+ val bottomSheetFragment = MyBottomSheetFragment()
+ bottomSheetFragment.show(parentFragmentManager, bottomSheetFragment.tag)
+ }
+
+ else -> {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kaixed/kchat/ui/fragment/settings/AccountSecurityFragment.kt b/app/src/main/java/com/kaixed/kchat/ui/fragment/settings/AccountSecurityFragment.kt
new file mode 100644
index 0000000..97757a9
--- /dev/null
+++ b/app/src/main/java/com/kaixed/kchat/ui/fragment/settings/AccountSecurityFragment.kt
@@ -0,0 +1,27 @@
+package com.kaixed.kchat.ui.fragment.settings
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.navigation.fragment.findNavController
+import com.kaixed.kchat.databinding.FragmentAccountSecurityBinding
+import com.kaixed.kchat.ui.base.BaseFragment
+
+
+class AccountSecurityFragment : BaseFragment() {
+ override fun inflateBinding(
+ inflater: LayoutInflater,
+ container: ViewGroup?
+ ): FragmentAccountSecurityBinding {
+ return FragmentAccountSecurityBinding.inflate(inflater, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.ctbTitleBar.setOnBackClickListener {
+ findNavController().navigateUp()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kaixed/kchat/ui/fragment/settings/ChatFragment.kt b/app/src/main/java/com/kaixed/kchat/ui/fragment/settings/ChatFragment.kt
new file mode 100644
index 0000000..6019572
--- /dev/null
+++ b/app/src/main/java/com/kaixed/kchat/ui/fragment/settings/ChatFragment.kt
@@ -0,0 +1,60 @@
+package com.kaixed.kchat.ui.fragment.settings
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.kaixed.kchat.R
+
+// TODO: Rename parameter arguments, choose names that match
+// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
+private const val ARG_PARAM1 = "param1"
+private const val ARG_PARAM2 = "param2"
+
+/**
+ * A simple [Fragment] subclass.
+ * Use the [ChatFragment.newInstance] factory method to
+ * create an instance of this fragment.
+ */
+class ChatFragment : Fragment() {
+ // TODO: Rename and change types of parameters
+ private var param1: String? = null
+ private var param2: String? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ arguments?.let {
+ param1 = it.getString(ARG_PARAM1)
+ param2 = it.getString(ARG_PARAM2)
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ // Inflate the layout for this fragment
+ return inflater.inflate(R.layout.fragment_chat, container, false)
+ }
+
+ companion object {
+ /**
+ * Use this factory method to create a new instance of
+ * this fragment using the provided parameters.
+ *
+ * @param param1 Parameter 1.
+ * @param param2 Parameter 2.
+ * @return A new instance of fragment ChatFragment.
+ */
+ // TODO: Rename and change types and number of parameters
+ @JvmStatic
+ fun newInstance(param1: String, param2: String) =
+ ChatFragment().apply {
+ arguments = Bundle().apply {
+ putString(ARG_PARAM1, param1)
+ putString(ARG_PARAM2, param2)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kaixed/kchat/ui/fragment/settings/NewMessageFragment.kt b/app/src/main/java/com/kaixed/kchat/ui/fragment/settings/NewMessageFragment.kt
new file mode 100644
index 0000000..2b14f3d
--- /dev/null
+++ b/app/src/main/java/com/kaixed/kchat/ui/fragment/settings/NewMessageFragment.kt
@@ -0,0 +1,60 @@
+package com.kaixed.kchat.ui.fragment.settings
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.kaixed.kchat.R
+
+// TODO: Rename parameter arguments, choose names that match
+// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
+private const val ARG_PARAM1 = "param1"
+private const val ARG_PARAM2 = "param2"
+
+/**
+ * A simple [Fragment] subclass.
+ * Use the [NewMessageFragment.newInstance] factory method to
+ * create an instance of this fragment.
+ */
+class NewMessageFragment : Fragment() {
+ // TODO: Rename and change types of parameters
+ private var param1: String? = null
+ private var param2: String? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ arguments?.let {
+ param1 = it.getString(ARG_PARAM1)
+ param2 = it.getString(ARG_PARAM2)
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ // Inflate the layout for this fragment
+ return inflater.inflate(R.layout.fragment_new_message, container, false)
+ }
+
+ companion object {
+ /**
+ * Use this factory method to create a new instance of
+ * this fragment using the provided parameters.
+ *
+ * @param param1 Parameter 1.
+ * @param param2 Parameter 2.
+ * @return A new instance of fragment NewMessageFragment.
+ */
+ // TODO: Rename and change types and number of parameters
+ @JvmStatic
+ fun newInstance(param1: String, param2: String) =
+ NewMessageFragment().apply {
+ arguments = Bundle().apply {
+ putString(ARG_PARAM1, param1)
+ putString(ARG_PARAM2, param2)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kaixed/kchat/ui/widget/CustomTitleBar.kt b/app/src/main/java/com/kaixed/kchat/ui/widget/CustomTitleBar.kt
index a1b9edb..12649e9 100644
--- a/app/src/main/java/com/kaixed/kchat/ui/widget/CustomTitleBar.kt
+++ b/app/src/main/java/com/kaixed/kchat/ui/widget/CustomTitleBar.kt
@@ -67,6 +67,10 @@ class CustomTitleBar @JvmOverloads constructor(
}
}
+ fun setOnBackClickListener(listener: OnClickListener?) {
+ binding.ivBack.setOnClickListener(listener)
+ }
+
fun setOnSettingClickListener(listener: OnClickListener?) {
binding.ivSetting.setOnClickListener(listener)
}
diff --git a/app/src/main/java/com/kaixed/kchat/ui/widget/MyBottomSheetFragment.kt b/app/src/main/java/com/kaixed/kchat/ui/widget/MyBottomSheetFragment.kt
index c405bae..ca5b19e 100644
--- a/app/src/main/java/com/kaixed/kchat/ui/widget/MyBottomSheetFragment.kt
+++ b/app/src/main/java/com/kaixed/kchat/ui/widget/MyBottomSheetFragment.kt
@@ -1,9 +1,10 @@
package com.kaixed.kchat.ui.widget
+import android.app.Activity
+import android.content.Context
import android.content.Intent
import android.os.Bundle
-import android.text.util.Linkify
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
diff --git a/app/src/main/java/com/kaixed/kchat/ui/widget/ReBoundScrollView.kt b/app/src/main/java/com/kaixed/kchat/ui/widget/ReBoundScrollView.kt
new file mode 100644
index 0000000..6752a85
--- /dev/null
+++ b/app/src/main/java/com/kaixed/kchat/ui/widget/ReBoundScrollView.kt
@@ -0,0 +1,129 @@
+package com.kaixed.kchat.ui.widget
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import android.view.animation.Animation
+import android.view.animation.TranslateAnimation
+import android.widget.ScrollView
+
+class ReBoundScrollView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : ScrollView(context, attrs, defStyleAttr) {
+
+ private var mEnableTopRebound = true
+ private var mEnableBottomRebound = true
+ private var mOnReboundEndListener: OnReboundEndListener? = null
+ private var mContentView: View? = null
+ private val mRect = Rect()
+
+ init {
+ overScrollMode = View.OVER_SCROLL_NEVER
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ mContentView = getChildAt(0)
+ }
+
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ super.onLayout(changed, l, t, r, b)
+ mContentView?.let {
+ mRect.set(it.left, it.top, it.right, it.bottom)
+ }
+ }
+
+ fun setOnReboundEndListener(onReboundEndListener: OnReboundEndListener): ReBoundScrollView {
+ this.mOnReboundEndListener = onReboundEndListener
+ return this
+ }
+
+ fun setEnableTopRebound(enableTopRebound: Boolean): ReBoundScrollView {
+ this.mEnableTopRebound = enableTopRebound
+ return this
+ }
+
+ fun setEnableBottomRebound(enableBottomRebound: Boolean): ReBoundScrollView {
+ this.mEnableBottomRebound = enableBottomRebound
+ return this
+ }
+
+ private var lastY = 0
+ private var rebound = false
+ private var reboundDirection = 0 // <0: bottom rebound, >0: top rebound, 0: no rebound
+
+ override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
+ mContentView?.let {
+ when (ev.action) {
+ MotionEvent.ACTION_DOWN -> {
+ lastY = ev.y.toInt()
+ }
+
+ MotionEvent.ACTION_MOVE -> {
+ if (!isScrollToTop() && !isScrollToBottom()) {
+ lastY = ev.y.toInt()
+ return super.dispatchTouchEvent(ev)
+ }
+ val deltaY = (ev.y - lastY).toInt()
+
+ if ((!mEnableTopRebound && deltaY > 0) || (!mEnableBottomRebound && deltaY < 0)) {
+ return super.dispatchTouchEvent(ev)
+ }
+
+ val offset = (deltaY * 0.48).toInt()
+ it.layout(mRect.left, mRect.top + offset, mRect.right, mRect.bottom + offset)
+ rebound = true
+ }
+
+ MotionEvent.ACTION_UP -> {
+ if (!rebound) return super.dispatchTouchEvent(ev)
+ reboundDirection = it.top - mRect.top
+ val animation =
+ TranslateAnimation(0f, 0f, it.top.toFloat(), mRect.top.toFloat())
+ animation.duration = 300
+ animation.setAnimationListener(object : Animation.AnimationListener {
+ override fun onAnimationStart(animation: Animation?) {}
+
+ override fun onAnimationEnd(animation: Animation?) {
+ mOnReboundEndListener?.let {
+ when {
+ reboundDirection > 0 -> it.onReboundTopComplete()
+ reboundDirection < 0 -> it.onReboundBottomComplete()
+ }
+ }
+ reboundDirection = 0
+ }
+
+ override fun onAnimationRepeat(animation: Animation?) {}
+ })
+ it.startAnimation(animation)
+ it.layout(mRect.left, mRect.top, mRect.right, mRect.bottom)
+ rebound = false
+ }
+ }
+ }
+ return super.dispatchTouchEvent(ev)
+ }
+
+ override fun setFillViewport(fillViewport: Boolean) {
+ super.setFillViewport(true) // 默认填充 ScrollView 或者在 XML 布局中设置 fillViewport 属性
+ }
+
+ private fun isScrollToTop(): Boolean {
+ return scrollY == 0
+ }
+
+ private fun isScrollToBottom(): Boolean {
+ mContentView?.let {
+ return it.height <= height + scrollY
+ }
+ return false
+ }
+
+ interface OnReboundEndListener {
+ fun onReboundTopComplete()
+ fun onReboundBottomComplete()
+ }
+}
diff --git a/app/src/main/java/com/kaixed/kchat/ui/widget/SmoothCheckBox.kt b/app/src/main/java/com/kaixed/kchat/ui/widget/SmoothCheckBox.kt
new file mode 100644
index 0000000..a8e7ef6
--- /dev/null
+++ b/app/src/main/java/com/kaixed/kchat/ui/widget/SmoothCheckBox.kt
@@ -0,0 +1,342 @@
+package com.kaixed.kchat.ui.widget
+
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.Point
+import android.os.Bundle
+import android.os.Parcelable
+import android.util.AttributeSet
+import android.view.View
+import android.view.animation.LinearInterpolator
+import android.widget.Checkable
+import com.kaixed.kchat.R
+
+/**
+ * Author : andy
+ * Date : 16/1/21 11:28
+ * Email : andyxialm@gmail.com
+ * Github : github.com/andyxialm
+ * Description : A custom CheckBox with animation for Android
+ */
+class SmoothCheckBox @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : View(context, attrs, defStyleAttr), Checkable {
+
+ private val KEY_INSTANCE_STATE = "InstanceState"
+
+ private val COLOR_TICK = Color.WHITE
+ private val COLOR_UNCHECKED = Color.WHITE
+ private val COLOR_CHECKED = Color.parseColor("#07C160")
+ private val COLOR_FLOOR_UNCHECKED = Color.parseColor("#DFDFDF")
+
+ private val DEF_DRAW_SIZE = 25
+ private val DEF_ANIM_DURATION = 300
+
+ private lateinit var mPaint: Paint
+ private lateinit var mTickPaint: Paint
+ private lateinit var mFloorPaint: Paint
+ private lateinit var mTickPath: Path
+
+ private lateinit var mTickPoints: Array
+ private lateinit var mCenterPoint: Point
+
+ private var mLeftLineDistance = 0f
+ private var mRightLineDistance = 0f
+ private var mDrewDistance = 0f
+ private var mScaleVal = 1.0f
+ private var mFloorScale = 1.0f
+
+ private var mWidth = 0
+ private var mAnimDuration = DEF_ANIM_DURATION
+ private var mStrokeWidth = 0
+ private var mCheckedColor = COLOR_CHECKED
+ private var mUnCheckedColor = COLOR_UNCHECKED
+ private var mFloorColor = COLOR_FLOOR_UNCHECKED
+ private var mFloorUnCheckedColor = COLOR_FLOOR_UNCHECKED
+
+ private var mChecked = false
+ private var mTickDrawing = false
+ private var mListener: OnCheckedChangeListener? = null
+
+ init {
+ init(attrs)
+ }
+
+ private fun init(attrs: AttributeSet?) {
+ val ta = context.obtainStyledAttributes(attrs, R.styleable.SmoothCheckBox)
+ val tickColor = ta.getColor(R.styleable.SmoothCheckBox_color_tick, COLOR_TICK)
+ mAnimDuration = ta.getInt(R.styleable.SmoothCheckBox_duration, DEF_ANIM_DURATION)
+ mFloorColor =
+ ta.getColor(R.styleable.SmoothCheckBox_color_unchecked_stroke, COLOR_FLOOR_UNCHECKED)
+ mCheckedColor = ta.getColor(R.styleable.SmoothCheckBox_color_checked, COLOR_CHECKED)
+ mUnCheckedColor = ta.getColor(R.styleable.SmoothCheckBox_color_unchecked, COLOR_UNCHECKED)
+ mStrokeWidth = ta.getDimensionPixelSize(R.styleable.SmoothCheckBox_stroke_width, dpToPx(0))
+ ta.recycle()
+
+ mFloorUnCheckedColor = mFloorColor
+
+ mTickPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ style = Paint.Style.STROKE
+ strokeCap = Paint.Cap.ROUND
+ color = tickColor
+ }
+
+ mFloorPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ style = Paint.Style.FILL
+ color = mFloorColor
+ }
+
+ mPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ style = Paint.Style.FILL
+ color = mCheckedColor
+ }
+
+ mTickPath = Path()
+ mCenterPoint = Point()
+ mTickPoints = Array(3) { Point() }
+
+ setOnClickListener {
+ toggle()
+ mTickDrawing = false
+ mDrewDistance = 0f
+ if (isChecked) {
+ startCheckedAnimation()
+ } else {
+ startUnCheckedAnimation()
+ }
+ }
+ }
+
+ private fun dpToPx(dp: Int): Int = (dp * resources.displayMetrics.density).toInt()
+
+ override fun onSaveInstanceState(): Parcelable {
+ val bundle = Bundle()
+ bundle.putParcelable(KEY_INSTANCE_STATE, super.onSaveInstanceState())
+ bundle.putBoolean(KEY_INSTANCE_STATE, isChecked)
+ return bundle
+ }
+
+ override fun onRestoreInstanceState(state: Parcelable?) {
+ if (state is Bundle) {
+ val bundle = state
+ val isChecked = bundle.getBoolean(KEY_INSTANCE_STATE)
+ setChecked(isChecked)
+ super.onRestoreInstanceState(bundle.getParcelable(KEY_INSTANCE_STATE))
+ return
+ }
+ super.onRestoreInstanceState(state)
+ }
+
+ override fun isChecked(): Boolean = mChecked
+
+ override fun toggle() {
+ setChecked(!isChecked())
+ }
+
+ override fun setChecked(checked: Boolean) {
+ mChecked = checked
+ reset()
+ invalidate()
+ mListener?.onCheckedChanged(this, mChecked)
+ }
+
+ /**
+ * checked with animation
+ * @param checked checked
+ * @param animate change with animation
+ */
+ fun setChecked(checked: Boolean, animate: Boolean) {
+ if (animate) {
+ mTickDrawing = false
+ mChecked = checked
+ mDrewDistance = 0f
+ if (checked) {
+ startCheckedAnimation()
+ } else {
+ startUnCheckedAnimation()
+ }
+ mListener?.onCheckedChanged(this, mChecked)
+ } else {
+ setChecked(checked)
+ }
+ }
+
+ private fun reset() {
+ mTickDrawing = true
+ mFloorScale = 1.0f
+ mScaleVal = if (isChecked) 0f else 1.0f
+ mFloorColor = if (isChecked) mCheckedColor else mFloorUnCheckedColor
+ mDrewDistance = if (isChecked) (mLeftLineDistance + mRightLineDistance) else 0f
+ }
+
+ private fun measureSize(measureSpec: Int): Int {
+ val defSize = dpToPx(DEF_DRAW_SIZE)
+ val specSize = MeasureSpec.getSize(measureSpec)
+ val specMode = MeasureSpec.getMode(measureSpec)
+
+ return when (specMode) {
+ MeasureSpec.UNSPECIFIED, MeasureSpec.AT_MOST -> minOf(defSize, specSize)
+ MeasureSpec.EXACTLY -> specSize
+ else -> defSize
+ }
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ setMeasuredDimension(measureSize(widthMeasureSpec), measureSize(heightMeasureSpec))
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ mWidth = measuredWidth
+ mStrokeWidth = (if (mStrokeWidth == 0) measuredWidth / 10 else mStrokeWidth).coerceIn(3, measuredWidth / 5)
+ mCenterPoint.x = mWidth / 2
+ mCenterPoint.y = measuredHeight / 2
+
+ // 调整 tick 的坐标,放置在左边中间偏下位置
+ mTickPoints[0].x = (mCenterPoint.x / 2) // 水平左边中间
+ mTickPoints[0].y = (mCenterPoint.y ) // 垂直偏下
+
+ mTickPoints[1].x = mTickPoints[0].x + measuredWidth / 4 // 水平方向向右偏移
+ mTickPoints[1].y = (mCenterPoint.y + measuredHeight / 6) // 垂直偏下
+
+ mTickPoints[2].x = mTickPoints[1].x + measuredWidth / 4 // 水平方向继续向右
+ mTickPoints[2].y = (mCenterPoint.y - measuredHeight / 6) // 稍微向上偏移
+
+ // 计算线段的距离
+ mLeftLineDistance = Math.sqrt(
+ Math.pow((mTickPoints[1].x - mTickPoints[0].x).toDouble(), 2.0) +
+ Math.pow((mTickPoints[1].y - mTickPoints[0].y).toDouble(), 2.0)
+ ).toFloat()
+
+ mRightLineDistance = Math.sqrt(
+ Math.pow((mTickPoints[2].x - mTickPoints[1].x).toDouble(), 2.0) +
+ Math.pow((mTickPoints[2].y - mTickPoints[1].y).toDouble(), 2.0)
+ ).toFloat()
+
+ mTickPaint.strokeWidth = mStrokeWidth.toFloat()
+ }
+
+
+ override fun onDraw(canvas: Canvas) {
+ drawBorder(canvas)
+ drawCenter(canvas)
+ drawTick(canvas)
+ }
+
+ private fun drawCenter(canvas: Canvas) {
+ mPaint.color = mUnCheckedColor
+ val radius = (mCenterPoint.x - mStrokeWidth) * mScaleVal
+ canvas.drawCircle(mCenterPoint.x.toFloat(), mCenterPoint.y.toFloat(), radius, mPaint)
+ }
+
+ private fun drawBorder(canvas: Canvas) {
+ mFloorPaint.color = mFloorColor
+ val radius = mCenterPoint.x
+ canvas.drawCircle(
+ mCenterPoint.x.toFloat(),
+ mCenterPoint.y.toFloat(),
+ radius * mFloorScale,
+ mFloorPaint
+ )
+ }
+
+ private fun drawTick(canvas: Canvas) {
+ if (mTickDrawing && isChecked) {
+ drawTickPath(canvas)
+ }
+ }
+
+ private fun drawTickPath(canvas: Canvas) {
+ mTickPath.reset()
+ mTickPath.moveTo(mTickPoints[0].x.toFloat(), mTickPoints[0].y.toFloat())
+ mTickPath.lineTo(mTickPoints[1].x.toFloat(), mTickPoints[1].y.toFloat())
+ mTickPath.lineTo(mTickPoints[2].x.toFloat(), mTickPoints[2].y.toFloat())
+ canvas.drawPath(mTickPath, mTickPaint)
+ }
+
+ private fun startCheckedAnimation() {
+ ValueAnimator.ofFloat(1.0f, 0f).apply {
+ duration = (mAnimDuration / 3 * 2).toLong()
+ interpolator = LinearInterpolator()
+ addUpdateListener {
+ mScaleVal = it.animatedValue as Float
+ mFloorColor = getGradientColor(mUnCheckedColor, mCheckedColor, 1 - mScaleVal)
+ postInvalidate()
+ }
+ start()
+ }
+
+ ValueAnimator.ofFloat(1.0f, 0.8f, 1.0f).apply {
+ duration = mAnimDuration.toLong()
+ interpolator = LinearInterpolator()
+ addUpdateListener {
+ mFloorScale = it.animatedValue as Float
+ postInvalidate()
+ }
+ start()
+ }
+
+ drawTickDelayed()
+ }
+
+ private fun startUnCheckedAnimation() {
+ ValueAnimator.ofFloat(0f, 1.0f).apply {
+ duration = mAnimDuration.toLong()
+ interpolator = LinearInterpolator()
+ addUpdateListener {
+ mScaleVal = it.animatedValue as Float
+ mFloorColor = getGradientColor(mCheckedColor, mFloorUnCheckedColor, mScaleVal)
+ postInvalidate()
+ }
+ start()
+ }
+
+ ValueAnimator.ofFloat(1.0f, 0.8f, 1.0f).apply {
+ duration = mAnimDuration.toLong()
+ interpolator = LinearInterpolator()
+ addUpdateListener {
+ mFloorScale = it.animatedValue as Float
+ postInvalidate()
+ }
+ start()
+ }
+ }
+
+ private fun drawTickDelayed() {
+ postDelayed({
+ mTickDrawing = true
+ postInvalidate()
+ }, mAnimDuration.toLong())
+ }
+
+ private fun getGradientColor(startColor: Int, endColor: Int, percent: Float): Int {
+ val startA = Color.alpha(startColor)
+ val startR = Color.red(startColor)
+ val startG = Color.green(startColor)
+ val startB = Color.blue(startColor)
+
+ val endA = Color.alpha(endColor)
+ val endR = Color.red(endColor)
+ val endG = Color.green(endColor)
+ val endB = Color.blue(endColor)
+
+ val currentA = (startA * (1 - percent) + endA * percent).toInt()
+ val currentR = (startR * (1 - percent) + endR * percent).toInt()
+ val currentG = (startG * (1 - percent) + endG * percent).toInt()
+ val currentB = (startB * (1 - percent) + endB * percent).toInt()
+
+ return Color.argb(currentA, currentR, currentG, currentB)
+ }
+
+ fun setOnCheckedChangeListener(listener: OnCheckedChangeListener?) {
+ mListener = listener
+ }
+
+ interface OnCheckedChangeListener {
+ fun onCheckedChanged(checkBox: SmoothCheckBox, isChecked: Boolean)
+ }
+}
diff --git a/app/src/main/java/com/kaixed/kchat/utils/CenteredImageSpan.kt b/app/src/main/java/com/kaixed/kchat/utils/CenteredImageSpan.kt
deleted file mode 100644
index 7a4c09d..0000000
--- a/app/src/main/java/com/kaixed/kchat/utils/CenteredImageSpan.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.kaixed.kchat.utils
-
-import android.graphics.Canvas
-import android.graphics.Paint
-import android.graphics.drawable.Drawable
-import android.text.style.ImageSpan
-
-/**
- * @Author: kaixed
- * @Date: 2024/10/23 18:07
- */
-internal class CenteredImageSpan(drawable: Drawable?) :
- ImageSpan(drawable!!, ALIGN_BASELINE) {
- override fun draw(
- canvas: Canvas,
- text: CharSequence?,
- start: Int,
- end: Int,
- x: Float,
- top: Int,
- y: Int,
- bottom: Int,
- paint: Paint
- ) {
- val drawable = drawable
- val fm = paint.fontMetricsInt
- val transY = (y + fm.descent + y + fm.ascent) / 2 - drawable.bounds.bottom / 2
- canvas.save()
- canvas.translate(x, transY.toFloat())
- drawable.draw(canvas)
- canvas.restore()
- }
-}
diff --git a/app/src/main/java/com/kaixed/kchat/utils/DensityUtil.kt b/app/src/main/java/com/kaixed/kchat/utils/DensityUtil.kt
index 5315056..fe76e71 100644
--- a/app/src/main/java/com/kaixed/kchat/utils/DensityUtil.kt
+++ b/app/src/main/java/com/kaixed/kchat/utils/DensityUtil.kt
@@ -33,6 +33,13 @@ object DensityUtil {
appContext.resources.displayMetrics
).toInt()
+ fun dp2px(dp: Int): Int =
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ dp.toFloat(),
+ appContext.resources.displayMetrics
+ ).toInt()
+
fun pxToDp(px: Int): Int =
(px / appContext.resources.displayMetrics.density).toInt()
}
diff --git a/app/src/main/java/com/kaixed/kchat/utils/ImageSpanUtil.kt b/app/src/main/java/com/kaixed/kchat/utils/ImageSpanUtil.kt
index e8ece41..978506a 100644
--- a/app/src/main/java/com/kaixed/kchat/utils/ImageSpanUtil.kt
+++ b/app/src/main/java/com/kaixed/kchat/utils/ImageSpanUtil.kt
@@ -2,11 +2,9 @@ package com.kaixed.kchat.utils
import android.content.Context
import android.text.Editable
-import android.text.SpannableString
import android.text.Spanned
-import androidx.core.content.res.ResourcesCompat
+import com.drake.spannable.span.CenterImageSpan
import com.kaixed.kchat.R
-import java.util.regex.Pattern
/**
* @Author: kaixed
@@ -17,36 +15,6 @@ object ImageSpanUtil {
private val emojiMap: MutableMap = hashMapOf("[委屈]" to R.drawable.emoji)
- fun setEmojiSpan(context: Context, text: String, textSize: Int): SpannableString {
- val spannableString = SpannableString(text)
- val regexPattern = "\\[.*?\\]"
-
- val pattern = Pattern.compile(regexPattern)
- val matcher = pattern.matcher(text)
- while (matcher.find()) {
- // 获取匹配的起始和结束位置
- val start = matcher.start()
- val end = matcher.end()
- val emojiResId = emojiMap[matcher.group()]
- if (emojiResId != null) {
- val drawable = ResourcesCompat.getDrawable(context.resources, emojiResId, null)
- drawable?.let {
- val newSize = (textSize * 1.2).toInt()
- it.setBounds(0, 0, newSize, newSize)
- val centeredImageSpan = CenteredImageSpan(it)
- val verticalAlignImageSpan = VerticalAlignImageSpan(it)
- spannableString.setSpan(
- verticalAlignImageSpan,
- start,
- end,
- Spanned.SPAN_INCLUSIVE_EXCLUSIVE
- )
- }
- }
- }
- return spannableString
- }
-
fun insertEmoji(
context: Context,
editable: Editable,
@@ -59,19 +27,16 @@ object ImageSpanUtil {
// 创建表情符号的ImageSpan
val emojiResId = emojiMap[emojiStr]
+
if (emojiResId != null) {
- val drawable = ResourcesCompat.getDrawable(context.resources, emojiResId, null)
- drawable?.let {
- val newSize = (textSize * 1.2).toInt()
- it.setBounds(0, 0, newSize, newSize)
- val centeredImageSpan = CenteredImageSpan(it)
- editable.setSpan(
- centeredImageSpan,
- index,
- index + emojiStr.length,
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
- )
- }
+ val span = CenterImageSpan(context, emojiResId).setDrawableSize(55)
+
+ editable.setSpan(
+ span,
+ index,
+ index + emojiStr.length,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
}
}
}
diff --git a/app/src/main/java/com/kaixed/kchat/utils/PopWindowUtil.kt b/app/src/main/java/com/kaixed/kchat/utils/PopWindowUtil.kt
index 2c724ff..4966da8 100644
--- a/app/src/main/java/com/kaixed/kchat/utils/PopWindowUtil.kt
+++ b/app/src/main/java/com/kaixed/kchat/utils/PopWindowUtil.kt
@@ -9,6 +9,7 @@ import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.PopupWindow
import androidx.core.view.updateMargins
+import androidx.viewbinding.ViewBinding
import com.kaixed.kchat.databinding.PopwindowsBinding
import com.kaixed.kchat.utils.DensityUtil.dpToPx
@@ -18,7 +19,7 @@ import com.kaixed.kchat.utils.DensityUtil.dpToPx
*/
object PopWindowUtil {
- fun showPopupWindow(context: Context, parentView: View, isMine: Boolean) {
+ fun showPopupWindow(context: Context, parentView: View, isMine: Boolean): PopupWindow {
val binding: PopwindowsBinding = PopwindowsBinding.inflate(LayoutInflater.from(context))
val popupWindow: PopupWindow = PopupWindow(
binding.root, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
@@ -70,7 +71,6 @@ object PopWindowUtil {
binding.ivArrowDown.visibility = View.GONE
}
-
if (isMine) {
rightEnough = (popupWidth / 2) < distanceToRightEdge
xOffset = if (rightEnough)
@@ -101,6 +101,8 @@ object PopWindowUtil {
popupWindow.showAsDropDown(parentView, xOffset, yOffset)
+ return popupWindow
+
// binding.ivWithdraw.setOnClickListener {
// messages[position].status = "withdraw"
// updateDb(messages[position])
diff --git a/app/src/main/java/com/kaixed/kchat/utils/SingleLiveEvent.kt b/app/src/main/java/com/kaixed/kchat/utils/SingleLiveEvent.kt
new file mode 100644
index 0000000..23c2c9e
--- /dev/null
+++ b/app/src/main/java/com/kaixed/kchat/utils/SingleLiveEvent.kt
@@ -0,0 +1,34 @@
+package com.kaixed.kchat.utils
+
+import androidx.annotation.MainThread
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+import java.util.concurrent.atomic.AtomicBoolean
+
+class SingleLiveEvent : MutableLiveData() {
+ private val mPending = AtomicBoolean(false)
+
+ override fun observe(owner: LifecycleOwner, observer: Observer) {
+ super.observe(owner) { t ->
+ // 只有在 mPending 为 true 时才会触发回调
+ if (mPending.compareAndSet(true, false)) {
+ observer.onChanged(t)
+ }
+ }
+ }
+
+ @MainThread
+ override fun setValue(value: T?) {
+ mPending.set(true)
+ super.setValue(value)
+ }
+
+ /**
+ * 用于 T 为 Void 类型的情况,使调用更简洁
+ */
+ @MainThread
+ fun call() {
+ setValue(null)
+ }
+}
diff --git a/app/src/main/java/com/kaixed/kchat/utils/TextUtil.kt b/app/src/main/java/com/kaixed/kchat/utils/TextUtil.kt
index e18cac1..c0db5f6 100644
--- a/app/src/main/java/com/kaixed/kchat/utils/TextUtil.kt
+++ b/app/src/main/java/com/kaixed/kchat/utils/TextUtil.kt
@@ -11,6 +11,36 @@ import java.util.Locale
*/
object TextUtil {
+ /**
+ * 从 URL 中提取图片的宽度和高度
+ *
+ * @param mainUrl 图片的 URL
+ * @return 三元组,包含提取后的 URL、宽度和高度
+ */
+ fun extractDimensionsAndPrefix(mainUrl: String): Triple? {
+ // 定义正则表达式,提取 ! 前的部分,w 和 h 参数
+ val regex = """(.*)!w=(\d+)&h=(\d+)""".toRegex()
+
+ // 使用正则表达式进行匹配
+ val matchResult = regex.find(mainUrl)
+
+ return matchResult?.let {
+ val prefix = it.groupValues[1] // 提取 ! 前的部分
+ val width = it.groupValues[2].toInt() // 提取 w 的值
+ val height = it.groupValues[3].toInt() // 提取 h 的值
+ val screenWidth = DensityUtil.getScreenWidth() / 2
+
+ // 计算宽高比
+ val ratio = width.toFloat() / screenWidth.toFloat()
+
+ // 根据比率调整宽高,保证图片不会超过屏幕宽度
+ val resultWidth = if (ratio > 1) (width / ratio).toInt() else width
+ val resultHeight = if (ratio > 1) (height / ratio).toInt() else height
+
+ Triple(prefix, resultWidth, resultHeight) // 返回三元组
+ }
+ }
+
fun getFriendCircleTime(timestamp: Long): String {
val currentTime = System.currentTimeMillis()
val difference = currentTime - timestamp
diff --git a/app/src/main/java/com/kaixed/kchat/utils/ViewUtil.kt b/app/src/main/java/com/kaixed/kchat/utils/ViewUtil.kt
index 0127f34..a0de572 100644
--- a/app/src/main/java/com/kaixed/kchat/utils/ViewUtil.kt
+++ b/app/src/main/java/com/kaixed/kchat/utils/ViewUtil.kt
@@ -1,5 +1,6 @@
package com.kaixed.kchat.utils
+import android.util.Log
import android.view.View
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
@@ -12,41 +13,93 @@ import com.kaixed.kchat.utils.DensityUtil.dp2Px
* @Date: 2024/11/26 12:12
*/
object ViewUtil {
- fun changeView(
+
+ fun setViewVisibility(
parentView: ConstraintLayout,
sender: Boolean,
avatarId: Int,
- contentId: Int
+ avatarMineId: Int,
+ contentId: Int,
+ contentMineId: Int
) {
- ConstraintSet().apply {
+ val constraintSet = ConstraintSet()
+ constraintSet.clone(parentView)
+
+ if (sender) {
+ constraintSet.setVisibility(avatarMineId, View.VISIBLE)
+ constraintSet.setVisibility(avatarId, View.GONE)
+ constraintSet.setVisibility(contentMineId, View.VISIBLE)
+ constraintSet.setVisibility(contentId, View.GONE)
+ } else {
+ constraintSet.setVisibility(avatarId, View.VISIBLE)
+ constraintSet.setVisibility(avatarMineId, View.GONE)
+ constraintSet.setVisibility(contentId, View.VISIBLE)
+ constraintSet.setVisibility(contentMineId, View.GONE)
+ }
+
+ constraintSet.applyTo(parentView)
+ }
+
+
+ fun changeView(
+ parentView: ConstraintLayout,
+ sender: Boolean,
+ avatarView: View,
+ contentView: View
+ ) {
+ // 获取 dp 到 px 的转换值
+ val margin = dp2Px(10)
+ Log.d("dp2Px", "Converted value: $margin")
+
+ // 创建 ConstraintSet
+ val constraintSet = ConstraintSet().apply {
clone(parentView)
+
if (sender) {
+ // 发送者布局调整:头像右对齐,内容左对齐
connect(
- avatarId, ConstraintSet.END,
+ avatarView.id, ConstraintSet.END,
parentView.id, ConstraintSet.END,
- dp2Px(10)
+ margin
)
connect(
- contentId, ConstraintSet.END,
- avatarId, ConstraintSet.START,
- dp2Px(10)
+ contentView.id, ConstraintSet.END,
+ avatarView.id, ConstraintSet.START,
+ margin
)
+ connect(contentView.id, ConstraintSet.TOP, avatarView.id, ConstraintSet.TOP)
+ connect(contentView.id, ConstraintSet.BOTTOM, avatarView.id, ConstraintSet.BOTTOM)
} else {
+ // 接收者布局调整:头像左对齐,内容右对齐
connect(
- avatarId, ConstraintSet.START,
+ avatarView.id, ConstraintSet.START,
parentView.id, ConstraintSet.START,
- dp2Px(10)
+ margin
)
connect(
- contentId, ConstraintSet.START,
- avatarId, ConstraintSet.END,
- dp2Px(10)
+ contentView.id, ConstraintSet.START,
+ avatarView.id, ConstraintSet.END,
+ margin
)
+ connect(contentView.id, ConstraintSet.TOP, avatarView.id, ConstraintSet.TOP)
+ connect(contentView.id, ConstraintSet.BOTTOM, avatarView.id, ConstraintSet.BOTTOM)
}
+
+ // 确保更新到父布局
applyTo(parentView)
}
+
+ // 调试:打印控件的可见性
+ Log.d("ConstraintDebug", "avatarView visibility: ${avatarView.visibility}")
+ Log.d("ConstraintDebug", "contentView visibility: ${contentView.visibility}")
+
+ // 强制父布局重新绘制,确保布局生效
+ parentView.post {
+ constraintSet.applyTo(parentView)
+ }
}
+
fun changeTimerVisibility(
position: Int,
messages: List,
diff --git a/app/src/main/java/com/kaixed/kchat/viewmodel/ContactViewModel.kt b/app/src/main/java/com/kaixed/kchat/viewmodel/ContactViewModel.kt
index 0df631f..10defba 100644
--- a/app/src/main/java/com/kaixed/kchat/viewmodel/ContactViewModel.kt
+++ b/app/src/main/java/com/kaixed/kchat/viewmodel/ContactViewModel.kt
@@ -9,6 +9,7 @@ import com.kaixed.kchat.data.local.entity.Contact
import com.kaixed.kchat.data.model.friend.FriendRequestItem
import com.kaixed.kchat.data.model.search.SearchUser
import com.kaixed.kchat.data.repository.ContactRepository
+import com.kaixed.kchat.utils.SingleLiveEvent
import io.objectbox.Box
import kotlinx.coroutines.launch
@@ -32,7 +33,7 @@ class ContactViewModel : ViewModel() {
val addContactResult: LiveData> = _addContactResult
// 搜索联系人
- private val _searchContactResult = MutableLiveData>()
+ private val _searchContactResult = SingleLiveEvent>()
val searchContactResult: LiveData> = _searchContactResult
// 获取联系人列表
diff --git a/app/src/main/res/anim/push_in_from_left.xml b/app/src/main/res/anim/push_in_from_left.xml
new file mode 100644
index 0000000..aa7f8a7
--- /dev/null
+++ b/app/src/main/res/anim/push_in_from_left.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/app/src/main/res/anim/push_in_from_right.xml b/app/src/main/res/anim/push_in_from_right.xml
new file mode 100644
index 0000000..255fe16
--- /dev/null
+++ b/app/src/main/res/anim/push_in_from_right.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/app/src/main/res/anim/push_out_to_left.xml b/app/src/main/res/anim/push_out_to_left.xml
new file mode 100644
index 0000000..f8f3b99
--- /dev/null
+++ b/app/src/main/res/anim/push_out_to_left.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/anim/push_out_to_right.xml b/app/src/main/res/anim/push_out_to_right.xml
new file mode 100644
index 0000000..da2f6c3
--- /dev/null
+++ b/app/src/main/res/anim/push_out_to_right.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/app/src/main/res/drawable-hdpi/image_loading.jpg b/app/src/main/res/drawable-hdpi/image_loading.jpg
new file mode 100644
index 0000000..1223502
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/image_loading.jpg differ
diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml
new file mode 100644
index 0000000..cfbe822
--- /dev/null
+++ b/app/src/main/res/layout/activity_about.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
index 01023c1..8673790 100644
--- a/app/src/main/res/layout/activity_login.xml
+++ b/app/src/main/res/layout/activity_login.xml
@@ -57,7 +57,22 @@
android:layout_height="0dp"
android:layout_marginStart="35dp"
android:background="@null"
- android:hint="请填写手机号或用户名"
+ android:hint="请填写用户名"
+ android:visibility="invisible"
+ 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"
+ app:layout_constraintTop_toBottomOf="@id/view" />
+
+
@@ -134,12 +150,12 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ layout="@layout/layout_bottom_navigation" />
diff --git a/app/src/main/res/layout/activity_profile_detail.xml b/app/src/main/res/layout/activity_profile_detail.xml
index d4e3b85..c88baeb 100644
--- a/app/src/main/res/layout/activity_profile_detail.xml
+++ b/app/src/main/res/layout/activity_profile_detail.xml
@@ -15,7 +15,7 @@
android:layout_height="wrap_content"
app:titleName="个人信息" />
-
@@ -89,7 +89,7 @@
app:itemName="我的地址" />
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_register.xml b/app/src/main/res/layout/activity_register.xml
index ecb8327..cfe338d 100644
--- a/app/src/main/res/layout/activity_register.xml
+++ b/app/src/main/res/layout/activity_register.xml
@@ -26,7 +26,7 @@
android:layout_marginTop="50dp"
android:text="手机号注册"
android:textColor="@color/black"
- android:textSize="24sp"
+ android:textSize="19sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -46,7 +46,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/view" />
@@ -58,7 +58,11 @@
android:layout_marginStart="35dp"
android:background="@null"
android:hint="例如:喜乐"
+ android:inputType="text"
+ android:maxLength="6"
+ android:maxLines="1"
android:textCursorDrawable="@drawable/cursor"
+ android:textSize="17sp"
app:layout_constraintBottom_toTopOf="@id/view0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_username"
@@ -67,7 +71,7 @@
@@ -93,7 +97,10 @@
android:layout_marginStart="35dp"
android:background="@null"
android:hint="请填写手机号"
+ android:maxLength="11"
+ android:maxLines="1"
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"
@@ -102,7 +109,7 @@
@@ -127,7 +134,10 @@
android:background="@null"
android:hint="请填写密码"
android:inputType="textPassword"
+ android:maxLength="16"
+ android:maxLines="1"
android:textCursorDrawable="@drawable/cursor"
+ android:textSize="17sp"
app:layout_constraintBottom_toTopOf="@id/view2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/et_username"
@@ -137,12 +147,22 @@
+
+
+ app:layout_constraintVertical_bias="0.95" />
+ app:layout_constraintVertical_bias="0.8" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_search_friends.xml b/app/src/main/res/layout/activity_search_friends.xml
index 00747b5..3918625 100644
--- a/app/src/main/res/layout/activity_search_friends.xml
+++ b/app/src/main/res/layout/activity_search_friends.xml
@@ -43,6 +43,7 @@
android:background="@null"
android:hint="账号/手机号"
android:maxLines="1"
+ android:inputType="text"
android:textColor="@color/black"
android:textSize="15sp" />
@@ -80,7 +81,7 @@
android:orientation="horizontal"
android:paddingHorizontal="15dp"
android:paddingVertical="10dp"
- android:visibility="gone"
+ android:visibility="invisible"
app:layout_constraintTop_toBottomOf="@id/view">
-
-
+ android:visibility="invisible"
+ app:layout_constraintTop_toBottomOf="@id/view">
+
+
+ app:layout_constraintTop_toBottomOf="@id/tv_title" />
-
-
-
-
-
-
-
-
+ android:layout_height="match_parent"
+ app:defaultNavHost="true"
+ app:navGraph="@navigation/nav_graph" />
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/bottom_sheet_layout.xml b/app/src/main/res/layout/bottom_sheet_layout.xml
index 5328a10..c06e3d2 100644
--- a/app/src/main/res/layout/bottom_sheet_layout.xml
+++ b/app/src/main/res/layout/bottom_sheet_layout.xml
@@ -1,42 +1,54 @@
-
+
+ app:cardCornerRadius="13dp"
+ app:cardElevation="0dp"
+ app:cardMaxElevation="0dp">
-
+ android:layout_height="wrap_content"
+ android:background="#F7F7F7"
+ android:orientation="vertical">
-
+
-
-
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/chat_recycle_item_custom_normal.xml b/app/src/main/res/layout/chat_recycle_item_custom_normal.xml
index d5d4068..dfec6b1 100644
--- a/app/src/main/res/layout/chat_recycle_item_custom_normal.xml
+++ b/app/src/main/res/layout/chat_recycle_item_custom_normal.xml
@@ -21,14 +21,17 @@
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginVertical="20dp"
+ android:layout_marginStart="10dp"
android:src="@drawable/ic_avatar"
+ app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_timer"
app:roundPercent="0.3" />
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/chat_recycle_item_image_normal.xml b/app/src/main/res/layout/chat_recycle_item_image_normal.xml
index de61e60..ad079bc 100644
--- a/app/src/main/res/layout/chat_recycle_item_image_normal.xml
+++ b/app/src/main/res/layout/chat_recycle_item_image_normal.xml
@@ -9,10 +9,10 @@
@@ -28,8 +30,31 @@
android:id="@+id/image"
android:layout_width="150dp"
android:layout_height="100dp"
+ android:layout_marginStart="10dp"
android:scaleType="centerCrop"
+ app:layout_constraintStart_toEndOf="@id/ifv_avatar"
app:layout_constraintTop_toTopOf="@id/ifv_avatar"
app:roundPercent="0.15" />
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_account_security.xml b/app/src/main/res/layout/fragment_account_security.xml
new file mode 100644
index 0000000..63c4716
--- /dev/null
+++ b/app/src/main/res/layout/fragment_account_security.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_chat.xml b/app/src/main/res/layout/fragment_chat.xml
new file mode 100644
index 0000000..6d82233
--- /dev/null
+++ b/app/src/main/res/layout/fragment_chat.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_discovery.xml b/app/src/main/res/layout/fragment_discovery.xml
index 6477875..5063446 100644
--- a/app/src/main/res/layout/fragment_discovery.xml
+++ b/app/src/main/res/layout/fragment_discovery.xml
@@ -18,25 +18,36 @@
android:textColor="@color/black"
android:textSize="16sp" />
-
+ android:layout_height="match_parent">
-
+
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_mine.xml b/app/src/main/res/layout/fragment_mine.xml
index 6916eb9..31d9434 100644
--- a/app/src/main/res/layout/fragment_mine.xml
+++ b/app/src/main/res/layout/fragment_mine.xml
@@ -32,7 +32,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
- android:text="糕菜菜"
+ android:text=""
android:textColor="@color/black"
android:textSize="19sp"
android:textStyle="bold"
@@ -43,7 +43,7 @@
android:id="@+id/tv_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="kid: kaixed"
+ android:text=""
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@id/ifv_avatar"
app:layout_constraintStart_toStartOf="@id/tv_nickname"
diff --git a/app/src/main/res/layout/fragment_new_message.xml b/app/src/main/res/layout/fragment_new_message.xml
new file mode 100644
index 0000000..e8569b7
--- /dev/null
+++ b/app/src/main/res/layout/fragment_new_message.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml
new file mode 100644
index 0000000..75957e6
--- /dev/null
+++ b/app/src/main/res/layout/fragment_settings.xml
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_bottom_navigation.xml b/app/src/main/res/layout/layout_bottom_navigation.xml
new file mode 100644
index 0000000..2f906e0
--- /dev/null
+++ b/app/src/main/res/layout/layout_bottom_navigation.xml
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/popwindows.xml b/app/src/main/res/layout/popwindows.xml
index e54eef9..d97e647 100644
--- a/app/src/main/res/layout/popwindows.xml
+++ b/app/src/main/res/layout/popwindows.xml
@@ -47,6 +47,7 @@
android:textSize="12sp" />
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/attr.xml b/app/src/main/res/values/attr.xml
index 5e6f454..c49317f 100644
--- a/app/src/main/res/values/attr.xml
+++ b/app/src/main/res/values/attr.xml
@@ -47,4 +47,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index afc3b64..e301c6d 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -2,7 +2,7 @@
#FF000000
#FFFFFFFF
- #006EEF
+ #2D88FE
#576B95
#07C160
#E5E5E5
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index a0bdd0d..17723e2 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,5 +1,5 @@
- 15dp
+ 8dp
0.2dp
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 5cdbd78..b1165ef 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -21,8 +21,6 @@ pictureselector = "v3.11.2"
pinyin4j = "2.5.1"
preference = "1.2.1"
retrofit = "2.11.0"
-shapedrawable = "3.2"
-shapeview = "9.2"
spannable = "1.2.7"
therouter = "1.2.2"
window = "1.3.0"
@@ -30,6 +28,7 @@ window = "1.3.0"
kotlin = "1.9.23"
coreKtx = "1.13.1"
objectbox = "4.0.2"
+navigationFragment = "2.8.4"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -41,8 +40,6 @@ pictureselector = { module = "io.github.lucksiege:pictureselector", version.ref
pinyin4j = { module = "com.belerweb:pinyin4j", version.ref = "pinyin4j" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit2-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" }
-shapedrawable = { module = "com.github.getActivity:ShapeDrawable", version.ref = "shapedrawable" }
-shapeview = { module = "com.github.getActivity:ShapeView", version.ref = "shapeview" }
spannable = { module = "com.github.liangjingkanji:spannable", version.ref = "spannable" }
therouter-ksp = { module = "cn.therouter:apt", version.ref = "therouter" }
objectbox-android-objectbrowser = { group = "io.objectbox", name = "objectbox-android-objectbrowser", version.ref = "objectbox" }
@@ -66,6 +63,7 @@ preference = { module = "androidx.preference:preference", version.ref = "prefere
therouter = { module = "cn.therouter:router", version.ref = "therouter" }
ucrop = { module = "io.github.lucksiege:ucrop", version.ref = "pictureselector" }
window = { module = "androidx.window:window", version.ref = "window" }
+androidx-navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment", version.ref = "navigationFragment" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }