feat: 新增二维码扫描加好友功能

- 引入华为统一扫码库
- 新增二维码扫描加好友功能
- 优化个人界面的头像显示
- 待修改二维码扫描为自定义界面,目前使用sdk默认界面
- 优化部分工具类
This commit is contained in:
糕小菜 2024-12-30 15:41:05 +08:00
parent 76288c7e99
commit 9258be9522
39 changed files with 597 additions and 264 deletions

View File

@ -0,0 +1,57 @@
{
"agcgw_all":{
"CN":"connect-drcn.dbankcloud.cn",
"CN_back":"connect-drcn.hispace.hicloud.com",
"DE":"connect-dre.dbankcloud.cn",
"DE_back":"connect-dre.hispace.hicloud.com",
"RU":"connect-drru.hispace.dbankcloud.ru",
"RU_back":"connect-drru.hispace.dbankcloud.cn",
"SG":"connect-dra.dbankcloud.cn",
"SG_back":"connect-dra.hispace.hicloud.com"
},
"websocketgw_all":{
"CN":"connect-ws-drcn.hispace.dbankcloud.cn",
"CN_back":"connect-ws-drcn.hispace.dbankcloud.com",
"DE":"connect-ws-dre.hispace.dbankcloud.cn",
"DE_back":"connect-ws-dre.hispace.dbankcloud.com",
"RU":"connect-ws-drru.hispace.dbankcloud.ru",
"RU_back":"connect-ws-drru.hispace.dbankcloud.cn",
"SG":"connect-ws-dra.hispace.dbankcloud.cn",
"SG_back":"connect-ws-dra.hispace.dbankcloud.com"
},
"client":{
"cp_id":"30086000706675172",
"product_id":"461323198429414855",
"client_id":"1587588378314972480",
"client_secret":"4625B77E16DE4F7BDCBACE964AA0769DD9D9662483352800E7F0C4C0643765D7",
"project_id":"461323198429414855",
"app_id":"113165777",
"api_key":"DQEDAIs/5KZBeWhfKozFIM3eckc6wZ02A0CKy2qOGxjxZAI3Yaz07A3y8TuT0VlF0AuHE0Ozc07Fq76OvItq4OW/Z/nI71mQcbO8VQ==",
"package_name":"com.kaixed.kchat"
},
"oauth_client":{
"client_id":"113165777",
"client_type":1
},
"app_info":{
"app_id":"113165777",
"package_name":"com.kaixed.kchat"
},
"configuration_version":"3.0",
"appInfos":[
{
"package_name":"com.kaixed.kchat",
"client":{
"app_id":"113165777"
},
"app_info":{
"package_name":"com.kaixed.kchat",
"app_id":"113165777"
},
"oauth_client":{
"client_type":1,
"client_id":"113165777"
}
}
]
}

View File

@ -91,10 +91,7 @@ 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.scanplus)
// implementation(libs.therouter)
// ksp(libs.therouter.ksp)
}

View File

@ -77,4 +77,15 @@
-dontwarn com.yalantis.ucrop**
-keep class com.yalantis.ucrop** { *; }
-keep interface com.yalantis.ucrop** { *; }
-keep interface com.yalantis.ucrop** { *; }
# 华为统一扫描
-ignorewarnings
-keepattributes *Annotation*
-keepattributes Exceptions
-keepattributes InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
-keep class com.huawei.hianalytics.**{*;}
-keep class com.huawei.updatesdk.**{*;}
-keep class com.huawei.hms.**{*;}

BIN
app/release/app-release.apk Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,37 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.kaixed.kchat",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "0.0.01",
"outputFile": "app-release.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/app-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/app-release.dm"
]
}
],
"minSdkVersionForDexing": 28
}

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 相机权限 -->
<uses-permission android:name="android.permission.CAMERA" /> <!-- 文件读取权限Android 12及更低版本申请 -->
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" /> <!-- 读存储媒体和文件权限Android 13及更高版本申请 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-feature
android:name="android.hardware.telephony"
@ -39,6 +43,12 @@
android:theme="@style/Theme.KChatAndroid"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".ui.activity.QrCodeActivity"
android:exported="false" />
<activity
android:name=".ui.activity.ScanActivity"
android:exported="false" />
<activity
android:name=".ui.activity.setting.UpdateTelephoneActivity"
android:exported="false" />

View File

@ -1,9 +1,9 @@
package com.kaixed.kchat
import android.app.Application
import com.kaixed.kchat.data.local.box.ObjectBox
import com.kaixed.kchat.data.local.box.ObjectBox.getBoxStore
import com.kaixed.kchat.data.local.box.ObjectBox.init
import com.kaixed.kchat.utils.DensityUtil
import com.kaixed.kchat.utils.ScreenUtils
import com.tencent.mmkv.MMKV
import io.objectbox.android.Admin
@ -13,14 +13,13 @@ import io.objectbox.android.Admin
* @Date: 2024/10/24 17:04
*/
class App : Application() {
override fun onCreate() {
super.onCreate()
MMKV.initialize(this)
init(this)
ObjectBox.init(this)
Admin(getBoxStore()).start(this)
DensityUtil.init(this)
ScreenUtils.init(this)
}
}

View File

@ -5,11 +5,12 @@ import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintSet
import com.kaixed.kchat.databinding.ActivityContactUpdatesBinding
import com.kaixed.kchat.utils.DensityUtil
import com.kaixed.kchat.utils.DensityUtil.dpToPx
import com.kaixed.kchat.utils.ScreenUtils
class ContactUpdatesActivity : AppCompatActivity() {
private lateinit var binding: ActivityContactUpdatesBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
@ -24,7 +25,7 @@ class ContactUpdatesActivity : AppCompatActivity() {
ConstraintSet.START,
ConstraintSet.PARENT_ID,
ConstraintSet.START,
dpToPx( 10)
ScreenUtils.dp2px(10)
)
constraintSet.connect(
@ -32,7 +33,7 @@ class ContactUpdatesActivity : AppCompatActivity() {
ConstraintSet.START,
binding.ifvAvatar.id,
ConstraintSet.END,
dpToPx( 10)
ScreenUtils.dp2px(10)
)
constraintSet.applyTo(binding.main)
}

View File

@ -21,7 +21,8 @@ import com.kaixed.kchat.ui.base.BaseActivity
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.ScreenUtils
import com.kaixed.kchat.utils.ScreenUtils.dp2px
import com.kaixed.kchat.utils.TextUtil
import kotlin.math.max
import kotlin.math.min
@ -94,7 +95,7 @@ class FriendCircleActivity : BaseActivity<ActivityFriendCircleBinding>() {
setContent()
binding.clTool.apply {
updateLayoutParams {
height = ConstantsUtil.getStatusBarHeight() + dp2Px(45)
height = ConstantsUtil.getStatusBarHeight() + dp2px(45)
}
updatePadding(
top = ConstantsUtil.getStatusBarHeight()

View File

@ -17,8 +17,8 @@ import com.kaixed.kchat.databinding.ActivityLoginBinding
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT_RATIO
import com.kaixed.kchat.utils.Constants.MMKV_COMMON_DATA
import com.kaixed.kchat.utils.DensityUtil.dpToPx
import com.kaixed.kchat.utils.DrawableUtil.createDrawable
import com.kaixed.kchat.utils.ScreenUtils.dp2px
import com.kaixed.kchat.viewmodel.UserViewModel
import com.tencent.mmkv.MMKV
@ -171,7 +171,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
} else {
Color.parseColor("#E1E1E1")
},
dpToPx(8)
dp2px(8)
)
}
}

View File

@ -1,9 +1,12 @@
package com.kaixed.kchat.ui.activity
import android.Manifest
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
@ -12,7 +15,11 @@ 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.huawei.hms.hmsscankit.ScanUtil
import com.huawei.hms.ml.scan.HmsScan
import com.kaixed.kchat.R
import com.kaixed.kchat.data.LocalDatabase
import com.kaixed.kchat.data.event.UnreadEvent
import com.kaixed.kchat.data.local.box.ObjectBox.getBox
import com.kaixed.kchat.data.local.entity.Contact
@ -23,6 +30,7 @@ import com.kaixed.kchat.ui.fragment.DiscoveryFragment
import com.kaixed.kchat.ui.fragment.HomeFragment
import com.kaixed.kchat.ui.fragment.MineFragment
import com.kaixed.kchat.ui.widget.LoadingDialogFragment
import com.kaixed.kchat.utils.ConstantsUtil.getAvatarUrl
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.kaixed.kchat.utils.ConstantsUtil.isFirstLaunchApp
import com.kaixed.kchat.viewmodel.ContactViewModel
@ -82,6 +90,61 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
initViewPager()
setListener()
preLoad()
}
private fun preLoad() {
Glide.with(this)
.load(getAvatarUrl())
.preload()
}
// 待优化成自定义界面目前使用的华为sdk的默认界面
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode != RESULT_OK || data == null) {
return
}
val errorCode =
data.getIntExtra(ScanUtil.RESULT_CODE, ScanUtil.SUCCESS)
if (errorCode == ScanUtil.ERROR_NO_READ_PERMISSION) {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_EXTERNAL_STORAGE
)
}
if (errorCode == ScanUtil.SUCCESS) {
val hmsScan: HmsScan? = data.getParcelableExtra(ScanUtil.RESULT)
hmsScan?.let {
handleResult(it.showResult)
}
}
}
// 处理扫描结果
private fun handleResult(result: String) {
if (result.startsWith("kchat@")) {
val contactId = result.substringAfter("kchat@")
if (contactId == getUsername()) {
startActivity(Intent(this, ContactsDetailActivity::class.java).apply {
putExtra("contactId", contactId)
putExtra("isMine", true)
})
}
if (LocalDatabase.isMyFriend(contactId)) {
startActivity(Intent(this, ContactsDetailActivity::class.java).apply {
putExtra("contactId", contactId)
})
} else {
contactViewModel.searchContact(contactId)
}
} else {
Toast.makeText(this, "暂不支持识别其他内容", Toast.LENGTH_SHORT).show()
}
}
override fun onDestroy() {
@ -96,6 +159,17 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
loadingDialogFragment.dismissLoading()
}
}
contactViewModel.searchContactResult.observe(this@MainActivity) { result ->
result.onSuccess {
Intent(this, ApplyFriendsDetailActivity::class.java).apply {
putExtra("user", it)
}
}
result.onFailure {
toast("暂不支持识别其他内容")
}
}
}
override fun initData() {

View File

@ -63,7 +63,9 @@ class ProfileDetailActivity : BaseActivity<ActivityProfileDetailBinding>() {
setListener()
updateContent(username)
}
override fun initData() {
getImageLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
@ -77,10 +79,6 @@ class ProfileDetailActivity : BaseActivity<ActivityProfileDetailBinding>() {
}
}
override fun initData() {
}
private fun updateAvatar(uri: Uri) {
val filePath = getPathFromUri(uri)
@ -165,6 +163,11 @@ class ProfileDetailActivity : BaseActivity<ActivityProfileDetailBinding>() {
Intent(this, RenameActivity::class.java)
)
}
binding.ciQrcode.setOnClickListener {
startActivity(
Intent(this, QrCodeActivity::class.java)
)
}
}
override fun onResume() {

View File

@ -0,0 +1,60 @@
package com.kaixed.kchat.ui.activity
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import androidx.activity.enableEdgeToEdge
import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams
import com.bumptech.glide.Glide
import com.huawei.hms.hmsscankit.ScanUtil
import com.huawei.hms.hmsscankit.WriterException
import com.huawei.hms.ml.scan.HmsBuildBitmapOption
import com.huawei.hms.ml.scan.HmsScan
import com.kaixed.kchat.R
import com.kaixed.kchat.databinding.ActivityQrCodeBinding
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.utils.ConstantsUtil.getAvatarUrl
import com.kaixed.kchat.utils.ConstantsUtil.getNickName
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.kaixed.kchat.utils.ScreenUtils
class QrCodeActivity : BaseActivity<ActivityQrCodeBinding>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding.ivQrcode.updateLayoutParams {
width = ScreenUtils.getScreenWidth() / 2
height = width
}
}
override fun initData() {
setupQrcode()
binding.tvNickname.text = getNickName()
Glide.with(this).load(getAvatarUrl()).error(R.drawable.ic_default_avatar)
.into(binding.ifvAvatar)
}
private fun setupQrcode() {
val content = "kchat@${getUsername()}"
val type = HmsScan.QRCODE_SCAN_TYPE
val width = ScreenUtils.getScreenWidth() / 2
val bgColor = ContextCompat.getColor(this, R.color.white)
val color = ContextCompat.getColor(this, R.color.white)
val options = HmsBuildBitmapOption.Creator()
.setBitmapColor(Color.parseColor("#468CFE")).setBitmapBackgroundColor(bgColor).create()
try {
val qrBitmap = ScanUtil.buildBitmap(content, type, width, width, options)
binding.ivQrcode.setImageBitmap(qrBitmap)
} catch (e: WriterException) {
Log.e("QrCodeActivity", "WriterException: ${e.message}")
}
}
override fun inflateBinding(): ActivityQrCodeBinding {
return ActivityQrCodeBinding.inflate(layoutInflater)
}
}

