消息页面优化

This commit is contained in:
itgaojian163 2024-11-02 18:39:14 +08:00
parent 9a4e454ac5
commit 7d7d7ccea5
11 changed files with 342 additions and 244 deletions

View File

@ -29,8 +29,7 @@
android:exported="false" /> android:exported="false" />
<activity <activity
android:name=".page.activity.ChatActivity" android:name=".page.activity.ChatActivity"
android:exported="false" android:exported="false" />
android:windowSoftInputMode="adjustPan|stateAlwaysHidden" />
<activity <activity
android:name=".page.activity.SplashActivity" android:name=".page.activity.SplashActivity"
android:exported="true" android:exported="true"

View File

@ -2,6 +2,7 @@ package com.tenlionsoft.aimz_k.net
import com.tenlionsoft.aimz_k.model.BaseSuccessBean import com.tenlionsoft.aimz_k.model.BaseSuccessBean
import com.tenlionsoft.baselib.model.VersionBean import com.tenlionsoft.baselib.model.VersionBean
import okhttp3.MultipartBody
import okhttp3.RequestBody import okhttp3.RequestBody
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
@ -52,7 +53,7 @@ interface UserApi {
@Headers("token:need") @Headers("token:need")
@Multipart @Multipart
@POST("app/file/uploadimage") @POST("app/file/uploadimage")
suspend fun doUploadImage(@Part file: Part): BaseSuccessBean suspend fun doUploadImage(@Part file: MultipartBody.Part): BaseSuccessBean
/** /**
@ -66,5 +67,5 @@ interface UserApi {
@Headers("token:need") @Headers("token:need")
@Multipart @Multipart
@POST("app/file/uploadvideo") @POST("app/file/uploadvideo")
fun doUploadVideo(@Part file: Part): BaseSuccessBean fun doUploadVideo(@Part file: MultipartBody.Part): BaseSuccessBean
} }

View File

@ -6,6 +6,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.util.Log import android.util.Log
import android.view.View
import android.view.WindowManager import android.view.WindowManager
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
@ -23,6 +24,7 @@ import com.tenlionsoft.baselib.utils.SpUtils
import com.tenlionsoft.baselib.widget.SoftKeyBoardListener import com.tenlionsoft.baselib.widget.SoftKeyBoardListener
import com.tenlionsoft.baselib.widget.wheel.WheelView import com.tenlionsoft.baselib.widget.wheel.WheelView
/** /**
* 聊天页面 * 聊天页面
*/ */
@ -31,7 +33,7 @@ class ChatActivity : BaseActivity() {
private lateinit var mLocalReceiver: LocalReceiver private lateinit var mLocalReceiver: LocalReceiver
var chatPageViewModel: ChatPageViewModel? = null var chatPageViewModel: ChatPageViewModel? = null
private val filePicker = FilePicker.getInstance(this) private val filePicker = FilePicker.getInstance(this)
private var mContentHeight: Int = 0
override fun bindView() { override fun bindView() {
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_chat); mBinding = DataBindingUtil.setContentView(this, R.layout.activity_chat);
@ -48,22 +50,19 @@ class ChatActivity : BaseActivity() {
chatPageViewModel = ViewModelProvider(this, object : ViewModelProvider.Factory { chatPageViewModel = ViewModelProvider(this, object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ChatPageViewModel( return ChatPageViewModel(
fromId!!, fromId!!, SpUtils.getId(), this@ChatActivity.applicationContext
SpUtils.getId(),
this@ChatActivity.applicationContext
) as T ) as T
} }
})[ChatPageViewModel::class.java] })[ChatPageViewModel::class.java]
mBinding.ivBack.setOnClickListener { finish(); } mBinding.ivBack.setOnClickListener { finish(); }
mBinding.srlChats.setEnableRefresh(false)
mBinding.srlChats.setEnableLoadMore(false)
mBinding.viewModel = chatPageViewModel mBinding.viewModel = chatPageViewModel
mBinding.lifecycleOwner = this mBinding.lifecycleOwner = this
//选择emoji //选择emoji
mBinding.rpvEmoji.setOnEmojiPickedListener { mBinding.rpvEmoji.setOnEmojiPickedListener {
mBinding.etMsg.append(it.emoji) mBinding.etMsg.append(it.emoji)
} }
//显示/隐藏软键盘 //显示/隐藏软键盘
// chatPageViewModel!!.showSoftKeyboard.observe(this) { // chatPageViewModel!!.showSoftKeyboard.observe(this) {
// if (it) { // if (it) {
@ -78,13 +77,13 @@ class ChatActivity : BaseActivity() {
// hideSoftKeyboard() // hideSoftKeyboard()
// } // }
// } // }
mBinding.rlContent.setOnClickListener { // mBinding.rlContent.setOnClickListener {
Log.e("ChatActivity", "bindView: 点击") // Log.e("ChatActivity", "bindView: 点击")
chatPageViewModel!!.showChooseLayout.value = false // chatPageViewModel!!.showChooseLayout.value = false
chatPageViewModel!!.showEmojiLayout.value = false // chatPageViewModel!!.showEmojiLayout.value = false
chatPageViewModel!!.showReplyLayout.value = false // chatPageViewModel!!.showReplyLayout.value = false
hideSoftKeyboard() // hideSoftKeyboard()
} // }
mBinding.wvView.setTextSize(14F, isSp = true) mBinding.wvView.setTextSize(14F, isSp = true)
mBinding.wvView.setAutoFitTextSize(true) mBinding.wvView.setAutoFitTextSize(true)
mBinding.wvView.setOnItemSelectedListener(object : WheelView.OnItemSelectedListener { mBinding.wvView.setOnItemSelectedListener(object : WheelView.OnItemSelectedListener {
@ -122,19 +121,54 @@ class ChatActivity : BaseActivity() {
} }
} }
chatPageViewModel!!.scrollListToBottom.observe(this) { chatPageViewModel!!.scrollListToBottom.observe(this) {
Log.e("TAG", "bindView:滚动 ${it} ")
if (it) { if (it) {
Log.e("ChatActivity", "bindView: ${Thread.currentThread().name}")
val layoutManager = mBinding.rlvChats.layoutManager as LinearLayoutManager val layoutManager = mBinding.rlvChats.layoutManager as LinearLayoutManager
val itemCount = layoutManager.itemCount val itemCount = layoutManager.itemCount
mBinding.rlvChats.scrollToPosition(itemCount - 1)//滚动到底部 //滚动到底部
mBinding.rlvChats.postDelayed({
layoutManager.scrollToPositionWithOffset(
itemCount - 1,
0
)
}, 50)
chatPageViewModel!!.scrollListToBottom.value = false chatPageViewModel!!.scrollListToBottom.value = false
} }
} }
mBinding.llBottomLayout.visibility = View.VISIBLE
mBinding.rlBottom.visibility = View.GONE
mBinding.rlvChats.viewTreeObserver.addOnGlobalLayoutListener {
mContentHeight = mBinding.rlvChats.height
}
SoftKeyBoardListener().setChangeListener(this@ChatActivity, SoftKeyBoardListener().setChangeListener(this@ChatActivity,
{ _ -> Log.e("ChatActivity", "bindView: 显示") }, { h ->
{ _ -> Log.e("ChatActivity", "bindView: 隐藏") }) mBinding.llBottomLayout.visibility = View.VISIBLE
mBinding.rlBottom.visibility = View.VISIBLE
//重置layout高度
val layoutParams = mBinding.rlvChats.layoutParams
layoutParams.height = mContentHeight - h
mBinding.rlvChats.layoutParams = layoutParams
val layoutManager = mBinding.rlvChats.layoutManager as LinearLayoutManager
val itemCount = layoutManager.itemCount
//滚动到底部
mBinding.rlvChats.postDelayed({
layoutManager.scrollToPositionWithOffset(
itemCount - 1,
0
)
}, 100)
},
{ _ ->
val layoutParams = mBinding.rlvChats.layoutParams
layoutParams.height = mContentHeight
mBinding.rlvChats.layoutParams = layoutParams
mBinding.llBottomLayout.visibility = View.VISIBLE
mBinding.rlBottom.visibility = View.GONE
})
registerLocalReceiver() registerLocalReceiver()
} }

View File

@ -40,7 +40,13 @@ class LoginActivity : BaseActivity() {
} }
loginPageViewModel.isLoginSuccess.observe(this) { loginPageViewModel.isLoginSuccess.observe(this) {
if (it) { if (it) {
startActivity(Intent(this@LoginActivity, MainActivity::class.java)) // startActivity(Intent(this@LoginActivity, MainActivity::class.java))
startActivity(
Intent(
this@LoginActivity,
ChatActivity::class.java
).putExtra("fromId", "2")
)
finish() finish()
} }
} }

View File

@ -16,6 +16,7 @@ import com.tenlionsoft.aimz_k.socket.WsManager
import com.tenlionsoft.baselib.contacts.NetConfig import com.tenlionsoft.baselib.contacts.NetConfig
import com.tenlionsoft.baselib.contacts.ProjectConfig import com.tenlionsoft.baselib.contacts.ProjectConfig
import com.tenlionsoft.baselib.utils.SpUtils import com.tenlionsoft.baselib.utils.SpUtils
import com.tenlionsoft.baselib.utils.ToastUtils
/** /**
@ -99,8 +100,11 @@ class SocketService : Service(), WsManager.MsgCallBack {
super.onDestroy() super.onDestroy()
} }
//发送消息
inner class LocalReceiver : BroadcastReceiver() { inner class LocalReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
val isConnect = mWsManager?.isNetworkConnected(context)
if (true == isConnect) {
when (intent?.action) { when (intent?.action) {
ProjectConfig.A_S_MSG_SEND -> { ProjectConfig.A_S_MSG_SEND -> {
val msgConvertBean = intent.getSerializableExtra("msgBean") val msgConvertBean = intent.getSerializableExtra("msgBean")
@ -108,6 +112,10 @@ class SocketService : Service(), WsManager.MsgCallBack {
mWsManager?.sendMessage(msg) mWsManager?.sendMessage(msg)
}//发送消息 }//发送消息
} }
} else {
ToastUtils.error("请检查网络")
}
} }
} }

View File

@ -265,7 +265,7 @@ class WsManager private constructor(val builder: Builder) : IWsManager {
} }
//检查网络是否连接 //检查网络是否连接
private fun isNetworkConnected(context: Context?): Boolean { public fun isNetworkConnected(context: Context?): Boolean {
if (context != null) { if (context != null) {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val activeNetwork: Network = cm.activeNetwork ?: return false val activeNetwork: Network = cm.activeNetwork ?: return false

View File

@ -20,13 +20,20 @@ import com.tenlionsoft.aimz_k.model.MsgConvertBean
import com.tenlionsoft.aimz_k.model.PickerType import com.tenlionsoft.aimz_k.model.PickerType
import com.tenlionsoft.aimz_k.model.Receiver import com.tenlionsoft.aimz_k.model.Receiver
import com.tenlionsoft.aimz_k.model.Sender import com.tenlionsoft.aimz_k.model.Sender
import com.tenlionsoft.aimz_k.net.UserApi
import com.tenlionsoft.baselib.base.BaseViewModel import com.tenlionsoft.baselib.base.BaseViewModel
import com.tenlionsoft.baselib.contacts.ProjectConfig import com.tenlionsoft.baselib.contacts.ProjectConfig
import com.tenlionsoft.baselib.net.RetrofitClient
import com.tenlionsoft.baselib.utils.SpUtils import com.tenlionsoft.baselib.utils.SpUtils
import com.tenlionsoft.baselib.utils.TimeUtils import com.tenlionsoft.baselib.utils.TimeUtils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody
class ChatPageViewModel( class ChatPageViewModel(
private val fromId: String, private val fromId: String,
@ -41,6 +48,8 @@ class ChatPageViewModel(
val chooseType = MutableLiveData<PickerType>()//选择文件类型 val chooseType = MutableLiveData<PickerType>()//选择文件类型
val scrollListToBottom = MutableLiveData<Boolean>(false) val scrollListToBottom = MutableLiveData<Boolean>(false)
private val _msgList = MutableLiveData<List<MsgBean>>() private val _msgList = MutableLiveData<List<MsgBean>>()
private val retrofitClient = RetrofitClient.getInstance(context)
private val netApi = retrofitClient.create(UserApi::class.java)
var adapter: ChatMsgAdapter = ChatMsgAdapter(_msgList.value ?: emptyList(), this) var adapter: ChatMsgAdapter = ChatMsgAdapter(_msgList.value ?: emptyList(), this)
private val mGson: Gson = Gson() private val mGson: Gson = Gson()
@ -121,7 +130,44 @@ class ChatPageViewModel(
* 上传图片 * 上传图片
*/ */
fun uploadImages(list: List<ImageMeta?>) { fun uploadImages(list: List<ImageMeta?>) {
if (list.isEmpty()) {
return
}
viewModelScope.launch {
doUploadImgs(list)
}
}
private suspend fun doUploadImgs(list: List<ImageMeta?>) {
try {
for (item in list) {
if (item != null) {
val requestFile: RequestBody =
item.file!!.asRequestBody("multipart/form-data".toMediaTypeOrNull())
val body: MultipartBody.Part =
MultipartBody.Part.createFormData(
"audio",
item.file!!.getName(),
requestFile
)
val bean = retrofitClient.makeApiCall {
netApi.doUploadImage(body)
}
if (bean.code == 200) {
//上传成功
//TODO 构建消息实体
//TODO 发送消息
//
} else {
//TODO 上传失败
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
} }
/** /**

View File

@ -24,8 +24,8 @@ class LoginPageViewModel : BaseViewModel() {
var userPwd: String = "admin" var userPwd: String = "admin"
val isLoginSuccess = MutableLiveData<Boolean>() val isLoginSuccess = MutableLiveData<Boolean>()
private val gson = Gson() private val gson = Gson()
private val userApi = RetrofitClient.getInstance(App.context) private val retrofitClient = RetrofitClient.getInstance(App.context)
.create(UserApi::class.java) private val userApi = retrofitClient.create(UserApi::class.java)
fun onUserNameChange(s: CharSequence, start: Int, before: Int, count: Int) { fun onUserNameChange(s: CharSequence, start: Int, before: Int, count: Int) {
@ -37,6 +37,7 @@ class LoginPageViewModel : BaseViewModel() {
} }
fun doLogin() { fun doLogin() {
isLoginSuccess.value = true
val isLegal = checkParams(); val isLegal = checkParams();
if (isLegal) { if (isLegal) {
viewModelScope.launch { viewModelScope.launch {
@ -54,12 +55,16 @@ class LoginPageViewModel : BaseViewModel() {
val userBody = LoginUser( val userBody = LoginUser(
userPwd, userPwd,
userName userName
); )
Log.e("LoginPageViewModel", "登陆前: Thread${Thread.currentThread().name}")
val loginUserStr = gson.toJson(userBody) val loginUserStr = gson.toJson(userBody)
val body = val body =
loginUserStr.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) loginUserStr.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
val user = userApi.doLogin(body);
val user = retrofitClient.makeApiCall {
Log.e("TAG", "login:${Thread.currentThread().name} ")
userApi.doLogin(body)
}
if (user.code == 200) { if (user.code == 200) {
//登陆成功 //登陆成功
val token = JwtUtils.parseToken(user.data) val token = JwtUtils.parseToken(user.data)
@ -69,12 +74,7 @@ class LoginPageViewModel : BaseViewModel() {
} else { } else {
//登陆成功 //登陆成功
//解析Token //解析Token
Log.e("LoginPageViewModel", "login: ${token}")
val appTokenUser = gson.fromJson(token, AppTokenUser::class.java) val appTokenUser = gson.fromJson(token, AppTokenUser::class.java)
Log.e(
"LoginPageViewModel",
"Thread ${Thread.currentThread().name} login: UserId${appTokenUser.user.id}"
)
SpUtils.putPassword(userPwd) SpUtils.putPassword(userPwd)
SpUtils.putAvatar(appTokenUser.user.avatar) SpUtils.putAvatar(appTokenUser.user.avatar)
SpUtils.putNickName(appTokenUser.user.nickname) SpUtils.putNickName(appTokenUser.user.nickname)
@ -93,7 +93,6 @@ class LoginPageViewModel : BaseViewModel() {
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
Log.e("LoginPageViewModel", "login: ${e.toString()}")
loginFailed() loginFailed()
ExParse.parse(e) ExParse.parse(e)
} }

View File

@ -11,7 +11,7 @@
type="com.tenlionsoft.aimz_k.viewmodel.ChatPageViewModel" /> type="com.tenlionsoft.aimz_k.viewmodel.ChatPageViewModel" />
</data> </data>
<RelativeLayout <LinearLayout
android:id="@+id/main" android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -23,11 +23,10 @@
<LinearLayout <LinearLayout
android:id="@+id/ll_title" android:id="@+id/ll_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="48dp"
android:orientation="vertical"> android:orientation="vertical">
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="48dp"
android:gravity="center_vertical" android:gravity="center_vertical"
@ -61,31 +60,28 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/rlContent"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_below="@id/ll_title" android:layout_below="@id/ll_title"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:orientation="vertical"> android:orientation="vertical">
<com.scwang.smart.refresh.layout.SmartRefreshLayout
android:id="@+id/srl_chats"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rlv_chats" android:id="@+id/rlv_chats"
setLinearLayoutAdapter="@{viewModel.adapter}" setLinearLayoutAdapter="@{viewModel.adapter}"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="0dp"
android:layout_weight="1"
android:background="@color/green"
tools:listitem="@layout/item_msg_my" /> tools:listitem="@layout/item_msg_my" />
</RelativeLayout>
</com.scwang.smart.refresh.layout.SmartRefreshLayout>
<LinearLayout
android:id="@+id/ll_bottom_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout <LinearLayout
android:id="@+id/ll_input_box" android:id="@+id/ll_input_box"
@ -163,7 +159,7 @@
<FrameLayout <FrameLayout
android:id="@+id/rl_bottom" android:id="@+id/rl_bottom"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="300dp">
<androidx.emoji2.emojipicker.EmojiPickerView <androidx.emoji2.emojipicker.EmojiPickerView
android:id="@+id/rpv_emoji" android:id="@+id/rpv_emoji"
@ -268,5 +264,6 @@
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>
</RelativeLayout> </LinearLayout>
</LinearLayout>
</layout> </layout>

View File

@ -2,6 +2,8 @@ package com.tenlionsoft.baselib.net
import android.content.Context import android.content.Context
import com.tenlionsoft.baselib.contacts.NetConfig import com.tenlionsoft.baselib.contacts.NetConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
@ -47,6 +49,12 @@ open class RetrofitClient {
return builder.build(); return builder.build();
} }
suspend fun <T> makeApiCall(apiCall: suspend () -> T): T {
return withContext(Dispatchers.IO) {
apiCall()
}
}
/** /**
* 创建 * 创建
*/ */

View File

@ -54,9 +54,9 @@ class ChatUiHelper {
mEditText = editText mEditText = editText
mEditText!!.requestFocus() mEditText!!.requestFocus()
mEditText!!.setOnTouchListener { v: View?, event: MotionEvent -> mEditText!!.setOnTouchListener { v: View?, event: MotionEvent ->
if (event.action == MotionEvent.ACTION_UP && mBottomLayout!!.isShown) { if (event.action == MotionEvent.ACTION_UP) {
lockContentHeight() //显示软件盘时,锁定内容高度,防止跳闪。 lockContentHeight() //显示软件盘时,锁定内容高度,防止跳闪。
hideBottomLayout(true) //隐藏表情布局,显示软件盘 // hideBottomLayout(true) //隐藏表情布局,显示软件盘
// mIvEmoji.setImageResource(R.drawable.ic_emoji); // mIvEmoji.setImageResource(R.drawable.ic_emoji);
//软件盘显示后,释放内容高度 //软件盘显示后,释放内容高度
mEditText!!.postDelayed( mEditText!!.postDelayed(
@ -67,23 +67,23 @@ class ChatUiHelper {
false false
} }
mEditText!!.addTextChangedListener(object : TextWatcher { // mEditText!!.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) { // override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
} // }
//
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) { // override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
if (mEditText!!.text.toString().trim { it <= ' ' }.isNotEmpty()) { // if (mEditText!!.text.toString().trim { it <= ' ' }.isNotEmpty()) {
mIvSend?.visibility = View.VISIBLE // mIvSend?.visibility = View.VISIBLE
// TODO mAddButton!!.visibility = View.GONE //// TODO mAddButton!!.visibility = View.GONE
} else { // } else {
mIvSend?.visibility = View.GONE // mIvSend?.visibility = View.GONE
// TODO mAddButton!!.visibility = View.VISIBLE //// TODO mAddButton!!.visibility = View.VISIBLE
} // }
} // }
//
override fun afterTextChanged(editable: Editable) { // override fun afterTextChanged(editable: Editable) {
} // }
}) // })
return this return this
} }