From 65ea7bcf0c2626eed2d390fc00c29d7538afbf81 Mon Sep 17 00:00:00 2001 From: pppscn <35696959@qq.com> Date: Mon, 26 Feb 2024 13:39:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=C2=B7=E5=BF=AB=E6=8D=B7=E6=8C=87=E4=BB=A4=20?= =?UTF-8?q?=E2=80=94=E2=80=94=20=E6=89=A7=E8=A1=8C=E5=8A=A8=E4=BD=9C?= =?UTF-8?q?=EF=BC=9A=E5=90=AF=E5=81=9C=E4=BB=BB=E5=8A=A1=20#389?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../forwarder/adapter/TaskRecyclerAdapter.kt | 104 ++++++ .../adapter/spinner/TaskSpinnerAdapter.kt | 167 ++++++++++ .../adapter/spinner/TaskSpinnerItem.kt | 49 +++ .../sms/forwarder/database/dao/TaskDao.kt | 6 + .../database/repository/TaskRepository.kt | 5 + .../entity/action/TaskActionSetting.kt | 10 + .../forwarder/fragment/TasksEditFragment.kt | 7 + .../fragment/action/TaskActionFragment.kt | 303 ++++++++++++++++++ .../idormy/sms/forwarder/utils/Constants.kt | 3 +- .../sms/forwarder/workers/ActionWorker.kt | 18 ++ .../main/res/drawable/auto_task_icon_task.xml | 20 ++ .../res/drawable/auto_task_icon_task_grey.xml | 20 ++ app/src/main/res/drawable/ic_menu_task.xml | 12 +- .../res/layout/adapter_task_list_item.xml | 87 +++++ .../res/layout/fragment_tasks_action_task.xml | 148 +++++++++ app/src/main/res/values-en/strings.xml | 12 +- app/src/main/res/values-zh-rCN/strings.xml | 8 + app/src/main/res/values-zh-rTW/strings.xml | 9 +- app/src/main/res/values/strings.xml | 8 + 19 files changed, 989 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/com/idormy/sms/forwarder/adapter/TaskRecyclerAdapter.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/TaskSpinnerAdapter.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/TaskSpinnerItem.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/entity/action/TaskActionSetting.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/fragment/action/TaskActionFragment.kt create mode 100644 app/src/main/res/drawable/auto_task_icon_task.xml create mode 100644 app/src/main/res/drawable/auto_task_icon_task_grey.xml create mode 100644 app/src/main/res/layout/adapter_task_list_item.xml create mode 100644 app/src/main/res/layout/fragment_tasks_action_task.xml diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/TaskRecyclerAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/TaskRecyclerAdapter.kt new file mode 100644 index 00000000..893e6e4c --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/TaskRecyclerAdapter.kt @@ -0,0 +1,104 @@ +package com.idormy.sms.forwarder.adapter + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.base.ItemMoveCallback +import com.idormy.sms.forwarder.database.entity.Task +import java.util.Collections + +@Suppress("DEPRECATION") +class TaskRecyclerAdapter( + var itemList: MutableList, + private var removeClickListener: ((Int) -> Unit)? = null, + private var editClickListener: ((Int) -> Unit)? = null, +) : RecyclerView.Adapter(), ItemMoveCallback.Listener { + + private lateinit var touchHelper: ItemTouchHelper + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.adapter_task_list_item, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = itemList[position] + holder.bind(item) + } + + override fun getItemCount(): Int = itemList.size + + fun setTouchHelper(touchHelper: ItemTouchHelper) { + this@TaskRecyclerAdapter.touchHelper = touchHelper + } + + @SuppressLint("ClickableViewAccessibility") + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener { + private val image: ImageView = itemView.findViewById(R.id.iv_image) + private val status: ImageView = itemView.findViewById(R.id.iv_status) + private val title: TextView = itemView.findViewById(R.id.tv_title) + private val editIcon: ImageView = itemView.findViewById(R.id.iv_edit) + private val removeIcon: ImageView = itemView.findViewById(R.id.iv_remove) + private val dragIcon: ImageView = itemView.findViewById(R.id.iv_drag) + + init { + if (removeClickListener == null) { + removeIcon.visibility = View.GONE + } else { + removeIcon.setOnClickListener(this) + } + + if (editClickListener == null) { + editIcon.visibility = View.GONE + } else { + editIcon.setOnClickListener(this) + } + + dragIcon.setOnTouchListener { _, event -> + if (event.actionMasked == MotionEvent.ACTION_DOWN) { + touchHelper.startDrag(this) + } + return@setOnTouchListener false + } + } + + fun bind(task: Task) { + image.setImageResource(task.imageId) + status.setImageResource(task.statusImageId) + title.text = task.name + } + + override fun onClick(v: View?) { + val position = adapterPosition + if (position != RecyclerView.NO_POSITION) { + when (v?.id) { + R.id.iv_edit -> editClickListener?.let { it(position) } + R.id.iv_remove -> removeClickListener?.let { it(position) } + } + } + } + } + + override fun onItemMove(fromPosition: Int, toPosition: Int) { + if (fromPosition < toPosition) { + for (i in fromPosition until toPosition) { + Collections.swap(itemList, i, i + 1) + } + } else { + for (i in fromPosition downTo toPosition + 1) { + Collections.swap(itemList, i, i - 1) + } + } + notifyItemMoved(fromPosition, toPosition) + } + + override fun onDragFinished() {} +} + diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/TaskSpinnerAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/TaskSpinnerAdapter.kt new file mode 100644 index 00000000..a386cd18 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/TaskSpinnerAdapter.kt @@ -0,0 +1,167 @@ +package com.idormy.sms.forwarder.adapter.spinner + +import android.annotation.SuppressLint +import android.os.Build +import android.text.Html +import android.text.TextUtils +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.utils.Log +import com.idormy.sms.forwarder.utils.STATUS_OFF +import com.xuexiang.xui.utils.CollectionUtils +import com.xuexiang.xui.widget.spinner.editspinner.BaseEditSpinnerAdapter +import com.xuexiang.xui.widget.spinner.editspinner.EditSpinnerFilter +import com.xuexiang.xutil.resource.ResUtils.getDrawable + +@Suppress("unused", "NAME_SHADOWING", "DEPRECATION") +class TaskSpinnerAdapter : BaseEditSpinnerAdapter, EditSpinnerFilter { + /** + * 选项的文字颜色 + */ + private var mTextColor = 0 + + /** + * 选项的文字大小 + */ + private var mTextSize = 0f + + /** + * 背景颜色 + */ + private var mBackgroundSelector = 0 + + /** + * 过滤关键词的选中颜色 + */ + private var mFilterColor = "#F15C58" + private var mIsFilterKey = false + + /** + * 构造方法 + * + * @param data 选项数据 + */ + constructor(data: List?) : super(data) + + /** + * 构造方法 + * + * @param data 选项数据 + */ + constructor(data: Array?) : super(data) + + override fun getEditSpinnerFilter(): EditSpinnerFilter { + return this + } + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? { + var convertView = convertView + val holder: ViewHolder + if (convertView == null) { + convertView = LayoutInflater.from(parent.context).inflate(R.layout.item_spinner_with_icon, parent, false) + holder = ViewHolder(convertView, mTextColor, mTextSize, mBackgroundSelector) + convertView.tag = holder + } else { + holder = convertView.tag as ViewHolder + } + val item = CollectionUtils.getListItem(mDataSource, mIndexs[position]) as TaskSpinnerItem + holder.iconView.setImageDrawable(item.icon) + holder.statusView.setImageDrawable( + getDrawable( + when (item.status) { + STATUS_OFF -> R.drawable.ic_stop + else -> R.drawable.ic_start + } + ) + ) + //holder.titleView.text = Html.fromHtml(item.toString()) + holder.titleView.text = Html.fromHtml(getItem(position)) + return convertView + } + + override fun onFilter(keyword: String): Boolean { + mDisplayData.clear() + Log.d("TaskSpinnerAdapter", "keyword = $keyword") + Log.d("TaskSpinnerAdapter", "mIndexs.indices = ${mIndexs.indices}") + if (TextUtils.isEmpty(keyword)) { + initDisplayData(mDataSource) + for (i in mIndexs.indices) { + mIndexs[i] = i + } + } else { + try { + for (i in mDataSource.indices) { + if (getDataSourceString(i).contains(keyword, ignoreCase = true)) { + mIndexs[mDisplayData.size] = i + if (mIsFilterKey) { + mDisplayData.add(getDataSourceString(i).replaceFirst(keyword.toRegex(), "$keyword")) + } else { + mDisplayData.add(getDataSourceString(i)) + } + } + } + } catch (e: Exception) { + e.printStackTrace() + Log.e("TaskSpinnerAdapter", "onFilter error: ${e.message}") + } + } + Log.d("TaskSpinnerAdapter", "mDisplayData = $mDisplayData") + notifyDataSetChanged() + return mDisplayData.size > 0 + } + + fun setTextColor(@ColorInt textColor: Int): TaskSpinnerAdapter<*> { + mTextColor = textColor + return this + } + + fun setTextSize(textSize: Float): TaskSpinnerAdapter<*> { + mTextSize = textSize + return this + } + + fun setBackgroundSelector(@DrawableRes backgroundSelector: Int): TaskSpinnerAdapter<*> { + mBackgroundSelector = backgroundSelector + return this + } + + fun setFilterColor(filterColor: String): TaskSpinnerAdapter<*> { + mFilterColor = filterColor + return this + } + + fun setIsFilterKey(isFilterKey: Boolean): TaskSpinnerAdapter<*> { + mIsFilterKey = isFilterKey + return this + } + + @SuppressLint("ObsoleteSdkInt") + private class ViewHolder(convertView: View, @ColorInt textColor: Int, textSize: Float, @DrawableRes backgroundSelector: Int) { + val iconView: ImageView = convertView.findViewById(R.id.iv_icon) + val statusView: ImageView = convertView.findViewById(R.id.iv_status) + val titleView: TextView = convertView.findViewById(R.id.tv_title) + + init { + if (textColor > 0) titleView.setTextColor(textColor) + if (textSize > 0F) titleView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) + if (backgroundSelector != 0) titleView.setBackgroundResource(backgroundSelector) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + val config = convertView.resources.configuration + if (config.layoutDirection == View.LAYOUT_DIRECTION_RTL) { + titleView.textDirection = View.TEXT_DIRECTION_RTL + } + } + } + } + + fun getItemSource(position: Int): T { + return mDataSource[mIndexs[position]] + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/TaskSpinnerItem.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/TaskSpinnerItem.kt new file mode 100644 index 00000000..2d487069 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/TaskSpinnerItem.kt @@ -0,0 +1,49 @@ +package com.idormy.sms.forwarder.adapter.spinner + +import android.graphics.drawable.Drawable + +@Suppress("unused") +class TaskSpinnerItem( + var title: CharSequence, + var icon: Drawable? = null, + var id: Long? = 0L, + var status: Int? = 1 +) { + + fun setTitle(title: CharSequence): TaskSpinnerItem { + this.title = title + return this + } + + fun setIcon(icon: Drawable?): TaskSpinnerItem { + this.icon = icon + return this + } + + fun setId(id: Long): TaskSpinnerItem { + this.id = id + return this + } + + fun setStatus(status: Int): TaskSpinnerItem { + this.status = status + return this + } + + // 注意:自定义实体需要重写对象的 toString 方法 + override fun toString(): String { + return title.toString() + } + + companion object { + @JvmStatic + fun of(title: CharSequence): TaskSpinnerItem { + return TaskSpinnerItem(title) + } + + @JvmStatic + fun arrayOf(vararg titles: CharSequence): Array { + return titles.map { TaskSpinnerItem(it) }.toTypedArray() + } + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/dao/TaskDao.kt b/app/src/main/java/com/idormy/sms/forwarder/database/dao/TaskDao.kt index 87d1ecd4..953d0c58 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/dao/TaskDao.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/dao/TaskDao.kt @@ -34,6 +34,9 @@ interface TaskDao { @Query("UPDATE Task SET status = :status WHERE id = :id") fun updateStatus(id: Long, status: Int) + @Query("UPDATE Task SET status=:status WHERE id IN (:ids)") + fun updateStatusByIds(ids: List, status: Int) + @Query("SELECT * FROM Task where id=:id") fun get(id: Long): Single @@ -46,6 +49,9 @@ interface TaskDao { @Query("SELECT * FROM Task where type >= 1000 ORDER BY id DESC") fun pagingSourceMine(): PagingSource + @Query("SELECT * FROM Task ORDER BY id DESC") + fun getAll(): Single> + @Transaction @RawQuery(observedEntities = [Task::class]) fun getAllRaw(query: SupportSQLiteQuery): List diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/repository/TaskRepository.kt b/app/src/main/java/com/idormy/sms/forwarder/database/repository/TaskRepository.kt index bbd7d118..d7da8200 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/repository/TaskRepository.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/repository/TaskRepository.kt @@ -4,6 +4,7 @@ import androidx.annotation.WorkerThread import androidx.sqlite.db.SimpleSQLiteQuery import com.idormy.sms.forwarder.database.dao.TaskDao import com.idormy.sms.forwarder.database.entity.Task +import io.reactivex.Single import java.util.Date class TaskRepository(private val taskDao: TaskDao) { @@ -20,10 +21,14 @@ class TaskRepository(private val taskDao: TaskDao) { fun updateExecTime(taskId: Long, lastExecTime: Date, nextExecTime: Date, status: Int) = taskDao.updateExecTime(taskId, lastExecTime, nextExecTime, status) + fun updateStatusByIds(ids: List, status: Int) = taskDao.updateStatusByIds(ids, status) + fun get(id: Long) = taskDao.get(id) suspend fun getOne(id: Long) = taskDao.getOne(id) + fun getAll(): Single> = taskDao.getAll() + fun getAllNonCache(): List { val query = SimpleSQLiteQuery("SELECT * FROM Task ORDER BY id ASC") return taskDao.getAllRaw(query) diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/action/TaskActionSetting.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/action/TaskActionSetting.kt new file mode 100644 index 00000000..4b935a0d --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/action/TaskActionSetting.kt @@ -0,0 +1,10 @@ +package com.idormy.sms.forwarder.entity.action + +import com.idormy.sms.forwarder.database.entity.Task +import java.io.Serializable + +data class TaskActionSetting( + var description: String = "", //描述 + var status: Int = 1, //状态:0-禁用;1-启用 + var taskList: List, //自动任务列表 +) : Serializable diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/TasksEditFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/TasksEditFragment.kt index 39ead034..bbe34b58 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/TasksEditFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/TasksEditFragment.kt @@ -208,6 +208,13 @@ class TasksEditFragment : BaseFragment(), View.OnClic CoreAnim.slide, R.drawable.auto_task_icon_resend ), + PageInfo( + getString(R.string.task_task), + "com.idormy.sms.forwarder.fragment.action.TaskActionFragment", + "{\"\":\"\"}", + CoreAnim.slide, + R.drawable.auto_task_icon_task + ), ) override fun initArgs() { diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/action/TaskActionFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/action/TaskActionFragment.kt new file mode 100644 index 00000000..0ef32622 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/action/TaskActionFragment.kt @@ -0,0 +1,303 @@ +package com.idormy.sms.forwarder.fragment.action + +import android.annotation.SuppressLint +import android.content.Intent +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.work.Data +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.TaskRecyclerAdapter +import com.idormy.sms.forwarder.adapter.base.ItemMoveCallback +import com.idormy.sms.forwarder.adapter.spinner.TaskSpinnerAdapter +import com.idormy.sms.forwarder.adapter.spinner.TaskSpinnerItem +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.core.Core +import com.idormy.sms.forwarder.database.entity.Task +import com.idormy.sms.forwarder.databinding.FragmentTasksActionTaskBinding +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.TaskSetting +import com.idormy.sms.forwarder.entity.action.TaskActionSetting +import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_ACTION +import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_ACTION +import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_ACTION +import com.idormy.sms.forwarder.utils.Log +import com.idormy.sms.forwarder.utils.STATUS_OFF +import com.idormy.sms.forwarder.utils.TASK_ACTION_TASK +import com.idormy.sms.forwarder.utils.TaskWorker +import com.idormy.sms.forwarder.utils.XToastUtils +import com.idormy.sms.forwarder.workers.ActionWorker +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.utils.CountDownButtonHelper +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xutil.resource.ResUtils.getDrawable +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.util.Date + +@Page(name = "Task") +@Suppress("PrivatePropertyName", "DEPRECATION") +class TaskActionFragment : BaseFragment(), View.OnClickListener { + + private val TAG: String = TaskActionFragment::class.java.simpleName + private var titleBar: TitleBar? = null + private var mCountDownHelper: CountDownButtonHelper? = null + + //所有自动任务下拉框 + private var taskListAll = mutableListOf() + private val taskSpinnerList = mutableListOf() + private lateinit var taskSpinnerAdapter: TaskSpinnerAdapter<*> + + //已选自动任务列表 + private var taskId = 0L + private var taskListSelected = mutableListOf() + private lateinit var taskRecyclerView: RecyclerView + private lateinit var taskRecyclerAdapter: TaskRecyclerAdapter + + @JvmField + @AutoWired(name = KEY_EVENT_DATA_ACTION) + var eventData: String? = null + + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentTasksActionTaskBinding { + return FragmentTasksActionTaskBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_task) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + //测试按钮增加倒计时,避免重复点击 + mCountDownHelper = CountDownButtonHelper(binding!!.btnTest, 1) + mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener { + override fun onCountDown(time: Int) { + binding!!.btnTest.text = String.format(getString(R.string.seconds_n), time) + } + + override fun onFinished() { + binding!!.btnTest.text = getString(R.string.test) + //获取自动任务列表 + getTaskList() + } + }) + + Log.d(TAG, "initViews eventData:$eventData") + if (eventData != null) { + val settingVo = Gson().fromJson(eventData, TaskActionSetting::class.java) + binding!!.rgStatus.check(if (settingVo.status == 1) R.id.rb_status_enable else R.id.rb_status_disable) + Log.d(TAG, settingVo.taskList.toString()) + settingVo.taskList.forEach { + taskId = it.id + taskListSelected.add(it) + } + Log.d(TAG, "initViews settingVo:$settingVo") + } + + //初始化自动任务下拉框 + initTask() + } + + override fun onDestroyView() { + if (mCountDownHelper != null) mCountDownHelper!!.recycle() + super.onDestroyView() + } + + @SuppressLint("SetTextI18n") + override fun initListeners() { + binding!!.btnTest.setOnClickListener(this) + binding!!.btnDel.setOnClickListener(this) + binding!!.btnSave.setOnClickListener(this) + } + + @SingleClick + override fun onClick(v: View) { + try { + when (v.id) { + R.id.btn_test -> { + mCountDownHelper?.start() + try { + val settingVo = checkSetting() + Log.d(TAG, settingVo.toString()) + val taskAction = TaskSetting(TASK_ACTION_TASK, getString(R.string.task_task), settingVo.description, Gson().toJson(settingVo), requestCode) + val taskActionsJson = Gson().toJson(arrayListOf(taskAction)) + val msgInfo = MsgInfo("task", getString(R.string.task_task), settingVo.description, Date(), getString(R.string.task_task)) + val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build() + val actionRequest = OneTimeWorkRequestBuilder().setInputData(actionData).build() + WorkManager.getInstance().enqueue(actionRequest) + } catch (e: Exception) { + mCountDownHelper?.finish() + e.printStackTrace() + Log.e(TAG, "onClick error: ${e.message}") + XToastUtils.error(e.message.toString(), 30000) + } + return + } + + R.id.btn_del -> { + popToBack() + return + } + + R.id.btn_save -> { + val settingVo = checkSetting() + val intent = Intent() + intent.putExtra(KEY_BACK_DESCRIPTION_ACTION, settingVo.description) + intent.putExtra(KEY_BACK_DATA_ACTION, Gson().toJson(settingVo)) + setFragmentResult(TASK_ACTION_TASK, intent) + popToBack() + return + } + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString(), 30000) + e.printStackTrace() + Log.e(TAG, "onClick error: ${e.message}") + } + } + + //初始化自动任务 + @SuppressLint("SetTextI18n", "NotifyDataSetChanged") + private fun initTask() { + //初始化自动任务下拉框 + binding!!.spTask.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long -> + try { + val item = taskSpinnerAdapter.getItemSource(position) as TaskSpinnerItem + taskId = item.id!! + if (taskId > 0L) { + taskListSelected.forEach { + if (taskId == it.id) { + XToastUtils.warning(getString(R.string.task_contains_tips)) + return@setOnItemClickListener + } + } + taskListAll.forEach { + if (taskId == it.id) { + taskListSelected.add(it) + } + } + taskRecyclerAdapter.notifyDataSetChanged() + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + } + } + + // 初始化已选自动任务列表 RecyclerView 和 Adapter + taskRecyclerView = binding!!.recyclerTasks + taskRecyclerAdapter = TaskRecyclerAdapter(taskListSelected, { position -> + taskListSelected.removeAt(position) + taskRecyclerAdapter.notifyItemRemoved(position) + taskRecyclerAdapter.notifyItemRangeChanged(position, taskListSelected.size) // 更新索引 + }) + taskRecyclerView.apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = taskRecyclerAdapter + } + val taskMoveCallback = ItemMoveCallback(object : ItemMoveCallback.Listener { + override fun onItemMove(fromPosition: Int, toPosition: Int) { + Log.d(TAG, "onItemMove: $fromPosition $toPosition") + taskRecyclerAdapter.onItemMove(fromPosition, toPosition) + taskListSelected = taskRecyclerAdapter.itemList + } + + override fun onDragFinished() { + taskListSelected = taskRecyclerAdapter.itemList + //taskRecyclerAdapter.notifyDataSetChanged() + Log.d(TAG, "onDragFinished: $taskListSelected") + } + }) + val taskTouchHelper = ItemTouchHelper(taskMoveCallback) + taskTouchHelper.attachToRecyclerView(taskRecyclerView) + taskRecyclerAdapter.setTouchHelper(taskTouchHelper) + + //获取自动任务列表 + getTaskList() + } + + //获取自动任务列表 + private fun getTaskList() { + Core.task.getAll().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(object : SingleObserver> { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + Log.e(TAG, "getTaskList error: ${e.message}") + } + + @SuppressLint("NotifyDataSetChanged") + override fun onSuccess(taskList: List) { + if (taskList.isEmpty()) { + XToastUtils.error(R.string.add_task_first) + return + } + + taskSpinnerList.clear() + taskListAll = taskList as MutableList + for (task in taskList) { + val name = if (task.name.length > 20) task.name.substring(0, 19) else task.name + taskSpinnerList.add(TaskSpinnerItem(name, getDrawable(if (STATUS_OFF == task.status) task.greyImageId else task.imageId), task.id, task.status)) + } + taskSpinnerAdapter = TaskSpinnerAdapter(taskSpinnerList).setIsFilterKey(true).setFilterColor("#EF5362").setBackgroundSelector(R.drawable.selector_custom_spinner_bg) + binding!!.spTask.setAdapter(taskSpinnerAdapter) + //taskSpinnerAdapter.notifyDataSetChanged() + + //更新taskListSelected的状态与名称 + taskListSelected.forEach { + taskListAll.forEach { task -> + if (it.id == task.id) { + //it.name = task.name + it.status = task.status + } + } + } + taskRecyclerAdapter.notifyDataSetChanged() + + } + }) + } + + //检查设置 + @SuppressLint("SetTextI18n") + private fun checkSetting(): TaskActionSetting { + if (taskListSelected.isEmpty() || taskId == 0L) { + throw Exception(getString(R.string.new_task_first)) + } + + val description = StringBuilder() + val status: Int + if (binding!!.rgStatus.checkedRadioButtonId == R.id.rb_status_enable) { + status = 1 + description.append(getString(R.string.enable)) + } else { + status = 0 + description.append(getString(R.string.disable)) + } + description.append(getString(R.string.menu_tasks)).append(", ").append(getString(R.string.specified_task)).append(": ") + description.append(taskListSelected.joinToString(",") { "[${it.id}]${it.name}" }) + + return TaskActionSetting(description.toString(), status, taskListSelected) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt index 104a908a..0fb6924a 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt @@ -248,6 +248,7 @@ const val TASK_ACTION_RULE = 2006 const val TASK_ACTION_SENDER = 2007 const val TASK_ACTION_ALARM = 2008 const val TASK_ACTION_RESEND = 2009 +const val TASK_ACTION_TASK = 2010 const val SP_BATTERY_INFO = "battery_info" const val SP_BATTERY_STATUS = "battery_status" @@ -269,7 +270,7 @@ const val SP_LOCK_SCREEN_ACTION = "lock_screen_action" const val DELAY_TIME_AFTER_SIM_READY = 5000L //切换语言需要替换的自定义模板标签列表 -val TAG_LANG = arrayOf("zh_CN", "zh_TW", "en") +//val TAG_LANG = arrayOf("zh_CN", "zh_TW", "en") val TAG_LIST = arrayOf( mapOf("zh_CN" to "{{来源号码}}", "zh_TW" to "{{來源號碼}}", "en" to "{{FROM}}"), mapOf("zh_CN" to "{{短信内容}}", "zh_TW" to "{{簡訊內容}}", "en" to "{{SMS}}"), diff --git a/app/src/main/java/com/idormy/sms/forwarder/workers/ActionWorker.kt b/app/src/main/java/com/idormy/sms/forwarder/workers/ActionWorker.kt index d0cb8698..9f51a740 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/workers/ActionWorker.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/workers/ActionWorker.kt @@ -25,6 +25,7 @@ import com.idormy.sms.forwarder.entity.action.RuleSetting import com.idormy.sms.forwarder.entity.action.SenderSetting import com.idormy.sms.forwarder.entity.action.SettingsSetting import com.idormy.sms.forwarder.entity.action.SmsSetting +import com.idormy.sms.forwarder.entity.action.TaskActionSetting import com.idormy.sms.forwarder.service.HttpServerService import com.idormy.sms.forwarder.service.LocationService import com.idormy.sms.forwarder.utils.CacheUtils @@ -49,6 +50,7 @@ import com.idormy.sms.forwarder.utils.TASK_ACTION_RULE import com.idormy.sms.forwarder.utils.TASK_ACTION_SENDER import com.idormy.sms.forwarder.utils.TASK_ACTION_SENDSMS import com.idormy.sms.forwarder.utils.TASK_ACTION_SETTINGS +import com.idormy.sms.forwarder.utils.TASK_ACTION_TASK import com.idormy.sms.forwarder.utils.TaskWorker import com.idormy.sms.forwarder.utils.task.ConditionUtils import com.jeremyliao.liveeventbus.LiveEventBus @@ -310,6 +312,22 @@ class ActionWorker(context: Context, params: WorkerParameters) : CoroutineWorker writeLog(String.format(getString(R.string.successful_execution), senderSetting.description), "SUCCESS") } + TASK_ACTION_TASK -> { + val taskActionSetting = Gson().fromJson(action.setting, TaskActionSetting::class.java) + if (taskActionSetting == null) { + writeLog("taskActionSetting is null") + continue + } + + val ids = taskActionSetting.taskList.map { it.id } + if (ids.isNotEmpty()) { + Core.task.updateStatusByIds(ids, taskActionSetting.status) + } + + successNum++ + writeLog(String.format(getString(R.string.successful_execution), taskActionSetting.description), "SUCCESS") + } + TASK_ACTION_ALARM -> { val alarmSetting = Gson().fromJson(action.setting, AlarmSetting::class.java) if (alarmSetting == null) { diff --git a/app/src/main/res/drawable/auto_task_icon_task.xml b/app/src/main/res/drawable/auto_task_icon_task.xml new file mode 100644 index 00000000..fb2e732d --- /dev/null +++ b/app/src/main/res/drawable/auto_task_icon_task.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/auto_task_icon_task_grey.xml b/app/src/main/res/drawable/auto_task_icon_task_grey.xml new file mode 100644 index 00000000..5f539b21 --- /dev/null +++ b/app/src/main/res/drawable/auto_task_icon_task_grey.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_menu_task.xml b/app/src/main/res/drawable/ic_menu_task.xml index d2e3d1ff..68db87d7 100644 --- a/app/src/main/res/drawable/ic_menu_task.xml +++ b/app/src/main/res/drawable/ic_menu_task.xml @@ -3,7 +3,13 @@ android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> - + + + diff --git a/app/src/main/res/layout/adapter_task_list_item.xml b/app/src/main/res/layout/adapter_task_list_item.xml new file mode 100644 index 00000000..cd17dd39 --- /dev/null +++ b/app/src/main/res/layout/adapter_task_list_item.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_tasks_action_task.xml b/app/src/main/res/layout/fragment_tasks_action_task.xml new file mode 100644 index 00000000..e1b6e6cb --- /dev/null +++ b/app/src/main/res/layout/fragment_tasks_action_task.xml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 57b88a67..d0e58c72 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -227,9 +227,11 @@ Please add a new sender and then choose it. Please add a new rule and then choose it. Please add a new frpc and then choose it. + Please add a new task and then choose it. Please add a sender first. Please add a rule first. Please add a frpc first. + Please add a task first. Select Sender Rule tester: Test SIM Slot @@ -830,6 +832,7 @@ Drop-down selection, keyword fuzzy match Drop-down selection, keyword fuzzy match Drop-down selection, keyword fuzzy match + Drop-down selection, keyword fuzzy match Installed Apps Extra Apps One package name per line\nEnable async loading of the App list for selection. @@ -982,6 +985,7 @@ [Note] The sender is already in the list, no need to add it again! [Note] The rule is already in the list, no need to add it again! [Note] The frpc is already in the list, no need to add it again! + [Note] The task is already in the list, no need to add it again! Local Call: Remote SMS: Clear @@ -1199,15 +1203,17 @@ Settings Control the configuration switch of "Settings". Rules On/Off - Control enabling/disabling of "Rules" + Control the enable/disable of "Rules" Channels On/Off - Control enabling/disabling of "Senders" + Control the enable/disable of "Senders" Alarm Alarm Resend Message Resend forwarded records since N hours ago, 0=ALL Resend forwarding records since %s hours ago for %s At least one "Original Result" must be selected + Tasks On/Off + Control the enable/disable of the "Auto Task" Second Minute @@ -1323,6 +1329,8 @@ Enter Rule IDs (comma-separated) Specified Sender Enter Sender IDs (comma-separated) + Specified Task + Enter Task IDs (comma-separated) English Current Activity language: diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 76ab74f8..2ef0e16c 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -228,9 +228,11 @@ 请选择发送通道(若无,请先添加) 请选择发送通道(若无,请先添加) 请选择发送通道(若无,请先添加) + 请选择自动任务(若无,请先添加) 请先去设置发送通道页面添加 请先去设置转发规则页面添加 请先去设置FRPC页面添加 + 请先去设置自动任务页面添加 发送通道 转发规则测试 测试模拟的接收卡槽 @@ -831,6 +833,7 @@ 下拉选择,关键字模糊匹配 下拉选择,关键字模糊匹配 下拉选择,关键字模糊匹配 + 下拉选择,关键字模糊匹配 已装APP列表 额外消除应用通知 一行一个包名\n开启异步加载App列表以便选择 @@ -983,6 +986,7 @@ 【注意】该发送通道已经在列表中,无需重复添加! 【注意】该转发规则已经在列表中,无需重复添加! 【注意】该Frpc已经在列表中,无需重复添加! + 【注意】该自动任务已经在列表中,无需重复添加! 本地呼叫: 远程发短信: 清除 @@ -1209,6 +1213,8 @@ 自动重发N小时以来的转发记录,0=全部 自动重发%s小时以来%s的转发记录 必须至少选择一个【原转发状态】 + 启停任务 + 控制【自动任务】的启用/禁用 @@ -1324,6 +1330,8 @@ 填写转发规则的id,多个以半角逗号分隔 指定通道 填写发送通道的id,多个以半角逗号分隔 + 指定任务 + 填写自动任务的id,多个以半角逗号分隔 简体中文 当前 Activity 语种: diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 8705c608..299aa091 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -228,9 +228,11 @@ 請選擇發送通道(若無,請先添加) 請選擇轉發規則(若無,請先添加) 請選擇FRPC(若無,請先添加) + 請選擇自動任務(若無,請先添加) 請先去設置發送通道頁面添加 請先去設置轉發規則頁面添加 請先去設置FRPC頁面添加 + 請先去設置自動任務頁面添加 發送通道 轉發規則測試 測試模擬的接收卡槽 @@ -831,6 +833,7 @@ 下拉選擇,關鍵字模糊匹配 下拉選擇,關鍵字模糊匹配 下拉選擇,關鍵字模糊匹配 + 下拉選擇,關鍵字模糊匹配 已裝APP列表 額外消除應用通知 一行一個包名\n開啟異步加載App列表以便選擇 @@ -983,6 +986,7 @@ 【注意】該發送通道已經在列表中,無需重複添加! 【注意】該轉發規則已經在列表中,無需重複添加! 【注意】該Frpc已經在列表中,無需重複添加! + 【注意】該自動任務已經在列表中,無需重複添加! 本地呼叫: 遠程發簡訊: 清除 @@ -1209,6 +1213,8 @@ 自動重發N小時以來的轉發記錄,0=全部 自動重發%s小時以來%s的轉發記錄 必须至少选择一個「原轉發狀態」 + 啟停任務 + 控制【自動任務】的啟用/禁用 @@ -1324,7 +1330,8 @@ 填寫轉發規則的ID,多個以半角逗號分隔 指定通道 填寫發送通道的ID,多個以半角逗號分隔 - + 指定任務 + 填寫自動任務的ID,多個以半角逗號分隔 繁體中文 當前 Activity 語種: diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f204f752..841ac51c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -228,9 +228,11 @@ 请选择发送通道(若无,请先添加) 请选择转发规则(若无,请先添加) 请选择Frpc(若无,请先添加) + 请选择自动任务(若无,请先添加) 请先去设置发送通道页面添加 请先去设置转发规则页面添加 请先去设置Frpc页面添加 + 请先去设置自动任务页面添加 发送通道 转发规则测试 测试模拟的接收卡槽 @@ -831,6 +833,7 @@ 下拉选择,关键字模糊匹配 下拉选择,关键字模糊匹配 下拉选择,关键字模糊匹配 + 下拉选择,关键字模糊匹配 已装APP列表 额外消除应用通知 一行一个包名\n开启异步加载App列表以便选择 @@ -983,6 +986,7 @@ 【注意】该发送通道已经在列表中,无需重复添加! 【注意】该转发规则已经在列表中,无需重复添加! 【注意】该Frpc已经在列表中,无需重复添加! + 【注意】该自动任务已经在列表中,无需重复添加! 本地呼叫: 远程发短信: 清除 @@ -1209,6 +1213,8 @@ 自动重发N小时以来的转发记录,0=全部 自动重发%s小时以来%s的转发记录 必须至少选择一个【原转发状态】 + 启停任务 + 控制【自动任务】的启用/禁用 @@ -1324,6 +1330,8 @@ 填写转发规则的id,多个以半角逗号分隔 指定通道 填写发送通道的id,多个以半角逗号分隔 + 指定任务 + 填写发送通道的id,多个以半角逗号分隔 跟随系统 当前 Activity 语种: