feat: 完善部分功能

This commit is contained in:
糕小菜 2024-12-07 13:26:29 +08:00
parent cb9447ab82
commit 5e9a3f6c8f
61 changed files with 1967 additions and 584 deletions

View File

@ -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)
}

View File

@ -39,6 +39,12 @@
android:theme="@style/Theme.KChatAndroid"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".ui.activity.setting.AboutActivity"
android:exported="false" />
<activity
android:name=".ui.activity.SettingActivity"
android:exported="false" />
<activity
android:name=".ui.activity.WebViewActivity"
android:exported="false" />

View File

@ -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()
}

View File

@ -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<ActivityApplyFriendsDetailBindin
return ActivityApplyFriendsDetailBinding.inflate(layoutInflater)
}
private var request: FriendRequestItem? = null
private var user: User? = null
private var user: SearchUser? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -32,17 +29,13 @@ class ApplyFriendsDetailActivity : BaseActivity<ActivityApplyFriendsDetailBindin
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun initData() {
user = intent.getParcelableExtra("user", User::class.java)
request = intent.getParcelableExtra(
"request",
FriendRequestItem::class.java
)
user = intent.getParcelableExtra("user", SearchUser::class.java)
}
private fun setContent() {
val nickname = request?.nickname ?: user?.nickname
val signature = request?.signature ?: user?.signature
val avatarUrl = request?.avatarUrl ?: user?.avatarUrl
val nickname = user?.nickname
val signature = user?.signature
val avatarUrl = user?.avatarUrl
// 设置到 UI 上
binding.tvContactName.text = nickname
@ -53,7 +46,7 @@ class ApplyFriendsDetailActivity : BaseActivity<ActivityApplyFriendsDetailBindin
private fun setOnClick() {
binding.rlSendMessage.setOnClickListener {
val contactId = request?.username ?: user?.username
val contactId = user?.username
contactId?.let {
val intent = Intent(this, ApplyAddFriendActivity::class.java)
intent.putExtra("contactId", it)

View File

@ -115,15 +115,10 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener,
enableEdgeToEdge()
firstLoadData()
initView()
setListener()
bindWebSocketService()
setPanelChange()
getKeyBoardVisibility()
}
@ -302,7 +297,6 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), OnItemClickListener,
)
}
binding.etInput.addTextChangedListener(
afterTextChanged = { _ ->
val isInputEmpty = binding.etInput.text.toString().isEmpty()
@ -362,11 +356,7 @@ class ChatActivity : BaseActivity<ActivityChatBinding>(), 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<ActivityChatBinding>(), OnItemClickListener,
when (position) {
0 -> {
selectPicture()
// 相册
}
}
}

View File

@ -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<ActivityFriendCircleBinding>() {
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() {

View File

@ -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<ActivityLoginBinding>() {
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<ActivityLoginBinding>() {
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<ActivityLoginBinding>() {
}
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<ActivityLoginBinding>() {
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(

View File

@ -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<ActivityMainBinding>(), View.OnClickListener {
class MainActivity : BaseActivity<ActivityMainBinding>() {
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<Contact> by lazy { getBoxStore().boxFor(Contact::class.java) }
private val contactBox: Box<Contact> 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<Fragment>(
@ -69,9 +73,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), View.OnClickListener {
initView()
initViewPager()
updateSelection(KEY_HOME_FRAGMENT)
}
override fun initData() {
@ -94,18 +95,17 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), 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<ActivityMainBinding>(), 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
)
}

View File

@ -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<ActivityProfileDetailBinding>() {
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<ActivityProfileDetailBinding>() {
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<ActivityProfileDetailBinding>() {
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)
}
}
}

View File

@ -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<ActivityRegisterBinding>() {
@ -30,8 +30,6 @@ class RegisterActivity : BaseActivity<ActivityRegisterBinding>() {
private val userViewModel: UserViewModel by viewModels()
private val userInfoBox: Box<UserInfo> by lazy { getBoxStore().boxFor(UserInfo::class.java) }
override fun inflateBinding(): ActivityRegisterBinding {
return ActivityRegisterBinding.inflate(layoutInflater)
}
@ -56,7 +54,6 @@ class RegisterActivity : BaseActivity<ActivityRegisterBinding>() {
binding.tvContinue.setOnClickListener {
onContinueClick()
}
}
@ -122,7 +119,7 @@ class RegisterActivity : BaseActivity<ActivityRegisterBinding>() {
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<ActivityRegisterBinding>() {
}
}
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()
}

View File

@ -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<ActivitySearchFriendsBinding>() {
@ -29,7 +25,7 @@ class SearchFriendsActivity : BaseActivity<ActivitySearchFriendsBinding>() {
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<ActivitySearchFriendsBinding>() {
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<ActivitySearchFriendsBinding>() {
}, 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<ActivitySearchFriendsBinding>() {
}
}
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
}
}

View File

@ -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<ActivitySettingBinding>() {
override fun inflateBinding(): ActivitySettingBinding {
return ActivitySettingBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
}
override fun initData() {
}
}

View File

@ -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<ActivityAboutBinding>() {
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)
}
}
}

View File

@ -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<TextView>(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<Messages> = 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<Messages> = ObjectBox.getBoxStore().boxFor(Messages::class.java)
val messagesBox: Box<Messages> = getBox(Messages::class.java)
messagesBox.put(message)
}

View File

@ -29,6 +29,8 @@ abstract class BaseFragment<VB : ViewBinding> : 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)

View File

@ -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<FragmentHomeBinding>(), OnItemListener,
@ -59,8 +57,6 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnItemListener,
private var talkerId: String? = null
private val homeViewModel: HomeViewModel by viewModels()
private val items: List<HomeItem> by lazy { getHomeItems() }
private var webSocketService: WebSocketService? = null

View File

@ -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<FragmentMineBinding>() {
@ -32,11 +34,7 @@ class MineFragment : BaseFragment<FragmentMineBinding>() {
}
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<FragmentMineBinding>() {
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)

View File

@ -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<FragmentSettingsBinding>(), 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 -> {
}
}
}
}

View File

@ -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<FragmentAccountSecurityBinding>() {
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()
}
}
}

