快速回复

This commit is contained in:
itgaojian163 2024-11-06 14:20:04 +08:00
parent 06f10d3ed3
commit ab1641eb15
19 changed files with 501 additions and 37 deletions

View File

@ -8,6 +8,6 @@
<option name="languageVersion" value="2.0" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.0.20" />
<option name="version" value="2.0.21" />
</component>
</project>

View File

@ -35,7 +35,6 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".page.activity.LoginActivity"
android:exported="false" />
@ -46,6 +45,10 @@
android:name=".page.activity.MainActivity"
android:exported="false"
android:launchMode="singleInstance" />
<activity
android:name=".page.activity.ManageReplyActivity"
android:exported="false"
android:windowSoftInputMode="adjustPan|stateHidden" />
<service
android:name=".services.SocketService"

View File

@ -0,0 +1,43 @@
package com.tenlionsoft.aimz_k.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import com.lxj.xpopup.XPopup
import com.tenlionsoft.aimz_k.databinding.ItemReplyBinding
import com.tenlionsoft.aimz_k.model.ReplyBean
import com.tenlionsoft.aimz_k.viewmodel.ManageReplyViewModel
import com.tenlionsoft.baselib.base.BaseBindingAdapter
class ReplyAdapter(datas: List<ReplyBean>, val viewModel: ManageReplyViewModel) :
BaseBindingAdapter<ReplyBean, ItemReplyBinding>(datas) {
override fun getItemBinding(parent: ViewGroup, viewType: Int): ItemReplyBinding {
return ItemReplyBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
}
override fun bindItem(holder: BaseViewHolder<ItemReplyBinding>, position: Int) {
holder.binding.pos = position
holder.binding.bean = list[position]
val xPopup = XPopup.Builder(holder.binding.root.context).watchView(holder.binding.root)
holder.binding.root.setOnLongClickListener {
xPopup.asAttachList(
arrayOf("删除", "编辑"),
intArrayOf(
com.tenlionsoft.baselib.R.drawable.ic_del,
com.tenlionsoft.baselib.R.drawable.ic_edit
),
{ p, _ ->
viewModel.onItemLongClickListener.onItemLongClick(
p, list[position]
)
},
0,
0
).show()
true
}
}
}

View File

@ -7,9 +7,14 @@ import androidx.room.RoomDatabase
/**
* 数据库的版本
*/
@Database(entities = [MsgBean::class, MsgCategoryBean::class], version = 1, exportSchema = false)
@Database(
entities = [MsgBean::class, MsgCategoryBean::class, ReplyBean::class],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun msgDao(): MsgDao
abstract fun categoryDao(): MsgCategoryDao
abstract fun replayDao(): ReplyDao
}

View File

