From b79d3d8493613fb4b2ac9c50d8573b1e933bde94 Mon Sep 17 00:00:00 2001 From: pppscn <35696959@qq.com> Date: Sun, 5 Feb 2023 09:15:20 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E5=8D=95=E4=B8=AA?= =?UTF-8?q?=E8=BD=AC=E5=8F=91=E8=A7=84=E5=88=99=E6=94=AF=E6=8C=81=E7=BB=91?= =?UTF-8?q?=E5=AE=9A=E5=A4=9A=E4=B8=AA=E5=8F=91=E9=80=81=E9=80=9A=E9=81=93?= =?UTF-8?q?=EF=BC=8C=E4=B8=94=E6=94=AF=E6=8C=81=E6=89=A7=E8=A1=8C=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=88=E5=85=A8=E9=83=A8=E6=89=A7=E8=A1=8C/?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E5=8D=B3=E6=AD=A2/=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E5=8D=B3=E6=AD=A2=EF=BC=89=20#247=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=EF=BC=9A=E8=BD=AC=E5=8F=91=E6=97=A5=E5=BF=97=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E4=BB=A5=E5=8E=9F=E5=A7=8B=E4=BF=A1=E6=81=AF=E4=B8=BA=E4=B8=BB?= =?UTF-8?q?=EF=BC=8C=E8=81=9A=E5=90=88=E5=B1=95=E7=A4=BA=E8=BD=AC=E5=8F=91?= =?UTF-8?q?=E6=97=A5=E5=BF=97=EF=BC=88=E4=B8=80=E5=AF=B9=E5=A4=9A=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/idormy/sms/forwarder/App.kt | 6 +- .../sms/forwarder/activity/MainActivity.kt | 866 +++++------ .../forwarder/adapter/LogsPagingAdapter.kt | 111 +- .../sms/forwarder/adapter/MsgPagingAdapter.kt | 78 + .../forwarder/adapter/RulePagingAdapter.kt | 144 +- .../com/idormy/sms/forwarder/core/Core.kt | 118 +- .../sms/forwarder/database/AppDatabase.kt | 106 +- .../sms/forwarder/database/dao/MsgDao.kt | 41 + .../sms/forwarder/database/dao/RuleDao.kt | 5 +- .../sms/forwarder/database/dao/SenderDao.kt | 3 + .../sms/forwarder/database/entity/Logs.kt | 38 +- .../database/entity/LogsAndRuleAndSender.kt | 50 +- .../forwarder/database/entity/LogsDetail.kt | 59 + .../sms/forwarder/database/entity/Msg.kt | 43 + .../forwarder/database/entity/MsgAndLogs.kt | 17 + .../sms/forwarder/database/entity/Rule.kt | 371 ++--- .../ext/{Converters.kt => ConvertersDate.kt} | 32 +- .../database/ext/ConvertersSenderList.kt | 29 + .../database/repository/MsgRepository.kt | 22 + .../database/repository/RuleRepository.kt | 73 +- .../database/repository/SenderRepository.kt | 68 +- .../viewmodel/BaseViewModelFactory.kt | 83 +- .../database/viewmodel/MsgViewModel.kt | 36 + .../database/viewmodel/RuleViewModel.kt | 73 +- .../sms/forwarder/fragment/LogsFragment.kt | 297 ++-- .../forwarder/fragment/RulesEditFragment.kt | 1338 +++++++++-------- .../sms/forwarder/fragment/RulesFragment.kt | 258 ++-- .../sms/forwarder/service/BatteryService.kt | 280 ++-- .../idormy/sms/forwarder/utils/Constants.kt | 8 + .../idormy/sms/forwarder/utils/PhoneUtils.kt | 6 +- .../idormy/sms/forwarder/utils/PrefsHelper.kt | 67 + .../idormy/sms/forwarder/utils/SendUtils.kt | 66 +- .../sms/forwarder/utils/sender/BarkUtils.kt | 19 +- .../utils/sender/DingtalkGroupRobotUtils.kt | 18 +- .../utils/sender/DingtalkInnerRobotUtils.kt | 31 +- .../sms/forwarder/utils/sender/EmailUtils.kt | 11 +- .../forwarder/utils/sender/FeishuAppUtils.kt | 31 +- .../sms/forwarder/utils/sender/FeishuUtils.kt | 18 +- .../sms/forwarder/utils/sender/GotifyUtils.kt | 18 +- .../forwarder/utils/sender/PushplusUtils.kt | 18 +- .../forwarder/utils/sender/ServerchanUtils.kt | 19 +- .../sms/forwarder/utils/sender/SmsUtils.kt | 10 +- .../forwarder/utils/sender/TelegramUtils.kt | 18 +- .../forwarder/utils/sender/UrlSchemeUtils.kt | 8 +- .../forwarder/utils/sender/WebhookUtils.kt | 11 +- .../utils/sender/WeworkAgentUtils.kt | 31 +- .../utils/sender/WeworkRobotUtils.kt | 15 +- .../sms/forwarder/workers/SendLogicWorker.kt | 36 + .../sms/forwarder/workers/SendWorker.kt | 20 +- .../adapter_logs_card_view_list_item.xml | 175 ++- .../adapter_rules_card_view_list_item.xml | 221 ++- .../main/res/layout/fragment_rules_edit.xml | 1029 ++++++------- .../res/layout/fragment_senders_webhook.xml | 537 +++---- app/src/main/res/layout/item_add_sender.xml | 70 + app/src/main/res/layout/item_logs.xml | 44 + app/src/main/res/layout/item_sender.xml | 44 + app/src/main/res/values/strings.xml | 7 + 57 files changed, 4047 insertions(+), 3204 deletions(-) create mode 100644 app/src/main/java/com/idormy/sms/forwarder/adapter/MsgPagingAdapter.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/database/dao/MsgDao.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/database/entity/LogsDetail.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/database/entity/Msg.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/database/entity/MsgAndLogs.kt rename app/src/main/java/com/idormy/sms/forwarder/database/ext/{Converters.kt => ConvertersDate.kt} (89%) create mode 100644 app/src/main/java/com/idormy/sms/forwarder/database/ext/ConvertersSenderList.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/database/repository/MsgRepository.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/MsgViewModel.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/utils/PrefsHelper.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/workers/SendLogicWorker.kt create mode 100644 app/src/main/res/layout/item_add_sender.xml create mode 100644 app/src/main/res/layout/item_logs.xml create mode 100644 app/src/main/res/layout/item_sender.xml diff --git a/app/src/main/java/com/idormy/sms/forwarder/App.kt b/app/src/main/java/com/idormy/sms/forwarder/App.kt index a8dbefaf..281cdcd6 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/App.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/App.kt @@ -17,10 +17,7 @@ import com.gyf.cactus.ext.cactus import com.idormy.sms.forwarder.activity.MainActivity import com.idormy.sms.forwarder.core.Core import com.idormy.sms.forwarder.database.AppDatabase -import com.idormy.sms.forwarder.database.repository.FrpcRepository -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.database.repository.* import com.idormy.sms.forwarder.entity.SimInfo import com.idormy.sms.forwarder.receiver.CactusReceiver import com.idormy.sms.forwarder.service.BatteryService @@ -48,6 +45,7 @@ class App : Application(), CactusCallback, Configuration.Provider by Core { val applicationScope = CoroutineScope(SupervisorJob()) val database by lazy { AppDatabase.getInstance(this) } val frpcRepository by lazy { FrpcRepository(database.frpcDao()) } + val msgRepository by lazy { MsgRepository(database.msgDao()) } val logsRepository by lazy { LogsRepository(database.logsDao()) } val ruleRepository by lazy { RuleRepository(database.ruleDao()) } val senderRepository by lazy { SenderRepository(database.senderDao()) } diff --git a/app/src/main/java/com/idormy/sms/forwarder/activity/MainActivity.kt b/app/src/main/java/com/idormy/sms/forwarder/activity/MainActivity.kt index 4a17226e..57efb90f 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/activity/MainActivity.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/activity/MainActivity.kt @@ -1,434 +1,434 @@ -package com.idormy.sms.forwarder.activity - -import android.annotation.SuppressLint -import android.app.ActivityManager -import android.content.Context -import android.content.Intent -import android.os.Build -import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater -import android.view.MenuItem -import android.view.View -import android.widget.TextView -import androidx.appcompat.app.ActionBarDrawerToggle -import androidx.appcompat.widget.Toolbar -import androidx.recyclerview.widget.RecyclerView -import androidx.viewpager.widget.ViewPager -import com.google.android.material.bottomnavigation.BottomNavigationView -import com.google.android.material.bottomsheet.BottomSheetDialog -import com.gyf.cactus.ext.cactusUpdateNotification -import com.idormy.sms.forwarder.App -import com.idormy.sms.forwarder.R -import com.idormy.sms.forwarder.adapter.WidgetItemAdapter -import com.idormy.sms.forwarder.core.BaseActivity -import com.idormy.sms.forwarder.core.webview.AgentWebActivity -import com.idormy.sms.forwarder.database.AppDatabase -import com.idormy.sms.forwarder.databinding.ActivityMainBinding -import com.idormy.sms.forwarder.fragment.* -import com.idormy.sms.forwarder.utils.* -import com.idormy.sms.forwarder.utils.sdkinit.XUpdateInit -import com.idormy.sms.forwarder.widget.GuideTipsDialog.Companion.showTips -import com.idormy.sms.forwarder.widget.GuideTipsDialog.Companion.showTipsForce -import com.jeremyliao.liveeventbus.LiveEventBus -import com.xuexiang.xaop.annotation.SingleClick -import com.xuexiang.xhttp2.XHttp -import com.xuexiang.xhttp2.callback.DownloadProgressCallBack -import com.xuexiang.xhttp2.exception.ApiException -import com.xuexiang.xpage.base.XPageFragment -import com.xuexiang.xpage.core.PageOption -import com.xuexiang.xpage.model.PageInfo -import com.xuexiang.xui.adapter.FragmentAdapter -import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder -import com.xuexiang.xui.utils.DensityUtils -import com.xuexiang.xui.utils.ResUtils -import com.xuexiang.xui.utils.WidgetUtils -import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction -import com.xuexiang.xui.widget.dialog.materialdialog.GravityEnum -import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog -import com.xuexiang.xutil.file.FileUtils -import com.xuexiang.xutil.net.NetworkUtils -import frpclib.Frpclib -import io.reactivex.CompletableObserver -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers -import java.io.File - - -@Suppress("DEPRECATION", "PrivatePropertyName") -class MainActivity : BaseActivity(), - View.OnClickListener, - BottomNavigationView.OnNavigationItemSelectedListener, - Toolbar.OnMenuItemClickListener, - RecyclerViewHolder.OnItemClickListener { - - private val TAG: String = MainActivity::class.java.simpleName - private lateinit var mTitles: Array - private var logsType: String = "sms" - private var ruleType: String = "sms" - - override fun viewBindingInflate(inflater: LayoutInflater?): ActivityMainBinding { - return ActivityMainBinding.inflate(inflater!!) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - initViews() - initData() - initListeners() - - //不在最近任务列表中显示 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && SettingUtils.enableExcludeFromRecents) { - val am = App.context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager - am.let { - val tasks = it.appTasks - if (!tasks.isNullOrEmpty()) { - tasks[0].setExcludeFromRecents(true) - } - } - } - } - - override val isSupportSlideBack: Boolean - get() = false - - private fun initViews() { - WidgetUtils.clearActivityBackground(this) - mTitles = ResUtils.getStringArray(R.array.home_titles) - binding!!.includeMain.toolbar.title = mTitles[0] - binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_logs) - binding!!.includeMain.toolbar.setOnMenuItemClickListener(this) - - //主页内容填充 - val fragments = arrayOf( - LogsFragment(), - RulesFragment(), - SendersFragment(), - SettingsFragment() - ) - val adapter = FragmentAdapter(supportFragmentManager, fragments) - binding!!.includeMain.viewPager.offscreenPageLimit = mTitles.size - 1 - binding!!.includeMain.viewPager.adapter = adapter - - if (!SettingUtils.enableHelpTip) { - val headerView = binding!!.navView.getHeaderView(0) - val tvSlogan = headerView.findViewById(R.id.tv_slogan) - tvSlogan.visibility = View.GONE - } - } - - private fun initData() { - //仅当有WIFI网络时自动检查更新/获取提示 - if (NetworkUtils.isWifi() && NetworkUtils.isHaveInternet()) { - showTips(this) - XUpdateInit.checkUpdate(this, false) - } - } - - fun initListeners() { - val toggle = ActionBarDrawerToggle( - this, - binding!!.drawerLayout, - binding!!.includeMain.toolbar, - R.string.navigation_drawer_open, - R.string.navigation_drawer_close - ) - binding!!.drawerLayout.addDrawerListener(toggle) - toggle.syncState() - - //侧边栏点击事件 - binding!!.navView.setNavigationItemSelectedListener { menuItem: MenuItem -> - if (menuItem.isCheckable) { - binding!!.drawerLayout.closeDrawers() - return@setNavigationItemSelectedListener handleNavigationItemSelected(menuItem) - } else { - when (menuItem.itemId) { - R.id.nav_server -> openNewPage(ServerFragment::class.java) - R.id.nav_client -> openNewPage(ClientFragment::class.java) - R.id.nav_frpc -> { - if (!FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so")) { - MaterialDialog.Builder(this) - .title( - String.format( - getString(R.string.frpclib_download_title), - FRPC_LIB_VERSION - ) - ) - .content(R.string.download_frpc_tips) - .positiveText(R.string.lab_yes) - .negativeText(R.string.lab_no) - .onPositive { _: MaterialDialog?, _: DialogAction? -> - downloadFrpcLib() - } - .show() - return@setNavigationItemSelectedListener false - } - - if (FRPC_LIB_VERSION == Frpclib.getVersion()) { - openNewPage(FrpcFragment::class.java) - } else { - MaterialDialog.Builder(this) - .title(R.string.frpclib_version_mismatch) - .content(R.string.download_frpc_tips) - .positiveText(R.string.lab_yes) - .negativeText(R.string.lab_no) - .onPositive { _: MaterialDialog?, _: DialogAction? -> - downloadFrpcLib() - } - .show() - } - } - R.id.nav_app_list -> openNewPage(AppListFragment::class.java) - R.id.nav_logcat -> openNewPage(LogcatFragment::class.java) - R.id.nav_help -> AgentWebActivity.goWeb(this, getString(R.string.url_help)) - R.id.nav_about -> openNewPage(AboutFragment::class.java) - else -> XToastUtils.toast("Click:" + menuItem.title) - } - } - true - } - - //主页事件监听 - binding!!.includeMain.viewPager.addOnPageChangeListener(object : - ViewPager.OnPageChangeListener { - override fun onPageScrolled( - position: Int, - positionOffset: Float, - positionOffsetPixels: Int, - ) { - } - - override fun onPageSelected(position: Int) { - val item = binding!!.includeMain.bottomNavigation.menu.getItem(position) - binding!!.includeMain.toolbar.title = item.title - binding!!.includeMain.toolbar.menu.clear() - when (item.title) { - getString(R.string.menu_rules) -> binding!!.includeMain.toolbar.inflateMenu( - R.menu.menu_rules - ) - getString(R.string.menu_senders) -> binding!!.includeMain.toolbar.inflateMenu( - R.menu.menu_senders - ) - getString(R.string.menu_settings) -> binding!!.includeMain.toolbar.inflateMenu( - R.menu.menu_settings - ) - else -> binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_logs) - } - item.isChecked = true - updateSideNavStatus(item) - } - - override fun onPageScrollStateChanged(state: Int) {} - }) - binding!!.includeMain.bottomNavigation.setOnNavigationItemSelectedListener(this) - - //tabBar分类切换 - LiveEventBus.get(EVENT_UPDATE_LOGS_TYPE, String::class.java).observe(this) { type: String -> - logsType = type - } - LiveEventBus.get(EVENT_UPDATE_RULE_TYPE, String::class.java).observe(this) { type: String -> - ruleType = type - } - - //更新通知栏文案 - LiveEventBus.get(EVENT_UPDATE_NOTIFY, String::class.java).observe(this) { notify: String -> - cactusUpdateNotification { - setContent(notify) - } - } - } - - /** - * 处理侧边栏点击事件 - * - * @param menuItem - * @return - */ - private fun handleNavigationItemSelected(menuItem: MenuItem): Boolean { - for (index in mTitles.indices) { - if (mTitles[index] == menuItem.title) { - binding!!.includeMain.toolbar.title = menuItem.title - binding!!.includeMain.viewPager.setCurrentItem(index, false) - return true - } - } - return false - } - - @SuppressLint("InflateParams") - override fun onMenuItemClick(item: MenuItem): Boolean { - when (item.itemId) { - R.id.action_notifications -> { - showTipsForce(this) - } - R.id.action_clear_logs -> { - MaterialDialog.Builder(this) - .content(R.string.delete_type_log_tips) - .positiveText(R.string.lab_yes) - .negativeText(R.string.lab_no) - .onPositive { _: MaterialDialog?, _: DialogAction? -> - AppDatabase.getInstance(this) - .logsDao() - .deleteAll(logsType) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(object : CompletableObserver { - override fun onSubscribe(d: Disposable) {} - override fun onComplete() { - XToastUtils.success(R.string.delete_type_log_toast) - } - - override fun onError(e: Throwable) { - e.message?.let { XToastUtils.error(it) } - } - }) - } - .show() - } - R.id.action_add_sender -> { - val dialog = BottomSheetDialog(this) - val view: View = - LayoutInflater.from(this).inflate(R.layout.dialog_sender_bottom_sheet, null) - val recyclerView: RecyclerView = view.findViewById(R.id.recyclerView) - - WidgetUtils.initGridRecyclerView(recyclerView, 4, DensityUtils.dp2px(1f)) - val widgetItemAdapter = WidgetItemAdapter(SENDER_FRAGMENT_LIST) - widgetItemAdapter.setOnItemClickListener(this) - recyclerView.adapter = widgetItemAdapter - - dialog.setContentView(view) - dialog.setCancelable(true) - dialog.setCanceledOnTouchOutside(true) - dialog.show() - WidgetUtils.transparentBottomSheetDialogBackground(dialog) - } - R.id.action_add_rule -> { - PageOption.to(RulesEditFragment::class.java) - .putString(KEY_RULE_TYPE, ruleType) - .setNewActivity(true) - .open(this) - } - /*R.id.action_restore_settings -> { - XToastUtils.success(logsType) - }*/ - } - return false - } - - @SingleClick - override fun onClick(v: View) { - } - - //================Navigation================// - /** - * 底部导航栏点击事件 - * - * @param menuItem - * @return - */ - override fun onNavigationItemSelected(menuItem: MenuItem): Boolean { - for (index in mTitles.indices) { - if (mTitles[index] == menuItem.title) { - binding!!.includeMain.toolbar.title = menuItem.title - binding!!.includeMain.viewPager.setCurrentItem(index, false) - updateSideNavStatus(menuItem) - return true - } - } - return false - } - - /** - * 更新侧边栏菜单选中状态 - * - * @param menuItem - */ - private fun updateSideNavStatus(menuItem: MenuItem) { - val side = binding!!.navView.menu.findItem(menuItem.itemId) - if (side != null) { - side.isChecked = true - } - } - - //按返回键不退出回到桌面 - @Deprecated("Deprecated in Java") - override fun onBackPressed() { - val intent = Intent(Intent.ACTION_MAIN) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - intent.addCategory(Intent.CATEGORY_HOME) - startActivity(intent) - } - - @SingleClick - override fun onItemClick(itemView: View, widgetInfo: PageInfo, pos: Int) { - try { - @Suppress("UNCHECKED_CAST") - PageOption.to(Class.forName(widgetInfo.classPath) as Class) //跳转的fragment - .setNewActivity(true) - .putInt(KEY_SENDER_TYPE, pos) //注意:目前刚好是这个顺序而已 - .open(this) - } catch (e: Exception) { - e.printStackTrace() - XToastUtils.error(e.message.toString()) - } - } - - //动态加载FrpcLib - private fun downloadFrpcLib() { - val cpuAbi = when (Build.CPU_ABI) { - "x86" -> "x86" - "x86_64" -> "x86_64" - "arm64-v8a" -> "arm64-v8a" - else -> "armeabi-v7a" - } - - val libPath = filesDir.absolutePath + "/libs" - val soFile = File(libPath) - if (!soFile.exists()) soFile.mkdirs() - val downloadUrl = String.format(FRPC_LIB_DOWNLOAD_URL, FRPC_LIB_VERSION, cpuAbi) - val mContext = this - val dialog: MaterialDialog = MaterialDialog.Builder(mContext) - .title(String.format(getString(R.string.frpclib_download_title), FRPC_LIB_VERSION)) - .content(getString(R.string.frpclib_download_content)) - .contentGravity(GravityEnum.CENTER) - .progress(false, 0, true) - .progressNumberFormat("%2dMB/%1dMB") - .build() - - XHttp.downLoad(downloadUrl) - .savePath(cacheDir.absolutePath) - .execute(object : DownloadProgressCallBack() { - override fun onStart() { - dialog.show() - } - - override fun onError(e: ApiException) { - dialog.dismiss() - XToastUtils.error(e.message.toString()) - } - - override fun update(bytesRead: Long, contentLength: Long, done: Boolean) { - Log.d(TAG, "onProgress: bytesRead=$bytesRead, contentLength=$contentLength") - dialog.maxProgress = (contentLength / 1048576L).toInt() - dialog.setProgress((bytesRead / 1048576L).toInt()) - } - - override fun onComplete(srcPath: String) { - dialog.dismiss() - Log.d(TAG, "srcPath = $srcPath") - - val srcFile = File(srcPath) - val destFile = File("$libPath/libgojni.so") - FileUtils.moveFile(srcFile, destFile, null) - - val intent: Intent? = packageManager.getLaunchIntentForPackage(packageName) - intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - startActivity(intent) - android.os.Process.killProcess(android.os.Process.myPid()) //杀掉以前进程 - } - }) - - } - +package com.idormy.sms.forwarder.activity + +import android.annotation.SuppressLint +import android.app.ActivityManager +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.widget.TextView +import androidx.appcompat.app.ActionBarDrawerToggle +import androidx.appcompat.widget.Toolbar +import androidx.recyclerview.widget.RecyclerView +import androidx.viewpager.widget.ViewPager +import com.google.android.material.bottomnavigation.BottomNavigationView +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.gyf.cactus.ext.cactusUpdateNotification +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.WidgetItemAdapter +import com.idormy.sms.forwarder.core.BaseActivity +import com.idormy.sms.forwarder.core.webview.AgentWebActivity +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.databinding.ActivityMainBinding +import com.idormy.sms.forwarder.fragment.* +import com.idormy.sms.forwarder.utils.* +import com.idormy.sms.forwarder.utils.sdkinit.XUpdateInit +import com.idormy.sms.forwarder.widget.GuideTipsDialog.Companion.showTips +import com.idormy.sms.forwarder.widget.GuideTipsDialog.Companion.showTipsForce +import com.jeremyliao.liveeventbus.LiveEventBus +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.callback.DownloadProgressCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xpage.base.XPageFragment +import com.xuexiang.xpage.core.PageOption +import com.xuexiang.xpage.model.PageInfo +import com.xuexiang.xui.adapter.FragmentAdapter +import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder +import com.xuexiang.xui.utils.DensityUtils +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.utils.WidgetUtils +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.GravityEnum +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import com.xuexiang.xutil.file.FileUtils +import com.xuexiang.xutil.net.NetworkUtils +import frpclib.Frpclib +import io.reactivex.CompletableObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.io.File + + +@Suppress("DEPRECATION", "PrivatePropertyName") +class MainActivity : BaseActivity(), + View.OnClickListener, + BottomNavigationView.OnNavigationItemSelectedListener, + Toolbar.OnMenuItemClickListener, + RecyclerViewHolder.OnItemClickListener { + + private val TAG: String = MainActivity::class.java.simpleName + private lateinit var mTitles: Array + private var logsType: String = "sms" + private var ruleType: String = "sms" + + override fun viewBindingInflate(inflater: LayoutInflater?): ActivityMainBinding { + return ActivityMainBinding.inflate(inflater!!) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initViews() + initData() + initListeners() + + //不在最近任务列表中显示 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && SettingUtils.enableExcludeFromRecents) { + val am = App.context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + am.let { + val tasks = it.appTasks + if (!tasks.isNullOrEmpty()) { + tasks[0].setExcludeFromRecents(true) + } + } + } + } + + override val isSupportSlideBack: Boolean + get() = false + + private fun initViews() { + WidgetUtils.clearActivityBackground(this) + mTitles = ResUtils.getStringArray(R.array.home_titles) + binding!!.includeMain.toolbar.title = mTitles[0] + binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_logs) + binding!!.includeMain.toolbar.setOnMenuItemClickListener(this) + + //主页内容填充 + val fragments = arrayOf( + LogsFragment(), + RulesFragment(), + SendersFragment(), + SettingsFragment() + ) + val adapter = FragmentAdapter(supportFragmentManager, fragments) + binding!!.includeMain.viewPager.offscreenPageLimit = mTitles.size - 1 + binding!!.includeMain.viewPager.adapter = adapter + + if (!SettingUtils.enableHelpTip) { + val headerView = binding!!.navView.getHeaderView(0) + val tvSlogan = headerView.findViewById(R.id.tv_slogan) + tvSlogan.visibility = View.GONE + } + } + + private fun initData() { + //仅当有WIFI网络时自动检查更新/获取提示 + if (NetworkUtils.isWifi() && NetworkUtils.isHaveInternet()) { + showTips(this) + XUpdateInit.checkUpdate(this, false) + } + } + + fun initListeners() { + val toggle = ActionBarDrawerToggle( + this, + binding!!.drawerLayout, + binding!!.includeMain.toolbar, + R.string.navigation_drawer_open, + R.string.navigation_drawer_close + ) + binding!!.drawerLayout.addDrawerListener(toggle) + toggle.syncState() + + //侧边栏点击事件 + binding!!.navView.setNavigationItemSelectedListener { menuItem: MenuItem -> + if (menuItem.isCheckable) { + binding!!.drawerLayout.closeDrawers() + return@setNavigationItemSelectedListener handleNavigationItemSelected(menuItem) + } else { + when (menuItem.itemId) { + R.id.nav_server -> openNewPage(ServerFragment::class.java) + R.id.nav_client -> openNewPage(ClientFragment::class.java) + R.id.nav_frpc -> { + if (!FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so")) { + MaterialDialog.Builder(this) + .title( + String.format( + getString(R.string.frpclib_download_title), + FRPC_LIB_VERSION + ) + ) + .content(R.string.download_frpc_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + downloadFrpcLib() + } + .show() + return@setNavigationItemSelectedListener false + } + + if (FRPC_LIB_VERSION == Frpclib.getVersion()) { + openNewPage(FrpcFragment::class.java) + } else { + MaterialDialog.Builder(this) + .title(R.string.frpclib_version_mismatch) + .content(R.string.download_frpc_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + downloadFrpcLib() + } + .show() + } + } + R.id.nav_app_list -> openNewPage(AppListFragment::class.java) + R.id.nav_logcat -> openNewPage(LogcatFragment::class.java) + R.id.nav_help -> AgentWebActivity.goWeb(this, getString(R.string.url_help)) + R.id.nav_about -> openNewPage(AboutFragment::class.java) + else -> XToastUtils.toast("Click:" + menuItem.title) + } + } + true + } + + //主页事件监听 + binding!!.includeMain.viewPager.addOnPageChangeListener(object : + ViewPager.OnPageChangeListener { + override fun onPageScrolled( + position: Int, + positionOffset: Float, + positionOffsetPixels: Int, + ) { + } + + override fun onPageSelected(position: Int) { + val item = binding!!.includeMain.bottomNavigation.menu.getItem(position) + binding!!.includeMain.toolbar.title = item.title + binding!!.includeMain.toolbar.menu.clear() + when (item.title) { + getString(R.string.menu_rules) -> binding!!.includeMain.toolbar.inflateMenu( + R.menu.menu_rules + ) + getString(R.string.menu_senders) -> binding!!.includeMain.toolbar.inflateMenu( + R.menu.menu_senders + ) + getString(R.string.menu_settings) -> binding!!.includeMain.toolbar.inflateMenu( + R.menu.menu_settings + ) + else -> binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_logs) + } + item.isChecked = true + updateSideNavStatus(item) + } + + override fun onPageScrollStateChanged(state: Int) {} + }) + binding!!.includeMain.bottomNavigation.setOnNavigationItemSelectedListener(this) + + //tabBar分类切换 + LiveEventBus.get(EVENT_UPDATE_LOGS_TYPE, String::class.java).observe(this) { type: String -> + logsType = type + } + LiveEventBus.get(EVENT_UPDATE_RULE_TYPE, String::class.java).observe(this) { type: String -> + ruleType = type + } + + //更新通知栏文案 + LiveEventBus.get(EVENT_UPDATE_NOTIFY, String::class.java).observe(this) { notify: String -> + cactusUpdateNotification { + setContent(notify) + } + } + } + + /** + * 处理侧边栏点击事件 + * + * @param menuItem + * @return + */ + private fun handleNavigationItemSelected(menuItem: MenuItem): Boolean { + for (index in mTitles.indices) { + if (mTitles[index] == menuItem.title) { + binding!!.includeMain.toolbar.title = menuItem.title + binding!!.includeMain.viewPager.setCurrentItem(index, false) + return true + } + } + return false + } + + @SuppressLint("InflateParams") + override fun onMenuItemClick(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_notifications -> { + showTipsForce(this) + } + R.id.action_clear_logs -> { + MaterialDialog.Builder(this) + .content(R.string.delete_type_log_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + AppDatabase.getInstance(this) + .msgDao() + .deleteAll(logsType) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : CompletableObserver { + override fun onSubscribe(d: Disposable) {} + override fun onComplete() { + XToastUtils.success(R.string.delete_type_log_toast) + } + + override fun onError(e: Throwable) { + e.message?.let { XToastUtils.error(it) } + } + }) + } + .show() + } + R.id.action_add_sender -> { + val dialog = BottomSheetDialog(this) + val view: View = + LayoutInflater.from(this).inflate(R.layout.dialog_sender_bottom_sheet, null) + val recyclerView: RecyclerView = view.findViewById(R.id.recyclerView) + + WidgetUtils.initGridRecyclerView(recyclerView, 4, DensityUtils.dp2px(1f)) + val widgetItemAdapter = WidgetItemAdapter(SENDER_FRAGMENT_LIST) + widgetItemAdapter.setOnItemClickListener(this) + recyclerView.adapter = widgetItemAdapter + + dialog.setContentView(view) + dialog.setCancelable(true) + dialog.setCanceledOnTouchOutside(true) + dialog.show() + WidgetUtils.transparentBottomSheetDialogBackground(dialog) + } + R.id.action_add_rule -> { + PageOption.to(RulesEditFragment::class.java) + .putString(KEY_RULE_TYPE, ruleType) + .setNewActivity(true) + .open(this) + } + /*R.id.action_restore_settings -> { + XToastUtils.success(logsType) + }*/ + } + return false + } + + @SingleClick + override fun onClick(v: View) { + } + + //================Navigation================// + /** + * 底部导航栏点击事件 + * + * @param menuItem + * @return + */ + override fun onNavigationItemSelected(menuItem: MenuItem): Boolean { + for (index in mTitles.indices) { + if (mTitles[index] == menuItem.title) { + binding!!.includeMain.toolbar.title = menuItem.title + binding!!.includeMain.viewPager.setCurrentItem(index, false) + updateSideNavStatus(menuItem) + return true + } + } + return false + } + + /** + * 更新侧边栏菜单选中状态 + * + * @param menuItem + */ + private fun updateSideNavStatus(menuItem: MenuItem) { + val side = binding!!.navView.menu.findItem(menuItem.itemId) + if (side != null) { + side.isChecked = true + } + } + + //按返回键不退出回到桌面 + @Deprecated("Deprecated in Java") + override fun onBackPressed() { + val intent = Intent(Intent.ACTION_MAIN) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + intent.addCategory(Intent.CATEGORY_HOME) + startActivity(intent) + } + + @SingleClick + override fun onItemClick(itemView: View, widgetInfo: PageInfo, pos: Int) { + try { + @Suppress("UNCHECKED_CAST") + PageOption.to(Class.forName(widgetInfo.classPath) as Class) //跳转的fragment + .setNewActivity(true) + .putInt(KEY_SENDER_TYPE, pos) //注意:目前刚好是这个顺序而已 + .open(this) + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(e.message.toString()) + } + } + + //动态加载FrpcLib + private fun downloadFrpcLib() { + val cpuAbi = when (Build.CPU_ABI) { + "x86" -> "x86" + "x86_64" -> "x86_64" + "arm64-v8a" -> "arm64-v8a" + else -> "armeabi-v7a" + } + + val libPath = filesDir.absolutePath + "/libs" + val soFile = File(libPath) + if (!soFile.exists()) soFile.mkdirs() + val downloadUrl = String.format(FRPC_LIB_DOWNLOAD_URL, FRPC_LIB_VERSION, cpuAbi) + val mContext = this + val dialog: MaterialDialog = MaterialDialog.Builder(mContext) + .title(String.format(getString(R.string.frpclib_download_title), FRPC_LIB_VERSION)) + .content(getString(R.string.frpclib_download_content)) + .contentGravity(GravityEnum.CENTER) + .progress(false, 0, true) + .progressNumberFormat("%2dMB/%1dMB") + .build() + + XHttp.downLoad(downloadUrl) + .savePath(cacheDir.absolutePath) + .execute(object : DownloadProgressCallBack() { + override fun onStart() { + dialog.show() + } + + override fun onError(e: ApiException) { + dialog.dismiss() + XToastUtils.error(e.message.toString()) + } + + override fun update(bytesRead: Long, contentLength: Long, done: Boolean) { + Log.d(TAG, "onProgress: bytesRead=$bytesRead, contentLength=$contentLength") + dialog.maxProgress = (contentLength / 1048576L).toInt() + dialog.setProgress((bytesRead / 1048576L).toInt()) + } + + override fun onComplete(srcPath: String) { + dialog.dismiss() + Log.d(TAG, "srcPath = $srcPath") + + val srcFile = File(srcPath) + val destFile = File("$libPath/libgojni.so") + FileUtils.moveFile(srcFile, destFile, null) + + val intent: Intent? = packageManager.getLaunchIntentForPackage(packageName) + intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + startActivity(intent) + android.os.Process.killProcess(android.os.Process.myPid()) //杀掉以前进程 + } + }) + + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/LogsPagingAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/LogsPagingAdapter.kt index 1eadb6ac..e8c1c78a 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/adapter/LogsPagingAdapter.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/LogsPagingAdapter.kt @@ -1,57 +1,56 @@ -package com.idormy.sms.forwarder.adapter - -import android.annotation.SuppressLint -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.paging.PagingDataAdapter -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import com.idormy.sms.forwarder.adapter.LogsPagingAdapter.MyViewHolder -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.xuexiang.xutil.data.DateUtils - -class LogsPagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter(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.logs.from - holder.binding.tvTime.text = DateUtils.getFriendlyTimeSpanByNow(item.logs.time) - holder.binding.tvContent.text = item.logs.content - holder.binding.ivSenderImage.setImageResource(Sender.getImageId(item.relation.sender.type)) - holder.binding.ivStatusImage.setImageResource(item.logs.statusImageId) - holder.binding.ivSimImage.setImageResource(item.logs.simImageId) - - 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: LogsAndRuleAndSender) - fun onItemRemove(view: View?, id: Int) - } - - companion object { - var diffCallback: DiffUtil.ItemCallback = object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: LogsAndRuleAndSender, newItem: LogsAndRuleAndSender): Boolean { - return oldItem.logs.id == newItem.logs.id - } - - @SuppressLint("DiffUtilEquals") - override fun areContentsTheSame(oldItem: LogsAndRuleAndSender, newItem: LogsAndRuleAndSender): Boolean { - return oldItem.logs === newItem.logs - } - } - } +package com.idormy.sms.forwarder.adapter + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.idormy.sms.forwarder.adapter.LogsPagingAdapter.MyViewHolder +import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender +import com.idormy.sms.forwarder.databinding.AdapterLogsCardViewListItemBinding +import com.xuexiang.xutil.data.DateUtils + +class LogsPagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter(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.logs.time) + holder.binding.tvContent.text = item.msg.content + //holder.binding.ivSenderImage.setImageResource(Sender.getImageId(item.sender.type)) + //holder.binding.ivStatusImage.setImageResource(item.logs.statusImageId) + holder.binding.ivSimImage.setImageResource(item.msg.simImageId) + + 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: LogsAndRuleAndSender) + fun onItemRemove(view: View?, id: Int) + } + + companion object { + var diffCallback: DiffUtil.ItemCallback = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: LogsAndRuleAndSender, newItem: LogsAndRuleAndSender): Boolean { + return oldItem.logs.id == newItem.logs.id + } + + @SuppressLint("DiffUtilEquals") + override fun areContentsTheSame(oldItem: LogsAndRuleAndSender, newItem: LogsAndRuleAndSender): Boolean { + return oldItem.logs === newItem.logs + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/MsgPagingAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/MsgPagingAdapter.kt new file mode 100644 index 00000000..c41413bb --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/MsgPagingAdapter.kt @@ -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(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(R.id.iv_sender_image) + val ivSenderStatus = layoutSenderItem.findViewById(R.id.iv_sender_status) + val tvSenderName = layoutSenderItem.findViewById(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 = object : DiffUtil.ItemCallback() { + 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 + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/RulePagingAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/RulePagingAdapter.kt index f4ae8c74..75d18d3a 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/adapter/RulePagingAdapter.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/RulePagingAdapter.kt @@ -1,68 +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 androidx.paging.PagingDataAdapter -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import com.idormy.sms.forwarder.R -import com.idormy.sms.forwarder.adapter.RulePagingAdapter.MyViewHolder -import com.idormy.sms.forwarder.database.entity.RuleAndSender -import com.idormy.sms.forwarder.databinding.AdapterRulesCardViewListItemBinding - -class RulePagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter(diffCallback) { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { - val binding = AdapterRulesCardViewListItemBinding.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.ivRuleImage.setImageResource(item.rule.imageId) - holder.binding.ivRuleStatus.setImageResource(item.rule.statusImageId) - holder.binding.tvRuleMatch.text = item.rule.ruleMatch - holder.binding.ivSenderImage.setImageResource(item.sender.imageId) - holder.binding.ivSenderStatus.setImageResource(item.sender.statusImageId) - holder.binding.tvSenderName.text = item.sender.name - - /*holder.binding.cardView.setOnClickListener { view: View? -> - itemClickListener.onItemClicked(view, item) - }*/ - holder.binding.ivCopy.setImageResource(R.drawable.ic_copy) - holder.binding.ivEdit.setImageResource(R.drawable.ic_edit) - holder.binding.ivDelete.setImageResource(R.drawable.ic_delete) - holder.binding.ivCopy.setOnClickListener { view: View? -> - itemClickListener.onItemClicked(view, item) - } - holder.binding.ivEdit.setOnClickListener { view: View? -> - itemClickListener.onItemClicked(view, item) - } - holder.binding.ivDelete.setOnClickListener { view: View? -> - itemClickListener.onItemClicked(view, item) - } - } - } - - class MyViewHolder(val binding: AdapterRulesCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root) - interface OnItemClickListener { - fun onItemClicked(view: View?, item: RuleAndSender) - fun onItemRemove(view: View?, id: Int) - } - - companion object { - var diffCallback: DiffUtil.ItemCallback = object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: RuleAndSender, newItem: RuleAndSender): Boolean { - return oldItem.rule.id == newItem.rule.id - } - - @SuppressLint("DiffUtilEquals") - override fun areContentsTheSame(oldItem: RuleAndSender, newItem: RuleAndSender): Boolean { - return oldItem.rule === newItem.rule - } - } - } +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.RulePagingAdapter.MyViewHolder +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.databinding.AdapterRulesCardViewListItemBinding + +class RulePagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter(diffCallback) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val binding = AdapterRulesCardViewListItemBinding.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.ivRuleImage.setImageResource(item.imageId) + holder.binding.ivRuleStatus.setImageResource(item.statusImageId) + holder.binding.tvRuleMatch.text = item.ruleMatch + + holder.binding.layoutSenders.removeAllViews() + for (sender in item.senderList) { + val layoutSenderItem = View.inflate(App.context, R.layout.item_sender, null) as LinearLayout + val ivSenderImage = layoutSenderItem.findViewById(R.id.iv_sender_image) + val ivSenderStatus = layoutSenderItem.findViewById(R.id.iv_sender_status) + val tvSenderName = layoutSenderItem.findViewById(R.id.tv_sender_name) + ivSenderImage.setImageResource(sender.imageId) + ivSenderStatus.setImageResource(sender.statusImageId) + tvSenderName.text = sender.name + holder.binding.layoutSenders.addView(layoutSenderItem) + } + + holder.binding.ivCopy.setImageResource(R.drawable.ic_copy) + holder.binding.ivEdit.setImageResource(R.drawable.ic_edit) + holder.binding.ivDelete.setImageResource(R.drawable.ic_delete) + holder.binding.ivCopy.setOnClickListener { view: View? -> + itemClickListener.onItemClicked(view, item) + } + holder.binding.ivEdit.setOnClickListener { view: View? -> + itemClickListener.onItemClicked(view, item) + } + holder.binding.ivDelete.setOnClickListener { view: View? -> + itemClickListener.onItemClicked(view, item) + } + } + } + + class MyViewHolder(val binding: AdapterRulesCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root) + interface OnItemClickListener { + fun onItemClicked(view: View?, item: Rule) + fun onItemRemove(view: View?, id: Int) + } + + companion object { + var diffCallback: DiffUtil.ItemCallback = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Rule, newItem: Rule): Boolean { + return oldItem.id == newItem.id + } + + @SuppressLint("DiffUtilEquals") + override fun areContentsTheSame(oldItem: Rule, newItem: Rule): Boolean { + return oldItem === newItem + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/Core.kt b/app/src/main/java/com/idormy/sms/forwarder/core/Core.kt index ff57f754..244717c4 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/core/Core.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/core/Core.kt @@ -1,60 +1,58 @@ -package com.idormy.sms.forwarder.core - -import android.app.Application -import android.content.Intent -import android.util.Log -import androidx.core.content.ContextCompat -import androidx.work.Configuration -import com.idormy.sms.forwarder.App -import com.idormy.sms.forwarder.BuildConfig -import com.idormy.sms.forwarder.database.repository.FrpcRepository -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 kotlinx.coroutines.launch - -object Core : Configuration.Provider { - lateinit var app: Application - val frpc: FrpcRepository by lazy { (app as App).frpcRepository } - val logs: LogsRepository by lazy { (app as App).logsRepository } - val rule: RuleRepository by lazy { (app as App).ruleRepository } - val sender: SenderRepository by lazy { (app as App).senderRepository } - /* - val telephonyManager: TelephonyManager by lazy { app.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager } - val smsManager: SmsManager by lazy { app.getSystemService(SmsManager::class.java) } - val subscriptionManager: SubscriptionManager by lazy { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - SubscriptionManager.from(app) - } else { - app.getSystemService(SubscriptionManager::class.java) - } - } - val user by lazy { app.getSystemService()!! }*/ - - - /*val directBootAware: Boolean get() = directBootSupported && dataStore.canToggleLocked - val directBootSupported by lazy { - Build.VERSION.SDK_INT >= 24 && try { - app.getSystemService()?.storageEncryptionStatus == - DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER - } catch (_: RuntimeException) { - false - } - }*/ - - fun init(app: Application) { - this.app = app - } - - override fun getWorkManagerConfiguration(): Configuration { - return Configuration.Builder().apply { - setDefaultProcessName(app.packageName + ":bg") - setMinimumLoggingLevel(if (BuildConfig.DEBUG) Log.VERBOSE else Log.INFO) - setExecutor { (app as App).applicationScope.launch { it.run() } } - setTaskExecutor { (app as App).applicationScope.launch { it.run() } } - }.build() - } - - fun startService() = ContextCompat.startForegroundService(app, Intent(app, ForegroundService::class.java)) -} +package com.idormy.sms.forwarder.core + +import android.app.Application +import android.content.Intent +import android.util.Log +import androidx.core.content.ContextCompat +import androidx.work.Configuration +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.BuildConfig +import com.idormy.sms.forwarder.database.repository.* +import com.idormy.sms.forwarder.service.ForegroundService +import kotlinx.coroutines.launch + +object Core : Configuration.Provider { + lateinit var app: Application + 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 rule: RuleRepository by lazy { (app as App).ruleRepository } + val sender: SenderRepository by lazy { (app as App).senderRepository } + /* + val telephonyManager: TelephonyManager by lazy { app.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager } + val smsManager: SmsManager by lazy { app.getSystemService(SmsManager::class.java) } + val subscriptionManager: SubscriptionManager by lazy { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + SubscriptionManager.from(app) + } else { + app.getSystemService(SubscriptionManager::class.java) + } + } + val user by lazy { app.getSystemService()!! }*/ + + + /*val directBootAware: Boolean get() = directBootSupported && dataStore.canToggleLocked + val directBootSupported by lazy { + Build.VERSION.SDK_INT >= 24 && try { + app.getSystemService()?.storageEncryptionStatus == + DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER + } catch (_: RuntimeException) { + false + } + }*/ + + fun init(app: Application) { + this.app = app + } + + override fun getWorkManagerConfiguration(): Configuration { + return Configuration.Builder().apply { + setDefaultProcessName(app.packageName + ":bg") + setMinimumLoggingLevel(if (BuildConfig.DEBUG) Log.VERBOSE else Log.INFO) + setExecutor { (app as App).applicationScope.launch { it.run() } } + setTaskExecutor { (app as App).applicationScope.launch { it.run() } } + }.build() + } + + fun startService() = ContextCompat.startForegroundService(app, Intent(app, ForegroundService::class.java)) +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/AppDatabase.kt b/app/src/main/java/com/idormy/sms/forwarder/database/AppDatabase.kt index def5a17b..4e9e4506 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/AppDatabase.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/AppDatabase.kt @@ -7,26 +7,22 @@ import androidx.room.RoomDatabase import androidx.room.TypeConverters import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import com.idormy.sms.forwarder.database.dao.FrpcDao -import com.idormy.sms.forwarder.database.dao.LogsDao -import com.idormy.sms.forwarder.database.dao.RuleDao -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.database.dao.* +import com.idormy.sms.forwarder.database.entity.* +import com.idormy.sms.forwarder.database.ext.ConvertersDate import com.idormy.sms.forwarder.utils.DATABASE_NAME @Database( - entities = [Frpc::class, Logs::class, Rule::class, Sender::class], - version = 11, + entities = [Frpc::class, Msg::class, Logs::class, Rule::class, Sender::class], + views = [LogsDetail::class], + version = 15, exportSchema = false ) -@TypeConverters(Converters::class) +@TypeConverters(ConvertersDate::class) abstract class AppDatabase : RoomDatabase() { abstract fun frpcDao(): FrpcDao + abstract fun msgDao(): MsgDao abstract fun logsDao(): LogsDao abstract fun ruleDao(): RuleDao abstract fun senderDao(): SenderDao @@ -96,6 +92,10 @@ custom_domains = smsf.demo.com MIGRATION_8_9, MIGRATION_9_10, MIGRATION_10_11, + MIGRATION_11_12, + MIGRATION_12_13, + MIGRATION_13_14, + MIGRATION_14_15, ) /*if (BuildConfig.DEBUG) { @@ -281,9 +281,89 @@ CREATE TABLE "Sender" ( //转发日志添加SIM卡槽ID private val MIGRATION_10_11 = object : Migration(10, 11) { 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") + } + } + } } diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/dao/MsgDao.kt b/app/src/main/java/com/idormy/sms/forwarder/database/dao/MsgDao.kt new file mode 100644 index 00000000..cdd1d39e --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/dao/MsgDao.kt @@ -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 + + @Query("SELECT count(*) FROM Msg where type=:type") + fun count(type: String): Single + + @Transaction + @Query("SELECT * FROM Msg WHERE type = :type ORDER BY id DESC") + fun pagingSource(type: String): PagingSource + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/dao/RuleDao.kt b/app/src/main/java/com/idormy/sms/forwarder/database/dao/RuleDao.kt index 14614364..dec2b6ae 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/dao/RuleDao.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/dao/RuleDao.kt @@ -25,6 +25,9 @@ interface RuleDao { @Query("SELECT * FROM Rule where id=:id") fun get(id: Long): Single + @Query("SELECT * FROM Rule where id=:id") + fun getOne(id: Long): Rule + @Query("SELECT count(*) FROM Rule where type=:type and status=:status") fun count(type: String, status: Int): Single @@ -40,7 +43,7 @@ interface RuleDao { @Transaction @Query("SELECT * FROM Rule where type=:type ORDER BY id DESC") - fun pagingSource(type: String): PagingSource + fun pagingSource(type: String): PagingSource @Transaction @Query("SELECT * FROM Rule where type=:type and status=:status and (sim_slot='ALL' or sim_slot=:simSlot)") diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/dao/SenderDao.kt b/app/src/main/java/com/idormy/sms/forwarder/database/dao/SenderDao.kt index 6aa8ac27..12baf331 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/dao/SenderDao.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/dao/SenderDao.kt @@ -25,6 +25,9 @@ interface SenderDao { @Query("SELECT * FROM Sender where id=:id") fun get(id: Long): Single + @Query("SELECT * FROM Sender where id=:id") + fun getOne(id: Long): Sender + @Query("SELECT count(*) FROM Sender where type=:type and status=:status") fun count(type: String, status: Int): Single diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/entity/Logs.kt b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Logs.kt index bfe94449..99bd964f 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/entity/Logs.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Logs.kt @@ -10,45 +10,47 @@ import java.util.* @Entity( tableName = "Logs", foreignKeys = [ + ForeignKey( + entity = Msg::class, + parentColumns = ["id"], + childColumns = ["msg_id"], + onDelete = ForeignKey.CASCADE, //级联操作 + onUpdate = ForeignKey.CASCADE //级联操作 + ), ForeignKey( entity = Rule::class, parentColumns = ["id"], childColumns = ["rule_id"], onDelete = ForeignKey.CASCADE, //级联操作 onUpdate = ForeignKey.CASCADE //级联操作 - ) + ), + ForeignKey( + entity = Sender::class, + parentColumns = ["id"], + childColumns = ["sender_id"], + onDelete = ForeignKey.CASCADE, //级联操作 + onUpdate = ForeignKey.CASCADE //级联操作 + ), ], indices = [ Index(value = ["id"], unique = true), - Index(value = ["rule_id"]) + Index(value = ["msg_id"]), + Index(value = ["rule_id"]), + Index(value = ["sender_id"]), ] ) data class Logs( @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 = "msg_id", defaultValue = "0") var msgId: Long = 0, @ColumnInfo(name = "rule_id", defaultValue = "0") var ruleId: Long = 0, - @ColumnInfo(name = "sim_info", defaultValue = "") var simInfo: String = "", - @ColumnInfo(name = "sub_id", defaultValue = "0") var subId: Int = 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(), ) : 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 get() { if (forwardStatus == 1) { diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/entity/LogsAndRuleAndSender.kt b/app/src/main/java/com/idormy/sms/forwarder/database/entity/LogsAndRuleAndSender.kt index 1c2c492a..f1fa8608 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/entity/LogsAndRuleAndSender.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/entity/LogsAndRuleAndSender.kt @@ -1,18 +1,32 @@ -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 LogsAndRuleAndSender( - @Embedded val logs: Logs, - - @Relation( - entity = Rule::class, - parentColumn = "rule_id", - entityColumn = "id" - ) - val relation: RuleAndSender, -) : Parcelable +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 LogsAndRuleAndSender( + @Embedded val logs: Logs, + + @Relation( + entity = Msg::class, + parentColumn = "msg_id", + entityColumn = "id" + ) + val msg: Msg, + + @Relation( + entity = Rule::class, + parentColumn = "rule_id", + entityColumn = "id" + ) + val rule: Rule, + + @Relation( + entity = Sender::class, + parentColumn = "sender_id", + entityColumn = "id" + ) + val sender: Sender, +) : Parcelable diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/entity/LogsDetail.kt b/app/src/main/java/com/idormy/sms/forwarder/database/entity/LogsDetail.kt new file mode 100644 index 00000000..5619c359 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/entity/LogsDetail.kt @@ -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 + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/entity/Msg.kt b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Msg.kt new file mode 100644 index 00000000..7074d871 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Msg.kt @@ -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 + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/entity/MsgAndLogs.kt b/app/src/main/java/com/idormy/sms/forwarder/database/entity/MsgAndLogs.kt new file mode 100644 index 00000000..b8cddfec --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/entity/MsgAndLogs.kt @@ -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 +) : Parcelable diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/entity/Rule.kt b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Rule.kt index 5fcc552a..c6ea518c 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/entity/Rule.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Rule.kt @@ -1,180 +1,193 @@ -package com.idormy.sms.forwarder.database.entity - -import android.os.Parcelable -import android.util.Log -import androidx.room.* -import com.idormy.sms.forwarder.R -import com.idormy.sms.forwarder.entity.MsgInfo -import com.idormy.sms.forwarder.utils.* -import com.xuexiang.xui.utils.ResUtils.getString -import kotlinx.parcelize.Parcelize -import java.util.* -import java.util.regex.Pattern -import java.util.regex.PatternSyntaxException - -@Parcelize -@Entity( - tableName = "Rule", - foreignKeys = [ - ForeignKey( - entity = Sender::class, - parentColumns = ["id"], - childColumns = ["sender_id"], - onDelete = ForeignKey.CASCADE, //级联操作 - onUpdate = ForeignKey.CASCADE //级联操作 - ) - ], - indices = [ - Index(value = ["id"], unique = true), - Index(value = ["sender_id"]) - ] -) -data class Rule( - @PrimaryKey(autoGenerate = true) - @ColumnInfo(name = "id") var id: Long, - @ColumnInfo(name = "type", defaultValue = "sms") var type: String, - @ColumnInfo(name = "filed", defaultValue = "transpond_all") var filed: String, - @ColumnInfo(name = "check", defaultValue = "is") var check: String, - @ColumnInfo(name = "value", defaultValue = "") var value: String, - @ColumnInfo(name = "sender_id", defaultValue = "0") var senderId: Long = 0, - @ColumnInfo(name = "sms_template", defaultValue = "") var smsTemplate: String = "", - @ColumnInfo(name = "regex_replace", defaultValue = "") var regexReplace: String = "", - @ColumnInfo(name = "sim_slot", defaultValue = "ALL") var simSlot: String = "", - @ColumnInfo(name = "status", defaultValue = "1") var status: Int = 1, - @ColumnInfo(name = "time") var time: Date = Date(), -) : Parcelable { - - companion object { - val TAG: String = Rule::class.java.simpleName - - fun getRuleMatch(filed: String?, check: String?, value: String?, simSlot: String?): Any { - val sb = StringBuilder() - sb.append(SIM_SLOT_MAP[simSlot]).append(getString(R.string.rule_card)) - if (filed == null || filed == FILED_TRANSPOND_ALL) { - sb.append(getString(R.string.rule_all_fw_to)) - } else { - sb.append(getString(R.string.rule_when)).append(FILED_MAP[filed]).append(CHECK_MAP[check]).append(value).append(getString(R.string.rule_fw_to)) - } - return sb.toString() - } - - } - - val ruleMatch: String - get() { - val simStr = if ("app" == type) "" else SIM_SLOT_MAP[simSlot].toString() + getString(R.string.rule_card) - return if (filed == FILED_TRANSPOND_ALL) { - simStr + getString(R.string.rule_all_fw_to) - } else { - simStr + getString(R.string.rule_when) + FILED_MAP[filed] + CHECK_MAP[check] + value + getString(R.string.rule_fw_to) - } - } - - val statusChecked: Boolean - get() = status != STATUS_OFF - - val imageId: Int - get() = when (simSlot) { - CHECK_SIM_SLOT_1 -> R.drawable.ic_sim1 - CHECK_SIM_SLOT_2 -> R.drawable.ic_sim2 - CHECK_SIM_SLOT_ALL -> if (type == "app") R.drawable.ic_app else R.drawable.ic_sim - else -> if (type == "app") R.drawable.ic_app else R.drawable.ic_sim - } - - val statusImageId: Int - get() = when (status) { - STATUS_OFF -> R.drawable.icon_off - else -> R.drawable.icon_on - } - - fun getSimSlotCheckId(): Int { - return when (simSlot) { - CHECK_SIM_SLOT_1 -> R.id.rb_sim_slot_1 - CHECK_SIM_SLOT_2 -> R.id.rb_sim_slot_2 - else -> R.id.rb_sim_slot_all - } - } - - fun getFiledCheckId(): Int { - return when (filed) { - FILED_MSG_CONTENT -> R.id.rb_content - FILED_PHONE_NUM -> R.id.rb_phone - FILED_PACKAGE_NAME -> R.id.rb_package_name - FILED_INFORM_CONTENT -> R.id.rb_inform_content - FILED_MULTI_MATCH -> R.id.rb_multi_match - else -> R.id.rb_transpond_all - } - } - - fun getCheckCheckId(): Int { - return when (check) { - CHECK_CONTAIN -> R.id.rb_contain - CHECK_NOT_CONTAIN -> R.id.rb_not_contain - CHECK_START_WITH -> R.id.rb_start_with - CHECK_END_WITH -> R.id.rb_end_with - CHECK_REGEX -> R.id.rb_regex - else -> R.id.rb_is - } - } - - //字段分支 - @Throws(Exception::class) - fun checkMsg(msg: MsgInfo?): Boolean { - - //检查这一行和上一行合并的结果是否命中 - var mixChecked = false - if (msg != null) { - //先检查规则是否命中 - when (this.filed) { - FILED_TRANSPOND_ALL -> mixChecked = true - FILED_PHONE_NUM, FILED_PACKAGE_NAME -> mixChecked = checkValue(msg.from) - FILED_MSG_CONTENT, FILED_INFORM_CONTENT -> mixChecked = checkValue(msg.content) - FILED_MULTI_MATCH -> mixChecked = RuleLineUtils.checkRuleLines(msg, this.value) - else -> {} - } - } - Log.i(TAG, "rule:$this checkMsg:$msg checked:$mixChecked") - return mixChecked - } - - //内容分支 - private fun checkValue(msgValue: String?): Boolean { - var checked = false - when (this.check) { - CHECK_IS -> checked = this.value == msgValue - CHECK_NOT_IS -> checked = this.value != msgValue - CHECK_CONTAIN -> if (msgValue != null) { - checked = msgValue.contains(this.value) - } - CHECK_NOT_CONTAIN -> if (msgValue != null) { - checked = !msgValue.contains(this.value) - } - CHECK_START_WITH -> if (msgValue != null) { - checked = msgValue.startsWith(this.value) - } - CHECK_END_WITH -> if (msgValue != null) { - checked = msgValue.endsWith(this.value) - } - CHECK_REGEX -> if (msgValue != null) { - try { - //checked = Pattern.matches(this.value, msgValue); - val pattern = Pattern.compile(this.value, Pattern.CASE_INSENSITIVE) - val matcher = pattern.matcher(msgValue) - while (matcher.find()) { - checked = true - break - } - } catch (e: PatternSyntaxException) { - Log.d(TAG, "PatternSyntaxException: ") - Log.d(TAG, "Description: " + e.description) - Log.d(TAG, "Index: " + e.index) - Log.d(TAG, "Message: " + e.message) - Log.d(TAG, "Pattern: " + e.pattern) - } - } - else -> {} - } - Log.i(TAG, "checkValue " + msgValue + " " + this.check + " " + this.value + " checked:" + checked) - return checked - } +package com.idormy.sms.forwarder.database.entity + +import android.os.Parcelable +import android.util.Log +import androidx.room.* +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.utils.* +import com.xuexiang.xui.utils.ResUtils.getString +import kotlinx.parcelize.Parcelize +import java.util.* +import java.util.regex.Pattern +import java.util.regex.PatternSyntaxException + +@Parcelize +@Entity( + tableName = "Rule", + foreignKeys = [ + ForeignKey( + entity = Sender::class, + parentColumns = ["id"], + childColumns = ["sender_id"], + onDelete = ForeignKey.CASCADE, //级联操作 + onUpdate = ForeignKey.CASCADE //级联操作 + ) + ], + indices = [ + Index(value = ["id"], unique = true), + Index(value = ["sender_id"]), + Index(value = ["sender_list"]) + ] +) +@TypeConverters(ConvertersSenderList::class) +data class Rule( + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = "id") var id: Long, + @ColumnInfo(name = "type", defaultValue = "sms") var type: String, + @ColumnInfo(name = "filed", defaultValue = "transpond_all") var filed: String, + @ColumnInfo(name = "check", defaultValue = "is") var check: String, + @ColumnInfo(name = "value", defaultValue = "") var value: String, + @ColumnInfo(name = "sender_id", defaultValue = "0") var senderId: Long = 0, + @ColumnInfo(name = "sms_template", defaultValue = "") var smsTemplate: String = "", + @ColumnInfo(name = "regex_replace", defaultValue = "") var regexReplace: String = "", + @ColumnInfo(name = "sim_slot", defaultValue = "ALL") var simSlot: String = "", + @ColumnInfo(name = "status", defaultValue = "1") var status: Int = 1, + @ColumnInfo(name = "time") var time: Date = Date(), + @ColumnInfo(name = "sender_list", defaultValue = "") var senderList: List, + @ColumnInfo(name = "sender_logic", defaultValue = "ALL") var senderLogic: String = "ALL", +) : Parcelable { + + companion object { + val TAG: String = Rule::class.java.simpleName + + fun getRuleMatch(filed: String?, check: String?, value: String?, simSlot: String?): Any { + val sb = StringBuilder() + sb.append(SIM_SLOT_MAP[simSlot]).append(getString(R.string.rule_card)) + if (filed == null || filed == FILED_TRANSPOND_ALL) { + sb.append(getString(R.string.rule_all_fw_to)) + } else { + sb.append(getString(R.string.rule_when)).append(FILED_MAP[filed]).append(CHECK_MAP[check]).append(value).append(getString(R.string.rule_fw_to)) + } + return sb.toString() + } + + } + + val ruleMatch: String + get() { + val simStr = if ("app" == type) "" else SIM_SLOT_MAP[simSlot].toString() + getString(R.string.rule_card) + return if (filed == FILED_TRANSPOND_ALL) { + simStr + getString(R.string.rule_all_fw_to) + } else { + simStr + getString(R.string.rule_when) + FILED_MAP[filed] + CHECK_MAP[check] + value + getString(R.string.rule_fw_to) + } + } + + val statusChecked: Boolean + get() = status != STATUS_OFF + + val imageId: Int + get() = when (simSlot) { + CHECK_SIM_SLOT_1 -> R.drawable.ic_sim1 + CHECK_SIM_SLOT_2 -> R.drawable.ic_sim2 + CHECK_SIM_SLOT_ALL -> if (type == "app") R.drawable.ic_app else R.drawable.ic_sim + else -> if (type == "app") R.drawable.ic_app else R.drawable.ic_sim + } + + val statusImageId: Int + get() = when (status) { + STATUS_OFF -> R.drawable.icon_off + 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 { + return when (simSlot) { + CHECK_SIM_SLOT_1 -> R.id.rb_sim_slot_1 + CHECK_SIM_SLOT_2 -> R.id.rb_sim_slot_2 + else -> R.id.rb_sim_slot_all + } + } + + fun getFiledCheckId(): Int { + return when (filed) { + FILED_MSG_CONTENT -> R.id.rb_content + FILED_PHONE_NUM -> R.id.rb_phone + FILED_PACKAGE_NAME -> R.id.rb_package_name + FILED_INFORM_CONTENT -> R.id.rb_inform_content + FILED_MULTI_MATCH -> R.id.rb_multi_match + else -> R.id.rb_transpond_all + } + } + + fun getCheckCheckId(): Int { + return when (check) { + CHECK_CONTAIN -> R.id.rb_contain + CHECK_NOT_CONTAIN -> R.id.rb_not_contain + CHECK_START_WITH -> R.id.rb_start_with + CHECK_END_WITH -> R.id.rb_end_with + CHECK_REGEX -> R.id.rb_regex + else -> R.id.rb_is + } + } + + //字段分支 + @Throws(Exception::class) + fun checkMsg(msg: MsgInfo?): Boolean { + + //检查这一行和上一行合并的结果是否命中 + var mixChecked = false + if (msg != null) { + //先检查规则是否命中 + when (this.filed) { + FILED_TRANSPOND_ALL -> mixChecked = true + FILED_PHONE_NUM, FILED_PACKAGE_NAME -> mixChecked = checkValue(msg.from) + FILED_MSG_CONTENT, FILED_INFORM_CONTENT -> mixChecked = checkValue(msg.content) + FILED_MULTI_MATCH -> mixChecked = RuleLineUtils.checkRuleLines(msg, this.value) + else -> {} + } + } + Log.i(TAG, "rule:$this checkMsg:$msg checked:$mixChecked") + return mixChecked + } + + //内容分支 + private fun checkValue(msgValue: String?): Boolean { + var checked = false + when (this.check) { + CHECK_IS -> checked = this.value == msgValue + CHECK_NOT_IS -> checked = this.value != msgValue + CHECK_CONTAIN -> if (msgValue != null) { + checked = msgValue.contains(this.value) + } + CHECK_NOT_CONTAIN -> if (msgValue != null) { + checked = !msgValue.contains(this.value) + } + CHECK_START_WITH -> if (msgValue != null) { + checked = msgValue.startsWith(this.value) + } + CHECK_END_WITH -> if (msgValue != null) { + checked = msgValue.endsWith(this.value) + } + CHECK_REGEX -> if (msgValue != null) { + try { + //checked = Pattern.matches(this.value, msgValue); + val pattern = Pattern.compile(this.value, Pattern.CASE_INSENSITIVE) + val matcher = pattern.matcher(msgValue) + while (matcher.find()) { + checked = true + break + } + } catch (e: PatternSyntaxException) { + Log.d(TAG, "PatternSyntaxException: ") + Log.d(TAG, "Description: " + e.description) + Log.d(TAG, "Index: " + e.index) + Log.d(TAG, "Message: " + e.message) + Log.d(TAG, "Pattern: " + e.pattern) + } + } + else -> {} + } + Log.i(TAG, "checkValue " + msgValue + " " + this.check + " " + this.value + " checked:" + checked) + return checked + } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/ext/Converters.kt b/app/src/main/java/com/idormy/sms/forwarder/database/ext/ConvertersDate.kt similarity index 89% rename from app/src/main/java/com/idormy/sms/forwarder/database/ext/Converters.kt rename to app/src/main/java/com/idormy/sms/forwarder/database/ext/ConvertersDate.kt index c55006e5..70e5133a 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/ext/Converters.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/ext/ConvertersDate.kt @@ -1,17 +1,17 @@ -package com.idormy.sms.forwarder.database.ext - -import androidx.room.TypeConverter -import java.util.* - -@Suppress("unused") -class Converters { - @TypeConverter - fun fromTimestamp(value: Long?): Date? { - return value?.let { Date(it) } - } - - @TypeConverter - fun dateToTimestamp(date: Date?): Long? { - return date?.time - } +package com.idormy.sms.forwarder.database.ext + +import androidx.room.TypeConverter +import java.util.* + +@Suppress("unused") +class ConvertersDate { + @TypeConverter + fun fromTimestamp(value: Long?): Date? { + return value?.let { Date(it) } + } + + @TypeConverter + fun dateToTimestamp(date: Date?): Long? { + return date?.time + } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/ext/ConvertersSenderList.kt b/app/src/main/java/com/idormy/sms/forwarder/database/ext/ConvertersSenderList.kt new file mode 100644 index 00000000..cdc09c22 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/ext/ConvertersSenderList.kt @@ -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 { + var senderList: MutableList = mutableListOf() + value.split(",").map { it.trim() }.forEach { + val sender = Core.sender.getOne(it.toLong()) + senderList.add(sender) + } + return senderList + } + + @TypeConverter + fun objectToString(list: List): String { + var senderList = ArrayList() + list.forEach { + senderList += it.id + } + return senderList.joinToString(",") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/repository/MsgRepository.kt b/app/src/main/java/com/idormy/sms/forwarder/database/repository/MsgRepository.kt new file mode 100644 index 00000000..fa0b4464 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/repository/MsgRepository.kt @@ -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) + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/repository/RuleRepository.kt b/app/src/main/java/com/idormy/sms/forwarder/database/repository/RuleRepository.kt index e8924446..c97710c3 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/repository/RuleRepository.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/repository/RuleRepository.kt @@ -1,36 +1,39 @@ -package com.idormy.sms.forwarder.database.repository - -import androidx.annotation.WorkerThread -import com.idormy.sms.forwarder.database.dao.RuleDao -import com.idormy.sms.forwarder.database.entity.Rule - -class RuleRepository( - private val ruleDao: RuleDao, -) { - - var listener: Listener? = null - - @WorkerThread - fun insert(rule: Rule) { - ruleDao.insert(rule) - } - - @WorkerThread - fun delete(id: Long) { - listener?.onDelete(id) - ruleDao.delete(id) - } - - @WorkerThread - fun get(id: Long) = ruleDao.get(id) - - 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) - - @WorkerThread - fun update(rule: Rule) = ruleDao.update(rule) - - //TODO:允许主线程访问,后面再优化 - val all: List = ruleDao.getAll() +package com.idormy.sms.forwarder.database.repository + +import androidx.annotation.WorkerThread +import com.idormy.sms.forwarder.database.dao.RuleDao +import com.idormy.sms.forwarder.database.entity.Rule + +class RuleRepository( + private val ruleDao: RuleDao, +) { + + var listener: Listener? = null + + @WorkerThread + fun insert(rule: Rule) { + ruleDao.insert(rule) + } + + @WorkerThread + fun delete(id: Long) { + listener?.onDelete(id) + ruleDao.delete(id) + } + + @WorkerThread + 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) + + fun getRuleList(type: String, status: Int, simSlot: String) = ruleDao.getRuleList(type, status, simSlot) + + @WorkerThread + fun update(rule: Rule) = ruleDao.update(rule) + + //TODO:允许主线程访问,后面再优化 + val all: List = ruleDao.getAll() } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/repository/SenderRepository.kt b/app/src/main/java/com/idormy/sms/forwarder/database/repository/SenderRepository.kt index c633325d..ef8301cf 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/repository/SenderRepository.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/repository/SenderRepository.kt @@ -1,34 +1,36 @@ -package com.idormy.sms.forwarder.database.repository - -import androidx.annotation.WorkerThread -import com.idormy.sms.forwarder.database.dao.SenderDao -import com.idormy.sms.forwarder.database.entity.Sender -import kotlinx.coroutines.flow.Flow - -class SenderRepository(private val senderDao: SenderDao) { - - var listener: Listener? = null - - @WorkerThread - fun insert(sender: Sender) = senderDao.insert(sender) - - @WorkerThread - fun delete(id: Long) { - listener?.onDelete(id) - senderDao.delete(id) - } - - fun get(id: Long) = senderDao.get(id) - - fun update(sender: Sender) = senderDao.update(sender) - - val count: Flow = senderDao.getOnCount() - - //TODO:允许主线程访问,后面再优化 - val all: List = senderDao.getAll2() - - fun deleteAll() { - senderDao.deleteAll() - } - +package com.idormy.sms.forwarder.database.repository + +import androidx.annotation.WorkerThread +import com.idormy.sms.forwarder.database.dao.SenderDao +import com.idormy.sms.forwarder.database.entity.Sender +import kotlinx.coroutines.flow.Flow + +class SenderRepository(private val senderDao: SenderDao) { + + var listener: Listener? = null + + @WorkerThread + fun insert(sender: Sender) = senderDao.insert(sender) + + @WorkerThread + fun delete(id: Long) { + listener?.onDelete(id) + senderDao.delete(id) + } + + fun get(id: Long) = senderDao.get(id) + + fun getOne(id: Long) = senderDao.getOne(id) + + fun update(sender: Sender) = senderDao.update(sender) + + val count: Flow = senderDao.getOnCount() + + //TODO:允许主线程访问,后面再优化 + val all: List = senderDao.getAll2() + + fun deleteAll() { + senderDao.deleteAll() + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/BaseViewModelFactory.kt b/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/BaseViewModelFactory.kt index df2d0372..6f802096 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/BaseViewModelFactory.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/BaseViewModelFactory.kt @@ -1,40 +1,45 @@ -package com.idormy.sms.forwarder.database.viewmodel - -import android.content.Context -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import com.idormy.sms.forwarder.database.AppDatabase - -class BaseViewModelFactory(private val context: Context?) : ViewModelProvider.Factory { - - override fun create(modelClass: Class): T { - - if (context == null) throw IllegalArgumentException("Context CAN NOT BE null") - - when { - modelClass.isAssignableFrom(FrpcViewModel::class.java) -> { - val frpcDao = AppDatabase.getInstance(context).frpcDao() - @Suppress("UNCHECKED_CAST") - return FrpcViewModel(frpcDao) as T - } - modelClass.isAssignableFrom(LogsViewModel::class.java) -> { - val logDao = AppDatabase.getInstance(context).logsDao() - @Suppress("UNCHECKED_CAST") - return LogsViewModel(logDao) as T - } - modelClass.isAssignableFrom(RuleViewModel::class.java) -> { - val ruleDao = AppDatabase.getInstance(context).ruleDao() - @Suppress("UNCHECKED_CAST") - return RuleViewModel(ruleDao) as T - } - modelClass.isAssignableFrom(SenderViewModel::class.java) -> { - val senderDao = AppDatabase.getInstance(context).senderDao() - @Suppress("UNCHECKED_CAST") - return SenderViewModel(senderDao) as T - } - } - - throw IllegalArgumentException("Unknown ViewModel class") - } - +package com.idormy.sms.forwarder.database.viewmodel + +import android.content.Context +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.idormy.sms.forwarder.database.AppDatabase + +class BaseViewModelFactory(private val context: Context?) : ViewModelProvider.Factory { + + override fun create(modelClass: Class): T { + + if (context == null) throw IllegalArgumentException("Context CAN NOT BE null") + + when { + modelClass.isAssignableFrom(FrpcViewModel::class.java) -> { + val frpcDao = AppDatabase.getInstance(context).frpcDao() + @Suppress("UNCHECKED_CAST") + 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) -> { + val logDao = AppDatabase.getInstance(context).logsDao() + @Suppress("UNCHECKED_CAST") + return LogsViewModel(logDao) as T + } + modelClass.isAssignableFrom(RuleViewModel::class.java) -> { + val ruleDao = AppDatabase.getInstance(context).ruleDao() + @Suppress("UNCHECKED_CAST") + return RuleViewModel(ruleDao) as T + } + modelClass.isAssignableFrom(SenderViewModel::class.java) -> { + val senderDao = AppDatabase.getInstance(context).senderDao() + @Suppress("UNCHECKED_CAST") + return SenderViewModel(senderDao) as T + } + } + + throw IllegalArgumentException("Unknown ViewModel class") + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/MsgViewModel.kt b/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/MsgViewModel.kt new file mode 100644 index 00000000..52e60c17 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/MsgViewModel.kt @@ -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> = Pager( + config = PagingConfig( + pageSize = 10, + enablePlaceholders = false, + initialLoadSize = 10 + ) + ) { + dao.pagingSource(type) + }.flow.cachedIn(viewModelScope) + + fun delete(id: Long) = ioThread { + dao.delete(id) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/RuleViewModel.kt b/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/RuleViewModel.kt index 550c4e2a..647e5e53 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/RuleViewModel.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/RuleViewModel.kt @@ -1,38 +1,37 @@ -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.RuleDao -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 kotlinx.coroutines.flow.Flow - -class RuleViewModel(private val dao: RuleDao) : ViewModel() { - private var type: String = "sms" - - fun setType(type: String): RuleViewModel { - this.type = type - return this - } - - val allRules: Flow> = Pager( - config = PagingConfig( - pageSize = 10, - enablePlaceholders = false, - initialLoadSize = 10 - ) - ) { dao.pagingSource(type) }.flow.cachedIn(viewModelScope) - - fun insertOrUpdate(rule: Rule) = ioThread { - if (rule.id > 0) dao.update(rule) else dao.insert(rule) - } - - fun delete(id: Long) = ioThread { - dao.delete(id) - } +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.RuleDao +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.database.ext.ioThread +import kotlinx.coroutines.flow.Flow + +class RuleViewModel(private val dao: RuleDao) : ViewModel() { + private var type: String = "sms" + + fun setType(type: String): RuleViewModel { + this.type = type + return this + } + + val allRules: Flow> = Pager( + config = PagingConfig( + pageSize = 10, + enablePlaceholders = false, + initialLoadSize = 10 + ) + ) { dao.pagingSource(type) }.flow.cachedIn(viewModelScope) + + fun insertOrUpdate(rule: Rule) = ioThread { + if (rule.id > 0) dao.update(rule) else dao.insert(rule) + } + + fun delete(id: Long) = ioThread { + dao.delete(id) + } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/LogsFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/LogsFragment.kt index d2f9dd2f..c96e1e86 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/LogsFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/LogsFragment.kt @@ -1,139 +1,160 @@ -package com.idormy.sms.forwarder.fragment - -import android.annotation.SuppressLint -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.RecyclerView.RecycledViewPool -import com.alibaba.android.vlayout.VirtualLayoutManager -import com.idormy.sms.forwarder.R -import com.idormy.sms.forwarder.adapter.LogsPagingAdapter -import com.idormy.sms.forwarder.core.BaseFragment -import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender -import com.idormy.sms.forwarder.database.entity.Rule -import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory -import com.idormy.sms.forwarder.database.viewmodel.LogsViewModel -import com.idormy.sms.forwarder.databinding.FragmentLogsBinding -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.SendUtils -import com.idormy.sms.forwarder.utils.XToastUtils -import com.jeremyliao.liveeventbus.LiveEventBus -import com.scwang.smartrefresh.layout.api.RefreshLayout -import com.xuexiang.xpage.annotation.Page -import com.xuexiang.xrouter.utils.TextUtils -import com.xuexiang.xui.utils.ResUtils -import com.xuexiang.xui.widget.actionbar.TitleBar -import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction -import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog -import com.xuexiang.xutil.data.DateUtils -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch -import java.text.SimpleDateFormat -import java.util.* - -@Suppress("PropertyName") -@Page(name = "转发日志") -class LogsFragment : BaseFragment(), LogsPagingAdapter.OnItemClickListener { - - val TAG: String = LogsFragment::class.java.simpleName - private var adapter = LogsPagingAdapter(this) - private val viewModel by viewModels { BaseViewModelFactory(context) } - private var currentType: String = "sms" - - override fun viewBindingInflate( - inflater: LayoutInflater, - container: ViewGroup, - ): FragmentLogsBinding { - return FragmentLogsBinding.inflate(inflater, container, false) - } - - /** - * @return 返回为 null意为不需要导航栏 - */ - override fun initTitle(): TitleBar? { - return null - } - - /** - * 初始化控件 - */ - override fun initViews() { - val virtualLayoutManager = VirtualLayoutManager(requireContext()) - binding!!.recyclerView.layoutManager = virtualLayoutManager - val viewPool = RecycledViewPool() - binding!!.recyclerView.setRecycledViewPool(viewPool) - viewPool.setMaxRecycledViews(0, 10) - binding!!.recyclerView.isFocusableInTouchMode = false - - binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.type_param_option)) - binding!!.tabBar.setOnTabClickListener { _, position -> - //XToastUtils.toast("点击了$title--$position") - currentType = when (position) { - 1 -> "call" - 2 -> "app" - else -> "sms" - } - viewModel.setType(currentType) - LiveEventBus.get(EVENT_UPDATE_LOGS_TYPE, String::class.java).post(currentType) - adapter.refresh() - binding!!.recyclerView.scrollToPosition(0) - } - } - - override fun initListeners() { - binding!!.recyclerView.adapter = adapter - - //下拉刷新 - binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> - //adapter.refresh() - lifecycleScope.launch { - viewModel.setType(currentType).allLogs.collectLatest { adapter.submitData(it) } - } - refreshLayout.finishRefresh() - } - - binding!!.refreshLayout.autoRefresh() - } - - override fun onItemClicked(view: View?, item: LogsAndRuleAndSender) { - val ruleStr = StringBuilder() - 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() - detailStr.append(ResUtils.getString(R.string.from)).append(item.logs.from).append("\n\n") - detailStr.append(ResUtils.getString(R.string.msg)).append(item.logs.content).append("\n\n") - if (!TextUtils.isEmpty(item.logs.simInfo)) detailStr.append(ResUtils.getString(R.string.slot)).append(item.logs.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()) - detailStr.append(ResUtils.getString(R.string.time)).append(DateUtils.date2String(item.logs.time, utcFormatter)).append("\n\n") - detailStr.append(ResUtils.getString(R.string.result)).append(FORWARD_STATUS_MAP[item.logs.forwardStatus]).append("\n--------------------\n").append(item.logs.forwardResponse) - - MaterialDialog.Builder(requireContext()) - .iconRes(item.logs.simImageId) - .title(R.string.details) - .content(detailStr.toString()) - .cancelable(true) - .positiveText(R.string.del) - .onPositive { _: MaterialDialog?, _: DialogAction? -> - viewModel.delete(item.logs.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) - } - .neutralText(R.string.rematch) - .neutralColor(ResUtils.getColors(R.color.red)) - .onNeutral { _: MaterialDialog?, _: DialogAction? -> - XToastUtils.toast(R.string.rematch_toast) - SendUtils.resendMsg(item, true) - } - .show() - } - - override fun onItemRemove(view: View?, id: Int) {} - +package com.idormy.sms.forwarder.fragment + +import android.annotation.SuppressLint +import android.text.TextUtils +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView.RecycledViewPool +import com.alibaba.android.vlayout.VirtualLayoutManager +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.MsgPagingAdapter +import com.idormy.sms.forwarder.core.BaseFragment +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.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.MsgViewModel +import com.idormy.sms.forwarder.databinding.FragmentLogsBinding +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.XToastUtils +import com.jeremyliao.liveeventbus.LiveEventBus +import com.scwang.smartrefresh.layout.api.RefreshLayout +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import com.xuexiang.xutil.data.DateUtils +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.* + +@Suppress("PropertyName") +@Page(name = "转发日志") +class LogsFragment : BaseFragment(), MsgPagingAdapter.OnItemClickListener { + + val TAG: String = LogsFragment::class.java.simpleName + private var adapter = MsgPagingAdapter(this) + private val viewModel by viewModels { BaseViewModelFactory(context) } + private var currentType: String = "sms" + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentLogsBinding { + return FragmentLogsBinding.inflate(inflater, container, false) + } + + /** + * @return 返回为 null意为不需要导航栏 + */ + override fun initTitle(): TitleBar? { + return null + } + + /** + * 初始化控件 + */ + override fun initViews() { + val virtualLayoutManager = VirtualLayoutManager(requireContext()) + binding!!.recyclerView.layoutManager = virtualLayoutManager + val viewPool = RecycledViewPool() + binding!!.recyclerView.setRecycledViewPool(viewPool) + viewPool.setMaxRecycledViews(0, 10) + binding!!.recyclerView.isFocusableInTouchMode = false + + binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.type_param_option)) + binding!!.tabBar.setOnTabClickListener { _, position -> + //XToastUtils.toast("点击了$title--$position") + currentType = when (position) { + 1 -> "call" + 2 -> "app" + else -> "sms" + } + viewModel.setType(currentType) + LiveEventBus.get(EVENT_UPDATE_LOGS_TYPE, String::class.java).post(currentType) + adapter.refresh() + binding!!.recyclerView.scrollToPosition(0) + } + } + + override fun initListeners() { + binding!!.recyclerView.adapter = adapter + + //下拉刷新 + binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> + //adapter.refresh() + lifecycleScope.launch { + viewModel.setType(currentType).allMsg.collectLatest { adapter.submitData(it) } + } + refreshLayout.finishRefresh() + } + + binding!!.refreshLayout.autoRefresh() + } + + override fun onItemClicked(view: View?, item: MsgAndLogs) { + Log.d(TAG, "item: $item") + + val detailStr = StringBuilder() + detailStr.append(ResUtils.getString(R.string.from)).append(item.msg.from).append("\n\n") + detailStr.append(ResUtils.getString(R.string.msg)).append(item.msg.content).append("\n\n") + if (!TextUtils.isEmpty(item.msg.simInfo)) detailStr.append(ResUtils.getString(R.string.slot)).append(item.msg.simInfo).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.msg.time, utcFormatter)) + + MaterialDialog.Builder(requireContext()) + .iconRes(item.msg.simImageId) + .title(R.string.details) + .content(detailStr.toString()) + .cancelable(true) + .positiveText(R.string.del) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(item.msg.id) + XToastUtils.success(R.string.delete_log_toast) + } + .neutralText(R.string.rematch) + .neutralColor(ResUtils.getColors(R.color.red)) + .onNeutral { _: MaterialDialog?, _: DialogAction? -> + XToastUtils.toast(R.string.rematch_toast) + //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() + } + + override fun onItemRemove(view: View?, id: Int) {} + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesEditFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesEditFragment.kt index e983fcb5..3b8f6fd4 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesEditFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesEditFragment.kt @@ -1,661 +1,679 @@ -package com.idormy.sms.forwarder.fragment - -import android.os.Looper -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.* -import androidx.fragment.app.viewModels -import com.idormy.sms.forwarder.App -import com.idormy.sms.forwarder.R -import com.idormy.sms.forwarder.adapter.spinner.AppListAdapterItem -import com.idormy.sms.forwarder.adapter.spinner.AppListSpinnerAdapter -import com.idormy.sms.forwarder.adapter.spinner.SenderAdapterItem -import com.idormy.sms.forwarder.adapter.spinner.SenderSpinnerAdapter -import com.idormy.sms.forwarder.core.BaseFragment -import com.idormy.sms.forwarder.database.AppDatabase -import com.idormy.sms.forwarder.database.entity.Rule -import com.idormy.sms.forwarder.database.entity.Sender -import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory -import com.idormy.sms.forwarder.database.viewmodel.RuleViewModel -import com.idormy.sms.forwarder.databinding.FragmentRulesEditBinding -import com.idormy.sms.forwarder.entity.MsgInfo -import com.idormy.sms.forwarder.utils.* -import com.xuexiang.xaop.annotation.SingleClick -import com.xuexiang.xpage.annotation.Page -import com.xuexiang.xrouter.annotation.AutoWired -import com.xuexiang.xrouter.launcher.XRouter -import com.xuexiang.xrouter.utils.TextUtils -import com.xuexiang.xui.utils.ResUtils -import com.xuexiang.xui.widget.actionbar.TitleBar -import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction -import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog -import com.xuexiang.xutil.app.AppUtils -import io.reactivex.SingleObserver -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async -import kotlinx.coroutines.launch -import java.util.* - -@Page(name = "转发规则·编辑器") -@Suppress("PrivatePropertyName", "CAST_NEVER_SUCCEEDS") -class RulesEditFragment : BaseFragment(), View.OnClickListener, CompoundButton.OnCheckedChangeListener { - - private val TAG: String = RulesEditFragment::class.java.simpleName - var titleBar: TitleBar? = null - private val viewModel by viewModels { BaseViewModelFactory(context) } - - //当前发送通道 - var senderId = 0L - - //发送通道列表 - private val senderSpinnerList = ArrayList() - private lateinit var senderSpinnerAdapter: SenderSpinnerAdapter<*> - - //已安装App信息列表 - private val appListSpinnerList = ArrayList() - private lateinit var appListSpinnerAdapter: AppListSpinnerAdapter<*> - - @JvmField - @AutoWired(name = KEY_RULE_ID) - var ruleId: Long = 0 - - @JvmField - @AutoWired(name = KEY_RULE_TYPE) - var ruleType: String = "sms" - - @JvmField - @AutoWired(name = KEY_RULE_CLONE) - var isClone: Boolean = false - - override fun initArgs() { - XRouter.getInstance().inject(this) - } - - override fun viewBindingInflate( - inflater: LayoutInflater, - container: ViewGroup, - ): FragmentRulesEditBinding { - return FragmentRulesEditBinding.inflate(inflater, container, false) - } - - override fun initTitle(): TitleBar? { - titleBar = super.initTitle()!!.setImmersive(false) - titleBar!!.setTitle(R.string.menu_rules) - return titleBar - } - - /** - * 初始化控件 - */ - override fun initViews() { - when (ruleType) { - "app" -> { - titleBar?.setTitle(R.string.app_rule) - binding!!.layoutSimSlot.visibility = View.GONE - binding!!.rbPhone.visibility = View.GONE - binding!!.rbContent.visibility = View.GONE - binding!!.tvMuRuleTips.setText(R.string.mu_rule_app_tips) - binding!!.btInsertExtra.visibility = View.GONE - binding!!.btInsertSender.visibility = View.GONE - binding!!.btInsertContent.visibility = View.GONE - } - "call" -> { - titleBar?.setTitle(R.string.call_rule) - binding!!.rbContent.visibility = View.GONE - binding!!.rbPackageName.visibility = View.GONE - binding!!.rbInformContent.visibility = View.GONE - binding!!.rbMultiMatch.visibility = View.GONE - binding!!.btInsertContent.visibility = View.GONE - binding!!.btInsertSenderApp.visibility = View.GONE - binding!!.btInsertTitleApp.visibility = View.GONE - binding!!.btInsertContentApp.visibility = View.GONE - } - else -> { - titleBar?.setTitle(R.string.sms_rule) - binding!!.rbPackageName.visibility = View.GONE - binding!!.rbInformContent.visibility = View.GONE - binding!!.btInsertSenderApp.visibility = View.GONE - binding!!.btInsertTitleApp.visibility = View.GONE - binding!!.btInsertContentApp.visibility = View.GONE - } - } - - if (ruleId <= 0) { //新增 - titleBar?.setSubTitle(getString(R.string.add_rule)) - binding!!.btnDel.setText(R.string.discard) - initSenderSpinner() - } else { //编辑 & 克隆 - binding!!.btnDel.setText(R.string.del) - initForm() - } - - //初始化APP下拉列表 - initAppSpinner() - } - - override fun initListeners() { - binding!!.btInsertSender.setOnClickListener(this) - binding!!.btInsertContent.setOnClickListener(this) - binding!!.btInsertSenderApp.setOnClickListener(this) - binding!!.btInsertContentApp.setOnClickListener(this) - binding!!.btInsertExtra.setOnClickListener(this) - binding!!.btInsertTime.setOnClickListener(this) - binding!!.btInsertDeviceName.setOnClickListener(this) - binding!!.btnTest.setOnClickListener(this) - binding!!.btnDel.setOnClickListener(this) - binding!!.btnSave.setOnClickListener(this) - - binding!!.sbSmsTemplate.setOnCheckedChangeListener(this) - binding!!.sbRegexReplace.setOnCheckedChangeListener(this) - - binding!!.rgFiled.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int -> - if (ruleType == "app" && appListSpinnerList.isNotEmpty()) { - binding!!.layoutAppList.visibility = if (checkedId == R.id.rb_inform_content) View.GONE else View.VISIBLE - } - when (checkedId) { - R.id.rb_transpond_all -> { - binding!!.rgCheck.check(R.id.rb_is) - binding!!.tvMuRuleTips.visibility = View.GONE - binding!!.layoutMatchType.visibility = View.GONE - binding!!.layoutMatchValue.visibility = View.GONE - } - R.id.rb_multi_match -> { - binding!!.rgCheck.check(R.id.rb_is) - binding!!.tvMuRuleTips.visibility = View.VISIBLE - binding!!.layoutMatchType.visibility = View.GONE - binding!!.layoutMatchValue.visibility = View.VISIBLE - } - else -> { - binding!!.tvMuRuleTips.visibility = View.GONE - binding!!.layoutMatchType.visibility = View.VISIBLE - binding!!.layoutMatchValue.visibility = View.VISIBLE - } - } - } - - binding!!.rgCheck.setOnCheckedChangeListener { group: RadioGroup?, checkedId: Int -> - if (group != null && checkedId > 0) { - binding!!.rgCheck2.clearCheck() - group.check(checkedId) - } - } - - binding!!.rgCheck2.setOnCheckedChangeListener { group: RadioGroup?, checkedId: Int -> - if (group != null && checkedId > 0) { - binding!!.rgCheck.clearCheck() - group.check(checkedId) - } - } - } - - override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { - when (buttonView?.id) { - R.id.sb_sms_template -> { - if (isChecked) { - binding!!.layoutSmsTemplate.visibility = View.VISIBLE - } else { - binding!!.layoutSmsTemplate.visibility = View.GONE - binding!!.etSmsTemplate.setText("") - } - } - R.id.sb_regex_replace -> { - if (isChecked) { - binding!!.layoutRegexReplace.visibility = View.VISIBLE - } else { - binding!!.layoutRegexReplace.visibility = View.GONE - binding!!.etRegexReplace.setText("") - } - } - else -> {} - } - } - - @SingleClick - override fun onClick(v: View) { - try { - val etSmsTemplate: EditText = binding!!.etSmsTemplate - when (v.id) { - R.id.bt_insert_sender -> { - CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_from)) - return - } - R.id.bt_insert_content -> { - CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_sms)) - return - } - R.id.bt_insert_sender_app -> { - CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_package_name)) - return - } - R.id.bt_insert_title_app -> { - CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_title)) - return - } - R.id.bt_insert_content_app -> { - CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_msg)) - return - } - R.id.bt_insert_extra -> { - CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_card_slot)) - return - } - R.id.bt_insert_time -> { - CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_receive_time)) - return - } - R.id.bt_insert_device_name -> { - CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_device_name)) - return - } - R.id.btn_test -> { - val ruleNew = checkForm() - testRule(ruleNew) - return - } - R.id.btn_del -> { - if (ruleId <= 0 || isClone) { - popToBack() - return - } - - 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? -> - viewModel.delete(ruleId) - XToastUtils.success(R.string.delete_rule_toast) - popToBack() - } - .show() - return - } - R.id.btn_save -> { - val ruleNew = checkForm() - if (isClone) ruleNew.id = 0 - Log.d(TAG, ruleNew.toString()) - viewModel.insertOrUpdate(ruleNew) - XToastUtils.success(R.string.tipSaveSuccess) - popToBack() - return - } - } - } catch (e: Exception) { - XToastUtils.error(e.message.toString()) - e.printStackTrace() - } - } - - //初始化发送通道下拉框 - private fun initSenderSpinner() { - AppDatabase.getInstance(requireContext()) - .senderDao() - .getAll() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(object : SingleObserver> { - override fun onSubscribe(d: Disposable) {} - - override fun onError(e: Throwable) { - e.printStackTrace() - } - - override fun onSuccess(senderList: List) { - if (senderList.isEmpty()) { - XToastUtils.error(R.string.add_sender_first) - return - } - - for (sender in senderList) { - val name = if (sender.name.length > 20) sender.name.substring(0, 19) else sender.name - 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) - - 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 -> - try { - //val sender = senderSpinnerList[position] - val sender = senderSpinnerAdapter.getItemSource(position) as SenderAdapterItem - sender.id.also { - senderId = it ?: 0L - } - 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 - if (STATUS_OFF == sender.status) { - XToastUtils.warning(getString(R.string.sender_disabled_tips)) - } - } catch (e: Exception) { - XToastUtils.error(e.message.toString()) - } - } - } - - //初始化APP下拉列表 - private fun initAppSpinner() { - if (ruleType != "app") return - - //未开启异步获取已安装App信息开关时,规则编辑不显示已安装APP下拉框 - if (!SettingUtils.enableLoadUserAppList && !SettingUtils.enableLoadSystemAppList) return - - val get = GlobalScope.async(Dispatchers.IO) { - if ((SettingUtils.enableLoadUserAppList && App.UserAppList.isEmpty()) - || (SettingUtils.enableLoadSystemAppList && App.SystemAppList.isEmpty()) - ) { - App.UserAppList.clear() - App.SystemAppList.clear() - val appInfoList = AppUtils.getAppsInfo() - for (appInfo in appInfoList) { - if (appInfo.isSystem) { - App.SystemAppList.add(appInfo) - } else { - App.UserAppList.add(appInfo) - } - } - App.UserAppList.sortBy { appInfo -> appInfo.name } - App.SystemAppList.sortBy { appInfo -> appInfo.name } - } - } - GlobalScope.launch(Dispatchers.Main) { - runCatching { - get.await() - if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) return@runCatching - - appListSpinnerList.clear() - if (SettingUtils.enableLoadUserAppList) { - for (appInfo in App.UserAppList) { - appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName)) - } - } - if (SettingUtils.enableLoadSystemAppList) { - for (appInfo in App.SystemAppList) { - appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName)) - } - } - - //列表为空也不显示下拉框 - if (appListSpinnerList.isEmpty()) return@runCatching - - appListSpinnerAdapter = AppListSpinnerAdapter(appListSpinnerList) - //.setTextColor(ResUtils.getColor(R.color.green)) - //.setTextSize(12F) - .setIsFilterKey(true) - .setFilterColor("#EF5362") - .setBackgroundSelector(R.drawable.selector_custom_spinner_bg) - binding!!.spApp.setAdapter(appListSpinnerAdapter) - binding!!.spApp.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long -> - try { - //val appInfo = appListSpinnerList[position] - val appInfo = appListSpinnerAdapter.getItemSource(position) as AppListAdapterItem - CommonUtils.insertOrReplaceText2Cursor(binding!!.etValue, appInfo.packageName.toString()) - } catch (e: Exception) { - XToastUtils.error(e.message.toString()) - } - } - binding!!.layoutAppList.visibility = View.VISIBLE - }.onFailure { - Log.e("GlobalScope", it.message.toString()) - } - } - } - - //初始化表单 - private fun initForm() { - AppDatabase.getInstance(requireContext()) - .ruleDao() - .get(ruleId) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(object : SingleObserver { - override fun onSubscribe(d: Disposable) {} - - override fun onError(e: Throwable) { - e.printStackTrace() - } - - override fun onSuccess(rule: Rule) { - senderId = rule.senderId - - if (isClone) { - titleBar?.setSubTitle(getString(R.string.clone_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() - } - }) - } - - //提交前检查表单 - private fun checkForm(): Rule { - if (senderId <= 0L) { - throw Exception(getString(R.string.new_sender_first)) - } - val filed = when (binding!!.rgFiled.checkedRadioButtonId) { - R.id.rb_content -> FILED_MSG_CONTENT - R.id.rb_phone -> FILED_PHONE_NUM - R.id.rb_package_name -> FILED_PACKAGE_NAME - R.id.rb_inform_content -> FILED_INFORM_CONTENT - R.id.rb_multi_match -> FILED_MULTI_MATCH - else -> FILED_TRANSPOND_ALL - } - val check = when (kotlin.math.max(binding!!.rgCheck.checkedRadioButtonId, binding!!.rgCheck2.checkedRadioButtonId)) { - R.id.rb_contain -> CHECK_CONTAIN - R.id.rb_not_contain -> CHECK_NOT_CONTAIN - R.id.rb_start_with -> CHECK_START_WITH - R.id.rb_end_with -> CHECK_END_WITH - R.id.rb_regex -> CHECK_REGEX - else -> CHECK_IS - } - val value = binding!!.etValue.text.toString().trim() - if (FILED_TRANSPOND_ALL != filed && TextUtils.isEmpty(value)) { - throw Exception(getString(R.string.invalid_match_value)) - } - if (FILED_MULTI_MATCH == filed) { - val lineError = checkMultiMatch(value) - if (lineError > 0) { - throw Exception(String.format(getString(R.string.invalid_multi_match), lineError)) - } - } - - val smsTemplate = binding!!.etSmsTemplate.text.toString().trim() - val regexReplace = binding!!.etRegexReplace.text.toString().trim() - val lineNum = checkRegexReplace(regexReplace) - if (lineNum > 0) { - throw Exception(String.format(getString(R.string.invalid_regex_replace), lineNum)) - } - - val simSlot = when (binding!!.rgSimSlot.checkedRadioButtonId) { - R.id.rb_sim_slot_1 -> CHECK_SIM_SLOT_1 - R.id.rb_sim_slot_2 -> CHECK_SIM_SLOT_2 - else -> CHECK_SIM_SLOT_ALL - } - val status = if (binding!!.sbStatus.isChecked) STATUS_ON else STATUS_OFF - //if (status == STATUS_OFF) { - // throw Exception(getString(R.string.invalid_rule_status)) - //} - - return Rule(ruleId, ruleType, filed, check, value, senderId, smsTemplate, regexReplace, simSlot, status) - } - - //检查多重匹配规则是否正确 - private fun checkMultiMatch(ruleStr: String?): Int { - if (TextUtils.isEmpty(ruleStr)) return 0 - - Log.d(TAG, getString(R.string.regex_multi_match)) - val regex = Regex(pattern = getString(R.string.regex_multi_match)) - var lineNum = 1 - val lineArray = ruleStr?.split("\\n".toRegex())?.toTypedArray() - for (line in lineArray!!) { - Log.d(TAG, line) - if (!line.matches(regex)) return lineNum - lineNum++ - } - - return 0 - } - - //检查正则替换填写是否正确 - private fun checkRegexReplace(regexReplace: String?): Int { - if (TextUtils.isEmpty(regexReplace)) return 0 - - var lineNum = 1 - val lineArray = regexReplace?.split("\\n".toRegex())?.toTypedArray() - for (line in lineArray!!) { - val position = line.indexOf("===") - if (position < 1) return lineNum - lineNum++ - } - return 0 - } - - private fun testRule(rule: Rule) { - val dialogTest = View.inflate(requireContext(), R.layout.dialog_rule_test, null) - val tvSimSlot = dialogTest.findViewById(R.id.tv_sim_slot) - val rgSimSlot = dialogTest.findViewById(R.id.rg_sim_slot) - val tvFrom = dialogTest.findViewById(R.id.tv_from) - val etFrom = dialogTest.findViewById(R.id.et_from) - val tvTitle = dialogTest.findViewById(R.id.tv_title) - val etTitle = dialogTest.findViewById(R.id.et_title) - val tvContent = dialogTest.findViewById(R.id.tv_content) - val etContent = dialogTest.findViewById(R.id.et_content) - - if ("app" == ruleType) { - tvSimSlot.visibility = View.GONE - rgSimSlot.visibility = View.GONE - tvTitle.visibility = View.VISIBLE - etTitle.visibility = View.VISIBLE - tvFrom.setText(R.string.test_package_name) - tvContent.setText(R.string.test_inform_content) - } else if ("call" == ruleType) { - tvContent.visibility = View.GONE - etContent.visibility = View.GONE - } - - 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? -> - 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 { - 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() - } +package com.idormy.sms.forwarder.fragment + +import android.annotation.SuppressLint +import android.os.Looper +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.* +import androidx.fragment.app.viewModels +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.spinner.AppListAdapterItem +import com.idormy.sms.forwarder.adapter.spinner.AppListSpinnerAdapter +import com.idormy.sms.forwarder.adapter.spinner.SenderAdapterItem +import com.idormy.sms.forwarder.adapter.spinner.SenderSpinnerAdapter +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.RuleViewModel +import com.idormy.sms.forwarder.databinding.FragmentRulesEditBinding +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.utils.* +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xrouter.utils.TextUtils +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import com.xuexiang.xutil.app.AppUtils +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import java.util.* + +@Page(name = "转发规则·编辑器") +@Suppress("PrivatePropertyName", "CAST_NEVER_SUCCEEDS") +class RulesEditFragment : BaseFragment(), View.OnClickListener, CompoundButton.OnCheckedChangeListener { + + private val TAG: String = RulesEditFragment::class.java.simpleName + var titleBar: TitleBar? = null + private val viewModel by viewModels { BaseViewModelFactory(context) } + + //当前发送通道 + var senderId = 0L + var senderListSelected: MutableList = mutableListOf() + private var senderItemMap = HashMap(2) + + //发送通道列表 + var senderListAll: MutableList = mutableListOf() + private val senderSpinnerList = ArrayList() + private lateinit var senderSpinnerAdapter: SenderSpinnerAdapter<*> + + //已安装App信息列表 + private val appListSpinnerList = ArrayList() + private lateinit var appListSpinnerAdapter: AppListSpinnerAdapter<*> + + @JvmField + @AutoWired(name = KEY_RULE_ID) + var ruleId: Long = 0 + + @JvmField + @AutoWired(name = KEY_RULE_TYPE) + var ruleType: String = "sms" + + @JvmField + @AutoWired(name = KEY_RULE_CLONE) + var isClone: Boolean = false + + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentRulesEditBinding { + return FragmentRulesEditBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false) + titleBar!!.setTitle(R.string.menu_rules) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + when (ruleType) { + "app" -> { + titleBar?.setTitle(R.string.app_rule) + binding!!.layoutSimSlot.visibility = View.GONE + binding!!.rbPhone.visibility = View.GONE + binding!!.rbContent.visibility = View.GONE + binding!!.tvMuRuleTips.setText(R.string.mu_rule_app_tips) + binding!!.btInsertExtra.visibility = View.GONE + binding!!.btInsertSender.visibility = View.GONE + binding!!.btInsertContent.visibility = View.GONE + } + "call" -> { + titleBar?.setTitle(R.string.call_rule) + binding!!.rbContent.visibility = View.GONE + binding!!.rbPackageName.visibility = View.GONE + binding!!.rbInformContent.visibility = View.GONE + binding!!.rbMultiMatch.visibility = View.GONE + binding!!.btInsertContent.visibility = View.GONE + binding!!.btInsertSenderApp.visibility = View.GONE + binding!!.btInsertTitleApp.visibility = View.GONE + binding!!.btInsertContentApp.visibility = View.GONE + } + else -> { + titleBar?.setTitle(R.string.sms_rule) + binding!!.rbPackageName.visibility = View.GONE + binding!!.rbInformContent.visibility = View.GONE + binding!!.btInsertSenderApp.visibility = View.GONE + binding!!.btInsertTitleApp.visibility = View.GONE + binding!!.btInsertContentApp.visibility = View.GONE + } + } + + if (ruleId <= 0) { //新增 + titleBar?.setSubTitle(getString(R.string.add_rule)) + binding!!.btnDel.setText(R.string.discard) + initSenderSpinner() + } else { //编辑 & 克隆 + binding!!.btnDel.setText(R.string.del) + initForm() + } + + //初始化APP下拉列表 + initAppSpinner() + } + + override fun initListeners() { + binding!!.btInsertSender.setOnClickListener(this) + binding!!.btInsertContent.setOnClickListener(this) + binding!!.btInsertSenderApp.setOnClickListener(this) + binding!!.btInsertContentApp.setOnClickListener(this) + binding!!.btInsertExtra.setOnClickListener(this) + binding!!.btInsertTime.setOnClickListener(this) + binding!!.btInsertDeviceName.setOnClickListener(this) + binding!!.btnTest.setOnClickListener(this) + binding!!.btnDel.setOnClickListener(this) + binding!!.btnSave.setOnClickListener(this) + + binding!!.sbSmsTemplate.setOnCheckedChangeListener(this) + binding!!.sbRegexReplace.setOnCheckedChangeListener(this) + + binding!!.rgFiled.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int -> + if (ruleType == "app" && appListSpinnerList.isNotEmpty()) { + binding!!.layoutAppList.visibility = if (checkedId == R.id.rb_inform_content) View.GONE else View.VISIBLE + } + when (checkedId) { + R.id.rb_transpond_all -> { + binding!!.rgCheck.check(R.id.rb_is) + binding!!.tvMuRuleTips.visibility = View.GONE + binding!!.layoutMatchType.visibility = View.GONE + binding!!.layoutMatchValue.visibility = View.GONE + } + R.id.rb_multi_match -> { + binding!!.rgCheck.check(R.id.rb_is) + binding!!.tvMuRuleTips.visibility = View.VISIBLE + binding!!.layoutMatchType.visibility = View.GONE + binding!!.layoutMatchValue.visibility = View.VISIBLE + } + else -> { + binding!!.tvMuRuleTips.visibility = View.GONE + binding!!.layoutMatchType.visibility = View.VISIBLE + binding!!.layoutMatchValue.visibility = View.VISIBLE + } + } + } + + binding!!.rgCheck.setOnCheckedChangeListener { group: RadioGroup?, checkedId: Int -> + if (group != null && checkedId > 0) { + binding!!.rgCheck2.clearCheck() + group.check(checkedId) + } + } + + binding!!.rgCheck2.setOnCheckedChangeListener { group: RadioGroup?, checkedId: Int -> + if (group != null && checkedId > 0) { + binding!!.rgCheck.clearCheck() + group.check(checkedId) + } + } + } + + override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { + when (buttonView?.id) { + R.id.sb_sms_template -> { + if (isChecked) { + binding!!.layoutSmsTemplate.visibility = View.VISIBLE + } else { + binding!!.layoutSmsTemplate.visibility = View.GONE + binding!!.etSmsTemplate.setText("") + } + } + R.id.sb_regex_replace -> { + if (isChecked) { + binding!!.layoutRegexReplace.visibility = View.VISIBLE + } else { + binding!!.layoutRegexReplace.visibility = View.GONE + binding!!.etRegexReplace.setText("") + } + } + else -> {} + } + } + + @SingleClick + override fun onClick(v: View) { + try { + val etSmsTemplate: EditText = binding!!.etSmsTemplate + when (v.id) { + R.id.bt_insert_sender -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_from)) + return + } + R.id.bt_insert_content -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_sms)) + return + } + R.id.bt_insert_sender_app -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_package_name)) + return + } + R.id.bt_insert_title_app -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_title)) + return + } + R.id.bt_insert_content_app -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_msg)) + return + } + R.id.bt_insert_extra -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_card_slot)) + return + } + R.id.bt_insert_time -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_receive_time)) + return + } + R.id.bt_insert_device_name -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_device_name)) + return + } + R.id.btn_test -> { + val ruleNew = checkForm() + testRule(ruleNew) + return + } + R.id.btn_del -> { + if (ruleId <= 0 || isClone) { + popToBack() + return + } + + 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? -> + viewModel.delete(ruleId) + XToastUtils.success(R.string.delete_rule_toast) + popToBack() + }.show() + return + } + R.id.btn_save -> { + val ruleNew = checkForm() + if (isClone) ruleNew.id = 0 + Log.d(TAG, ruleNew.toString()) + viewModel.insertOrUpdate(ruleNew) + XToastUtils.success(R.string.tipSaveSuccess) + popToBack() + return + } + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + e.printStackTrace() + } + } + + //初始化发送通道下拉框 + private fun initSenderSpinner() { + AppDatabase.getInstance(requireContext()).senderDao().getAll().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(object : SingleObserver> { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(senderList: List) { + if (senderList.isEmpty()) { + XToastUtils.error(R.string.add_sender_first) + return + } + + senderListAll = senderList as MutableList + for (sender in senderList) { + val name = if (sender.name.length > 20) sender.name.substring(0, 19) else sender.name + 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) + + if (senderListSelected.isNotEmpty()) { + for (sender in senderListSelected) { + for (senderItem in senderSpinnerList) { + if (sender.id == senderItem.id) { + addSenderItemLinearLayout(senderItemMap, binding!!.layoutSenders, senderItem) + } + } + } + } + } + }) + binding!!.spSender.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long -> + try { + val sender = senderSpinnerAdapter.getItemSource(position) as SenderAdapterItem + senderId = sender.id!! + if (senderId > 0L) { + senderListSelected.forEach { + if (senderId == it.id) { + XToastUtils.warning(getString(R.string.sender_contains_tips)) + return@setOnItemClickListener + } + } + senderListAll.forEach { + if (senderId == it.id) { + senderListSelected.add(it) + addSenderItemLinearLayout(senderItemMap, binding!!.layoutSenders, sender) + } + } + + if (STATUS_OFF == sender.status) { + XToastUtils.warning(getString(R.string.sender_disabled_tips)) + } + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + } + } + } + + /** + * 动态增删header + * + * @param senderItemMap 管理item的map,用于删除指定header + * @param layoutSenders 需要挂载item的LinearLayout + * @param sender SenderAdapterItem + */ + @SuppressLint("SetTextI18n") + private fun addSenderItemLinearLayout( + senderItemMap: MutableMap, layoutSenders: LinearLayout, sender: SenderAdapterItem + ) { + val layoutSenderItem = View.inflate(requireContext(), R.layout.item_add_sender, null) as LinearLayout + val ivRemoveSender = layoutSenderItem.findViewById(R.id.iv_remove_sender) + val ivSenderImage = layoutSenderItem.findViewById(R.id.iv_sender_image) + val ivSenderStatus = layoutSenderItem.findViewById(R.id.iv_sender_status) + val tvSenderName = layoutSenderItem.findViewById(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下拉列表 + private fun initAppSpinner() { + if (ruleType != "app") return + + //未开启异步获取已安装App信息开关时,规则编辑不显示已安装APP下拉框 + if (!SettingUtils.enableLoadUserAppList && !SettingUtils.enableLoadSystemAppList) return + + val get = GlobalScope.async(Dispatchers.IO) { + if ((SettingUtils.enableLoadUserAppList && App.UserAppList.isEmpty()) || (SettingUtils.enableLoadSystemAppList && App.SystemAppList.isEmpty())) { + App.UserAppList.clear() + App.SystemAppList.clear() + val appInfoList = AppUtils.getAppsInfo() + for (appInfo in appInfoList) { + if (appInfo.isSystem) { + App.SystemAppList.add(appInfo) + } else { + App.UserAppList.add(appInfo) + } + } + App.UserAppList.sortBy { appInfo -> appInfo.name } + App.SystemAppList.sortBy { appInfo -> appInfo.name } + } + } + GlobalScope.launch(Dispatchers.Main) { + runCatching { + get.await() + if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) return@runCatching + + appListSpinnerList.clear() + if (SettingUtils.enableLoadUserAppList) { + for (appInfo in App.UserAppList) { + appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName)) + } + } + if (SettingUtils.enableLoadSystemAppList) { + for (appInfo in App.SystemAppList) { + appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName)) + } + } + + //列表为空也不显示下拉框 + if (appListSpinnerList.isEmpty()) return@runCatching + + appListSpinnerAdapter = AppListSpinnerAdapter(appListSpinnerList) + //.setTextColor(ResUtils.getColor(R.color.green)) + //.setTextSize(12F) + .setIsFilterKey(true).setFilterColor("#EF5362").setBackgroundSelector(R.drawable.selector_custom_spinner_bg) + binding!!.spApp.setAdapter(appListSpinnerAdapter) + binding!!.spApp.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long -> + try { + //val appInfo = appListSpinnerList[position] + val appInfo = appListSpinnerAdapter.getItemSource(position) as AppListAdapterItem + CommonUtils.insertOrReplaceText2Cursor(binding!!.etValue, appInfo.packageName.toString()) + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + } + } + binding!!.layoutAppList.visibility = View.VISIBLE + }.onFailure { + Log.e("GlobalScope", it.message.toString()) + } + } + } + + //初始化表单 + private fun initForm() { + AppDatabase.getInstance(requireContext()).ruleDao().get(ruleId).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(rule: Rule) { + Log.d(TAG, rule.senderList.toString()) + rule.senderList.forEach { + senderId = it.id + senderListSelected.add(it) + } + + if (isClone) { + titleBar?.setSubTitle(getString(R.string.clone_rule)) + binding!!.btnDel.setText(R.string.discard) + } else { + titleBar?.setSubTitle(getString(R.string.edit_rule)) + } + 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 { + if (senderListSelected.isEmpty() || senderId == 0L) { + throw Exception(getString(R.string.new_sender_first)) + } + val filed = when (binding!!.rgFiled.checkedRadioButtonId) { + R.id.rb_content -> FILED_MSG_CONTENT + R.id.rb_phone -> FILED_PHONE_NUM + R.id.rb_package_name -> FILED_PACKAGE_NAME + R.id.rb_inform_content -> FILED_INFORM_CONTENT + R.id.rb_multi_match -> FILED_MULTI_MATCH + else -> FILED_TRANSPOND_ALL + } + val check = when (kotlin.math.max(binding!!.rgCheck.checkedRadioButtonId, binding!!.rgCheck2.checkedRadioButtonId)) { + R.id.rb_contain -> CHECK_CONTAIN + R.id.rb_not_contain -> CHECK_NOT_CONTAIN + R.id.rb_start_with -> CHECK_START_WITH + R.id.rb_end_with -> CHECK_END_WITH + R.id.rb_regex -> CHECK_REGEX + else -> CHECK_IS + } + val value = binding!!.etValue.text.toString().trim() + if (FILED_TRANSPOND_ALL != filed && TextUtils.isEmpty(value)) { + throw Exception(getString(R.string.invalid_match_value)) + } + if (FILED_MULTI_MATCH == filed) { + val lineError = checkMultiMatch(value) + if (lineError > 0) { + throw Exception(String.format(getString(R.string.invalid_multi_match), lineError)) + } + } + + val smsTemplate = binding!!.etSmsTemplate.text.toString().trim() + val regexReplace = binding!!.etRegexReplace.text.toString().trim() + val lineNum = checkRegexReplace(regexReplace) + if (lineNum > 0) { + 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) { + R.id.rb_sim_slot_1 -> CHECK_SIM_SLOT_1 + R.id.rb_sim_slot_2 -> CHECK_SIM_SLOT_2 + else -> CHECK_SIM_SLOT_ALL + } + val status = if (binding!!.sbStatus.isChecked) STATUS_ON else STATUS_OFF + //if (status == STATUS_OFF) { + // throw Exception(getString(R.string.invalid_rule_status)) + //} + + return Rule(ruleId, ruleType, filed, check, value, senderId, smsTemplate, regexReplace, simSlot, status, Date(), senderListSelected, senderLogic) + } + + //检查多重匹配规则是否正确 + private fun checkMultiMatch(ruleStr: String?): Int { + if (TextUtils.isEmpty(ruleStr)) return 0 + + Log.d(TAG, getString(R.string.regex_multi_match)) + val regex = Regex(pattern = getString(R.string.regex_multi_match)) + var lineNum = 1 + val lineArray = ruleStr?.split("\\n".toRegex())?.toTypedArray() + for (line in lineArray!!) { + Log.d(TAG, line) + if (!line.matches(regex)) return lineNum + lineNum++ + } + + return 0 + } + + //检查正则替换填写是否正确 + private fun checkRegexReplace(regexReplace: String?): Int { + if (TextUtils.isEmpty(regexReplace)) return 0 + + var lineNum = 1 + val lineArray = regexReplace?.split("\\n".toRegex())?.toTypedArray() + for (line in lineArray!!) { + val position = line.indexOf("===") + if (position < 1) return lineNum + lineNum++ + } + return 0 + } + + private fun testRule(rule: Rule) { + val dialogTest = View.inflate(requireContext(), R.layout.dialog_rule_test, null) + val tvSimSlot = dialogTest.findViewById(R.id.tv_sim_slot) + val rgSimSlot = dialogTest.findViewById(R.id.rg_sim_slot) + val tvFrom = dialogTest.findViewById(R.id.tv_from) + val etFrom = dialogTest.findViewById(R.id.et_from) + val tvTitle = dialogTest.findViewById(R.id.tv_title) + val etTitle = dialogTest.findViewById(R.id.et_title) + val tvContent = dialogTest.findViewById(R.id.tv_content) + val etContent = dialogTest.findViewById(R.id.et_content) + + if ("app" == ruleType) { + tvSimSlot.visibility = View.GONE + rgSimSlot.visibility = View.GONE + tvTitle.visibility = View.VISIBLE + etTitle.visibility = View.VISIBLE + tvFrom.setText(R.string.test_package_name) + tvContent.setText(R.string.test_inform_content) + } else if ("call" == ruleType) { + tvContent.visibility = View.GONE + etContent.visibility = View.GONE + } + + 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? -> + 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)) + } + + 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() + } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesFragment.kt index bf353e0c..e3451d0a 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesFragment.kt @@ -1,131 +1,129 @@ -package com.idormy.sms.forwarder.fragment - -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.RecyclerView.RecycledViewPool -import com.alibaba.android.vlayout.VirtualLayoutManager -import com.idormy.sms.forwarder.R -import com.idormy.sms.forwarder.adapter.RulePagingAdapter -import com.idormy.sms.forwarder.core.BaseFragment -import com.idormy.sms.forwarder.database.entity.RuleAndSender -import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory -import com.idormy.sms.forwarder.database.viewmodel.RuleViewModel -import com.idormy.sms.forwarder.databinding.FragmentRulesBinding -import com.idormy.sms.forwarder.utils.* -import com.jeremyliao.liveeventbus.LiveEventBus -import com.scwang.smartrefresh.layout.api.RefreshLayout -import com.xuexiang.xpage.annotation.Page -import com.xuexiang.xpage.core.PageOption -import com.xuexiang.xui.utils.ResUtils -import com.xuexiang.xui.widget.actionbar.TitleBar -import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction -import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch - -@Suppress("PropertyName") -@Page(name = "转发规则") -class RulesFragment : BaseFragment(), RulePagingAdapter.OnItemClickListener { - - val TAG: String = RulesFragment::class.java.simpleName - private var adapter = RulePagingAdapter(this) - private val viewModel by viewModels { BaseViewModelFactory(context) } - private var currentType: String = "sms" - - override fun viewBindingInflate( - inflater: LayoutInflater, - container: ViewGroup, - ): FragmentRulesBinding { - return FragmentRulesBinding.inflate(inflater, container, false) - } - - /** - * @return 返回为 null意为不需要导航栏 - */ - override fun initTitle(): TitleBar? { - return null - } - - /** - * 初始化控件 - */ - override fun initViews() { - val virtualLayoutManager = VirtualLayoutManager(requireContext()) - binding!!.recyclerView.layoutManager = virtualLayoutManager - val viewPool = RecycledViewPool() - binding!!.recyclerView.setRecycledViewPool(viewPool) - viewPool.setMaxRecycledViews(0, 10) - - binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.type_param_option)) - binding!!.tabBar.setOnTabClickListener { _, position -> - //XToastUtils.toast("点击了$title--$position") - currentType = when (position) { - 1 -> "call" - 2 -> "app" - else -> "sms" - } - viewModel.setType(currentType) - LiveEventBus.get(EVENT_UPDATE_RULE_TYPE, String::class.java).post(currentType) - adapter.refresh() - binding!!.recyclerView.scrollToPosition(0) - } - } - - override fun initListeners() { - binding!!.recyclerView.adapter = adapter - - //下拉刷新 - binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> - refreshLayout.layout.postDelayed({ - //adapter!!.refresh() - lifecycleScope.launch { - viewModel.setType(currentType).allRules.collectLatest { adapter.submitData(it) } - } - refreshLayout.finishRefresh() - }, 200) - } - - binding!!.refreshLayout.autoRefresh() - } - - override fun onItemClicked(view: View?, item: RuleAndSender) { - Log.e(TAG, item.toString()) - when (view?.id) { - R.id.iv_copy -> { - PageOption.to(RulesEditFragment::class.java) - .setNewActivity(true) - .putLong(KEY_RULE_ID, item.rule.id) - .putString(KEY_RULE_TYPE, item.rule.type) - .putBoolean(KEY_RULE_CLONE, true) - .open(this) - } - R.id.iv_edit -> { - PageOption.to(RulesEditFragment::class.java) - .setNewActivity(true) - .putLong(KEY_RULE_ID, item.rule.id) - .putString(KEY_RULE_TYPE, item.rule.type) - .open(this) - } - R.id.iv_delete -> { - 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? -> - viewModel.delete(item.rule.id) - XToastUtils.success(R.string.delete_rule_toast) - } - .show() - } - else -> {} - } - } - - override fun onItemRemove(view: View?, id: Int) {} - +package com.idormy.sms.forwarder.fragment + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView.RecycledViewPool +import com.alibaba.android.vlayout.VirtualLayoutManager +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.RulePagingAdapter +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.RuleViewModel +import com.idormy.sms.forwarder.databinding.FragmentRulesBinding +import com.idormy.sms.forwarder.utils.* +import com.jeremyliao.liveeventbus.LiveEventBus +import com.scwang.smartrefresh.layout.api.RefreshLayout +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xpage.core.PageOption +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +@Suppress("PropertyName") +@Page(name = "转发规则") +class RulesFragment : BaseFragment(), RulePagingAdapter.OnItemClickListener { + + val TAG: String = RulesFragment::class.java.simpleName + private var adapter = RulePagingAdapter(this) + private val viewModel by viewModels { BaseViewModelFactory(context) } + private var currentType: String = "sms" + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentRulesBinding { + return FragmentRulesBinding.inflate(inflater, container, false) + } + + /** + * @return 返回为 null意为不需要导航栏 + */ + override fun initTitle(): TitleBar? { + return null + } + + /** + * 初始化控件 + */ + override fun initViews() { + val virtualLayoutManager = VirtualLayoutManager(requireContext()) + binding!!.recyclerView.layoutManager = virtualLayoutManager + val viewPool = RecycledViewPool() + binding!!.recyclerView.setRecycledViewPool(viewPool) + viewPool.setMaxRecycledViews(0, 10) + + binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.type_param_option)) + binding!!.tabBar.setOnTabClickListener { _, position -> + //XToastUtils.toast("点击了$title--$position") + currentType = when (position) { + 1 -> "call" + 2 -> "app" + else -> "sms" + } + viewModel.setType(currentType) + LiveEventBus.get(EVENT_UPDATE_RULE_TYPE, String::class.java).post(currentType) + adapter.refresh() + binding!!.recyclerView.scrollToPosition(0) + } + } + + override fun initListeners() { + binding!!.recyclerView.adapter = adapter + + //下拉刷新 + binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> + refreshLayout.layout.postDelayed({ + //adapter!!.refresh() + lifecycleScope.launch { + viewModel.setType(currentType).allRules.collectLatest { adapter.submitData(it) } + } + refreshLayout.finishRefresh() + }, 200) + } + + binding!!.refreshLayout.autoRefresh() + } + + override fun onItemClicked(view: View?, item: Rule) { + when (view?.id) { + R.id.iv_copy -> { + PageOption.to(RulesEditFragment::class.java) + .setNewActivity(true) + .putLong(KEY_RULE_ID, item.id) + .putString(KEY_RULE_TYPE, item.type) + .putBoolean(KEY_RULE_CLONE, true) + .open(this) + } + R.id.iv_edit -> { + PageOption.to(RulesEditFragment::class.java) + .setNewActivity(true) + .putLong(KEY_RULE_ID, item.id) + .putString(KEY_RULE_TYPE, item.type) + .open(this) + } + R.id.iv_delete -> { + 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? -> + viewModel.delete(item.id) + XToastUtils.success(R.string.delete_rule_toast) + } + .show() + } + else -> {} + } + } + + override fun onItemRemove(view: View?, id: Int) {} + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.kt b/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.kt index 78f1a996..d3b606da 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.kt @@ -1,141 +1,141 @@ -package com.idormy.sms.forwarder.service - -import android.annotation.SuppressLint -import android.app.Service -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.os.IBinder -import android.util.Log -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager -import androidx.work.workDataOf -import com.google.gson.Gson -import com.idormy.sms.forwarder.R -import com.idormy.sms.forwarder.core.Core -import com.idormy.sms.forwarder.entity.MsgInfo -import com.idormy.sms.forwarder.utils.BatteryUtils -import com.idormy.sms.forwarder.utils.SettingUtils -import com.idormy.sms.forwarder.utils.Worker -import com.idormy.sms.forwarder.workers.SendWorker -import java.util.* - -@Suppress("DEPRECATION") -class BatteryService : Service() { - - override fun onBind(intent: Intent): IBinder? { - return null - } - - override fun onCreate() { - super.onCreate() - Log.i(TAG, "onCreate--------------") - - //纯客户端模式 - //if (SettingUtils.enablePureClientMode) return - - val batteryFilter = IntentFilter() - batteryFilter.addAction(Intent.ACTION_BATTERY_CHANGED) - registerReceiver(batteryReceiver, batteryFilter) - } - - override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { - Log.i(TAG, "onStartCommand--------------") - return START_STICKY - } - - override fun onDestroy() { - Log.i(TAG, "onDestroy--------------") - super.onDestroy() - - //纯客户端模式 - //if (SettingUtils.enablePureClientMode) return - - unregisterReceiver(batteryReceiver) - } - - // 接收电池信息更新的广播 - private val batteryReceiver: BroadcastReceiver = object : BroadcastReceiver() { - @SuppressLint("DefaultLocale") - override fun onReceive(context: Context, intent: Intent) { - //自动删除N天前的转发记录 - if (SettingUtils.autoCleanLogsDays > 0) { - Log.d(TAG, "自动删除N天前的转发记录") - val cal = Calendar.getInstance() - cal.add(Calendar.DAY_OF_MONTH, 0 - SettingUtils.autoCleanLogsDays) - Core.logs.deleteTimeAgo(cal.timeInMillis) - } - - //电量发生变化 - val levelCur: Int = intent.getIntExtra("level", 0) - val levelPre: Int = SettingUtils.batteryLevelCurrent - if (levelCur != levelPre) { - var msg: String = BatteryUtils.getBatteryInfo(intent).toString() - SettingUtils.batteryLevelCurrent = levelCur - val levelMin: Int = SettingUtils.batteryLevelMin - val levelMax: Int = SettingUtils.batteryLevelMax - if (SettingUtils.batteryLevelOnce && levelMin > 0 && levelPre > levelCur && levelCur <= levelMin) { //电量下降到下限 - msg = String.format(getString(R.string.below_level_min), msg) - sendMessage(context, msg) - return - } else if (SettingUtils.batteryLevelOnce && levelMax > 0 && levelPre < levelCur && levelCur >= levelMax) { //电量上升到上限 - msg = String.format(getString(R.string.over_level_max), msg) - sendMessage(context, msg) - return - } else if (!SettingUtils.batteryLevelOnce && levelMin > 0 && levelPre > levelCur && levelCur == levelMin) { //电量下降到下限 - msg = String.format(getString(R.string.reach_level_min), msg) - sendMessage(context, msg) - return - } else if (!SettingUtils.batteryLevelOnce && levelMax > 0 && levelPre < levelCur && levelCur == levelMax) { //电量上升到上限 - msg = String.format(getString(R.string.reach_level_max), msg) - sendMessage(context, msg) - return - } - } - - //充电状态改变 - val status: Int = intent.getIntExtra("status", 0) - if (SettingUtils.enableBatteryReceiver) { - val oldStatus: Int = SettingUtils.batteryStatus - if (status != oldStatus) { - var msg: String = BatteryUtils.getBatteryInfo(intent).toString() - SettingUtils.batteryStatus = status - msg = getString(R.string.battery_status_changed) + BatteryUtils.getStatus( - oldStatus - ) + " → " + BatteryUtils.getStatus(status) + msg - sendMessage(context, msg) - } - } - } - } - - //发送信息 - private fun sendMessage(context: Context, msg: String) { - Log.i(TAG, msg) - try { - val msgInfo = MsgInfo( - "app", - "88888888", - msg, - Date(), - getString(R.string.battery_status_monitor), - -1 - ) - val request = OneTimeWorkRequestBuilder() - .setInputData( - workDataOf( - Worker.sendMsgInfo to Gson().toJson(msgInfo), - ) - ) - .build() - WorkManager.getInstance(context).enqueue(request) - } catch (e: Exception) { - Log.e(TAG, "getLog e:" + e.message) - } - } - - companion object { - private const val TAG = "BatteryReceiver" - } +package com.idormy.sms.forwarder.service + +import android.annotation.SuppressLint +import android.app.Service +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.IBinder +import android.util.Log +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.workDataOf +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.Core +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.utils.BatteryUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.Worker +import com.idormy.sms.forwarder.workers.SendWorker +import java.util.* + +@Suppress("DEPRECATION") +class BatteryService : Service() { + + override fun onBind(intent: Intent): IBinder? { + return null + } + + override fun onCreate() { + super.onCreate() + Log.i(TAG, "onCreate--------------") + + //纯客户端模式 + //if (SettingUtils.enablePureClientMode) return + + val batteryFilter = IntentFilter() + batteryFilter.addAction(Intent.ACTION_BATTERY_CHANGED) + registerReceiver(batteryReceiver, batteryFilter) + } + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + Log.i(TAG, "onStartCommand--------------") + return START_STICKY + } + + override fun onDestroy() { + Log.i(TAG, "onDestroy--------------") + super.onDestroy() + + //纯客户端模式 + //if (SettingUtils.enablePureClientMode) return + + unregisterReceiver(batteryReceiver) + } + + // 接收电池信息更新的广播 + private val batteryReceiver: BroadcastReceiver = object : BroadcastReceiver() { + @SuppressLint("DefaultLocale") + override fun onReceive(context: Context, intent: Intent) { + //自动删除N天前的转发记录 + if (SettingUtils.autoCleanLogsDays > 0) { + Log.d(TAG, "自动删除N天前的转发记录") + val cal = Calendar.getInstance() + cal.add(Calendar.DAY_OF_MONTH, 0 - SettingUtils.autoCleanLogsDays) + Core.msg.deleteTimeAgo(cal.timeInMillis) + } + + //电量发生变化 + val levelCur: Int = intent.getIntExtra("level", 0) + val levelPre: Int = SettingUtils.batteryLevelCurrent + if (levelCur != levelPre) { + var msg: String = BatteryUtils.getBatteryInfo(intent).toString() + SettingUtils.batteryLevelCurrent = levelCur + val levelMin: Int = SettingUtils.batteryLevelMin + val levelMax: Int = SettingUtils.batteryLevelMax + if (SettingUtils.batteryLevelOnce && levelMin > 0 && levelPre > levelCur && levelCur <= levelMin) { //电量下降到下限 + msg = String.format(getString(R.string.below_level_min), msg) + sendMessage(context, msg) + return + } else if (SettingUtils.batteryLevelOnce && levelMax > 0 && levelPre < levelCur && levelCur >= levelMax) { //电量上升到上限 + msg = String.format(getString(R.string.over_level_max), msg) + sendMessage(context, msg) + return + } else if (!SettingUtils.batteryLevelOnce && levelMin > 0 && levelPre > levelCur && levelCur == levelMin) { //电量下降到下限 + msg = String.format(getString(R.string.reach_level_min), msg) + sendMessage(context, msg) + return + } else if (!SettingUtils.batteryLevelOnce && levelMax > 0 && levelPre < levelCur && levelCur == levelMax) { //电量上升到上限 + msg = String.format(getString(R.string.reach_level_max), msg) + sendMessage(context, msg) + return + } + } + + //充电状态改变 + val status: Int = intent.getIntExtra("status", 0) + if (SettingUtils.enableBatteryReceiver) { + val oldStatus: Int = SettingUtils.batteryStatus + if (status != oldStatus) { + var msg: String = BatteryUtils.getBatteryInfo(intent).toString() + SettingUtils.batteryStatus = status + msg = getString(R.string.battery_status_changed) + BatteryUtils.getStatus( + oldStatus + ) + " → " + BatteryUtils.getStatus(status) + msg + sendMessage(context, msg) + } + } + } + } + + //发送信息 + private fun sendMessage(context: Context, msg: String) { + Log.i(TAG, msg) + try { + val msgInfo = MsgInfo( + "app", + "88888888", + msg, + Date(), + getString(R.string.battery_status_monitor), + -1 + ) + val request = OneTimeWorkRequestBuilder() + .setInputData( + workDataOf( + Worker.sendMsgInfo to Gson().toJson(msgInfo), + ) + ) + .build() + WorkManager.getInstance(context).enqueue(request) + } catch (e: Exception) { + Log.e(TAG, "getLog e:" + e.message) + } + } + + companion object { + private const val TAG = "BatteryReceiver" + } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt index 5c8bb0fe..b766fe49 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt @@ -12,6 +12,9 @@ object Worker { const val sendLogId = "send_log_id" const val sendSbnId = "send_sbn_id" 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_1 = "SIM1" 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() { init { put("sms", getString(R.string.rule_sms)) diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/PhoneUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/PhoneUtils.kt index dc5bc65e..b717827e 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/PhoneUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/PhoneUtils.kt @@ -187,9 +187,11 @@ class PhoneUtils private constructor() { 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" - ) ?: return callInfoList + )) ?: return callInfoList Log.i(TAG, "cursor count:" + cursor.count) // 避免超过总数后循环取出 diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/PrefsHelper.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/PrefsHelper.kt new file mode 100644 index 00000000..e2e41db1 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/PrefsHelper.kt @@ -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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/SendUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/SendUtils.kt index 2e6601cc..eab93ae9 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/SendUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/SendUtils.kt @@ -9,11 +9,11 @@ import com.google.gson.Gson import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender 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.result.SendResponse import com.idormy.sms.forwarder.entity.setting.* 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.UpdateLogsWorker import com.xuexiang.xui.utils.ResUtils @@ -24,13 +24,6 @@ import java.util.* object SendUtils { private const val TAG = "SendUtils" - //批量发送消息 - /*fun sendMsgList(infoList: List, type: String) { - for (msgInfo in infoList) { - sendMsg(msgInfo, type) - } - }*/ - //发送消息 fun sendMsg(msgInfo: MsgInfo) { val request = OneTimeWorkRequestBuilder() @@ -53,9 +46,9 @@ object SendUtils { e.printStackTrace() Date() } - val simInfo: String = item.logs.simInfo + val simInfo: String = item.msg.simInfo 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") if (rematch) { @@ -63,72 +56,73 @@ object SendUtils { 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 { + val sender = rule.senderList[senderIndex] when (sender.type) { TYPE_DINGTALK_GROUP_ROBOT -> { 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 -> { 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 -> { 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 -> { 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 -> { 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 -> { 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 -> { 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 -> { 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 -> { 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 -> { 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 -> { 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 -> { 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 -> { 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 -> { 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 -> { val settingVo = Gson().fromJson(sender.jsonSetting, UrlSchemeSetting::class.java) - UrlSchemeUtils.sendMsg(settingVo, msgInfo, rule, logId) + UrlSchemeUtils.sendMsg(settingVo, msgInfo, rule, senderIndex, logId, msgId) } else -> { 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() + .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) { diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/BarkUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/BarkUtils.kt index e195b8c5..90ef07a9 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/BarkUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/BarkUtils.kt @@ -25,7 +25,9 @@ class BarkUtils { setting: BarkSetting, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { val title: String = if (rule != null) { msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace) @@ -86,18 +88,19 @@ class BarkUtils { override fun onError(e: ApiException) { 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) { Log.i(TAG, response) val resp = Gson().fromJson(response, BarkResult::class.java) - if (resp?.code == 200L) { - SendUtils.updateLogs(logId, 2, response) - } else { - SendUtils.updateLogs(logId, 0, response) - } + val status = if (resp?.code == 200L) 2 else 0 + SendUtils.updateLogs(logId, status, response) + SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId) + } }) @@ -105,7 +108,7 @@ class BarkUtils { } fun sendMsg(setting: BarkSetting, msgInfo: MsgInfo) { - sendMsg(setting, msgInfo, null, null) + sendMsg(setting, msgInfo) } } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/DingtalkGroupRobotUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/DingtalkGroupRobotUtils.kt index a8e95c60..cf2c49c3 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/DingtalkGroupRobotUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/DingtalkGroupRobotUtils.kt @@ -30,7 +30,9 @@ class DingtalkGroupRobotUtils private constructor() { setting: DingtalkGroupRobotSetting, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { val content: String = if (rule != null) { msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) @@ -97,18 +99,18 @@ class DingtalkGroupRobotUtils private constructor() { override fun onError(e: ApiException) { 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) { Log.i(TAG, response) val resp = Gson().fromJson(response, DingtalkResult::class.java) - if (resp?.errcode == 0L) { - SendUtils.updateLogs(logId, 2, response) - } else { - SendUtils.updateLogs(logId, 0, response) - } + val status = if (resp?.errcode == 0L) 2 else 0 + SendUtils.updateLogs(logId, status, response) + SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId) } }) @@ -116,7 +118,7 @@ class DingtalkGroupRobotUtils private constructor() { } fun sendMsg(setting: DingtalkGroupRobotSetting, msgInfo: MsgInfo) { - sendMsg(setting, msgInfo, null, null) + sendMsg(setting, msgInfo) } } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/DingtalkInnerRobotUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/DingtalkInnerRobotUtils.kt index 5cbf9a38..7f82167b 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/DingtalkInnerRobotUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/DingtalkInnerRobotUtils.kt @@ -36,12 +36,14 @@ class DingtalkInnerRobotUtils private constructor() { setting: DingtalkInnerRobotSetting, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { var accessToken: String by SharedPreference("accessToken_" + setting.agentID, "") var expiresIn: Long by SharedPreference("expiresIn_" + setting.agentID, 0L) 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" @@ -93,7 +95,9 @@ class DingtalkInnerRobotUtils private constructor() { override fun onError(e: ApiException) { 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) { @@ -103,9 +107,10 @@ class DingtalkInnerRobotUtils private constructor() { if (!TextUtils.isEmpty(resp?.accessToken)) { accessToken = resp.accessToken.toString() expiresIn = System.currentTimeMillis() + ((resp.expireIn ?: 7200) - 120) * 1000L //提前2分钟过期 - sendTextMsg(setting, msgInfo, rule, logId) + sendTextMsg(setting, msgInfo, rule, senderIndex, logId, msgId) } else { 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, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { val requestUrl = "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend" Log.d(TAG, "requestUrl:$requestUrl") @@ -197,25 +204,25 @@ class DingtalkInnerRobotUtils private constructor() { override fun onError(e: ApiException) { 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) { Log.i(TAG, response) val resp = Gson().fromJson(response, DingtalkInnerRobotResult::class.java) - if (!TextUtils.isEmpty(resp?.processQueryKey)) { - SendUtils.updateLogs(logId, 2, response) - } else { - SendUtils.updateLogs(logId, 0, response) - } + val status = if (!TextUtils.isEmpty(resp?.processQueryKey)) 2 else 0 + SendUtils.updateLogs(logId, status, response) + SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId) } }) } fun sendMsg(setting: DingtalkInnerRobotSetting, msgInfo: MsgInfo) { - sendMsg(setting, msgInfo, null, null) + sendMsg(setting, msgInfo) } } diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/EmailUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/EmailUtils.kt index 2b233f3f..f992233c 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/EmailUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/EmailUtils.kt @@ -21,7 +21,9 @@ class EmailUtils { setting: EmailSetting, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { val title: String = if (rule != null) { msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace) @@ -134,18 +136,21 @@ class EmailUtils { MailSender.getInstance().sendMail(mail, object : MailSender.OnMailSendListener { override fun onError(e: Throwable) { 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() { SendUtils.updateLogs(logId, 2, ResUtils.getString(R.string.request_succeeded)) + SendUtils.senderLogic(2, msgInfo, rule, senderIndex, msgId) } }) } fun sendMsg(setting: EmailSetting, msgInfo: MsgInfo) { - sendMsg(setting, msgInfo, null, null) + sendMsg(setting, msgInfo) } } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/FeishuAppUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/FeishuAppUtils.kt index f8353d0c..b0e07209 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/FeishuAppUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/FeishuAppUtils.kt @@ -28,13 +28,15 @@ class FeishuAppUtils private constructor() { setting: FeishuAppSetting, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { var accessToken: String by SharedPreference("feishu_access_token_" + setting.appId, "") var expiresIn: Long by SharedPreference("feishu_expires_in_" + setting.appId, 0L) 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" @@ -51,7 +53,9 @@ class FeishuAppUtils private constructor() { override fun onError(e: ApiException) { 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) { @@ -61,9 +65,10 @@ class FeishuAppUtils private constructor() { if (!TextUtils.isEmpty(resp?.tenant_access_token)) { accessToken = resp.tenant_access_token.toString() expiresIn = System.currentTimeMillis() + ((resp.expire ?: 7010) - 120) * 1000L //提前2分钟过期 - sendTextMsg(setting, msgInfo, rule, logId) + sendTextMsg(setting, msgInfo, rule, senderIndex, logId, msgId) } else { 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, msgInfo: MsgInfo, 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" Log.d(TAG, "requestUrl:$requestUrl") @@ -117,7 +124,9 @@ class FeishuAppUtils private constructor() { override fun onError(e: ApiException) { 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) { @@ -126,18 +135,16 @@ class FeishuAppUtils private constructor() { //Log.d(TAG, "cipherSuite=" + response.handshake().cipherSuite().toString()) val resp = Gson().fromJson(response, FeishuAppResult::class.java) - if (resp?.code == 0L) { - SendUtils.updateLogs(logId, 2, response) - } else { - SendUtils.updateLogs(logId, 0, response) - } + val status = if (resp?.code == 0L) 2 else 0 + SendUtils.updateLogs(logId, status, response) + SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId) } }) } fun sendMsg(setting: FeishuAppSetting, msgInfo: MsgInfo) { - sendMsg(setting, msgInfo, null, null) + sendMsg(setting, msgInfo) } private fun jsonInnerStr(string: String?): String { diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/FeishuUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/FeishuUtils.kt index 7268c6c0..87baa234 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/FeishuUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/FeishuUtils.kt @@ -83,7 +83,9 @@ class FeishuUtils private constructor() { setting: FeishuSetting, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { val from: String = msgInfo.from val title: String = if (rule != null) { @@ -144,18 +146,18 @@ class FeishuUtils private constructor() { override fun onError(e: ApiException) { 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) { Log.i(TAG, response) val resp = Gson().fromJson(response, FeishuResult::class.java) - if (resp?.code == 0L) { - SendUtils.updateLogs(logId, 2, response) - } else { - SendUtils.updateLogs(logId, 0, response) - } + val status = if (resp?.code == 0L) 2 else 0 + SendUtils.updateLogs(logId, status, response) + SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId) } }) @@ -181,7 +183,7 @@ class FeishuUtils private constructor() { } fun sendMsg(setting: FeishuSetting, msgInfo: MsgInfo) { - sendMsg(setting, msgInfo, null, null) + sendMsg(setting, msgInfo) } } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/GotifyUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/GotifyUtils.kt index 52b797e7..ab5e6990 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/GotifyUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/GotifyUtils.kt @@ -23,7 +23,9 @@ class GotifyUtils { setting: GotifySetting, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { val title: String = if (rule != null) { msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace) @@ -64,18 +66,18 @@ class GotifyUtils { override fun onError(e: ApiException) { 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) { Log.i(TAG, response) val resp = Gson().fromJson(response, GotifyResult::class.java) - if (resp?.id != null) { - SendUtils.updateLogs(logId, 2, response) - } else { - SendUtils.updateLogs(logId, 0, response) - } + val status = if (resp?.id != null) 2 else 0 + SendUtils.updateLogs(logId, status, response) + SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId) } }) @@ -83,7 +85,7 @@ class GotifyUtils { } fun sendMsg(setting: GotifySetting, msgInfo: MsgInfo) { - sendMsg(setting, msgInfo, null, null) + sendMsg(setting, msgInfo) } } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/PushplusUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/PushplusUtils.kt index 4d5a0644..3289231c 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/PushplusUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/PushplusUtils.kt @@ -27,7 +27,9 @@ class PushplusUtils private constructor() { setting: PushplusSetting, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { val title: String = if (rule != null) { msgInfo.getTitleForSend(setting.titleTemplate.toString(), rule.regexReplace) @@ -79,18 +81,18 @@ class PushplusUtils private constructor() { override fun onError(e: ApiException) { 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) { Log.i(TAG, response) val resp = Gson().fromJson(response, PushplusResult::class.java) - if (resp?.code == 200L) { - SendUtils.updateLogs(logId, 2, response) - } else { - SendUtils.updateLogs(logId, 0, response) - } + val status = if (resp?.code == 200L) 2 else 0 + SendUtils.updateLogs(logId, status, response) + SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId) } }) @@ -98,7 +100,7 @@ class PushplusUtils private constructor() { } fun sendMsg(setting: PushplusSetting, msgInfo: MsgInfo) { - sendMsg(setting, msgInfo, null, null) + sendMsg(setting, msgInfo) } } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/ServerchanUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/ServerchanUtils.kt index 5269345d..67018ece 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/ServerchanUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/ServerchanUtils.kt @@ -24,7 +24,9 @@ class ServerchanUtils { setting: ServerchanSetting, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { val title: String = if (rule != null) { msgInfo.getTitleForSend(setting.titleTemplate.toString(), rule.regexReplace) @@ -58,18 +60,17 @@ class ServerchanUtils { override fun onError(e: ApiException) { 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) { Log.i(TAG, response) - val resp = Gson().fromJson(response, ServerchanResult::class.java) - if (resp?.code == 0L) { - SendUtils.updateLogs(logId, 2, response) - } else { - SendUtils.updateLogs(logId, 0, response) - } + val status = if (resp?.code == 0L) 2 else 0 + SendUtils.updateLogs(logId, status, response) + SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId) } }) @@ -77,7 +78,7 @@ class ServerchanUtils { } fun sendMsg(setting: ServerchanSetting, msgInfo: MsgInfo) { - sendMsg(setting, msgInfo, null, null) + sendMsg(setting, msgInfo) } } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/SmsUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/SmsUtils.kt index ffaa1c45..1bac601b 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/SmsUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/SmsUtils.kt @@ -26,16 +26,20 @@ class SmsUtils { setting: SmsSetting, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { //仅当无网络时启用 && 判断是否真实有网络 if (setting.onlyNoNetwork == true && NetworkUtils.isHaveInternet() && NetworkUtils.isAvailableByPing()) { SendUtils.updateLogs(logId, 0, ResUtils.getString(R.string.OnlyNoNetwork)) + SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId) return } 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.senderLogic(0, msgInfo, rule, senderIndex, msgId) return } @@ -62,13 +66,15 @@ class SmsUtils { val res: String? = PhoneUtils.sendSms(mSubscriptionId, mobiles, content) if (res == null) { SendUtils.updateLogs(logId, 2, ResUtils.getString(R.string.request_succeeded)) + SendUtils.senderLogic(2, msgInfo, rule, senderIndex, msgId) } else { SendUtils.updateLogs(logId, 0, res) + SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId) } } fun sendMsg(setting: SmsSetting, msgInfo: MsgInfo) { - sendMsg(setting, msgInfo, null, null) + sendMsg(setting, msgInfo) } } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/TelegramUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/TelegramUtils.kt index 6db91de8..b87ced12 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/TelegramUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/TelegramUtils.kt @@ -30,7 +30,9 @@ class TelegramUtils private constructor() { setting: TelegramSetting, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { if (setting.method == null || setting.method == "POST") { msgInfo.content = htmlEncode(msgInfo.content) @@ -116,18 +118,18 @@ class TelegramUtils private constructor() { override fun onError(e: ApiException) { 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) { Log.i(TAG, response) val resp = Gson().fromJson(response, TelegramResult::class.java) - if (resp?.ok == true) { - SendUtils.updateLogs(logId, 2, response) - } else { - SendUtils.updateLogs(logId, 0, response) - } + val status = if (resp?.ok == true) 2 else 0 + SendUtils.updateLogs(logId, status, response) + SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId) } }) @@ -135,7 +137,7 @@ class TelegramUtils private constructor() { } fun sendMsg(setting: TelegramSetting, msgInfo: MsgInfo) { - sendMsg(setting, msgInfo, null, null) + sendMsg(setting, msgInfo) } private fun htmlEncode(source: String?): String { diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/UrlSchemeUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/UrlSchemeUtils.kt index a5760127..9e404b4e 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/UrlSchemeUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/UrlSchemeUtils.kt @@ -26,7 +26,9 @@ class UrlSchemeUtils private constructor() { setting: UrlSchemeSetting, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { val from: String = msgInfo.from val content: String = if (rule != null) { @@ -62,16 +64,18 @@ class UrlSchemeUtils private constructor() { try { XUtil.getContext().startActivity(intent) SendUtils.updateLogs(logId, 2, "调用成功") + SendUtils.senderLogic(2, msgInfo, rule, senderIndex, msgId) } catch (e: Exception) { e.printStackTrace() Log.e(TAG, e.message.toString()) SendUtils.updateLogs(logId, 0, e.message.toString()) + SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId) } } fun sendMsg(setting: UrlSchemeSetting, msgInfo: MsgInfo) { - sendMsg(setting, msgInfo, null, null) + sendMsg(setting, msgInfo) } } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WebhookUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WebhookUtils.kt index 05906d24..90c9cb6d 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WebhookUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WebhookUtils.kt @@ -32,7 +32,9 @@ class WebhookUtils { setting: WebhookSetting, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { val from: String = msgInfo.from val content: String = if (rule != null) { @@ -183,12 +185,15 @@ class WebhookUtils { override fun onError(e: ApiException) { 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) { Log.i(TAG, 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) { - sendMsg(setting, msgInfo, null, null) + sendMsg(setting, msgInfo) } } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkAgentUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkAgentUtils.kt index 875be7ff..8beab397 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkAgentUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkAgentUtils.kt @@ -36,13 +36,15 @@ class WeworkAgentUtils private constructor() { setting: WeworkAgentSetting, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { var accessToken: String by SharedPreference("access_token_" + setting.agentID, "") var expiresIn: Long by SharedPreference("expires_in_" + setting.agentID, 0L) 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?" @@ -90,7 +92,9 @@ class WeworkAgentUtils private constructor() { override fun onError(e: ApiException) { 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) { @@ -100,9 +104,10 @@ class WeworkAgentUtils private constructor() { if (resp?.errcode == 0L) { accessToken = resp.access_token.toString() expiresIn = System.currentTimeMillis() + ((resp.expires_in ?: 7200) - 120) * 1000L //提前2分钟过期 - sendTextMsg(setting, msgInfo, rule, logId) + sendTextMsg(setting, msgInfo, rule, senderIndex, logId, msgId) } else { 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, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { val content: String = if (rule != null) { msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) @@ -181,25 +188,25 @@ class WeworkAgentUtils private constructor() { override fun onError(e: ApiException) { 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) { Log.i(TAG, response) val resp = Gson().fromJson(response, DingtalkResult::class.java) - if (resp?.errcode == 0L) { - SendUtils.updateLogs(logId, 2, response) - } else { - SendUtils.updateLogs(logId, 0, response) - } + val status = if (resp?.errcode == 0L) 2 else 0 + SendUtils.updateLogs(logId, status, response) + SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId) } }) } fun sendMsg(setting: WeworkAgentSetting, msgInfo: MsgInfo) { - sendMsg(setting, msgInfo, null, null) + sendMsg(setting, msgInfo) } } diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkRobotUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkRobotUtils.kt index 8048fafe..5a43fc40 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkRobotUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkRobotUtils.kt @@ -23,7 +23,9 @@ class WeworkRobotUtils private constructor() { setting: WeworkRobotSetting, msgInfo: MsgInfo, rule: Rule?, - logId: Long?, + senderIndex: Int = 0, + logId: Long = 0L, + msgId: Long = 0L ) { val content: String = if (rule != null) { msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) @@ -58,17 +60,16 @@ class WeworkRobotUtils private constructor() { override fun onError(e: ApiException) { Log.e(TAG, e.detailMessage) SendUtils.updateLogs(logId, 0, e.displayMessage) + SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId) } override fun onSuccess(response: String) { Log.i(TAG, response) val resp = Gson().fromJson(response, WeworkRobotResult::class.java) - if (resp?.errcode == 0L) { - SendUtils.updateLogs(logId, 2, response) - } else { - SendUtils.updateLogs(logId, 0, response) - } + val status = if (resp?.errcode == 0L) 2 else 0 + SendUtils.updateLogs(logId, status, response) + SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId) } }) @@ -76,7 +77,7 @@ class WeworkRobotUtils private constructor() { } fun sendMsg(setting: WeworkRobotSetting, msgInfo: MsgInfo) { - sendMsg(setting, msgInfo, null, null) + sendMsg(setting, msgInfo) } } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/workers/SendLogicWorker.kt b/app/src/main/java/com/idormy/sms/forwarder/workers/SendLogicWorker.kt new file mode 100644 index 00000000..301ccb18 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/workers/SendLogicWorker.kt @@ -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() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/workers/SendWorker.kt b/app/src/main/java/com/idormy/sms/forwarder/workers/SendWorker.kt index e2c104e5..1e488764 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/workers/SendWorker.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/workers/SendWorker.kt @@ -9,7 +9,8 @@ import androidx.work.workDataOf 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.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.utils.* import com.xuexiang.xutil.security.CipherUtils @@ -55,7 +56,6 @@ class SendWorker( Log.e("SendWorker", "免打扰(禁用转发)时间段") return@withContext Result.failure(workDataOf("send" to "failed")) } - } val msgInfoJson = inputData.getString(Worker.sendMsgInfo) @@ -75,18 +75,22 @@ class SendWorker( //【注意】卡槽id:-1=获取失败、0=卡槽1、1=卡槽2,但是 Rule 表里存的是 SIM1/SIM2 val simSlot = "SIM" + (msgInfo.simSlot + 1) - val ruleList: List = Core.rule.getRuleAndSender(msgInfo.type, 1, simSlot) + val ruleList: List = Core.rule.getRuleList(msgInfo.type, 1, simSlot) if (ruleList.isEmpty()) { 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) { - if (!rule.rule.checkMsg(msgInfo)) continue - val log = Logs( - 0, msgInfo.type, msgInfo.from, msgInfo.content, rule.rule.id, msgInfo.simInfo, msgInfo.subId - ) + Log.d("SendWorker", rule.toString()) + if (!rule.checkMsg(msgInfo)) continue + + val sender = rule.senderList[0] + val log = Logs(0, msgInfo.type, msgId, rule.id, sender.id) val logId = Core.logs.insert(log) - SendUtils.sendMsgSender(msgInfo, rule.rule, rule.sender, logId) + SendUtils.sendMsgSender(msgInfo, rule, 0, logId, msgId) } } catch (e: Exception) { diff --git a/app/src/main/res/layout/adapter_logs_card_view_list_item.xml b/app/src/main/res/layout/adapter_logs_card_view_list_item.xml index 8d2cbd9e..bf80a7e2 100644 --- a/app/src/main/res/layout/adapter_logs_card_view_list_item.xml +++ b/app/src/main/res/layout/adapter_logs_card_view_list_item.xml @@ -1,92 +1,85 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/adapter_rules_card_view_list_item.xml b/app/src/main/res/layout/adapter_rules_card_view_list_item.xml index d13aa88a..594d0162 100644 --- a/app/src/main/res/layout/adapter_rules_card_view_list_item.xml +++ b/app/src/main/res/layout/adapter_rules_card_view_list_item.xml @@ -1,124 +1,99 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_rules_edit.xml b/app/src/main/res/layout/fragment_rules_edit.xml index f967131b..7b89778a 100644 --- a/app/src/main/res/layout/fragment_rules_edit.xml +++ b/app/src/main/res/layout/fragment_rules_edit.xml @@ -1,506 +1,525 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_senders_webhook.xml b/app/src/main/res/layout/fragment_senders_webhook.xml index 2bbb3437..2a9d335a 100644 --- a/app/src/main/res/layout/fragment_senders_webhook.xml +++ b/app/src/main/res/layout/fragment_senders_webhook.xml @@ -1,262 +1,277 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_add_sender.xml b/app/src/main/res/layout/item_add_sender.xml new file mode 100644 index 00000000..190ae6ef --- /dev/null +++ b/app/src/main/res/layout/item_add_sender.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_logs.xml b/app/src/main/res/layout/item_logs.xml new file mode 100644 index 00000000..55f88ccc --- /dev/null +++ b/app/src/main/res/layout/item_logs.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_sender.xml b/app/src/main/res/layout/item_sender.xml new file mode 100644 index 00000000..55f88ccc --- /dev/null +++ b/app/src/main/res/layout/item_sender.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d12a0ae7..0b625bb0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -223,6 +223,10 @@ 测试模拟的APP包名 测试模拟的通知标题 测试模拟的通知内容 + 执行逻辑 + 全部执行 + 失败即止 + 成功即止 匹配卡槽 匹配字段 手机号 @@ -841,6 +845,7 @@ Frpc运行失败 删除成功 【注意】该发送通道已经禁用,其关联的规则即便匹配上也不会发送! + 【注意】该发送通道已经在列表中,无需重复添加! 本地呼叫: 远程发短信: 清除 @@ -942,4 +947,6 @@ 复制公钥 SM4密钥 客户端/服务端交互采用SM4加解密 + + 删除发送通道