View File

@ -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)
}
}
}
}

View File

@ -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)
}
}
}
}

View File

@ -67,6 +67,10 @@ class CustomTitleBar @JvmOverloads constructor(
}
}
fun setOnBackClickListener(listener: OnClickListener?) {
binding.ivBack.setOnClickListener(listener)
}
fun setOnSettingClickListener(listener: OnClickListener?) {
binding.ivSetting.setOnClickListener(listener)
}

View File

@ -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

View File

@ -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()
}
}

View File

@ -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<Point>
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)
}
}

View File

@ -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()
}
}

View File

@ -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()
}

View File

@ -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<String, Int> = 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
)
}
}
}

View File

@ -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])

View File

@ -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<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
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)
}
}

View File

@ -11,6 +11,36 @@ import java.util.Locale
*/
object TextUtil {
/**
* URL 中提取图片的宽度和高度
*
* @param mainUrl 图片的 URL
* @return 三元组包含提取后的 URL宽度和高度
*/
fun extractDimensionsAndPrefix(mainUrl: String): Triple<String, Int, Int>? {
// 定义正则表达式,提取 ! 前的部分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

View File

@ -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<Messages>,

View File

@ -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<Result<String?>> = _addContactResult
// 搜索联系人
private val _searchContactResult = MutableLiveData<Result<SearchUser?>>()
private val _searchContactResult = SingleLiveEvent<Result<SearchUser?>>()
val searchContactResult: LiveData<Result<SearchUser?>> = _searchContactResult
// 获取联系人列表

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromXDelta="-100%"
android:toXDelta="0%" />
</set>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromXDelta="100%"
android:toXDelta="0%" />
</set>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromXDelta="0%"
android:toXDelta="-100%" />
</set>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromXDelta="0%"
android:toXDelta="100%" />
</set>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".ui.activity.setting.AboutActivity">
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.kaixed.kchat.ui.widget.ReBoundScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:layout_width="65dp"
android:layout_height="65dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="40dp"
android:src="@mipmap/ic_launcher"
app:roundPercent="0.3" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="30dp"
android:text="开心聊"
android:textColor="@color/black"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="3dp"
android:text="Version 0.0.01"
android:textColor="@color/black"
android:textSize="17sp" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:layout_marginHorizontal="30dp"
android:layout_marginTop="40dp"
android:background="#E5E5E5" />
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="30dp"
android:background="@color/white"
app:isBottomDividerVisible="true"
app:itemName="功能介绍" />
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="30dp"
android:background="@color/white"
app:isBottomDividerVisible="true"
app:itemName="投诉" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/ci_check_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="30dp"
android:background="@color/white"
app:itemName="检查新版本" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:layout_marginHorizontal="30dp"
android:background="#E5E5E5" />
</LinearLayout>
</com.kaixed.kchat.ui.widget.ReBoundScrollView>
</LinearLayout>

View File

@ -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" />
<EditText
android:id="@+id/et_telephone"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="35dp"
android:background="@null"
android:hint="请填写手机号"
android:textCursorDrawable="@drawable/cursor"
android:textSize="17sp"
app:layout_constraintBottom_toTopOf="@id/view1"
@ -126,7 +141,8 @@
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
android:text="上述账号/邮箱仅用于登陆验证"
android:textSize="16sp"
android:textColor="#A5A5A5"
android:textSize="13sp"
app:layout_constraintBottom_toTopOf="@id/tv_login"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
@ -134,12 +150,12 @@
<TextView
android:id="@+id/tv_login"
android:layout_width="150dp"
android:layout_height="48dp"
android:layout_height="43dp"
android:background="@drawable/login_bac"
android:gravity="center"
android:text="同意并登录"
android:textColor="#b4B4B4"
android:textSize="19sp"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -152,7 +168,7 @@
android:layout_height="wrap_content"
android:text="紧急冻结"
android:textColor="@color/normal"
android:textSize="17sp"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -165,7 +181,7 @@
android:layout_height="wrap_content"
android:text="找回密码"
android:textColor="@color/normal"
android:textSize="17sp"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="@id/tv_urgent_freeze"
app:layout_constraintEnd_toStartOf="@id/tv_urgent_freeze"
app:layout_constraintHorizontal_bias="0.7"
@ -178,7 +194,7 @@
android:layout_height="wrap_content"
android:text="安全中心"
android:textColor="@color/normal"
android:textSize="17sp"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="@id/tv_urgent_freeze"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.3"

View File

@ -19,159 +19,8 @@
android:layout_height="0dp"
android:layout_weight="1.0" />
<androidx.constraintlayout.widget.ConstraintLayout
<include
android:id="@+id/bottomNavContainer"
android:layout_width="match_parent"
android:layout_height="55dp"
android:background="@color/white"
android:paddingBottom="4dp">
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:background="#D2D2D2"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_home"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/cl_contact"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.25">
<ImageView
android:id="@+id/iv_home"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="@drawable/ic_main"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_home"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="主页"
android:textColor="@color/black"
android:textSize="11sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_home" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_contact"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/cl_discovery"
app:layout_constraintStart_toEndOf="@id/cl_home"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.25">
<ImageView
android:id="@+id/iv_contact"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="@drawable/ic_message"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_contact"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="联系人"
android:textColor="@color/black"
android:textSize="11sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_contact" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_discovery"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/cl_contact"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.25">
<ImageView
android:id="@+id/iv_discovery"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="@drawable/ic_discovery"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="8dp"
android:layout_height="8dp"
android:background="@drawable/icon_red_dot"
app:layout_constraintCircle="@id/iv_discovery"
app:layout_constraintCircleAngle="45"
app:layout_constraintCircleRadius="15dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_discovery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发现"
android:textColor="@color/black"
android:textSize="11sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_discovery" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_mine"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/cl_discovery"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.25">
<ImageView
android:id="@+id/iv_mine"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="@drawable/ic_mine"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_mine"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我的"
android:textColor="@color/black"
android:textSize="11sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_mine" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
layout="@layout/layout_bottom_navigation" />
</LinearLayout>

View File

@ -15,7 +15,7 @@
android:layout_height="wrap_content"
app:titleName="个人信息" />
<androidx.core.widget.NestedScrollView
<com.kaixed.kchat.ui.widget.ReBoundScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -89,7 +89,7 @@
app:itemName="我的地址" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</com.kaixed.kchat.ui.widget.ReBoundScrollView>
</LinearLayout>

View File

@ -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 @@
<View
android:id="@+id/view0"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_height="0.5dp"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="15dp"
android:background="#D8D8D8"
@ -81,7 +85,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/view0" />
@ -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 @@
<View
android:id="@+id/view1"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_height="0.5dp"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="15dp"
android:background="#D8D8D8"
@ -116,7 +123,7 @@
android:layout_marginTop="15dp"
android:text="密码"
android:textColor="@color/black"
android:textSize="19sp"
android:textSize="17sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/view1" />
@ -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 @@
<View
android:id="@+id/view2"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_height="0.5dp"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="15dp"
android:background="#D8D8D8"
app:layout_constraintTop_toBottomOf="@id/tv_password" />
<com.kaixed.kchat.ui.widget.SmoothCheckBox
android:id="@+id/smooth_check_box"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginEnd="3dp"
app:color_unchecked_stroke="#B2B2B2"
app:duration="200"
app:layout_constraintEnd_toStartOf="@id/tv_tip"
app:layout_constraintTop_toTopOf="@id/tv_tip" />
<TextView
android:id="@+id/tv_tip"
android:layout_width="wrap_content"
@ -150,12 +170,12 @@
android:layout_marginBottom="15dp"
android:gravity="center_horizontal"
android:text="我已阅读并同意《软件许可及服务协议》\n本页面收集的信息仅用于注册账户"
android:textSize="16sp"
android:textSize="13sp"
app:layout_constraintBottom_toTopOf="@id/tv_continue"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/view2"
app:layout_constraintVertical_bias="0.9" />
app:layout_constraintVertical_bias="0.95" />
<TextView
android:id="@+id/tv_continue"
@ -165,11 +185,11 @@
android:gravity="center"
android:text="同意并继续"
android:textColor="#b4B4B4"
android:textSize="19sp"
android:textSize="17sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/view2"
app:layout_constraintVertical_bias="0.7" />
app:layout_constraintVertical_bias="0.8" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -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">
<androidx.constraintlayout.utils.widget.ImageFilterView
@ -131,34 +132,33 @@
app:layout_constraintTop_toBottomOf="@id/view" />
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:background="@color/white"
android:paddingHorizontal="15dp"
android:paddingVertical="7dp"
android:text="个人"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cv_search" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_friends"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/tv_title">
android:visibility="invisible"
app:layout_constraintTop_toBottomOf="@id/view">
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:paddingHorizontal="15dp"
android:paddingVertical="7dp"
android:text="个人"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/view_divider"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="#E5E5E5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/iv_avatar"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@id/tv_title" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/iv_avatar"
@ -169,7 +169,7 @@
android:src="@drawable/ic_avatar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@id/view_divider"
app:roundPercent="0.3" />
<TextView

View File

@ -1,44 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/gray"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".ui.activity.SettingActivity">
<ImageView
android:id="@+id/iv_back"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_marginStart="15dp"
android:layout_marginTop="15dp"
android:src="@drawable/ic_left_arrow"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/setting"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@id/iv_back"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/iv_back" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_setting"
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="15dp"
android:overScrollMode="never"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_back" />
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -1,42 +1,54 @@
<com.hjq.shape.layout.ShapeLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/gray"
android:orientation="vertical"
app:shape_radiusInTopLeft="16dp"
app:shape_radiusInTopRight="16dp"
app:shape_type="rectangle">
app:cardCornerRadius="13dp"
app:cardElevation="0dp"
app:cardMaxElevation="0dp">
<com.hjq.shape.view.ShapeTextView
android:id="@+id/tv_quit_account"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="55dp"
android:gravity="center_horizontal|center_vertical"
android:text="退出登录"
android:textColor="@color/black"
app:shape_radiusInTopLeft="16dp"
app:shape_radiusInTopRight="16dp"
app:shape_solidColor="@color/white"
app:shape_type="rectangle" />
android:layout_height="wrap_content"
android:background="#F7F7F7"
android:orientation="vertical">
<TextView
android:id="@+id/tv_quit_app"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginTop="1dp"
android:background="@color/white"
android:gravity="center_horizontal|center_vertical"
android:text="关闭应用"
android:textColor="@color/black" />
<TextView
android:id="@+id/tv_quit_account"
android:layout_width="match_parent"
android:layout_height="55dp"
android:background="@color/white"
android:gravity="center_horizontal|center_vertical"
android:text="退出登录"
android:textColor="@color/black"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_cancel"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginTop="8dp"
android:background="@color/white"
android:gravity="center_horizontal|center_vertical"
android:text="取消"
android:textColor="@color/black" />
</com.hjq.shape.layout.ShapeLinearLayout>
<TextView
android:id="@+id/tv_quit_app"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginTop="1dp"
android:background="@color/white"
android:gravity="center_horizontal|center_vertical"
android:text="关闭应用"
android:textColor="@color/black"
android:textSize="16sp" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:background="#E5E5E5" />
<TextView
android:id="@+id/tv_cancel"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginTop="8dp"
android:background="@color/white"
android:gravity="center_horizontal|center_vertical"
android:text="取消"
android:textColor="@color/black"
android:textSize="16sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -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" />
<TextView
android:id="@+id/tv_message_content"
android:id="@+id/tv_msg_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:background="@drawable/chat_send_btn_bac_mine"
android:gravity="center_vertical"
android:maxWidth="250dp"
@ -40,6 +43,37 @@
android:textColorHighlight="#CCCCCC"
android:textIsSelectable="true"
android:textSize="17sp"
app:layout_constraintStart_toEndOf="@id/ifv_avatar"
app:layout_constraintTop_toTopOf="@id/ifv_avatar" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv_avatar_mine"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginEnd="10dp"
android:layout_marginVertical="20dp"
android:src="@drawable/ic_avatar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_timer"
app:roundPercent="0.3" />
<TextView
android:id="@+id/tv_msg_content_mine"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/chat_send_btn_bac_mine"
android:gravity="center_vertical"
android:maxWidth="250dp"
android:minHeight="35dp"
android:paddingHorizontal="12dp"
android:paddingVertical="6dp"
android:layout_marginEnd="10dp"
android:text="haha"
android:textColor="@color/white"
android:textColorHighlight="#CCCCCC"
android:textIsSelectable="true"
android:textSize="17sp"
app:layout_constraintEnd_toStartOf="@id/ifv_avatar_mine"
app:layout_constraintTop_toTopOf="@id/ifv_avatar_mine" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -9,10 +9,10 @@
<TextView
android:id="@+id/tv_timer"
android:layout_width="match_parent"
android:textSize="12sp"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:paddingTop="20dp"
android:textSize="12sp"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.utils.widget.ImageFilterView
@ -20,6 +20,8 @@
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginVertical="20dp"
android:layout_marginStart="10dp"
app:layout_constraintStart_toStartOf="parent"
android:src="@drawable/ic_avatar"
app:layout_constraintTop_toBottomOf="@id/tv_timer"
app:roundPercent="0.3" />
@ -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" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv_avatar_mine"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginVertical="20dp"
android:layout_marginStart="10dp"
app:layout_constraintEnd_toEndOf="parent"
android:src="@drawable/ic_avatar"
app:layout_constraintTop_toBottomOf="@id/tv_timer"
app:roundPercent="0.3" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/image_mine"
android:layout_width="150dp"
android:layout_height="100dp"
android:layout_marginEnd="10dp"
android:scaleType="centerCrop"
app:layout_constraintEnd_toStartOf="@id/ifv_avatar_mine"
app:layout_constraintTop_toTopOf="@id/ifv_avatar_mine"
app:roundPercent="0.15" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,16 @@
<?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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.settings.AccountSecurityFragment">
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:id="@+id/ctb_title_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleName="账号与安全" />
</LinearLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.settings.ChatFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
</FrameLayout>

View File

@ -18,25 +18,36 @@
android:textColor="@color/black"
android:textSize="16sp" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/ci_friend_circle"
<com.kaixed.kchat.ui.widget.ReBoundScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemLeftIcon="@drawable/ic_friend_circle"
app:itemName="朋友圈" />
android:layout_height="match_parent">
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:itemLeftIcon="@drawable/ic_discovery_scan"
app:itemName="扫一扫" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:isTopDividerVisible="true"
app:itemLeftIcon="@drawable/ic_discovery_music"
app:itemName="听一听" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/ci_friend_circle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemLeftIcon="@drawable/ic_friend_circle"
app:itemName="朋友圈" />
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:itemLeftIcon="@drawable/ic_discovery_scan"
app:itemName="扫一扫" />
<com.kaixed.kchat.ui.widget.CustomItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:isTopDividerVisible="true"
app:itemLeftIcon="@drawable/ic_discovery_music"
app:itemName="听一听" />
</LinearLayout>
</com.kaixed.kchat.ui.widget.ReBoundScrollView>
</LinearLayout>

View File

@ -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"

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.settings.NewMessageFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
</FrameLayout>

View File

@ -0,0 +1,159 @@
<?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"
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/gray"
android:fitsSystemWindows="true"
android:orientation="vertical">
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:id="@+id/custom_title_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleName="设置" />
<com.kaixed.kchat.ui.widget.ReBoundScrollView
android:id="@+id/re_bound_scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/scroll_content_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/item_account_security"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemName="账号与安全" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/item_teen_mode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin"
app:isBottomDividerVisible="true"
app:itemName="青少年模式" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/item_care_mode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemName="关怀模式" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/item_new_message_notification"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin"
app:isBottomDividerVisible="true"
app:itemName="新消息通知" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/item_chat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:isBottomDividerVisible="true"
app:itemName="聊天" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/item_device"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:isBottomDividerVisible="true"
app:itemName="设备" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/item_general"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemName="通用" />
<TextView
android:id="@+id/text_privacy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:paddingTop="10dp"
android:paddingBottom="5dp"
android:text="隐私" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/item_friend_permission"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:isBottomDividerVisible="true"
app:itemName="朋友权限" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/item_personal_info_permission"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:isBottomDividerVisible="true"
app:itemName="个人信息与权限" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/item_personal_info_collection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:isBottomDividerVisible="true"
app:itemName="个人信息收集清单" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/item_third_party_info_sharing"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemName="第三方信息共享清单" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/item_plugin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin"
app:isBottomDividerVisible="true"
app:itemName="插件" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/item_about_kchat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin"
app:isBottomDividerVisible="true"
app:itemName="关于kchat" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/item_help_feedback"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemName="帮助与反馈" />
<TextView
android:id="@+id/tv_switch_account"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="@dimen/margin"
android:background="@color/white"
android:gravity="center"
android:text="切换账号"
android:textColor="@color/black"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_logout"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="@dimen/margin"
android:layout_marginBottom="20dp"
android:background="@color/white"
android:gravity="center"
android:text="退出"
android:textColor="@color/black"
android:textSize="16sp" />
</LinearLayout>
</com.kaixed.kchat.ui.widget.ReBoundScrollView>
</LinearLayout>

View File

@ -0,0 +1,157 @@
<?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="55dp"
android:background="@color/white"
android:paddingBottom="4dp">
<View
android:layout_width="match_parent"
android:layout_height="@dimen/divider_height"
android:background="#D2D2D2"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_home"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/cl_contact"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.25">
<ImageView
android:id="@+id/iv_home"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="@drawable/ic_main"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_home"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="主页"
android:textColor="@color/black"
android:textSize="11sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_home" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_contact"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/cl_discovery"
app:layout_constraintStart_toEndOf="@id/cl_home"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.25">
<ImageView
android:id="@+id/iv_contact"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="@drawable/ic_message"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_contact"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="联系人"
android:textColor="@color/black"
android:textSize="11sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_contact" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_discovery"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/cl_contact"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.25">
<ImageView
android:id="@+id/iv_discovery"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="@drawable/ic_discovery"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="8dp"
android:layout_height="8dp"
android:background="@drawable/icon_red_dot"
app:layout_constraintCircle="@id/iv_discovery"
app:layout_constraintCircleAngle="45"
app:layout_constraintCircleRadius="15dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_discovery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发现"
android:textColor="@color/black"
android:textSize="11sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_discovery" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_mine"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/cl_discovery"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.25">
<ImageView
android:id="@+id/iv_mine"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="@drawable/ic_mine"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_mine"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我的"
android:textColor="@color/black"
android:textSize="11sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_mine" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -47,6 +47,7 @@
android:textSize="12sp" />
<TextView
android:id="@+id/tv_delete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation 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/nav_graph"
app:startDestination="@id/firstFragment">
<fragment
android:id="@+id/firstFragment"
android:name="com.kaixed.kchat.ui.fragment.SettingsFragment"
android:label="SettingsFragment"
tools:layout="@layout/fragment_settings">
<action
android:id="@+id/action_settings_to_security"
app:destination="@id/securityFragment" />
</fragment>
<fragment
android:id="@+id/securityFragment"
android:name="com.kaixed.kchat.ui.fragment.settings.AccountSecurityFragment"
android:label="Third Fragment"
tools:layout="@layout/fragment_account_security" />
</navigation>

View File

@ -47,4 +47,13 @@
<!-- 是否默认打开 -->
<attr name="sb_checked" format="boolean" />
</declare-styleable>
<declare-styleable name="SmoothCheckBox">
<attr name="duration" format="integer"/>
<attr name="stroke_width" format="dimension"/>
<attr name="color_tick" format="color"/>
<attr name="color_checked" format="color"/>
<attr name="color_unchecked" format="color"/>
<attr name="color_unchecked_stroke" format="color"/>
</declare-styleable>
</resources>

View File

@ -2,7 +2,7 @@
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="blue">#006EEF</color>
<color name="blue">#2D88FE</color>
<color name="normal">#576B95</color>
<color name="green">#07C160</color>
<color name="gray">#E5E5E5</color>

View File

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

View File

@ -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" }