@ -11,7 +11,7 @@ interface MsgDao {
* 更新信息状态
*/
@Query("UPDATE db_msg SET status = :status WHERE messageId = :messageId")
fun updateStatus(messageId: String, status: String)
suspend fun updateStatus(messageId: String, status: String)
/**
* 获取全部信息
@ -19,7 +19,7 @@ interface MsgDao {
* @return
*/
@Query("SELECT * FROM db_msg")
fun getAllMsg(): List<MsgBean?>?
suspend fun getAllMsg(): List<MsgBean?>?
/**
* 根据ID获取消息
@ -28,7 +28,7 @@ interface MsgDao {
* @return
*/
@Query("SELECT * FROM db_msg WHERE id =(:id) ")
fun getMsgById(id: String?): List<MsgBean?>?
suspend fun getMsgById(id: String?): List<MsgBean?>?
/**
*
@ -41,7 +41,7 @@ interface MsgDao {
* @return
*/
@Query("SELECT * FROM db_msg WHERE `senderId`=(:from)")
fun getMsgByFrom(from: String?): List<MsgBean?>?
suspend fun getMsgByFrom(from: String?): List<MsgBean?>?
/**
* 根据发送给谁进行查询
@ -50,7 +50,7 @@ interface MsgDao {
* @return
*/
@Query("SELECT * FROM db_msg WHERE `receiverId` =(:to)")
fun getMsgByTo(to: String?): List<MsgBean?>?
suspend fun getMsgByTo(to: String?): List<MsgBean?>?
/**
@ -62,13 +62,13 @@ interface MsgDao {
* @return
*/
@Query("SELECT * FROM db_msg WHERE `senderId`=(:form) ORDER BY timestamp LIMIT (((:page)-1)*(:pageSize)),(:pageSize)")
fun getMsgByPage(form: String?, pageSize: Int, page: Int): List<MsgBean?>?
suspend fun getMsgByPage(form: String?, pageSize: Int, page: Int): List<MsgBean?>?
/**
* 查询与单人的聊天记录-分页
*/
@Query("SELECT * FROM db_msg t1 WHERE t1.id IN (SELECT id FROM db_msg WHERE `senderId`=(:form) AND `receiverId`=(:to) OR `senderId`=(:to) AND `receiverId`=(:form) ORDER BY timestamp DESC LIMIT (((:page)-1)*(:pageSize)),(:pageSize)) ORDER by t1.timestamp ASC")
fun getMsgByFromOrToPage(
suspend fun getMsgByFromOrToPage(
form: String?,
to: String?,
pageSize: Int,
@ -79,7 +79,7 @@ interface MsgDao {
* 查询与单人的聊天记录-全部
*/
@Query("SELECT * FROM db_msg t1 WHERE t1.id IN (SELECT id FROM db_msg WHERE senderId=:form AND receiverId=:to OR senderId=:to AND receiverId=:form ORDER BY timestamp DESC) ORDER by t1.timestamp ASC")
fun getMsgByFromOrToPage(
suspend fun getMsgByFromOrToPage(
form: String,
to: String,
): List<MsgBean>
@ -96,13 +96,13 @@ interface MsgDao {
* @param beans
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(vararg beans: MsgBean)
suspend fun insertAll(vararg beans: MsgBean)
/**
* 批量添加
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(beans: List<MsgBean>)
suspend fun insertAll(beans: List<MsgBean>)
/**
@ -117,5 +117,5 @@ interface MsgDao {
* 清除全部聊天记录
*/
@Query("DELETE FROM db_msg")
fun delAllMsg()
suspend fun delAllMsg()
}

View File

@ -0,0 +1,15 @@
package com.tenlionsoft.aimz_k.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "db_reply")
data class ReplyBean(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
@ColumnInfo(name = "content")
val content: String,
@ColumnInfo(name = "remark")
var remark: String? = ""
)

View File

@ -0,0 +1,52 @@
package com.tenlionsoft.aimz_k.model
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@Dao
interface ReplyDao {
/**
* 更新内容
*/
@Query("UPDATE db_reply SET content = :content WHERE id = :id")
suspend fun updateContent(id: Long, content: String)
/**
* 获取全部
*
* @return
*/
@Query("SELECT * FROM db_reply")
suspend fun getAllReply(): List<ReplyBean>?
/**
* 添加多个
*
* @param beans
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(vararg beans: ReplyBean)
/**
* 批量添加
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(beans: List<ReplyBean>)
/**
* 添加-单个
*
* @param bean
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(bean: ReplyBean)
/**
* 删除
*/
@Query("DELETE FROM db_reply WHERE id=:id")
suspend fun delReply(id: Long)
}

View File

@ -0,0 +1,33 @@
package com.tenlionsoft.aimz_k.page.activity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import com.tenlionsoft.aimz_k.R
import com.tenlionsoft.aimz_k.databinding.ActivityManageReplyBinding
import com.tenlionsoft.aimz_k.viewmodel.ManageReplyViewModel
import com.tenlionsoft.baselib.base.BaseActivity
class ManageReplyActivity : BaseActivity() {
private lateinit var mBind: ActivityManageReplyBinding
private var viewModel: ManageReplyViewModel? = null
override fun bindView() {
mBind =
DataBindingUtil.setContentView(this@ManageReplyActivity, R.layout.activity_manage_reply)
mBind.ivBack.setOnClickListener { finish() }
viewModel = ViewModelProvider(this)[ManageReplyViewModel::class.java]
mBind.model = viewModel
mBind.lifecycleOwner = this
viewModel!!.showSoftKeyboard.observe(this) {
if (it) {
showSoftKeyBoard(mBind.etReplyContent)
mBind.etReplyContent.requestFocus()
} else {
hideSoftKeyboard()
mBind.etReplyContent.clearFocus()
}
}
}
}

View File

@ -1,31 +1,82 @@
package com.tenlionsoft.aimz_k.page.fragments
import androidx.fragment.app.viewModels
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.tenlionsoft.aimz_k.R
import com.tenlionsoft.aimz_k.databinding.FragmentMineBinding
import com.tenlionsoft.aimz_k.page.activity.MainActivity
import com.tenlionsoft.aimz_k.page.activity.ManageReplyActivity
import com.tenlionsoft.aimz_k.viewmodel.MineViewModel
import com.tenlionsoft.baselib.utils.SpUtils
class MineFragment : Fragment() {
private lateinit var mBind: FragmentMineBinding
private lateinit var mActivity: MainActivity
companion object {
fun newInstance() = MineFragment()
}
private val viewModel: MineViewModel by viewModels()
private lateinit var viewModel: MineViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_mine, container, false)
mBind = DataBindingUtil.inflate(
layoutInflater,
R.layout.fragment_mine,
container,
false
)
viewModel = ViewModelProvider(this, object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MineViewModel() as T
}
})[MineViewModel::class.java]
mBind.model = viewModel
mBind.lifecycleOwner = this
initView()
return mBind.root
}
private fun initView() {
mBind.tvName.text = SpUtils.getNickName()
mBind.tvAccount.text = SpUtils.getUserName()
val requestOptions = RequestOptions()
.error(R.drawable.ic_user_default)
.placeholder(R.drawable.ic_user_default)
.skipMemoryCache(false)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerInside()
Glide.with(mActivity)
.load(SpUtils.getAvatar())
.apply(requestOptions)
.into(mBind.ivUserIcon)
mBind.llSetting.setOnClickListener {
startActivity(Intent(mActivity, ManageReplyActivity::class.java))
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is MainActivity) {
mActivity = context
}
}
}

