优化:单个转发规则支持绑定多个发送通道,且支持执行逻辑(全部执行/失败即止/成功即止) #247

优化:转发日志列表以原始信息为主,聚合展示转发日志(一对多)
This commit is contained in:
pppscn 2023-02-05 09:15:20 +08:00
parent b4870207d1
commit b79d3d8493
57 changed files with 4047 additions and 3204 deletions

View File

@ -17,10 +17,7 @@ import com.gyf.cactus.ext.cactus
import com.idormy.sms.forwarder.activity.MainActivity import com.idormy.sms.forwarder.activity.MainActivity
import com.idormy.sms.forwarder.core.Core import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.database.AppDatabase import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.database.repository.FrpcRepository import com.idormy.sms.forwarder.database.repository.*
import com.idormy.sms.forwarder.database.repository.LogsRepository
import com.idormy.sms.forwarder.database.repository.RuleRepository
import com.idormy.sms.forwarder.database.repository.SenderRepository
import com.idormy.sms.forwarder.entity.SimInfo import com.idormy.sms.forwarder.entity.SimInfo
import com.idormy.sms.forwarder.receiver.CactusReceiver import com.idormy.sms.forwarder.receiver.CactusReceiver
import com.idormy.sms.forwarder.service.BatteryService import com.idormy.sms.forwarder.service.BatteryService
@ -48,6 +45,7 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
val applicationScope = CoroutineScope(SupervisorJob()) val applicationScope = CoroutineScope(SupervisorJob())
val database by lazy { AppDatabase.getInstance(this) } val database by lazy { AppDatabase.getInstance(this) }
val frpcRepository by lazy { FrpcRepository(database.frpcDao()) } val frpcRepository by lazy { FrpcRepository(database.frpcDao()) }
val msgRepository by lazy { MsgRepository(database.msgDao()) }
val logsRepository by lazy { LogsRepository(database.logsDao()) } val logsRepository by lazy { LogsRepository(database.logsDao()) }
val ruleRepository by lazy { RuleRepository(database.ruleDao()) } val ruleRepository by lazy { RuleRepository(database.ruleDao()) }
val senderRepository by lazy { SenderRepository(database.senderDao()) } val senderRepository by lazy { SenderRepository(database.senderDao()) }

View File

@ -269,7 +269,7 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(),
.negativeText(R.string.lab_no) .negativeText(R.string.lab_no)
.onPositive { _: MaterialDialog?, _: DialogAction? -> .onPositive { _: MaterialDialog?, _: DialogAction? ->
AppDatabase.getInstance(this) AppDatabase.getInstance(this)
.logsDao() .msgDao()
.deleteAll(logsType) .deleteAll(logsType)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())

View File

@ -9,7 +9,6 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.idormy.sms.forwarder.adapter.LogsPagingAdapter.MyViewHolder import com.idormy.sms.forwarder.adapter.LogsPagingAdapter.MyViewHolder
import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender
import com.idormy.sms.forwarder.database.entity.Sender
import com.idormy.sms.forwarder.databinding.AdapterLogsCardViewListItemBinding import com.idormy.sms.forwarder.databinding.AdapterLogsCardViewListItemBinding
import com.xuexiang.xutil.data.DateUtils import com.xuexiang.xutil.data.DateUtils
@ -23,12 +22,12 @@ class LogsPagingAdapter(private val itemClickListener: OnItemClickListener) : Pa
override fun onBindViewHolder(holder: MyViewHolder, position: Int) { override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = getItem(position) val item = getItem(position)
if (item != null) { if (item != null) {
holder.binding.tvFrom.text = item.logs.from holder.binding.tvFrom.text = item.msg.from
holder.binding.tvTime.text = DateUtils.getFriendlyTimeSpanByNow(item.logs.time) holder.binding.tvTime.text = DateUtils.getFriendlyTimeSpanByNow(item.logs.time)
holder.binding.tvContent.text = item.logs.content holder.binding.tvContent.text = item.msg.content
holder.binding.ivSenderImage.setImageResource(Sender.getImageId(item.relation.sender.type)) //holder.binding.ivSenderImage.setImageResource(Sender.getImageId(item.sender.type))
holder.binding.ivStatusImage.setImageResource(item.logs.statusImageId) //holder.binding.ivStatusImage.setImageResource(item.logs.statusImageId)
holder.binding.ivSimImage.setImageResource(item.logs.simImageId) holder.binding.ivSimImage.setImageResource(item.msg.simImageId)
holder.binding.cardView.setOnClickListener { view: View? -> holder.binding.cardView.setOnClickListener { view: View? ->
itemClickListener.onItemClicked(view, item) itemClickListener.onItemClicked(view, item)

View File

@ -0,0 +1,78 @@
package com.idormy.sms.forwarder.adapter
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.MsgPagingAdapter.MyViewHolder
import com.idormy.sms.forwarder.database.entity.LogsDetail
import com.idormy.sms.forwarder.database.entity.MsgAndLogs
import com.idormy.sms.forwarder.databinding.AdapterLogsCardViewListItemBinding
import com.xuexiang.xutil.data.DateUtils
class MsgPagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter<MsgAndLogs, MyViewHolder>(diffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding = AdapterLogsCardViewListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = getItem(position)
if (item != null) {
holder.binding.tvFrom.text = item.msg.from
holder.binding.tvTime.text = DateUtils.getFriendlyTimeSpanByNow(item.msg.time)
holder.binding.tvContent.text = item.msg.content
//holder.binding.ivSenderImage.setImageResource(Sender.getImageId(item.sender.type))
//holder.binding.ivStatusImage.setImageResource(item.msg.statusImageId)
holder.binding.ivSimImage.setImageResource(item.msg.simImageId)
holder.binding.layoutLogs.removeAllViews()
for (logs in item.logsList) {
val layoutSenderItem = View.inflate(App.context, R.layout.item_logs, null) as LinearLayout
val ivSenderImage = layoutSenderItem.findViewById<ImageView>(R.id.iv_sender_image)
val ivSenderStatus = layoutSenderItem.findViewById<ImageView>(R.id.iv_sender_status)
val tvSenderName = layoutSenderItem.findViewById<TextView>(R.id.tv_sender_name)
ivSenderImage.setImageResource(logs.senderImageId)
ivSenderStatus.setImageResource(logs.statusImageId)
tvSenderName.text = logs.senderName
layoutSenderItem.setOnClickListener { view: View? ->
itemClickListener.onLogsClicked(view, logs)
}
holder.binding.layoutLogs.addView(layoutSenderItem)
}
holder.binding.cardView.setOnClickListener { view: View? ->
itemClickListener.onItemClicked(view, item)
}
}
}
class MyViewHolder(val binding: AdapterLogsCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root)
interface OnItemClickListener {
fun onItemClicked(view: View?, item: MsgAndLogs)
fun onLogsClicked(view: View?, item: LogsDetail)
fun onItemRemove(view: View?, id: Int)
}
companion object {
var diffCallback: DiffUtil.ItemCallback<MsgAndLogs> = object : DiffUtil.ItemCallback<MsgAndLogs>() {
override fun areItemsTheSame(oldItem: MsgAndLogs, newItem: MsgAndLogs): Boolean {
return oldItem.msg.id == newItem.msg.id
}
@SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(oldItem: MsgAndLogs, newItem: MsgAndLogs): Boolean {
return oldItem.msg === newItem.msg
}
}
}
}

View File

@ -4,15 +4,19 @@ import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.paging.PagingDataAdapter import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.RulePagingAdapter.MyViewHolder import com.idormy.sms.forwarder.adapter.RulePagingAdapter.MyViewHolder
import com.idormy.sms.forwarder.database.entity.RuleAndSender import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.databinding.AdapterRulesCardViewListItemBinding import com.idormy.sms.forwarder.databinding.AdapterRulesCardViewListItemBinding
class RulePagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter<RuleAndSender, MyViewHolder>(diffCallback) { class RulePagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter<Rule, MyViewHolder>(diffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding = AdapterRulesCardViewListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) val binding = AdapterRulesCardViewListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
@ -22,16 +26,22 @@ class RulePagingAdapter(private val itemClickListener: OnItemClickListener) : Pa
override fun onBindViewHolder(holder: MyViewHolder, position: Int) { override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = getItem(position) val item = getItem(position)
if (item != null) { if (item != null) {
holder.binding.ivRuleImage.setImageResource(item.rule.imageId) holder.binding.ivRuleImage.setImageResource(item.imageId)
holder.binding.ivRuleStatus.setImageResource(item.rule.statusImageId) holder.binding.ivRuleStatus.setImageResource(item.statusImageId)
holder.binding.tvRuleMatch.text = item.rule.ruleMatch holder.binding.tvRuleMatch.text = item.ruleMatch
holder.binding.ivSenderImage.setImageResource(item.sender.imageId)
holder.binding.ivSenderStatus.setImageResource(item.sender.statusImageId) holder.binding.layoutSenders.removeAllViews()
holder.binding.tvSenderName.text = item.sender.name for (sender in item.senderList) {
val layoutSenderItem = View.inflate(App.context, R.layout.item_sender, null) as LinearLayout
val ivSenderImage = layoutSenderItem.findViewById<ImageView>(R.id.iv_sender_image)
val ivSenderStatus = layoutSenderItem.findViewById<ImageView>(R.id.iv_sender_status)
val tvSenderName = layoutSenderItem.findViewById<TextView>(R.id.tv_sender_name)
ivSenderImage.setImageResource(sender.imageId)
ivSenderStatus.setImageResource(sender.statusImageId)
tvSenderName.text = sender.name
holder.binding.layoutSenders.addView(layoutSenderItem)
}
/*holder.binding.cardView.setOnClickListener { view: View? ->
itemClickListener.onItemClicked(view, item)
}*/
holder.binding.ivCopy.setImageResource(R.drawable.ic_copy) holder.binding.ivCopy.setImageResource(R.drawable.ic_copy)
holder.binding.ivEdit.setImageResource(R.drawable.ic_edit) holder.binding.ivEdit.setImageResource(R.drawable.ic_edit)
holder.binding.ivDelete.setImageResource(R.drawable.ic_delete) holder.binding.ivDelete.setImageResource(R.drawable.ic_delete)
@ -49,19 +59,19 @@ class RulePagingAdapter(private val itemClickListener: OnItemClickListener) : Pa
class MyViewHolder(val binding: AdapterRulesCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root) class MyViewHolder(val binding: AdapterRulesCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root)
interface OnItemClickListener { interface OnItemClickListener {
fun onItemClicked(view: View?, item: RuleAndSender) fun onItemClicked(view: View?, item: Rule)
fun onItemRemove(view: View?, id: Int) fun onItemRemove(view: View?, id: Int)
} }
companion object { companion object {
var diffCallback: DiffUtil.ItemCallback<RuleAndSender> = object : DiffUtil.ItemCallback<RuleAndSender>() { var diffCallback: DiffUtil.ItemCallback<Rule> = object : DiffUtil.ItemCallback<Rule>() {
override fun areItemsTheSame(oldItem: RuleAndSender, newItem: RuleAndSender): Boolean { override fun areItemsTheSame(oldItem: Rule, newItem: Rule): Boolean {
return oldItem.rule.id == newItem.rule.id return oldItem.id == newItem.id
} }
@SuppressLint("DiffUtilEquals") @SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(oldItem: RuleAndSender, newItem: RuleAndSender): Boolean { override fun areContentsTheSame(oldItem: Rule, newItem: Rule): Boolean {
return oldItem.rule === newItem.rule return oldItem === newItem
} }
} }
} }

View File

@ -7,16 +7,14 @@ import androidx.core.content.ContextCompat
import androidx.work.Configuration import androidx.work.Configuration
import com.idormy.sms.forwarder.App import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.BuildConfig import com.idormy.sms.forwarder.BuildConfig
import com.idormy.sms.forwarder.database.repository.FrpcRepository import com.idormy.sms.forwarder.database.repository.*
import com.idormy.sms.forwarder.database.repository.LogsRepository
import com.idormy.sms.forwarder.database.repository.RuleRepository
import com.idormy.sms.forwarder.database.repository.SenderRepository
import com.idormy.sms.forwarder.service.ForegroundService import com.idormy.sms.forwarder.service.ForegroundService
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
object Core : Configuration.Provider { object Core : Configuration.Provider {
lateinit var app: Application lateinit var app: Application
val frpc: FrpcRepository by lazy { (app as App).frpcRepository } val frpc: FrpcRepository by lazy { (app as App).frpcRepository }
val msg: MsgRepository by lazy { (app as App).msgRepository }
val logs: LogsRepository by lazy { (app as App).logsRepository } val logs: LogsRepository by lazy { (app as App).logsRepository }
val rule: RuleRepository by lazy { (app as App).ruleRepository } val rule: RuleRepository by lazy { (app as App).ruleRepository }
val sender: SenderRepository by lazy { (app as App).senderRepository } val sender: SenderRepository by lazy { (app as App).senderRepository }

View File

@ -7,26 +7,22 @@ import androidx.room.RoomDatabase
import androidx.room.TypeConverters import androidx.room.TypeConverters
import androidx.room.migration.Migration import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import com.idormy.sms.forwarder.database.dao.FrpcDao import com.idormy.sms.forwarder.database.dao.*
import com.idormy.sms.forwarder.database.dao.LogsDao import com.idormy.sms.forwarder.database.entity.*
import com.idormy.sms.forwarder.database.dao.RuleDao import com.idormy.sms.forwarder.database.ext.ConvertersDate
import com.idormy.sms.forwarder.database.dao.SenderDao
import com.idormy.sms.forwarder.database.entity.Frpc
import com.idormy.sms.forwarder.database.entity.Logs
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.database.entity.Sender
import com.idormy.sms.forwarder.database.ext.Converters
import com.idormy.sms.forwarder.utils.DATABASE_NAME import com.idormy.sms.forwarder.utils.DATABASE_NAME
@Database( @Database(
entities = [Frpc::class, Logs::class, Rule::class, Sender::class], entities = [Frpc::class, Msg::class, Logs::class, Rule::class, Sender::class],
version = 11, views = [LogsDetail::class],
version = 15,
exportSchema = false exportSchema = false
) )
@TypeConverters(Converters::class) @TypeConverters(ConvertersDate::class)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun frpcDao(): FrpcDao abstract fun frpcDao(): FrpcDao
abstract fun msgDao(): MsgDao
abstract fun logsDao(): LogsDao abstract fun logsDao(): LogsDao
abstract fun ruleDao(): RuleDao abstract fun ruleDao(): RuleDao
abstract fun senderDao(): SenderDao abstract fun senderDao(): SenderDao
@ -96,6 +92,10 @@ custom_domains = smsf.demo.com
MIGRATION_8_9, MIGRATION_8_9,
MIGRATION_9_10, MIGRATION_9_10,
MIGRATION_10_11, MIGRATION_10_11,
MIGRATION_11_12,
MIGRATION_12_13,
MIGRATION_13_14,
MIGRATION_14_15,
) )
/*if (BuildConfig.DEBUG) { /*if (BuildConfig.DEBUG) {
@ -281,9 +281,89 @@ CREATE TABLE "Sender" (
//转发日志添加SIM卡槽ID //转发日志添加SIM卡槽ID
private val MIGRATION_10_11 = object : Migration(10, 11) { private val MIGRATION_10_11 = object : Migration(10, 11) {
override fun migrate(database: SupportSQLiteDatabase) { override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("Alter table Logs add column sub_id INTEGER NOT NULL DEFAULT 0 ") database.execSQL("Alter table Logs add column sub_id INTEGER NOT NULL DEFAULT 0")
} }
} }
//单个转发规则可绑定多个发送通道
private val MIGRATION_11_12 = object : Migration(11, 12) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("Alter table Logs add column sender_id INTEGER NOT NULL DEFAULT 0")
database.execSQL("Update Logs Set sender_id = (Select sender_id from Rule where Logs.rule_id = Rule.id)")
database.execSQL("Alter table Rule add column sender_list TEXT NOT NULL DEFAULT ''")
database.execSQL("Update Rule set sender_list = sender_id")
database.execSQL("CREATE INDEX \"index_Rule_sender_ids\" ON \"Rule\" ( \"sender_list\" ASC)")
//删除字段sender_id
/*database.execSQL("Create table Rule_t as Select id,type,filed,check,value,sender_list,sms_template,regex_replace,sim_slot,status,time from Rule where 1 = 1")
database.execSQL("Drop table Rule")
database.execSQL("Alter table Rule_t rename to Rule")
database.execSQL("CREATE UNIQUE INDEX \"index_Rule_id\" ON \"Rule\" ( \"id\" ASC)")*/
}
}
//转发规则添加发送通道逻辑
private val MIGRATION_12_13 = object : Migration(12, 13) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("Alter table Rule add column sender_logic TEXT NOT NULL DEFAULT 'ALL'")
}
}
//分割Logs表
private val MIGRATION_13_14 = object : Migration(13, 14) {
override fun migrate(database: SupportSQLiteDatabase) {
//database.execSQL("Create table Msg as Select id,type,`from`,content,(case when sim_info like 'SIM1%' then '0' when sim_info like 'SIM2%' then '1' else '-1' end) as sim_slot,sim_info,sub_id,time from Logs where 1 = 1")
database.execSQL(
"""
CREATE TABLE "Msg" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"type" TEXT NOT NULL DEFAULT 'sms',
"from" TEXT NOT NULL DEFAULT '',
"content" TEXT NOT NULL DEFAULT '',
"sim_slot" INTEGER NOT NULL DEFAULT -1,
"sim_info" TEXT NOT NULL DEFAULT '',
"sub_id" INTEGER NOT NULL DEFAULT 0,
"time" INTEGER NOT NULL
)
""".trimIndent()
)
database.execSQL("INSERT INTO Msg (id,type,`from`,content,sim_slot,sim_info,sub_id,time) Select id,type,`from`,content,(case when sim_info like 'SIM1%' then '0' when sim_info like 'SIM2%' then '1' else '-1' end) as sim_slot,sim_info,sub_id,time from Logs where 1 = 1")
database.execSQL("CREATE UNIQUE INDEX \"index_Msg_id\" ON \"Msg\" ( \"id\" ASC)")
database.execSQL("ALTER TABLE Logs RENAME TO Logs_old")
//database.execSQL("Create table Logs_new as Select id,id as msg_id,rule_id,sender_id,forward_status,forward_response,time from Logs where 1 = 1")
database.execSQL(
"""
CREATE TABLE "Logs" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"type" TEXT NOT NULL DEFAULT 'sms',
"msg_id" INTEGER NOT NULL DEFAULT 0,
"rule_id" INTEGER NOT NULL DEFAULT 0,
"sender_id" INTEGER NOT NULL DEFAULT 0,
"forward_status" INTEGER NOT NULL DEFAULT 1,
"forward_response" TEXT NOT NULL DEFAULT '',
"time" INTEGER NOT NULL,
FOREIGN KEY ("msg_id") REFERENCES "Msg" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY ("rule_id") REFERENCES "Rule" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY ("sender_id") REFERENCES "Sender" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
""".trimIndent()
)
database.execSQL("INSERT INTO Logs (id,type,msg_id,rule_id,sender_id,forward_status,forward_response,time) SELECT id,type,id as msg_id,rule_id,sender_id,forward_status,forward_response,time FROM Logs_old")
database.execSQL("DROP TABLE Logs_old")
database.execSQL("CREATE UNIQUE INDEX \"index_Logs_id\" ON \"Logs\" ( \"id\" ASC)")
database.execSQL("CREATE INDEX \"index_Logs_msg_id\" ON \"Logs\" ( \"msg_id\" ASC)")
database.execSQL("CREATE INDEX \"index_Logs_rule_id\" ON \"Logs\" ( \"rule_id\" ASC)")
database.execSQL("CREATE INDEX \"index_Logs_sender_id\" ON \"Logs\" ( \"sender_id\" ASC)")
}
}
// 定义数据库迁移配置
private val MIGRATION_14_15 = object : Migration(14, 15) {
override fun migrate(database: SupportSQLiteDatabase) {
// 这里新建一个视图(视图名称要用两个半角的间隔号括起来)
database.execSQL("CREATE VIEW `LogsDetail` AS SELECT LOGS.id,LOGS.type,LOGS.msg_id,LOGS.rule_id,LOGS.sender_id,LOGS.forward_status,LOGS.forward_response,LOGS.TIME,Rule.filed AS rule_filed,Rule.`check` AS rule_check,Rule.value AS rule_value,Rule.sim_slot AS rule_sim_slot,Sender.type AS sender_type,Sender.NAME AS sender_name FROM LOGS LEFT JOIN Rule ON LOGS.rule_id = Rule.id LEFT JOIN Sender ON LOGS.sender_id = Sender.id")
}
}
} }
} }

View File

@ -0,0 +1,41 @@
package com.idormy.sms.forwarder.database.dao
import androidx.paging.PagingSource
import androidx.room.*
import com.idormy.sms.forwarder.database.entity.Msg
import com.idormy.sms.forwarder.database.entity.MsgAndLogs
import io.reactivex.Completable
import io.reactivex.Single
@Dao
interface MsgDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(msg: Msg): Long
@Delete
fun delete(msg: Msg): Completable
@Query("DELETE FROM Msg where id=:id")
fun delete(id: Long)
@Query("DELETE FROM Msg where type=:type")
fun deleteAll(type: String): Completable
@Query("DELETE FROM Msg where time<:time")
fun deleteTimeAgo(time: Long)
@Update
fun update(msg: Msg): Completable
@Query("SELECT * FROM Msg where id=:id")
fun get(id: Long): Single<Msg>
@Query("SELECT count(*) FROM Msg where type=:type")
fun count(type: String): Single<Int>
@Transaction
@Query("SELECT * FROM Msg WHERE type = :type ORDER BY id DESC")
fun pagingSource(type: String): PagingSource<Int, MsgAndLogs>
}