View File

@ -5,10 +5,7 @@ import android.graphics.Color
import android.graphics.Typeface
import android.os.Bundle
import android.text.Editable
import android.text.TextPaint
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
@ -19,8 +16,8 @@ import com.kaixed.kchat.R
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.utils.ScreenUtils.dp2px
import com.kaixed.kchat.viewmodel.UserViewModel
class RegisterActivity : BaseActivity<ActivityRegisterBinding>() {
@ -133,7 +130,7 @@ class RegisterActivity : BaseActivity<ActivityRegisterBinding>() {
private fun setContinueButtonState(textColor: Int, backgroundColor: Int) {
binding.tvContinue.setTextColor(textColor)
binding.tvContinue.background = createDrawable(
backgroundColor, dpToPx(8)
backgroundColor, dp2px(8)
)
}

View File

@ -0,0 +1,100 @@
package com.kaixed.kchat.ui.activity
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.core.app.ActivityCompat
import com.huawei.hms.hmsscankit.ScanUtil
import com.huawei.hms.ml.scan.HmsScan
import com.huawei.hms.ml.scan.HmsScanAnalyzerOptions
import com.kaixed.kchat.databinding.ActivityScanBinding
import com.kaixed.kchat.ui.base.BaseActivity
class ScanActivity : BaseActivity<ActivityScanBinding>() {
companion object {
private const val CAMERA_REQ_CODE = 100
private const val STORAGE_REQ_CODE = 101
const val REQUEST_CODE_SCAN = 0x012
private const val PERMISSIONS_LENGTH = 1
}
private val options by lazy {
HmsScanAnalyzerOptions.Creator().setHmsScanTypes(HmsScan.ALL_SCAN_TYPE).create()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
initPermission()
scan()
}
private fun scan() {
ScanUtil.startScan(this, REQUEST_CODE_SCAN, options)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK && requestCode == REQUEST_CODE_SCAN && data != null) {
val errorCode: Int = data.getIntExtra(ScanUtil.RESULT_CODE, ScanUtil.SUCCESS)
if (errorCode == ScanUtil.ERROR_NO_READ_PERMISSION) {
requestPermissions(
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
STORAGE_REQ_CODE
)
}
if (errorCode == ScanUtil.SUCCESS) {
val hmsScan: HmsScan? = data.getParcelableExtra(ScanUtil.RESULT)
hmsScan?.let {
handleResult(it.showResult)
}
}
}
}
private fun handleResult(result: String) {
if (result.startsWith("kchat@")) {
val contactId = result.substringAfter("kchat@")
startActivity(Intent(this, ContactsDetailActivity::class.java).apply {
putExtra("contactId", contactId)
})
finish()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == CAMERA_REQ_CODE && grantResults.size == PERMISSIONS_LENGTH && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
scan()
}
if (requestCode == STORAGE_REQ_CODE && grantResults.size == PERMISSIONS_LENGTH && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
scan()
}
}
override fun initData() {
}
private fun initPermission() {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE),
CAMERA_REQ_CODE
)
}
override fun inflateBinding(): ActivityScanBinding {
return ActivityScanBinding.inflate(layoutInflater)
}
}

View File

@ -95,7 +95,7 @@ class ChatAdapter(
holder.bindData(singleMessage)
}
}
(holder as? HasTimer)?.let {
(holder as? IHasTimer)?.let {
changeTimerVisibility(it.getTimerView(), singleMessage)
}
handleLongClick(holder, position, singleMessage)
@ -124,7 +124,7 @@ class ChatAdapter(
MessagesManager.deleteMessage(message.msgLocalId)
}
interface HasTimer {
interface IHasTimer {
fun getTimerView(): TextView
}
@ -140,7 +140,7 @@ class ChatAdapter(
}
class CustomViewHolder(val binding: ChatRecycleItemCustomNormalBinding) :
RecyclerView.ViewHolder(binding.root), HasTimer {
RecyclerView.ViewHolder(binding.root), IHasTimer {
fun bindData(message: Messages) {
val sender = message.senderId == getUsername()
@ -168,7 +168,7 @@ class ChatAdapter(
}
class ImageViewHolder(val binding: ChatRecycleItemImageNormalBinding) :
RecyclerView.ViewHolder(binding.root), HasTimer {
RecyclerView.ViewHolder(binding.root), IHasTimer {
fun bindData(message: Messages) {
val sender = message.senderId == getUsername()
val avatarUrl = if (sender) ConstantsUtil.getAvatarUrl() else message.avatarUrl

View File

@ -7,8 +7,8 @@ import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.kaixed.kchat.data.model.dynamic.FriendCircleItem
import com.kaixed.kchat.databinding.ItemFriendCircleBinding
import com.kaixed.kchat.utils.DensityUtil
import com.kaixed.kchat.utils.DensityUtil.dp2Px
import com.kaixed.kchat.utils.ScreenUtils.dp2px
import com.kaixed.kchat.utils.ScreenUtils.getScreenWidth
import com.kaixed.kchat.utils.TextUtil
/**
@ -59,13 +59,13 @@ class FriendCircleAdapter(val context: Context, val items: List<FriendCircleItem
}
private fun getGridViewHeight(size: Int): Int {
val width = DensityUtil.getScreenWidth()
val width = getScreenWidth()
return when (size) {
1, 2 -> (width - dp2Px(100)) / 2
3 -> (width - dp2Px(105)) / 3
4 -> (width - dp2Px(100)) + dp2Px(5)
5, 6 -> 2 * (width - dp2Px(100)) / 3 + dp2Px(5)
else -> 3 * (width - dp2Px(105)) / 3 + 2 * dp2Px(5)
1, 2 -> (width - dp2px(100)) / 2
3 -> (width - dp2px(105)) / 3
4 -> (width - dp2px(100)) + dp2px(5)
5, 6 -> 2 * (width - dp2px(100)) / 3 + dp2px(5)
else -> 3 * (width - dp2px(105)) / 3 + 2 * dp2px(5)
}
}
}

View File

@ -1,5 +1,6 @@
package com.kaixed.kchat.ui.fragment
import android.Manifest
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ObjectAnimator
@ -8,6 +9,7 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Build
@ -19,32 +21,32 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity.BIND_AUTO_CREATE
import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.LinearLayoutManager
import com.huawei.hms.hmsscankit.ScanUtil
import com.huawei.hms.ml.scan.HmsScan
import com.huawei.hms.ml.scan.HmsScanAnalyzerOptions
import com.kaixed.kchat.R
import com.kaixed.kchat.data.local.entity.Conversation
import com.kaixed.kchat.data.model.HomeItem
import com.kaixed.kchat.databinding.FragmentHomeBinding
import com.kaixed.kchat.manager.ConversationManager
import com.kaixed.kchat.manager.MessagesManager
import com.kaixed.kchat.service.WebSocketService
import com.kaixed.kchat.service.WebSocketService.LocalBinder
import com.kaixed.kchat.ui.activity.AddFriendsActivity
import com.kaixed.kchat.ui.activity.FriendCircleActivity
import com.kaixed.kchat.ui.activity.MainActivity
import com.kaixed.kchat.ui.activity.SearchActivity
import com.kaixed.kchat.ui.activity.TestActivity
import com.kaixed.kchat.ui.adapter.ConversationAdapter
import com.kaixed.kchat.ui.adapter.MyGridAdapter
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 org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnItemListener,
OnDialogFragmentClickListener {
@ -123,11 +125,52 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(), OnItemListener,
(activity as MainActivity).updatePosition(1)
}
HomeItems.SCAN -> {
scan()
}
else -> Toast.makeText(requireContext(), "暂未实现", Toast.LENGTH_SHORT).show()
}
}
}
companion object {
private const val REQUEST_CODE_SCAN = 0x012
}
private val options by lazy {
HmsScanAnalyzerOptions.Creator().setHmsScanTypes(HmsScan.QRCODE_SCAN_TYPE).create()
}
// 用于权限请求
private val requestPermissionsLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
val cameraPermissionGranted = permissions[Manifest.permission.CAMERA] == true
if (cameraPermissionGranted) {
scan()
} else {
Toast.makeText(requireContext(), "没有权限,无法扫描", Toast.LENGTH_SHORT).show()
}
}
private fun scan() {
when {
ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED -> {
ScanUtil.startScan(activity, REQUEST_CODE_SCAN, options)
}
else -> {
requestPermissionsLauncher.launch(
arrayOf(Manifest.permission.CAMERA)
)
}
}
}
private fun setOnClick() {
binding.ivSearch.setOnClickListener {
val intent = Intent(context, SearchActivity::class.java)

View File

@ -18,6 +18,8 @@ import com.kaixed.kchat.utils.ConstantsUtil.getUsername
class MineFragment : BaseFragment<FragmentMineBinding>() {
private var isAdded = false
override fun inflateBinding(
inflater: LayoutInflater,
container: ViewGroup?
@ -28,6 +30,17 @@ class MineFragment : BaseFragment<FragmentMineBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.ifvAvatar.viewTreeObserver.addOnGlobalLayoutListener {
context?.let {
isAdded = true
Glide.with(it)
.load(getAvatarUrl())
.placeholder(R.drawable.ic_default_avatar)
.error(R.drawable.ic_default_avatar)
.into(binding.ifvAvatar)
}
}
binding.clProfile.setOnClickListener {
startActivity(Intent(requireContext(), ProfileDetailActivity::class.java))
}
@ -49,10 +62,12 @@ class MineFragment : BaseFragment<FragmentMineBinding>() {
binding.tvNickname.text = nickname
val kid = "星联号: ${getUsername()}"
binding.tvId.text = kid
Glide.with(requireContext()).load(getAvatarUrl())
.placeholder(R.drawable.ic_default_avatar)
.error(R.drawable.ic_default_avatar)
.into(binding.ifvAvatar)
if (!isAdded) {
Glide.with(requireContext())
.load(getAvatarUrl())
.placeholder(R.drawable.ic_default_avatar)
.error(R.drawable.ic_default_avatar)
.into(binding.ifvAvatar)
}
}
}

View File

@ -10,7 +10,7 @@ import androidx.annotation.RequiresApi
import androidx.appcompat.widget.AppCompatEditText
import androidx.core.content.ContextCompat
import com.kaixed.kchat.R
import com.kaixed.kchat.utils.DensityUtil.dp2px
import com.kaixed.kchat.utils.ScreenUtils
/**
* 自定义 EditText 控件用于绘制下划线
@ -29,7 +29,7 @@ class CustomEditText @JvmOverloads constructor(
private val normalLineColor: Int by lazy { Color.parseColor("#E0E0E0") }
// 下划线高度
private val lineHeight: Float by lazy { dp2px(1).toFloat() }
private val lineHeight: Float by lazy { ScreenUtils.dp2px(1).toFloat() }
// 画笔,用于绘制下划线
private var paint: Paint = Paint().apply {

View File

@ -37,7 +37,6 @@ class CustomTitleBar @JvmOverloads constructor(
val titleIcon = typedArray.getResourceId(R.styleable.CustomTitleBar_titleIcon, -1)
val btnName = typedArray.getString(R.styleable.CustomTitleBar_btnName) ?: ""
if (titleIcon != -1) {
binding.ivSetting.visibility = View.VISIBLE
binding.ivSetting.setImageResource(titleIcon)

View File

@ -8,7 +8,7 @@ import android.view.Window
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import com.kaixed.kchat.databinding.DialogLoadingBinding
import com.kaixed.kchat.utils.DensityUtil.dpToPx
import com.kaixed.kchat.utils.ScreenUtils.dp2px
class LoadingDialogFragment : DialogFragment() {
@ -42,8 +42,8 @@ class LoadingDialogFragment : DialogFragment() {
// 设置对话框的大小
val params = binding.root.layoutParams
params.height = dpToPx(120)
params.width = dpToPx(120)
params.height = dp2px(120)
params.width = dp2px(120)
binding.root.layoutParams = params
return dialog

View File

@ -1,45 +0,0 @@
package com.kaixed.kchat.utils
import android.content.Context
import android.util.TypedValue
/**
* @Author: kaixed
* @Date: 2024/10/24 22:07
*/
object DensityUtil {
private lateinit var appContext: Context
fun init(context: Context) {
appContext = context.applicationContext
}
fun getScreenWidth(): Int = appContext.resources.displayMetrics.widthPixels
fun getScreenHeight(): Int = appContext.resources.displayMetrics.heightPixels
fun dpToPx(dp: Int): Int =
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp.toFloat(),
appContext.resources.displayMetrics
).toInt()
fun dp2Px(dp: Int): Int =
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp.toFloat(),
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

@ -9,9 +9,8 @@ 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
import com.kaixed.kchat.utils.ScreenUtils.dp2px
/**
* @Author: kaixed
@ -30,8 +29,8 @@ object PopWindowUtil {
}
// 获取屏幕的总宽度
val screenWidth = context.resources.displayMetrics.widthPixels
val screenHeight = context.resources.displayMetrics.heightPixels
val screenWidth = ScreenUtils.getScreenWidth()
val screenHeight = ScreenUtils.getScreenHeight()
val location = IntArray(2)
parentView.getLocationOnScreen(location)
@ -76,19 +75,18 @@ object PopWindowUtil {
xOffset = if (rightEnough)
(parentWidth - popupWidth) / 2
else
parentWidth - popupWidth + dpToPx(45)
parentWidth - popupWidth + dp2px(45)
marginStart = popupWidth - (dpToPx(55) + parentWidth / 2)
marginStart = popupWidth - (dp2px(55) + parentWidth / 2)
} else {
leftEnough = (popupWidth / 2) < distanceToLeftEdge
xOffset = if (leftEnough) parentWidth - popupWidth
else -dpToPx(45)
else -dp2px(45)
marginStart = dpToPx(35) + parentWidth / 2
marginStart = dp2px(35) + parentWidth / 2
}
binding.ivArrowDown.layoutParams =
(binding.ivArrowUp.layoutParams as LinearLayout.LayoutParams).apply {
val enoughSpace = if (isMine) rightEnough else leftEnough

View File

@ -0,0 +1,27 @@
package com.kaixed.kchat.utils
import android.content.Context
import android.util.TypedValue
/**
* @Author: kaixed
* @Date: 2024/12/29 22:49
*/
object ScreenUtils {
private lateinit var appContext: Context
fun init(context: Context) {
appContext = context
}
fun getScreenWidth(): Int = appContext.resources.displayMetrics.widthPixels
fun getScreenHeight(): Int = appContext.resources.displayMetrics.heightPixels
fun dp2px(dp: Int): Int =
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp.toFloat(),
appContext.resources.displayMetrics
).toInt()
}

View File

@ -1,5 +1,6 @@
package com.kaixed.kchat.utils
import com.kaixed.kchat.utils.ScreenUtils.getScreenWidth
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
@ -34,7 +35,7 @@ object TextUtil {
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 screenWidth = getScreenWidth() / 2
// 计算宽高比
val ratio = width.toFloat() / screenWidth.toFloat()

View File

@ -1,41 +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
import android.util.Log
/**
* @Author: kaixed
* @Date: 2024/10/28 15:57
*/
class VerticalAlignImageSpan : ImageSpan {
constructor(d: Drawable?) : super(d!!)
constructor(d: Drawable?, verticalAlignment: Int) : super(d!!, verticalAlignment)
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 fontMetrics = paint.fontMetrics
val transY =
((y + fontMetrics.ascent + y + fontMetrics.descent) / 2 - (drawable.bounds.bottom + drawable
.bounds.top) / 2).toInt()
Log.d("VerticalAlignImageSpan", "transY-> $transY")
canvas.save()
canvas.translate(x, transY.toFloat())
drawable.draw(canvas)
canvas.restore()
}
}

View File

@ -6,7 +6,6 @@ import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.kaixed.kchat.data.local.entity.Messages
import com.kaixed.kchat.utils.DensityUtil.dp2Px
/**
* @Author: kaixed
@ -40,66 +39,6 @@ object ViewUtil {
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(
avatarView.id, ConstraintSet.END,
parentView.id, ConstraintSet.END,
margin
)
connect(
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(
avatarView.id, ConstraintSet.START,
parentView.id, ConstraintSet.START,
margin
)
connect(
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(
tvTimer: TextView,
singleMessage: Messages

View File

@ -57,6 +57,7 @@
app:itemName="星联号" />
<com.kaixed.kchat.ui.widget.CustomItem
android:id="@+id/ci_qrcode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:iconSize="21dp"

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ui.activity.QrCodeActivity">
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:titleIcon="@drawable/ic_more" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginBottom="30dp"
app:layout_constraintBottom_toTopOf="@id/iv_qrcode"
app:layout_constraintStart_toStartOf="@id/iv_qrcode"
app:roundPercent="0.2" />
<TextView
android:id="@+id/tv_nickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@id/tv_address"
app:layout_constraintStart_toEndOf="@id/ifv_avatar"
app:layout_constraintTop_toTopOf="@id/ifv_avatar" />
<TextView
android:id="@+id/tv_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="中国大陆"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@id/ifv_avatar"
app:layout_constraintStart_toEndOf="@id/ifv_avatar"
app:layout_constraintStart_toStartOf="@id/tv_nickname"
app:layout_constraintTop_toBottomOf="@id/tv_nickname" />
<ImageView
android:id="@+id/iv_qrcode"
android:layout_width="200dp"
android:layout_height="200dp"
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_tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:text="扫一扫上面的二维码图案,加我为好友"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="@id/iv_qrcode"
app:layout_constraintStart_toStartOf="@id/iv_qrcode"
app:layout_constraintTop_toBottomOf="@id/iv_qrcode" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.activity.ScanActivity">
<com.kaixed.kchat.ui.widget.CustomTitleBar
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<FrameLayout
android:id="@+id/fl_scan_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

View File

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white">
<TextView
android:id="@+id/tv_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:paddingVertical="15dp"
android:text="账号与安全"
android:textColor="@color/black"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/view"
app:layout_goneMarginTop="0dp" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:src="@drawable/icon_arrow_right"
app:layout_constraintBottom_toBottomOf="@id/tv_item"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_item" />
<View
android:id="@+id/view_item_decoration"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="#E5E5E5"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/tv_item" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -15,5 +15,7 @@ buildscript {
}
dependencies {
classpath(libs.objectbox.gradle.plugin)
// classpath("com.android.tools.build:gradle:8.6.0")
classpath(libs.agcp)
}
}

View File

@ -1,4 +1,5 @@
[versions]
agcp = "1.9.1.301"
agp = "8.3.2"
converterGson = "2.11.0"
emoji2 = "1.5.0"
@ -23,6 +24,7 @@ pictureselector = "v3.11.2"
pinyin4j = "2.5.1"
preference = "1.2.1"
retrofit = "2.11.0"
scanplus = "2.12.0.301"
spannable = "1.2.7"
therouter = "1.2.2"
window = "1.3.0"
@ -30,9 +32,9 @@ window = "1.3.0"
kotlin = "1.9.23"
coreKtx = "1.13.1"
objectbox = "4.0.2"
navigationFragment = "2.8.4"
[libraries]
agcp = { module = "com.huawei.agconnect:agcp", version.ref = "agcp" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
compress = { module = "io.github.lucksiege:compress", version.ref = "pictureselector" }
eventbus = { module = "org.greenrobot:eventbus", version.ref = "eventbus" }
@ -44,6 +46,7 @@ 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" }
scanplus = { module = "com.huawei.hms:scanplus", version.ref = "scanplus" }
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" }
@ -67,7 +70,6 @@ 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" }

View File

@ -1,6 +1,6 @@
#Fri May 03 23:21:48 CST 2024
#Mon Dec 30 11:12:59 CST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

BIN
kchat.jks Normal file

Binary file not shown.

View File

@ -9,6 +9,7 @@ pluginManagement {
}
mavenCentral()
gradlePluginPortal()
maven(url = "https://developer.huawei.com/repo/")
}
}
dependencyResolutionManagement {
@ -16,9 +17,8 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven {
url = uri("https://jitpack.io")
}
maven { url = uri("https://jitpack.io") }
maven(url = "https://developer.huawei.com/repo/")
}
}