View File

@ -23,6 +23,7 @@ import com.tenlionsoft.aimz_k.model.MsgBean
import com.tenlionsoft.aimz_k.model.MsgConvertBean
import com.tenlionsoft.aimz_k.model.PickerType
import com.tenlionsoft.aimz_k.model.Receiver
import com.tenlionsoft.aimz_k.model.ReplyBean
import com.tenlionsoft.aimz_k.model.Sender
import com.tenlionsoft.aimz_k.net.UserApi
import com.tenlionsoft.baselib.base.BaseViewModel
@ -59,7 +60,7 @@ class ChatPageViewModel(
var onItemClickListener: AdapterItemClickListener<MsgBean>? = null
private val mGson: Gson = Gson()
private var msgHandlerList: ArrayList<MsgConvertBean> = arrayListOf()
private val _replyList = MutableLiveData<List<ReplyBean>?>()
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
@ -89,13 +90,26 @@ class ChatPageViewModel(
Log.e("ChatPageViewModel", "Init: ${showSendBtn.value}")
viewModelScope.launch {
val list = getList()
val rList = getReplyList()
Log.e("ChatPageViewModel", "Init:${list} ")
if (rList != null) {
_replyList.value = rList
} else {
_replyList.value = emptyList()
}
_msgList.value = list
adapter.setData(list)
scrollListToBottom.value = true
}
}
private suspend fun getReplyList(): List<ReplyBean>? {
return withContext(Dispatchers.IO) {
val rDao = DbManager.db.replayDao()
return@withContext rDao.getAllReply()
}
}
//获取列表
private suspend fun getList(): List<MsgBean> {
return withContext(Dispatchers.IO) {

View File

@ -0,0 +1,97 @@
package com.tenlionsoft.aimz_k.viewmodel
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.tenlionsoft.aimz_k.adapter.ReplyAdapter
import com.tenlionsoft.aimz_k.model.DbManager
import com.tenlionsoft.aimz_k.model.ReplyBean
import com.tenlionsoft.baselib.base.BaseViewModel
import com.tenlionsoft.baselib.utils.ToastUtils
import com.tenlionsoft.baselib.widget.AdapterItemLongClickListener
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ManageReplyViewModel : BaseViewModel(), AdapterItemLongClickListener<ReplyBean> {
val txtMsg = MutableLiveData("")
val _replyList = MutableLiveData<List<ReplyBean>?>()
var adapter: ReplyAdapter = ReplyAdapter(_replyList.value ?: emptyList(), this)
var onItemLongClickListener: AdapterItemLongClickListener<ReplyBean> = this
val curBean = MutableLiveData<ReplyBean?>()
init {
getList()
}
private fun getList() {
viewModelScope.launch {
val list: List<ReplyBean>? = withContext(Dispatchers.IO) {
val rDao = DbManager.db.replayDao()
val list = rDao.getAllReply()
list
}
if (!list.isNullOrEmpty()) {
_replyList.value = list
} else {
_replyList.value = emptyList()
}
Log.e("ManageReplyViewModel", "init:${adapter} ${Thread.currentThread().name}");
adapter.setData(_replyList.value!!)
}
}
fun onTxtChange(s: CharSequence, start: Int, before: Int, count: Int) {
txtMsg.value = s.toString()
}
//添加短语
fun toAddReply() {
if (txtMsg.value != null && txtMsg.value!!.isNotEmpty()) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val rDao = DbManager.db.replayDao()
if (curBean.value != null) {
//更新
rDao.updateContent(curBean.value!!.id, txtMsg.value!!)
} else {
//新增
val b = ReplyBean(content = txtMsg.value!!)
rDao.insert(b)
}
}
txtMsg.value = ""
curBean.value = null
ToastUtils.success("保存成功")
getList()
showSoftKeyboard.value = false
}
} else {
ToastUtils.error("请输入短语")
}
}
override fun onItemLongClick(type: Int, d: ReplyBean) {
when (type) {
//删除
0 -> {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val rDao = DbManager.db.replayDao()
rDao.delReply(d.id)
}
ToastUtils.success("删除成功")
getList()
}
}
//修改
1 -> {
curBean.value = d
txtMsg.value = d.content
showSoftKeyboard.value = true
}
}
}
}

