diff --git a/README.md b/README.md index ad0f279..e595b24 100644 --- a/README.md +++ b/README.md @@ -7,5 +7,4 @@ 或者采用 `Nginx` 等Web服务器进行反向代理 -## TODO (新版本适配中) -* SQL历史记录 \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/java/me/kbai/zhenxunui/api/ApiService.kt b/app/src/main/java/me/kbai/zhenxunui/api/ApiService.kt index 8736550..2eb9e06 100644 --- a/app/src/main/java/me/kbai/zhenxunui/api/ApiService.kt +++ b/app/src/main/java/me/kbai/zhenxunui/api/ApiService.kt @@ -6,6 +6,7 @@ import me.kbai.zhenxunui.model.DeleteRemoteFile import me.kbai.zhenxunui.model.EditRemoteFile import me.kbai.zhenxunui.model.ExecuteSql import me.kbai.zhenxunui.model.FriendListItem +import me.kbai.zhenxunui.model.GetSqlLog import me.kbai.zhenxunui.model.GroupInfo import me.kbai.zhenxunui.model.GroupListItem import me.kbai.zhenxunui.model.HandleRequest @@ -18,6 +19,7 @@ import me.kbai.zhenxunui.model.RenameRemoteFile import me.kbai.zhenxunui.model.RequestListResult import me.kbai.zhenxunui.model.SendMessage import me.kbai.zhenxunui.model.SqlLog +import me.kbai.zhenxunui.model.SqlLogPage import me.kbai.zhenxunui.model.TableColumn import me.kbai.zhenxunui.model.TableListItem import me.kbai.zhenxunui.model.UpdateGroup @@ -118,8 +120,8 @@ interface ApiService { @GET("database/get_table_column") suspend fun getTableColumn(@Query("table_name") table: String): ApiResponse> - @GET("database/get_sql_log") - suspend fun getSqlLog(): ApiResponse> + @POST("database/get_sql_log") + suspend fun getSqlLog(@Body getSqlLog: GetSqlLog): ApiResponse @POST("database/exec_sql") suspend fun executeSql(@Body sql: ExecuteSql): ApiResponse>> diff --git a/app/src/main/java/me/kbai/zhenxunui/extends/ApiExtends.kt b/app/src/main/java/me/kbai/zhenxunui/extends/ApiExtends.kt index 6d9c907..46919bd 100644 --- a/app/src/main/java/me/kbai/zhenxunui/extends/ApiExtends.kt +++ b/app/src/main/java/me/kbai/zhenxunui/extends/ApiExtends.kt @@ -2,10 +2,8 @@ package me.kbai.zhenxunui.extends import android.app.Application import android.view.View -import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch import me.kbai.zhenxunui.repository.Resource @@ -40,18 +38,12 @@ suspend fun > Flow.apiCollect( action.invoke(value) } -/** - * 要记得在 active state 恢复 button 状态 - */ fun > Flow.launchAndApiCollectIn( owner: LifecycleOwner, button: View? = null, - activeState: Lifecycle.State = Lifecycle.State.STARTED, action: suspend (value: T) -> Unit ) = owner.lifecycleScope.launch { - owner.repeatOnLifecycle(activeState) { - apiCollect(button = button, action = action) - } + apiCollect(button = button, action = action) } //fun > Flow.checkToken( diff --git a/app/src/main/java/me/kbai/zhenxunui/model/GetSqlLog.kt b/app/src/main/java/me/kbai/zhenxunui/model/GetSqlLog.kt new file mode 100644 index 0000000..6badfc4 --- /dev/null +++ b/app/src/main/java/me/kbai/zhenxunui/model/GetSqlLog.kt @@ -0,0 +1,10 @@ +package me.kbai.zhenxunui.model + +data class GetSqlLog( + val index: Int = 1, + val size: Int = 5 +) { + companion object { + val DEFAULT = GetSqlLog() + } +} \ No newline at end of file diff --git a/app/src/main/java/me/kbai/zhenxunui/model/SqlLogPage.kt b/app/src/main/java/me/kbai/zhenxunui/model/SqlLogPage.kt new file mode 100644 index 0000000..b0f4630 --- /dev/null +++ b/app/src/main/java/me/kbai/zhenxunui/model/SqlLogPage.kt @@ -0,0 +1,6 @@ +package me.kbai.zhenxunui.model + +data class SqlLogPage( + val total: Int, + val data: List +) \ No newline at end of file diff --git a/app/src/main/java/me/kbai/zhenxunui/repository/ApiRepository.kt b/app/src/main/java/me/kbai/zhenxunui/repository/ApiRepository.kt index fd522ce..9e57305 100644 --- a/app/src/main/java/me/kbai/zhenxunui/repository/ApiRepository.kt +++ b/app/src/main/java/me/kbai/zhenxunui/repository/ApiRepository.kt @@ -13,6 +13,7 @@ import me.kbai.zhenxunui.model.ChatMessage import me.kbai.zhenxunui.model.DeleteRemoteFile import me.kbai.zhenxunui.model.EditRemoteFile import me.kbai.zhenxunui.model.ExecuteSql +import me.kbai.zhenxunui.model.GetSqlLog import me.kbai.zhenxunui.model.HandleRequest import me.kbai.zhenxunui.model.PluginDetail import me.kbai.zhenxunui.model.PluginInfo @@ -125,6 +126,8 @@ object ApiRepository { fun executeSql(sql: ExecuteSql) = networkFlow { BotApi.service.executeSql(sql) } + fun getSqlLog() = networkFlow { BotApi.service.getSqlLog(GetSqlLog.DEFAULT) } + fun readDir(path: String) = networkFlow { BotApi.service.readDir(path) } fun renameFile(file: RemoteFile, newName: String) = networkFlow { diff --git a/app/src/main/java/me/kbai/zhenxunui/ui/db/DbManageFragment.kt b/app/src/main/java/me/kbai/zhenxunui/ui/db/DbManageFragment.kt index 8c4e8e2..cb21ef2 100644 --- a/app/src/main/java/me/kbai/zhenxunui/ui/db/DbManageFragment.kt +++ b/app/src/main/java/me/kbai/zhenxunui/ui/db/DbManageFragment.kt @@ -1,7 +1,12 @@ package me.kbai.zhenxunui.ui.db +import android.annotation.SuppressLint +import android.util.LayoutDirection +import android.view.Gravity import android.view.LayoutInflater +import android.view.MotionEvent import android.view.ViewGroup +import android.widget.TextView import androidx.fragment.app.viewModels import me.kbai.zhenxunui.base.BaseFragment import me.kbai.zhenxunui.databinding.FragmentDbManageBinding @@ -11,6 +16,7 @@ import me.kbai.zhenxunui.extends.viewLifecycleScope import me.kbai.zhenxunui.repository.Resource import me.kbai.zhenxunui.tool.GlobalToast import me.kbai.zhenxunui.viewmodel.DbManageViewModel +import me.kbai.zhenxunui.viewmodel.SqlLogViewModel /** * @author Sean on 2023/5/30 @@ -18,12 +24,15 @@ import me.kbai.zhenxunui.viewmodel.DbManageViewModel class DbManageFragment : BaseFragment() { private val mViewModel by viewModels() + private val mSqlLogViewModel by viewModels() override fun getViewBinding( inflater: LayoutInflater, container: ViewGroup? ): FragmentDbManageBinding = FragmentDbManageBinding.inflate(inflater, container, false) + + @SuppressLint("ClickableViewAccessibility") override fun initView() = viewBinding.run { srlRefresh.setOnRefreshListener { mViewModel.requestTableList() @@ -38,13 +47,37 @@ class DbManageFragment : BaseFragment() { if (it.data != null) { SqlResultDialogFragment().show(childFragmentManager) } + + mSqlLogViewModel.requestSqlLog() } } - } - override fun onStart() { - super.onStart() - viewBinding.btnSql.isEnabled = true + etSql.setOnTouchListener touch@{ v, event -> + if (event.action != MotionEvent.ACTION_UP) return@touch false + val drawableEnd = (v as TextView).compoundDrawables[2] ?: return@touch false + val drawableLeft: Int + val drawableRight: Int + + if (v.layoutDirection == LayoutDirection.LTR) { + drawableLeft = v.right - v.paddingEnd - drawableEnd.bounds.width() + drawableRight = v.right + } else { + drawableLeft = 0 + drawableRight = v.paddingEnd + drawableEnd.bounds.width() + } + + if (event.x >= drawableLeft && event.x <= drawableRight) { + SqlLogPopupWindow(this@DbManageFragment) + .setOnItemSelectedListener { popup, sqlLog -> + etSql.setText(sqlLog.sql) + etSql.setSelection(sqlLog.sql.length) + popup.dismiss() + } + .show(v) + return@touch true + } + return@touch false + } } override fun initData() { diff --git a/app/src/main/java/me/kbai/zhenxunui/ui/db/SqlLogPopupWindow.kt b/app/src/main/java/me/kbai/zhenxunui/ui/db/SqlLogPopupWindow.kt new file mode 100644 index 0000000..6fc543d --- /dev/null +++ b/app/src/main/java/me/kbai/zhenxunui/ui/db/SqlLogPopupWindow.kt @@ -0,0 +1,88 @@ +package me.kbai.zhenxunui.ui.db + +import android.view.Gravity +import android.view.View +import android.view.ViewGroup.LayoutParams +import android.view.WindowManager +import android.view.inputmethod.InputMethodManager +import android.widget.ArrayAdapter +import android.widget.PopupWindow +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Observer +import me.kbai.zhenxunui.R +import me.kbai.zhenxunui.databinding.PopupSqlLogBinding +import me.kbai.zhenxunui.model.SqlLog +import me.kbai.zhenxunui.viewmodel.SqlLogViewModel + + +class SqlLogPopupWindow(host: Fragment) : PopupWindow( + View.inflate(host.requireContext(), R.layout.popup_sql_log, null) +) { + private val mBinding = PopupSqlLogBinding.bind(contentView) + private val mViewModel by host.viewModels() + + private val mObserver: Observer> + + private val mWindowManager = host.requireActivity().windowManager + + private var mOnItemSelectedListener: ((SqlLogPopupWindow, SqlLog) -> Unit)? = null + + init { + isFocusable = true + isOutsideTouchable = true + animationStyle = R.style.PopupAnim + + mObserver = Observer { list -> + mBinding.lvSql.apply { + adapter = ArrayAdapter( + mBinding.root.context, R.layout.item_sql_log, 0, list.map { it.sql } + ) + setOnItemClickListener { _, _, position, _ -> + mOnItemSelectedListener?.invoke(this@SqlLogPopupWindow, list[position]) + } + } + mBinding.pbWaiting.isVisible = false + } + mViewModel.logs.observeForever(mObserver) + } + + fun setOnItemSelectedListener(listener: (popup: SqlLogPopupWindow, sqlLog: SqlLog) -> Unit): SqlLogPopupWindow { + mOnItemSelectedListener = listener + return this + } + + fun show(anchor: View) { + anchor.context.getSystemService(InputMethodManager::class.java) + .hideSoftInputFromWindow(anchor.windowToken, 0) + + width = anchor.width + height = LayoutParams.WRAP_CONTENT + showAsDropDown(anchor, 0, 0, Gravity.BOTTOM) + + val container: View = contentView.parent.let { + if (it is View) it else contentView + } + val params = (container.layoutParams as WindowManager.LayoutParams).apply { + flags = flags or WindowManager.LayoutParams.FLAG_DIM_BEHIND + dimAmount = 0.1f + } + mWindowManager.updateViewLayout(container, params) + } + + override fun dismiss() { + mViewModel.logs.removeObserver(mObserver) + + val container: View = contentView.parent.let { + if (it is View) it else contentView + } + val params = (container.layoutParams as WindowManager.LayoutParams).apply { + flags = flags and WindowManager.LayoutParams.FLAG_DIM_BEHIND.inv() + dimAmount = 0f + } + mWindowManager.updateViewLayout(container, params) + + super.dismiss() + } +} \ No newline at end of file diff --git a/app/src/main/java/me/kbai/zhenxunui/ui/group/ConversationFragment.kt b/app/src/main/java/me/kbai/zhenxunui/ui/group/ConversationFragment.kt index 06d739d..d3e2078 100644 --- a/app/src/main/java/me/kbai/zhenxunui/ui/group/ConversationFragment.kt +++ b/app/src/main/java/me/kbai/zhenxunui/ui/group/ConversationFragment.kt @@ -46,7 +46,7 @@ class ConversationFragment : BaseFragment() { btnSend.isEnabled = !it.isNullOrEmpty() } - btnSend.setOnDebounceClickListener { + btnSend.setOnDebounceClickListener { btn -> val message = etText.text?.toString() if (message.isNullOrEmpty()) { return@setOnDebounceClickListener @@ -54,7 +54,7 @@ class ConversationFragment : BaseFragment() { etText.setText("") mViewModel.sendMessage(mUserId, mGroupId, message) - .launchAndApiCollectIn(this@ConversationFragment) { + .launchAndApiCollectIn(this@ConversationFragment, btn) { if (it.success()) { mMessageAdapter.addData( mViewModel.insertSentMessage(mGroupId, mUserId, message) @@ -66,11 +66,6 @@ class ConversationFragment : BaseFragment() { } } - override fun onStart() { - super.onStart() - viewBinding.btnSend.isEnabled = true - } - override fun initData() { mGroupId = arguments?.getString(ARGS_GROUP_ID) mUserId = arguments?.getString(ARGS_USER_ID) diff --git a/app/src/main/java/me/kbai/zhenxunui/viewmodel/PluginTypeViewModel.kt b/app/src/main/java/me/kbai/zhenxunui/viewmodel/PluginTypeViewModel.kt index 3aa245b..f726475 100644 --- a/app/src/main/java/me/kbai/zhenxunui/viewmodel/PluginTypeViewModel.kt +++ b/app/src/main/java/me/kbai/zhenxunui/viewmodel/PluginTypeViewModel.kt @@ -27,7 +27,8 @@ class PluginTypeViewModel : ViewModel() { fun modifyPluginData(position: Int, data: PluginInfo, notify: Boolean = true) { val list = _plugins.value - list[position] = data + //后台接口 getPluginDetail version 错误, 固定为 0 + list[position] = data.copy(version = list[position].version) if (notify) _plugins.value = list } } \ No newline at end of file diff --git a/app/src/main/java/me/kbai/zhenxunui/viewmodel/SqlLogViewModel.kt b/app/src/main/java/me/kbai/zhenxunui/viewmodel/SqlLogViewModel.kt new file mode 100644 index 0000000..3c96bd2 --- /dev/null +++ b/app/src/main/java/me/kbai/zhenxunui/viewmodel/SqlLogViewModel.kt @@ -0,0 +1,30 @@ +package me.kbai.zhenxunui.viewmodel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.launch +import me.kbai.zhenxunui.extends.apiCollect +import me.kbai.zhenxunui.model.SqlLog +import me.kbai.zhenxunui.repository.ApiRepository +import me.kbai.zhenxunui.tool.GlobalToast + +class SqlLogViewModel : ViewModel() { + private val _logs: MutableLiveData> = MutableLiveData() + val logs: LiveData> = _logs + + init { + requestSqlLog() + } + + fun requestSqlLog() = viewModelScope.launch { + ApiRepository.getSqlLog().apiCollect { + if (it.success() && it.data != null) { + _logs.value = it.data.data + } else { + GlobalToast.showToast(it.message) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/anim_top_enter.xml b/app/src/main/res/anim/anim_top_enter.xml new file mode 100644 index 0000000..78c52d3 --- /dev/null +++ b/app/src/main/res/anim/anim_top_enter.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/anim_top_exit.xml b/app/src/main/res/anim/anim_top_exit.xml new file mode 100644 index 0000000..5ea3168 --- /dev/null +++ b/app/src/main/res/anim/anim_top_exit.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_list_item_round_bottom.xml b/app/src/main/res/drawable/bg_list_item_round_bottom.xml new file mode 100644 index 0000000..c9301f1 --- /dev/null +++ b/app/src/main/res/drawable/bg_list_item_round_bottom.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_db_manage.xml b/app/src/main/res/layout/fragment_db_manage.xml index 40b4345..fad4f6f 100644 --- a/app/src/main/res/layout/fragment_db_manage.xml +++ b/app/src/main/res/layout/fragment_db_manage.xml @@ -41,6 +41,7 @@ android:id="@+id/et_sql" android:layout_width="match_parent" android:layout_height="match_parent" + android:drawableEnd="@drawable/ic_arrow_drop_down_16" android:inputType="text" android:padding="0dp" android:textSize="@dimen/et_text_size" diff --git a/app/src/main/res/layout/fragment_request.xml b/app/src/main/res/layout/fragment_request.xml index 44b9d86..cff66a2 100644 --- a/app/src/main/res/layout/fragment_request.xml +++ b/app/src/main/res/layout/fragment_request.xml @@ -5,9 +5,11 @@ android:layout_height="match_parent"> @@ -15,25 +17,33 @@ - + app:layout_constraintTop_toTopOf="parent"> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/popup_sql_log.xml b/app/src/main/res/layout/popup_sql_log.xml new file mode 100644 index 0000000..408ecb3 --- /dev/null +++ b/app/src/main/res/layout/popup_sql_log.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index ec51f01..dd78af1 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -28,5 +28,5 @@ 18dp 20dp 28dp - 48dp + 58dp \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index c3a4d0b..463d436 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -35,7 +35,7 @@ @color/dn_white - + + \ No newline at end of file diff --git a/docs/conversation.jpg b/docs/conversation.jpg new file mode 100644 index 0000000..cbfe478 Binary files /dev/null and b/docs/conversation.jpg differ diff --git a/docs/db.jpg b/docs/db.jpg new file mode 100644 index 0000000..78ef9ac Binary files /dev/null and b/docs/db.jpg differ diff --git a/docs/edit.jpg b/docs/edit.jpg new file mode 100644 index 0000000..0804190 Binary files /dev/null and b/docs/edit.jpg differ diff --git a/docs/main.jpg b/docs/main.jpg new file mode 100644 index 0000000..dc4b0ef Binary files /dev/null and b/docs/main.jpg differ diff --git a/docs/menu.jpg b/docs/menu.jpg new file mode 100644 index 0000000..d79c5d6 Binary files /dev/null and b/docs/menu.jpg differ diff --git a/docs/plugin.jpg b/docs/plugin.jpg new file mode 100644 index 0000000..be336fc Binary files /dev/null and b/docs/plugin.jpg differ