View File

@ -25,6 +25,9 @@ interface RuleDao {
@Query("SELECT * FROM Rule where id=:id") @Query("SELECT * FROM Rule where id=:id")
fun get(id: Long): Single<Rule> fun get(id: Long): Single<Rule>
@Query("SELECT * FROM Rule where id=:id")
fun getOne(id: Long): Rule
@Query("SELECT count(*) FROM Rule where type=:type and status=:status") @Query("SELECT count(*) FROM Rule where type=:type and status=:status")
fun count(type: String, status: Int): Single<Int> fun count(type: String, status: Int): Single<Int>
@ -40,7 +43,7 @@ interface RuleDao {
@Transaction @Transaction
@Query("SELECT * FROM Rule where type=:type ORDER BY id DESC") @Query("SELECT * FROM Rule where type=:type ORDER BY id DESC")
fun pagingSource(type: String): PagingSource<Int, RuleAndSender> fun pagingSource(type: String): PagingSource<Int, Rule>
@Transaction @Transaction
@Query("SELECT * FROM Rule where type=:type and status=:status and (sim_slot='ALL' or sim_slot=:simSlot)") @Query("SELECT * FROM Rule where type=:type and status=:status and (sim_slot='ALL' or sim_slot=:simSlot)")

View File

@ -25,6 +25,9 @@ interface SenderDao {
@Query("SELECT * FROM Sender where id=:id") @Query("SELECT * FROM Sender where id=:id")
fun get(id: Long): Single<Sender> fun get(id: Long): Single<Sender>
@Query("SELECT * FROM Sender where id=:id")
fun getOne(id: Long): Sender
@Query("SELECT count(*) FROM Sender where type=:type and status=:status") @Query("SELECT count(*) FROM Sender where type=:type and status=:status")
fun count(type: String, status: Int): Single<Int> fun count(type: String, status: Int): Single<Int>

View File

@ -10,45 +10,47 @@ import java.util.*
@Entity( @Entity(
tableName = "Logs", tableName = "Logs",
foreignKeys = [ foreignKeys = [
ForeignKey(
entity = Msg::class,
parentColumns = ["id"],
childColumns = ["msg_id"],
onDelete = ForeignKey.CASCADE, //级联操作
onUpdate = ForeignKey.CASCADE //级联操作
),
ForeignKey( ForeignKey(
entity = Rule::class, entity = Rule::class,
parentColumns = ["id"], parentColumns = ["id"],
childColumns = ["rule_id"], childColumns = ["rule_id"],
onDelete = ForeignKey.CASCADE, //级联操作 onDelete = ForeignKey.CASCADE, //级联操作
onUpdate = ForeignKey.CASCADE //级联操作 onUpdate = ForeignKey.CASCADE //级联操作
) ),
ForeignKey(
entity = Sender::class,
parentColumns = ["id"],
childColumns = ["sender_id"],
onDelete = ForeignKey.CASCADE, //级联操作
onUpdate = ForeignKey.CASCADE //级联操作
),
], ],
indices = [ indices = [
Index(value = ["id"], unique = true), Index(value = ["id"], unique = true),
Index(value = ["rule_id"]) Index(value = ["msg_id"]),
Index(value = ["rule_id"]),
Index(value = ["sender_id"]),
] ]
) )
data class Logs( data class Logs(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id") var id: Long, @ColumnInfo(name = "id") var id: Long,
@ColumnInfo(name = "type", defaultValue = "sms") var type: String, @ColumnInfo(name = "type", defaultValue = "sms") var type: String,
@ColumnInfo(name = "from", defaultValue = "") var from: String, @ColumnInfo(name = "msg_id", defaultValue = "0") var msgId: Long = 0,
@ColumnInfo(name = "content", defaultValue = "") var content: String,
@ColumnInfo(name = "rule_id", defaultValue = "0") var ruleId: Long = 0, @ColumnInfo(name = "rule_id", defaultValue = "0") var ruleId: Long = 0,
@ColumnInfo(name = "sim_info", defaultValue = "") var simInfo: String = "", @ColumnInfo(name = "sender_id", defaultValue = "0") var senderId: Long = 0,
@ColumnInfo(name = "sub_id", defaultValue = "0") var subId: Int = 0,
@ColumnInfo(name = "forward_status", defaultValue = "1") var forwardStatus: Int = 1, @ColumnInfo(name = "forward_status", defaultValue = "1") var forwardStatus: Int = 1,
@ColumnInfo(name = "forward_response", defaultValue = "") var forwardResponse: String = "", @ColumnInfo(name = "forward_response", defaultValue = "") var forwardResponse: String = "",
@ColumnInfo(name = "time") var time: Date = Date(), @ColumnInfo(name = "time") var time: Date = Date(),
) : Parcelable { ) : Parcelable {
val simImageId: Int
get() {
if (simInfo.isNotEmpty()) {
if (simInfo.replace("-", "").startsWith("SIM2")) {
return R.drawable.ic_sim2 //mipmap
} else if (simInfo.replace("-", "").startsWith("SIM1")) {
return R.drawable.ic_sim1
}
}
return R.drawable.ic_sim
}
val statusImageId: Int val statusImageId: Int
get() { get() {
if (forwardStatus == 1) { if (forwardStatus == 1) {

View File

@ -9,10 +9,24 @@ import kotlinx.parcelize.Parcelize
data class LogsAndRuleAndSender( data class LogsAndRuleAndSender(
@Embedded val logs: Logs, @Embedded val logs: Logs,
@Relation(
entity = Msg::class,
parentColumn = "msg_id",
entityColumn = "id"
)
val msg: Msg,
@Relation( @Relation(
entity = Rule::class, entity = Rule::class,
parentColumn = "rule_id", parentColumn = "rule_id",
entityColumn = "id" entityColumn = "id"
) )
val relation: RuleAndSender, val rule: Rule,
@Relation(
entity = Sender::class,
parentColumn = "sender_id",
entityColumn = "id"
)
val sender: Sender,
) : Parcelable ) : Parcelable

View File

@ -0,0 +1,59 @@
package com.idormy.sms.forwarder.database.entity
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.DatabaseView
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.utils.*
import kotlinx.parcelize.Parcelize
import java.util.*
@Parcelize
@DatabaseView("SELECT LOGS.id,LOGS.type,LOGS.msg_id,LOGS.rule_id,LOGS.sender_id,LOGS.forward_status,LOGS.forward_response,LOGS.TIME,Rule.filed AS rule_filed,Rule.`check` AS rule_check,Rule.value AS rule_value,Rule.sim_slot AS rule_sim_slot,Sender.type AS sender_type,Sender.NAME AS sender_name FROM LOGS LEFT JOIN Rule ON LOGS.rule_id = Rule.id LEFT JOIN Sender ON LOGS.sender_id = Sender.id")
data class LogsDetail(
@ColumnInfo(name = "id") var id: Long,
@ColumnInfo(name = "type", defaultValue = "sms") var type: String,
@ColumnInfo(name = "msg_id", defaultValue = "0") var msgId: Long = 0,
@ColumnInfo(name = "rule_id", defaultValue = "0") var ruleId: Long = 0,
@ColumnInfo(name = "sender_id", defaultValue = "0") var senderId: Long = 0,
@ColumnInfo(name = "forward_status", defaultValue = "1") var forwardStatus: Int = 1,
@ColumnInfo(name = "forward_response", defaultValue = "") var forwardResponse: String = "",
@ColumnInfo(name = "time") var time: Date = Date(),
@ColumnInfo(name = "rule_filed", defaultValue = "") var ruleFiled: String,
@ColumnInfo(name = "rule_check", defaultValue = "") var ruleCheck: String,
@ColumnInfo(name = "rule_value", defaultValue = "") var ruleValue: String,
@ColumnInfo(name = "rule_sim_slot", defaultValue = "") var ruleSimSlot: String,
@ColumnInfo(name = "sender_type", defaultValue = "1") var senderType: Int = 1,
@ColumnInfo(name = "sender_name", defaultValue = "") var senderName: String,
) : Parcelable {
val statusImageId: Int
get() {
if (forwardStatus == 1) {
return R.drawable.ic_round_warning
} else if (forwardStatus == 2) {
return R.drawable.ic_round_check
}
return R.drawable.ic_round_cancel
}
val senderImageId: Int
get() = when (senderType) {
TYPE_DINGTALK_GROUP_ROBOT -> R.drawable.icon_dingtalk
TYPE_EMAIL -> R.drawable.icon_email
TYPE_BARK -> R.drawable.icon_bark
TYPE_WEBHOOK -> R.drawable.icon_webhook
TYPE_WEWORK_ROBOT -> R.drawable.icon_wework_robot
TYPE_WEWORK_AGENT -> R.drawable.icon_wework_agent
TYPE_SERVERCHAN -> R.drawable.icon_serverchan
TYPE_TELEGRAM -> R.drawable.icon_telegram
TYPE_FEISHU -> R.drawable.icon_feishu
TYPE_PUSHPLUS -> R.drawable.icon_pushplus
TYPE_GOTIFY -> R.drawable.icon_gotify
TYPE_SMS -> R.drawable.icon_sms
TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner
TYPE_FEISHU_APP -> R.drawable.icon_feishu_app
TYPE_URL_SCHEME -> R.drawable.icon_url_scheme
else -> R.drawable.icon_sms
}
}

View File

@ -0,0 +1,43 @@
package com.idormy.sms.forwarder.database.entity
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import com.idormy.sms.forwarder.R
import kotlinx.parcelize.Parcelize
import java.util.*
@Parcelize
@Entity(
tableName = "Msg",
indices = [
Index(value = ["id"], unique = true)
]
)
data class Msg(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id") var id: Long,
@ColumnInfo(name = "type", defaultValue = "sms") var type: String,
@ColumnInfo(name = "from", defaultValue = "") var from: String,
@ColumnInfo(name = "content", defaultValue = "") var content: String,
@ColumnInfo(name = "sim_slot", defaultValue = "-1") var simSlot: Int = -1, //卡槽id-1=获取失败、0=卡槽1、1=卡槽2
@ColumnInfo(name = "sim_info", defaultValue = "") var simInfo: String = "",
@ColumnInfo(name = "sub_id", defaultValue = "0") var subId: Int = 0,
@ColumnInfo(name = "time") var time: Date = Date(),
) : Parcelable {
val simImageId: Int
get() {
if (simInfo.isNotEmpty()) {
if (simInfo.replace("-", "").startsWith("SIM2")) {
return R.drawable.ic_sim2 //mipmap
} else if (simInfo.replace("-", "").startsWith("SIM1")) {
return R.drawable.ic_sim1
}
}
return R.drawable.ic_sim
}
}

View File

@ -0,0 +1,17 @@
package com.idormy.sms.forwarder.database.entity
import android.os.Parcelable
import androidx.room.Embedded
import androidx.room.Relation
import kotlinx.parcelize.Parcelize
@Parcelize
data class MsgAndLogs(
@Embedded val msg: Msg,
@Relation(
parentColumn = "id",
entityColumn = "msg_id"
)
val logsList: List<LogsDetail>
) : Parcelable

View File

@ -4,6 +4,7 @@ import android.os.Parcelable
import android.util.Log import android.util.Log
import androidx.room.* import androidx.room.*
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.database.ext.ConvertersSenderList
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.xuexiang.xui.utils.ResUtils.getString import com.xuexiang.xui.utils.ResUtils.getString
@ -26,9 +27,11 @@ import java.util.regex.PatternSyntaxException
], ],
indices = [ indices = [
Index(value = ["id"], unique = true), Index(value = ["id"], unique = true),
Index(value = ["sender_id"]) Index(value = ["sender_id"]),
Index(value = ["sender_list"])
] ]
) )
@TypeConverters(ConvertersSenderList::class)
data class Rule( data class Rule(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id") var id: Long, @ColumnInfo(name = "id") var id: Long,
@ -42,6 +45,8 @@ data class Rule(
@ColumnInfo(name = "sim_slot", defaultValue = "ALL") var simSlot: String = "", @ColumnInfo(name = "sim_slot", defaultValue = "ALL") var simSlot: String = "",
@ColumnInfo(name = "status", defaultValue = "1") var status: Int = 1, @ColumnInfo(name = "status", defaultValue = "1") var status: Int = 1,
@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_logic", defaultValue = "ALL") var senderLogic: String = "ALL",
) : Parcelable { ) : Parcelable {
companion object { companion object {
@ -87,6 +92,14 @@ data class Rule(
else -> R.drawable.icon_on else -> R.drawable.icon_on
} }
fun getSenderLogicCheckId(): Int {
return when (senderLogic) {
SENDER_LOGIC_UNTIL_FAIL -> R.id.rb_sender_logic_until_fail
SENDER_LOGIC_UNTIL_SUCCESS -> R.id.rb_sender_logic_until_success
else -> R.id.rb_sender_logic_all
}
}
fun getSimSlotCheckId(): Int { fun getSimSlotCheckId(): Int {
return when (simSlot) { return when (simSlot) {
CHECK_SIM_SLOT_1 -> R.id.rb_sim_slot_1 CHECK_SIM_SLOT_1 -> R.id.rb_sim_slot_1

View File

@ -4,7 +4,7 @@ import androidx.room.TypeConverter
import java.util.* import java.util.*
@Suppress("unused") @Suppress("unused")
class Converters { class ConvertersDate {
@TypeConverter @TypeConverter
fun fromTimestamp(value: Long?): Date? { fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) } return value?.let { Date(it) }

View File

@ -0,0 +1,29 @@
package com.idormy.sms.forwarder.database.ext
import androidx.room.TypeConverter
import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.database.entity.Sender
import java.util.*
@Suppress("unused")
class ConvertersSenderList {
@TypeConverter
fun stringToObject(value: String): List<Sender> {
var senderList: MutableList<Sender> = mutableListOf()
value.split(",").map { it.trim() }.forEach {
val sender = Core.sender.getOne(it.toLong())
senderList.add(sender)
}
return senderList
}
@TypeConverter
fun objectToString(list: List<Sender>): String {
var senderList = ArrayList<Long>()
list.forEach {
senderList += it.id
}
return senderList.joinToString(",")
}
}

View File

@ -0,0 +1,22 @@
package com.idormy.sms.forwarder.database.repository
import androidx.annotation.WorkerThread
import com.idormy.sms.forwarder.database.dao.MsgDao
import com.idormy.sms.forwarder.database.entity.Msg
class MsgRepository(private val msgDao: MsgDao) {
@WorkerThread
fun delete(id: Long) {
msgDao.delete(id)
}
@WorkerThread
fun deleteTimeAgo(time: Long) {
msgDao.deleteTimeAgo(time)
}
@WorkerThread
suspend fun insert(msg: Msg): Long = msgDao.insert(msg)
}

View File

@ -24,6 +24,9 @@ class RuleRepository(
@WorkerThread @WorkerThread
fun get(id: Long) = ruleDao.get(id) fun get(id: Long) = ruleDao.get(id)
@WorkerThread
fun getOne(id: Long) = ruleDao.getOne(id)
suspend fun getRuleAndSender(type: String, status: Int, simSlot: String) = ruleDao.getRuleAndSender(type, status, simSlot) suspend fun getRuleAndSender(type: String, status: Int, simSlot: String) = ruleDao.getRuleAndSender(type, status, simSlot)
fun getRuleList(type: String, status: Int, simSlot: String) = ruleDao.getRuleList(type, status, simSlot) fun getRuleList(type: String, status: Int, simSlot: String) = ruleDao.getRuleList(type, status, simSlot)

View File

@ -20,6 +20,8 @@ class SenderRepository(private val senderDao: SenderDao) {
fun get(id: Long) = senderDao.get(id) fun get(id: Long) = senderDao.get(id)
fun getOne(id: Long) = senderDao.getOne(id)
fun update(sender: Sender) = senderDao.update(sender) fun update(sender: Sender) = senderDao.update(sender)
val count: Flow<Long> = senderDao.getOnCount() val count: Flow<Long> = senderDao.getOnCount()

View File

@ -17,6 +17,11 @@ class BaseViewModelFactory(private val context: Context?) : ViewModelProvider.Fa
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return FrpcViewModel(frpcDao) as T return FrpcViewModel(frpcDao) as T
} }
modelClass.isAssignableFrom(MsgViewModel::class.java) -> {
val msgDao = AppDatabase.getInstance(context).msgDao()
@Suppress("UNCHECKED_CAST")
return MsgViewModel(msgDao) as T
}
modelClass.isAssignableFrom(LogsViewModel::class.java) -> { modelClass.isAssignableFrom(LogsViewModel::class.java) -> {
val logDao = AppDatabase.getInstance(context).logsDao() val logDao = AppDatabase.getInstance(context).logsDao()
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")

View File

@ -0,0 +1,36 @@
package com.idormy.sms.forwarder.database.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.idormy.sms.forwarder.database.dao.MsgDao
import com.idormy.sms.forwarder.database.entity.MsgAndLogs
import com.idormy.sms.forwarder.database.ext.ioThread
import kotlinx.coroutines.flow.Flow
class MsgViewModel(private val dao: MsgDao) : ViewModel() {
private var type: String = "sms"
fun setType(type: String): MsgViewModel {
this.type = type
return this
}
val allMsg: Flow<PagingData<MsgAndLogs>> = Pager(
config = PagingConfig(
pageSize = 10,
enablePlaceholders = false,
initialLoadSize = 10
)
) {
dao.pagingSource(type)
}.flow.cachedIn(viewModelScope)
fun delete(id: Long) = ioThread {
dao.delete(id)
}
}

View File

@ -8,7 +8,6 @@ import androidx.paging.PagingData
import androidx.paging.cachedIn import androidx.paging.cachedIn
import com.idormy.sms.forwarder.database.dao.RuleDao import com.idormy.sms.forwarder.database.dao.RuleDao
import com.idormy.sms.forwarder.database.entity.Rule import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.database.entity.RuleAndSender
import com.idormy.sms.forwarder.database.ext.ioThread import com.idormy.sms.forwarder.database.ext.ioThread
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -20,7 +19,7 @@ class RuleViewModel(private val dao: RuleDao) : ViewModel() {
return this return this
} }
val allRules: Flow<PagingData<RuleAndSender>> = Pager( val allRules: Flow<PagingData<Rule>> = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = 10, pageSize = 10,
enablePlaceholders = false, enablePlaceholders = false,

View File

@ -1,6 +1,8 @@
package com.idormy.sms.forwarder.fragment package com.idormy.sms.forwarder.fragment
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.text.TextUtils
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -9,21 +11,20 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
import com.alibaba.android.vlayout.VirtualLayoutManager import com.alibaba.android.vlayout.VirtualLayoutManager
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.LogsPagingAdapter import com.idormy.sms.forwarder.adapter.MsgPagingAdapter
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender import com.idormy.sms.forwarder.database.entity.LogsDetail
import com.idormy.sms.forwarder.database.entity.MsgAndLogs
import com.idormy.sms.forwarder.database.entity.Rule import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory
import com.idormy.sms.forwarder.database.viewmodel.LogsViewModel import com.idormy.sms.forwarder.database.viewmodel.MsgViewModel
import com.idormy.sms.forwarder.databinding.FragmentLogsBinding import com.idormy.sms.forwarder.databinding.FragmentLogsBinding
import com.idormy.sms.forwarder.utils.EVENT_UPDATE_LOGS_TYPE import com.idormy.sms.forwarder.utils.EVENT_UPDATE_LOGS_TYPE
import com.idormy.sms.forwarder.utils.FORWARD_STATUS_MAP import com.idormy.sms.forwarder.utils.FORWARD_STATUS_MAP
import com.idormy.sms.forwarder.utils.SendUtils
import com.idormy.sms.forwarder.utils.XToastUtils import com.idormy.sms.forwarder.utils.XToastUtils
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
import com.scwang.smartrefresh.layout.api.RefreshLayout import com.scwang.smartrefresh.layout.api.RefreshLayout
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.utils.TextUtils
import com.xuexiang.xui.utils.ResUtils 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
@ -36,11 +37,11 @@ import java.util.*
@Suppress("PropertyName") @Suppress("PropertyName")
@Page(name = "转发日志") @Page(name = "转发日志")
class LogsFragment : BaseFragment<FragmentLogsBinding?>(), LogsPagingAdapter.OnItemClickListener { class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnItemClickListener {
val TAG: String = LogsFragment::class.java.simpleName val TAG: String = LogsFragment::class.java.simpleName
private var adapter = LogsPagingAdapter(this) private var adapter = MsgPagingAdapter(this)
private val viewModel by viewModels<LogsViewModel> { BaseViewModelFactory(context) } private val viewModel by viewModels<MsgViewModel> { BaseViewModelFactory(context) }
private var currentType: String = "sms" private var currentType: String = "sms"
override fun viewBindingInflate( override fun viewBindingInflate(
@ -90,7 +91,7 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), LogsPagingAdapter.OnI
binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout ->
//adapter.refresh() //adapter.refresh()
lifecycleScope.launch { lifecycleScope.launch {
viewModel.setType(currentType).allLogs.collectLatest { adapter.submitData(it) } viewModel.setType(currentType).allMsg.collectLatest { adapter.submitData(it) }
} }
refreshLayout.finishRefresh() refreshLayout.finishRefresh()
} }
@ -98,38 +99,58 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), LogsPagingAdapter.OnI
binding!!.refreshLayout.autoRefresh() binding!!.refreshLayout.autoRefresh()
} }
override fun onItemClicked(view: View?, item: LogsAndRuleAndSender) { override fun onItemClicked(view: View?, item: MsgAndLogs) {
val ruleStr = StringBuilder() Log.d(TAG, "item: $item")
ruleStr.append(Rule.getRuleMatch(item.relation.rule.filed, item.relation.rule.check, item.relation.rule.value, item.relation.rule.simSlot)).append(item.relation.sender.name)
val detailStr = StringBuilder() val detailStr = StringBuilder()
detailStr.append(ResUtils.getString(R.string.from)).append(item.logs.from).append("\n\n") detailStr.append(ResUtils.getString(R.string.from)).append(item.msg.from).append("\n\n")
detailStr.append(ResUtils.getString(R.string.msg)).append(item.logs.content).append("\n\n") detailStr.append(ResUtils.getString(R.string.msg)).append(item.msg.content).append("\n\n")
if (!TextUtils.isEmpty(item.logs.simInfo)) detailStr.append(ResUtils.getString(R.string.slot)).append(item.logs.simInfo).append("\n\n") if (!TextUtils.isEmpty(item.msg.simInfo)) detailStr.append(ResUtils.getString(R.string.slot)).append(item.msg.simInfo).append("\n\n")
detailStr.append(ResUtils.getString(R.string.rule)).append(ruleStr.toString()).append("\n\n")
@SuppressLint("SimpleDateFormat") val utcFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) @SuppressLint("SimpleDateFormat") val utcFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
detailStr.append(ResUtils.getString(R.string.time)).append(DateUtils.date2String(item.logs.time, utcFormatter)).append("\n\n") detailStr.append(ResUtils.getString(R.string.time)).append(DateUtils.date2String(item.msg.time, utcFormatter))
detailStr.append(ResUtils.getString(R.string.result)).append(FORWARD_STATUS_MAP[item.logs.forwardStatus]).append("\n--------------------\n").append(item.logs.forwardResponse)
MaterialDialog.Builder(requireContext()) MaterialDialog.Builder(requireContext())
.iconRes(item.logs.simImageId) .iconRes(item.msg.simImageId)
.title(R.string.details) .title(R.string.details)
.content(detailStr.toString()) .content(detailStr.toString())
.cancelable(true) .cancelable(true)
.positiveText(R.string.del) .positiveText(R.string.del)
.onPositive { _: MaterialDialog?, _: DialogAction? -> .onPositive { _: MaterialDialog?, _: DialogAction? ->
viewModel.delete(item.logs.id) viewModel.delete(item.msg.id)
XToastUtils.success(R.string.delete_log_toast) XToastUtils.success(R.string.delete_log_toast)
} }
.negativeText(R.string.resend)
.onNegative { _: MaterialDialog?, _: DialogAction? ->
XToastUtils.toast(R.string.resend_toast)
SendUtils.resendMsg(item, false)
}
.neutralText(R.string.rematch) .neutralText(R.string.rematch)
.neutralColor(ResUtils.getColors(R.color.red)) .neutralColor(ResUtils.getColors(R.color.red))
.onNeutral { _: MaterialDialog?, _: DialogAction? -> .onNeutral { _: MaterialDialog?, _: DialogAction? ->
XToastUtils.toast(R.string.rematch_toast) XToastUtils.toast(R.string.rematch_toast)
SendUtils.resendMsg(item, true) //SendUtils.resendMsg(item, true)
}
.show()
}
override fun onLogsClicked(view: View?, item: LogsDetail) {
Log.d(TAG, "item: $item")
val ruleStr = StringBuilder()
ruleStr.append(Rule.getRuleMatch(item.ruleFiled, item.ruleCheck, item.ruleValue, item.ruleSimSlot)).append(item.senderName)
val detailStr = StringBuilder()
detailStr.append(ResUtils.getString(R.string.rule)).append(ruleStr.toString()).append("\n\n")
@SuppressLint("SimpleDateFormat") val utcFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
detailStr.append(ResUtils.getString(R.string.time)).append(DateUtils.date2String(item.time, utcFormatter)).append("\n\n")
detailStr.append(ResUtils.getString(R.string.result)).append(FORWARD_STATUS_MAP[item.forwardStatus]).append("\n--------------------\n").append(item.forwardResponse)
MaterialDialog.Builder(requireContext())
.title(R.string.details)
.content(detailStr.toString())
.cancelable(true)
.positiveText(R.string.del)
.onPositive { _: MaterialDialog?, _: DialogAction? ->
viewModel.delete(item.id)
XToastUtils.success(R.string.delete_log_toast)
}
.negativeText(R.string.resend)
.onNegative { _: MaterialDialog?, _: DialogAction? ->
XToastUtils.toast(R.string.resend_toast)
//SendUtils.resendMsg(item, false)
} }
.show() .show()
} }

View File

@ -1,5 +1,6 @@
package com.idormy.sms.forwarder.fragment package com.idormy.sms.forwarder.fragment
import android.annotation.SuppressLint
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
@ -52,8 +53,11 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
//当前发送通道 //当前发送通道
var senderId = 0L var senderId = 0L
var senderListSelected: MutableList<Sender> = mutableListOf()
private var senderItemMap = HashMap<Long, LinearLayout>(2)
//发送通道列表 //发送通道列表
var senderListAll: MutableList<Sender> = mutableListOf()
private val senderSpinnerList = ArrayList<SenderAdapterItem>() private val senderSpinnerList = ArrayList<SenderAdapterItem>()
private lateinit var senderSpinnerAdapter: SenderSpinnerAdapter<*> private lateinit var senderSpinnerAdapter: SenderSpinnerAdapter<*>
@ -264,17 +268,11 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
return return
} }
MaterialDialog.Builder(requireContext()) MaterialDialog.Builder(requireContext()).title(R.string.delete_rule_title).content(R.string.delete_rule_tips).positiveText(R.string.lab_yes).negativeText(R.string.lab_no).onPositive { _: MaterialDialog?, _: DialogAction? ->
.title(R.string.delete_rule_title) viewModel.delete(ruleId)
.content(R.string.delete_rule_tips) XToastUtils.success(R.string.delete_rule_toast)
.positiveText(R.string.lab_yes) popToBack()
.negativeText(R.string.lab_no) }.show()
.onPositive { _: MaterialDialog?, _: DialogAction? ->
viewModel.delete(ruleId)
XToastUtils.success(R.string.delete_rule_toast)
popToBack()
}
.show()
return return
} }
R.id.btn_save -> { R.id.btn_save -> {
@ -295,73 +293,62 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
//初始化发送通道下拉框 //初始化发送通道下拉框
private fun initSenderSpinner() { private fun initSenderSpinner() {
AppDatabase.getInstance(requireContext()) AppDatabase.getInstance(requireContext()).senderDao().getAll().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(object : SingleObserver<List<Sender>> {
.senderDao() override fun onSubscribe(d: Disposable) {}
.getAll()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : SingleObserver<List<Sender>> {
override fun onSubscribe(d: Disposable) {}
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
e.printStackTrace() e.printStackTrace()
}
override fun onSuccess(senderList: List<Sender>) {
if (senderList.isEmpty()) {
XToastUtils.error(R.string.add_sender_first)
return
} }
override fun onSuccess(senderList: List<Sender>) { senderListAll = senderList as MutableList<Sender>
if (senderList.isEmpty()) { for (sender in senderList) {
XToastUtils.error(R.string.add_sender_first) val name = if (sender.name.length > 20) sender.name.substring(0, 19) else sender.name
return senderSpinnerList.add(SenderAdapterItem(name, sender.imageId, sender.id, sender.status))
} }
senderSpinnerAdapter = SenderSpinnerAdapter(senderSpinnerList)
//.setTextColor(ResUtils.getColor(R.color.green))
//.setTextSize(12F)
.setIsFilterKey(true).setFilterColor("#EF5362").setBackgroundSelector(R.drawable.selector_custom_spinner_bg)
binding!!.spSender.setAdapter(senderSpinnerAdapter)
for (sender in senderList) { if (senderListSelected.isNotEmpty()) {
val name = if (sender.name.length > 20) sender.name.substring(0, 19) else sender.name for (sender in senderListSelected) {
senderSpinnerList.add(SenderAdapterItem(name, sender.imageId, sender.id, sender.status)) for (senderItem in senderSpinnerList) {
} if (sender.id == senderItem.id) {
senderSpinnerAdapter = SenderSpinnerAdapter(senderSpinnerList) addSenderItemLinearLayout(senderItemMap, binding!!.layoutSenders, senderItem)
//.setTextColor(ResUtils.getColor(R.color.green))
//.setTextSize(12F)
.setIsFilterKey(true)
.setFilterColor("#EF5362")
.setBackgroundSelector(R.drawable.selector_custom_spinner_bg)
binding!!.spSender.setAdapter(senderSpinnerAdapter)
if (senderId > 0) {
for (sender in senderSpinnerList) {
if (sender.id == senderId) {
binding!!.ivSenderImage.setImageDrawable(sender.icon)
binding!!.ivSenderStatus.setImageDrawable(
ResUtils.getDrawable(
when (sender.status) {
STATUS_OFF -> R.drawable.icon_off
else -> R.drawable.icon_on
}
)
)
binding!!.tvSenderName.text = sender.title
} }
} }
} }
} }
}) }
})
binding!!.spSender.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long -> binding!!.spSender.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long ->
try { try {
//val sender = senderSpinnerList[position]
val sender = senderSpinnerAdapter.getItemSource(position) as SenderAdapterItem val sender = senderSpinnerAdapter.getItemSource(position) as SenderAdapterItem
sender.id.also { senderId = sender.id!!
senderId = it ?: 0L if (senderId > 0L) {
} senderListSelected.forEach {
binding!!.ivSenderImage.setImageDrawable(sender.icon) if (senderId == it.id) {
binding!!.ivSenderStatus.setImageDrawable( XToastUtils.warning(getString(R.string.sender_contains_tips))
ResUtils.getDrawable( return@setOnItemClickListener
when (sender.status) {
STATUS_OFF -> R.drawable.icon_off
else -> R.drawable.icon_on
} }
) }
) senderListAll.forEach {
binding!!.tvSenderName.text = sender.title if (senderId == it.id) {
if (STATUS_OFF == sender.status) { senderListSelected.add(it)
XToastUtils.warning(getString(R.string.sender_disabled_tips)) addSenderItemLinearLayout(senderItemMap, binding!!.layoutSenders, sender)
}
}
if (STATUS_OFF == sender.status) {
XToastUtils.warning(getString(R.string.sender_disabled_tips))
}
} }
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(e.message.toString()) XToastUtils.error(e.message.toString())
@ -369,6 +356,60 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
} }
} }
/**
* 动态增删header
*
* @param senderItemMap 管理item的map用于删除指定header
* @param layoutSenders 需要挂载item的LinearLayout
* @param sender SenderAdapterItem
*/
@SuppressLint("SetTextI18n")
private fun addSenderItemLinearLayout(
senderItemMap: MutableMap<Long, LinearLayout>, layoutSenders: LinearLayout, sender: SenderAdapterItem
) {
val layoutSenderItem = View.inflate(requireContext(), R.layout.item_add_sender, null) as LinearLayout
val ivRemoveSender = layoutSenderItem.findViewById<ImageView>(R.id.iv_remove_sender)
val ivSenderImage = layoutSenderItem.findViewById<ImageView>(R.id.iv_sender_image)
val ivSenderStatus = layoutSenderItem.findViewById<ImageView>(R.id.iv_sender_status)
val tvSenderName = layoutSenderItem.findViewById<TextView>(R.id.tv_sender_name)
ivSenderImage.setImageDrawable(sender.icon)
ivSenderStatus.setImageDrawable(ResUtils.getDrawable(if (STATUS_OFF == sender.status) R.drawable.icon_off else R.drawable.icon_on))
val senderItemId = sender.id as Long
tvSenderName.text = "ID-$senderItemId${sender.title}"
ivRemoveSender.tag = senderItemId
ivRemoveSender.setOnClickListener { view2: View ->
val tagId = view2.tag as Long
layoutSenders.removeView(senderItemMap[tagId])
senderItemMap.remove(tagId)
//senderListSelected.removeIf { it.id == tagId }
for (it in senderListSelected) {
if (it.id == tagId) {
senderListSelected -= it
break
}
}
Log.d(TAG, senderListSelected.count().toString())
Log.d(TAG, senderListSelected.toString())
if (senderListSelected.isEmpty()) senderId = 0L
if (senderListSelected.count() > 1) {
binding!!.layoutSenderLogic.visibility = View.VISIBLE
} else {
binding!!.layoutSenderLogic.visibility = View.GONE
binding!!.rgSenderLogic.check(R.id.rb_sender_logic_all)
}
}
layoutSenders.addView(layoutSenderItem)
senderItemMap[senderItemId] = layoutSenderItem
if (senderListSelected.count() > 1) {
binding!!.layoutSenderLogic.visibility = View.VISIBLE
} else {
binding!!.layoutSenderLogic.visibility = View.GONE
binding!!.rgSenderLogic.check(R.id.rb_sender_logic_all)
}
}
//初始化APP下拉列表 //初始化APP下拉列表
private fun initAppSpinner() { private fun initAppSpinner() {
if (ruleType != "app") return if (ruleType != "app") return
@ -377,9 +418,7 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
if (!SettingUtils.enableLoadUserAppList && !SettingUtils.enableLoadSystemAppList) return if (!SettingUtils.enableLoadUserAppList && !SettingUtils.enableLoadSystemAppList) return
val get = GlobalScope.async(Dispatchers.IO) { val get = GlobalScope.async(Dispatchers.IO) {
if ((SettingUtils.enableLoadUserAppList && App.UserAppList.isEmpty()) if ((SettingUtils.enableLoadUserAppList && App.UserAppList.isEmpty()) || (SettingUtils.enableLoadSystemAppList && App.SystemAppList.isEmpty())) {
|| (SettingUtils.enableLoadSystemAppList && App.SystemAppList.isEmpty())
) {
App.UserAppList.clear() App.UserAppList.clear()
App.SystemAppList.clear() App.SystemAppList.clear()
val appInfoList = AppUtils.getAppsInfo() val appInfoList = AppUtils.getAppsInfo()
@ -417,9 +456,7 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
appListSpinnerAdapter = AppListSpinnerAdapter(appListSpinnerList) appListSpinnerAdapter = AppListSpinnerAdapter(appListSpinnerList)
//.setTextColor(ResUtils.getColor(R.color.green)) //.setTextColor(ResUtils.getColor(R.color.green))
//.setTextSize(12F) //.setTextSize(12F)
.setIsFilterKey(true) .setIsFilterKey(true).setFilterColor("#EF5362").setBackgroundSelector(R.drawable.selector_custom_spinner_bg)
.setFilterColor("#EF5362")
.setBackgroundSelector(R.drawable.selector_custom_spinner_bg)
binding!!.spApp.setAdapter(appListSpinnerAdapter) binding!!.spApp.setAdapter(appListSpinnerAdapter)
binding!!.spApp.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long -> binding!!.spApp.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long ->
try { try {
@ -439,53 +476,53 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
//初始化表单 //初始化表单
private fun initForm() { private fun initForm() {
AppDatabase.getInstance(requireContext()) AppDatabase.getInstance(requireContext()).ruleDao().get(ruleId).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(object : SingleObserver<Rule> {
.ruleDao() override fun onSubscribe(d: Disposable) {}
.get(ruleId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : SingleObserver<Rule> {
override fun onSubscribe(d: Disposable) {}
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
e.printStackTrace() e.printStackTrace()
}
override fun onSuccess(rule: Rule) {
Log.d(TAG, rule.senderList.toString())
rule.senderList.forEach {
senderId = it.id
senderListSelected.add(it)
} }
override fun onSuccess(rule: Rule) { if (isClone) {
senderId = rule.senderId titleBar?.setSubTitle(getString(R.string.clone_rule))
binding!!.btnDel.setText(R.string.discard)
if (isClone) { } else {
titleBar?.setSubTitle(getString(R.string.clone_rule)) titleBar?.setSubTitle(getString(R.string.edit_rule))
binding!!.btnDel.setText(R.string.discard)
} else {
titleBar?.setSubTitle(getString(R.string.edit_rule))
}
Log.d(TAG, rule.toString())
binding!!.rgSimSlot.check(rule.getSimSlotCheckId())
binding!!.rgFiled.check(rule.getFiledCheckId())
val checkId = rule.getCheckCheckId()
if (checkId == R.id.rb_is || checkId == R.id.rb_contain || checkId == R.id.rb_not_contain) {
binding!!.rgCheck.check(checkId)
} else {
binding!!.rgCheck2.check(checkId)
}
binding!!.etValue.setText(rule.value)
binding!!.sbSmsTemplate.isChecked = !TextUtils.isEmpty(rule.smsTemplate.trim())
binding!!.etSmsTemplate.setText(rule.smsTemplate.trim())
binding!!.sbRegexReplace.isChecked = !TextUtils.isEmpty(rule.regexReplace.trim())
binding!!.etRegexReplace.setText(rule.regexReplace.trim())
binding!!.sbStatus.isChecked = rule.statusChecked
//初始化发送通道下拉框
initSenderSpinner()
} }
}) Log.d(TAG, rule.toString())
binding!!.rgSenderLogic.check(rule.getSenderLogicCheckId())
binding!!.rgSimSlot.check(rule.getSimSlotCheckId())
binding!!.rgFiled.check(rule.getFiledCheckId())
val checkId = rule.getCheckCheckId()
if (checkId == R.id.rb_is || checkId == R.id.rb_contain || checkId == R.id.rb_not_contain) {
binding!!.rgCheck.check(checkId)
} else {
binding!!.rgCheck2.check(checkId)
}
binding!!.etValue.setText(rule.value)
binding!!.sbSmsTemplate.isChecked = !TextUtils.isEmpty(rule.smsTemplate.trim())
binding!!.etSmsTemplate.setText(rule.smsTemplate.trim())
binding!!.sbRegexReplace.isChecked = !TextUtils.isEmpty(rule.regexReplace.trim())
binding!!.etRegexReplace.setText(rule.regexReplace.trim())
binding!!.sbStatus.isChecked = rule.statusChecked
//初始化发送通道下拉框
initSenderSpinner()
}
})
} }
//提交前检查表单 //提交前检查表单
private fun checkForm(): Rule { private fun checkForm(): Rule {
if (senderId <= 0L) { if (senderListSelected.isEmpty() || senderId == 0L) {
throw Exception(getString(R.string.new_sender_first)) throw Exception(getString(R.string.new_sender_first))
} }
val filed = when (binding!!.rgFiled.checkedRadioButtonId) { val filed = when (binding!!.rgFiled.checkedRadioButtonId) {
@ -522,6 +559,12 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
throw Exception(String.format(getString(R.string.invalid_regex_replace), lineNum)) throw Exception(String.format(getString(R.string.invalid_regex_replace), lineNum))
} }
val senderLogic = when (binding!!.rgSenderLogic.checkedRadioButtonId) {
R.id.rb_sender_logic_until_fail -> SENDER_LOGIC_UNTIL_FAIL
R.id.rb_sender_logic_until_success -> SENDER_LOGIC_UNTIL_SUCCESS
else -> SENDER_LOGIC_ALL
}
val simSlot = when (binding!!.rgSimSlot.checkedRadioButtonId) { val simSlot = when (binding!!.rgSimSlot.checkedRadioButtonId) {
R.id.rb_sim_slot_1 -> CHECK_SIM_SLOT_1 R.id.rb_sim_slot_1 -> CHECK_SIM_SLOT_1
R.id.rb_sim_slot_2 -> CHECK_SIM_SLOT_2 R.id.rb_sim_slot_2 -> CHECK_SIM_SLOT_2
@ -532,7 +575,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) return Rule(ruleId, ruleType, filed, check, value, senderId, smsTemplate, regexReplace, simSlot, status, Date(), senderListSelected, senderLogic)
} }
//检查多重匹配规则是否正确 //检查多重匹配规则是否正确
@ -589,73 +632,48 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
etContent.visibility = View.GONE etContent.visibility = View.GONE
} }
MaterialDialog.Builder(requireContext()) MaterialDialog.Builder(requireContext()).iconRes(android.R.drawable.ic_dialog_email).title(R.string.rule_tester).customView(dialogTest, true).cancelable(false).autoDismiss(false).neutralText(R.string.action_back).neutralColor(ResUtils.getColors(R.color.darkGrey)).onNeutral { dialog: MaterialDialog?, _: DialogAction? ->
.iconRes(android.R.drawable.ic_dialog_email) dialog?.dismiss()
.title(R.string.rule_tester) }.positiveText(R.string.action_test).onPositive { _: MaterialDialog?, _: DialogAction? ->
.customView(dialogTest, true) try {
.cancelable(false) val simSlot = when (if (ruleType == "app") -1 else rgSimSlot.checkedRadioButtonId) {
.autoDismiss(false) R.id.rb_sim_slot_1 -> 0
.neutralText(R.string.action_back) R.id.rb_sim_slot_2 -> 1
.neutralColor(ResUtils.getColors(R.color.darkGrey)) else -> -1
.onNeutral { dialog: MaterialDialog?, _: DialogAction? ->
dialog?.dismiss()
}
.positiveText(R.string.action_test)
.onPositive { _: MaterialDialog?, _: DialogAction? ->
try {
val simSlot = when (if (ruleType == "app") -1 else rgSimSlot.checkedRadioButtonId) {
R.id.rb_sim_slot_1 -> 0
R.id.rb_sim_slot_2 -> 1
else -> -1
}
val testSim = "SIM" + (simSlot + 1)
val ruleSim: String = rule.simSlot
if (ruleSim != "ALL" && ruleSim != testSim) {
throw java.lang.Exception(getString(R.string.card_slot_does_not_match))
}
//获取卡槽信息
val simInfo = when (simSlot) {
0 -> "SIM1_" + SettingUtils.extraSim1
1 -> "SIM2_" + SettingUtils.extraSim2
else -> etTitle.text.toString()
}
val msgInfo = MsgInfo(ruleType, etFrom.text.toString(), etContent.text.toString(), Date(), simInfo, simSlot)
if (!rule.checkMsg(msgInfo)) {
throw java.lang.Exception(getString(R.string.unmatched_rule))
}
AppDatabase.getInstance(requireContext())
.senderDao()
.get(senderId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : SingleObserver<Sender> {
override fun onSubscribe(d: Disposable) {}
override fun onError(e: Throwable) {
e.printStackTrace()
}
override fun onSuccess(sender: Sender) {
Thread {
try {
SendUtils.sendMsgSender(msgInfo, rule, sender, 0L)
} catch (e: Exception) {
e.printStackTrace()
if (Looper.myLooper() == null) Looper.prepare()
XToastUtils.error(e.message.toString())
Looper.loop()
}
}.start()
}
})
} catch (e: Exception) {
XToastUtils.error(e.message.toString())
} }
}.show()
val testSim = "SIM" + (simSlot + 1)
val ruleSim: String = rule.simSlot
if (ruleSim != "ALL" && ruleSim != testSim) {
throw java.lang.Exception(getString(R.string.card_slot_does_not_match))
}
//获取卡槽信息
val simInfo = when (simSlot) {
0 -> "SIM1_" + SettingUtils.extraSim1
1 -> "SIM2_" + SettingUtils.extraSim2
else -> etTitle.text.toString()
}
val msgInfo = MsgInfo(ruleType, etFrom.text.toString(), etContent.text.toString(), Date(), simInfo, simSlot)
if (!rule.checkMsg(msgInfo)) {
throw java.lang.Exception(getString(R.string.unmatched_rule))
}
Thread {
try {
SendUtils.sendMsgSender(msgInfo, rule)
} catch (e: Exception) {
e.printStackTrace()
if (Looper.myLooper() == null) Looper.prepare()
XToastUtils.error(e.message.toString())
Looper.loop()
}
}.start()
} catch (e: Exception) {
XToastUtils.error(e.message.toString())
}
}.show()
} }
} }

View File

@ -1,6 +1,5 @@
package com.idormy.sms.forwarder.fragment package com.idormy.sms.forwarder.fragment
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -11,7 +10,7 @@ import com.alibaba.android.vlayout.VirtualLayoutManager
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.RulePagingAdapter import com.idormy.sms.forwarder.adapter.RulePagingAdapter
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.database.entity.RuleAndSender import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory
import com.idormy.sms.forwarder.database.viewmodel.RuleViewModel import com.idormy.sms.forwarder.database.viewmodel.RuleViewModel
import com.idormy.sms.forwarder.databinding.FragmentRulesBinding import com.idormy.sms.forwarder.databinding.FragmentRulesBinding
@ -92,22 +91,21 @@ class RulesFragment : BaseFragment<FragmentRulesBinding?>(), RulePagingAdapter.O
binding!!.refreshLayout.autoRefresh() binding!!.refreshLayout.autoRefresh()
} }
override fun onItemClicked(view: View?, item: RuleAndSender) { override fun onItemClicked(view: View?, item: Rule) {
Log.e(TAG, item.toString())
when (view?.id) { when (view?.id) {
R.id.iv_copy -> { R.id.iv_copy -> {
PageOption.to(RulesEditFragment::class.java) PageOption.to(RulesEditFragment::class.java)
.setNewActivity(true) .setNewActivity(true)
.putLong(KEY_RULE_ID, item.rule.id) .putLong(KEY_RULE_ID, item.id)
.putString(KEY_RULE_TYPE, item.rule.type) .putString(KEY_RULE_TYPE, item.type)
.putBoolean(KEY_RULE_CLONE, true) .putBoolean(KEY_RULE_CLONE, true)
.open(this) .open(this)
} }
R.id.iv_edit -> { R.id.iv_edit -> {
PageOption.to(RulesEditFragment::class.java) PageOption.to(RulesEditFragment::class.java)
.setNewActivity(true) .setNewActivity(true)
.putLong(KEY_RULE_ID, item.rule.id) .putLong(KEY_RULE_ID, item.id)
.putString(KEY_RULE_TYPE, item.rule.type) .putString(KEY_RULE_TYPE, item.type)
.open(this) .open(this)
} }
R.id.iv_delete -> { R.id.iv_delete -> {
@ -117,7 +115,7 @@ class RulesFragment : BaseFragment<FragmentRulesBinding?>(), RulePagingAdapter.O
.positiveText(R.string.lab_yes) .positiveText(R.string.lab_yes)
.negativeText(R.string.lab_no) .negativeText(R.string.lab_no)
.onPositive { _: MaterialDialog?, _: DialogAction? -> .onPositive { _: MaterialDialog?, _: DialogAction? ->
viewModel.delete(item.rule.id) viewModel.delete(item.id)
XToastUtils.success(R.string.delete_rule_toast) XToastUtils.success(R.string.delete_rule_toast)
} }
.show() .show()

View File

@ -64,7 +64,7 @@ class BatteryService : Service() {
Log.d(TAG, "自动删除N天前的转发记录") Log.d(TAG, "自动删除N天前的转发记录")
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
cal.add(Calendar.DAY_OF_MONTH, 0 - SettingUtils.autoCleanLogsDays) cal.add(Calendar.DAY_OF_MONTH, 0 - SettingUtils.autoCleanLogsDays)
Core.logs.deleteTimeAgo(cal.timeInMillis) Core.msg.deleteTimeAgo(cal.timeInMillis)
} }
//电量发生变化 //电量发生变化

View File

@ -12,6 +12,9 @@ object Worker {
const val sendLogId = "send_log_id" const val sendLogId = "send_log_id"
const val sendSbnId = "send_sbn_id" const val sendSbnId = "send_sbn_id"
const val updateLogs = "update_logs" const val updateLogs = "update_logs"
const val ruleId = "rule_id"
const val senderIndex = "sender_index"
const val msgId = "msg_id"
} }
//初始化相关 //初始化相关
@ -103,6 +106,11 @@ const val CHECK_REGEX = "regex"
const val CHECK_SIM_SLOT_ALL = "ALL" const val CHECK_SIM_SLOT_ALL = "ALL"
const val CHECK_SIM_SLOT_1 = "SIM1" const val CHECK_SIM_SLOT_1 = "SIM1"
const val CHECK_SIM_SLOT_2 = "SIM2" const val CHECK_SIM_SLOT_2 = "SIM2"
//发送通道执行逻辑ALL=全部执行, UntilFail=失败即终止, UntilSuccess=成功即终止
const val SENDER_LOGIC_ALL = "ALL"
const val SENDER_LOGIC_UNTIL_FAIL = "UntilFail"
const val SENDER_LOGIC_UNTIL_SUCCESS = "UntilSuccess"
val TYPE_MAP = object : HashMap<String, String>() { val TYPE_MAP = object : HashMap<String, String>() {
init { init {
put("sms", getString(R.string.rule_sms)) put("sms", getString(R.string.rule_sms))

View File

@ -187,9 +187,11 @@ class PhoneUtils private constructor() {
Log.d(TAG, "selectionArgs = $selectionArgs") Log.d(TAG, "selectionArgs = $selectionArgs")
//为了兼容性这里全部取出后手动分页 //为了兼容性这里全部取出后手动分页
val cursor = Core.app.contentResolver.query( val cursor = (if (limit == 1) Core.app.contentResolver.query(
CallLog.Calls.CONTENT_URI, null, selection, selectionArgs.toTypedArray(), CallLog.Calls.DEFAULT_SORT_ORDER + " limit $limit offset $offset"
) else Core.app.contentResolver.query(
CallLog.Calls.CONTENT_URI, null, selection, selectionArgs.toTypedArray(), CallLog.Calls.DEFAULT_SORT_ORDER // + " limit $limit offset $offset" CallLog.Calls.CONTENT_URI, null, selection, selectionArgs.toTypedArray(), CallLog.Calls.DEFAULT_SORT_ORDER // + " limit $limit offset $offset"
) ?: return callInfoList )) ?: return callInfoList
Log.i(TAG, "cursor count:" + cursor.count) Log.i(TAG, "cursor count:" + cursor.count)
// 避免超过总数后循环取出 // 避免超过总数后循环取出

View File

@ -0,0 +1,67 @@
@file:Suppress("DEPRECATION")
package com.idormy.sms.forwarder.utils
import android.content.Context
import android.content.SharedPreferences
import android.preference.PreferenceManager
/**
* Created by aykutasil on 8.12.2016.
*/
@Suppress("unused")
class PrefsHelper private constructor() {
lateinit var preference: SharedPreferences
val prefEditor: SharedPreferences.Editor
get() = preference.edit()
constructor(context: Context, prefName: String) : this() {
preference = context.getSharedPreferences(prefName, Context.MODE_PRIVATE)
}
constructor(context: Context) : this() {
preference = getDefaultPreference(context)
}
companion object {
private val DEFAULT_STRING_VALUE: String? = null
private const val DEFAULT_INT_VALUE = 0
private const val DEFAULT_BOOLEAN_VALUE = false
fun getDefaultPreference(context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context)
}
fun writePrefString(context: Context, key: String, value: String?) {
PrefsHelper(context).prefEditor.putString(key, value).commit()
}
fun readPrefString(context: Context, key: String): String? {
return PrefsHelper(context).preference.getString(key, DEFAULT_STRING_VALUE)
}
fun writePrefInt(context: Context, key: String, value: Int) {
PrefsHelper(context).prefEditor.putInt(key, value).commit()
}
fun readPrefInt(context: Context, key: String): Int {
return PrefsHelper(context).preference.getInt(key, DEFAULT_INT_VALUE)
}
fun writePrefBool(context: Context, key: String, value: Boolean) {
PrefsHelper(context).prefEditor.putBoolean(key, value).commit()
}
fun readPrefBool(context: Context, key: String): Boolean {
return PrefsHelper(context).preference.getBoolean(key, DEFAULT_BOOLEAN_VALUE)
}
fun clearPreference(context: Context) {
PrefsHelper(context).preference.edit().clear().apply()
}
}
}

View File

@ -9,11 +9,11 @@ import com.google.gson.Gson
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender
import com.idormy.sms.forwarder.database.entity.Rule import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.database.entity.Sender
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.result.SendResponse import com.idormy.sms.forwarder.entity.result.SendResponse
import com.idormy.sms.forwarder.entity.setting.* import com.idormy.sms.forwarder.entity.setting.*
import com.idormy.sms.forwarder.utils.sender.* import com.idormy.sms.forwarder.utils.sender.*
import com.idormy.sms.forwarder.workers.SendLogicWorker
import com.idormy.sms.forwarder.workers.SendWorker 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
@ -24,13 +24,6 @@ import java.util.*
object SendUtils { object SendUtils {
private const val TAG = "SendUtils" private const val TAG = "SendUtils"
//批量发送消息
/*fun sendMsgList(infoList: List<MsgInfo>, type: String) {
for (msgInfo in infoList) {
sendMsg(msgInfo, type)
}
}*/
//发送消息 //发送消息
fun sendMsg(msgInfo: MsgInfo) { fun sendMsg(msgInfo: MsgInfo) {
val request = OneTimeWorkRequestBuilder<SendWorker>() val request = OneTimeWorkRequestBuilder<SendWorker>()
@ -53,9 +46,9 @@ object SendUtils {
e.printStackTrace() e.printStackTrace()
Date() Date()
} }
val simInfo: String = item.logs.simInfo val simInfo: String = item.msg.simInfo
val simSlot: Int = if (simInfo.startsWith("SIM2")) 2 else 1 val simSlot: Int = if (simInfo.startsWith("SIM2")) 2 else 1
val msgInfo = MsgInfo(item.logs.type, item.logs.from, item.logs.content, date, simInfo, simSlot) val msgInfo = MsgInfo(item.msg.type, item.msg.from, item.msg.content, date, simInfo, simSlot)
Log.d(TAG, "resendMsg msgInfo:$msgInfo") Log.d(TAG, "resendMsg msgInfo:$msgInfo")
if (rematch) { if (rematch) {
@ -63,72 +56,73 @@ object SendUtils {
return return
} }
sendMsgSender(msgInfo, item.relation.rule, item.relation.sender, item.logs.id) //sendMsgSender(msgInfo, item.rule, item.sender, item.logs.id)
} }
//匹配发送通道发送消息 //匹配发送通道发送消息
fun sendMsgSender(msgInfo: MsgInfo, rule: Rule, sender: Sender, logId: Long) { fun sendMsgSender(msgInfo: MsgInfo, rule: Rule, senderIndex: Int = 0, logId: Long = 0L, msgId: Long = 0L) {
try { try {
val sender = rule.senderList[senderIndex]
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)
DingtalkGroupRobotUtils.sendMsg(settingVo, msgInfo, rule, logId) DingtalkGroupRobotUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_EMAIL -> { TYPE_EMAIL -> {
val settingVo = Gson().fromJson(sender.jsonSetting, EmailSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, EmailSetting::class.java)
EmailUtils.sendMsg(settingVo, msgInfo, rule, logId) EmailUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_BARK -> { TYPE_BARK -> {
val settingVo = Gson().fromJson(sender.jsonSetting, BarkSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, BarkSetting::class.java)
BarkUtils.sendMsg(settingVo, msgInfo, rule, logId) BarkUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_WEBHOOK -> { TYPE_WEBHOOK -> {
val settingVo = Gson().fromJson(sender.jsonSetting, WebhookSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, WebhookSetting::class.java)
WebhookUtils.sendMsg(settingVo, msgInfo, rule, logId) WebhookUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_WEWORK_ROBOT -> { TYPE_WEWORK_ROBOT -> {
val settingVo = Gson().fromJson(sender.jsonSetting, WeworkRobotSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, WeworkRobotSetting::class.java)
WeworkRobotUtils.sendMsg(settingVo, msgInfo, rule, logId) WeworkRobotUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_WEWORK_AGENT -> { TYPE_WEWORK_AGENT -> {
val settingVo = Gson().fromJson(sender.jsonSetting, WeworkAgentSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, WeworkAgentSetting::class.java)
WeworkAgentUtils.sendMsg(settingVo, msgInfo, rule, logId) WeworkAgentUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_SERVERCHAN -> { TYPE_SERVERCHAN -> {
val settingVo = Gson().fromJson(sender.jsonSetting, ServerchanSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, ServerchanSetting::class.java)
ServerchanUtils.sendMsg(settingVo, msgInfo, rule, logId) ServerchanUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_TELEGRAM -> { TYPE_TELEGRAM -> {
val settingVo = Gson().fromJson(sender.jsonSetting, TelegramSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, TelegramSetting::class.java)
TelegramUtils.sendMsg(settingVo, msgInfo, rule, logId) TelegramUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_SMS -> { TYPE_SMS -> {
val settingVo = Gson().fromJson(sender.jsonSetting, SmsSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, SmsSetting::class.java)
SmsUtils.sendMsg(settingVo, msgInfo, rule, logId) SmsUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_FEISHU -> { TYPE_FEISHU -> {
val settingVo = Gson().fromJson(sender.jsonSetting, FeishuSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, FeishuSetting::class.java)
FeishuUtils.sendMsg(settingVo, msgInfo, rule, logId) FeishuUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_PUSHPLUS -> { TYPE_PUSHPLUS -> {
val settingVo = Gson().fromJson(sender.jsonSetting, PushplusSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, PushplusSetting::class.java)
PushplusUtils.sendMsg(settingVo, msgInfo, rule, logId) PushplusUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_GOTIFY -> { TYPE_GOTIFY -> {
val settingVo = Gson().fromJson(sender.jsonSetting, GotifySetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, GotifySetting::class.java)
GotifyUtils.sendMsg(settingVo, msgInfo, rule, logId) GotifyUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_DINGTALK_INNER_ROBOT -> { TYPE_DINGTALK_INNER_ROBOT -> {
val settingVo = Gson().fromJson(sender.jsonSetting, DingtalkInnerRobotSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, DingtalkInnerRobotSetting::class.java)
DingtalkInnerRobotUtils.sendMsg(settingVo, msgInfo, rule, logId) DingtalkInnerRobotUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_FEISHU_APP -> { TYPE_FEISHU_APP -> {
val settingVo = Gson().fromJson(sender.jsonSetting, FeishuAppSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, FeishuAppSetting::class.java)
FeishuAppUtils.sendMsg(settingVo, msgInfo, rule, logId) FeishuAppUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
TYPE_URL_SCHEME -> { TYPE_URL_SCHEME -> {
val settingVo = Gson().fromJson(sender.jsonSetting, UrlSchemeSetting::class.java) val settingVo = Gson().fromJson(sender.jsonSetting, UrlSchemeSetting::class.java)
UrlSchemeUtils.sendMsg(settingVo, msgInfo, rule, logId) UrlSchemeUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId)
} }
else -> { else -> {
updateLogs(logId, 0, "未知发送通道") updateLogs(logId, 0, "未知发送通道")
@ -140,6 +134,24 @@ object SendUtils {
} }
} }
fun senderLogic(status: Int, msgInfo: MsgInfo, rule: Rule?, senderIndex: Int = 0, msgId: Long = 0L) {
if (rule == null) return
//发送通道执行逻辑ALL=全部执行, UntilFail=失败即终止, UntilSuccess=成功即终止
if (senderIndex < rule.senderList.count() - 1 && ((status == 2 && rule.senderLogic == SENDER_LOGIC_UNTIL_FAIL) || (status == 0 && rule.senderLogic == SENDER_LOGIC_UNTIL_SUCCESS))) {
val request = OneTimeWorkRequestBuilder<SendLogicWorker>()
.setInputData(
workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo),
Worker.ruleId to rule.id,
Worker.senderIndex to senderIndex + 1,
Worker.msgId to msgId,
)
)
.build()
WorkManager.getInstance(XUtil.getContext()).enqueue(request)
}
}
//更新转发日志状态 //更新转发日志状态
fun updateLogs(logId: Long?, status: Int, response: String) { fun updateLogs(logId: Long?, status: Int, response: String) {

View File

@ -25,7 +25,9 @@ class BarkUtils {
setting: BarkSetting, setting: BarkSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val title: String = if (rule != null) { val title: String = if (rule != null) {
msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace) msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace)
@ -86,18 +88,19 @@ class BarkUtils {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, BarkResult::class.java) val resp = Gson().fromJson(response, BarkResult::class.java)
if (resp?.code == 200L) { val status = if (resp?.code == 200L) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
@ -105,7 +108,7 @@ class BarkUtils {
} }
fun sendMsg(setting: BarkSetting, msgInfo: MsgInfo) { fun sendMsg(setting: BarkSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -30,7 +30,9 @@ class DingtalkGroupRobotUtils private constructor() {
setting: DingtalkGroupRobotSetting, setting: DingtalkGroupRobotSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val content: String = if (rule != null) { val content: String = if (rule != null) {
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
@ -97,18 +99,18 @@ class DingtalkGroupRobotUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, DingtalkResult::class.java) val resp = Gson().fromJson(response, DingtalkResult::class.java)
if (resp?.errcode == 0L) { val status = if (resp?.errcode == 0L) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
@ -116,7 +118,7 @@ class DingtalkGroupRobotUtils private constructor() {
} }
fun sendMsg(setting: DingtalkGroupRobotSetting, msgInfo: MsgInfo) { fun sendMsg(setting: DingtalkGroupRobotSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -36,12 +36,14 @@ class DingtalkInnerRobotUtils private constructor() {
setting: DingtalkInnerRobotSetting, setting: DingtalkInnerRobotSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
var accessToken: String by SharedPreference("accessToken_" + setting.agentID, "") var accessToken: String by SharedPreference("accessToken_" + setting.agentID, "")
var expiresIn: Long by SharedPreference("expiresIn_" + setting.agentID, 0L) var expiresIn: Long by SharedPreference("expiresIn_" + setting.agentID, 0L)
if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) { if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) {
return sendTextMsg(setting, msgInfo, rule, logId) return sendTextMsg(setting, msgInfo, rule, senderIndex, logId, msgId)
} }
val requestUrl = "https://api.dingtalk.com/v1.0/oauth2/accessToken" val requestUrl = "https://api.dingtalk.com/v1.0/oauth2/accessToken"
@ -93,7 +95,9 @@ class DingtalkInnerRobotUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
@ -103,9 +107,10 @@ class DingtalkInnerRobotUtils private constructor() {
if (!TextUtils.isEmpty(resp?.accessToken)) { if (!TextUtils.isEmpty(resp?.accessToken)) {
accessToken = resp.accessToken.toString() accessToken = resp.accessToken.toString()
expiresIn = System.currentTimeMillis() + ((resp.expireIn ?: 7200) - 120) * 1000L //提前2分钟过期 expiresIn = System.currentTimeMillis() + ((resp.expireIn ?: 7200) - 120) * 1000L //提前2分钟过期
sendTextMsg(setting, msgInfo, rule, logId) sendTextMsg(setting, msgInfo, rule, senderIndex, logId, msgId)
} else { } else {
SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response)) SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response))
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
} }
} }
@ -118,7 +123,9 @@ class DingtalkInnerRobotUtils private constructor() {
setting: DingtalkInnerRobotSetting, setting: DingtalkInnerRobotSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val requestUrl = "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend" val requestUrl = "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend"
Log.d(TAG, "requestUrl$requestUrl") Log.d(TAG, "requestUrl$requestUrl")
@ -197,25 +204,25 @@ class DingtalkInnerRobotUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, DingtalkInnerRobotResult::class.java) val resp = Gson().fromJson(response, DingtalkInnerRobotResult::class.java)
if (!TextUtils.isEmpty(resp?.processQueryKey)) { val status = if (!TextUtils.isEmpty(resp?.processQueryKey)) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
} }
fun sendMsg(setting: DingtalkInnerRobotSetting, msgInfo: MsgInfo) { fun sendMsg(setting: DingtalkInnerRobotSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }

View File

@ -21,7 +21,9 @@ class EmailUtils {
setting: EmailSetting, setting: EmailSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val title: String = if (rule != null) { val title: String = if (rule != null) {
msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace) msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace)
@ -134,18 +136,21 @@ class EmailUtils {
MailSender.getInstance().sendMail(mail, object : MailSender.OnMailSendListener { MailSender.getInstance().sendMail(mail, object : MailSender.OnMailSendListener {
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
Log.e("MailSender", e.message.toString()) Log.e("MailSender", e.message.toString())
SendUtils.updateLogs(logId, 0, e.message.toString()) val status = 0
SendUtils.updateLogs(logId, status, e.message.toString())
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess() { override fun onSuccess() {
SendUtils.updateLogs(logId, 2, ResUtils.getString(R.string.request_succeeded)) SendUtils.updateLogs(logId, 2, ResUtils.getString(R.string.request_succeeded))
SendUtils.senderLogic(2, msgInfo, rule, senderIndex, msgId)
} }
}) })
} }
fun sendMsg(setting: EmailSetting, msgInfo: MsgInfo) { fun sendMsg(setting: EmailSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -28,13 +28,15 @@ class FeishuAppUtils private constructor() {
setting: FeishuAppSetting, setting: FeishuAppSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
var accessToken: String by SharedPreference("feishu_access_token_" + setting.appId, "") var accessToken: String by SharedPreference("feishu_access_token_" + setting.appId, "")
var expiresIn: Long by SharedPreference("feishu_expires_in_" + setting.appId, 0L) var expiresIn: Long by SharedPreference("feishu_expires_in_" + setting.appId, 0L)
if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) { if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) {
return sendTextMsg(setting, msgInfo, rule, logId) return sendTextMsg(setting, msgInfo, rule, senderIndex, logId, msgId)
} }
val requestUrl = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" val requestUrl = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
@ -51,7 +53,9 @@ class FeishuAppUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
@ -61,9 +65,10 @@ class FeishuAppUtils private constructor() {
if (!TextUtils.isEmpty(resp?.tenant_access_token)) { if (!TextUtils.isEmpty(resp?.tenant_access_token)) {
accessToken = resp.tenant_access_token.toString() accessToken = resp.tenant_access_token.toString()
expiresIn = System.currentTimeMillis() + ((resp.expire ?: 7010) - 120) * 1000L //提前2分钟过期 expiresIn = System.currentTimeMillis() + ((resp.expire ?: 7010) - 120) * 1000L //提前2分钟过期
sendTextMsg(setting, msgInfo, rule, logId) sendTextMsg(setting, msgInfo, rule, senderIndex, logId, msgId)
} else { } else {
SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response)) SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response))
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
} }
} }
@ -76,7 +81,9 @@ class FeishuAppUtils private constructor() {
setting: FeishuAppSetting, setting: FeishuAppSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val requestUrl = "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=user_id" val requestUrl = "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=user_id"
Log.d(TAG, "requestUrl$requestUrl") Log.d(TAG, "requestUrl$requestUrl")
@ -117,7 +124,9 @@ class FeishuAppUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
@ -126,18 +135,16 @@ class FeishuAppUtils private constructor() {
//Log.d(TAG, "cipherSuite=" + response.handshake().cipherSuite().toString()) //Log.d(TAG, "cipherSuite=" + response.handshake().cipherSuite().toString())
val resp = Gson().fromJson(response, FeishuAppResult::class.java) val resp = Gson().fromJson(response, FeishuAppResult::class.java)
if (resp?.code == 0L) { val status = if (resp?.code == 0L) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
} }
fun sendMsg(setting: FeishuAppSetting, msgInfo: MsgInfo) { fun sendMsg(setting: FeishuAppSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
private fun jsonInnerStr(string: String?): String { private fun jsonInnerStr(string: String?): String {

View File

@ -83,7 +83,9 @@ class FeishuUtils private constructor() {
setting: FeishuSetting, setting: FeishuSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val from: String = msgInfo.from val from: String = msgInfo.from
val title: String = if (rule != null) { val title: String = if (rule != null) {
@ -144,18 +146,18 @@ class FeishuUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, FeishuResult::class.java) val resp = Gson().fromJson(response, FeishuResult::class.java)
if (resp?.code == 0L) { val status = if (resp?.code == 0L) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
@ -181,7 +183,7 @@ class FeishuUtils private constructor() {
} }
fun sendMsg(setting: FeishuSetting, msgInfo: MsgInfo) { fun sendMsg(setting: FeishuSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -23,7 +23,9 @@ class GotifyUtils {
setting: GotifySetting, setting: GotifySetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val title: String = if (rule != null) { val title: String = if (rule != null) {
msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace) msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace)
@ -64,18 +66,18 @@ class GotifyUtils {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, GotifyResult::class.java) val resp = Gson().fromJson(response, GotifyResult::class.java)
if (resp?.id != null) { val status = if (resp?.id != null) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
@ -83,7 +85,7 @@ class GotifyUtils {
} }
fun sendMsg(setting: GotifySetting, msgInfo: MsgInfo) { fun sendMsg(setting: GotifySetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -27,7 +27,9 @@ class PushplusUtils private constructor() {
setting: PushplusSetting, setting: PushplusSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val title: String = if (rule != null) { val title: String = if (rule != null) {
msgInfo.getTitleForSend(setting.titleTemplate.toString(), rule.regexReplace) msgInfo.getTitleForSend(setting.titleTemplate.toString(), rule.regexReplace)
@ -79,18 +81,18 @@ class PushplusUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, PushplusResult::class.java) val resp = Gson().fromJson(response, PushplusResult::class.java)
if (resp?.code == 200L) { val status = if (resp?.code == 200L) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
@ -98,7 +100,7 @@ class PushplusUtils private constructor() {
} }
fun sendMsg(setting: PushplusSetting, msgInfo: MsgInfo) { fun sendMsg(setting: PushplusSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -24,7 +24,9 @@ class ServerchanUtils {
setting: ServerchanSetting, setting: ServerchanSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val title: String = if (rule != null) { val title: String = if (rule != null) {
msgInfo.getTitleForSend(setting.titleTemplate.toString(), rule.regexReplace) msgInfo.getTitleForSend(setting.titleTemplate.toString(), rule.regexReplace)
@ -58,18 +60,17 @@ class ServerchanUtils {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, ServerchanResult::class.java) val resp = Gson().fromJson(response, ServerchanResult::class.java)
if (resp?.code == 0L) { val status = if (resp?.code == 0L) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
@ -77,7 +78,7 @@ class ServerchanUtils {
} }
fun sendMsg(setting: ServerchanSetting, msgInfo: MsgInfo) { fun sendMsg(setting: ServerchanSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -26,16 +26,20 @@ class SmsUtils {
setting: SmsSetting, setting: SmsSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
//仅当无网络时启用 && 判断是否真实有网络 //仅当无网络时启用 && 判断是否真实有网络
if (setting.onlyNoNetwork == true && NetworkUtils.isHaveInternet() && NetworkUtils.isAvailableByPing()) { if (setting.onlyNoNetwork == true && NetworkUtils.isHaveInternet() && NetworkUtils.isAvailableByPing()) {
SendUtils.updateLogs(logId, 0, ResUtils.getString(R.string.OnlyNoNetwork)) SendUtils.updateLogs(logId, 0, ResUtils.getString(R.string.OnlyNoNetwork))
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
return return
} }
if (ActivityCompat.checkSelfPermission(XUtil.getContext(), Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.checkSelfPermission(XUtil.getContext(), Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
SendUtils.updateLogs(logId, 0, ResUtils.getString(R.string.no_sms_sending_permission)) SendUtils.updateLogs(logId, 0, ResUtils.getString(R.string.no_sms_sending_permission))
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
return return
} }
@ -62,13 +66,15 @@ class SmsUtils {
val res: String? = PhoneUtils.sendSms(mSubscriptionId, mobiles, content) val res: String? = PhoneUtils.sendSms(mSubscriptionId, mobiles, content)
if (res == null) { if (res == null) {
SendUtils.updateLogs(logId, 2, ResUtils.getString(R.string.request_succeeded)) SendUtils.updateLogs(logId, 2, ResUtils.getString(R.string.request_succeeded))
SendUtils.senderLogic(2, msgInfo, rule, senderIndex, msgId)
} else { } else {
SendUtils.updateLogs(logId, 0, res) SendUtils.updateLogs(logId, 0, res)
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
} }
} }
fun sendMsg(setting: SmsSetting, msgInfo: MsgInfo) { fun sendMsg(setting: SmsSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -30,7 +30,9 @@ class TelegramUtils private constructor() {
setting: TelegramSetting, setting: TelegramSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
if (setting.method == null || setting.method == "POST") { if (setting.method == null || setting.method == "POST") {
msgInfo.content = htmlEncode(msgInfo.content) msgInfo.content = htmlEncode(msgInfo.content)
@ -116,18 +118,18 @@ class TelegramUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, TelegramResult::class.java) val resp = Gson().fromJson(response, TelegramResult::class.java)
if (resp?.ok == true) { val status = if (resp?.ok == true) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
@ -135,7 +137,7 @@ class TelegramUtils private constructor() {
} }
fun sendMsg(setting: TelegramSetting, msgInfo: MsgInfo) { fun sendMsg(setting: TelegramSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
private fun htmlEncode(source: String?): String { private fun htmlEncode(source: String?): String {

View File

@ -26,7 +26,9 @@ class UrlSchemeUtils private constructor() {
setting: UrlSchemeSetting, setting: UrlSchemeSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val from: String = msgInfo.from val from: String = msgInfo.from
val content: String = if (rule != null) { val content: String = if (rule != null) {
@ -62,16 +64,18 @@ class UrlSchemeUtils private constructor() {
try { try {
XUtil.getContext().startActivity(intent) XUtil.getContext().startActivity(intent)
SendUtils.updateLogs(logId, 2, "调用成功") SendUtils.updateLogs(logId, 2, "调用成功")
SendUtils.senderLogic(2, msgInfo, rule, senderIndex, msgId)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
Log.e(TAG, e.message.toString()) Log.e(TAG, e.message.toString())
SendUtils.updateLogs(logId, 0, e.message.toString()) SendUtils.updateLogs(logId, 0, e.message.toString())
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
} }
} }
fun sendMsg(setting: UrlSchemeSetting, msgInfo: MsgInfo) { fun sendMsg(setting: UrlSchemeSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -32,7 +32,9 @@ class WebhookUtils {
setting: WebhookSetting, setting: WebhookSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val from: String = msgInfo.from val from: String = msgInfo.from
val content: String = if (rule != null) { val content: String = if (rule != null) {
@ -183,12 +185,15 @@ class WebhookUtils {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, 2, response)
SendUtils.senderLogic(2, msgInfo, rule, senderIndex, msgId)
} }
}) })
@ -203,7 +208,7 @@ class WebhookUtils {
} }
fun sendMsg(setting: WebhookSetting, msgInfo: MsgInfo) { fun sendMsg(setting: WebhookSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -36,13 +36,15 @@ class WeworkAgentUtils private constructor() {
setting: WeworkAgentSetting, setting: WeworkAgentSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
var accessToken: String by SharedPreference("access_token_" + setting.agentID, "") var accessToken: String by SharedPreference("access_token_" + setting.agentID, "")
var expiresIn: Long by SharedPreference("expires_in_" + setting.agentID, 0L) var expiresIn: Long by SharedPreference("expires_in_" + setting.agentID, 0L)
if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) { if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) {
return sendTextMsg(setting, msgInfo, rule, logId) return sendTextMsg(setting, msgInfo, rule, senderIndex, logId, msgId)
} }
var getTokenUrl = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" var getTokenUrl = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?"
@ -90,7 +92,9 @@ class WeworkAgentUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
@ -100,9 +104,10 @@ class WeworkAgentUtils private constructor() {
if (resp?.errcode == 0L) { if (resp?.errcode == 0L) {
accessToken = resp.access_token.toString() accessToken = resp.access_token.toString()
expiresIn = System.currentTimeMillis() + ((resp.expires_in ?: 7200) - 120) * 1000L //提前2分钟过期 expiresIn = System.currentTimeMillis() + ((resp.expires_in ?: 7200) - 120) * 1000L //提前2分钟过期
sendTextMsg(setting, msgInfo, rule, logId) sendTextMsg(setting, msgInfo, rule, senderIndex, logId, msgId)
} else { } else {
SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response)) SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response))
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
} }
} }
@ -115,7 +120,9 @@ class WeworkAgentUtils private constructor() {
setting: WeworkAgentSetting, setting: WeworkAgentSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val content: String = if (rule != null) { val content: String = if (rule != null) {
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
@ -181,25 +188,25 @@ class WeworkAgentUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) val status = 0
SendUtils.updateLogs(logId, status, e.displayMessage)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, DingtalkResult::class.java) val resp = Gson().fromJson(response, DingtalkResult::class.java)
if (resp?.errcode == 0L) { val status = if (resp?.errcode == 0L) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
} }
fun sendMsg(setting: WeworkAgentSetting, msgInfo: MsgInfo) { fun sendMsg(setting: WeworkAgentSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }

View File

@ -23,7 +23,9 @@ class WeworkRobotUtils private constructor() {
setting: WeworkRobotSetting, setting: WeworkRobotSetting,
msgInfo: MsgInfo, msgInfo: MsgInfo,
rule: Rule?, rule: Rule?,
logId: Long?, senderIndex: Int = 0,
logId: Long = 0L,
msgId: Long = 0L
) { ) {
val content: String = if (rule != null) { val content: String = if (rule != null) {
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
@ -58,17 +60,16 @@ class WeworkRobotUtils private constructor() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
SendUtils.updateLogs(logId, 0, e.displayMessage) SendUtils.updateLogs(logId, 0, e.displayMessage)
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
val resp = Gson().fromJson(response, WeworkRobotResult::class.java) val resp = Gson().fromJson(response, WeworkRobotResult::class.java)
if (resp?.errcode == 0L) { val status = if (resp?.errcode == 0L) 2 else 0
SendUtils.updateLogs(logId, 2, response) SendUtils.updateLogs(logId, status, response)
} else { SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
SendUtils.updateLogs(logId, 0, response)
}
} }
}) })
@ -76,7 +77,7 @@ class WeworkRobotUtils private constructor() {
} }
fun sendMsg(setting: WeworkRobotSetting, msgInfo: MsgInfo) { fun sendMsg(setting: WeworkRobotSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null) sendMsg(setting, msgInfo)
} }
} }
} }

View File

@ -0,0 +1,36 @@
package com.idormy.sms.forwarder.workers
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.google.gson.Gson
import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.database.entity.Logs
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.SendUtils
import com.idormy.sms.forwarder.utils.Worker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class SendLogicWorker(
context: Context,
workerParams: WorkerParameters,
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
val msgInfoJson = inputData.getString(Worker.sendMsgInfo)
val msgInfo = Gson().fromJson(msgInfoJson, MsgInfo::class.java)
val ruleId = inputData.getLong(Worker.ruleId, 0L)
val senderIndex = inputData.getInt(Worker.senderIndex, 0)
val msgId = inputData.getLong(Worker.msgId, 0L)
val rule = Core.rule.getOne(ruleId)
val sender = rule.senderList[senderIndex]
val log = Logs(0, rule.type, msgId, rule.id, sender.id)
val logId = Core.logs.insert(log)
SendUtils.sendMsgSender(msgInfo, rule, senderIndex, logId, msgId)
return@withContext Result.success()
}
}

View File

@ -9,7 +9,8 @@ import androidx.work.workDataOf
import com.google.gson.Gson import com.google.gson.Gson
import com.idormy.sms.forwarder.core.Core import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.database.entity.Logs import com.idormy.sms.forwarder.database.entity.Logs
import com.idormy.sms.forwarder.database.entity.RuleAndSender import com.idormy.sms.forwarder.database.entity.Msg
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.xuexiang.xutil.security.CipherUtils import com.xuexiang.xutil.security.CipherUtils
@ -55,7 +56,6 @@ class SendWorker(
Log.e("SendWorker", "免打扰(禁用转发)时间段") Log.e("SendWorker", "免打扰(禁用转发)时间段")
return@withContext Result.failure(workDataOf("send" to "failed")) return@withContext Result.failure(workDataOf("send" to "failed"))
} }
} }
val msgInfoJson = inputData.getString(Worker.sendMsgInfo) val msgInfoJson = inputData.getString(Worker.sendMsgInfo)
@ -75,18 +75,22 @@ class SendWorker(
//【注意】卡槽id-1=获取失败、0=卡槽1、1=卡槽2但是 Rule 表里存的是 SIM1/SIM2 //【注意】卡槽id-1=获取失败、0=卡槽1、1=卡槽2但是 Rule 表里存的是 SIM1/SIM2
val simSlot = "SIM" + (msgInfo.simSlot + 1) val simSlot = "SIM" + (msgInfo.simSlot + 1)
val ruleList: List<RuleAndSender> = Core.rule.getRuleAndSender(msgInfo.type, 1, simSlot) val ruleList: List<Rule> = Core.rule.getRuleList(msgInfo.type, 1, simSlot)
if (ruleList.isEmpty()) { if (ruleList.isEmpty()) {
return@withContext Result.failure(workDataOf("send" to "failed")) return@withContext Result.failure(workDataOf("send" to "failed"))
} }
val msg = Msg(0, msgInfo.type, msgInfo.from, msgInfo.content, msgInfo.simSlot, msgInfo.simInfo, msgInfo.subId)
val msgId = Core.msg.insert(msg)
for (rule in ruleList) { for (rule in ruleList) {
if (!rule.rule.checkMsg(msgInfo)) continue Log.d("SendWorker", rule.toString())
val log = Logs( if (!rule.checkMsg(msgInfo)) continue
0, msgInfo.type, msgInfo.from, msgInfo.content, rule.rule.id, msgInfo.simInfo, msgInfo.subId
) val sender = rule.senderList[0]
val log = Logs(0, msgInfo.type, msgId, rule.id, sender.id)
val logId = Core.logs.insert(log) val logId = Core.logs.insert(log)
SendUtils.sendMsgSender(msgInfo, rule.rule, rule.sender, logId) SendUtils.sendMsgSender(msgInfo, rule, 0, logId, msgId)
} }
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -20,30 +20,9 @@
android:orientation="horizontal"> android:orientation="horizontal">
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:layout_weight="1"
<ImageView
android:id="@+id/iv_sender_image"
android:layout_width="@dimen/card_view_image_size"
android:layout_height="@dimen/card_view_image_size"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/iv_status_image"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="24dp"
android:layout_marginTop="-16dp"
tools:ignore="ContentDescription" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <LinearLayout
@ -85,6 +64,20 @@
android:maxLines="3" android:maxLines="3"
android:textSize="11sp" /> android:textSize="11sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="3dp"
android:layout_marginBottom="3dp"
android:background="?attr/xui_config_color_separator_light" />
<LinearLayout
android:id="@+id/layout_Logs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"></LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -17,6 +17,7 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
<LinearLayout <LinearLayout
@ -40,57 +41,31 @@
</LinearLayout> </LinearLayout>
<TextView
android:id="@+id/tv_rule_match"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:ellipsize="end"
android:gravity="start"
android:maxEms="8"
android:maxLines="3"
android:textSize="11sp" />
<LinearLayout <LinearLayout
android:layout_width="@dimen/card_view_image_size" android:layout_width="0dp"
android:layout_height="@dimen/card_view_image_size" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_weight="1"
android:gravity="center_horizontal"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout
android:layout_width="24dp"
android:layout_height="24dp"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_sender_image"
android:layout_width="24dp"
android:layout_height="24dp"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/iv_sender_status"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginStart="14dp"
android:layout_marginTop="-10dp"
tools:ignore="ContentDescription" />
</LinearLayout>
<TextView <TextView
android:id="@+id/tv_sender_name" android:id="@+id/tv_rule_match"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="5dp"
android:ellipsize="end" android:ellipsize="end"
android:gravity="center" android:gravity="start"
android:maxEms="6" android:maxEms="8"
android:maxLines="1" android:maxLines="3"
android:textSize="9sp" android:textSize="11sp" />
tools:ignore="SmallSp" />
<LinearLayout
android:id="@+id/layout_Senders"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:orientation="horizontal"></LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -23,65 +23,84 @@
style="@style/ruleBarStyle" style="@style/ruleBarStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/select_sender"
android:textStyle="bold" />
<LinearLayout <LinearLayout
android:layout_width="50dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:gravity="center_vertical"
android:layout_marginStart="5dp" android:orientation="horizontal"
android:gravity="center_horizontal" android:paddingBottom="5dp">
android:orientation="vertical">
<LinearLayout
android:layout_width="24dp"
android:layout_height="24dp"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_sender_image"
android:layout_width="24dp"
android:layout_height="24dp"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/iv_sender_status"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginStart="14dp"
android:layout_marginTop="-10dp"
tools:ignore="ContentDescription" />
</LinearLayout>
<TextView <TextView
android:id="@+id/tv_sender_name" android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" android:text="@string/select_sender"
android:gravity="center" android:textStyle="bold" />
android:maxEms="6"
android:maxLines="1" <com.xuexiang.xui.widget.spinner.editspinner.EditSpinner
android:textSize="9sp" android:id="@+id/sp_sender"
tools:ignore="SmallSp" /> android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
app:es_hint="@string/choose_sender"
app:es_maxLength="20"
app:es_maxLine="1" />
</LinearLayout> </LinearLayout>
<com.xuexiang.xui.widget.spinner.editspinner.EditSpinner <View
android:id="@+id/sp_sender" android:layout_width="match_parent"
android:layout_width="0dp" android:layout_height="1dp"
android:background="?attr/xui_config_color_separator_light" />
<LinearLayout
android:id="@+id/layout_Senders"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="5dp" android:orientation="vertical"></LinearLayout>
android:layout_weight="1"
app:es_hint="@string/choose_sender" <LinearLayout
app:es_maxLength="20" android:id="@+id/layout_sender_logic"
app:es_maxLine="1" /> android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sender_logic"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/rg_sender_logic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_sender_logic_all"
style="@style/rg_rb_style_wrap"
android:checked="true"
android:text="@string/sender_logic_all" />
<RadioButton
android:id="@+id/rb_sender_logic_until_fail"
style="@style/rg_rb_style_wrap"
android:text="@string/sender_logic_until_fail" />
<RadioButton
android:id="@+id/rb_sender_logic_until_success"
style="@style/rg_rb_style_wrap"
android:text="@string/sender_logic_until_success" />
</RadioGroup>
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -65,7 +65,6 @@
android:id="@+id/rg_method" android:id="@+id/rg_method"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:orientation="horizontal"> android:orientation="horizontal">
<RadioButton <RadioButton
@ -73,25 +72,41 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:checked="true" android:checked="true"
android:text="@string/post" /> android:scaleX="0.7"
android:scaleY="0.7"
android:text="@string/post"
android:textSize="14sp"
android:translationX="18dp" />
<RadioButton <RadioButton
android:id="@+id/rb_method_get" android:id="@+id/rb_method_get"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/get" /> android:scaleX="0.7"
android:scaleY="0.7"
android:text="@string/get"
android:textSize="14sp"
android:translationX="18dp" />
<RadioButton <RadioButton
android:id="@+id/rb_method_put" android:id="@+id/rb_method_put"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/put" /> android:scaleX="0.7"
android:scaleY="0.7"
android:text="@string/put"
android:textSize="14sp"
android:translationX="18dp" />
<RadioButton <RadioButton
android:id="@+id/rb_method_patch" android:id="@+id/rb_method_patch"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/patch" /> android:scaleX="0.7"
android:scaleY="0.7"
android:text="@string/patch"
android:textSize="14sp"
android:translationX="18dp" />
</RadioGroup> </RadioGroup>

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/xui_config_color_white"
android:orientation="vertical"
android:paddingStart="5dp"
android:paddingEnd="5dp"
tools:ignore="UseCompoundDrawables">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<LinearLayout
android:layout_width="24dp"
android:layout_height="24dp"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_sender_image"
android:layout_width="24dp"
android:layout_height="24dp"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/iv_sender_status"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginStart="14dp"
android:layout_marginTop="-10dp"
tools:ignore="ContentDescription" />
</LinearLayout>
<TextView
android:id="@+id/tv_sender_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:ellipsize="marquee"
android:minHeight="?attr/ms_item_height_size"
android:paddingStart="?attr/ms_padding_left_size"
android:paddingTop="?attr/ms_padding_top_size"
android:paddingEnd="?attr/ms_padding_left_size"
android:paddingBottom="?attr/ms_padding_top_size"
android:singleLine="true"
tools:ignore="PrivateResource" />
<ImageView
android:id="@+id/iv_remove_sender"
android:layout_width="18dp"
android:layout_height="18dp"
android:contentDescription="@string/sender_del"
android:src="@drawable/icon_delete"
app:tint="@color/design_default_color_error" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/xui_config_color_separator_light" />
</LinearLayout>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_horizontal"
android:orientation="vertical">
<LinearLayout
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_sender_image"
android:layout_width="24dp"
android:layout_height="24dp"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/iv_sender_status"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginStart="14dp"
android:layout_marginTop="-10dp"
tools:ignore="ContentDescription,VisualLintBounds" />
</LinearLayout>
<TextView
android:id="@+id/tv_sender_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center"
android:maxEms="10"
android:maxLines="1"
android:textSize="9sp"
tools:ignore="SmallSp" />
</LinearLayout>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_horizontal"
android:orientation="vertical">
<LinearLayout
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_sender_image"
android:layout_width="24dp"
android:layout_height="24dp"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/iv_sender_status"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginStart="14dp"
android:layout_marginTop="-10dp"
tools:ignore="ContentDescription,VisualLintBounds" />
</LinearLayout>
<TextView
android:id="@+id/tv_sender_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center"
android:maxEms="10"
android:maxLines="1"
android:textSize="9sp"
tools:ignore="SmallSp" />
</LinearLayout>

View File

@ -223,6 +223,10 @@
<string name="test_package_name">测试模拟的APP包名</string> <string name="test_package_name">测试模拟的APP包名</string>
<string name="test_inform_title">测试模拟的通知标题</string> <string name="test_inform_title">测试模拟的通知标题</string>
<string name="test_inform_content">测试模拟的通知内容</string> <string name="test_inform_content">测试模拟的通知内容</string>
<string name="sender_logic">执行逻辑</string>
<string name="sender_logic_all">全部执行</string>
<string name="sender_logic_until_fail">失败即止</string>
<string name="sender_logic_until_success">成功即止</string>
<string name="match_sim_slot">匹配卡槽</string> <string name="match_sim_slot">匹配卡槽</string>
<string name="match_field">匹配字段</string> <string name="match_field">匹配字段</string>
<string name="phone_number">手机号</string> <string name="phone_number">手机号</string>
@ -841,6 +845,7 @@
<string name="frpc_failed_to_run">Frpc运行失败</string> <string name="frpc_failed_to_run">Frpc运行失败</string>
<string name="successfully_deleted">删除成功</string> <string name="successfully_deleted">删除成功</string>
<string name="sender_disabled_tips">【注意】该发送通道已经禁用,其关联的规则即便匹配上也不会发送!</string> <string name="sender_disabled_tips">【注意】该发送通道已经禁用,其关联的规则即便匹配上也不会发送!</string>
<string name="sender_contains_tips">【注意】该发送通道已经在列表中,无需重复添加!</string>
<string name="local_call">本地呼叫:</string> <string name="local_call">本地呼叫:</string>
<string name="remote_sms">远程发短信:</string> <string name="remote_sms">远程发短信:</string>
<string name="clear">清除</string> <string name="clear">清除</string>
@ -942,4 +947,6 @@
<string name="copy_public_key">复制公钥</string> <string name="copy_public_key">复制公钥</string>
<string name="sm4_key">SM4密钥</string> <string name="sm4_key">SM4密钥</string>
<string name="sm4_key_tips">客户端/服务端交互采用SM4加解密</string> <string name="sm4_key_tips">客户端/服务端交互采用SM4加解密</string>
<string name="sender_del">删除发送通道</string>
</resources> </resources>