View File

@ -54,9 +54,9 @@ object BindingUtils {
.apply(requestOptions)
.into(imageView)
}
}
@BindingAdapter("imgResource")
@JvmStatic
fun bindImgUrl(imageView: ImageView, url: Int) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="model"
type="com.tenlionsoft.aimz_k.viewmodel.ManageReplyViewModel" />
</data>
<RelativeLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".page.activity.ManageReplyActivity">
<LinearLayout
android:id="@+id/ll_title"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp">
<ImageView
android:id="@+id/iv_back"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_centerVertical="true"
android:src="@drawable/ic_back_left" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:text="回复短语"
android:textColor="#1D1D1D"
android:textSize="20sp" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#CBCBCB" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/ll_title"
android:layout_marginTop="5dp"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
setLinearLayoutAdapter="@{model.adapter}"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginBottom="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<EditText
android:id="@+id/et_reply_content"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginRight="5dp"
android:layout_weight="1"
android:background="@drawable/shp_white_5_radius"
android:hint="请输入内容"
android:maxLines="5"
android:minLines="1"
android:onTextChanged="@{model::onTxtChange}"
android:padding="10dp"
android:text="@{model.txtMsg}"
android:textSize="14sp" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/sel_login_btn"
android:elevation="0dp"
android:onClick="@{()->model.toAddReply()}"
android:stateListAnimator="@null"
android:text="保存"
android:textColor="@color/col_login_btn" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</layout>

View File

@ -4,6 +4,9 @@
<data>
<variable
name="model"
type="com.tenlionsoft.aimz_k.viewmodel.MineViewModel" />
</data>
<LinearLayout
@ -22,6 +25,7 @@
android:padding="20dp">
<ImageView
android:id="@+id/iv_user_icon"
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@drawable/ic_load_error" />
@ -33,6 +37,7 @@
android:orientation="vertical">
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="名称"
@ -40,6 +45,7 @@
android:textSize="20sp" />
<TextView
android:id="@+id/tv_account"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
@ -58,6 +64,7 @@
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_setting"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
@ -65,8 +72,8 @@
android:padding="15dp">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_width="28dp"
android:layout_height="28dp"
android:scaleType="fitXY"
android:src="@drawable/ic_setting_gear" />
@ -76,7 +83,7 @@
android:layout_marginLeft="10dp"
android:text="设置"
android:textColor="@color/txt_black"
android:textSize="20sp" />
android:textSize="14sp" />
</LinearLayout>
<View
@ -94,8 +101,8 @@
android:padding="15dp">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_width="28dp"
android:layout_height="28dp"
android:scaleType="fitXY"
android:src="@drawable/ic_exit_blue" />
@ -105,7 +112,7 @@
android:layout_marginLeft="10dp"
android:text="退出登录"
android:textColor="@color/txt_black"
android:textSize="20sp" />
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="bean"
type="com.tenlionsoft.aimz_k.model.ReplyBean" />
<variable
name="pos"
type="Integer" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="@{bean.content}"
android:textColor="@color/black"
android:textSize="16sp"
tools:text="测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容测试内容" />
</LinearLayout>
</layout>

View File

@ -37,16 +37,14 @@ abstract class BaseActivity : DataBindingActivity() {
private fun hideSoftKeyboard(et: View?) {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
if (et != null) {
imm.hideSoftInputFromWindow(et.windowToken, 0)
}
et?.postDelayed({ imm.hideSoftInputFromWindow(et.windowToken, 0) }, 100)
}
fun showSoftKeyBoard(view: View?) {
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
if (view != null) {
imm.showSoftInput(view, 0)
}
view?.postDelayed({
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
}, 100)
}
fun setStatusBarColor(dark: Boolean) {

View File

@ -7,5 +7,5 @@ import com.tenlionsoft.baselib.model.ViewState
open class BaseViewModel : ViewModel() {
val showLoadDialog = MutableLiveData<Boolean>()
val pageState = MutableLiveData<ViewState>()
val showSoftKeyboard = MutableLiveData(false)
val showSoftKeyboard = MutableLiveData<Boolean>(false)
}

View File

@ -9,5 +9,6 @@
<color name="red">#E75D58</color>
<color name="green">#45F68B</color>
<color name="gray_6f">#6F6E6E</color>
<color name="gray_e6">#E6E6E6</color>
<color name="black">#000000</color>
</resources>