新增:转发规则可以设置免打扰(禁用转发)时间段 #318

This commit is contained in:
pppscn 2023-07-21 14:38:23 +08:00
parent 3c0bead575
commit 2bee1dad87
5 changed files with 157 additions and 5 deletions

View File

@ -15,7 +15,7 @@ import com.idormy.sms.forwarder.utils.DATABASE_NAME
@Database( @Database(
entities = [Frpc::class, Msg::class, Logs::class, Rule::class, Sender::class], entities = [Frpc::class, Msg::class, Logs::class, Rule::class, Sender::class],
views = [LogsDetail::class], views = [LogsDetail::class],
version = 15, version = 16,
exportSchema = false exportSchema = false
) )
@TypeConverters(ConvertersDate::class) @TypeConverters(ConvertersDate::class)
@ -96,6 +96,7 @@ custom_domains = smsf.demo.com
MIGRATION_12_13, MIGRATION_12_13,
MIGRATION_13_14, MIGRATION_13_14,
MIGRATION_14_15, MIGRATION_14_15,
MIGRATION_15_16,
) )
/*if (BuildConfig.DEBUG) { /*if (BuildConfig.DEBUG) {
@ -364,6 +365,14 @@ CREATE TABLE "Logs" (
} }
} }
//免打扰(禁用转发)时间段
private val MIGRATION_15_16 = object : Migration(15, 16) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("Alter table rule add column silent_period_start INTEGER NOT NULL DEFAULT 0 ")
database.execSQL("Alter table rule add column silent_period_end INTEGER NOT NULL DEFAULT 0 ")
}
}
} }
} }

View File

@ -47,6 +47,9 @@ data class Rule(
@ColumnInfo(name = "time") var time: Date = Date(), @ColumnInfo(name = "time") var time: Date = Date(),
@ColumnInfo(name = "sender_list", defaultValue = "") var senderList: List<Sender>, @ColumnInfo(name = "sender_list", defaultValue = "") var senderList: List<Sender>,
@ColumnInfo(name = "sender_logic", defaultValue = "ALL") var senderLogic: String = "ALL", @ColumnInfo(name = "sender_logic", defaultValue = "ALL") var senderLogic: String = "ALL",
//免打扰(禁用转发)时间段
@ColumnInfo(name = "silent_period_start", defaultValue = "0") var silentPeriodStart: Int = 0,
@ColumnInfo(name = "silent_period_end", defaultValue = "0") var silentPeriodEnd: Int = 0,
) : Parcelable { ) : Parcelable {
companion object { companion object {

View File

@ -37,6 +37,8 @@ import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import com.xuexiang.xui.widget.picker.widget.builder.OptionsPickerBuilder
import com.xuexiang.xui.widget.picker.widget.listener.OnOptionsSelectListener
import com.xuexiang.xutil.XUtil import com.xuexiang.xutil.XUtil
import io.reactivex.SingleObserver import io.reactivex.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
@ -46,13 +48,18 @@ import kotlinx.coroutines.*
import java.util.* import java.util.*
@Page(name = "转发规则·编辑器") @Page(name = "转发规则·编辑器")
@Suppress("PrivatePropertyName") @Suppress("PrivatePropertyName", "DEPRECATION")
class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClickListener, CompoundButton.OnCheckedChangeListener { class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClickListener, CompoundButton.OnCheckedChangeListener {
private val TAG: String = RulesEditFragment::class.java.simpleName private val TAG: String = RulesEditFragment::class.java.simpleName
var titleBar: TitleBar? = null var titleBar: TitleBar? = null
private val viewModel by viewModels<RuleViewModel> { BaseViewModelFactory(context) } private val viewModel by viewModels<RuleViewModel> { BaseViewModelFactory(context) }
//免打扰(禁用转发)时间段
private val mTimeOption = DataProvider.timePeriodOption
private var silentPeriodStart = 0
private var silentPeriodEnd = 0
//当前发送通道 //当前发送通道
var senderId = 0L var senderId = 0L
var senderListSelected: MutableList<Sender> = mutableListOf() var senderListSelected: MutableList<Sender> = mutableListOf()
@ -119,6 +126,7 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
//监听已安装App信息列表加载完成事件 //监听已安装App信息列表加载完成事件
LiveEventBus.get(EVENT_LOAD_APP_LIST, String::class.java).observeStickyForever(appListObserver) LiveEventBus.get(EVENT_LOAD_APP_LIST, String::class.java).observeStickyForever(appListObserver)
} }
"call" -> { "call" -> {
titleBar?.setTitle(R.string.call_rule) titleBar?.setTitle(R.string.call_rule)
binding!!.rbContent.visibility = View.GONE binding!!.rbContent.visibility = View.GONE
@ -130,6 +138,7 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
binding!!.btInsertTitleApp.visibility = View.GONE binding!!.btInsertTitleApp.visibility = View.GONE
binding!!.btInsertContentApp.visibility = View.GONE binding!!.btInsertContentApp.visibility = View.GONE
} }
else -> { else -> {
titleBar?.setTitle(R.string.sms_rule) titleBar?.setTitle(R.string.sms_rule)
binding!!.rbPackageName.visibility = View.GONE binding!!.rbPackageName.visibility = View.GONE
@ -152,6 +161,7 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
} }
override fun initListeners() { override fun initListeners() {
binding!!.btnSilentPeriod.setOnClickListener(this)
binding!!.btInsertSender.setOnClickListener(this) binding!!.btInsertSender.setOnClickListener(this)
binding!!.btInsertContent.setOnClickListener(this) binding!!.btInsertContent.setOnClickListener(this)
binding!!.btInsertSenderApp.setOnClickListener(this) binding!!.btInsertSenderApp.setOnClickListener(this)
@ -164,6 +174,7 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
binding!!.btnDel.setOnClickListener(this) binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this) binding!!.btnSave.setOnClickListener(this)
binding!!.sbStatus.setOnCheckedChangeListener(this)
binding!!.sbSmsTemplate.setOnCheckedChangeListener(this) binding!!.sbSmsTemplate.setOnCheckedChangeListener(this)
binding!!.sbRegexReplace.setOnCheckedChangeListener(this) binding!!.sbRegexReplace.setOnCheckedChangeListener(this)
@ -178,12 +189,14 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
binding!!.layoutMatchType.visibility = View.GONE binding!!.layoutMatchType.visibility = View.GONE
binding!!.layoutMatchValue.visibility = View.GONE binding!!.layoutMatchValue.visibility = View.GONE
} }
R.id.rb_multi_match -> { R.id.rb_multi_match -> {
binding!!.rgCheck.check(R.id.rb_is) binding!!.rgCheck.check(R.id.rb_is)
binding!!.tvMuRuleTips.visibility = View.VISIBLE binding!!.tvMuRuleTips.visibility = View.VISIBLE
binding!!.layoutMatchType.visibility = View.GONE binding!!.layoutMatchType.visibility = View.GONE
binding!!.layoutMatchValue.visibility = View.VISIBLE binding!!.layoutMatchValue.visibility = View.VISIBLE
} }
else -> { else -> {
binding!!.tvMuRuleTips.visibility = View.GONE binding!!.tvMuRuleTips.visibility = View.GONE
binding!!.layoutMatchType.visibility = View.VISIBLE binding!!.layoutMatchType.visibility = View.VISIBLE
@ -209,6 +222,10 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
when (buttonView?.id) { when (buttonView?.id) {
R.id.sb_status -> {
binding!!.layoutSilentPeriod.visibility = if (isChecked) View.VISIBLE else View.GONE
}
R.id.sb_sms_template -> { R.id.sb_sms_template -> {
if (isChecked) { if (isChecked) {
binding!!.layoutSmsTemplate.visibility = View.VISIBLE binding!!.layoutSmsTemplate.visibility = View.VISIBLE
@ -217,6 +234,7 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
binding!!.etSmsTemplate.setText("") binding!!.etSmsTemplate.setText("")
} }
} }
R.id.sb_regex_replace -> { R.id.sb_regex_replace -> {
if (isChecked) { if (isChecked) {
binding!!.layoutRegexReplace.visibility = View.VISIBLE binding!!.layoutRegexReplace.visibility = View.VISIBLE
@ -225,6 +243,7 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
binding!!.etRegexReplace.setText("") binding!!.etRegexReplace.setText("")
} }
} }
else -> {} else -> {}
} }
} }
@ -234,43 +253,66 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
try { try {
val etSmsTemplate: EditText = binding!!.etSmsTemplate val etSmsTemplate: EditText = binding!!.etSmsTemplate
when (v.id) { when (v.id) {
R.id.btn_silent_period -> {
OptionsPickerBuilder(context, OnOptionsSelectListener { _: View?, options1: Int, options2: Int, _: Int ->
silentPeriodStart = options1
silentPeriodEnd = options2
val txt = mTimeOption[options1] + " ~ " + mTimeOption[options2]
binding!!.tvSilentPeriod.text = txt
XToastUtils.toast(txt)
return@OnOptionsSelectListener false
}).setTitleText(getString(R.string.select_time_period)).setSelectOptions(silentPeriodStart, silentPeriodEnd).build<Any>().also {
it.setNPicker(mTimeOption, mTimeOption)
it.show()
}
}
R.id.bt_insert_sender -> { R.id.bt_insert_sender -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_from)) CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_from))
return return
} }
R.id.bt_insert_content -> { R.id.bt_insert_content -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_sms)) CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_sms))
return return
} }
R.id.bt_insert_sender_app -> { R.id.bt_insert_sender_app -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_package_name)) CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_package_name))
return return
} }
R.id.bt_insert_title_app -> { R.id.bt_insert_title_app -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_title)) CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_title))
return return
} }
R.id.bt_insert_content_app -> { R.id.bt_insert_content_app -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_msg)) CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_msg))
return return
} }
R.id.bt_insert_extra -> { R.id.bt_insert_extra -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_card_slot)) CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_card_slot))
return return
} }
R.id.bt_insert_time -> { R.id.bt_insert_time -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_receive_time)) CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_receive_time))
return return
} }
R.id.bt_insert_device_name -> { R.id.bt_insert_device_name -> {
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_device_name)) CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_device_name))
return return
} }
R.id.btn_test -> { R.id.btn_test -> {
val ruleNew = checkForm() val ruleNew = checkForm()
testRule(ruleNew) testRule(ruleNew)
return return
} }
R.id.btn_del -> { R.id.btn_del -> {
if (ruleId <= 0 || isClone) { if (ruleId <= 0 || isClone) {
popToBack() popToBack()
@ -284,6 +326,7 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
}.show() }.show()
return return
} }
R.id.btn_save -> { R.id.btn_save -> {
val ruleNew = checkForm() val ruleNew = checkForm()
if (isClone) ruleNew.id = 0 if (isClone) ruleNew.id = 0
@ -301,7 +344,11 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
} }
//初始化发送通道下拉框 //初始化发送通道下拉框
@SuppressLint("SetTextI18n")
private fun initSenderSpinner() { private fun initSenderSpinner() {
//免打扰(禁用转发)时间段
binding!!.tvSilentPeriod.text = mTimeOption[silentPeriodStart] + " ~ " + mTimeOption[silentPeriodEnd]
AppDatabase.getInstance(requireContext()).senderDao().getAll().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(object : SingleObserver<List<Sender>> { AppDatabase.getInstance(requireContext()).senderDao().getAll().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(object : SingleObserver<List<Sender>> {
override fun onSubscribe(d: Disposable) {} override fun onSubscribe(d: Disposable) {}
@ -503,6 +550,8 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
binding!!.sbRegexReplace.isChecked = !TextUtils.isEmpty(rule.regexReplace.trim()) binding!!.sbRegexReplace.isChecked = !TextUtils.isEmpty(rule.regexReplace.trim())
binding!!.etRegexReplace.setText(rule.regexReplace.trim()) binding!!.etRegexReplace.setText(rule.regexReplace.trim())
binding!!.sbStatus.isChecked = rule.statusChecked binding!!.sbStatus.isChecked = rule.statusChecked
silentPeriodStart = rule.silentPeriodStart
silentPeriodEnd = rule.silentPeriodEnd
//初始化发送通道下拉框 //初始化发送通道下拉框
initSenderSpinner() initSenderSpinner()
@ -565,7 +614,7 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
// throw Exception(getString(R.string.invalid_rule_status)) // throw Exception(getString(R.string.invalid_rule_status))
//} //}
return Rule(ruleId, ruleType, filed, check, value, senderId, smsTemplate, regexReplace, simSlot, status, Date(), senderListSelected, senderLogic) return Rule(ruleId, ruleType, filed, check, value, senderId, smsTemplate, regexReplace, simSlot, status, Date(), senderListSelected, senderLogic, silentPeriodStart, silentPeriodEnd)
} }
//检查多重匹配规则是否正确 //检查多重匹配规则是否正确
@ -635,7 +684,7 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
val testSim = "SIM" + (simSlot + 1) val testSim = "SIM" + (simSlot + 1)
val ruleSim: String = rule.simSlot val ruleSim: String = rule.simSlot
if (ruleSim != "ALL" && ruleSim != testSim) { if (ruleSim != "ALL" && ruleSim != testSim) {
throw java.lang.Exception(getString(R.string.card_slot_does_not_match)) throw Exception(getString(R.string.card_slot_does_not_match))
} }
//获取卡槽信息 //获取卡槽信息
@ -647,7 +696,7 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
val msgInfo = MsgInfo(ruleType, etFrom.text.toString(), etContent.text.toString(), Date(), simInfo, simSlot) val msgInfo = MsgInfo(ruleType, etFrom.text.toString(), etContent.text.toString(), Date(), simInfo, simSlot)
if (!rule.checkMsg(msgInfo)) { if (!rule.checkMsg(msgInfo)) {
throw java.lang.Exception(getString(R.string.unmatched_rule)) throw Exception(getString(R.string.unmatched_rule))
} }
Thread { Thread {

View File

@ -1,5 +1,6 @@
package com.idormy.sms.forwarder.utils package com.idormy.sms.forwarder.utils
import android.annotation.SuppressLint
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OneTimeWorkRequestBuilder
@ -19,7 +20,12 @@ import com.idormy.sms.forwarder.workers.SendWorker
import com.idormy.sms.forwarder.workers.UpdateLogsWorker import com.idormy.sms.forwarder.workers.UpdateLogsWorker
import com.xuexiang.xui.utils.ResUtils import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xutil.XUtil import com.xuexiang.xutil.XUtil
import java.text.ParsePosition
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
@Suppress("DEPRECATION")
object SendUtils { object SendUtils {
private const val TAG = "SendUtils" private const val TAG = "SendUtils"
@ -58,6 +64,7 @@ object SendUtils {
} }
//匹配发送通道发送消息 //匹配发送通道发送消息
@SuppressLint("SimpleDateFormat")
fun sendMsgSender(msgInfo: MsgInfo, rule: Rule, senderIndex: Int = 0, logId: Long = 0L, msgId: Long = 0L) { fun sendMsgSender(msgInfo: MsgInfo, rule: Rule, senderIndex: Int = 0, logId: Long = 0L, msgId: Long = 0L) {
try { try {
val sender = rule.senderList[senderIndex] val sender = rule.senderList[senderIndex]
@ -67,6 +74,35 @@ object SendUtils {
senderLogic(0, msgInfo, rule, senderIndex, msgId) senderLogic(0, msgInfo, rule, senderIndex, msgId)
return return
} }
//免打扰(禁用转发)时间段
if (rule.silentPeriodStart != rule.silentPeriodEnd) {
val periodStartDay = Date()
var periodStartEnd = Date()
//跨天了
if (rule.silentPeriodStart > rule.silentPeriodEnd) {
val c: Calendar = Calendar.getInstance()
c.time = periodStartEnd
c.add(Calendar.DAY_OF_MONTH, 1)
periodStartEnd = c.time
}
val dateFmt = SimpleDateFormat("yyyy-MM-dd")
val mTimeOption = DataProvider.timePeriodOption
val periodStartStr = dateFmt.format(periodStartDay) + " " + mTimeOption[rule.silentPeriodStart] + ":00"
val periodEndStr = dateFmt.format(periodStartEnd) + " " + mTimeOption[rule.silentPeriodEnd] + ":00"
val timeFmt = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val periodStart = timeFmt.parse(periodStartStr, ParsePosition(0))?.time
val periodEnd = timeFmt.parse(periodEndStr, ParsePosition(0))?.time
val now = System.currentTimeMillis()
if (periodStart != null && periodEnd != null && now in periodStart..periodEnd) {
Log.d(TAG, "免打扰(禁用转发)时间段")
updateLogs(logId, 0, ResUtils.getString(R.string.silent_time_period))
senderLogic(0, msgInfo, rule, senderIndex, msgId)
return
}
}
when (sender.type) { when (sender.type) {
TYPE_DINGTALK_GROUP_ROBOT -> { TYPE_DINGTALK_GROUP_ROBOT -> {
val settingVo = Gson().fromJson(sender.jsonSetting, DingtalkGroupRobotSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, DingtalkGroupRobotSetting::class.java)

View File

@ -481,6 +481,61 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/layout_silent_period"
style="@style/ruleBarStyleWithSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingEnd="15dp"
tools:ignore="RtlSymmetry">
<LinearLayout
android:layout_width="180dp"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/silent_time_period"
android:textStyle="bold"
tools:ignore="RelativeOverlap" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/silent_time_period_tips"
android:textSize="9sp"
tools:ignore="SmallSp" />
</LinearLayout>
<TextView
android:id="@+id/tv_silent_period"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:gravity="center" />
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
android:id="@+id/btn_silent_period"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:gravity="center"
android:padding="5dp"
android:text="@string/select"
android:textColor="@color/white"
android:textSize="10sp"
app:sb_color_unpressed="@color/colorPrimary"
app:sb_ripple_color="@color/white"
app:sb_ripple_duration="500"
app:sb_shape_type="rectangle"
tools:ignore="SmallSp" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>