mirror of
https://github.com/pppscn/SmsForwarder
synced 2025-08-03 01:17:41 +08:00
Compare commits
131 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
751f1b3163 | ||
![]() |
45dc37aea9 | ||
![]() |
de9509fead | ||
![]() |
114052d1c6 | ||
![]() |
0391868790 | ||
![]() |
44728b858a | ||
![]() |
23acf01c61 | ||
![]() |
b733fc2d4c | ||
![]() |
992cabe323 | ||
![]() |
8b37862121 | ||
![]() |
f49310027e | ||
![]() |
cc86c0718f | ||
![]() |
2a910fe504 | ||
![]() |
0a3f9f53f1 | ||
![]() |
a77f630524 | ||
![]() |
7c33566696 | ||
![]() |
f029048d09 | ||
![]() |
b05d856f26 | ||
![]() |
3a341f6c4e | ||
![]() |
c86f20c6de | ||
![]() |
aa8ab646f5 | ||
![]() |
8e7c425fdc | ||
![]() |
dc3726d0de | ||
![]() |
3adfffee4f | ||
![]() |
c56ac0e425 | ||
![]() |
3cc2311600 | ||
![]() |
5c19c10121 | ||
![]() |
785a3a2364 | ||
![]() |
66831865cb | ||
![]() |
d4ed59b37f | ||
![]() |
573174feea | ||
![]() |
a83f0da45a | ||
![]() |
b35ba17beb | ||
![]() |
ff83a8a5f7 | ||
![]() |
9436e3498b | ||
![]() |
e3df9bada6 | ||
![]() |
3fccec54b1 | ||
![]() |
9e28c2922c | ||
![]() |
9c03e30e5d | ||
![]() |
d5f476bc32 | ||
![]() |
f7d2544ee9 | ||
![]() |
8cc4c76735 | ||
![]() |
9dbb2572fa | ||
![]() |
505c719b4a | ||
![]() |
40c8c5be6e | ||
![]() |
b80b1f9620 | ||
![]() |
4e8d8283c2 | ||
![]() |
894155df22 | ||
![]() |
b0e1ce76cf | ||
![]() |
6e59db0159 | ||
![]() |
0308030a7d | ||
![]() |
50d286294d | ||
![]() |
7597f68433 | ||
![]() |
073bae0db8 | ||
![]() |
ac45ff6245 | ||
![]() |
01aeb8d698 | ||
![]() |
7e6499be1b | ||
![]() |
5eed98121e | ||
![]() |
9107fa4589 | ||
![]() |
27fa30a5e1 | ||
![]() |
866f90831f | ||
![]() |
a53fa6db12 | ||
![]() |
40ee077ea7 | ||
![]() |
29ca4d3300 | ||
![]() |
65581cf4dc | ||
![]() |
89fcf65b72 | ||
![]() |
c6d4b9a7f5 | ||
![]() |
1bd8cb04f8 | ||
![]() |
5e0537e1e2 | ||
![]() |
75b0e48815 | ||
![]() |
356d16a417 | ||
![]() |
ffb54194e2 | ||
![]() |
750c339411 | ||
![]() |
38ffe3e281 | ||
![]() |
971e262300 | ||
![]() |
9e1b9abc75 | ||
![]() |
9bf46c19ae | ||
![]() |
3968759b2e | ||
![]() |
11355f2b55 | ||
![]() |
a5d263944f | ||
![]() |
e131690ac7 | ||
![]() |
75b356246c | ||
![]() |
8cefd5fded | ||
![]() |
c20350da13 | ||
![]() |
41b23613a6 | ||
![]() |
b02c03a092 | ||
![]() |
275c2667c1 | ||
![]() |
86d0c35ea9 | ||
![]() |
a4aade94c5 | ||
![]() |
1ee0c12f42 | ||
![]() |
ad79f5e445 | ||
![]() |
e4432cb037 | ||
![]() |
22474b2295 | ||
![]() |
d3899d9404 | ||
![]() |
8540822b67 | ||
![]() |
0650e8b9bf | ||
![]() |
6097cb9258 | ||
![]() |
5125ea2eda | ||
![]() |
a1f1456dfc | ||
![]() |
e5ed94019f | ||
![]() |
838009b938 | ||
![]() |
09d3e3f785 | ||
![]() |
da30f0dda2 | ||
![]() |
bc6131ffd8 | ||
![]() |
0036044d54 | ||
![]() |
f0ef8e9dcd | ||
![]() |
65ea7bcf0c | ||
![]() |
2b091bbefe | ||
![]() |
06bc7adbe4 | ||
![]() |
c4f298ebe8 | ||
![]() |
e8012e8e21 | ||
![]() |
3fc0a953e7 | ||
![]() |
26bd64a1e2 | ||
![]() |
132dcd808c | ||
![]() |
117a2e42d9 | ||
![]() |
9aeca6f3f6 | ||
![]() |
b6c98e7f33 | ||
![]() |
e696125611 | ||
![]() |
d8909ad676 | ||
![]() |
ccd98ab2da | ||
![]() |
f4f6080b9a | ||
![]() |
9379882ca3 | ||
![]() |
ef115f1b96 | ||
![]() |
098a8f1a4c | ||
![]() |
d6623902f3 | ||
![]() |
eed99b5baf | ||
![]() |
a27da6b1d3 | ||
![]() |
dba756c2f4 | ||
![]() |
6ad1763d38 | ||
![]() |
0b7ecc4096 | ||
![]() |
b930209772 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1,3 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
custom: ["https://foruda.gitee.com/images/1705068554951915754/89c6e226_16273.png", "https://afdian.net/a/pppscn", "https://github.com/pppscn/SmsForwarder/wiki/%E6%89%93%E8%B5%8F%E5%90%8D%E5%8D%95", "https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427"]
|
||||
custom: ["https://foruda.gitee.com/images/1730529431184709105/20bfc86c_16273.gif", "https://ifdian.net/a/pppscn", "https://github.com/pppscn/SmsForwarder/wiki/%E6%89%93%E8%B5%8F%E5%90%8D%E5%8D%95", "https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427"]
|
||||
|
87
.github/workflows/Weekly_Build.yml
vendored
87
.github/workflows/Weekly_Build.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
||||
- name: Delete Weekly Build
|
||||
uses: Mattraks/delete-workflow-runs@v2
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
token: ${{ secrets.TOKEN }}
|
||||
repository: ${{ github.repository }}
|
||||
retain_days: 0 # 全部删除只留正在跑的一条
|
||||
keep_minimum_runs: 0 # 全部删除只留正在跑的一条
|
||||
@ -45,39 +45,84 @@ jobs:
|
||||
# 打包release
|
||||
- name: Build with Gradle
|
||||
run: bash ./gradlew assembleRelease
|
||||
# 存档打包的文件,以便后续上传,TODO: 看起来有点笨,有没有更好的方法?
|
||||
- name: Init APP Version Name
|
||||
# 自动发布预览计划
|
||||
- name: Parse output-metadata.json and upload APKs to XUpdate
|
||||
run: |
|
||||
echo "ver_name=$(grep -m 1 '\"versionName\":' ${{ env.output }}//output-metadata.json | grep -oP '\"versionName\": \"\K[^\"]+')" >> $GITHUB_ENV
|
||||
echo "ver_code=$(grep -m 1 '\"versionCode\":' ${{ env.output }}//output-metadata.json | grep -oP '\"versionCode\": \K\d+' | tail -c 4)" >> $GITHUB_ENV
|
||||
echo "ver_date=$(grep -m 1 '\"outputFile\":' ${{ env.output }}//output-metadata.json | grep -oP '\"outputFile\": \"[^\"]+' | grep -oP '\d{8}')" >> $GITHUB_ENV
|
||||
metadata_file="${{ env.output }}/output-metadata.json"
|
||||
applicationId=$(jq -r '.applicationId' "$metadata_file")
|
||||
buildDate=$(jq -r '.buildDate' "$metadata_file")
|
||||
buildTime=$(jq -r '.buildTime' "$metadata_file")
|
||||
gitCommitId=$(jq -r '.gitCommitId' "$metadata_file")
|
||||
|
||||
# 遍历 elements,并从 outputFile 中提取 versionName 和 versionCode
|
||||
jq -r '.elements | sort_by(.outputFile) | .[].outputFile' "$metadata_file" |
|
||||
while IFS= read -r apk; do
|
||||
echo "APK: $apk"
|
||||
# 使用正则表达式从文件名中提取 versionName、versionCode 和 ABI
|
||||
if [[ $apk =~ SmsF_([^_]+)_([^_]+)_(.+)_release.apk ]]; then
|
||||
versionName="${BASH_REMATCH[1]}"
|
||||
versionCode="${BASH_REMATCH[2]}"
|
||||
abi="${BASH_REMATCH[3]}"
|
||||
echo "ver_name=$versionName" >> $GITHUB_ENV
|
||||
echo "ver_code=${versionCode: -3}" >> $GITHUB_ENV
|
||||
|
||||
response=$(curl --retry 3 -X POST -H "Cache-Control: no-cache" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Cookie: xupdate_token=${{ secrets.XUPDATE_TOKEN }}" \
|
||||
-H "X-Token: ${{ secrets.X_TOKEN }}" \
|
||||
-d "{\"appKey\":\"${applicationId}\",\"versionName\":\"${versionName}\",\"versionCode\":\"${versionCode}\",\"modifyContent\":\"\",\"updateStatus\":1,\"gitCommitId\":\"${gitCommitId}\",\"buildTime\":\"${buildTime}\",\"uploadTime\":\"${buildTime}\"}" \
|
||||
${{ secrets.URL_ADD_VERSION }})
|
||||
|
||||
version_id=$(echo $response | grep -oP '"versionId":\s*\K\d+') # 提取versionId
|
||||
echo "versionId: $version_id"
|
||||
|
||||
if [[ $version_id =~ ^[0-9]+$ ]]; then
|
||||
curl --retry 3 -X POST -H "Cache-Control: no-cache" \
|
||||
-H "Content-Type: multipart/form-data" \
|
||||
-H "Cookie: xupdate_token=${{ secrets.XUPDATE_TOKEN }}" \
|
||||
-H "X-Token: ${{ secrets.X_TOKEN }}" \
|
||||
-F "file=@${{ env.output }}/${apk};filename=${apk}" \
|
||||
-F "versionId=${version_id}" \
|
||||
${{ secrets.URL_UPLOAD_APK }}
|
||||
|
||||
# If upload is successful, set success to true to exit the retry loop
|
||||
if [[ $? -eq 0 ]]; then
|
||||
success=true
|
||||
fi
|
||||
else
|
||||
echo "Error: version_id is not a valid number. skip upload apk"
|
||||
fi
|
||||
|
||||
fi
|
||||
done
|
||||
# 存档打包的文件,以便后续上传,TODO: 看起来有点笨,有没有更好的方法?
|
||||
- name: Upload App To Artifact universal
|
||||
if: success () || failure ()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "SmsF_${{ env.ver_name }}_100${{ env.ver_code }}_universal_${{ env.ver_date }}_release.apk"
|
||||
path: "${{ env.output }}//SmsF_*_universal_*.apk"
|
||||
name: "SmsF_${{ env.ver_name }}_100${{ env.ver_code }}_universal_release.apk"
|
||||
path: "${{ env.output }}/SmsF_*_universal_release.apk"
|
||||
- name: Upload App To Artifact armeabi-v7a
|
||||
if: success () || failure ()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "SmsF_${{ env.ver_name }}_200${{ env.ver_code }}_armeabi-v7a_${{ env.ver_date }}_release.apk"
|
||||
path: "${{ env.output }}//SmsF_*_armeabi-v7a_*.apk"
|
||||
name: "SmsF_${{ env.ver_name }}_200${{ env.ver_code }}_armeabi-v7a_release.apk"
|
||||
path: "${{ env.output }}/SmsF_*_armeabi-v7a_release.apk"
|
||||
- name: Upload App To Artifact arm64-v8a
|
||||
if: success () || failure ()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "SmsF_${{ env.ver_name }}_300${{ env.ver_code }}_arm64-v8a_${{ env.ver_date }}_release.apk"
|
||||
path: "${{ env.output }}//SmsF_*_arm64-v8a_*.apk"
|
||||
name: "SmsF_${{ env.ver_name }}_300${{ env.ver_code }}_arm64-v8a_release.apk"
|
||||
path: "${{ env.output }}/SmsF_*_arm64-v8a_release.apk"
|
||||
- name: Upload App To Artifact x86
|
||||
if: success () || failure ()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "SmsF_${{ env.ver_name }}_400${{ env.ver_code }}_x86_${{ env.ver_date }}_release.apk"
|
||||
path: "${{ env.output }}//SmsF_*_x86_${{ env.ver_date }}_*.apk"
|
||||
name: "SmsF_${{ env.ver_name }}_400${{ env.ver_code }}_x86_release.apk"
|
||||
path: "${{ env.output }}/SmsF_*_x86_release.apk"
|
||||
- name: Upload App To Artifact x86_64
|
||||
if: success () || failure ()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "SmsF_${{ env.ver_name }}_500${{ env.ver_code }}_x86_64_${{ env.ver_date }}_release.apk"
|
||||
path: "${{ env.output }}//SmsF_*_x86_64_${{ env.ver_date }}_*.apk"
|
||||
name: "SmsF_${{ env.ver_name }}_500${{ env.ver_code }}_x86_64_release.apk"
|
||||
path: "${{ env.output }}/SmsF_*_x86_64_release.apk"
|
57
.gitignore
vendored
57
.gitignore
vendored
@ -1,38 +1,31 @@
|
||||
*.bak
|
||||
*.classpath
|
||||
*.iml
|
||||
.gradle
|
||||
/LocalRepository
|
||||
/keystores
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/codeStyles
|
||||
/.idea/inspectionProfiles
|
||||
/.idea/libraries
|
||||
/.idea/dictionaries
|
||||
/.idea/markdown-navigator
|
||||
/.idea/*.xml
|
||||
*.project
|
||||
*/*.classpath
|
||||
*/*.project
|
||||
*/.settings/*
|
||||
.DS_Store
|
||||
.externalNativeBuild
|
||||
.gradle
|
||||
.settings/*
|
||||
/*.txt
|
||||
/.idea
|
||||
/LocalRepository
|
||||
/app/build
|
||||
/app/debug
|
||||
/app/mapping.txt
|
||||
/app/pppscn.jks
|
||||
/app/release
|
||||
/app/seeds.txt
|
||||
/app/src/test
|
||||
/app/unused.txt
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
*.project
|
||||
*/*.project
|
||||
*.classpath
|
||||
*/*.classpath
|
||||
.settings/*
|
||||
*/.settings/*
|
||||
/app/pppscn.jks
|
||||
/app/release/*
|
||||
/app/build/*
|
||||
/psd
|
||||
/keystore/keystore.properties
|
||||
/app/release
|
||||
/keystore
|
||||
*.bak
|
||||
/pic/working_principle.drawio
|
||||
/app/debug
|
||||
/.idea
|
||||
/app/mapping.txt
|
||||
/app/seeds.txt
|
||||
/app/unused.txt
|
||||
/local.properties
|
||||
/pic/*.bkp
|
||||
/*.txt
|
||||
/pic/*.drawio
|
||||
/pic/*.vsdx
|
||||
/psd
|
||||
/pic/*.psd
|
||||
|
34
README.md
34
README.md
@ -14,13 +14,17 @@
|
||||
|
||||
包括主动控制服务端与客户端,让你轻松远程发短信、查短信、查通话、查话簿、查电量等。(V3.0 新增)
|
||||
|
||||
自动任务・快捷指令,轻松自动化,助您事半功倍,更多时间享受亲情陪伴!(v3.3 新增)
|
||||
|
||||
> 注意:从`2022-06-06`开始,原`Java版`的代码归档到`v2.x`分支,不再更新!
|
||||
|
||||
> 1、从`v2.x`到`v3.x`不是简单的功能迭代,采用`kotlin`全新重构了(不是单纯的迁移代码,起初我也是这么认为的),由于我是第一次使用`kotlin`开发(Java版也是第一次),到处踩坑,每一行代码都是度娘手把手教会我的,所以`v3.x`版本可能一开始并不稳定。另外,眼睛葡萄膜炎还没好,晚上不敢肝,中间停摆了个把月,进度缓慢,历时2个月终于让`V3.x`顺产了!
|
||||
> `v3.x` 适配 Android 4.4 ~ 13.0
|
||||
|
||||
> 2、如果目前`v2.x`用的好好的没必要升级(之前也是这么建议大家的,没必要每版必跟,除非你急需新功能)
|
||||
> `加入SmsF预览体验计划`(在线更新每周构建版,率先体验新版&修复BUG)
|
||||
|
||||
> 3、`v3.x` 适配 Android 4.4 ~ 13.0
|
||||
**升级操作提示:**
|
||||
- `加入SmsF预览体验计划`后在线更新(`关于软件`页面开启,`v3.3.0_240305+`适用)
|
||||
- 手动下载:https://github.com/pppscn/SmsForwarder/actions/workflows/Weekly_Build.yml
|
||||
|
||||
--------
|
||||
|
||||
@ -34,7 +38,7 @@
|
||||
|
||||
* 隐私声明: **SmsForwarder 不会收集任何您的隐私数据!!!** APP启动时发送版本信息发送到友盟统计;手动检查新版本时发送版本号用于检查新版本;除此之外,没有任何数据!!!
|
||||
|
||||
* 防诈提醒: `SmsForwarder`完全免费开源,请您在 [打赏](https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427) 前务必确认是否出于自愿?本项目不参与任何刷单返利担保!**请您远离刷单返利陷阱,谨防网络诈骗!**
|
||||
* 防诈提醒: `SmsForwarder`完全免费开源,请您在 [打赏](https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427) 前务必确认是否出于自愿?本项目不参与任何刷单返利担保!**请您远离刷单返利陷阱,谨防网络诈骗!**
|
||||
|
||||
--------
|
||||
|
||||
@ -46,7 +50,7 @@
|
||||
|
||||
## 界面预览:
|
||||
|
||||

|
||||

|
||||
|
||||
更多截图参见 https://github.com/pppscn/SmsForwarder/wiki
|
||||
|
||||
@ -68,7 +72,7 @@
|
||||
|
||||
> ⚠ Gitee Wiki:https://gitee.com/pp/SmsForwarder/wikis/pages
|
||||
|
||||

|
||||

|
||||
|
||||
--------
|
||||
|
||||
@ -77,17 +81,15 @@
|
||||
+ 提交issues 或 pr
|
||||
+ 加入交流群(群内都是机油互帮互助,禁止发任何与SmsForwarder使用无关的内容)
|
||||
|
||||
| TG Group |
|
||||
| :--: |
|
||||
|  |
|
||||
| TG Group |
|
||||
|:---------------------------------------------------:|
|
||||
|  |
|
||||
| [+QBZgnL_fxYM0NjE9](https://t.me/+QBZgnL_fxYM0NjE9) |
|
||||
|
||||
## 感谢
|
||||
|
||||
> [感谢所有赞助本项目的热心网友 --> 打赏名单](https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427)
|
||||
|
||||
> 【特别提醒】为了规避收款码被盗用的风险,即日起加入 [爱发电](https://afdian.net/a/pppscn),原来的收款账户已被冻结,赞助码全部作废!!!AT.2024-01-08
|
||||
|
||||
> 本项目得到以下项目的支持与帮助,在此表示衷心的感谢!
|
||||
|
||||
+ https://github.com/xiaoyuanhost/TranspondSms (项目原型)
|
||||
@ -99,13 +101,19 @@
|
||||
+ https://github.com/yanzhenjie/AndServer (HttpServer)
|
||||
+ https://github.com/jenly1314/Location (Location)
|
||||
+ https://gitee.com/xuankaicat/kmnkt (socket通信)
|
||||
+ [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg?_ga=2.126618957.1361252949.1638261367-1417196221.1635638144&_gl=1*1pfl3dq*_ga*MTQxNzE5NjIyMS4xNjM1NjM4MTQ0*_ga_V0XZL7QHEB*MTYzODMzMjA4OC43LjAuMTYzODMzMjA5Ny4w" alt="GitHub license" style="width:96px" width="96" />](https://jb.gg/OpenSourceSupport) (License Certificate for JetBrains All Products Pack)
|
||||
+ [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" alt="GitHub license" style="width:159px; height: 32px" width="159" height="32" />](https://jb.gg/OpenSourceSupport) (License Certificate for JetBrains All Products Pack)
|
||||
|
||||
--------
|
||||
|
||||
## 如果您觉得本工具对您有帮助,不妨在右上角点亮一颗小星星,以示鼓励!
|
||||
|
||||
[](https://github.com/pppscn/SmsForwarder)
|
||||
<a href="https://star-history.com/#pppscn/SmsForwarder&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=pppscn/SmsForwarder&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=pppscn/SmsForwarder&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=pppscn/SmsForwarder&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
--------
|
||||
|
||||
|
35
README_en.md
35
README_en.md
@ -12,7 +12,19 @@ SmsForwarder - Not only forwarding text messages, but also a must-have for backu
|
||||
|
||||
listens to SMS, incoming calls, and App notifications on Android mobile devices, and forward according to user defined rules to another App/device, including DingTalk, WeCom and WeCom Group Bot, Feishu App and Feishu Group Bot, E-mail, Bark, Webhook, Telegram Bot, ServerChan, PushPlus, SMS, etc.
|
||||
|
||||
Including active control of the server and client, allowing you to easily and remotely send text messages, check text messages, check calls, check the phone book, check the battery, etc.
|
||||
Including active control of the server and client, allowing you to easily and remotely send text messages, check text messages, check calls, check the phone book, check the battery, etc. (New in v3.0+)
|
||||
|
||||
Automated Tasks & Quick Commands, effortlessly automate your life, doubling your efficiency, leaving more time to cherish family bonds! (New in v3.3+)
|
||||
|
||||
> Notice: Starting from `2022-06-06`, the original `Java edition` code has been archived to the `v2.x` branch and will no longer be updated!
|
||||
|
||||
> `v3.x` is compatible with Android 4.4 ~ 13.0.
|
||||
|
||||
> `Join the SmsF Preview Program` (online weekly build updates, be the first to experience new versions & bug fixes).
|
||||
|
||||
**Upgrade Instructions:**
|
||||
- After joining the SmsF Preview Experience Program, update online (available from `About Software` page, applicable for `v3.3.0_240305+`).
|
||||
- Manual download: [https://github.com/pppscn/SmsForwarder/actions/workflows/Weekly_Build.yml](https://github.com/pppscn/SmsForwarder/actions/workflows/Weekly_Build.yml)
|
||||
|
||||
--------
|
||||
|
||||
@ -36,7 +48,7 @@ Including active control of the server and client, allowing you to easily and re
|
||||
|
||||
## Screenshots :
|
||||
|
||||

|
||||

|
||||
|
||||
See more screenshots:https://github.com/pppscn/SmsForwarder/wiki
|
||||
|
||||
@ -56,7 +68,7 @@ See more screenshots:https://github.com/pppscn/SmsForwarder/wiki
|
||||
|
||||
> ⚠ Gitee: https://gitee.com/pp/SmsForwarder/wikis/pages
|
||||
|
||||

|
||||

|
||||
|
||||
--------
|
||||
|
||||
@ -65,12 +77,11 @@ See more screenshots:https://github.com/pppscn/SmsForwarder/wiki
|
||||
+ Submit an issue or Pull Request.
|
||||
+ Join group chat (only Chinese groups/channels available currently)
|
||||
|
||||
| Telegram Group |
|
||||
| :--: |
|
||||
|  |
|
||||
| Telegram Group |
|
||||
|:---------------------------------------------------:|
|
||||
|  |
|
||||
| [+QBZgnL_fxYM0NjE9](https://t.me/+QBZgnL_fxYM0NjE9) |
|
||||
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
> [Thanks to all the enthusiastic netizens who sponsored this project --> Reward list](https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427)
|
||||
@ -86,13 +97,19 @@ See more screenshots:https://github.com/pppscn/SmsForwarder/wiki
|
||||
+ https://github.com/yanzhenjie/AndServer (HttpServer)
|
||||
+ https://github.com/jenly1314/Location (Location)
|
||||
+ https://gitee.com/xuankaicat/kmnkt (socket)
|
||||
+ [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg?_ga=2.126618957.1361252949.1638261367-1417196221.1635638144&_gl=1*1pfl3dq*_ga*MTQxNzE5NjIyMS4xNjM1NjM4MTQ0*_ga_V0XZL7QHEB*MTYzODMzMjA4OC43LjAuMTYzODMzMjA5Ny4w" alt="GitHub license" style="width:96px" width="96" />](https://jb.gg/OpenSourceSupport) (License Certificate for JetBrains All Products Pack)
|
||||
+ [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" alt="GitHub license" style="width:159px; height: 32px" width="159" height="32" />](https://jb.gg/OpenSourceSupport) (License Certificate for JetBrains All Products Pack)
|
||||
|
||||
--------
|
||||
|
||||
## Star this repo if you find this application useful!
|
||||
|
||||
[](https://github.com/pppscn/SmsForwarder)
|
||||
<a href="https://star-history.com/#pppscn/SmsForwarder&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=pppscn/SmsForwarder&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=pppscn/SmsForwarder&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=pppscn/SmsForwarder&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
--------
|
||||
|
||||
|
141
app/build.gradle
141
app/build.gradle
@ -1,5 +1,7 @@
|
||||
//file:noinspection GrDeprecatedAPIUsage
|
||||
//file:noinspection DependencyNotationArgument
|
||||
import groovy.json.JsonBuilder
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
@ -7,6 +9,8 @@ plugins {
|
||||
id 'kotlin-parcelize'
|
||||
id 'img-optimizer'
|
||||
id 'com.yanzhenjie.andserver'
|
||||
//AspectJX: https://github.com/wurensen/gradle_plugin_android_aspectjx
|
||||
//id "io.github.wurensen.android-aspectjx" version "3.3.2"
|
||||
}
|
||||
|
||||
def keyProps = new Properties()
|
||||
@ -21,10 +25,24 @@ if (isNeedPackage.toBoolean() && isUseBooster.toBoolean()) {
|
||||
}
|
||||
|
||||
android {
|
||||
//noinspection GradleDependency
|
||||
// 禁用过时 API 警告
|
||||
configure(allprojects) {
|
||||
gradle.projectsEvaluated {
|
||||
tasks.withType(JavaCompile).tap {
|
||||
configureEach {
|
||||
options.compilerArgs << "-Xlint:-removal"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildToolsVersion build_versions.build_tools
|
||||
compileSdkVersion build_versions.target_sdk
|
||||
|
||||
testOptions {
|
||||
unitTests.returnDefaultValues = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
@ -34,13 +52,22 @@ android {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
//编译日期
|
||||
def buildDate = new Date().format("yyMMdd", TimeZone.getTimeZone("GMT+08"))
|
||||
//编译时间
|
||||
def buildTime = new Date().format("yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone("GMT+08"))
|
||||
//Git 的 Commit ID
|
||||
def gitCommitId = getGitCommitId()
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.idormy.sms.forwarder"
|
||||
minSdkVersion build_versions.min_sdk
|
||||
targetSdkVersion build_versions.target_sdk
|
||||
versionCode build_versions.version_code
|
||||
versionName build_versions.version_name
|
||||
versionName = "${build_versions.version_name}.${buildDate}"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
buildConfigField "String", "BUILD_TIME", "\"${buildTime}\""
|
||||
buildConfigField "String", "GIT_COMMIT_ID", "\"${gitCommitId}\""
|
||||
|
||||
multiDexEnabled true
|
||||
//vectorDrawables.useSupportLibrary = true
|
||||
@ -76,8 +103,6 @@ android {
|
||||
// 调试模式开关
|
||||
debuggable false
|
||||
jniDebuggable false
|
||||
// 压缩对齐开关
|
||||
zipAlignEnabled true
|
||||
// 移除无用的资源
|
||||
shrinkResources true
|
||||
// 代码混淆开关
|
||||
@ -107,8 +132,6 @@ android {
|
||||
// 调试模式开关
|
||||
debuggable true
|
||||
jniDebuggable true
|
||||
// 压缩对齐开关
|
||||
zipAlignEnabled true
|
||||
// 移除无用的资源
|
||||
shrinkResources true
|
||||
// 代码混淆开关
|
||||
@ -154,22 +177,38 @@ android {
|
||||
exclude 'lib/x86/libgojni.so'
|
||||
exclude 'lib/x86_64/libgojni.so'
|
||||
}
|
||||
jniLibs {
|
||||
excludes += ["kotlin/**"]
|
||||
}
|
||||
resources {
|
||||
merge 'META-INF/mailcap'
|
||||
pickFirst 'META-INF/LICENSE.md'
|
||||
pickFirst 'META-INF/NOTICE.md'
|
||||
excludes += ['META-INF/DEPENDENCIES.txt', 'META-INF/LICENSE.txt', 'META-INF/NOTICE.txt', 'META-INF/NOTICE', 'META-INF/LICENSE', 'META-INF/DEPENDENCIES', 'META-INF/notice.txt', 'META-INF/license.txt', 'META-INF/dependencies.txt', 'META-INF/LGPL2.1']
|
||||
excludes += ["META-INF/*.kotlin_module", "META-INF/*.version", "kotlin/**", "DebugProbesKt.bin"]
|
||||
}
|
||||
}
|
||||
|
||||
android.applicationVariants.configureEach { variant ->
|
||||
// Assigns a different version code for each output APK.
|
||||
variant.outputs.each { output ->
|
||||
def date = new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+08"))
|
||||
//noinspection GrDeprecatedAPIUsage
|
||||
def abiName = output.getFilter(com.android.build.OutputFile.ABI)
|
||||
if (abiName == null) abiName = "universal"
|
||||
output.versionCodeOverride = abiCodes.get(abiName, 0) * 100000 + variant.versionCode
|
||||
output.outputFileName = "SmsF_${versionName}_${output.versionCode}_${abiName}_${date}_${variant.name}.apk"
|
||||
output.outputFileName = "SmsF_${versionName}_${output.versionCode}_${abiName}_${variant.name}.apk"
|
||||
|
||||
// 修改 output-metadata.json 追加编译日期、编译时间、Git Commit ID
|
||||
def assembleTaskName = "assemble${variant.name.capitalize()}"
|
||||
tasks.named(assembleTaskName) {
|
||||
doLast {
|
||||
def metadataFile = file("${output.outputFile.parent}/output-metadata.json")
|
||||
def metadata = new JsonSlurper().parseText(metadataFile.text)
|
||||
metadata.buildDate = buildDate
|
||||
metadata.buildTime = buildTime
|
||||
metadata.gitCommitId = gitCommitId
|
||||
metadataFile.text = new JsonBuilder(metadata).toPrettyString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,6 +228,25 @@ android {
|
||||
}
|
||||
namespace 'com.idormy.sms.forwarder'
|
||||
|
||||
if (isNeedClean.toBoolean()) {
|
||||
//编译前清理项目缓存
|
||||
preBuild.dependsOn clean
|
||||
//编译后清理垃圾文件
|
||||
gradle.buildFinished { buildResult ->
|
||||
if (buildResult.failure == null) {
|
||||
println "Build succeeded, cleaning text files..."
|
||||
//delete rootProject.buildDir
|
||||
FileTree rootTree = fileTree(dir: rootDir)
|
||||
rootTree.each { File file ->
|
||||
if ((file.toString().contains("ajcore") || file.toString().contains("mapping") || file.toString().contains("seeds") || file.toString().contains("unused")) && file.toString().endsWith(".txt")) {
|
||||
delete file
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println "Build failed, cleanTxt not executed."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -196,10 +254,8 @@ dependencies {
|
||||
//frpc
|
||||
implementation files('libs/frpclib.aar')
|
||||
|
||||
//kmnkt基于Kotlin Multiplatform的跨平台socket通信统一接口,支持UDP/TCP/MQTT协议
|
||||
//https://github.com/xuankaicat/kmnkt
|
||||
//MQTT协议
|
||||
implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5")
|
||||
//implementation files('libs/socket-2.0.0-alpha06-2.aar')
|
||||
|
||||
testImplementation deps.junit
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
@ -221,16 +277,16 @@ dependencies {
|
||||
//vLayout:https://github.com/alibaba/vlayout
|
||||
implementation 'com.alibaba.android:vlayout:1.3.0'
|
||||
//下拉刷新
|
||||
implementation 'com.github.xuexiangjys.SmartRefreshLayout:refresh-header:1.1.5'
|
||||
implementation 'com.github.xuexiangjys.SmartRefreshLayout:refresh-layout:1.1.5'
|
||||
implementation 'com.github.xuexiangjys.SmartRefreshLayout:refresh-header:1.1.4'
|
||||
implementation 'com.github.xuexiangjys.SmartRefreshLayout:refresh-layout:1.1.4'
|
||||
//WebView
|
||||
implementation 'com.github.xuexiangjys.AgentWeb:agentweb-core:1.0.0'
|
||||
implementation 'com.github.xuexiangjys.AgentWeb:agentweb-download:1.0.0'//选填
|
||||
implementation 'com.github.xuexiangjys.AgentWeb:agentweb-core:1.0.1'
|
||||
implementation 'com.github.xuexiangjys.AgentWeb:agentweb-download:1.0.1'//选填
|
||||
//屏幕适配AutoSize:https://github.com/JessYanCoding/AndroidAutoSize
|
||||
implementation 'me.jessyan:autosize:1.2.1'
|
||||
//umeng统计
|
||||
implementation 'com.umeng.umsdk:common:9.6.6'
|
||||
implementation 'com.umeng.umsdk:asms:1.8.0'
|
||||
//友盟统计
|
||||
implementation 'com.umeng.umsdk:common:9.6.8'
|
||||
implementation 'com.umeng.umsdk:asms:1.8.6'
|
||||
|
||||
//预加载占位控件
|
||||
implementation 'me.samlss:broccoli:1.0.0'
|
||||
@ -261,18 +317,21 @@ dependencies {
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
|
||||
//CodeView:https://github.com/AmrDeveloper/CodeView
|
||||
implementation 'io.github.amrdeveloper:codeview:1.3.8'
|
||||
implementation 'io.github.amrdeveloper:codeview:1.3.9'
|
||||
|
||||
//LiveEventBus:https://github.com/JeremyLiao/LiveEventBus
|
||||
implementation 'io.github.jeremyliao:live-event-bus-x:1.8.0'
|
||||
|
||||
//MarkdownView:https://github.com/tiagohm/MarkdownView
|
||||
implementation 'com.github.tiagohm.MarkdownView:library:0.19.0'
|
||||
//implementation 'com.github.tiagohm.MarkdownView:emoji:0.19.0'
|
||||
implementation 'com.github.pppscn.MarkdownView:library:0.19.0'
|
||||
//implementation 'com.github.pppscn.MarkdownView:emoji:0.19.0'
|
||||
|
||||
def retrofit2_version = '2.9.0'
|
||||
//noinspection GradleDependency
|
||||
implementation "com.squareup.retrofit2:retrofit:$retrofit2_version"
|
||||
//noinspection GradleDependency
|
||||
implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version"
|
||||
//noinspection GradleDependency
|
||||
implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit2_version"
|
||||
|
||||
def paging_version = "3.1.1"
|
||||
@ -283,13 +342,28 @@ dependencies {
|
||||
testImplementation "androidx.paging:paging-common-ktx:$paging_version"
|
||||
|
||||
//权限请求框架:https://github.com/getActivity/XXPermissions
|
||||
implementation 'com.github.getActivity:XXPermissions:18.6'
|
||||
implementation 'com.github.getActivity:XXPermissions:20.0'
|
||||
//语种切换框架:https://github.com/getActivity/MultiLanguages
|
||||
implementation 'com.github.getActivity:MultiLanguages:b47f7be' //9.3
|
||||
|
||||
def mail_version = '1.6.7'
|
||||
implementation "com.sun.mail:android-mail:$mail_version"
|
||||
implementation "com.sun.mail:android-activation:$mail_version"
|
||||
// https://jakartaee.github.io/mail-api/Android
|
||||
def mail_version = '2.0.1'
|
||||
implementation "com.sun.mail:jakarta.mail:$mail_version"
|
||||
implementation "com.sun.activation:jakarta.activation:$mail_version"
|
||||
|
||||
//国密算法SM4 的JAVA实现(基于BC实现)
|
||||
def bouncycastle_version = '1.77'
|
||||
//noinspection GradleDependency
|
||||
api "org.bouncycastle:bcprov-jdk18on:$bouncycastle_version"
|
||||
//邮件 S/MIME 加密和签名
|
||||
//implementation "org.spongycastle:bcmail-jdk18on:$bouncycastle_version" //Android下报错
|
||||
//noinspection GradleDependency
|
||||
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastle_version"
|
||||
//implementation "org.bouncycastle:bctls-jdk18on:$bouncycastle_version"
|
||||
//邮件 PGP 加密和签名
|
||||
//implementation "org.bouncycastle:bcpg-jdk18on:$bouncycastle_version" //Thunderbird无法解密
|
||||
//PGPainless: https://github.com/pgpainless/pgpainless
|
||||
implementation 'org.pgpainless:pgpainless-core:1.6.7'
|
||||
|
||||
//Android Keep Alive(安卓保活),Cactus 集成双进程前台服务,JobScheduler,onePix(一像素),WorkManager,无声音乐
|
||||
//https://github.com/gyf-dev/Cactus
|
||||
@ -299,9 +373,6 @@ dependencies {
|
||||
implementation 'cn.ppps.andserver:api:2.1.12'
|
||||
kapt 'cn.ppps.andserver:processor:2.1.12'
|
||||
|
||||
//国密算法SM4 的JAVA实现(基于BC实现)
|
||||
api 'org.bouncycastle:bcprov-jdk15on:1.70'
|
||||
|
||||
//Location 是一个通过 Android 自带的 LocationManager 来实现的定位功能:https://github.com/jenly1314/Location
|
||||
implementation 'com.github.pppscn:location:1.0.0'
|
||||
|
||||
@ -317,3 +388,13 @@ dependencies {
|
||||
apply from: 'x-library.gradle'
|
||||
//walle多渠道打包
|
||||
//apply from: 'multiple-channel.gradle'
|
||||
|
||||
//获取 commit ID
|
||||
static def getGitCommitId() {
|
||||
try {
|
||||
return 'git rev-parse --short HEAD'.execute().text.trim()
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace()
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
@ -6,7 +6,12 @@
|
||||
<uses-feature
|
||||
android:name="android.hardware.telephony"
|
||||
android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.flash"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="com.android.permission.GET_INSTALLED_APPS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
@ -67,6 +72,13 @@
|
||||
<uses-permission
|
||||
android:name="android.permission.REBOOT"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
@ -101,7 +113,7 @@
|
||||
android:taskAffinity=":splash"
|
||||
android:theme="@style/AppTheme.Launch.App"
|
||||
android:windowSoftInputMode="adjustPan|stateHidden"
|
||||
tools:ignore="TranslucentOrientation">
|
||||
tools:ignore="DiscouragedApi,TranslucentOrientation">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
@ -113,21 +125,24 @@
|
||||
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
|
||||
android:exported="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustPan|stateHidden" />
|
||||
android:windowSoftInputMode="adjustPan|stateHidden"
|
||||
tools:ignore="DiscouragedApi" />
|
||||
<activity
|
||||
android:name=".activity.ClientActivity"
|
||||
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
|
||||
android:exported="true"
|
||||
android:launchMode="singleInstance"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustPan|stateHidden" />
|
||||
android:windowSoftInputMode="adjustPan|stateHidden"
|
||||
tools:ignore="DiscouragedApi" />
|
||||
<activity
|
||||
android:name=".activity.TaskActivity"
|
||||
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
|
||||
android:exported="true"
|
||||
android:launchMode="singleInstance"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustPan|stateHidden" />
|
||||
android:windowSoftInputMode="adjustPan|stateHidden"
|
||||
tools:ignore="DiscouragedApi" />
|
||||
<!--通用浏览器-->
|
||||
<activity
|
||||
android:name=".core.webview.AgentWebActivity"
|
||||
@ -148,10 +163,6 @@
|
||||
<data android:scheme="https" />
|
||||
<data android:scheme="about" />
|
||||
<data android:scheme="javascript" />
|
||||
<!-- 设置自己的deeplink -->
|
||||
<!-- <data-->
|
||||
<!-- android:host="xxx.com"-->
|
||||
<!-- android:scheme="xui"/>-->
|
||||
</intent-filter>
|
||||
<!-- AppLink -->
|
||||
<intent-filter
|
||||
@ -185,26 +196,30 @@
|
||||
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
|
||||
android:exported="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustPan|stateHidden" />
|
||||
android:windowSoftInputMode="adjustPan|stateHidden"
|
||||
tools:ignore="DiscouragedApi" />
|
||||
<!-- 版本更新提示-->
|
||||
<activity
|
||||
android:name=".utils.update.UpdateTipDialog"
|
||||
android:exported="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/DialogTheme" />
|
||||
android:theme="@style/DialogTheme"
|
||||
tools:ignore="DiscouragedApi" />
|
||||
<!-- Webview拦截提示弹窗-->
|
||||
<activity
|
||||
android:name=".core.webview.WebViewInterceptDialog"
|
||||
android:exported="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/DialogTheme" />
|
||||
android:theme="@style/DialogTheme"
|
||||
tools:ignore="DiscouragedApi" />
|
||||
<!-- applink的中转页面 -->
|
||||
<activity
|
||||
android:name=".core.XPageTransferActivity"
|
||||
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
|
||||
android:exported="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustPan|stateHidden" />
|
||||
android:windowSoftInputMode="adjustPan|stateHidden"
|
||||
tools:ignore="DiscouragedApi" />
|
||||
|
||||
<!--屏幕自适应设计图-->
|
||||
<meta-data
|
||||
@ -216,6 +231,10 @@
|
||||
android:exported="true"
|
||||
android:value="640" />
|
||||
|
||||
<service
|
||||
android:name=".service.BluetoothScanService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name=".service.ForegroundService"
|
||||
android:enabled="true" />
|
||||
@ -244,6 +263,30 @@
|
||||
<action android:name="android.intent.action.BATTERY_CHANGED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".receiver.BluetoothReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<!-- 蓝牙设备发现 -->
|
||||
<action android:name="android.bluetooth.device.action.FOUND" />
|
||||
<!-- 蓝牙扫描完成 -->
|
||||
<action android:name="android.bluetooth.adapter.action.DISCOVERY_FINISHED" />
|
||||
<!-- 蓝牙状态改变 -->
|
||||
<action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
|
||||
<!-- 蓝牙扫描模式改变 -->
|
||||
<action android:name="android.bluetooth.adapter.action.SCAN_MODE_CHANGED" />
|
||||
<!-- 本地蓝牙名称改变 -->
|
||||
<action android:name="android.bluetooth.adapter.action.LOCAL_NAME_CHANGED" />
|
||||
<!-- 蓝牙连接状态改变 -->
|
||||
<action android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
|
||||
<!-- 蓝牙设备配对状态改变 -->
|
||||
<action android:name="android.bluetooth.device.action.BOND_STATE_CHANGED" />
|
||||
<!-- 蓝牙设备连接 -->
|
||||
<action android:name="android.bluetooth.device.action.ACL_CONNECTED" />
|
||||
<!-- 蓝牙设备断开连接 -->
|
||||
<action android:name="android.bluetooth.device.action.ACL_DISCONNECTED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".receiver.BootCompletedReceiver"
|
||||
android:defaultToDeviceProtectedStorage="true"
|
||||
@ -272,6 +315,7 @@
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SCREEN_OFF" />
|
||||
<action android:name="android.intent.action.SCREEN_ON" />
|
||||
<action android:name="android.intent.action.ACTION_USER_PRESENT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
@ -336,4 +380,4 @@
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
@ -1,13 +1,21 @@
|
||||
{
|
||||
"Code": 0,
|
||||
"Data": [
|
||||
{
|
||||
"title": "短信转发器",
|
||||
"content": "本软件用于监控Android手机短信、来电、APP通知,并根据指定规则转发到其他设备!<br />\n请确认您是否清楚该软件的用途?!<br />\n否则,请立即卸载!"
|
||||
},
|
||||
{
|
||||
"title": "防诈提醒",
|
||||
"content": "本软件不参与任何刷单返利担保!请您远离刷单返利陷阱,谨防网络诈骗!<br />\n请再次确认是否出于本人自用需要自愿安装?!<br />\n否则,请立即卸载!"
|
||||
},
|
||||
{
|
||||
"title": "新用户必读",
|
||||
"content": "开始设置之前,请您认真地看一遍 <a href=\"https://gitee.com/pp/SmsForwarder/wikis/pages\"><font color=\"#800080\">Wiki</font></a> !<br />\n遇到问题,请按照 <a href=\"https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4877445&doc_id=1821427\"><font color=\"#0000FF\">常见问题</font></a> 章节进行排查!<br />\n没找到答案的,再加入互助交流群提问,请清楚地描述问题,并给出对应的配置截图与相关日志,方便大家直观的判断问题! "
|
||||
},
|
||||
{
|
||||
"title": "SmsF 互助交流群",
|
||||
"content": "<a href=\"https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=1W5aewP&appChannel=share&businessType=9&from=246610&biz=ka\">QQ频道号: q7oofwp13s</a><br /><a href=\"https://t.me/+QBZgnL_fxYM0NjE9\">Telegram 群组</a><br />钉钉群号: 29760014208"
|
||||
"content": "<a href=\"https://t.me/+QBZgnL_fxYM0NjE9\">Telegram 群组</a>"
|
||||
},
|
||||
{
|
||||
"title": "打赏名单",
|
||||
|
@ -3,6 +3,8 @@ package com.idormy.sms.forwarder
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.app.PendingIntent
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
@ -13,6 +15,7 @@ import android.os.Build
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.multidex.MultiDex
|
||||
import androidx.work.Configuration
|
||||
import androidx.work.WorkManager
|
||||
import com.gyf.cactus.Cactus
|
||||
import com.gyf.cactus.callback.CactusCallback
|
||||
import com.gyf.cactus.ext.cactus
|
||||
@ -21,32 +24,55 @@ import com.hjq.language.OnLanguageListener
|
||||
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.*
|
||||
import com.idormy.sms.forwarder.database.repository.FrpcRepository
|
||||
import com.idormy.sms.forwarder.database.repository.LogsRepository
|
||||
import com.idormy.sms.forwarder.database.repository.MsgRepository
|
||||
import com.idormy.sms.forwarder.database.repository.RuleRepository
|
||||
import com.idormy.sms.forwarder.database.repository.SenderRepository
|
||||
import com.idormy.sms.forwarder.database.repository.TaskRepository
|
||||
import com.idormy.sms.forwarder.entity.SimInfo
|
||||
import com.idormy.sms.forwarder.receiver.BatteryReceiver
|
||||
import com.idormy.sms.forwarder.receiver.BluetoothReceiver
|
||||
import com.idormy.sms.forwarder.receiver.CactusReceiver
|
||||
import com.idormy.sms.forwarder.receiver.LockScreenReceiver
|
||||
import com.idormy.sms.forwarder.receiver.NetworkChangeReceiver
|
||||
import com.idormy.sms.forwarder.service.BluetoothScanService
|
||||
import com.idormy.sms.forwarder.service.ForegroundService
|
||||
import com.idormy.sms.forwarder.service.HttpServerService
|
||||
import com.idormy.sms.forwarder.service.LocationService
|
||||
import com.idormy.sms.forwarder.utils.*
|
||||
import com.idormy.sms.forwarder.utils.ACTION_START
|
||||
import com.idormy.sms.forwarder.utils.AppInfo
|
||||
import com.idormy.sms.forwarder.utils.CactusSave
|
||||
import com.idormy.sms.forwarder.utils.FRONT_CHANNEL_ID
|
||||
import com.idormy.sms.forwarder.utils.FRONT_CHANNEL_NAME
|
||||
import com.idormy.sms.forwarder.utils.FRONT_NOTIFY_ID
|
||||
import com.idormy.sms.forwarder.utils.FRPC_LIB_VERSION
|
||||
import com.idormy.sms.forwarder.utils.HistoryUtils
|
||||
import com.idormy.sms.forwarder.utils.HttpServerUtils
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.idormy.sms.forwarder.utils.SharedPreference
|
||||
import com.idormy.sms.forwarder.utils.sdkinit.UMengInit
|
||||
import com.idormy.sms.forwarder.utils.sdkinit.XBasicLibInit
|
||||
import com.idormy.sms.forwarder.utils.sdkinit.XUpdateInit
|
||||
import com.idormy.sms.forwarder.utils.tinker.TinkerLoadLibrary
|
||||
import com.king.location.LocationClient
|
||||
import com.xuexiang.xutil.file.FileUtils
|
||||
import frpclib.Frpclib
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import java.io.BufferedWriter
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.io.IOException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@ -67,6 +93,24 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
lateinit var context: Context
|
||||
|
||||
//自定义模板可用变量标签
|
||||
var COMMON_TAG_MAP: MutableMap<String, String> = mutableMapOf()
|
||||
var SMS_TAG_MAP: MutableMap<String, String> = mutableMapOf()
|
||||
var CALL_TAG_MAP: MutableMap<String, String> = mutableMapOf()
|
||||
var APP_TAG_MAP: MutableMap<String, String> = mutableMapOf()
|
||||
var LOCATION_TAG_MAP: MutableMap<String, String> = mutableMapOf()
|
||||
var BATTERY_TAG_MAP: MutableMap<String, String> = mutableMapOf()
|
||||
var NETWORK_TAG_MAP: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
//通话类型:1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
|
||||
var CALL_TYPE_MAP: MutableMap<String, String> = mutableMapOf()
|
||||
var FILED_MAP: MutableMap<String, String> = mutableMapOf()
|
||||
var CHECK_MAP: MutableMap<String, String> = mutableMapOf()
|
||||
var SIM_SLOT_MAP: MutableMap<String, String> = mutableMapOf()
|
||||
var FORWARD_STATUS_MAP: MutableMap<Int, String> = mutableMapOf()
|
||||
var BARK_LEVEL_MAP: MutableMap<String, String> = mutableMapOf()
|
||||
var BARK_ENCRYPTION_ALGORITHM_MAP: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
//已插入SIM卡信息
|
||||
var SimInfoList: MutableMap<Int, SimInfo> = mutableMapOf()
|
||||
|
||||
@ -91,6 +135,12 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
|
||||
val LocationClient by lazy { LocationClient(context) }
|
||||
val Geocoder by lazy { Geocoder(context) }
|
||||
val DateFormat by lazy { SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) }
|
||||
|
||||
//Frpclib是否已经初始化
|
||||
var FrpclibInited = false
|
||||
|
||||
//是否需要在拼接字符串时添加空格
|
||||
var isNeedSpaceBetweenWords = false
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
@ -132,12 +182,16 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
|
||||
//纯客户端模式
|
||||
if (SettingUtils.enablePureClientMode) return
|
||||
|
||||
//初始化WorkManager
|
||||
WorkManager.initialize(this, Configuration.Builder().build())
|
||||
|
||||
//动态加载FrpcLib
|
||||
val libPath = filesDir.absolutePath + "/libs"
|
||||
val soFile = File(libPath)
|
||||
if (soFile.exists()) {
|
||||
try {
|
||||
TinkerLoadLibrary.installNativeLibraryPath(classLoader, soFile)
|
||||
FrpclibInited = FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so") && FRPC_LIB_VERSION == Frpclib.getVersion()
|
||||
} catch (throwable: Throwable) {
|
||||
Log.e("APP", throwable.message.toString())
|
||||
}
|
||||
@ -145,7 +199,7 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
|
||||
|
||||
//启动前台服务
|
||||
val foregroundServiceIntent = Intent(this, ForegroundService::class.java)
|
||||
foregroundServiceIntent.action = "START"
|
||||
foregroundServiceIntent.action = ACTION_START
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForegroundService(foregroundServiceIntent)
|
||||
} else {
|
||||
@ -162,7 +216,7 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
|
||||
//启动LocationService
|
||||
if (SettingUtils.enableLocation) {
|
||||
val locationServiceIntent = Intent(this, LocationService::class.java)
|
||||
locationServiceIntent.action = "START"
|
||||
locationServiceIntent.action = ACTION_START
|
||||
startService(locationServiceIntent)
|
||||
}
|
||||
|
||||
@ -171,6 +225,26 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
|
||||
val batteryFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
|
||||
registerReceiver(batteryReceiver, batteryFilter)
|
||||
|
||||
//监听蓝牙状态变化
|
||||
val bluetoothReceiver = BluetoothReceiver()
|
||||
val filter = IntentFilter().apply {
|
||||
addAction(BluetoothDevice.ACTION_FOUND)
|
||||
addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
|
||||
addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
|
||||
addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED)
|
||||
addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)
|
||||
addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
|
||||
addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
|
||||
addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
|
||||
addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
|
||||
}
|
||||
registerReceiver(bluetoothReceiver, filter)
|
||||
if (SettingUtils.enableBluetooth) {
|
||||
val bluetoothScanServiceIntent = Intent(this, BluetoothScanService::class.java)
|
||||
bluetoothScanServiceIntent.action = ACTION_START
|
||||
startService(bluetoothScanServiceIntent)
|
||||
}
|
||||
|
||||
//监听网络变化
|
||||
val networkReceiver = NetworkChangeReceiver()
|
||||
val networkFilter = IntentFilter().apply {
|
||||
@ -186,6 +260,7 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
|
||||
val lockScreenFilter = IntentFilter().apply {
|
||||
addAction(Intent.ACTION_SCREEN_OFF)
|
||||
addAction(Intent.ACTION_SCREEN_ON)
|
||||
addAction(Intent.ACTION_USER_PRESENT)
|
||||
}
|
||||
registerReceiver(lockScreenReceiver, lockScreenFilter)
|
||||
|
||||
@ -224,7 +299,7 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P && SettingUtils.enableOnePixelActivity) {
|
||||
setOnePixEnabled(true)
|
||||
}
|
||||
//奔溃是否可以重启用户界面
|
||||
//崩溃是否可以重启用户界面
|
||||
setCrashRestartUIEnabled(true)
|
||||
addCallback({
|
||||
Log.d(TAG, "Cactus保活:onStop回调")
|
||||
@ -251,13 +326,13 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
|
||||
Core.init(this)
|
||||
// 配置文件初始化
|
||||
SharedPreference.init(applicationContext)
|
||||
// X系列基础库初始化
|
||||
XBasicLibInit.init(this)
|
||||
// 初始化日志打印
|
||||
isDebug = SettingUtils.enableDebugMode
|
||||
Log.init(applicationContext)
|
||||
// 转发历史工具类初始化
|
||||
HistoryUtils.init(applicationContext)
|
||||
// X系列基础库初始化
|
||||
XBasicLibInit.init(this)
|
||||
// 版本更新初始化
|
||||
XUpdateInit.init(this)
|
||||
// 运营统计数据
|
||||
@ -267,13 +342,22 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
|
||||
// 设置语种变化监听器
|
||||
MultiLanguages.setOnLanguageListener(object : OnLanguageListener {
|
||||
override fun onAppLocaleChange(oldLocale: Locale, newLocale: Locale) {
|
||||
// 注意:只有setAppLanguage时触发,clearAppLanguage时不触发
|
||||
Log.i(TAG, "监听到应用切换了语种,旧语种:$oldLocale,新语种:$newLocale")
|
||||
switchLanguage(newLocale)
|
||||
}
|
||||
|
||||
override fun onSystemLocaleChange(oldLocale: Locale, newLocale: Locale) {
|
||||
Log.i(TAG, "监听到系统切换了语种,旧语种:" + oldLocale + ",新语种:" + newLocale + ",是否跟随系统:" + MultiLanguages.isSystemLanguage(this@App))
|
||||
Log.i(TAG, "监听到系统切换了语种,旧语种:$oldLocale,新语种:$newLocale")
|
||||
switchLanguage(newLocale)
|
||||
/*val isFlowSystem = SettingUtils.isFlowSystemLanguage //MultiLanguages.isSystemLanguage(context)取值不对,一直是false
|
||||
Log.i(TAG, "监听到系统切换了语种,旧语种:$oldLocale,新语种:$newLocale,是否跟随系统:$isFlowSystem")
|
||||
if (isFlowSystem) {
|
||||
CommonUtils.switchLanguage(oldLocale, newLocale)
|
||||
}*/
|
||||
}
|
||||
})
|
||||
switchLanguage(MultiLanguages.getAppLanguage(this))
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
@ -311,4 +395,161 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//多语言切换时枚举常量自动切换语言
|
||||
private fun switchLanguage(newLocale: Locale) {
|
||||
isNeedSpaceBetweenWords = !newLocale.language.contains("zh")
|
||||
|
||||
//自定义模板可用变量标签
|
||||
COMMON_TAG_MAP.clear()
|
||||
COMMON_TAG_MAP.putAll(
|
||||
mapOf(
|
||||
getString(R.string.tag_receive_time) to getString(R.string.insert_tag_receive_time),
|
||||
getString(R.string.tag_current_time) to getString(R.string.insert_tag_current_time),
|
||||
getString(R.string.tag_device_name) to getString(R.string.insert_tag_device_name),
|
||||
getString(R.string.tag_app_version) to getString(R.string.insert_tag_app_version),
|
||||
)
|
||||
)
|
||||
SMS_TAG_MAP.clear()
|
||||
SMS_TAG_MAP.putAll(
|
||||
mapOf(
|
||||
getString(R.string.tag_from) to getString(R.string.insert_tag_from),
|
||||
getString(R.string.tag_sms) to getString(R.string.insert_tag_sms),
|
||||
getString(R.string.tag_card_slot) to getString(R.string.insert_tag_card_slot),
|
||||
getString(R.string.tag_card_subid) to getString(R.string.insert_tag_card_subid),
|
||||
getString(R.string.tag_contact_name) to getString(R.string.insert_tag_contact_name),
|
||||
getString(R.string.tag_phone_area) to getString(R.string.insert_tag_phone_area),
|
||||
)
|
||||
)
|
||||
CALL_TAG_MAP.clear()
|
||||
CALL_TAG_MAP.putAll(
|
||||
mapOf(
|
||||
getString(R.string.tag_from) to getString(R.string.insert_tag_from),
|
||||
getString(R.string.tag_sms) to getString(R.string.insert_tag_msg),
|
||||
getString(R.string.tag_card_slot) to getString(R.string.insert_tag_card_slot),
|
||||
getString(R.string.tag_card_subid) to getString(R.string.insert_tag_card_subid),
|
||||
getString(R.string.tag_call_type) to getString(R.string.insert_tag_call_type),
|
||||
getString(R.string.tag_contact_name) to getString(R.string.insert_tag_contact_name),
|
||||
getString(R.string.tag_phone_area) to getString(R.string.insert_tag_phone_area),
|
||||
)
|
||||
)
|
||||
APP_TAG_MAP.clear()
|
||||
APP_TAG_MAP.putAll(
|
||||
mapOf(
|
||||
getString(R.string.tag_uid) to getString(R.string.insert_tag_uid),
|
||||
getString(R.string.tag_package_name) to getString(R.string.insert_tag_package_name),
|
||||
getString(R.string.tag_app_name) to getString(R.string.insert_tag_app_name),
|
||||
getString(R.string.tag_title) to getString(R.string.insert_tag_title),
|
||||
getString(R.string.tag_msg) to getString(R.string.insert_tag_msg),
|
||||
)
|
||||
)
|
||||
LOCATION_TAG_MAP.clear()
|
||||
LOCATION_TAG_MAP.putAll(
|
||||
mapOf(
|
||||
getString(R.string.tag_location) to getString(R.string.insert_tag_location),
|
||||
getString(R.string.tag_location_longitude) to getString(R.string.insert_tag_location_longitude),
|
||||
getString(R.string.tag_location_latitude) to getString(R.string.insert_tag_location_latitude),
|
||||
getString(R.string.tag_location_address) to getString(R.string.insert_tag_location_address),
|
||||
)
|
||||
)
|
||||
BATTERY_TAG_MAP.clear()
|
||||
BATTERY_TAG_MAP.putAll(
|
||||
mapOf(
|
||||
getString(R.string.tag_battery_pct) to getString(R.string.insert_tag_battery_pct),
|
||||
getString(R.string.tag_battery_status) to getString(R.string.insert_tag_battery_status),
|
||||
getString(R.string.tag_battery_plugged) to getString(R.string.insert_tag_battery_plugged),
|
||||
getString(R.string.tag_battery_info) to getString(R.string.insert_tag_battery_info),
|
||||
getString(R.string.tag_battery_info_simple) to getString(R.string.insert_tag_battery_info_simple),
|
||||
)
|
||||
)
|
||||
NETWORK_TAG_MAP.clear()
|
||||
NETWORK_TAG_MAP.putAll(
|
||||
mapOf(
|
||||
getString(R.string.tag_ipv4) to getString(R.string.insert_tag_ipv4),
|
||||
getString(R.string.tag_ipv6) to getString(R.string.insert_tag_ipv6),
|
||||
getString(R.string.tag_ip_list) to getString(R.string.insert_tag_ip_list),
|
||||
getString(R.string.tag_net_type) to getString(R.string.insert_tag_net_type),
|
||||
)
|
||||
)
|
||||
|
||||
CALL_TYPE_MAP.clear()
|
||||
CALL_TYPE_MAP.putAll(
|
||||
mapOf(
|
||||
//"0" to getString(R.string.unknown_call),
|
||||
"1" to getString(R.string.incoming_call_ended),
|
||||
"2" to getString(R.string.outgoing_call_ended),
|
||||
"3" to getString(R.string.missed_call),
|
||||
"4" to getString(R.string.incoming_call_received),
|
||||
"5" to getString(R.string.incoming_call_answered),
|
||||
"6" to getString(R.string.outgoing_call_started),
|
||||
)
|
||||
)
|
||||
|
||||
FILED_MAP.clear()
|
||||
FILED_MAP.putAll(
|
||||
mapOf(
|
||||
"transpond_all" to getString(R.string.rule_transpond_all),
|
||||
"phone_num" to getString(R.string.rule_phone_num),
|
||||
"msg_content" to getString(R.string.rule_msg_content),
|
||||
"multi_match" to getString(R.string.rule_multi_match),
|
||||
"package_name" to getString(R.string.rule_package_name),
|
||||
"inform_content" to getString(R.string.rule_inform_content),
|
||||
"call_type" to getString(R.string.rule_call_type),
|
||||
"uid" to getString(R.string.rule_uid),
|
||||
)
|
||||
)
|
||||
|
||||
CHECK_MAP.clear()
|
||||
CHECK_MAP.putAll(
|
||||
mapOf(
|
||||
"is" to getString(R.string.rule_is),
|
||||
"notis" to getString(R.string.rule_notis),
|
||||
"contain" to getString(R.string.rule_contain),
|
||||
"startwith" to getString(R.string.rule_startwith),
|
||||
"endwith" to getString(R.string.rule_endwith),
|
||||
"notcontain" to getString(R.string.rule_notcontain),
|
||||
"regex" to getString(R.string.rule_regex),
|
||||
)
|
||||
)
|
||||
|
||||
SIM_SLOT_MAP.clear()
|
||||
SIM_SLOT_MAP.putAll(
|
||||
mapOf(
|
||||
"ALL" to getString(R.string.rule_any),
|
||||
"SIM1" to "SIM1",
|
||||
"SIM2" to "SIM2",
|
||||
)
|
||||
)
|
||||
|
||||
FORWARD_STATUS_MAP.clear()
|
||||
FORWARD_STATUS_MAP.putAll(
|
||||
mapOf(
|
||||
0 to getString(R.string.failed),
|
||||
1 to getString(R.string.processing),
|
||||
2 to getString(R.string.success),
|
||||
)
|
||||
)
|
||||
|
||||
BARK_LEVEL_MAP.clear()
|
||||
BARK_LEVEL_MAP.putAll(
|
||||
mapOf(
|
||||
"active" to getString(R.string.bark_level_active),
|
||||
"timeSensitive" to getString(R.string.bark_level_timeSensitive),
|
||||
"passive" to getString(R.string.bark_level_passive)
|
||||
)
|
||||
)
|
||||
|
||||
BARK_ENCRYPTION_ALGORITHM_MAP.clear()
|
||||
BARK_ENCRYPTION_ALGORITHM_MAP.putAll(
|
||||
mapOf(
|
||||
"none" to getString(R.string.bark_encryption_algorithm_none),
|
||||
"AES128/CBC/PKCS7Padding" to "AES128/CBC/PKCS7Padding",
|
||||
"AES128/ECB/PKCS7Padding" to "AES128/ECB/PKCS7Padding",
|
||||
"AES192/CBC/PKCS7Padding" to "AES192/CBC/PKCS7Padding",
|
||||
"AES192/ECB/PKCS7Padding" to "AES192/ECB/PKCS7Padding",
|
||||
"AES256/CBC/PKCS7Padding" to "AES256/CBC/PKCS7Padding",
|
||||
"AES256/ECB/PKCS7Padding" to "AES256/ECB/PKCS7Padding",
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ import com.idormy.sms.forwarder.fragment.ServerFragment
|
||||
import com.idormy.sms.forwarder.fragment.SettingsFragment
|
||||
import com.idormy.sms.forwarder.fragment.TasksFragment
|
||||
import com.idormy.sms.forwarder.service.ForegroundService
|
||||
import com.idormy.sms.forwarder.utils.ACTION_START
|
||||
import com.idormy.sms.forwarder.utils.CommonUtils.Companion.restartApplication
|
||||
import com.idormy.sms.forwarder.utils.EVENT_LOAD_APP_LIST
|
||||
import com.idormy.sms.forwarder.utils.FRPC_LIB_DOWNLOAD_URL
|
||||
@ -50,6 +51,7 @@ import com.jeremyliao.liveeventbus.LiveEventBus
|
||||
import com.xuexiang.xhttp2.XHttp
|
||||
import com.xuexiang.xhttp2.callback.DownloadProgressCallBack
|
||||
import com.xuexiang.xhttp2.exception.ApiException
|
||||
import com.xuexiang.xui.XUI.getContext
|
||||
import com.xuexiang.xui.utils.ResUtils
|
||||
import com.xuexiang.xui.utils.ThemeUtils
|
||||
import com.xuexiang.xui.utils.ViewUtils
|
||||
@ -63,7 +65,6 @@ import com.yarolegovich.slidingrootnav.SlideGravity
|
||||
import com.yarolegovich.slidingrootnav.SlidingRootNav
|
||||
import com.yarolegovich.slidingrootnav.SlidingRootNavBuilder
|
||||
import com.yarolegovich.slidingrootnav.callback.DragStateListener
|
||||
import frpclib.Frpclib
|
||||
import java.io.File
|
||||
|
||||
@Suppress("PrivatePropertyName", "unused", "DEPRECATION")
|
||||
@ -122,7 +123,7 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(), DrawerAdapter.OnItemS
|
||||
//启动前台服务
|
||||
if (!ForegroundService.isRunning) {
|
||||
val serviceIntent = Intent(this, ForegroundService::class.java)
|
||||
serviceIntent.action = "START"
|
||||
serviceIntent.action = ACTION_START
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForegroundService(serviceIntent)
|
||||
} else {
|
||||
@ -179,7 +180,7 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(), DrawerAdapter.OnItemS
|
||||
//仅当开启自动检查且有网络时自动检查更新/获取提示
|
||||
if (SettingUtils.autoCheckUpdate && NetworkUtils.isHaveInternet()) {
|
||||
showTips(this)
|
||||
XUpdateInit.checkUpdate(this, false)
|
||||
XUpdateInit.checkUpdate(this, false, SettingUtils.joinPreviewProgram)
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,10 +208,6 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(), DrawerAdapter.OnItemS
|
||||
private fun initSlidingMenu(savedInstanceState: Bundle?) {
|
||||
mSlidingRootNav = SlidingRootNavBuilder(this).withGravity(if (ResUtils.isRtl(this)) SlideGravity.RIGHT else SlideGravity.LEFT).withMenuOpened(false).withContentClickableWhenMenuOpened(false).withSavedState(savedInstanceState).withMenuLayout(R.layout.menu_left_drawer).inject()
|
||||
mLLMenu = mSlidingRootNav.layout.findViewById(R.id.ll_menu)
|
||||
//val ivQrcode = mSlidingRootNav.layout.findViewById<AppCompatImageView>(R.id.iv_qrcode)
|
||||
//ivQrcode.setOnClickListener { openNewPage(SettingsFragment::class.java) }
|
||||
//val ivSetting = mSlidingRootNav.layout.findViewById<AppCompatImageView>(R.id.iv_setting)
|
||||
//ivSetting.setOnClickListener { openNewPage(SettingsFragment::class.java) }
|
||||
ViewUtils.setVisibility(mLLMenu, false)
|
||||
mAdapter = DrawerAdapter(
|
||||
mutableListOf(
|
||||
@ -243,25 +240,6 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(), DrawerAdapter.OnItemS
|
||||
|
||||
override fun onDragEnd(isMenuOpened: Boolean) {
|
||||
ViewUtils.setVisibility(mLLMenu, isMenuOpened)
|
||||
/*if (isMenuOpened) {
|
||||
if (!GuideCaseView.isShowOnce(this@MainActivity, getString(R.string.guide_key_sliding_root_navigation))) {
|
||||
val guideStep1 = GuideCaseView.Builder(this@MainActivity)
|
||||
.title("点击进入,可切换主题样式哦~~")
|
||||
.titleSize(18, TypedValue.COMPLEX_UNIT_SP)
|
||||
.focusOn(ivSetting)
|
||||
.build()
|
||||
val guideStep2 = GuideCaseView.Builder(this@MainActivity)
|
||||
.title("点击进入,扫码关注哦~~")
|
||||
.titleSize(18, TypedValue.COMPLEX_UNIT_SP)
|
||||
.focusOn(ivQrcode)
|
||||
.build()
|
||||
GuideCaseQueue()
|
||||
.add(guideStep1)
|
||||
.add(guideStep2)
|
||||
.show()
|
||||
GuideCaseView.setShowOnce(this@MainActivity, getString(R.string.guide_key_sliding_root_navigation))
|
||||
}
|
||||
}*/
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -279,7 +257,7 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(), DrawerAdapter.OnItemS
|
||||
POS_SERVER -> openNewPage(ServerFragment::class.java)
|
||||
POS_CLIENT -> openNewPage(ClientFragment::class.java)
|
||||
POS_FRPC -> {
|
||||
if (FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so") && FRPC_LIB_VERSION == Frpclib.getVersion()) {
|
||||
if (App.FrpclibInited) {
|
||||
openNewPage(FrpcFragment::class.java)
|
||||
return
|
||||
}
|
||||
@ -302,14 +280,26 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(), DrawerAdapter.OnItemS
|
||||
}
|
||||
|
||||
POS_APPS -> {
|
||||
if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) {
|
||||
XToastUtils.info(getString(R.string.loading_app_list))
|
||||
val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build()
|
||||
WorkManager.getInstance(this).enqueue(request)
|
||||
needToAppListFragment = true
|
||||
return
|
||||
}
|
||||
openNewPage(AppListFragment::class.java)
|
||||
//检查读取应用列表权限是否获取
|
||||
XXPermissions.with(this).permission(Permission.GET_INSTALLED_APPS).request(object : OnPermissionCallback {
|
||||
override fun onGranted(permissions: MutableList<String>, allGranted: Boolean) {
|
||||
if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) {
|
||||
XToastUtils.info(getString(R.string.loading_app_list))
|
||||
val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build()
|
||||
WorkManager.getInstance(getContext()).enqueue(request)
|
||||
needToAppListFragment = true
|
||||
return
|
||||
}
|
||||
openNewPage(AppListFragment::class.java)
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: MutableList<String>, doNotAskAgain: Boolean) {
|
||||
XToastUtils.error(R.string.tips_get_installed_apps)
|
||||
if (doNotAskAgain) {
|
||||
XXPermissions.startPermissionActivity(getContext(), permissions)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
POS_HELP -> AgentWebActivity.goWeb(this, getString(R.string.url_help))
|
||||
@ -348,6 +338,7 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(), DrawerAdapter.OnItemS
|
||||
.build()
|
||||
|
||||
XHttp.downLoad(downloadUrl)
|
||||
.ignoreHttpsCert()
|
||||
.savePath(cacheDir.absolutePath)
|
||||
.execute(object : DownloadProgressCallBack<String?>() {
|
||||
override fun onStart() {
|
||||
@ -388,4 +379,4 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(), DrawerAdapter.OnItemS
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,122 @@
|
||||
package com.idormy.sms.forwarder.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothClass
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.idormy.sms.forwarder.R
|
||||
|
||||
class BluetoothRecyclerAdapter(
|
||||
private val itemList: List<BluetoothDevice>,
|
||||
private var itemClickListener: ((Int) -> Unit)? = null,
|
||||
private var removeClickListener: ((Int) -> Unit)? = null,
|
||||
private var editClickListener: ((Int) -> Unit)? = null,
|
||||
) : RecyclerView.Adapter<BluetoothRecyclerAdapter.ViewHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.adapter_bluetooth_list_item, parent, false)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = itemList[position]
|
||||
holder.bind(item)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = itemList.size
|
||||
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
|
||||
private val textDeviceName: TextView = itemView.findViewById(R.id.text_device_name)
|
||||
private val textDeviceAddress: TextView = itemView.findViewById(R.id.text_device_address)
|
||||
private val imageDeviceIcon: ImageView = itemView.findViewById(R.id.image_device_icon)
|
||||
private val editIcon: ImageView = itemView.findViewById(R.id.iv_edit)
|
||||
private val removeIcon: ImageView = itemView.findViewById(R.id.iv_remove)
|
||||
|
||||
init {
|
||||
if (removeClickListener == null) {
|
||||
removeIcon.visibility = View.GONE
|
||||
} else {
|
||||
removeIcon.setOnClickListener(this)
|
||||
}
|
||||
|
||||
if (editClickListener == null) {
|
||||
editIcon.visibility = View.GONE
|
||||
} else {
|
||||
editIcon.setOnClickListener(this)
|
||||
}
|
||||
|
||||
if (itemClickListener != null) {
|
||||
itemView.setOnClickListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun bind(device: BluetoothDevice) {
|
||||
// 设置设备名称和地址
|
||||
textDeviceName.text = device.name ?: "Unknown Device"
|
||||
textDeviceAddress.text = device.address
|
||||
|
||||
// 根据设备类型设置图标
|
||||
val deviceType = getDeviceType(device)
|
||||
val iconResId = when (deviceType) {
|
||||
DeviceType.CELLPHONE -> R.drawable.ic_bt_cellphone
|
||||
DeviceType.HEADPHONES -> R.drawable.ic_bt_headphones
|
||||
DeviceType.HEADSET_HFP -> R.drawable.ic_bt_headset_hfp
|
||||
DeviceType.IMAGING -> R.drawable.ic_bt_imaging
|
||||
DeviceType.LAPTOP -> R.drawable.ic_bt_laptop
|
||||
DeviceType.MISC_HID -> R.drawable.ic_bt_misc_hid
|
||||
DeviceType.NETWORK_PAN -> R.drawable.ic_bt_network_pan
|
||||
DeviceType.WRISTBAND -> R.drawable.ic_bt_wristband
|
||||
else -> R.drawable.ic_bt_bluetooth
|
||||
}
|
||||
imageDeviceIcon.setImageResource(iconResId)
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
val position = adapterPosition
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
when (v?.id) {
|
||||
R.id.iv_edit -> editClickListener?.let { it(position) }
|
||||
R.id.iv_remove -> removeClickListener?.let { it(position) }
|
||||
else -> itemClickListener?.let { it(position) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun getDeviceType(device: BluetoothDevice): DeviceType {
|
||||
val deviceClass = device.bluetoothClass?.majorDeviceClass ?: BluetoothClass.Device.Major.MISC
|
||||
@Suppress("DUPLICATE_LABEL_IN_WHEN")
|
||||
return when (deviceClass) {
|
||||
BluetoothClass.Device.Major.PHONE -> DeviceType.CELLPHONE
|
||||
BluetoothClass.Device.Major.AUDIO_VIDEO -> DeviceType.HEADPHONES
|
||||
BluetoothClass.Device.Major.PERIPHERAL -> DeviceType.HEADSET_HFP
|
||||
BluetoothClass.Device.Major.IMAGING -> DeviceType.IMAGING
|
||||
BluetoothClass.Device.Major.COMPUTER -> DeviceType.LAPTOP
|
||||
BluetoothClass.Device.Major.PERIPHERAL -> DeviceType.MISC_HID
|
||||
BluetoothClass.Device.Major.NETWORKING -> DeviceType.NETWORK_PAN
|
||||
BluetoothClass.Device.Major.WEARABLE -> DeviceType.WRISTBAND
|
||||
else -> DeviceType.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class DeviceType {
|
||||
CELLPHONE,
|
||||
HEADPHONES,
|
||||
HEADSET_HFP,
|
||||
IMAGING,
|
||||
LAPTOP,
|
||||
MISC_HID,
|
||||
NETWORK_PAN,
|
||||
WRISTBAND,
|
||||
UNKNOWN
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import android.view.ViewGroup
|
||||
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.FrpcPagingAdapter.MyViewHolder
|
||||
import com.idormy.sms.forwarder.database.entity.Frpc
|
||||
@ -31,7 +32,7 @@ class FrpcPagingAdapter(private val itemClickListener: OnItemClickListener) : Pa
|
||||
holder.binding.tvUid.text = "UID:${item.uid}"
|
||||
holder.binding.tvName.text = item.name
|
||||
|
||||
if (item.connecting || Frpclib.isRunning(item.uid)) {
|
||||
if (item.connecting || (App.FrpclibInited && Frpclib.isRunning(item.uid))) {
|
||||
holder.binding.ivPlay.setImageResource(R.drawable.ic_stop)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
holder.binding.ivPlay.imageTintList = getColors(R.color.colorStop)
|
||||
|
@ -29,7 +29,7 @@ class RulePagingAdapter(private val itemClickListener: OnItemClickListener) : Pa
|
||||
if (item != null) {
|
||||
holder.binding.ivRuleImage.setImageResource(item.imageId)
|
||||
holder.binding.ivRuleStatus.setImageResource(item.statusImageId)
|
||||
holder.binding.tvRuleMatch.text = item.ruleMatch
|
||||
holder.binding.tvRuleMatch.text = item.getName(false)
|
||||
|
||||
holder.binding.layoutSenders.removeAllViews()
|
||||
for (sender in item.senderList) {
|
||||
|
@ -78,7 +78,7 @@ class RuleRecyclerAdapter(
|
||||
}
|
||||
image.setImageResource(icon)
|
||||
status.setImageResource(rule.statusImageId)
|
||||
title.text = rule.name
|
||||
title.text = rule.getName()
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
|
@ -0,0 +1,104 @@
|
||||
package com.idormy.sms.forwarder.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.adapter.base.ItemMoveCallback
|
||||
import com.idormy.sms.forwarder.database.entity.Task
|
||||
import java.util.Collections
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class TaskRecyclerAdapter(
|
||||
var itemList: MutableList<Task>,
|
||||
private var removeClickListener: ((Int) -> Unit)? = null,
|
||||
private var editClickListener: ((Int) -> Unit)? = null,
|
||||
) : RecyclerView.Adapter<TaskRecyclerAdapter.ViewHolder>(), ItemMoveCallback.Listener {
|
||||
|
||||
private lateinit var touchHelper: ItemTouchHelper
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.adapter_task_list_item, parent, false)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = itemList[position]
|
||||
holder.bind(item)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = itemList.size
|
||||
|
||||
fun setTouchHelper(touchHelper: ItemTouchHelper) {
|
||||
this@TaskRecyclerAdapter.touchHelper = touchHelper
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
|
||||
private val image: ImageView = itemView.findViewById(R.id.iv_image)
|
||||
private val status: ImageView = itemView.findViewById(R.id.iv_status)
|
||||
private val title: TextView = itemView.findViewById(R.id.tv_title)
|
||||
private val editIcon: ImageView = itemView.findViewById(R.id.iv_edit)
|
||||
private val removeIcon: ImageView = itemView.findViewById(R.id.iv_remove)
|
||||
private val dragIcon: ImageView = itemView.findViewById(R.id.iv_drag)
|
||||
|
||||
init {
|
||||
if (removeClickListener == null) {
|
||||
removeIcon.visibility = View.GONE
|
||||
} else {
|
||||
removeIcon.setOnClickListener(this)
|
||||
}
|
||||
|
||||
if (editClickListener == null) {
|
||||
editIcon.visibility = View.GONE
|
||||
} else {
|
||||
editIcon.setOnClickListener(this)
|
||||
}
|
||||
|
||||
dragIcon.setOnTouchListener { _, event ->
|
||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
touchHelper.startDrag(this)
|
||||
}
|
||||
return@setOnTouchListener false
|
||||
}
|
||||
}
|
||||
|
||||
fun bind(task: Task) {
|
||||
image.setImageResource(task.imageId)
|
||||
status.setImageResource(task.statusImageId)
|
||||
title.text = task.name
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
val position = adapterPosition
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
when (v?.id) {
|
||||
R.id.iv_edit -> editClickListener?.let { it(position) }
|
||||
R.id.iv_remove -> removeClickListener?.let { it(position) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemMove(fromPosition: Int, toPosition: Int) {
|
||||
if (fromPosition < toPosition) {
|
||||
for (i in fromPosition until toPosition) {
|
||||
Collections.swap(itemList, i, i + 1)
|
||||
}
|
||||
} else {
|
||||
for (i in fromPosition downTo toPosition + 1) {
|
||||
Collections.swap(itemList, i, i - 1)
|
||||
}
|
||||
}
|
||||
notifyItemMoved(fromPosition, toPosition)
|
||||
}
|
||||
|
||||
override fun onDragFinished() {}
|
||||
}
|
||||
|
@ -0,0 +1,167 @@
|
||||
package com.idormy.sms.forwarder.adapter.spinner
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.text.Html
|
||||
import android.text.TextUtils
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.STATUS_OFF
|
||||
import com.xuexiang.xui.utils.CollectionUtils
|
||||
import com.xuexiang.xui.widget.spinner.editspinner.BaseEditSpinnerAdapter
|
||||
import com.xuexiang.xui.widget.spinner.editspinner.EditSpinnerFilter
|
||||
import com.xuexiang.xutil.resource.ResUtils.getDrawable
|
||||
|
||||
@Suppress("unused", "NAME_SHADOWING", "DEPRECATION")
|
||||
class TaskSpinnerAdapter<T> : BaseEditSpinnerAdapter<T>, EditSpinnerFilter {
|
||||
/**
|
||||
* 选项的文字颜色
|
||||
*/
|
||||
private var mTextColor = 0
|
||||
|
||||
/**
|
||||
* 选项的文字大小
|
||||
*/
|
||||
private var mTextSize = 0f
|
||||
|
||||
/**
|
||||
* 背景颜色
|
||||
*/
|
||||
private var mBackgroundSelector = 0
|
||||
|
||||
/**
|
||||
* 过滤关键词的选中颜色
|
||||
*/
|
||||
private var mFilterColor = "#F15C58"
|
||||
private var mIsFilterKey = false
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* @param data 选项数据
|
||||
*/
|
||||
constructor(data: List<T>?) : super(data)
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* @param data 选项数据
|
||||
*/
|
||||
constructor(data: Array<T>?) : super(data)
|
||||
|
||||
override fun getEditSpinnerFilter(): EditSpinnerFilter {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? {
|
||||
var convertView = convertView
|
||||
val holder: ViewHolder
|
||||
if (convertView == null) {
|
||||
convertView = LayoutInflater.from(parent.context).inflate(R.layout.item_spinner_with_icon, parent, false)
|
||||
holder = ViewHolder(convertView, mTextColor, mTextSize, mBackgroundSelector)
|
||||
convertView.tag = holder
|
||||
} else {
|
||||
holder = convertView.tag as ViewHolder
|
||||
}
|
||||
val item = CollectionUtils.getListItem(mDataSource, mIndexs[position]) as TaskSpinnerItem
|
||||
holder.iconView.setImageDrawable(item.icon)
|
||||
holder.statusView.setImageDrawable(
|
||||
getDrawable(
|
||||
when (item.status) {
|
||||
STATUS_OFF -> R.drawable.ic_stop
|
||||
else -> R.drawable.ic_start
|
||||
}
|
||||
)
|
||||
)
|
||||
//holder.titleView.text = Html.fromHtml(item.toString())
|
||||
holder.titleView.text = Html.fromHtml(getItem(position))
|
||||
return convertView
|
||||
}
|
||||
|
||||
override fun onFilter(keyword: String): Boolean {
|
||||
mDisplayData.clear()
|
||||
Log.d("TaskSpinnerAdapter", "keyword = $keyword")
|
||||
Log.d("TaskSpinnerAdapter", "mIndexs.indices = ${mIndexs.indices}")
|
||||
if (TextUtils.isEmpty(keyword)) {
|
||||
initDisplayData(mDataSource)
|
||||
for (i in mIndexs.indices) {
|
||||
mIndexs[i] = i
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
for (i in mDataSource.indices) {
|
||||
if (getDataSourceString(i).contains(keyword, ignoreCase = true)) {
|
||||
mIndexs[mDisplayData.size] = i
|
||||
if (mIsFilterKey) {
|
||||
mDisplayData.add(getDataSourceString(i).replaceFirst(keyword.toRegex(), "<font color=\"$mFilterColor\">$keyword</font>"))
|
||||
} else {
|
||||
mDisplayData.add(getDataSourceString(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e("TaskSpinnerAdapter", "onFilter error: ${e.message}")
|
||||
}
|
||||
}
|
||||
Log.d("TaskSpinnerAdapter", "mDisplayData = $mDisplayData")
|
||||
notifyDataSetChanged()
|
||||
return mDisplayData.size > 0
|
||||
}
|
||||
|
||||
fun setTextColor(@ColorInt textColor: Int): TaskSpinnerAdapter<*> {
|
||||
mTextColor = textColor
|
||||
return this
|
||||
}
|
||||
|
||||
fun setTextSize(textSize: Float): TaskSpinnerAdapter<*> {
|
||||
mTextSize = textSize
|
||||
return this
|
||||
}
|
||||
|
||||
fun setBackgroundSelector(@DrawableRes backgroundSelector: Int): TaskSpinnerAdapter<*> {
|
||||
mBackgroundSelector = backgroundSelector
|
||||
return this
|
||||
}
|
||||
|
||||
fun setFilterColor(filterColor: String): TaskSpinnerAdapter<*> {
|
||||
mFilterColor = filterColor
|
||||
return this
|
||||
}
|
||||
|
||||
fun setIsFilterKey(isFilterKey: Boolean): TaskSpinnerAdapter<*> {
|
||||
mIsFilterKey = isFilterKey
|
||||
return this
|
||||
}
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
private class ViewHolder(convertView: View, @ColorInt textColor: Int, textSize: Float, @DrawableRes backgroundSelector: Int) {
|
||||
val iconView: ImageView = convertView.findViewById(R.id.iv_icon)
|
||||
val statusView: ImageView = convertView.findViewById(R.id.iv_status)
|
||||
val titleView: TextView = convertView.findViewById(R.id.tv_title)
|
||||
|
||||
init {
|
||||
if (textColor > 0) titleView.setTextColor(textColor)
|
||||
if (textSize > 0F) titleView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
if (backgroundSelector != 0) titleView.setBackgroundResource(backgroundSelector)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
val config = convertView.resources.configuration
|
||||
if (config.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
|
||||
titleView.textDirection = View.TEXT_DIRECTION_RTL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getItemSource(position: Int): T {
|
||||
return mDataSource[mIndexs[position]]
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.idormy.sms.forwarder.adapter.spinner
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
@Suppress("unused")
|
||||
class TaskSpinnerItem(
|
||||
var title: CharSequence,
|
||||
var icon: Drawable? = null,
|
||||
var id: Long? = 0L,
|
||||
var status: Int? = 1
|
||||
) {
|
||||
|
||||
fun setTitle(title: CharSequence): TaskSpinnerItem {
|
||||
this.title = title
|
||||
return this
|
||||
}
|
||||
|
||||
fun setIcon(icon: Drawable?): TaskSpinnerItem {
|
||||
this.icon = icon
|
||||
return this
|
||||
}
|
||||
|
||||
fun setId(id: Long): TaskSpinnerItem {
|
||||
this.id = id
|
||||
return this
|
||||
}
|
||||
|
||||
fun setStatus(status: Int): TaskSpinnerItem {
|
||||
this.status = status
|
||||
return this
|
||||
}
|
||||
|
||||
// 注意:自定义实体需要重写对象的 toString 方法
|
||||
override fun toString(): String {
|
||||
return title.toString()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun of(title: CharSequence): TaskSpinnerItem {
|
||||
return TaskSpinnerItem(title)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun arrayOf(vararg titles: CharSequence): Array<TaskSpinnerItem> {
|
||||
return titles.map { TaskSpinnerItem(it) }.toTypedArray()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package com.idormy.sms.forwarder.core.http.api
|
||||
|
||||
import com.idormy.sms.forwarder.core.http.entity.TipInfo
|
||||
import com.xuexiang.xhttp2.model.ApiResult
|
||||
import io.reactivex.Observable
|
||||
import retrofit2.http.GET
|
||||
|
||||
/**
|
||||
* @author xuexiang
|
||||
* @since 2021/1/9 7:01 PM
|
||||
*/
|
||||
@Suppress("unused")
|
||||
class ApiService {
|
||||
/**
|
||||
* 使用的是retrofit的接口定义
|
||||
*/
|
||||
interface IGetService {
|
||||
/**
|
||||
* 获得小贴士
|
||||
*/
|
||||
@get:GET("/pp/SmsForwarder.wiki/raw/master/tips.json")
|
||||
val tips: Observable<ApiResult<List<TipInfo?>?>>
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package com.idormy.sms.forwarder.core.http.callback
|
||||
|
||||
import com.xuexiang.xhttp2.callback.SimpleCallBack
|
||||
import com.xuexiang.xhttp2.exception.ApiException
|
||||
import com.xuexiang.xhttp2.model.XHttpRequest
|
||||
import com.xuexiang.xutil.common.StringUtils
|
||||
import com.xuexiang.xutil.common.logger.Logger
|
||||
|
||||
/**
|
||||
* 不带错误提示的网络请求回调
|
||||
*
|
||||
* @author xuexiang
|
||||
* @since 2019-11-18 23:02
|
||||
*/
|
||||
@Suppress("unused")
|
||||
abstract class NoTipCallBack<T> : SimpleCallBack<T> {
|
||||
/**
|
||||
* 记录一下请求的url,确定出错的请求是哪个请求
|
||||
*/
|
||||
private var mUrl: String? = null
|
||||
|
||||
constructor()
|
||||
constructor(req: XHttpRequest) : this(req.url)
|
||||
constructor(url: String?) {
|
||||
mUrl = url
|
||||
}
|
||||
|
||||
override fun onError(e: ApiException) {
|
||||
if (!StringUtils.isEmpty(mUrl)) {
|
||||
Logger.e("Request Url: $mUrl", e)
|
||||
} else {
|
||||
Logger.e(e)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package com.idormy.sms.forwarder.core.http.callback
|
||||
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.xuexiang.xhttp2.callback.SimpleCallBack
|
||||
import com.xuexiang.xhttp2.exception.ApiException
|
||||
import com.xuexiang.xhttp2.model.XHttpRequest
|
||||
import com.xuexiang.xutil.common.StringUtils
|
||||
import com.xuexiang.xutil.common.logger.Logger
|
||||
|
||||
/**
|
||||
* 带错误toast提示的网络请求回调
|
||||
*
|
||||
* @author xuexiang
|
||||
* @since 2019-11-18 23:02
|
||||
*/
|
||||
@Suppress("unused")
|
||||
abstract class TipCallBack<T> : SimpleCallBack<T> {
|
||||
/**
|
||||
* 记录一下请求的url,确定出错的请求是哪个请求
|
||||
*/
|
||||
private var mUrl: String? = null
|
||||
|
||||
constructor()
|
||||
constructor(req: XHttpRequest) : this(req.url)
|
||||
constructor(url: String?) {
|
||||
mUrl = url
|
||||
}
|
||||
|
||||
override fun onError(e: ApiException) {
|
||||
XToastUtils.error(e)
|
||||
if (!StringUtils.isEmpty(mUrl)) {
|
||||
Logger.e("Request Url: $mUrl", e)
|
||||
} else {
|
||||
Logger.e(e)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package com.idormy.sms.forwarder.core.http.callback
|
||||
|
||||
import com.idormy.sms.forwarder.core.BaseFragment
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.xuexiang.xhttp2.callback.ProgressLoadingCallBack
|
||||
import com.xuexiang.xhttp2.exception.ApiException
|
||||
import com.xuexiang.xhttp2.model.XHttpRequest
|
||||
import com.xuexiang.xhttp2.subsciber.impl.IProgressLoader
|
||||
import com.xuexiang.xutil.common.StringUtils
|
||||
import com.xuexiang.xutil.common.logger.Logger
|
||||
|
||||
/**
|
||||
* 带错误toast提示和加载进度条的网络请求回调
|
||||
*
|
||||
* @author xuexiang
|
||||
* @since 2019-11-18 23:16
|
||||
*/
|
||||
@Suppress("unused")
|
||||
abstract class TipProgressLoadingCallBack<T> : ProgressLoadingCallBack<T> {
|
||||
/**
|
||||
* 记录一下请求的url,确定出错的请求是哪个请求
|
||||
*/
|
||||
private var mUrl: String? = null
|
||||
|
||||
constructor(fragment: BaseFragment<*>) : super(fragment.progressLoader)
|
||||
constructor(iProgressLoader: IProgressLoader?) : super(iProgressLoader)
|
||||
constructor(req: XHttpRequest, iProgressLoader: IProgressLoader?) : this(
|
||||
req.url,
|
||||
iProgressLoader
|
||||
)
|
||||
|
||||
constructor(url: String?, iProgressLoader: IProgressLoader?) : super(iProgressLoader) {
|
||||
mUrl = url
|
||||
}
|
||||
|
||||
override fun onError(e: ApiException) {
|
||||
super.onError(e)
|
||||
XToastUtils.error(e)
|
||||
if (!StringUtils.isEmpty(mUrl)) {
|
||||
Logger.e("Request Url: $mUrl", e)
|
||||
} else {
|
||||
Logger.e(e)
|
||||
}
|
||||
}
|
||||
}
|
@ -2,13 +2,13 @@ package com.idormy.sms.forwarder.core.webview
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.core.webview.WebViewInterceptDialog.Companion.show
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.just.agentweb.core.client.MiddlewareWebClientBase
|
||||
import com.xuexiang.xutil.resource.ResUtils.getStringArray
|
||||
import java.util.Locale
|
||||
|
@ -22,11 +22,13 @@ import com.idormy.sms.forwarder.database.entity.Sender
|
||||
import com.idormy.sms.forwarder.database.entity.Task
|
||||
import com.idormy.sms.forwarder.database.ext.ConvertersDate
|
||||
import com.idormy.sms.forwarder.utils.DATABASE_NAME
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.idormy.sms.forwarder.utils.TAG_LIST
|
||||
|
||||
@Database(
|
||||
entities = [Frpc::class, Msg::class, Logs::class, Rule::class, Sender::class, Task::class],
|
||||
views = [LogsDetail::class],
|
||||
version = 18,
|
||||
version = 20,
|
||||
exportSchema = false
|
||||
)
|
||||
@TypeConverters(ConvertersDate::class)
|
||||
@ -107,6 +109,8 @@ custom_domains = smsf.demo.com
|
||||
MIGRATION_15_16,
|
||||
MIGRATION_16_17,
|
||||
MIGRATION_17_18,
|
||||
MIGRATION_18_19,
|
||||
MIGRATION_19_20,
|
||||
)
|
||||
|
||||
/*if (BuildConfig.DEBUG) {
|
||||
@ -194,33 +198,38 @@ CREATE TABLE "Frpc" (
|
||||
)
|
||||
database.execSQL(
|
||||
"""
|
||||
INSERT INTO "Frpc" VALUES ('830b0a0e-c2b3-4f95-b3c9-55db12923d2e', '远程控制SmsForwarder', '[common]
|
||||
INSERT INTO "Frpc" VALUES ('830b0a0e-c2b3-4f95-b3c9-55db12923d2e', '远程控制SmsForwarder', '
|
||||
#frps服务端公网IP
|
||||
server_addr = 88.88.88.88
|
||||
serverAddr = "88.88.88.88"
|
||||
#frps服务端公网端口
|
||||
server_port = 8888
|
||||
#可选,建议启用
|
||||
token = 88888888
|
||||
serverPort = 8888
|
||||
#连接服务端的超时时间(增大时间避免frpc在网络未就绪的情况下启动失败)
|
||||
dial_server_timeout = 60
|
||||
transport.dialServerTimeout = 60
|
||||
#第一次登陆失败后是否退出
|
||||
login_fail_exit = false
|
||||
loginFailExit = false
|
||||
#可选,建议启用
|
||||
auth.method = "token"
|
||||
auth.token = "88888888"
|
||||
|
||||
#[二选一即可]每台机器不可重复,通过 http://88.88.88.88:5000 访问
|
||||
[SmsForwarder-TCP]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 5000
|
||||
#只要修改下面这一行(frps所在服务器必须暴露的公网端口)
|
||||
remote_port = 5000
|
||||
#[二选一即可]每台机器的 name 和 remotePort 不可重复,通过 http://88.88.88.88:5000 访问
|
||||
[[proxies]]
|
||||
#同一个frps下,多台设备的 name 不可重复
|
||||
name = "SmsForwarder-TCP-001"
|
||||
type = "tcp"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 5000
|
||||
#只要修改下面这一行(frps所在服务器必须暴露且防火墙放行的公网端口,同一个frps下不可重复)
|
||||
remotePort = 5000
|
||||
|
||||
#[二选一即可]每台机器不可重复,通过 http://smsf.demo.com 访问
|
||||
[SmsForwarder-HTTP]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 5000
|
||||
#[二选一即可]每台机器的 name 和 customDomains 不可重复,通过 http://smsf.demo.com 访问
|
||||
[[proxies]]
|
||||
#同一个frps下,多台设备的 name 不可重复
|
||||
name = "SmsForwarder-HTTP-001"
|
||||
type = "http"
|
||||
localPort = 5000
|
||||
#只要修改下面这一行(在frps端将域名反代到vhost_http_port)
|
||||
custom_domains = smsf.demo.com
|
||||
customDomains = ["smsf.demo.com"]
|
||||
|
||||
', 0, '1651334400000')
|
||||
""".trimIndent()
|
||||
)
|
||||
@ -408,7 +417,46 @@ CREATE TABLE "Task" (
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
//TODO:原来的电量/网络/SIM卡状态转换为自动化任务
|
||||
}
|
||||
}
|
||||
|
||||
//自定义模板可用变量统一成英文标签
|
||||
private val MIGRATION_18_19 = object : Migration(18, 19) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
//替换自定义模板标签
|
||||
var smsTemplate = SettingUtils.smsTemplate
|
||||
//替换Rule.sms_template中的标签
|
||||
var ruleColumnCN = "sms_template"
|
||||
var ruleColumnTW = "sms_template"
|
||||
//替换Sender.json_setting中的标签
|
||||
var senderColumnCN = "json_setting"
|
||||
var senderColumnTW = "json_setting"
|
||||
|
||||
for (i in TAG_LIST.indices) {
|
||||
val tagCN = TAG_LIST[i]["zh_CN"].toString()
|
||||
val tagTW = TAG_LIST[i]["zh_TW"].toString()
|
||||
val tagEN = TAG_LIST[i]["en"].toString()
|
||||
smsTemplate = smsTemplate.replace(tagCN, tagEN)
|
||||
ruleColumnCN = "REPLACE($ruleColumnCN, '$tagCN', '$tagEN')"
|
||||
ruleColumnTW = "REPLACE($ruleColumnTW, '$tagTW', '$tagEN')"
|
||||
senderColumnCN = "REPLACE($senderColumnCN, '$tagCN', '$tagEN')"
|
||||
senderColumnTW = "REPLACE($senderColumnTW, '$tagTW', '$tagEN')"
|
||||
}
|
||||
|
||||
database.execSQL("UPDATE Rule SET sms_template = $ruleColumnCN WHERE sms_template != ''")
|
||||
database.execSQL("UPDATE Rule SET sms_template = $ruleColumnTW WHERE sms_template != ''")
|
||||
|
||||
database.execSQL("UPDATE Sender SET json_setting = $senderColumnCN WHERE type NOT IN (4, 5, 6, 7, 8, 14)")
|
||||
database.execSQL("UPDATE Sender SET json_setting = $senderColumnTW WHERE type NOT IN (4, 5, 6, 7, 8, 14)")
|
||||
|
||||
SettingUtils.smsTemplate = smsTemplate
|
||||
}
|
||||
}
|
||||
|
||||
//免打扰星期段
|
||||
private val MIGRATION_19_20 = object : Migration(19, 20) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("Alter table rule add column silent_day_of_week TEXT NOT NULL DEFAULT '' ")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,8 +6,10 @@ import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.RawQuery
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import androidx.sqlite.db.SupportSQLiteQuery
|
||||
import com.idormy.sms.forwarder.database.entity.Logs
|
||||
import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender
|
||||
import io.reactivex.Completable
|
||||
@ -67,4 +69,7 @@ interface LogsDao {
|
||||
@Query("SELECT * FROM Logs WHERE type = :type ORDER BY id DESC")
|
||||
fun pagingSource(type: String): PagingSource<Int, LogsAndRuleAndSender>
|
||||
|
||||
@Transaction
|
||||
@RawQuery(observedEntities = [Logs::class])
|
||||
fun getLogsRaw(query: SupportSQLiteQuery): List<Logs>
|
||||
}
|
@ -1,7 +1,15 @@
|
||||
package com.idormy.sms.forwarder.database.dao
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.RawQuery
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import androidx.sqlite.db.SupportSQLiteQuery
|
||||
import com.idormy.sms.forwarder.database.entity.Msg
|
||||
import com.idormy.sms.forwarder.database.entity.MsgAndLogs
|
||||
import io.reactivex.Completable
|
||||
@ -19,8 +27,8 @@ interface MsgDao {
|
||||
@Query("DELETE FROM Msg where id=:id")
|
||||
fun delete(id: Long)
|
||||
|
||||
@Query("DELETE FROM Msg where type=:type")
|
||||
fun deleteAll(type: String): Completable
|
||||
@RawQuery
|
||||
fun deleteAll(sql: SupportSQLiteQuery): Int
|
||||
|
||||
@Query("DELETE FROM Msg")
|
||||
fun deleteAll()
|
||||
@ -41,4 +49,8 @@ interface MsgDao {
|
||||
@Query("SELECT * FROM Msg WHERE type = :type ORDER BY id DESC")
|
||||
fun pagingSource(type: String): PagingSource<Int, MsgAndLogs>
|
||||
|
||||
@Transaction
|
||||
@RawQuery(observedEntities = [MsgAndLogs::class])
|
||||
fun pagingSource(query: SupportSQLiteQuery): PagingSource<Int, MsgAndLogs>
|
||||
|
||||
}
|
@ -34,6 +34,9 @@ interface TaskDao {
|
||||
@Query("UPDATE Task SET status = :status WHERE id = :id")
|
||||
fun updateStatus(id: Long, status: Int)
|
||||
|
||||
@Query("UPDATE Task SET status=:status WHERE id IN (:ids)")
|
||||
fun updateStatusByIds(ids: List<Long>, status: Int)
|
||||
|
||||
@Query("SELECT * FROM Task where id=:id")
|
||||
fun get(id: Long): Single<Task>
|
||||
|
||||
@ -46,6 +49,9 @@ interface TaskDao {
|
||||
@Query("SELECT * FROM Task where type >= 1000 ORDER BY id DESC")
|
||||
fun pagingSourceMine(): PagingSource<Int, Task>
|
||||
|
||||
@Query("SELECT * FROM Task ORDER BY id DESC")
|
||||
fun getAll(): Single<List<Task>>
|
||||
|
||||
@Transaction
|
||||
@RawQuery(observedEntities = [Task::class])
|
||||
fun getAllRaw(query: SupportSQLiteQuery): List<Task>
|
||||
|
@ -5,6 +5,7 @@ import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.PrimaryKey
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.utils.STATUS_OFF
|
||||
import com.idormy.sms.forwarder.utils.STATUS_ON
|
||||
@ -34,6 +35,6 @@ data class Frpc(
|
||||
}
|
||||
|
||||
val status: Int
|
||||
get() = if (connecting || Frpclib.isRunning(uid)) STATUS_ON else STATUS_OFF
|
||||
get() = if (connecting || (App.FrpclibInited && Frpclib.isRunning(uid))) STATUS_ON else STATUS_OFF
|
||||
|
||||
}
|
@ -2,6 +2,11 @@ package com.idormy.sms.forwarder.database.entity
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.*
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.App.Companion.CALL_TYPE_MAP
|
||||
import com.idormy.sms.forwarder.App.Companion.CHECK_MAP
|
||||
import com.idormy.sms.forwarder.App.Companion.FILED_MAP
|
||||
import com.idormy.sms.forwarder.App.Companion.SIM_SLOT_MAP
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.database.ext.ConvertersSenderList
|
||||
import com.idormy.sms.forwarder.entity.MsgInfo
|
||||
@ -49,103 +54,85 @@ data class Rule(
|
||||
//免打扰(禁用转发)时间段
|
||||
@ColumnInfo(name = "silent_period_start", defaultValue = "0") var silentPeriodStart: Int = 0,
|
||||
@ColumnInfo(name = "silent_period_end", defaultValue = "0") var silentPeriodEnd: Int = 0,
|
||||
@ColumnInfo(name = "silent_day_of_week", defaultValue = "") var silentDayOfWeek: String = "",
|
||||
) : Parcelable {
|
||||
|
||||
companion object {
|
||||
val TAG: String = Rule::class.java.simpleName
|
||||
|
||||
//通话类型:1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
|
||||
val CALL_TYPE_MAP = mapOf(
|
||||
//"0" to getString(R.string.unknown_call),
|
||||
"1" to getString(R.string.incoming_call_ended),
|
||||
"2" to getString(R.string.outgoing_call_ended),
|
||||
"3" to getString(R.string.missed_call),
|
||||
"4" to getString(R.string.incoming_call_received),
|
||||
"5" to getString(R.string.incoming_call_answered),
|
||||
"6" to getString(R.string.outgoing_call_started),
|
||||
)
|
||||
val FILED_MAP = object : HashMap<String, String>() {
|
||||
init {
|
||||
put("transpond_all", getString(R.string.rule_transpond_all))
|
||||
put("phone_num", getString(R.string.rule_phone_num))
|
||||
put("msg_content", getString(R.string.rule_msg_content))
|
||||
put("multi_match", getString(R.string.rule_multi_match))
|
||||
put("package_name", getString(R.string.rule_package_name))
|
||||
put("inform_content", getString(R.string.rule_inform_content))
|
||||
put("call_type", getString(R.string.rule_call_type))
|
||||
put("uid", getString(R.string.rule_uid))
|
||||
}
|
||||
}
|
||||
val CHECK_MAP = object : HashMap<String, String>() {
|
||||
init {
|
||||
put("is", getString(R.string.rule_is))
|
||||
put("notis", getString(R.string.rule_notis))
|
||||
put("contain", getString(R.string.rule_contain))
|
||||
put("startwith", getString(R.string.rule_startwith))
|
||||
put("endwith", getString(R.string.rule_endwith))
|
||||
put("notcontain", getString(R.string.rule_notcontain))
|
||||
put("regex", getString(R.string.rule_regex))
|
||||
}
|
||||
}
|
||||
val SIM_SLOT_MAP = object : HashMap<String, String>() {
|
||||
init {
|
||||
put("ALL", getString(R.string.rule_all))
|
||||
put("SIM1", "SIM1")
|
||||
put("SIM2", "SIM2")
|
||||
}
|
||||
}
|
||||
|
||||
fun getRuleMatch(filed: String?, check: String?, value: String?, simSlot: String?): Any {
|
||||
fun getRuleMatch(type: String?, filed: String?, check: String?, value: String?, simSlot: String?, senderList: List<Sender>? = null): String {
|
||||
val blank = if (App.isNeedSpaceBetweenWords) " " else ""
|
||||
val sb = StringBuilder()
|
||||
sb.append(SIM_SLOT_MAP[simSlot]).append(getString(R.string.rule_card))
|
||||
if (type != "app") sb.append(SIM_SLOT_MAP[simSlot]).append(blank).append(getString(R.string.rule_card)).append(blank)
|
||||
when (filed) {
|
||||
null, FILED_TRANSPOND_ALL -> {
|
||||
sb.append(getString(R.string.rule_all_fw_to))
|
||||
}
|
||||
null, FILED_TRANSPOND_ALL -> sb.append(getString(R.string.rule_all_fw_to))
|
||||
FILED_CALL_TYPE -> sb.append(getString(R.string.rule_when))
|
||||
.append(blank)
|
||||
.append(FILED_MAP[filed])
|
||||
.append(blank)
|
||||
.append(CHECK_MAP[check])
|
||||
.append(blank)
|
||||
.append(CALL_TYPE_MAP[value])
|
||||
.append(blank)
|
||||
.append(getString(R.string.rule_fw_to))
|
||||
|
||||
FILED_CALL_TYPE -> {
|
||||
sb.append(getString(R.string.rule_when)).append(FILED_MAP[filed]).append(CHECK_MAP[check]).append(CALL_TYPE_MAP[value]).append(getString(R.string.rule_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))
|
||||
}
|
||||
else -> sb.append(getString(R.string.rule_when))
|
||||
.append(blank)
|
||||
.append(FILED_MAP[filed])
|
||||
.append(blank)
|
||||
.append(CHECK_MAP[check])
|
||||
.append(blank)
|
||||
.append(value)
|
||||
.append(blank)
|
||||
.append(getString(R.string.rule_fw_to))
|
||||
}
|
||||
if (!senderList.isNullOrEmpty()) {
|
||||
sb.append(blank).append(senderList.joinToString(",") { it.name })
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val name: String
|
||||
val description: String
|
||||
get() {
|
||||
val blank = if (App.isNeedSpaceBetweenWords) " " else ""
|
||||
val card = SIM_SLOT_MAP[simSlot].toString() + blank + getString(R.string.rule_card) + blank
|
||||
val sb = StringBuilder()
|
||||
if (type == "call" || type == "sms") sb.append(SIM_SLOT_MAP[simSlot].toString()).append(getString(R.string.rule_card))
|
||||
when (filed) {
|
||||
FILED_TRANSPOND_ALL -> sb.append(getString(R.string.rule_all_fw_to))
|
||||
FILED_CALL_TYPE -> sb.append(getString(R.string.rule_when) + FILED_MAP[filed] + CHECK_MAP[check] + CALL_TYPE_MAP[value] + getString(R.string.rule_fw_to))
|
||||
else -> sb.append(getString(R.string.rule_when) + FILED_MAP[filed] + CHECK_MAP[check] + value + getString(R.string.rule_fw_to))
|
||||
when (type) {
|
||||
"app" -> sb.append(getString(R.string.task_app_when))
|
||||
"call" -> sb.append(String.format(getString(R.string.task_call_when), card))
|
||||
"sms" -> sb.append(String.format(getString(R.string.task_sms_when), card))
|
||||
}
|
||||
sb.append(blank)
|
||||
when (filed) {
|
||||
FILED_TRANSPOND_ALL -> sb.append("")
|
||||
FILED_CALL_TYPE -> sb.append(getString(R.string.rule_when))
|
||||
.append(blank)
|
||||
.append(FILED_MAP[filed])
|
||||
.append(blank)
|
||||
.append(CHECK_MAP[check])
|
||||
.append(blank)
|
||||
.append(CALL_TYPE_MAP[value])
|
||||
|
||||
else -> sb.append(getString(R.string.rule_when))
|
||||
.append(blank)
|
||||
.append(FILED_MAP[filed])
|
||||
.append(blank)
|
||||
.append(CHECK_MAP[check])
|
||||
.append(blank)
|
||||
.append(value)
|
||||
}
|
||||
sb.append(senderList.joinToString(",") { it.name })
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
val ruleMatch: String
|
||||
get() {
|
||||
val simStr = if ("app" == type) "" else SIM_SLOT_MAP[simSlot].toString() + getString(R.string.rule_card)
|
||||
return when (filed) {
|
||||
FILED_TRANSPOND_ALL -> {
|
||||
simStr + getString(R.string.rule_all_fw_to)
|
||||
}
|
||||
|
||||
FILED_CALL_TYPE -> {
|
||||
simStr + getString(R.string.rule_when) + FILED_MAP[filed] + CHECK_MAP[check] + CALL_TYPE_MAP[value] + getString(R.string.rule_fw_to)
|
||||
}
|
||||
|
||||
else -> {
|
||||
simStr + getString(R.string.rule_when) + FILED_MAP[filed] + CHECK_MAP[check] + value + getString(R.string.rule_fw_to)
|
||||
}
|
||||
}
|
||||
fun getName(appendSenderList: Boolean = true): String {
|
||||
return if (appendSenderList) {
|
||||
getRuleMatch(type, filed, check, value, simSlot, senderList)
|
||||
} else {
|
||||
getRuleMatch(type, filed, check, value, simSlot, null)
|
||||
}
|
||||
}
|
||||
|
||||
val statusChecked: Boolean
|
||||
get() = status != STATUS_OFF
|
||||
@ -228,47 +215,49 @@ data class Rule(
|
||||
|
||||
//内容分支
|
||||
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)
|
||||
}
|
||||
if (msgValue == null) return false
|
||||
|
||||
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)
|
||||
fun evaluateCondition(condition: String): Boolean {
|
||||
return when (check) {
|
||||
CHECK_IS -> msgValue == condition
|
||||
CHECK_NOT_IS -> msgValue != condition
|
||||
CHECK_CONTAIN -> msgValue.contains(condition)
|
||||
CHECK_NOT_CONTAIN -> !msgValue.contains(condition)
|
||||
CHECK_START_WITH -> msgValue.startsWith(condition)
|
||||
CHECK_END_WITH -> msgValue.endsWith(condition)
|
||||
CHECK_REGEX -> try {
|
||||
val pattern = Pattern.compile(condition, Pattern.CASE_INSENSITIVE)
|
||||
val matcher = pattern.matcher(msgValue)
|
||||
while (matcher.find()) {
|
||||
checked = true
|
||||
break
|
||||
}
|
||||
matcher.find()
|
||||
} 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)
|
||||
Log.i(TAG, "PatternSyntaxException: ${e.description}, Index: ${e.index}, Message: ${e.message}, Pattern: ${e.pattern}")
|
||||
false
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun parseAndEvaluate(expression: String): Boolean {
|
||||
// Split by "||" and evaluate each segment joined by "&&"
|
||||
val orGroups = expression.split("||")
|
||||
return orGroups.any { orGroup ->
|
||||
val andGroups = orGroup.split("&&")
|
||||
andGroups.all { andGroup ->
|
||||
val trimmedCondition = andGroup.trim()
|
||||
evaluateCondition(trimmedCondition)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
Log.i(TAG, "checkValue " + msgValue + " " + this.check + " " + this.value + " checked:" + checked)
|
||||
|
||||
val checked = if (value.contains("&&") || value.contains("||")) {
|
||||
parseAndEvaluate(value)
|
||||
} else {
|
||||
evaluateCondition(value)
|
||||
}
|
||||
|
||||
Log.i(TAG, "checkValue $msgValue $check $value checked:$checked")
|
||||
return checked
|
||||
}
|
||||
|
||||
}
|
@ -34,8 +34,7 @@ class FrpcRepository(private val frpcDao: FrpcDao) {
|
||||
fun getByUids(uids: List<String>, instr: String): List<Frpc> {
|
||||
val frpcs = frpcDao.getByUids(uids)
|
||||
// 将结果按照 instr() 的顺序进行排序
|
||||
frpcs.sortedBy { instr.indexOf(it.uid) }
|
||||
return frpcs
|
||||
return frpcs.sortedBy { instr.indexOf(it.uid) }
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package com.idormy.sms.forwarder.database.repository
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||
import com.idormy.sms.forwarder.database.dao.LogsDao
|
||||
import com.idormy.sms.forwarder.database.entity.Logs
|
||||
|
||||
@ -22,4 +23,20 @@ class LogsRepository(private val logsDao: LogsDao) {
|
||||
|
||||
fun getOne(id: Long) = logsDao.getOne(id)
|
||||
|
||||
fun getIdsByTimeAndStatus(hours: Int, statusList: List<Int>): List<Logs> {
|
||||
var sql = "SELECT * FROM Logs WHERE 1=1"
|
||||
if (hours > 0) {
|
||||
val time = System.currentTimeMillis() - hours * 3600000
|
||||
sql += " AND time>=$time"
|
||||
}
|
||||
if (statusList.isNotEmpty()) {
|
||||
val statusListStr = statusList.joinToString(",")
|
||||
sql += " AND forward_status IN ($statusListStr)"
|
||||
}
|
||||
sql += " ORDER BY id ASC"
|
||||
|
||||
val query = SimpleSQLiteQuery(sql)
|
||||
return logsDao.getLogsRaw(query)
|
||||
}
|
||||
|
||||
}
|
@ -14,8 +14,6 @@ class MsgRepository(private val msgDao: MsgDao) {
|
||||
|
||||
fun deleteAll() = msgDao.deleteAll()
|
||||
|
||||
fun deleteAll(type: String) = msgDao.deleteAll(type)
|
||||
|
||||
@WorkerThread
|
||||
fun deleteTimeAgo(time: Long) = msgDao.deleteTimeAgo(time)
|
||||
|
||||
|
@ -33,8 +33,7 @@ class SenderRepository(private val senderDao: SenderDao) {
|
||||
fun getByIds(ids: List<Long>, instr: String): List<Sender> {
|
||||
val senders = senderDao.getByIds(ids)
|
||||
// 将结果按照 instr() 的顺序进行排序
|
||||
senders.sortedBy { instr.indexOf(it.id.toString()) }
|
||||
return senders
|
||||
return senders.sortedBy { instr.indexOf(it.id.toString()) }
|
||||
}
|
||||
|
||||
fun getAllNonCache(): List<Sender> {
|
||||
|
@ -4,6 +4,7 @@ import androidx.annotation.WorkerThread
|
||||
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||
import com.idormy.sms.forwarder.database.dao.TaskDao
|
||||
import com.idormy.sms.forwarder.database.entity.Task
|
||||
import io.reactivex.Single
|
||||
import java.util.Date
|
||||
|
||||
class TaskRepository(private val taskDao: TaskDao) {
|
||||
@ -20,10 +21,14 @@ class TaskRepository(private val taskDao: TaskDao) {
|
||||
|
||||
fun updateExecTime(taskId: Long, lastExecTime: Date, nextExecTime: Date, status: Int) = taskDao.updateExecTime(taskId, lastExecTime, nextExecTime, status)
|
||||
|
||||
fun updateStatusByIds(ids: List<Long>, status: Int) = taskDao.updateStatusByIds(ids, status)
|
||||
|
||||
fun get(id: Long) = taskDao.get(id)
|
||||
|
||||
suspend fun getOne(id: Long) = taskDao.getOne(id)
|
||||
|
||||
fun getAll(): Single<List<Task>> = taskDao.getAll()
|
||||
|
||||
fun getAllNonCache(): List<Task> {
|
||||
val query = SimpleSQLiteQuery("SELECT * FROM Task ORDER BY id ASC")
|
||||
return taskDao.getAllRaw(query)
|
||||
|
@ -6,19 +6,28 @@ import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||
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 com.idormy.sms.forwarder.utils.Log
|
||||
import com.xuexiang.xutil.data.DateUtils
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class MsgViewModel(private val dao: MsgDao) : ViewModel() {
|
||||
private var type: String = "sms"
|
||||
private var filter: MutableMap<String, Any> = mutableMapOf()
|
||||
|
||||
fun setType(type: String): MsgViewModel {
|
||||
this.type = type
|
||||
return this
|
||||
}
|
||||
|
||||
fun setFilter(filter: MutableMap<String, Any>): MsgViewModel {
|
||||
this.filter = filter
|
||||
return this
|
||||
}
|
||||
|
||||
val allMsg: Flow<PagingData<MsgAndLogs>> = Pager(
|
||||
config = PagingConfig(
|
||||
pageSize = 10,
|
||||
@ -26,11 +35,67 @@ class MsgViewModel(private val dao: MsgDao) : ViewModel() {
|
||||
initialLoadSize = 10
|
||||
)
|
||||
) {
|
||||
dao.pagingSource(type)
|
||||
if (filter.isEmpty()) {
|
||||
dao.pagingSource(type)
|
||||
} else {
|
||||
val sb = StringBuilder().apply {
|
||||
append("SELECT * FROM Msg WHERE type = '$type'")
|
||||
append(getOtherCondition())
|
||||
append(" ORDER BY id DESC")
|
||||
}
|
||||
|
||||
//Log.d("MsgViewModel", "sql: $sb")
|
||||
val query = SimpleSQLiteQuery(sb.toString())
|
||||
dao.pagingSource(query)
|
||||
}
|
||||
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
|
||||
fun delete(id: Long) = ioThread {
|
||||
dao.delete(id)
|
||||
}
|
||||
|
||||
fun deleteAll() = ioThread {
|
||||
val sb = StringBuilder().apply {
|
||||
append("DELETE FROM Msg WHERE type = '$type'")
|
||||
if (filter.isNotEmpty()) {
|
||||
append(getOtherCondition())
|
||||
}
|
||||
}
|
||||
|
||||
Log.d("MsgViewModel", "sql: $sb")
|
||||
val query = SimpleSQLiteQuery(sb.toString())
|
||||
dao.deleteAll(query)
|
||||
}
|
||||
|
||||
private fun getOtherCondition(): String {
|
||||
return StringBuilder().apply {
|
||||
filter["from"]?.toString()?.takeIf { it.isNotEmpty() }?.let { append(" AND `from` LIKE '%$it%'") }
|
||||
filter["content"]?.toString()?.takeIf { it.isNotEmpty() }?.let { append(" AND content LIKE '%$it%'") }
|
||||
filter["title"]?.toString()?.takeIf { it.isNotEmpty() }?.let { append(" AND sim_info LIKE '%$it%'") }
|
||||
filter["start_time"]?.toString()?.takeIf { it.isNotEmpty() }?.let {
|
||||
val date = DateUtils.string2Date(it, DateUtils.yyyyMMddHHmmss.get())
|
||||
append(" AND time >= '${date.time}'")
|
||||
}
|
||||
filter["end_time"]?.toString()?.takeIf { it.isNotEmpty() }?.let {
|
||||
val date = DateUtils.string2Date(it, DateUtils.yyyyMMddHHmmss.get())
|
||||
append(" AND time <= '${date.time}'")
|
||||
}
|
||||
if (filter["sim_slot"] is Int && filter["sim_slot"] != -1) {
|
||||
append(" AND sim_slot = ${filter["sim_slot"]}")
|
||||
}
|
||||
val callTypeFilter = filter["call_type"] as? MutableList<*>
|
||||
if (!callTypeFilter.isNullOrEmpty()) {
|
||||
val callTypeString = callTypeFilter.joinToString(",") { it.toString() }
|
||||
append(" AND call_type IN ($callTypeString)")
|
||||
}
|
||||
val forwardStatusFilter = filter["forward_status"] as? MutableList<*>
|
||||
if (!forwardStatusFilter.isNullOrEmpty()) {
|
||||
val forwardStatusString = forwardStatusFilter.joinToString(",") { it.toString() }
|
||||
val subSql = "SELECT DISTINCT msg_id FROM Logs WHERE type = '$type' and forward_status IN ($forwardStatusString)"
|
||||
append(" AND id in ($subSql)")
|
||||
}
|
||||
}.toString()
|
||||
}
|
||||
|
||||
}
|
@ -4,20 +4,24 @@ import android.annotation.SuppressLint
|
||||
import android.text.TextUtils
|
||||
import com.google.gson.Gson
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.App.Companion.CALL_TYPE_MAP
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.utils.AppUtils
|
||||
import com.idormy.sms.forwarder.utils.BatteryUtils
|
||||
import com.idormy.sms.forwarder.utils.HttpServerUtils
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.PhoneUtils
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.enableSmsTemplate
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.extraDeviceMark
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.smsTemplate
|
||||
import com.idormy.sms.forwarder.utils.task.TaskUtils
|
||||
import com.xuexiang.xutil.net.NetworkUtils
|
||||
import com.xuexiang.xutil.resource.ResUtils.getString
|
||||
import java.io.Serializable
|
||||
import java.net.URLEncoder
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
|
||||
@Suppress("unused")
|
||||
data class MsgInfo(
|
||||
@ -32,69 +36,26 @@ data class MsgInfo(
|
||||
var uid: Int = 0, //APP通知的UID
|
||||
) : Serializable {
|
||||
|
||||
private val titleForSend: String
|
||||
get() = getTitleForSend("", "")
|
||||
val titleForSend = getTitleForSend()
|
||||
|
||||
//通话类型:1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
|
||||
private val callTypeMap = mapOf(
|
||||
//"0" to getString(R.string.unknown_call),
|
||||
"1" to getString(R.string.incoming_call_ended),
|
||||
"2" to getString(R.string.outgoing_call_ended),
|
||||
"3" to getString(R.string.missed_call),
|
||||
"4" to getString(R.string.incoming_call_received),
|
||||
"5" to getString(R.string.incoming_call_answered),
|
||||
"6" to getString(R.string.outgoing_call_started),
|
||||
)
|
||||
val smsVoForSend = getContentForSend()
|
||||
|
||||
fun getTitleForSend(titleTemplate: String): String {
|
||||
return getTitleForSend(titleTemplate, "")
|
||||
}
|
||||
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
fun getTitleForSend(titleTemplate: String, regexReplace: String): String {
|
||||
fun getTitleForSend(titleTemplate: String = "", regexReplace: String = ""): String {
|
||||
var template = titleTemplate.replace("null", "")
|
||||
if (TextUtils.isEmpty(template)) template = getString(R.string.tag_from)
|
||||
val deviceMark = extraDeviceMark.trim()
|
||||
val versionName = AppUtils.getAppVersionName()
|
||||
val splitSimInfo = simInfo.split("#####")
|
||||
val title = splitSimInfo.getOrElse(0) { simInfo }
|
||||
val scheme = splitSimInfo.getOrElse(1) { "" }
|
||||
val titleForSend: String = template.replace(getString(R.string.tag_from), from)
|
||||
.replace(getString(R.string.tag_package_name), from)
|
||||
.replace(getString(R.string.tag_sms), content)
|
||||
.replace(getString(R.string.tag_msg), content)
|
||||
.replace(getString(R.string.tag_card_slot), title)
|
||||
.replace(getString(R.string.tag_card_subid), subId.toString())
|
||||
.replace(getString(R.string.tag_title), title)
|
||||
.replace(getString(R.string.tag_scheme), scheme)
|
||||
.replace(getString(R.string.tag_uid), uid.toString())
|
||||
.replace(getString(R.string.tag_receive_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date))
|
||||
.replace(getString(R.string.tag_current_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()))
|
||||
.replace(getString(R.string.tag_device_name), deviceMark)
|
||||
.replace(getString(R.string.tag_app_version), versionName)
|
||||
.replace(getString(R.string.tag_call_type), callTypeMap[callType.toString()] ?: getString(R.string.unknown_call))
|
||||
.replace(getString(R.string.tag_battery_pct), TaskUtils.batteryPct.toString())
|
||||
.replace(getString(R.string.tag_battery_status), BatteryUtils.getStatus(TaskUtils.batteryStatus))
|
||||
.replace(getString(R.string.tag_battery_plugged), BatteryUtils.getPlugged(TaskUtils.batteryPlugged))
|
||||
.replace(getString(R.string.tag_battery_info), TaskUtils.batteryInfo)
|
||||
.trim()
|
||||
return replaceLocationTag(replaceAppName(regexReplace(titleForSend, regexReplace), from))
|
||||
|
||||
return replaceTemplate(template, regexReplace)
|
||||
}
|
||||
|
||||
val smsVoForSend: String
|
||||
get() = getContentForSend("", "")
|
||||
|
||||
fun getContentForSend(ruleSmsTemplate: String): String {
|
||||
return getContentForSend(ruleSmsTemplate, "")
|
||||
}
|
||||
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
fun getContentForSend(ruleSmsTemplate: String, regexReplace: String): String {
|
||||
val deviceMark = extraDeviceMark.trim()
|
||||
fun getContentForSend(ruleSmsTemplate: String = "", regexReplace: String = ""): String {
|
||||
var customSmsTemplate: String = getString(R.string.tag_from).toString() + "\n" +
|
||||
getString(R.string.tag_sms) + "\n" +
|
||||
getString(R.string.tag_card_slot) + "\n" +
|
||||
(if (type == "app") "" else "SubId:${getString(R.string.tag_card_subid)}\n") +
|
||||
when (type) {
|
||||
"sms", "call" -> "SubId:${getString(R.string.tag_card_subid)}\n"
|
||||
"app" -> "UID:${getString(R.string.tag_uid)}\n"
|
||||
else -> ""
|
||||
} +
|
||||
getString(R.string.tag_receive_time) + "\n" +
|
||||
getString(R.string.tag_device_name)
|
||||
|
||||
@ -108,92 +69,156 @@ data class MsgInfo(
|
||||
customSmsTemplate = smsTemplate.replace("null", "")
|
||||
}
|
||||
}
|
||||
val versionName = AppUtils.getAppVersionName()
|
||||
val splitSimInfo = simInfo.split("#####")
|
||||
val title = splitSimInfo.getOrElse(0) { simInfo }
|
||||
val scheme = splitSimInfo.getOrElse(1) { "" }
|
||||
val smsVoForSend: String = customSmsTemplate.replace(getString(R.string.tag_from), from)
|
||||
.replace(getString(R.string.tag_package_name), from)
|
||||
.replace(getString(R.string.tag_sms), content)
|
||||
.replace(getString(R.string.tag_msg), content)
|
||||
.replace(getString(R.string.tag_card_slot), title)
|
||||
.replace(getString(R.string.tag_card_subid), subId.toString())
|
||||
.replace(getString(R.string.tag_title), title)
|
||||
.replace(getString(R.string.tag_scheme), scheme)
|
||||
.replace(getString(R.string.tag_uid), uid.toString())
|
||||
.replace(getString(R.string.tag_receive_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date))
|
||||
.replace(getString(R.string.tag_current_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()))
|
||||
.replace(getString(R.string.tag_device_name), deviceMark)
|
||||
.replace(getString(R.string.tag_app_version), versionName)
|
||||
.replace(getString(R.string.tag_call_type), callTypeMap[callType.toString()] ?: getString(R.string.unknown_call))
|
||||
.replace(getString(R.string.tag_battery_pct), TaskUtils.batteryPct.toString())
|
||||
.replace(getString(R.string.tag_battery_status), BatteryUtils.getStatus(TaskUtils.batteryStatus))
|
||||
.replace(getString(R.string.tag_battery_plugged), BatteryUtils.getPlugged(TaskUtils.batteryPlugged))
|
||||
.replace(getString(R.string.tag_battery_info), TaskUtils.batteryInfo)
|
||||
.trim()
|
||||
return replaceLocationTag(replaceAppName(regexReplace(smsVoForSend, regexReplace), from))
|
||||
|
||||
return replaceTemplate(customSmsTemplate, regexReplace)
|
||||
}
|
||||
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
fun getContentFromJson(jsonTemplate: String): String {
|
||||
var template = jsonTemplate.replace("null", "")
|
||||
if (TextUtils.isEmpty(template)) template = getString(R.string.tag_from)
|
||||
val deviceMark = extraDeviceMark.trim()
|
||||
val versionName = AppUtils.getAppVersionName()
|
||||
val splitSimInfo = simInfo.split("#####")
|
||||
var title = splitSimInfo.getOrElse(0) { simInfo }
|
||||
title = jsonInnerStr(title)
|
||||
var scheme = splitSimInfo.getOrElse(1) { "" }
|
||||
scheme = jsonInnerStr(scheme)
|
||||
from = jsonInnerStr(from)
|
||||
content = jsonInnerStr(content)
|
||||
return replaceTemplate(template, "", "Gson")
|
||||
}
|
||||
|
||||
val msgForSend: String = template.replace(getString(R.string.tag_from), from)
|
||||
.replace(getString(R.string.tag_package_name), from)
|
||||
.replace(getString(R.string.tag_sms), content)
|
||||
.replace(getString(R.string.tag_msg), content)
|
||||
.replace(getString(R.string.tag_card_slot), title)
|
||||
.replace(getString(R.string.tag_card_subid), subId.toString())
|
||||
.replace(getString(R.string.tag_title), title)
|
||||
.replace(getString(R.string.tag_scheme), scheme)
|
||||
.replace(getString(R.string.tag_uid), uid.toString())
|
||||
.replace(getString(R.string.tag_receive_time), jsonInnerStr(SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)))
|
||||
.replace(getString(R.string.tag_current_time), jsonInnerStr(SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date())))
|
||||
.replace(getString(R.string.tag_device_name), jsonInnerStr(deviceMark))
|
||||
.replace(getString(R.string.tag_app_version), jsonInnerStr(versionName))
|
||||
.replace(getString(R.string.tag_call_type), jsonInnerStr(callTypeMap[callType.toString()] ?: getString(R.string.unknown_call)))
|
||||
.replace(getString(R.string.tag_battery_pct), jsonInnerStr(TaskUtils.batteryPct.toString()))
|
||||
.replace(getString(R.string.tag_battery_status), jsonInnerStr(BatteryUtils.getStatus(TaskUtils.batteryStatus)))
|
||||
.replace(getString(R.string.tag_battery_plugged), jsonInnerStr(BatteryUtils.getPlugged(TaskUtils.batteryPlugged)))
|
||||
.replace(getString(R.string.tag_battery_info), jsonInnerStr(TaskUtils.batteryInfo))
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
fun replaceTemplate(template: String, regexReplace: String = "", encoderName: String = ""): String {
|
||||
return template.replaceTag(getString(R.string.tag_from), from, encoderName)
|
||||
.replaceTag(getString(R.string.tag_package_name), from, encoderName)
|
||||
.replaceTag(getString(R.string.tag_sms), content, encoderName)
|
||||
.replaceTag(getString(R.string.tag_msg), content, encoderName)
|
||||
.replaceTag(getString(R.string.tag_card_slot), simInfo, encoderName)
|
||||
.replaceTag(getString(R.string.tag_card_subid), subId.toString(), encoderName)
|
||||
.replaceTag(getString(R.string.tag_title), simInfo, encoderName)
|
||||
.replaceTag(getString(R.string.tag_uid), uid.toString(), encoderName)
|
||||
.replaceTag(
|
||||
getString(R.string.tag_receive_time),
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date),
|
||||
encoderName
|
||||
)
|
||||
.replaceTag(
|
||||
getString(R.string.tag_current_time),
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()),
|
||||
encoderName
|
||||
)
|
||||
.replaceTag(getString(R.string.tag_device_name), extraDeviceMark.trim(), encoderName)
|
||||
.replaceTag(getString(R.string.tag_app_version), AppUtils.getAppVersionName(), encoderName)
|
||||
.replaceTag(
|
||||
getString(R.string.tag_call_type),
|
||||
CALL_TYPE_MAP[callType.toString()] ?: getString(R.string.unknown_call), encoderName
|
||||
)
|
||||
.replaceTag(getString(R.string.tag_ipv4), TaskUtils.ipv4, encoderName)
|
||||
.replaceTag(getString(R.string.tag_ipv6), TaskUtils.ipv6, encoderName)
|
||||
.replaceTag(getString(R.string.tag_ip_list), TaskUtils.ipList, encoderName)
|
||||
.replaceTag(getString(R.string.tag_battery_pct), "%.0f%%".format(TaskUtils.batteryPct), encoderName)
|
||||
.replaceTag(getString(R.string.tag_battery_status), BatteryUtils.getStatus(TaskUtils.batteryStatus), encoderName)
|
||||
.replaceTag(getString(R.string.tag_battery_plugged), BatteryUtils.getPlugged(TaskUtils.batteryPlugged), encoderName)
|
||||
.replaceTag(getString(R.string.tag_battery_info), TaskUtils.batteryInfo, encoderName)
|
||||
.replaceTag(
|
||||
getString(R.string.tag_battery_info_simple),
|
||||
"%.0f%%".format(TaskUtils.batteryPct)
|
||||
+ with(BatteryUtils.getPlugged(TaskUtils.batteryPlugged)) {
|
||||
if (this == getString(R.string.battery_unknown)) "" else " - $this"
|
||||
},
|
||||
encoderName
|
||||
)
|
||||
.replaceTag(
|
||||
getString(R.string.tag_net_type), with(NetworkUtils.getNetStateType()) {
|
||||
if (this == NetworkUtils.NetState.NET_NO || this == NetworkUtils.NetState.NET_UNKNOWN)
|
||||
this.name
|
||||
this.name.removePrefix("NET_")
|
||||
},
|
||||
encoderName
|
||||
)
|
||||
.replaceAppNameTag(from, encoderName)
|
||||
.replaceLocationTag(encoderName)
|
||||
.replaceContactNameTag(encoderName)
|
||||
.replacePhoneAreaTag(encoderName)
|
||||
.regexReplace(regexReplace)
|
||||
.trim()
|
||||
return replaceLocationTag(replaceAppName(msgForSend, from, true), true)
|
||||
}
|
||||
|
||||
//正则替换内容
|
||||
private fun regexReplace(content: String, regexReplace: String): String {
|
||||
return if (TextUtils.isEmpty(regexReplace)) content else try {
|
||||
var newContent = content
|
||||
private fun String.regexReplace(regexReplace: String): String {
|
||||
return if (TextUtils.isEmpty(regexReplace)) this else try {
|
||||
var newContent = this
|
||||
val lineArray = regexReplace.split("\\n".toRegex()).toTypedArray()
|
||||
for (line in lineArray) {
|
||||
val lineSplit = line.split("===".toRegex()).toTypedArray()
|
||||
if (lineSplit.isNotEmpty()) {
|
||||
val regex = lineSplit[0]
|
||||
val replacement = if (lineSplit.size >= 2) lineSplit[1].replace("\\\\n".toRegex(), "\n") else ""
|
||||
val replacement =
|
||||
if (lineSplit.size >= 2)
|
||||
lineSplit[1].replace("\\\\n".toRegex(), "\n") else ""
|
||||
newContent = newContent.replace(regex.toRegex(), replacement)
|
||||
}
|
||||
}
|
||||
newContent
|
||||
} catch (e: Exception) {
|
||||
Log.e("RegexReplace", "Failed to get the receiving phone number:" + e.message)
|
||||
content
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
//替换标签(支持正则替换)
|
||||
private fun String.replaceTag(tag: String, info: String, encoderName: String = "", ignoreCase: Boolean = true): String {
|
||||
var result = when (encoderName) {
|
||||
"Gson" -> this.replace(tag, toJsonStr(info), ignoreCase)
|
||||
"URLEncoder" -> this.replace(tag, URLEncoder.encode(info, "UTF-8"), ignoreCase)
|
||||
else -> this.replace(tag, info, ignoreCase)
|
||||
}
|
||||
|
||||
val tagName = tag.removePrefix("{{").removeSuffix("}}")
|
||||
val tagRegex = "\\{\\{${tagName}###([^=]+)===(.*?)\\}\\}".toRegex()
|
||||
tagRegex.findAll(result).forEach {
|
||||
try {
|
||||
Log.d("MsgInfo", "tagRegex: ${it.value}, ${it.groupValues}")
|
||||
val regex = it.groupValues[1]
|
||||
val replacement = it.groupValues[2]
|
||||
val temp = info.replace(regex.toRegex(), replacement)
|
||||
Log.d("MsgInfo", "tagRegex: regex=$regex, replacement=$replacement, temp=$temp")
|
||||
result = when (encoderName) {
|
||||
"Gson" -> this.replace(it.value, toJsonStr(temp), ignoreCase)
|
||||
"URLEncoder" -> this.replace(it.value, URLEncoder.encode(temp, "UTF-8"), ignoreCase)
|
||||
else -> this.replace(it.value, temp, ignoreCase)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("MsgInfo", "Failed to replace tagRegex: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
//替换{{CONTACT_NAME}}标签
|
||||
private fun String.replaceContactNameTag(encoderName: String = ""): String {
|
||||
if (TextUtils.isEmpty(this)) return this
|
||||
if (this.indexOf(getString(R.string.tag_contact_name)) == -1) return this
|
||||
|
||||
val contacts = PhoneUtils.getContactByNumber(from)
|
||||
var contactName = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number)
|
||||
when (encoderName) {
|
||||
"Gson" -> contactName = toJsonStr(contactName)
|
||||
"URLEncoder" -> contactName = URLEncoder.encode(contactName, "UTF-8")
|
||||
}
|
||||
return this.replaceTag(getString(R.string.tag_contact_name), contactName)
|
||||
}
|
||||
|
||||
//替换{{PHONE_AREA}}标签
|
||||
private fun String.replacePhoneAreaTag(encoderName: String = ""): String {
|
||||
if (TextUtils.isEmpty(this)) return this
|
||||
if (this.indexOf(getString(R.string.tag_phone_area)) == -1) return this
|
||||
|
||||
var phoneArea = PhoneUtils.getPhoneArea(from)
|
||||
when (encoderName) {
|
||||
"Gson" -> phoneArea = toJsonStr(phoneArea)
|
||||
"URLEncoder" -> phoneArea = URLEncoder.encode(phoneArea, "UTF-8")
|
||||
}
|
||||
return this.replaceTag(getString(R.string.tag_phone_area), phoneArea)
|
||||
}
|
||||
|
||||
//替换{{APP名称}}标签
|
||||
private fun replaceAppName(content: String, packageName: String, needJson: Boolean = false): String {
|
||||
if (TextUtils.isEmpty(content)) return content
|
||||
if (content.indexOf(getString(R.string.tag_app_name)) == -1) return content
|
||||
private fun String.replaceAppNameTag(packageName: String, encoderName: String = ""): String {
|
||||
if (TextUtils.isEmpty(this)) return this
|
||||
if (this.indexOf(getString(R.string.tag_app_name)) == -1) return this
|
||||
|
||||
var appName = ""
|
||||
if (SettingUtils.enableLoadUserAppList && App.UserAppList.isNotEmpty()) {
|
||||
@ -212,25 +237,41 @@ data class MsgInfo(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needJson) {
|
||||
appName = jsonInnerStr(appName)
|
||||
|
||||
when (encoderName) {
|
||||
"Gson" -> appName = toJsonStr(appName)
|
||||
"URLEncoder" -> appName = URLEncoder.encode(appName, "UTF-8")
|
||||
}
|
||||
return content.replace(getString(R.string.tag_app_name), appName)
|
||||
|
||||
return this.replaceTag(getString(R.string.tag_app_name), appName)
|
||||
}
|
||||
|
||||
//替换 {{定位信息}} 标签
|
||||
private fun replaceLocationTag(content: String, needJson: Boolean = false): String {
|
||||
if (TextUtils.isEmpty(content)) return content
|
||||
if (content.indexOf(getString(R.string.tag_location)) == -1) return content
|
||||
private fun String.replaceLocationTag(encoderName: String = ""): String {
|
||||
if (TextUtils.isEmpty(this)) return this
|
||||
|
||||
var location = HttpServerUtils.apiLocationCache.toString()
|
||||
if (needJson) {
|
||||
location = jsonInnerStr(location)
|
||||
val location = HttpServerUtils.apiLocationCache
|
||||
var locationStr = location.toString()
|
||||
var address = location.address
|
||||
when (encoderName) {
|
||||
"Gson" -> {
|
||||
locationStr = toJsonStr(locationStr)
|
||||
address = toJsonStr(address)
|
||||
}
|
||||
|
||||
"URLEncoder" -> {
|
||||
locationStr = URLEncoder.encode(locationStr, "UTF-8")
|
||||
address = URLEncoder.encode(address, "UTF-8")
|
||||
}
|
||||
}
|
||||
return content.replace(getString(R.string.tag_location), location)
|
||||
return this.replaceTag(getString(R.string.tag_location), locationStr)
|
||||
.replaceTag(getString(R.string.tag_location_longitude), location.longitude.toString())
|
||||
.replaceTag(getString(R.string.tag_location_latitude), location.latitude.toString())
|
||||
.replaceTag(getString(R.string.tag_location_address), address)
|
||||
}
|
||||
|
||||
private fun jsonInnerStr(string: String?): String {
|
||||
//直接插入json字符串需要转义
|
||||
private fun toJsonStr(string: String?): String {
|
||||
if (string == null) return "null"
|
||||
|
||||
val jsonStr: String = Gson().toJson(string)
|
||||
@ -245,4 +286,4 @@ data class MsgInfo(
|
||||
", simInfo=" + simInfo +
|
||||
'}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
package com.idormy.sms.forwarder.entity.action
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class AlarmSetting(
|
||||
var description: String = "", //描述
|
||||
var action: String = "stop", //动作: start=启动警报, stop=停止警报
|
||||
var volume: Int = 80, //播放音量,0-100
|
||||
var playTimes: Int = 1, //播放次数,0=无限循环,-1=禁用
|
||||
var music: String = "", //音乐文件
|
||||
var repeatTimes: Int = 5, //振动重复次数,0=无限循环,-1=禁用
|
||||
var vibrate: String = "---___===___", //振动律动:=强振动, -弱震动, _不振动, 时长都是100ms
|
||||
var flashTimes: Int = 5, //闪烁次数,0=无限循环,-1=禁用
|
||||
var flash: String = "XXOOXXOO", //闪烁律动:X亮灯, O灭灯, 时长都是100ms
|
||||
) : Serializable
|
@ -0,0 +1,9 @@
|
||||
package com.idormy.sms.forwarder.entity.action
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class ResendSetting(
|
||||
var description: String = "", //描述
|
||||
var hours: Int = 1, //自动重发N小时以来的转发记录,0=不限制
|
||||
var statusList: List<Int> = listOf(0), //状态列表,默认只重发失败的
|
||||
) : Serializable
|
@ -0,0 +1,10 @@
|
||||
package com.idormy.sms.forwarder.entity.action
|
||||
|
||||
import com.idormy.sms.forwarder.database.entity.Task
|
||||
import java.io.Serializable
|
||||
|
||||
data class TaskActionSetting(
|
||||
var description: String = "", //描述
|
||||
var status: Int = 1, //状态:0-禁用;1-启用
|
||||
var taskList: List<Task>, //自动任务列表
|
||||
) : Serializable
|
@ -0,0 +1,88 @@
|
||||
package com.idormy.sms.forwarder.entity.condition
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.xuexiang.xutil.resource.ResUtils.getString
|
||||
import java.io.Serializable
|
||||
|
||||
data class BluetoothSetting(
|
||||
var description: String = "", //描述
|
||||
var action: String = BluetoothAdapter.ACTION_STATE_CHANGED, //事件
|
||||
var state: Int = BluetoothAdapter.STATE_ON, //蓝牙状态
|
||||
var result: Int = 1, //搜索结果:1-已发现,0-未发现
|
||||
var device: String = "", //设备MAC地址
|
||||
) : Serializable {
|
||||
|
||||
constructor(actionCheckId: Int, stateCheckId: Int, resultCheckId: Int, deviceAddress: String) : this() {
|
||||
device = deviceAddress
|
||||
action = when (actionCheckId) {
|
||||
R.id.rb_action_discovery_finished -> BluetoothAdapter.ACTION_DISCOVERY_FINISHED
|
||||
R.id.rb_action_acl_connected -> BluetoothDevice.ACTION_ACL_CONNECTED
|
||||
R.id.rb_action_acl_disconnected -> BluetoothDevice.ACTION_ACL_DISCONNECTED
|
||||
else -> BluetoothAdapter.ACTION_STATE_CHANGED
|
||||
}
|
||||
state = when (stateCheckId) {
|
||||
R.id.rb_state_off -> BluetoothAdapter.STATE_OFF
|
||||
else -> BluetoothAdapter.STATE_ON
|
||||
}
|
||||
result = when (resultCheckId) {
|
||||
R.id.rb_undiscovered -> 0
|
||||
else -> 1
|
||||
}
|
||||
val sb = StringBuilder()
|
||||
|
||||
if (action == BluetoothAdapter.ACTION_STATE_CHANGED) {
|
||||
sb.append(getString(R.string.bluetooth_state_changed)).append(", ").append(getString(R.string.specified_state)).append(": ")
|
||||
if (state == BluetoothAdapter.STATE_ON) {
|
||||
sb.append(getString(R.string.state_on))
|
||||
} else {
|
||||
sb.append(getString(R.string.state_off))
|
||||
}
|
||||
} else if (action == BluetoothAdapter.ACTION_DISCOVERY_FINISHED) {
|
||||
sb.append(getString(R.string.bluetooth_discovery_finished)).append(", ")
|
||||
if (result == 1) {
|
||||
sb.append(getString(R.string.discovered))
|
||||
} else {
|
||||
sb.append(getString(R.string.undiscovered))
|
||||
}
|
||||
val blank = if (App.isNeedSpaceBetweenWords) " " else ""
|
||||
sb.append(blank).append(getString(R.string.specified_device)).append(": ").append(device)
|
||||
} else {
|
||||
if (action == BluetoothDevice.ACTION_ACL_CONNECTED) {
|
||||
sb.append(getString(R.string.bluetooth_acl_connected))
|
||||
} else if (action == BluetoothDevice.ACTION_ACL_DISCONNECTED) {
|
||||
sb.append(getString(R.string.bluetooth_acl_disconnected))
|
||||
}
|
||||
sb.append(", ").append(getString(R.string.specified_device)).append(": ").append(device)
|
||||
}
|
||||
|
||||
description = sb.toString()
|
||||
}
|
||||
|
||||
fun getActionCheckId(): Int {
|
||||
return when (action) {
|
||||
BluetoothAdapter.ACTION_STATE_CHANGED -> R.id.rb_action_state_changed
|
||||
BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> R.id.rb_action_discovery_finished
|
||||
BluetoothDevice.ACTION_ACL_CONNECTED -> R.id.rb_action_acl_connected
|
||||
BluetoothDevice.ACTION_ACL_DISCONNECTED -> R.id.rb_action_acl_disconnected
|
||||
else -> R.id.rb_action_state_changed
|
||||
}
|
||||
}
|
||||
|
||||
fun getStateCheckId(): Int {
|
||||
return when (state) {
|
||||
BluetoothAdapter.STATE_ON -> R.id.rb_state_on
|
||||
BluetoothAdapter.STATE_OFF -> R.id.rb_state_off
|
||||
else -> R.id.rb_state_on
|
||||
}
|
||||
}
|
||||
|
||||
fun getResultCheckId(): Int {
|
||||
return when (result) {
|
||||
0 -> R.id.rb_undiscovered
|
||||
else -> R.id.rb_discovered
|
||||
}
|
||||
}
|
||||
}
|
@ -73,7 +73,7 @@ data class ChargeSetting(
|
||||
|
||||
fun getMsg(statusNew: Int, statusOld: Int, pluggedNew: Int, pluggedOld: Int, batteryInfo: String): String {
|
||||
|
||||
if (statusNew != status || pluggedNew != plugged) return ""
|
||||
if (statusNew != status || (pluggedNew != plugged && plugged != 0)) return ""
|
||||
|
||||
return getString(R.string.battery_status_changed) + getStatusStr(statusOld) + "(" + getPluggedStr(pluggedOld) + ") → " + getStatusStr(statusNew) + "(" + getPluggedStr(pluggedNew) + ")" + batteryInfo
|
||||
}
|
||||
|
@ -8,29 +8,61 @@ import java.io.Serializable
|
||||
data class LockScreenSetting(
|
||||
var description: String = "", //描述
|
||||
var action: String = Intent.ACTION_SCREEN_OFF, //事件
|
||||
var timeAfterScreenOff: Int = 5, //锁屏后时间
|
||||
var timeAfterScreenOn: Int = 5, //解锁后时间
|
||||
var timeAfterScreenOff: Int = 5, //熄屏后时间
|
||||
var timeAfterScreenOn: Int = 5, //开锁后时间
|
||||
var timeAfterScreenLocked: Int = 5, //锁屏后时间
|
||||
var timeAfterScreenUnlocked: Int = 5, //解锁后时间
|
||||
var checkAgain: Boolean = false, //是否再次校验
|
||||
) : Serializable {
|
||||
|
||||
constructor(actionCheckId: Int, timeAfterOff: Int, timeAfterOn: Int) : this() {
|
||||
if (actionCheckId == R.id.rb_action_screen_on) {
|
||||
val duration = if (timeAfterOn > 0) String.format(getString(R.string.duration_minute), timeAfterOn.toString()) else ""
|
||||
description = String.format(getString(R.string.time_after_screen_on_description), duration)
|
||||
action = Intent.ACTION_SCREEN_ON
|
||||
} else {
|
||||
val duration = if (timeAfterOff > 0) String.format(getString(R.string.duration_minute), timeAfterOff.toString()) else ""
|
||||
description = String.format(getString(R.string.time_after_screen_off_description), duration)
|
||||
action = Intent.ACTION_SCREEN_OFF
|
||||
constructor(actionCheckId: Int, timeAfterOff: Int, timeAfterOn: Int, timeAfterLocked: Int, timeAfterUnlocked: Int, checkAgain: Boolean = false) : this() {
|
||||
val duration = when (actionCheckId) {
|
||||
R.id.rb_action_screen_on -> {
|
||||
val durationStr = if (timeAfterOn > 0) String.format(getString(R.string.duration_minute), timeAfterOn.toString()) else ""
|
||||
description = String.format(getString(R.string.time_after_screen_on_description), durationStr)
|
||||
action = Intent.ACTION_SCREEN_ON
|
||||
timeAfterOn
|
||||
}
|
||||
|
||||
R.id.rb_action_screen_unlocked -> {
|
||||
val durationStr = if (timeAfterUnlocked > 0) String.format(getString(R.string.duration_minute), timeAfterUnlocked.toString()) else ""
|
||||
description = String.format(getString(R.string.time_after_screen_unlocked_description), durationStr)
|
||||
action = Intent.ACTION_USER_PRESENT
|
||||
timeAfterUnlocked
|
||||
}
|
||||
|
||||
R.id.rb_action_screen_locked -> {
|
||||
val durationStr = if (timeAfterLocked > 0) String.format(getString(R.string.duration_minute), timeAfterLocked.toString()) else ""
|
||||
description = String.format(getString(R.string.time_after_screen_locked_description), durationStr)
|
||||
action = Intent.ACTION_SCREEN_OFF + "_LOCKED"
|
||||
timeAfterLocked
|
||||
}
|
||||
|
||||
else -> {
|
||||
val durationStr = if (timeAfterOff > 0) String.format(getString(R.string.duration_minute), timeAfterOff.toString()) else ""
|
||||
description = String.format(getString(R.string.time_after_screen_off_description), durationStr)
|
||||
action = Intent.ACTION_SCREEN_OFF
|
||||
timeAfterOff
|
||||
}
|
||||
}
|
||||
|
||||
timeAfterScreenOff = timeAfterOff
|
||||
timeAfterScreenOn = timeAfterOn
|
||||
timeAfterScreenLocked = timeAfterLocked
|
||||
timeAfterScreenUnlocked = timeAfterUnlocked
|
||||
this.checkAgain = checkAgain
|
||||
if (checkAgain && duration > 0) {
|
||||
description += ", " + getString(R.string.task_condition_check_again)
|
||||
}
|
||||
}
|
||||
|
||||
fun getActionCheckId(): Int {
|
||||
return when (action) {
|
||||
Intent.ACTION_SCREEN_ON -> R.id.rb_action_screen_on
|
||||
else -> R.id.rb_action_screen_off
|
||||
Intent.ACTION_SCREEN_OFF -> R.id.rb_action_screen_off
|
||||
Intent.ACTION_USER_PRESENT -> R.id.rb_action_screen_unlocked
|
||||
else -> R.id.rb_action_screen_locked
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,25 +4,29 @@ import java.io.Serializable
|
||||
|
||||
data class BarkSetting(
|
||||
//推送地址
|
||||
var server: String,
|
||||
var server: String = "",
|
||||
//分组名称
|
||||
val group: String? = "",
|
||||
val group: String = "",
|
||||
//消息图标
|
||||
val icon: String? = "",
|
||||
val icon: String = "",
|
||||
//消息声音
|
||||
val sound: String? = "",
|
||||
val sound: String = "",
|
||||
//消息角标
|
||||
val badge: String? = "",
|
||||
val badge: String = "",
|
||||
//消息链接
|
||||
val url: String? = "",
|
||||
val url: String = "",
|
||||
//通知级别
|
||||
val level: String? = "active",
|
||||
val level: String = "active",
|
||||
//标题模板
|
||||
val title: String? = "",
|
||||
val title: String = "",
|
||||
//加密算法
|
||||
val transformation: String = "none",
|
||||
//加密密钥
|
||||
val key: String = "",
|
||||
//初始偏移向量
|
||||
val iv: String = "",
|
||||
) : Serializable
|
||||
//持续提醒
|
||||
val call: String = "",
|
||||
//自动复制模板
|
||||
val autoCopy: String = "",
|
||||
) : Serializable
|
||||
|
@ -5,12 +5,12 @@ import java.io.Serializable
|
||||
|
||||
data class DingtalkGroupRobotSetting(
|
||||
var token: String = "",
|
||||
var secret: String? = "",
|
||||
var atAll: Boolean? = false,
|
||||
var atMobiles: String? = "",
|
||||
var atDingtalkIds: String? = "",
|
||||
var msgtype: String? = "text",
|
||||
val titleTemplate: String? = "",
|
||||
var secret: String = "",
|
||||
var atAll: Boolean = false,
|
||||
var atMobiles: String = "",
|
||||
var atDingtalkIds: String = "",
|
||||
var msgtype: String = "text",
|
||||
val titleTemplate: String = "",
|
||||
) : Serializable {
|
||||
|
||||
fun getMsgTypeCheckId(): Int {
|
||||
|
@ -10,13 +10,13 @@ data class DingtalkInnerRobotSetting(
|
||||
val appSecret: String = "",
|
||||
val userIds: String = "",
|
||||
val msgKey: String = "sampleText",
|
||||
val titleTemplate: String? = "",
|
||||
val titleTemplate: String = "",
|
||||
val proxyType: Proxy.Type = Proxy.Type.DIRECT,
|
||||
val proxyHost: String? = "",
|
||||
val proxyPort: String? = "",
|
||||
val proxyAuthenticator: Boolean? = false,
|
||||
val proxyUsername: String? = "",
|
||||
val proxyPassword: String? = "",
|
||||
val proxyHost: String = "",
|
||||
val proxyPort: String = "",
|
||||
val proxyAuthenticator: Boolean = false,
|
||||
val proxyUsername: String = "",
|
||||
val proxyPassword: String = "",
|
||||
) : Serializable {
|
||||
|
||||
fun getProxyTypeCheckId(): Int {
|
||||
|
@ -1,16 +1,30 @@
|
||||
package com.idormy.sms.forwarder.entity.setting
|
||||
|
||||
import com.idormy.sms.forwarder.R
|
||||
import java.io.Serializable
|
||||
|
||||
data class EmailSetting(
|
||||
var mailType: String? = "",
|
||||
var fromEmail: String? = "",
|
||||
var pwd: String? = "",
|
||||
var nickname: String? = "",
|
||||
var host: String? = "",
|
||||
var port: String? = "",
|
||||
var ssl: Boolean? = false,
|
||||
var startTls: Boolean? = false,
|
||||
var toEmail: String? = "",
|
||||
var title: String? = "",
|
||||
) : Serializable
|
||||
var mailType: String = "",
|
||||
var fromEmail: String = "",
|
||||
var pwd: String = "",
|
||||
var nickname: String = "",
|
||||
var host: String = "",
|
||||
var port: String = "",
|
||||
var ssl: Boolean = false,
|
||||
var startTls: Boolean = false,
|
||||
var title: String = "",
|
||||
var recipients: MutableMap<String, Pair<String, String>> = mutableMapOf(),
|
||||
var toEmail: String = "",
|
||||
var keystore: String = "",
|
||||
var password: String = "",
|
||||
var encryptionProtocol: String = "Plain", //加密协议: S/MIME、OpenPGP、Plain(不传证书)
|
||||
) : Serializable {
|
||||
|
||||
fun getEncryptionProtocolCheckId(): Int {
|
||||
return when (encryptionProtocol) {
|
||||
"S/MIME" -> R.id.rb_encryption_protocol_smime
|
||||
"OpenPGP" -> R.id.rb_encryption_protocol_openpgp
|
||||
else -> R.id.rb_encryption_protocol_plain
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,14 +5,14 @@ import java.io.Serializable
|
||||
|
||||
data class FeishuSetting(
|
||||
var webhook: String = "",
|
||||
val secret: String? = "",
|
||||
val msgType: String? = "interactive",
|
||||
val titleTemplate: String? = "",
|
||||
val secret: String = "",
|
||||
val msgType: String = "interactive",
|
||||
val titleTemplate: String = "",
|
||||
val messageCard: String = "", //自定义消息卡片
|
||||
) : Serializable {
|
||||
|
||||
fun getMsgTypeCheckId(): Int {
|
||||
return if (msgType == null || msgType == "interactive") {
|
||||
return if (msgType == "interactive") {
|
||||
R.id.rb_msg_type_interactive
|
||||
} else {
|
||||
R.id.rb_msg_type_text
|
||||
|
@ -4,6 +4,6 @@ import java.io.Serializable
|
||||
|
||||
data class GotifySetting(
|
||||
var webServer: String = "",
|
||||
val title: String? = "",
|
||||
val priority: String? = "",
|
||||
val title: String = "",
|
||||
val priority: String = "",
|
||||
) : Serializable
|
@ -5,11 +5,11 @@ import java.io.Serializable
|
||||
data class PushplusSetting(
|
||||
var website: String = "www.pushplus.plus",
|
||||
var token: String = "",
|
||||
val topic: String? = "",
|
||||
val template: String? = "",
|
||||
val channel: String? = "",
|
||||
val webhook: String? = "",
|
||||
val callbackUrl: String? = "",
|
||||
val validTime: String? = "",
|
||||
val titleTemplate: String? = "",
|
||||
val topic: String = "",
|
||||
val template: String = "",
|
||||
val channel: String = "",
|
||||
val webhook: String = "",
|
||||
val callbackUrl: String = "",
|
||||
val validTime: String = "",
|
||||
val titleTemplate: String = "",
|
||||
) : Serializable
|
@ -4,7 +4,7 @@ import java.io.Serializable
|
||||
|
||||
data class ServerchanSetting(
|
||||
var sendKey: String = "",
|
||||
var channel: String? = "",
|
||||
var openid: String? = "",
|
||||
var titleTemplate: String? = "",
|
||||
var channel: String = "",
|
||||
var openid: String = "",
|
||||
var titleTemplate: String = "",
|
||||
) : Serializable
|
@ -6,7 +6,7 @@ import java.io.Serializable
|
||||
data class SmsSetting(
|
||||
var simSlot: Int = 0,
|
||||
var mobiles: String = "",
|
||||
var onlyNoNetwork: Boolean? = false,
|
||||
var onlyNoNetwork: Boolean = false,
|
||||
) : Serializable {
|
||||
|
||||
fun getSmsSimSlotCheckId(): Int {
|
||||
|
@ -4,12 +4,12 @@ import com.idormy.sms.forwarder.R
|
||||
import java.io.Serializable
|
||||
|
||||
data class SocketSetting(
|
||||
val method: String? = "MQTT",
|
||||
val method: String = "MQTT",
|
||||
var address: String = "", //IP地址
|
||||
val port: Int = 0, //端口号
|
||||
val msgTemplate: String = "", //消息模板
|
||||
val secret: String? = "", //签名密钥
|
||||
val response: String? = "", //成功应答关键字
|
||||
val secret: String = "", //签名密钥
|
||||
val response: String = "", //成功应答关键字
|
||||
val username: String = "", //用户名
|
||||
val password: String = "", //密码
|
||||
val inCharset: String = "", //输入编码
|
||||
@ -23,7 +23,7 @@ data class SocketSetting(
|
||||
|
||||
fun getMethodCheckId(): Int {
|
||||
return when (method) {
|
||||
null, "MQTT" -> R.id.rb_method_mqtt
|
||||
"MQTT" -> R.id.rb_method_mqtt
|
||||
"TCP" -> R.id.rb_method_tcp
|
||||
"UDP" -> R.id.rb_method_udp
|
||||
else -> R.id.rb_method_mqtt
|
||||
|
@ -5,19 +5,20 @@ import java.io.Serializable
|
||||
import java.net.Proxy
|
||||
|
||||
data class TelegramSetting(
|
||||
val method: String? = "POST",
|
||||
val method: String = "POST",
|
||||
var apiToken: String = "",
|
||||
val chatId: String = "",
|
||||
val proxyType: Proxy.Type = Proxy.Type.DIRECT,
|
||||
val proxyHost: String? = "",
|
||||
val proxyPort: String? = "",
|
||||
val proxyAuthenticator: Boolean? = false,
|
||||
val proxyUsername: String? = "",
|
||||
val proxyPassword: String? = "",
|
||||
val proxyHost: String = "",
|
||||
val proxyPort: String = "",
|
||||
val proxyAuthenticator: Boolean = false,
|
||||
val proxyUsername: String = "",
|
||||
val proxyPassword: String = "",
|
||||
val parseMode: String = "HTML",
|
||||
) : Serializable {
|
||||
|
||||
fun getMethodCheckId(): Int {
|
||||
return if (method == null || method == "POST") R.id.rb_method_post else R.id.rb_method_get
|
||||
return if (method == "GET") R.id.rb_method_get else R.id.rb_method_post
|
||||
}
|
||||
|
||||
fun getProxyTypeCheckId(): Int {
|
||||
@ -27,4 +28,12 @@ data class TelegramSetting(
|
||||
else -> R.id.rb_proxyNone
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getParseModeCheckId(): Int {
|
||||
return when (parseMode) {
|
||||
"TEXT" -> R.id.rb_parse_mode_text
|
||||
"MarkdownV2" -> R.id.rb_parse_mode_markdown
|
||||
else -> R.id.rb_parse_mode_html
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,5 +3,5 @@ package com.idormy.sms.forwarder.entity.setting
|
||||
import java.io.Serializable
|
||||
|
||||
data class UrlSchemeSetting(
|
||||
var urlScheme: String,
|
||||
var urlScheme: String = "",
|
||||
) : Serializable
|
@ -2,21 +2,36 @@ package com.idormy.sms.forwarder.entity.setting
|
||||
|
||||
import com.idormy.sms.forwarder.R
|
||||
import java.io.Serializable
|
||||
import java.net.Proxy
|
||||
|
||||
data class WebhookSetting(
|
||||
val method: String? = "POST",
|
||||
val method: String = "POST",
|
||||
var webServer: String = "",
|
||||
val secret: String? = "",
|
||||
val response: String? = "",
|
||||
val webParams: String? = "",
|
||||
val headers: Map<String, String>?,
|
||||
val secret: String = "",
|
||||
val response: String = "",
|
||||
val webParams: String = "",
|
||||
val headers: Map<String, String> = mapOf(),
|
||||
val proxyType: Proxy.Type = Proxy.Type.DIRECT,
|
||||
val proxyHost: String = "",
|
||||
val proxyPort: String = "",
|
||||
val proxyAuthenticator: Boolean = false,
|
||||
val proxyUsername: String = "",
|
||||
val proxyPassword: String = "",
|
||||
) : Serializable {
|
||||
fun getMethodCheckId(): Int {
|
||||
return when (method) {
|
||||
null, "POST" -> R.id.rb_method_post
|
||||
"POST" -> R.id.rb_method_post
|
||||
"PUT" -> R.id.rb_method_put
|
||||
"PATCH" -> R.id.rb_method_patch
|
||||
else -> R.id.rb_method_get
|
||||
}
|
||||
}
|
||||
|
||||
fun getProxyTypeCheckId(): Int {
|
||||
return when (proxyType) {
|
||||
Proxy.Type.HTTP -> R.id.rb_proxyHttp
|
||||
Proxy.Type.SOCKS -> R.id.rb_proxySocks
|
||||
else -> R.id.rb_proxyNone
|
||||
}
|
||||
}
|
||||
}
|
@ -8,16 +8,16 @@ data class WeworkAgentSetting(
|
||||
var corpID: String = "",
|
||||
val agentID: String = "",
|
||||
val secret: String = "",
|
||||
val atAll: Boolean? = false,
|
||||
val toUser: String? = "@all",
|
||||
val toParty: String? = "",
|
||||
val toTag: String? = "",
|
||||
val atAll: Boolean = false,
|
||||
val toUser: String = "@all",
|
||||
val toParty: String = "",
|
||||
val toTag: String = "",
|
||||
val proxyType: Proxy.Type = Proxy.Type.DIRECT,
|
||||
val proxyHost: String? = "",
|
||||
val proxyPort: String? = "",
|
||||
val proxyAuthenticator: Boolean? = false,
|
||||
val proxyUsername: String? = "",
|
||||
val proxyPassword: String? = "",
|
||||
val proxyHost: String = "",
|
||||
val proxyPort: String = "",
|
||||
val proxyAuthenticator: Boolean = false,
|
||||
val proxyUsername: String = "",
|
||||
val proxyPassword: String = "",
|
||||
val customizeAPI: String = "https://qyapi.weixin.qq.com",
|
||||
) : Serializable {
|
||||
|
||||
|
@ -4,11 +4,11 @@ import com.idormy.sms.forwarder.R
|
||||
import java.io.Serializable
|
||||
|
||||
data class WeworkRobotSetting(
|
||||
var webHook: String,
|
||||
var webHook: String = "",
|
||||
val msgType: String = "text",
|
||||
var atAll: Boolean? = false,
|
||||
var atUserIds: String? = "",
|
||||
var atMobiles: String? = "",
|
||||
var atAll: Boolean = false,
|
||||
var atUserIds: String = "",
|
||||
var atMobiles: String = "",
|
||||
) : Serializable {
|
||||
|
||||
fun getMsgTypeCheckId(): Int {
|
||||
|
@ -3,6 +3,8 @@ package com.idormy.sms.forwarder.fragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.BuildConfig
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.core.BaseFragment
|
||||
import com.idormy.sms.forwarder.core.webview.AgentWebActivity
|
||||
@ -25,7 +27,6 @@ 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.xui.widget.textview.supertextview.SuperTextView
|
||||
import com.xuexiang.xutil.file.FileUtils
|
||||
import frpclib.Frpclib
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
@ -55,7 +56,7 @@ class AboutFragment : BaseFragment<FragmentAboutBinding?>(), SuperTextView.OnSup
|
||||
binding!!.menuVersion.setLeftString(String.format(resources.getString(R.string.about_app_version), AppUtils.getAppVersionName()))
|
||||
binding!!.menuCache.setLeftString(String.format(resources.getString(R.string.about_cache_size), CacheUtils.getTotalCacheSize(requireContext())))
|
||||
|
||||
if (FileUtils.isFileExists(context?.filesDir?.absolutePath + "/libs/libgojni.so")) {
|
||||
if (App.FrpclibInited) {
|
||||
binding!!.menuFrpc.setLeftString(String.format(resources.getString(R.string.about_frpc_version), Frpclib.getVersion()))
|
||||
binding!!.menuFrpc.visibility = View.VISIBLE
|
||||
}
|
||||
@ -68,11 +69,19 @@ class AboutFragment : BaseFragment<FragmentAboutBinding?>(), SuperTextView.OnSup
|
||||
binding!!.scbAutoCheckUpdate.setOnCheckedChangeListener { _, isChecked ->
|
||||
SettingUtils.autoCheckUpdate = isChecked
|
||||
}
|
||||
|
||||
binding!!.sbJoinPreviewProgram.isChecked = SettingUtils.joinPreviewProgram
|
||||
binding!!.sbJoinPreviewProgram.setOnCheckedChangeListener { _, isChecked ->
|
||||
SettingUtils.joinPreviewProgram = isChecked
|
||||
if (isChecked) {
|
||||
XToastUtils.success(getString(R.string.join_preview_program_tips))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun initListeners() {
|
||||
binding!!.btnUpdate.setOnClickListener {
|
||||
XUpdateInit.checkUpdate(requireContext(), true)
|
||||
XUpdateInit.checkUpdate(requireContext(), true, SettingUtils.joinPreviewProgram)
|
||||
}
|
||||
binding!!.btnCache.setOnClickListener {
|
||||
HistoryUtils.clearPreference()
|
||||
@ -107,6 +116,8 @@ class AboutFragment : BaseFragment<FragmentAboutBinding?>(), SuperTextView.OnSup
|
||||
AgentWebActivity.goWeb(context, getString(R.string.url_project_gitee))
|
||||
}
|
||||
|
||||
binding!!.menuJoinPreviewProgram.setOnSuperTextViewClickListener(this)
|
||||
binding!!.menuVersion.setOnSuperTextViewClickListener(this)
|
||||
binding!!.menuWechatMiniprogram.setOnSuperTextViewClickListener(this)
|
||||
binding!!.menuDonation.setOnSuperTextViewClickListener(this)
|
||||
binding!!.menuUserProtocol.setOnSuperTextViewClickListener(this)
|
||||
@ -116,6 +127,22 @@ class AboutFragment : BaseFragment<FragmentAboutBinding?>(), SuperTextView.OnSup
|
||||
@SingleClick
|
||||
override fun onClick(v: SuperTextView) {
|
||||
when (v.id) {
|
||||
R.id.menu_join_preview_program -> {
|
||||
XToastUtils.info(getString(R.string.join_preview_program_tips))
|
||||
}
|
||||
|
||||
R.id.menu_version -> {
|
||||
XToastUtils.info(
|
||||
String.format(
|
||||
getString(R.string.about_app_version_tips),
|
||||
AppUtils.getAppVersionName(),
|
||||
AppUtils.getAppVersionCode(),
|
||||
BuildConfig.BUILD_TIME,
|
||||
BuildConfig.GIT_COMMIT_ID
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
R.id.menu_donation -> {
|
||||
previewMarkdown(this, getString(R.string.about_item_donation_link), getString(R.string.url_donation_link), false)
|
||||
}
|
||||
|
@ -9,6 +9,9 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.hjq.permissions.OnPermissionCallback
|
||||
import com.hjq.permissions.Permission
|
||||
import com.hjq.permissions.XXPermissions
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.adapter.AppListAdapter
|
||||
@ -24,11 +27,11 @@ import com.scwang.smartrefresh.layout.api.RefreshLayout
|
||||
import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener
|
||||
import com.xuexiang.xaop.annotation.SingleClick
|
||||
import com.xuexiang.xpage.annotation.Page
|
||||
import com.xuexiang.xui.XUI
|
||||
import com.xuexiang.xui.utils.DensityUtils
|
||||
import com.xuexiang.xui.utils.ThemeUtils
|
||||
import com.xuexiang.xui.utils.WidgetUtils
|
||||
import com.xuexiang.xui.widget.actionbar.TitleBar
|
||||
import com.xuexiang.xutil.XUtil
|
||||
import com.xuexiang.xutil.resource.ResUtils.getStringArray
|
||||
|
||||
@Suppress("PrivatePropertyName", "DEPRECATION")
|
||||
@ -91,10 +94,10 @@ class AppListFragment : BaseFragment<FragmentAppListBinding?>() {
|
||||
}
|
||||
|
||||
override fun onRefresh(refreshLayout: RefreshLayout) {
|
||||
appListAdapter?.refresh(getAppsList(true))
|
||||
refreshLayout.layout.postDelayed({
|
||||
appListAdapter?.refresh(getAppsList(true))
|
||||
refreshLayout.finishRefresh()
|
||||
}, 3000)
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
appListAdapter?.setOnItemClickListener { _, item, _ ->
|
||||
@ -120,12 +123,24 @@ class AppListFragment : BaseFragment<FragmentAppListBinding?>() {
|
||||
|
||||
private fun getAppsList(refresh: Boolean): MutableList<AppInfo> {
|
||||
if (refresh || (currentType == "user" && App.UserAppList.isEmpty()) || (currentType == "system" && App.SystemAppList.isEmpty())) {
|
||||
XToastUtils.info(getString(R.string.loading_app_list))
|
||||
val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build()
|
||||
WorkManager.getInstance(XUtil.getContext()).enqueue(request)
|
||||
//检查读取应用列表权限是否获取
|
||||
XXPermissions.with(this).permission(Permission.GET_INSTALLED_APPS).request(object : OnPermissionCallback {
|
||||
override fun onGranted(permissions: MutableList<String>, allGranted: Boolean) {
|
||||
XToastUtils.info(getString(R.string.loading_app_list))
|
||||
val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build()
|
||||
WorkManager.getInstance(XUI.getContext()).enqueue(request)
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: MutableList<String>, doNotAskAgain: Boolean) {
|
||||
XToastUtils.error(R.string.tips_get_installed_apps)
|
||||
if (doNotAskAgain) {
|
||||
XXPermissions.startPermissionActivity(XUI.getContext(), permissions)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return if (currentType == "system") App.SystemAppList else App.UserAppList
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
|
||||
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
|
||||
import com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
|
||||
import com.xuexiang.xutil.resource.ResUtils.getColors
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Page(name = "Frp内网穿透·编辑配置")
|
||||
@ -34,6 +35,7 @@ class FrpcEditFragment : BaseFragment<FragmentFrpcEditBinding?>() {
|
||||
private var titleBar: TitleBar? = null
|
||||
private var frpc: Frpc? = null
|
||||
private val viewModel by viewModels<FrpcViewModel> { BaseViewModelFactory(context) }
|
||||
private val codeview by lazy { binding!!.codeview }
|
||||
|
||||
override fun initViews() {
|
||||
val pairCompleteMap: MutableMap<Char, Char> = HashMap()
|
||||
@ -43,14 +45,25 @@ class FrpcEditFragment : BaseFragment<FragmentFrpcEditBinding?>() {
|
||||
pairCompleteMap['<'] = '>'
|
||||
pairCompleteMap['"'] = '"'
|
||||
|
||||
binding!!.editText.enablePairComplete(true)
|
||||
binding!!.editText.enablePairCompleteCenterCursor(true)
|
||||
binding!!.editText.setPairCompleteMap(pairCompleteMap)
|
||||
codeview.enablePairComplete(true)
|
||||
codeview.enablePairCompleteCenterCursor(true)
|
||||
codeview.setPairCompleteMap(pairCompleteMap)
|
||||
|
||||
binding!!.editText.setEnableLineNumber(true)
|
||||
binding!!.editText.setLineNumberTextColor(Color.LTGRAY)
|
||||
binding!!.editText.setLineNumberTextSize(24f)
|
||||
binding!!.editText.textSize = 14f
|
||||
codeview.setEnableLineNumber(true)
|
||||
codeview.setLineNumberTextColor(Color.LTGRAY)
|
||||
codeview.setLineNumberTextSize(24f)
|
||||
codeview.textSize = 14f
|
||||
|
||||
//语法高亮
|
||||
val syntaxPatterns: MutableMap<Pattern, Int> = HashMap()
|
||||
syntaxPatterns[Pattern.compile("\\s*#.*")] = Color.GRAY
|
||||
syntaxPatterns[Pattern.compile("\\[\\[?([^]]*?)]]?", Pattern.DOTALL)] = Color.MAGENTA
|
||||
syntaxPatterns[Pattern.compile("\\[\\[?")] = Color.WHITE
|
||||
syntaxPatterns[Pattern.compile("]]?")] = Color.WHITE
|
||||
syntaxPatterns[Pattern.compile(".*(?=\\s=)")] = Color.YELLOW
|
||||
syntaxPatterns[Pattern.compile("(?<=\\s=)\\s*\"[^\"]*\"\\s*\n", Pattern.DOTALL)] = Color.GREEN
|
||||
syntaxPatterns[Pattern.compile("(?<=\\s=).*\n")] = Color.CYAN
|
||||
codeview.setSyntaxPatternsMap(syntaxPatterns)
|
||||
}
|
||||
|
||||
override fun viewBindingInflate(inflater: LayoutInflater, container: ViewGroup): FragmentFrpcEditBinding {
|
||||
@ -74,7 +87,7 @@ class FrpcEditFragment : BaseFragment<FragmentFrpcEditBinding?>() {
|
||||
tvName.setText(frpc!!.name)
|
||||
sbAutorun.setCheckedImmediately(frpc!!.autorun == 1)
|
||||
|
||||
frpc!!.config = binding!!.editText.text.toString()
|
||||
frpc!!.config = codeview.text.toString()
|
||||
|
||||
if (TextUtils.isEmpty(frpc!!.config)) {
|
||||
XToastUtils.error(R.string.tips_input_config_content)
|
||||
@ -128,7 +141,7 @@ class FrpcEditFragment : BaseFragment<FragmentFrpcEditBinding?>() {
|
||||
titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_restore) {
|
||||
@SingleClick
|
||||
override fun performAction(view: View) {
|
||||
binding!!.editText.setText(frpc?.config!!)
|
||||
codeview.setText(frpc?.config!!)
|
||||
XToastUtils.success(R.string.tipRestoreSuccess)
|
||||
}
|
||||
})
|
||||
@ -138,7 +151,7 @@ class FrpcEditFragment : BaseFragment<FragmentFrpcEditBinding?>() {
|
||||
override fun initListeners() {
|
||||
LiveEventBus.get(INTENT_FRPC_EDIT_FILE, Frpc::class.java).observeSticky(this) { value: Frpc ->
|
||||
frpc = value
|
||||
binding!!.editText.setText(value.config)
|
||||
codeview.setText(value.config)
|
||||
titleBar!!.setTitle(if (TextUtils.isEmpty(value.name)) getString(R.string.noName) else value.name)
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ 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.App
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.adapter.FrpcPagingAdapter
|
||||
import com.idormy.sms.forwarder.core.BaseFragment
|
||||
@ -17,10 +18,12 @@ import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory
|
||||
import com.idormy.sms.forwarder.database.viewmodel.FrpcViewModel
|
||||
import com.idormy.sms.forwarder.databinding.FragmentFrpcsBinding
|
||||
import com.idormy.sms.forwarder.service.ForegroundService
|
||||
import com.idormy.sms.forwarder.utils.ACTION_START
|
||||
import com.idormy.sms.forwarder.utils.EVENT_FRPC_DELETE_CONFIG
|
||||
import com.idormy.sms.forwarder.utils.EVENT_FRPC_RUNNING_ERROR
|
||||
import com.idormy.sms.forwarder.utils.EVENT_FRPC_RUNNING_SUCCESS
|
||||
import com.idormy.sms.forwarder.utils.EVENT_FRPC_UPDATE_CONFIG
|
||||
import com.idormy.sms.forwarder.utils.FRPC_LIB_VERSION
|
||||
import com.idormy.sms.forwarder.utils.FrpcUtils
|
||||
import com.idormy.sms.forwarder.utils.INTENT_FRPC_APPLY_FILE
|
||||
import com.idormy.sms.forwarder.utils.INTENT_FRPC_EDIT_FILE
|
||||
@ -144,9 +147,14 @@ class FrpcFragment : BaseFragment<FragmentFrpcsBinding?>(), FrpcPagingAdapter.On
|
||||
}
|
||||
|
||||
R.id.iv_play -> {
|
||||
if (!App.FrpclibInited) {
|
||||
XToastUtils.error(String.format(getString(R.string.frpclib_download_title), FRPC_LIB_VERSION))
|
||||
return
|
||||
}
|
||||
|
||||
if (!ForegroundService.isRunning) {
|
||||
val serviceIntent = Intent(requireContext(), ForegroundService::class.java)
|
||||
serviceIntent.action = "START"
|
||||
serviceIntent.action = ACTION_START
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
requireContext().startForegroundService(serviceIntent)
|
||||
} else {
|
||||
@ -189,6 +197,11 @@ class FrpcFragment : BaseFragment<FragmentFrpcsBinding?>(), FrpcPagingAdapter.On
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (!App.FrpclibInited) {
|
||||
XToastUtils.error(String.format(getString(R.string.frpclib_download_title), FRPC_LIB_VERSION))
|
||||
return
|
||||
}
|
||||
|
||||
//编辑或删除需要先停止客户端
|
||||
if (Frpclib.isRunning(item.uid)) {
|
||||
XToastUtils.warning(R.string.tipServiceRunning)
|
||||
|
@ -1,19 +1,23 @@
|
||||
package com.idormy.sms.forwarder.fragment
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.text.InputType
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RadioGroup
|
||||
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.App.Companion.FORWARD_STATUS_MAP
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.activity.MainActivity
|
||||
import com.idormy.sms.forwarder.adapter.MsgPagingAdapter
|
||||
import com.idormy.sms.forwarder.core.BaseFragment
|
||||
import com.idormy.sms.forwarder.core.Core
|
||||
import com.idormy.sms.forwarder.database.entity.LogsDetail
|
||||
import com.idormy.sms.forwarder.database.entity.MsgAndLogs
|
||||
import com.idormy.sms.forwarder.database.entity.Rule
|
||||
@ -27,19 +31,23 @@ import com.scwang.smartrefresh.layout.api.RefreshLayout
|
||||
import com.xuexiang.xaop.annotation.SingleClick
|
||||
import com.xuexiang.xpage.annotation.Page
|
||||
import com.xuexiang.xui.widget.actionbar.TitleBar
|
||||
import com.xuexiang.xui.widget.button.SmoothCheckBox
|
||||
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
|
||||
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
|
||||
import com.xuexiang.xui.widget.picker.widget.TimePickerView
|
||||
import com.xuexiang.xui.widget.picker.widget.builder.TimePickerBuilder
|
||||
import com.xuexiang.xui.widget.picker.widget.configure.TimePickerType
|
||||
import com.xuexiang.xutil.data.DateUtils
|
||||
import com.xuexiang.xutil.resource.ResUtils.getColors
|
||||
import com.xuexiang.xutil.resource.ResUtils.getStringArray
|
||||
import io.reactivex.CompletableObserver
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import com.xuexiang.xutil.tip.ToastUtils
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
@Suppress("PrivatePropertyName")
|
||||
@Page(name = "转发日志")
|
||||
@ -50,13 +58,11 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnIt
|
||||
private var adapter = MsgPagingAdapter(this)
|
||||
private val viewModel by viewModels<MsgViewModel> { BaseViewModelFactory(context) }
|
||||
private var currentType: String = "sms"
|
||||
private val FORWARD_STATUS_MAP = object : HashMap<Int, String>() {
|
||||
init {
|
||||
put(0, getString(R.string.failed))
|
||||
put(1, getString(R.string.processing))
|
||||
put(2, getString(R.string.success))
|
||||
}
|
||||
}
|
||||
|
||||
//日志筛选
|
||||
private var currentFilter: MutableMap<String, Any> = mutableMapOf()
|
||||
private var logsFilterPopup: MaterialDialog? = null
|
||||
private var timePicker: TimePickerView? = null
|
||||
|
||||
override fun viewBindingInflate(
|
||||
inflater: LayoutInflater,
|
||||
@ -74,27 +80,29 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnIt
|
||||
@SingleClick
|
||||
override fun performAction(view: View) {
|
||||
MaterialDialog.Builder(requireContext())
|
||||
.content(R.string.delete_type_log_tips)
|
||||
.content(if (currentFilter.isEmpty()) R.string.delete_type_log_tips else R.string.delete_filter_log_tips)
|
||||
.positiveText(R.string.lab_yes)
|
||||
.negativeText(R.string.lab_no)
|
||||
.onPositive { _: MaterialDialog?, _: DialogAction? ->
|
||||
Core.msg.deleteAll(currentType)
|
||||
.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) }
|
||||
}
|
||||
})
|
||||
try {
|
||||
Log.d(TAG, "deleteAll, currentType:$currentType, currentFilter:$currentFilter")
|
||||
viewModel.setType(currentType).setFilter(currentFilter).deleteAll()
|
||||
reloadData()
|
||||
XToastUtils.success(if (currentFilter.isEmpty()) R.string.delete_type_log_toast else R.string.delete_filter_log_toast)
|
||||
} catch (e: Exception) {
|
||||
e.message?.let { XToastUtils.error(it) }
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
})
|
||||
titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_filter) {
|
||||
@SingleClick
|
||||
override fun performAction(view: View) {
|
||||
initLogsFilterDialog()
|
||||
logsFilterPopup?.show()
|
||||
}
|
||||
})
|
||||
return titleBar
|
||||
}
|
||||
|
||||
@ -121,9 +129,8 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnIt
|
||||
2 -> "app"
|
||||
else -> "sms"
|
||||
}
|
||||
viewModel.setType(currentType)
|
||||
adapter.refresh()
|
||||
binding!!.recyclerView.scrollToPosition(0)
|
||||
initLogsFilterDialog(true)
|
||||
reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,7 +141,7 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnIt
|
||||
binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout ->
|
||||
//adapter.refresh()
|
||||
lifecycleScope.launch {
|
||||
viewModel.setType(currentType).allMsg.collectLatest { adapter.submitData(it) }
|
||||
viewModel.setType(currentType).setFilter(currentFilter).allMsg.collectLatest { adapter.submitData(it) }
|
||||
}
|
||||
refreshLayout.finishRefresh()
|
||||
}
|
||||
@ -149,12 +156,8 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnIt
|
||||
detailStr.append(getString(R.string.from)).append(item.msg.from).append("\n\n")
|
||||
if (!TextUtils.isEmpty(item.msg.simInfo)) {
|
||||
if (item.msg.type == "app") {
|
||||
val splitSimInfo = item.msg.simInfo.split("#####")
|
||||
val title = splitSimInfo.getOrElse(0) { item.msg.simInfo }
|
||||
val scheme = splitSimInfo.getOrElse(1) { "" }
|
||||
detailStr.append(getString(R.string.title)).append(title).append("\n\n")
|
||||
detailStr.append(getString(R.string.title)).append(item.msg.simInfo).append("\n\n")
|
||||
detailStr.append(getString(R.string.msg)).append(item.msg.content).append("\n\n")
|
||||
if (!TextUtils.isEmpty(scheme) && scheme != "null") detailStr.append(getString(R.string.scheme)).append(scheme).append("\n\n")
|
||||
} else {
|
||||
detailStr.append(getString(R.string.msg)).append(item.msg.content).append("\n\n")
|
||||
detailStr.append(getString(R.string.slot)).append(item.msg.simInfo).append("\n\n")
|
||||
@ -185,7 +188,7 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnIt
|
||||
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)
|
||||
ruleStr.append(Rule.getRuleMatch(item.type, item.ruleFiled, item.ruleCheck, item.ruleValue, item.ruleSimSlot)).append(item.senderName)
|
||||
val detailStr = StringBuilder()
|
||||
detailStr.append(getString(R.string.rule)).append(ruleStr.toString()).append("\n\n")
|
||||
@SuppressLint("SimpleDateFormat") val utcFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
|
||||
@ -196,11 +199,6 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnIt
|
||||
.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)
|
||||
@ -211,4 +209,139 @@ class LogsFragment : BaseFragment<FragmentLogsBinding?>(), MsgPagingAdapter.OnIt
|
||||
|
||||
override fun onItemRemove(view: View?, id: Int) {}
|
||||
|
||||
private fun reloadData() {
|
||||
viewModel.setType(currentType).setFilter(currentFilter)
|
||||
adapter.refresh()
|
||||
binding!!.recyclerView.scrollToPosition(0)
|
||||
}
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
private fun initLogsFilterDialog(needInit: Boolean = false) {
|
||||
if (logsFilterPopup == null || needInit) {
|
||||
currentFilter = mutableMapOf()
|
||||
|
||||
val logsFilterDialog = View.inflate(requireContext(), R.layout.dialog_logs_filter, null)
|
||||
val layoutTitle = logsFilterDialog.findViewById<LinearLayout>(R.id.layout_title)
|
||||
val layoutSimSlot = logsFilterDialog.findViewById<LinearLayout>(R.id.layout_sim_slot)
|
||||
val layoutCallType = logsFilterDialog.findViewById<LinearLayout>(R.id.layout_call_type)
|
||||
when (currentType) {
|
||||
"app" -> {
|
||||
layoutTitle.visibility = View.VISIBLE
|
||||
layoutSimSlot.visibility = View.GONE
|
||||
layoutCallType.visibility = View.GONE
|
||||
}
|
||||
|
||||
"call" -> {
|
||||
layoutTitle.visibility = View.GONE
|
||||
layoutSimSlot.visibility = View.VISIBLE
|
||||
layoutCallType.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
else -> {
|
||||
layoutTitle.visibility = View.GONE
|
||||
layoutSimSlot.visibility = View.VISIBLE
|
||||
layoutCallType.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
val scbCallType1 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_call_type1)
|
||||
val scbCallType2 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_call_type2)
|
||||
val scbCallType3 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_call_type3)
|
||||
val scbCallType4 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_call_type4)
|
||||
val scbCallType5 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_call_type5)
|
||||
val scbCallType6 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_call_type6)
|
||||
val etFrom = logsFilterDialog.findViewById<EditText>(R.id.et_from)
|
||||
val etContent = logsFilterDialog.findViewById<EditText>(R.id.et_content)
|
||||
val etTitle = logsFilterDialog.findViewById<EditText>(R.id.et_title)
|
||||
val rgSimSlot = logsFilterDialog.findViewById<RadioGroup>(R.id.rg_sim_slot)
|
||||
val etStartTime = logsFilterDialog.findViewById<EditText>(R.id.et_start_time)
|
||||
val scbForwardStatus0 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_forward_status_0)
|
||||
val scbForwardStatus1 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_forward_status_1)
|
||||
val scbForwardStatus2 = logsFilterDialog.findViewById<SmoothCheckBox>(R.id.scb_forward_status_2)
|
||||
etStartTime.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (hasFocus) {
|
||||
showTimePicker(etStartTime.text.toString().trim(), getString(R.string.start_time), etStartTime)
|
||||
} else {
|
||||
timePicker?.dismiss()
|
||||
}
|
||||
}
|
||||
val etEndTime = logsFilterDialog.findViewById<EditText>(R.id.et_end_time)
|
||||
etEndTime.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (hasFocus) {
|
||||
showTimePicker(etEndTime.text.toString().trim(), getString(R.string.end_time), etEndTime)
|
||||
} else {
|
||||
timePicker?.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
logsFilterPopup = MaterialDialog.Builder(requireContext())
|
||||
.iconRes(android.R.drawable.ic_menu_search)
|
||||
.title(R.string.menu_logs)
|
||||
.customView(logsFilterDialog, true)
|
||||
.cancelable(false)
|
||||
.autoDismiss(false)
|
||||
.neutralText(R.string.reset)
|
||||
.neutralColor(getColors(R.color.darkGrey))
|
||||
.onNeutral { dialog: MaterialDialog?, _: DialogAction? ->
|
||||
dialog?.dismiss()
|
||||
currentFilter = mutableMapOf()
|
||||
logsFilterPopup = null
|
||||
reloadData()
|
||||
}.positiveText(R.string.search).onPositive { dialog: MaterialDialog?, _: DialogAction? ->
|
||||
currentFilter = mutableMapOf()
|
||||
currentFilter["from"] = etFrom.text.toString().trim()
|
||||
currentFilter["content"] = etContent.text.toString().trim()
|
||||
currentFilter["title"] = etTitle.text.toString().trim()
|
||||
currentFilter["start_time"] = etStartTime.text.toString().trim()
|
||||
currentFilter["end_time"] = etEndTime.text.toString().trim()
|
||||
currentFilter["sim_slot"] = if (currentType == "app") -1 else when (rgSimSlot.checkedRadioButtonId) {
|
||||
R.id.rb_sim_slot_1 -> 0
|
||||
R.id.rb_sim_slot_2 -> 1
|
||||
else -> -1
|
||||
}
|
||||
if (currentType == "call") {
|
||||
currentFilter["call_type"] = mutableListOf<Int>().apply {
|
||||
if (scbCallType1.isChecked) add(1)
|
||||
if (scbCallType2.isChecked) add(2)
|
||||
if (scbCallType3.isChecked) add(3)
|
||||
if (scbCallType4.isChecked) add(4)
|
||||
if (scbCallType5.isChecked) add(5)
|
||||
if (scbCallType6.isChecked) add(6)
|
||||
}
|
||||
}
|
||||
currentFilter["forward_status"] = mutableListOf<Int>().apply {
|
||||
if (scbForwardStatus0.isChecked) add(0)
|
||||
if (scbForwardStatus1.isChecked) add(1)
|
||||
if (scbForwardStatus2.isChecked) add(2)
|
||||
}
|
||||
reloadData()
|
||||
dialog?.dismiss()
|
||||
}.build()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showTimePicker(time: String, title: String, et: EditText) {
|
||||
et.inputType = InputType.TYPE_NULL
|
||||
val calendar: Calendar = Calendar.getInstance()
|
||||
calendar.time = try {
|
||||
if (time.isEmpty()) Date() else DateUtils.string2Date(time, DateUtils.yyyyMMddHHmmss.get())
|
||||
} catch (e: Exception) {
|
||||
Date()
|
||||
}
|
||||
timePicker = TimePickerBuilder(context) { date, _ ->
|
||||
ToastUtils.toast(DateUtils.date2String(date, DateUtils.yyyyMMddHHmmss.get()))
|
||||
et.setText(DateUtils.date2String(date, DateUtils.yyyyMMddHHmmss.get()))
|
||||
}
|
||||
.setTimeSelectChangeListener { date ->
|
||||
Log.i("pvTime", "onTimeSelectChanged")
|
||||
et.setText(DateUtils.date2String(date, DateUtils.yyyyMMddHHmmss.get()))
|
||||
}
|
||||
.setType(TimePickerType.ALL)
|
||||
.setTitleText(title)
|
||||
.isDialog(true)
|
||||
.setOutSideCancelable(false)
|
||||
.setDate(calendar)
|
||||
.build()
|
||||
timePicker?.show(false)
|
||||
}
|
||||
}
|
@ -4,7 +4,11 @@ import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import android.widget.AdapterView
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.EditText
|
||||
import android.widget.RadioGroup
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
@ -13,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.App.Companion.CALL_TYPE_MAP
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.adapter.SenderRecyclerAdapter
|
||||
import com.idormy.sms.forwarder.adapter.base.ItemMoveCallback
|
||||
@ -28,7 +33,40 @@ 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.idormy.sms.forwarder.utils.CHECK_CONTAIN
|
||||
import com.idormy.sms.forwarder.utils.CHECK_END_WITH
|
||||
import com.idormy.sms.forwarder.utils.CHECK_IS
|
||||
import com.idormy.sms.forwarder.utils.CHECK_NOT_CONTAIN
|
||||
import com.idormy.sms.forwarder.utils.CHECK_REGEX
|
||||
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_1
|
||||
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_2
|
||||
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_ALL
|
||||
import com.idormy.sms.forwarder.utils.CHECK_START_WITH
|
||||
import com.idormy.sms.forwarder.utils.CommonUtils
|
||||
import com.idormy.sms.forwarder.utils.DataProvider
|
||||
import com.idormy.sms.forwarder.utils.EVENT_LOAD_APP_LIST
|
||||
import com.idormy.sms.forwarder.utils.EVENT_TOAST_ERROR
|
||||
import com.idormy.sms.forwarder.utils.FILED_CALL_TYPE
|
||||
import com.idormy.sms.forwarder.utils.FILED_INFORM_CONTENT
|
||||
import com.idormy.sms.forwarder.utils.FILED_MSG_CONTENT
|
||||
import com.idormy.sms.forwarder.utils.FILED_MULTI_MATCH
|
||||
import com.idormy.sms.forwarder.utils.FILED_PACKAGE_NAME
|
||||
import com.idormy.sms.forwarder.utils.FILED_PHONE_NUM
|
||||
import com.idormy.sms.forwarder.utils.FILED_TRANSPOND_ALL
|
||||
import com.idormy.sms.forwarder.utils.FILED_UID
|
||||
import com.idormy.sms.forwarder.utils.KEY_RULE_CLONE
|
||||
import com.idormy.sms.forwarder.utils.KEY_RULE_ID
|
||||
import com.idormy.sms.forwarder.utils.KEY_RULE_TYPE
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.PhoneUtils
|
||||
import com.idormy.sms.forwarder.utils.SENDER_LOGIC_ALL
|
||||
import com.idormy.sms.forwarder.utils.SENDER_LOGIC_UNTIL_FAIL
|
||||
import com.idormy.sms.forwarder.utils.SENDER_LOGIC_UNTIL_SUCCESS
|
||||
import com.idormy.sms.forwarder.utils.STATUS_OFF
|
||||
import com.idormy.sms.forwarder.utils.STATUS_ON
|
||||
import com.idormy.sms.forwarder.utils.SendUtils
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.idormy.sms.forwarder.workers.LoadAppListWorker
|
||||
import com.jeremyliao.liveeventbus.LiveEventBus
|
||||
import com.xuexiang.xaop.annotation.SingleClick
|
||||
@ -49,8 +87,8 @@ import io.reactivex.SingleObserver
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.*
|
||||
import java.util.*
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
|
||||
@Page(name = "转发规则·编辑器")
|
||||
@Suppress("PrivatePropertyName")
|
||||
@ -60,17 +98,6 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
|
||||
private var titleBar: TitleBar? = null
|
||||
private val viewModel by viewModels<RuleViewModel> { BaseViewModelFactory(context) }
|
||||
|
||||
//通话类型:1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
|
||||
private val CALL_TYPE_MAP = mapOf(
|
||||
//"0" to getString(R.string.unknown_call),
|
||||
"1" to getString(R.string.incoming_call_ended),
|
||||
"2" to getString(R.string.outgoing_call_ended),
|
||||
"3" to getString(R.string.missed_call),
|
||||
"4" to getString(R.string.incoming_call_received),
|
||||
"5" to getString(R.string.incoming_call_answered),
|
||||
"6" to getString(R.string.outgoing_call_started),
|
||||
)
|
||||
|
||||
private var callType = 1
|
||||
private var callTypeIndex = 0
|
||||
|
||||
@ -139,9 +166,6 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
|
||||
binding!!.rbCallType.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
|
||||
//初始化APP下拉列表
|
||||
initAppSpinner()
|
||||
//监听已安装App信息列表加载完成事件
|
||||
@ -156,11 +180,6 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
|
||||
binding!!.rbInformContent.visibility = View.GONE
|
||||
//binding!!.rbMultiMatch.visibility = View.GONE
|
||||
binding!!.tvMuRuleTips.setText(R.string.mu_rule_call_tips)
|
||||
binding!!.btInsertContent.visibility = View.GONE
|
||||
binding!!.btInsertSenderApp.visibility = View.GONE
|
||||
binding!!.btInsertUid.visibility = View.GONE
|
||||
binding!!.btInsertTitleApp.visibility = View.GONE
|
||||
binding!!.btInsertContentApp.visibility = View.GONE
|
||||
|
||||
//通话类型:1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
|
||||
binding!!.spCallType.setItems(CALL_TYPE_MAP.values.toList())
|
||||
@ -183,13 +202,12 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
|
||||
binding!!.rbPackageName.visibility = View.GONE
|
||||
binding!!.rbUid.visibility = View.GONE
|
||||
binding!!.rbInformContent.visibility = View.GONE
|
||||
binding!!.btInsertSenderApp.visibility = View.GONE
|
||||
binding!!.btInsertUid.visibility = View.GONE
|
||||
binding!!.btInsertTitleApp.visibility = View.GONE
|
||||
binding!!.btInsertContentApp.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
//创建标签按钮
|
||||
CommonUtils.createTagButtons(requireContext(), binding!!.glSmsTemplate, binding!!.etSmsTemplate, ruleType)
|
||||
|
||||
if (ruleId <= 0) { //新增
|
||||
titleBar?.setSubTitle(getString(R.string.add_rule))
|
||||
binding!!.btnDel.setText(R.string.discard)
|
||||
@ -203,15 +221,6 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
|
||||
|
||||
override fun initListeners() {
|
||||
binding!!.btnSilentPeriod.setOnClickListener(this)
|
||||
binding!!.btInsertSender.setOnClickListener(this)
|
||||
binding!!.btInsertContent.setOnClickListener(this)
|
||||
binding!!.btInsertSenderApp.setOnClickListener(this)
|
||||
binding!!.btInsertUid.setOnClickListener(this)
|
||||
binding!!.btInsertTitleApp.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)
|
||||
@ -307,7 +316,6 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
val etSmsTemplate: EditText = binding!!.etSmsTemplate
|
||||
when (v.id) {
|
||||
R.id.btn_silent_period -> {
|
||||
OptionsPickerBuilder(context, OnOptionsSelectListener { _: View?, options1: Int, options2: Int, _: Int ->
|
||||
@ -323,51 +331,6 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
|
||||
}
|
||||
}
|
||||
|
||||
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_uid -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_uid))
|
||||
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)
|
||||
@ -613,6 +576,21 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
|
||||
silentPeriodEnd = rule.silentPeriodEnd
|
||||
//初始化发送通道下拉框
|
||||
initSenderSpinner()
|
||||
|
||||
//绑定免打扰日期
|
||||
val silentPeriodDays = rule.silentDayOfWeek.split(",").filter { it.isNotEmpty() }.map { it.toInt() }
|
||||
if (silentPeriodDays.isNotEmpty()) {
|
||||
val map = mapOf(
|
||||
Calendar.SUNDAY to binding!!.sun,
|
||||
Calendar.MONDAY to binding!!.mon,
|
||||
Calendar.TUESDAY to binding!!.tue,
|
||||
Calendar.WEDNESDAY to binding!!.wed,
|
||||
Calendar.THURSDAY to binding!!.thu,
|
||||
Calendar.FRIDAY to binding!!.fri,
|
||||
Calendar.SATURDAY to binding!!.sat,
|
||||
)
|
||||
silentPeriodDays.forEach { map[it]?.isChecked = true }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -666,6 +644,10 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
|
||||
}
|
||||
|
||||
val smsTemplate = binding!!.etSmsTemplate.text.toString().trim()
|
||||
val checkResult = CommonUtils.checkTemplateTag(smsTemplate)
|
||||
if (checkResult.isNotEmpty()) {
|
||||
throw Exception(checkResult)
|
||||
}
|
||||
val regexReplace = binding!!.etRegexReplace.text.toString().trim()
|
||||
val lineNum = checkRegexReplace(regexReplace)
|
||||
if (lineNum > 0) {
|
||||
@ -685,6 +667,19 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
|
||||
}
|
||||
val status = if (binding!!.sbStatus.isChecked) STATUS_ON else STATUS_OFF
|
||||
|
||||
val map = mapOf(
|
||||
Calendar.SUNDAY to binding!!.sun,
|
||||
Calendar.MONDAY to binding!!.mon,
|
||||
Calendar.TUESDAY to binding!!.tue,
|
||||
Calendar.WEDNESDAY to binding!!.wed,
|
||||
Calendar.THURSDAY to binding!!.thu,
|
||||
Calendar.FRIDAY to binding!!.fri,
|
||||
Calendar.SATURDAY to binding!!.sat,
|
||||
)
|
||||
|
||||
val silentDayOfWeek = map.filter { it.value.isChecked }
|
||||
.toList().map { it.first }.joinToString(",")
|
||||
|
||||
return Rule(
|
||||
ruleId,
|
||||
ruleType,
|
||||
@ -700,7 +695,8 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
|
||||
senderListSelected,
|
||||
senderLogic,
|
||||
silentPeriodStart,
|
||||
silentPeriodEnd
|
||||
silentPeriodEnd,
|
||||
silentDayOfWeek,
|
||||
)
|
||||
}
|
||||
|
||||
@ -730,6 +726,14 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
|
||||
for (line in lineArray!!) {
|
||||
val position = line.indexOf("===")
|
||||
if (position < 1) return lineNum
|
||||
|
||||
// 校验正则表达式部分是否合法
|
||||
try {
|
||||
line.substring(0, position).toRegex()
|
||||
} catch (e: Exception) {
|
||||
return lineNum
|
||||
}
|
||||
|
||||
lineNum++
|
||||
}
|
||||
return 0
|
||||
@ -814,7 +818,7 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
|
||||
val contactName = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number)
|
||||
msg.append(getString(R.string.contact)).append(contactName).append("\n")
|
||||
msg.append(getString(R.string.mandatory_type))
|
||||
msg.append(CALL_TYPE_MAP[callType.toString()] ?: getString(R.string.unknown_call))
|
||||
msg.append(CALL_TYPE_MAP[callTypeTest.toString()] ?: getString(R.string.unknown_call))
|
||||
} else {
|
||||
msg.append(etContent.text.toString())
|
||||
}
|
||||
@ -839,4 +843,4 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,15 @@ import com.idormy.sms.forwarder.core.BaseFragment
|
||||
import com.idormy.sms.forwarder.databinding.FragmentServerBinding
|
||||
import com.idormy.sms.forwarder.service.HttpServerService
|
||||
import com.idormy.sms.forwarder.service.LocationService
|
||||
import com.idormy.sms.forwarder.utils.*
|
||||
import com.idormy.sms.forwarder.utils.ACTION_RESTART
|
||||
import com.idormy.sms.forwarder.utils.Base64
|
||||
import com.idormy.sms.forwarder.utils.HTTP_SERVER_PORT
|
||||
import com.idormy.sms.forwarder.utils.HttpServerUtils
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.RandomUtils
|
||||
import com.idormy.sms.forwarder.utils.SM4Crypt
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.xuexiang.xaop.annotation.SingleClick
|
||||
import com.xuexiang.xpage.annotation.Page
|
||||
import com.xuexiang.xui.widget.actionbar.TitleBar
|
||||
@ -256,7 +264,7 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
|
||||
}
|
||||
//重启前台服务,启动/停止定位服务
|
||||
val serviceIntent = Intent(requireContext(), LocationService::class.java)
|
||||
serviceIntent.action = "RESTART"
|
||||
serviceIntent.action = ACTION_RESTART
|
||||
requireContext().startService(serviceIntent)
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,6 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.Criteria
|
||||
import android.location.LocationManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
@ -18,7 +17,12 @@ import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import android.widget.AdapterView
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RadioGroup
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.Observer
|
||||
@ -37,16 +41,34 @@ import com.idormy.sms.forwarder.adapter.spinner.AppListSpinnerAdapter
|
||||
import com.idormy.sms.forwarder.core.BaseFragment
|
||||
import com.idormy.sms.forwarder.databinding.FragmentSettingsBinding
|
||||
import com.idormy.sms.forwarder.entity.SimInfo
|
||||
import com.idormy.sms.forwarder.fragment.client.CloneFragment
|
||||
import com.idormy.sms.forwarder.receiver.BootCompletedReceiver
|
||||
import com.idormy.sms.forwarder.service.BluetoothScanService
|
||||
import com.idormy.sms.forwarder.service.ForegroundService
|
||||
import com.idormy.sms.forwarder.service.LocationService
|
||||
import com.idormy.sms.forwarder.utils.*
|
||||
import com.idormy.sms.forwarder.utils.ACTION_RESTART
|
||||
import com.idormy.sms.forwarder.utils.ACTION_START
|
||||
import com.idormy.sms.forwarder.utils.ACTION_STOP
|
||||
import com.idormy.sms.forwarder.utils.ACTION_UPDATE_NOTIFICATION
|
||||
import com.idormy.sms.forwarder.utils.AppUtils.getAppPackageName
|
||||
import com.idormy.sms.forwarder.utils.BluetoothUtils
|
||||
import com.idormy.sms.forwarder.utils.CommonUtils
|
||||
import com.idormy.sms.forwarder.utils.DataProvider
|
||||
import com.idormy.sms.forwarder.utils.EVENT_LOAD_APP_LIST
|
||||
import com.idormy.sms.forwarder.utils.EXTRA_UPDATE_NOTIFICATION
|
||||
import com.idormy.sms.forwarder.utils.KEY_DEFAULT_SELECTION
|
||||
import com.idormy.sms.forwarder.utils.KeepAliveUtils
|
||||
import com.idormy.sms.forwarder.utils.LocationUtils
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.PhoneUtils
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.idormy.sms.forwarder.widget.GuideTipsDialog
|
||||
import com.idormy.sms.forwarder.workers.LoadAppListWorker
|
||||
import com.jeremyliao.liveeventbus.LiveEventBus
|
||||
import com.xuexiang.xaop.annotation.SingleClick
|
||||
import com.xuexiang.xpage.annotation.Page
|
||||
import com.xuexiang.xpage.core.PageOption
|
||||
import com.xuexiang.xui.widget.actionbar.TitleBar
|
||||
import com.xuexiang.xui.widget.button.SmoothCheckBox
|
||||
import com.xuexiang.xui.widget.button.switchbutton.SwitchButton
|
||||
@ -58,8 +80,7 @@ import com.xuexiang.xui.widget.picker.widget.listener.OnOptionsSelectListener
|
||||
import com.xuexiang.xutil.XUtil
|
||||
import com.xuexiang.xutil.XUtil.getPackageManager
|
||||
import com.xuexiang.xutil.file.FileUtils
|
||||
import kotlinx.coroutines.*
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
@Suppress("SpellCheckingInspection", "PrivatePropertyName")
|
||||
@Page(name = "通用设置")
|
||||
@ -68,6 +89,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
private val TAG: String = SettingsFragment::class.java.simpleName
|
||||
private var titleBar: TitleBar? = null
|
||||
private val mTimeOption = DataProvider.timePeriodOption
|
||||
private var initViewsFinished = false
|
||||
|
||||
//已安装App信息列表
|
||||
private val appListSpinnerList = ArrayList<AppListAdapterItem>()
|
||||
@ -95,6 +117,15 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
GuideTipsDialog.showTipsForce(requireContext())
|
||||
}
|
||||
})
|
||||
titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_restore) {
|
||||
@SingleClick
|
||||
override fun performAction(view: View) {
|
||||
PageOption.to(CloneFragment::class.java)
|
||||
.putInt(KEY_DEFAULT_SELECTION, 1) //默认离线模式
|
||||
.setNewActivity(true)
|
||||
.open(this@SettingsFragment)
|
||||
}
|
||||
})
|
||||
return titleBar
|
||||
}
|
||||
|
||||
@ -112,7 +143,9 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
//转发应用通知
|
||||
switchEnableAppNotify(binding!!.sbEnableAppNotify, binding!!.scbCancelAppNotify, binding!!.scbNotUserPresent)
|
||||
|
||||
//启用GPS定位功能
|
||||
//发现蓝牙设备服务
|
||||
switchEnableBluetooth(binding!!.sbEnableBluetooth, binding!!.layoutBluetoothSetting, binding!!.xsbScanInterval, binding!!.scbIgnoreAnonymous)
|
||||
//GPS定位功能
|
||||
switchEnableLocation(binding!!.sbEnableLocation, binding!!.layoutLocationSetting, binding!!.rgAccuracy, binding!!.rgPowerRequirement, binding!!.xsbMinInterval, binding!!.xsbMinDistance)
|
||||
//短信指令
|
||||
switchEnableSmsCommand(binding!!.sbEnableSmsCommand, binding!!.etSafePhone)
|
||||
@ -127,6 +160,10 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
}
|
||||
//免打扰(禁用转发)时间段
|
||||
binding!!.tvSilentPeriod.text = mTimeOption[SettingUtils.silentPeriodStart] + " ~ " + mTimeOption[SettingUtils.silentPeriodEnd]
|
||||
binding!!.scbSilentPeriodLogs.isChecked = SettingUtils.enableSilentPeriodLogs
|
||||
binding!!.scbSilentPeriodLogs.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
|
||||
SettingUtils.enableSilentPeriodLogs = isChecked
|
||||
}
|
||||
|
||||
//开机启动
|
||||
checkWithReboot(binding!!.sbWithReboot, binding!!.tvAutoStartup)
|
||||
@ -143,12 +180,18 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
editAddExtraDeviceMark(binding!!.etExtraDeviceMark)
|
||||
//SIM1主键
|
||||
editAddSubidSim1(binding!!.etSubidSim1)
|
||||
//SIM2主键
|
||||
editAddSubidSim2(binding!!.etSubidSim2)
|
||||
//SIM1备注
|
||||
editAddExtraSim1(binding!!.etExtraSim1)
|
||||
//SIM2备注
|
||||
editAddExtraSim2(binding!!.etExtraSim2)
|
||||
|
||||
// sim 槽只有一个的时候不显示 SIM2 设置
|
||||
if (PhoneUtils.getSimSlotCount() != 1) {
|
||||
//SIM2主键
|
||||
editAddSubidSim2(binding!!.etSubidSim2)
|
||||
//SIM2备注
|
||||
editAddExtraSim2(binding!!.etExtraSim2)
|
||||
} else {
|
||||
binding!!.layoutSim2.visibility = View.GONE
|
||||
}
|
||||
//通知内容
|
||||
editNotifyContent(binding!!.etNotifyContent)
|
||||
//启用自定义模版
|
||||
@ -163,6 +206,8 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
switchDebugMode(binding!!.sbDebugMode)
|
||||
//多语言设置
|
||||
switchLanguage(binding!!.rgMainLanguages)
|
||||
|
||||
initViewsFinished = true
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@ -176,11 +221,6 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
binding!!.btnExtraDeviceMark.setOnClickListener(this)
|
||||
binding!!.btnExtraSim1.setOnClickListener(this)
|
||||
binding!!.btnExtraSim2.setOnClickListener(this)
|
||||
binding!!.btInsertSender.setOnClickListener(this)
|
||||
binding!!.btInsertContent.setOnClickListener(this)
|
||||
binding!!.btInsertExtra.setOnClickListener(this)
|
||||
binding!!.btInsertTime.setOnClickListener(this)
|
||||
binding!!.btInsertDeviceName.setOnClickListener(this)
|
||||
binding!!.btnExportLog.setOnClickListener(this)
|
||||
|
||||
//监听已安装App信息列表加载完成事件
|
||||
@ -190,7 +230,6 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
@SuppressLint("SetTextI18n")
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
val etSmsTemplate: EditText = binding!!.etSmsTemplate
|
||||
when (v.id) {
|
||||
R.id.btn_silent_period -> {
|
||||
OptionsPickerBuilder(context, OnOptionsSelectListener { _: View?, options1: Int, options2: Int, _: Int ->
|
||||
@ -259,42 +298,11 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
return
|
||||
}
|
||||
|
||||
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_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_export_log -> {
|
||||
// 申请储存权限
|
||||
XXPermissions.with(this)
|
||||
//.permission(*Permission.Group.STORAGE)
|
||||
.permission(Permission.MANAGE_EXTERNAL_STORAGE).request(object : OnPermissionCallback {
|
||||
// 申请储存权限
|
||||
.permission(Permission.MANAGE_EXTERNAL_STORAGE)
|
||||
.request(object : OnPermissionCallback {
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
try {
|
||||
@ -331,11 +339,9 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
//转发短信
|
||||
@SuppressLint("UseSwitchCompatOrMaterialCode")
|
||||
private fun switchEnableSms(sbEnableSms: SwitchButton) {
|
||||
sbEnableSms.isChecked = SettingUtils.enableSms
|
||||
sbEnableSms.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
SettingUtils.enableSms = isChecked
|
||||
if (isChecked) {
|
||||
//检查权限是否获取
|
||||
XXPermissions.with(this)
|
||||
// 接收 WAP 推送消息
|
||||
.permission(Permission.RECEIVE_WAP_PUSH)
|
||||
@ -346,22 +352,23 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
// 发送短信
|
||||
//.permission(Permission.SEND_SMS)
|
||||
// 读取短信
|
||||
.permission(Permission.READ_SMS).request(object : OnPermissionCallback {
|
||||
.permission(Permission.READ_SMS)
|
||||
.request(object : OnPermissionCallback {
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
if (all) {
|
||||
XToastUtils.info(R.string.toast_granted_all)
|
||||
} else {
|
||||
XToastUtils.info(R.string.toast_granted_part)
|
||||
Log.d(TAG, "onGranted: permissions=$permissions, all=$all")
|
||||
if (!all) {
|
||||
XToastUtils.warning(getString(R.string.forward_sms) + ": " + getString(R.string.toast_granted_part))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: List<String>, never: Boolean) {
|
||||
Log.e(TAG, "onDenied: permissions=$permissions, never=$never")
|
||||
if (never) {
|
||||
XToastUtils.info(R.string.toast_denied_never)
|
||||
XToastUtils.error(getString(R.string.forward_sms) + ": " + getString(R.string.toast_denied_never))
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(requireContext(), permissions)
|
||||
} else {
|
||||
XToastUtils.info(R.string.toast_denied)
|
||||
XToastUtils.error(getString(R.string.forward_sms) + ": " + getString(R.string.toast_denied))
|
||||
}
|
||||
SettingUtils.enableSms = false
|
||||
sbEnableSms.isChecked = false
|
||||
@ -369,12 +376,12 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
})
|
||||
}
|
||||
}
|
||||
sbEnableSms.isChecked = SettingUtils.enableSms
|
||||
}
|
||||
|
||||
//转发通话
|
||||
@SuppressLint("UseSwitchCompatOrMaterialCode")
|
||||
private fun switchEnablePhone(sbEnablePhone: SwitchButton, scbCallType1: SmoothCheckBox, scbCallType2: SmoothCheckBox, scbCallType3: SmoothCheckBox, scbCallType4: SmoothCheckBox, scbCallType5: SmoothCheckBox, scbCallType6: SmoothCheckBox) {
|
||||
sbEnablePhone.isChecked = SettingUtils.enablePhone
|
||||
scbCallType1.isChecked = SettingUtils.enableCallType1
|
||||
scbCallType2.isChecked = SettingUtils.enableCallType2
|
||||
scbCallType3.isChecked = SettingUtils.enableCallType3
|
||||
@ -390,7 +397,6 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
}
|
||||
SettingUtils.enablePhone = isChecked
|
||||
if (isChecked) {
|
||||
//检查权限是否获取
|
||||
XXPermissions.with(this)
|
||||
// 读取电话状态
|
||||
.permission(Permission.READ_PHONE_STATE)
|
||||
@ -399,22 +405,23 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
// 读取通话记录
|
||||
.permission(Permission.READ_CALL_LOG)
|
||||
// 读取联系人
|
||||
.permission(Permission.READ_CONTACTS).request(object : OnPermissionCallback {
|
||||
.permission(Permission.READ_CONTACTS)
|
||||
.request(object : OnPermissionCallback {
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
if (all) {
|
||||
XToastUtils.info(R.string.toast_granted_all)
|
||||
} else {
|
||||
XToastUtils.info(R.string.toast_granted_part)
|
||||
Log.d(TAG, "onGranted: permissions=$permissions, all=$all")
|
||||
if (!all) {
|
||||
XToastUtils.warning(getString(R.string.forward_calls) + ": " + getString(R.string.toast_granted_part))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: List<String>, never: Boolean) {
|
||||
Log.e(TAG, "onDenied: permissions=$permissions, never=$never")
|
||||
if (never) {
|
||||
XToastUtils.info(R.string.toast_denied_never)
|
||||
XToastUtils.error(getString(R.string.forward_calls) + ": " + getString(R.string.toast_denied_never))
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(requireContext(), permissions)
|
||||
} else {
|
||||
XToastUtils.info(R.string.toast_denied)
|
||||
XToastUtils.error(getString(R.string.forward_calls) + ": " + getString(R.string.toast_denied))
|
||||
}
|
||||
SettingUtils.enablePhone = false
|
||||
sbEnablePhone.isChecked = false
|
||||
@ -422,6 +429,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
})
|
||||
}
|
||||
}
|
||||
sbEnablePhone.isChecked = SettingUtils.enablePhone
|
||||
scbCallType1.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
|
||||
SettingUtils.enableCallType1 = isChecked
|
||||
if (!isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3 && !SettingUtils.enableCallType4 && !SettingUtils.enableCallType5 && !SettingUtils.enableCallType6) {
|
||||
@ -475,34 +483,31 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
//转发应用通知
|
||||
@SuppressLint("UseSwitchCompatOrMaterialCode")
|
||||
private fun switchEnableAppNotify(sbEnableAppNotify: SwitchButton, scbCancelAppNotify: SmoothCheckBox, scbNotUserPresent: SmoothCheckBox) {
|
||||
val isEnable: Boolean = SettingUtils.enableAppNotify
|
||||
sbEnableAppNotify.isChecked = isEnable
|
||||
|
||||
val layoutOptionalAction: LinearLayout = binding!!.layoutOptionalAction
|
||||
layoutOptionalAction.visibility = if (isEnable) View.VISIBLE else View.GONE
|
||||
//val layoutAppList: LinearLayout = binding!!.layoutAppList
|
||||
//layoutAppList.visibility = if (isEnable) View.VISIBLE else View.GONE
|
||||
|
||||
sbEnableAppNotify.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
layoutOptionalAction.visibility = if (isChecked) View.VISIBLE else View.GONE
|
||||
//layoutAppList.visibility = if (isChecked) View.VISIBLE else View.GONE
|
||||
binding!!.layoutOptionalAction.visibility = if (isChecked) View.VISIBLE else View.GONE
|
||||
SettingUtils.enableAppNotify = isChecked
|
||||
if (isChecked) {
|
||||
//检查权限是否获取
|
||||
XXPermissions.with(this).permission(Permission.BIND_NOTIFICATION_LISTENER_SERVICE).request(OnPermissionCallback { _, allGranted ->
|
||||
if (!allGranted) {
|
||||
SettingUtils.enableAppNotify = false
|
||||
sbEnableAppNotify.isChecked = false
|
||||
XToastUtils.error(R.string.tips_notification_listener)
|
||||
return@OnPermissionCallback
|
||||
}
|
||||
XXPermissions.with(this)
|
||||
.permission(Permission.BIND_NOTIFICATION_LISTENER_SERVICE)
|
||||
.request(OnPermissionCallback { permissions, allGranted ->
|
||||
if (!allGranted) {
|
||||
Log.e(TAG, "onGranted: permissions=$permissions, allGranted=false")
|
||||
SettingUtils.enableAppNotify = false
|
||||
sbEnableAppNotify.isChecked = false
|
||||
XToastUtils.error(R.string.tips_notification_listener)
|
||||
return@OnPermissionCallback
|
||||
}
|
||||
|
||||
SettingUtils.enableAppNotify = true
|
||||
sbEnableAppNotify.isChecked = true
|
||||
CommonUtils.toggleNotificationListenerService(requireContext())
|
||||
})
|
||||
SettingUtils.enableAppNotify = true
|
||||
sbEnableAppNotify.isChecked = true
|
||||
CommonUtils.toggleNotificationListenerService(requireContext())
|
||||
})
|
||||
}
|
||||
}
|
||||
val isEnable = SettingUtils.enableAppNotify
|
||||
sbEnableAppNotify.isChecked = isEnable
|
||||
binding!!.layoutOptionalAction.visibility = if (isEnable) View.VISIBLE else View.GONE
|
||||
|
||||
scbCancelAppNotify.isChecked = SettingUtils.enableCancelAppNotify
|
||||
scbCancelAppNotify.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
|
||||
SettingUtils.enableCancelAppNotify = isChecked
|
||||
@ -513,37 +518,124 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
}
|
||||
}
|
||||
|
||||
//启用定位功能
|
||||
//发现蓝牙设备服务
|
||||
private fun switchEnableBluetooth(@SuppressLint("UseSwitchCompatOrMaterialCode") sbEnableBluetooth: SwitchButton, layoutBluetoothSetting: LinearLayout, xsbScanInterval: XSeekBar, scbIgnoreAnonymous: SmoothCheckBox) {
|
||||
sbEnableBluetooth.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
SettingUtils.enableBluetooth = isChecked
|
||||
layoutBluetoothSetting.visibility = if (isChecked) View.VISIBLE else View.GONE
|
||||
if (isChecked) {
|
||||
XXPermissions.with(this)
|
||||
.permission(Permission.BLUETOOTH_SCAN)
|
||||
.permission(Permission.BLUETOOTH_CONNECT)
|
||||
.permission(Permission.BLUETOOTH_ADVERTISE)
|
||||
.permission(Permission.ACCESS_FINE_LOCATION)
|
||||
.request(object : OnPermissionCallback {
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
Log.d(TAG, "onGranted: permissions=$permissions, all=$all")
|
||||
if (!all) {
|
||||
XToastUtils.warning(getString(R.string.enable_bluetooth) + ": " + getString(R.string.toast_granted_part))
|
||||
}
|
||||
restartBluetoothService(ACTION_START)
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: List<String>, never: Boolean) {
|
||||
Log.e(TAG, "onDenied: permissions=$permissions, never=$never")
|
||||
if (never) {
|
||||
XToastUtils.error(getString(R.string.enable_bluetooth) + ": " + getString(R.string.toast_denied_never))
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(requireContext(), permissions)
|
||||
} else {
|
||||
XToastUtils.error(getString(R.string.enable_bluetooth) + ": " + getString(R.string.toast_denied))
|
||||
}
|
||||
SettingUtils.enableBluetooth = false
|
||||
sbEnableBluetooth.isChecked = false
|
||||
restartBluetoothService(ACTION_STOP)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
restartBluetoothService(ACTION_STOP)
|
||||
}
|
||||
}
|
||||
val isEnable = SettingUtils.enableBluetooth
|
||||
sbEnableBluetooth.isChecked = isEnable
|
||||
layoutBluetoothSetting.visibility = if (isEnable) View.VISIBLE else View.GONE
|
||||
|
||||
//扫描蓝牙设备间隔
|
||||
xsbScanInterval.setDefaultValue((SettingUtils.bluetoothScanInterval / 1000).toInt())
|
||||
xsbScanInterval.setOnSeekBarListener { _: XSeekBar?, newValue: Int ->
|
||||
if (newValue * 1000L != SettingUtils.bluetoothScanInterval) {
|
||||
SettingUtils.bluetoothScanInterval = newValue * 1000L
|
||||
restartBluetoothService()
|
||||
}
|
||||
}
|
||||
|
||||
//是否忽略匿名设备
|
||||
scbIgnoreAnonymous.isChecked = SettingUtils.bluetoothIgnoreAnonymous
|
||||
scbIgnoreAnonymous.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
|
||||
SettingUtils.bluetoothIgnoreAnonymous = isChecked
|
||||
restartBluetoothService()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//重启蓝牙扫描服务
|
||||
private fun restartBluetoothService(action: String = ACTION_RESTART) {
|
||||
if (!initViewsFinished) return
|
||||
Log.d(TAG, "restartBluetoothService, action: $action")
|
||||
val serviceIntent = Intent(requireContext(), BluetoothScanService::class.java)
|
||||
//如果蓝牙功能已启用,但是系统蓝牙功能不可用,则关闭蓝牙功能
|
||||
if (SettingUtils.enableBluetooth && (!BluetoothUtils.isBluetoothEnabled() || !BluetoothUtils.hasBluetoothCapability(App.context))) {
|
||||
XToastUtils.error(getString(R.string.toast_bluetooth_not_enabled))
|
||||
SettingUtils.enableBluetooth = false
|
||||
binding!!.sbEnableBluetooth.isChecked = false
|
||||
binding!!.layoutBluetoothSetting.visibility = View.GONE
|
||||
serviceIntent.action = ACTION_STOP
|
||||
} else {
|
||||
serviceIntent.action = action
|
||||
}
|
||||
requireContext().startService(serviceIntent)
|
||||
}
|
||||
|
||||
//GPS定位服务
|
||||
private fun switchEnableLocation(@SuppressLint("UseSwitchCompatOrMaterialCode") sbEnableLocation: SwitchButton, layoutLocationSetting: LinearLayout, rgAccuracy: RadioGroup, rgPowerRequirement: RadioGroup, xsbMinInterval: XSeekBar, xsbMinDistance: XSeekBar) {
|
||||
//是否启用定位功能
|
||||
sbEnableLocation.isChecked = SettingUtils.enableLocation
|
||||
layoutLocationSetting.visibility = if (SettingUtils.enableLocation) View.VISIBLE else View.GONE
|
||||
sbEnableLocation.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
SettingUtils.enableLocation = isChecked
|
||||
if (isChecked) {
|
||||
XXPermissions.with(this).permission(Permission.ACCESS_COARSE_LOCATION).permission(Permission.ACCESS_FINE_LOCATION).permission(Permission.ACCESS_BACKGROUND_LOCATION).request(object : OnPermissionCallback {
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
restartLocationService("START")
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: List<String>, never: Boolean) {
|
||||
if (never) {
|
||||
XToastUtils.error(R.string.toast_denied_never)
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(requireContext(), permissions)
|
||||
} else {
|
||||
XToastUtils.error(R.string.toast_denied)
|
||||
}
|
||||
SettingUtils.enableLocation = false
|
||||
sbEnableLocation.isChecked = false
|
||||
restartLocationService("STOP")
|
||||
}
|
||||
})
|
||||
} else {
|
||||
restartLocationService("STOP")
|
||||
}
|
||||
layoutLocationSetting.visibility = if (isChecked) View.VISIBLE else View.GONE
|
||||
if (isChecked) {
|
||||
XXPermissions.with(this)
|
||||
.permission(Permission.ACCESS_COARSE_LOCATION)
|
||||
.permission(Permission.ACCESS_FINE_LOCATION)
|
||||
.permission(Permission.ACCESS_BACKGROUND_LOCATION)
|
||||
.request(object : OnPermissionCallback {
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
Log.d(TAG, "onGranted: permissions=$permissions, all=$all")
|
||||
if (!all) {
|
||||
XToastUtils.warning(getString(R.string.enable_location) + ": " + getString(R.string.toast_granted_part))
|
||||
}
|
||||
restartLocationService(ACTION_START)
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: List<String>, never: Boolean) {
|
||||
Log.e(TAG, "onDenied: permissions=$permissions, never=$never")
|
||||
if (never) {
|
||||
XToastUtils.error(getString(R.string.enable_location) + ": " + getString(R.string.toast_denied_never))
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(requireContext(), permissions)
|
||||
} else {
|
||||
XToastUtils.error(getString(R.string.enable_location) + ": " + getString(R.string.toast_denied))
|
||||
}
|
||||
SettingUtils.enableLocation = false
|
||||
sbEnableLocation.isChecked = false
|
||||
restartLocationService(ACTION_STOP)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
restartLocationService(ACTION_STOP)
|
||||
}
|
||||
}
|
||||
val isEnable = SettingUtils.enableLocation
|
||||
sbEnableLocation.isChecked = isEnable
|
||||
layoutLocationSetting.visibility = if (isEnable) View.VISIBLE else View.GONE
|
||||
|
||||
//设置位置精度:高精度(默认)
|
||||
rgAccuracy.check(
|
||||
@ -588,29 +680,34 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
//设置位置更新最小时间间隔(单位:毫秒); 默认间隔:10000毫秒,最小间隔:1000毫秒
|
||||
xsbMinInterval.setDefaultValue((SettingUtils.locationMinInterval / 1000).toInt())
|
||||
xsbMinInterval.setOnSeekBarListener { _: XSeekBar?, newValue: Int ->
|
||||
SettingUtils.locationMinInterval = newValue * 1000L
|
||||
restartLocationService()
|
||||
if (newValue * 1000L != SettingUtils.locationMinInterval) {
|
||||
SettingUtils.locationMinInterval = newValue * 1000L
|
||||
restartLocationService()
|
||||
}
|
||||
}
|
||||
|
||||
//设置位置更新最小距离(单位:米);默认距离:0米
|
||||
xsbMinDistance.setDefaultValue(SettingUtils.locationMinDistance)
|
||||
xsbMinDistance.setOnSeekBarListener { _: XSeekBar?, newValue: Int ->
|
||||
SettingUtils.locationMinDistance = newValue
|
||||
restartLocationService()
|
||||
if (newValue != SettingUtils.locationMinDistance) {
|
||||
SettingUtils.locationMinDistance = newValue
|
||||
restartLocationService()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//重启定位服务
|
||||
private fun restartLocationService(action: String = "RESTART") {
|
||||
private fun restartLocationService(action: String = ACTION_RESTART) {
|
||||
if (!initViewsFinished) return
|
||||
Log.d(TAG, "restartLocationService, action: $action")
|
||||
val serviceIntent = Intent(requireContext(), LocationService::class.java)
|
||||
val locationManager = App.context.getSystemService(Context.LOCATION_SERVICE) as LocationManager?
|
||||
val isGpsEnabled = locationManager?.isProviderEnabled(LocationManager.GPS_PROVIDER) == true
|
||||
if (!isGpsEnabled && SettingUtils.enableLocation) {
|
||||
XToastUtils.error(getString(R.string.toast_gps_not_enabled))
|
||||
//如果定位功能已启用,但是系统定位功能不可用,则关闭定位功能
|
||||
if (SettingUtils.enableLocation && (!LocationUtils.isLocationEnabled(App.context) || !LocationUtils.hasLocationCapability(App.context))) {
|
||||
XToastUtils.error(getString(R.string.toast_location_not_enabled))
|
||||
SettingUtils.enableLocation = false
|
||||
binding!!.sbEnableLocation.isChecked = false
|
||||
serviceIntent.action = "STOP"
|
||||
binding!!.layoutLocationSetting.visibility = View.GONE
|
||||
serviceIntent.action = ACTION_STOP
|
||||
} else {
|
||||
serviceIntent.action = action
|
||||
}
|
||||
@ -620,14 +717,10 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
//接受短信指令
|
||||
@SuppressLint("UseSwitchCompatOrMaterialCode")
|
||||
private fun switchEnableSmsCommand(sbEnableSmsCommand: SwitchButton, etSafePhone: EditText) {
|
||||
sbEnableSmsCommand.isChecked = SettingUtils.enableSmsCommand
|
||||
etSafePhone.visibility = if (SettingUtils.enableSmsCommand) View.VISIBLE else View.GONE
|
||||
|
||||
sbEnableSmsCommand.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
SettingUtils.enableSmsCommand = isChecked
|
||||
etSafePhone.visibility = if (isChecked) View.VISIBLE else View.GONE
|
||||
if (isChecked) {
|
||||
//检查权限是否获取
|
||||
XXPermissions.with(this)
|
||||
// 系统设置
|
||||
.permission(Permission.WRITE_SETTINGS)
|
||||
@ -636,22 +729,21 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
// 发送短信
|
||||
.permission(Permission.SEND_SMS)
|
||||
// 读取短信
|
||||
.permission(Permission.READ_SMS).request(object : OnPermissionCallback {
|
||||
.permission(Permission.READ_SMS)
|
||||
.request(object : OnPermissionCallback {
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
if (all) {
|
||||
XToastUtils.info(R.string.toast_granted_all)
|
||||
} else {
|
||||
XToastUtils.info(R.string.toast_granted_part)
|
||||
if (!all) {
|
||||
XToastUtils.warning(getString(R.string.sms_command) + ": " + getString(R.string.toast_denied_never))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: List<String>, never: Boolean) {
|
||||
if (never) {
|
||||
XToastUtils.info(R.string.toast_denied_never)
|
||||
XToastUtils.error(getString(R.string.sms_command) + ": " + getString(R.string.toast_denied_never))
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(requireContext(), permissions)
|
||||
} else {
|
||||
XToastUtils.info(R.string.toast_denied)
|
||||
XToastUtils.error(getString(R.string.sms_command) + ": " + getString(R.string.toast_denied))
|
||||
}
|
||||
SettingUtils.enableSmsCommand = false
|
||||
sbEnableSmsCommand.isChecked = false
|
||||
@ -659,6 +751,9 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
})
|
||||
}
|
||||
}
|
||||
val isEnable = SettingUtils.enableSmsCommand
|
||||
sbEnableSmsCommand.isChecked = isEnable
|
||||
etSafePhone.visibility = if (isEnable) View.VISIBLE else View.GONE
|
||||
|
||||
etSafePhone.setText(SettingUtils.smsCommandSafePhone)
|
||||
etSafePhone.addTextChangedListener(object : TextWatcher {
|
||||
@ -865,7 +960,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
|
||||
//设置SIM1主键
|
||||
private fun editAddSubidSim1(etSubidSim1: EditText) {
|
||||
etSubidSim1.setText(SettingUtils.subidSim1.toString())
|
||||
etSubidSim1.setText("${SettingUtils.subidSim1}")
|
||||
etSubidSim1.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
||||
@ -882,7 +977,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
|
||||
//设置SIM2主键
|
||||
private fun editAddSubidSim2(etSubidSim2: EditText) {
|
||||
etSubidSim2.setText(SettingUtils.subidSim2.toString())
|
||||
etSubidSim2.setText("${SettingUtils.subidSim2}")
|
||||
etSubidSim2.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
||||
@ -931,8 +1026,8 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
val notifyContent = etNotifyContent.text.toString().trim()
|
||||
SettingUtils.notifyContent = notifyContent
|
||||
val updateIntent = Intent(context, ForegroundService::class.java)
|
||||
updateIntent.action = "UPDATE_NOTIFICATION"
|
||||
updateIntent.putExtra("UPDATED_CONTENT", notifyContent)
|
||||
updateIntent.action = ACTION_UPDATE_NOTIFICATION
|
||||
updateIntent.putExtra(EXTRA_UPDATE_NOTIFICATION, notifyContent)
|
||||
context?.let { ContextCompat.startForegroundService(it, updateIntent) }
|
||||
}
|
||||
})
|
||||
@ -966,6 +1061,8 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
|
||||
//设置转发信息模版
|
||||
private fun editSmsTemplate(textSmsTemplate: EditText) {
|
||||
//创建标签按钮
|
||||
CommonUtils.createTagButtons(requireContext(), binding!!.glSmsTemplate, textSmsTemplate, "all")
|
||||
textSmsTemplate.setText(SettingUtils.smsTemplate)
|
||||
textSmsTemplate.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||
@ -1013,11 +1110,12 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
|
||||
//多语言设置
|
||||
private fun switchLanguage(rgMainLanguages: RadioGroup) {
|
||||
val context = App.context
|
||||
rgMainLanguages.check(
|
||||
if (MultiLanguages.isSystemLanguage(requireContext())) {
|
||||
if (MultiLanguages.isSystemLanguage(context)) {
|
||||
R.id.rb_main_language_auto
|
||||
} else {
|
||||
when (MultiLanguages.getAppLanguage(requireContext())) {
|
||||
when (MultiLanguages.getAppLanguage(context)) {
|
||||
LocaleContract.getSimplifiedChineseLocale() -> R.id.rb_main_language_cn
|
||||
LocaleContract.getTraditionalChineseLocale() -> R.id.rb_main_language_tw
|
||||
LocaleContract.getEnglishLocale() -> R.id.rb_main_language_en
|
||||
@ -1027,35 +1125,49 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
)
|
||||
|
||||
rgMainLanguages.setOnCheckedChangeListener { _, checkedId ->
|
||||
// 是否需要重启
|
||||
val oldLang = MultiLanguages.getAppLanguage(context)
|
||||
var newLang = MultiLanguages.getSystemLanguage(context)
|
||||
//SettingUtils.isFlowSystemLanguage = false
|
||||
when (checkedId) {
|
||||
R.id.rb_main_language_auto -> {
|
||||
// 只为了触发onAppLocaleChange
|
||||
MultiLanguages.setAppLanguage(context, newLang)
|
||||
// SettingUtils.isFlowSystemLanguage = true
|
||||
// 跟随系统
|
||||
MultiLanguages.clearAppLanguage(requireContext())
|
||||
MultiLanguages.clearAppLanguage(context)
|
||||
}
|
||||
|
||||
R.id.rb_main_language_cn -> {
|
||||
// 简体中文
|
||||
MultiLanguages.setAppLanguage(requireContext(), LocaleContract.getSimplifiedChineseLocale())
|
||||
newLang = LocaleContract.getSimplifiedChineseLocale()
|
||||
MultiLanguages.setAppLanguage(context, newLang)
|
||||
}
|
||||
|
||||
R.id.rb_main_language_tw -> {
|
||||
// 繁体中文
|
||||
MultiLanguages.setAppLanguage(requireContext(), LocaleContract.getTraditionalChineseLocale())
|
||||
newLang = LocaleContract.getTraditionalChineseLocale()
|
||||
MultiLanguages.setAppLanguage(context, newLang)
|
||||
}
|
||||
|
||||
R.id.rb_main_language_en -> {
|
||||
// 英语
|
||||
MultiLanguages.setAppLanguage(requireContext(), LocaleContract.getEnglishLocale())
|
||||
newLang = LocaleContract.getEnglishLocale()
|
||||
MultiLanguages.setAppLanguage(context, newLang)
|
||||
}
|
||||
}
|
||||
|
||||
// 重启应用
|
||||
XToastUtils.toast(R.string.multi_languages_toast)
|
||||
val intent = Intent(App.context, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
startActivity(intent)
|
||||
requireActivity().finish()
|
||||
Log.d(TAG, "oldLang: $oldLang, newLang: $newLang")
|
||||
if (oldLang.toString() != newLang.toString()) {
|
||||
//CommonUtils.switchLanguage(oldLang, newLang)
|
||||
XToastUtils.toast(R.string.multi_languages_toast)
|
||||
//切换语种后重启APP
|
||||
Thread.sleep(200)
|
||||
val intent = Intent(App.context, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
startActivity(intent)
|
||||
requireActivity().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1065,6 +1177,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
"huawei" -> getString(R.string.auto_start_huawei)
|
||||
"honor" -> getString(R.string.auto_start_honor)
|
||||
"xiaomi" -> getString(R.string.auto_start_xiaomi)
|
||||
"redmi" -> getString(R.string.auto_start_redmi)
|
||||
"oppo" -> getString(R.string.auto_start_oppo)
|
||||
"vivo" -> getString(R.string.auto_start_vivo)
|
||||
"meizu" -> getString(R.string.auto_start_meizu)
|
||||
@ -1280,4 +1393,4 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -82,118 +82,167 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
|
||||
PageInfo(
|
||||
getString(R.string.task_cron),
|
||||
"com.idormy.sms.forwarder.fragment.condition.CronFragment",
|
||||
"{\"\":\"\"}",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_custom_time,
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_to_address),
|
||||
"com.idormy.sms.forwarder.fragment.condition.ToAddressFragment",
|
||||
"{\"\":\"\"}",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_to_address,
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_leave_address),
|
||||
"com.idormy.sms.forwarder.fragment.condition.LeaveAddressFragment",
|
||||
"{\"\":\"\"}",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_leave_address,
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_network),
|
||||
"com.idormy.sms.forwarder.fragment.condition.NetworkFragment",
|
||||
"{\"\":\"\"}",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_network
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_sim),
|
||||
"com.idormy.sms.forwarder.fragment.condition.SimFragment",
|
||||
"{\"\":\"\"}",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_sim
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_battery),
|
||||
"com.idormy.sms.forwarder.fragment.condition.BatteryFragment",
|
||||
"{\"\":\"\"}",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_battery
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_charge),
|
||||
"com.idormy.sms.forwarder.fragment.condition.ChargeFragment",
|
||||
"{\"\":\"\"}",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_charge
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_lock_screen),
|
||||
"com.idormy.sms.forwarder.fragment.condition.LockScreenFragment",
|
||||
"{\"\":\"\"}",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_lock_screen
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_sms),
|
||||
"com.idormy.sms.forwarder.fragment.condition.MsgFragment",
|
||||
"sms",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_sms
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_call),
|
||||
"com.idormy.sms.forwarder.fragment.condition.MsgFragment",
|
||||
"call",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_incall
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_app),
|
||||
"com.idormy.sms.forwarder.fragment.condition.MsgFragment",
|
||||
"app",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_start_activity
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_bluetooth),
|
||||
"com.idormy.sms.forwarder.fragment.condition.BluetoothFragment",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_bluetooth
|
||||
),
|
||||
)
|
||||
|
||||
private var TASK_ACTION_FRAGMENT_LIST = listOf(
|
||||
PageInfo(
|
||||
getString(R.string.task_sendsms),
|
||||
"com.idormy.sms.forwarder.fragment.action.SendSmsFragment",
|
||||
"{\"\":\"\"}",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_sms
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_notification),
|
||||
"com.idormy.sms.forwarder.fragment.action.NotificationFragment",
|
||||
"{\"\":\"\"}",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_notification,
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_cleaner),
|
||||
"com.idormy.sms.forwarder.fragment.action.CleanerFragment",
|
||||
"{\"\":\"\"}",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_cleaner
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_settings),
|
||||
"com.idormy.sms.forwarder.fragment.action.SettingsFragment",
|
||||
"{\"\":\"\"}",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_settings
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_frpc),
|
||||
"com.idormy.sms.forwarder.fragment.action.FrpcFragment",
|
||||
"{\"\":\"\"}",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_frpc
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_http_server),
|
||||
"com.idormy.sms.forwarder.fragment.action.HttpServerFragment",
|
||||
"{\"\":\"\"}",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_http_server
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_rule),
|
||||
"com.idormy.sms.forwarder.fragment.action.RuleFragment",
|
||||
"{\"\":\"\"}",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_rule
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_sender),
|
||||
"com.idormy.sms.forwarder.fragment.action.SenderFragment",
|
||||
"{\"\":\"\"}",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_sender
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_alarm),
|
||||
"com.idormy.sms.forwarder.fragment.action.AlarmFragment",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_alarm
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_resend),
|
||||
"com.idormy.sms.forwarder.fragment.action.ResendFragment",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_resend
|
||||
),
|
||||
PageInfo(
|
||||
getString(R.string.task_task),
|
||||
"com.idormy.sms.forwarder.fragment.action.TaskActionFragment",
|
||||
"",
|
||||
CoreAnim.slide,
|
||||
R.drawable.auto_task_icon_task
|
||||
),
|
||||
)
|
||||
|
||||
override fun initArgs() {
|
||||
@ -417,13 +466,20 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
|
||||
private fun checkForm(): Task {
|
||||
val taskName = binding!!.etName.text.toString().trim()
|
||||
if (taskName.isEmpty()) {
|
||||
throw Exception("请输入任务名称")
|
||||
throw Exception(getString(R.string.invalid_task_name))
|
||||
}
|
||||
if (conditionsList.size <= 0) {
|
||||
throw Exception("请添加触发条件")
|
||||
throw Exception(getString(R.string.invalid_conditions))
|
||||
}
|
||||
if (actionsList.size <= 0) {
|
||||
throw Exception("请添加执行动作")
|
||||
throw Exception(getString(R.string.invalid_actions))
|
||||
}
|
||||
|
||||
//短信广播/通话广播/APP通知 类型条件只能放在第一个
|
||||
for (i in 1 until conditionsList.size) {
|
||||
if (conditionsList[i].type == TASK_CONDITION_SMS || conditionsList[i].type == TASK_CONDITION_CALL || conditionsList[i].type == TASK_CONDITION_APP) {
|
||||
throw Exception(getString(R.string.msg_condition_must_be_trigger))
|
||||
}
|
||||
}
|
||||
|
||||
val lastExecTime = Date()
|
||||
@ -438,7 +494,7 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
|
||||
//检查定时任务的时间设置
|
||||
val cronSetting = Gson().fromJson(firstCondition.setting, CronSetting::class.java)
|
||||
if (cronSetting.expression.isEmpty()) {
|
||||
throw Exception("请设置定时任务的时间")
|
||||
throw Exception(getString(R.string.invalid_cron))
|
||||
}
|
||||
val cronExpression = CronExpression(cronSetting.expression)
|
||||
nextExecTime = cronExpression.getNextValidTimeAfter(lastExecTime)
|
||||
@ -483,6 +539,11 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
|
||||
//判断点击的是条件还是动作
|
||||
if (widgetInfo.classPath.contains(".condition.")) {
|
||||
val typeCondition = pos + KEY_BACK_CODE_CONDITION
|
||||
//短信广播、通话广播、APP通知 类型条件必须作为触发提交
|
||||
if ((typeCondition == TASK_CONDITION_SMS || typeCondition == TASK_CONDITION_CALL || typeCondition == TASK_CONDITION_APP) && actionsList.isNotEmpty()) {
|
||||
XToastUtils.error(getString(R.string.msg_condition_must_be_trigger))
|
||||
return
|
||||
}
|
||||
//判断是否已经添加过该类型条件
|
||||
for (item in conditionsList) {
|
||||
//注意:TASK_CONDITION_XXX 枚举值 等于 TASK_CONDITION_FRAGMENT_LIST 索引加上 KEY_BACK_CODE_CONDITION,不可改变
|
||||
@ -502,7 +563,7 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
|
||||
.negativeText(R.string.lab_no).onPositive { _: MaterialDialog?, _: DialogAction? ->
|
||||
SettingUtils.enableLocation = true
|
||||
val serviceIntent = Intent(requireContext(), LocationService::class.java)
|
||||
serviceIntent.action = "START"
|
||||
serviceIntent.action = ACTION_START
|
||||
requireContext().startService(serviceIntent)
|
||||
}.show()
|
||||
return
|
||||
@ -513,6 +574,12 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
|
||||
XToastUtils.error(getString(R.string.only_one_location_condition))
|
||||
return
|
||||
}
|
||||
|
||||
//短信广播、通话广播、APP通知 类型条件互斥
|
||||
if ((typeCondition == TASK_CONDITION_SMS || typeCondition == TASK_CONDITION_CALL || typeCondition == TASK_CONDITION_APP) && (item.type == TASK_CONDITION_SMS || item.type == TASK_CONDITION_CALL || item.type == TASK_CONDITION_APP)) {
|
||||
XToastUtils.error(getString(R.string.only_one_msg_condition))
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val typeAction = pos + KEY_BACK_CODE_ACTION
|
||||
@ -525,8 +592,10 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST") PageOption.to(Class.forName(widgetInfo.classPath) as Class<XPageFragment>) //跳转的fragment
|
||||
.setRequestCode(0) //requestCode: 0 新增 、>0 编辑(itemListXxx 的索引加1)
|
||||
.putString(KEY_EVENT_PARAMS_CONDITION, widgetInfo.params)
|
||||
.open(this)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
@ -617,6 +686,7 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
|
||||
PageOption.to(Class.forName(widgetInfo.classPath) as Class<XPageFragment>) //跳转的fragment
|
||||
.setRequestCode(position + 1) //requestCode: 0 新增 、>0 编辑(conditionsList 的索引加1)
|
||||
.putString(KEY_EVENT_DATA_CONDITION, condition.setting)
|
||||
.putString(KEY_EVENT_PARAMS_CONDITION, widgetInfo.params)
|
||||
.open(this)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,390 @@
|
||||
package com.idormy.sms.forwarder.fragment.action
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Environment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.google.gson.Gson
|
||||
import com.hjq.permissions.OnPermissionCallback
|
||||
import com.hjq.permissions.Permission
|
||||
import com.hjq.permissions.XXPermissions
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.core.BaseFragment
|
||||
import com.idormy.sms.forwarder.databinding.FragmentTasksActionAlarmBinding
|
||||
import com.idormy.sms.forwarder.entity.MsgInfo
|
||||
import com.idormy.sms.forwarder.entity.TaskSetting
|
||||
import com.idormy.sms.forwarder.entity.action.AlarmSetting
|
||||
import com.idormy.sms.forwarder.utils.CommonUtils
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_ACTION
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_ACTION
|
||||
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_ACTION
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.TASK_ACTION_ALARM
|
||||
import com.idormy.sms.forwarder.utils.TaskWorker
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.idormy.sms.forwarder.workers.ActionWorker
|
||||
import com.xuexiang.xaop.annotation.SingleClick
|
||||
import com.xuexiang.xpage.annotation.Page
|
||||
import com.xuexiang.xrouter.annotation.AutoWired
|
||||
import com.xuexiang.xrouter.launcher.XRouter
|
||||
import com.xuexiang.xui.utils.CountDownButtonHelper
|
||||
import com.xuexiang.xui.widget.actionbar.TitleBar
|
||||
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
|
||||
import java.io.File
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
@Page(name = "Alarm")
|
||||
@Suppress("PrivatePropertyName", "DEPRECATION")
|
||||
class AlarmFragment : BaseFragment<FragmentTasksActionAlarmBinding?>(), View.OnClickListener {
|
||||
|
||||
private val TAG: String = AlarmFragment::class.java.simpleName
|
||||
private var titleBar: TitleBar? = null
|
||||
private var mCountDownHelper: CountDownButtonHelper? = null
|
||||
private var appContext: App? = null
|
||||
|
||||
@JvmField
|
||||
@AutoWired(name = KEY_EVENT_DATA_ACTION)
|
||||
var eventData: String? = null
|
||||
|
||||
override fun initArgs() {
|
||||
XRouter.getInstance().inject(this)
|
||||
}
|
||||
|
||||
override fun viewBindingInflate(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup,
|
||||
): FragmentTasksActionAlarmBinding {
|
||||
return FragmentTasksActionAlarmBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun initTitle(): TitleBar? {
|
||||
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_alarm)
|
||||
return titleBar
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化控件
|
||||
*/
|
||||
override fun initViews() {
|
||||
appContext = requireActivity().application as App
|
||||
//测试按钮增加倒计时,避免重复点击
|
||||
mCountDownHelper = CountDownButtonHelper(binding!!.btnTest, 2)
|
||||
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
|
||||
override fun onCountDown(time: Int) {
|
||||
binding!!.btnTest.text = String.format(getString(R.string.seconds_n), time)
|
||||
}
|
||||
|
||||
override fun onFinished() {
|
||||
binding!!.btnTest.text = getString(R.string.test)
|
||||
}
|
||||
})
|
||||
|
||||
binding!!.sbEnableMusic.setOnCheckedChangeListener { _, isChecked ->
|
||||
binding!!.layoutAlarmSettingsContent.visibility = if (isChecked) View.VISIBLE else View.GONE
|
||||
checkSetting(true)
|
||||
}
|
||||
binding!!.sbEnableVibrate.setOnCheckedChangeListener { _, isChecked ->
|
||||
binding!!.layoutVibrateSettingsContent.visibility = if (isChecked) View.VISIBLE else View.GONE
|
||||
checkSetting(true)
|
||||
}
|
||||
|
||||
var settingVo = AlarmSetting()
|
||||
Log.d(TAG, "initViews eventData:$eventData")
|
||||
if (eventData != null) {
|
||||
settingVo = Gson().fromJson(eventData, AlarmSetting::class.java)
|
||||
Log.d(TAG, "initViews settingVo:$settingVo")
|
||||
if (settingVo.action == "start") {
|
||||
binding!!.rgAlarmState.check(R.id.rb_start_alarm)
|
||||
binding!!.layoutAlarmSettings.visibility = View.VISIBLE
|
||||
binding!!.layoutVibrateSettings.visibility = View.VISIBLE
|
||||
binding!!.layoutFlashSettings.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding!!.rgAlarmState.check(R.id.rb_stop_alarm)
|
||||
binding!!.layoutAlarmSettings.visibility = View.GONE
|
||||
binding!!.layoutVibrateSettings.visibility = View.GONE
|
||||
binding!!.layoutFlashSettings.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
binding!!.xsbVolume.setDefaultValue(settingVo.volume)
|
||||
binding!!.xsbPlayTimes.setDefaultValue(if (settingVo.playTimes >= 0) settingVo.playTimes else 0)
|
||||
binding!!.etMusicPath.setText(settingVo.music)
|
||||
binding!!.xsbRepeatTimes.setDefaultValue(if (settingVo.repeatTimes >= 0) settingVo.repeatTimes else 0)
|
||||
binding!!.etVibrationEffect.setText(settingVo.vibrate)
|
||||
binding!!.xsbFlashTimes.setDefaultValue(if (settingVo.flashTimes >= 0) settingVo.flashTimes else 0)
|
||||
binding!!.etFlashEffect.setText(settingVo.flash)
|
||||
binding!!.sbEnableMusic.isChecked = settingVo.playTimes >= 0
|
||||
binding!!.sbEnableVibrate.isChecked = settingVo.repeatTimes >= 0
|
||||
binding!!.sbEnableFlash.isChecked = settingVo.flashTimes >= 0
|
||||
binding!!.layoutAlarmSettingsContent.visibility = if (settingVo.playTimes >= 0) View.VISIBLE else View.GONE
|
||||
binding!!.layoutVibrateSettingsContent.visibility = if (settingVo.repeatTimes >= 0) View.VISIBLE else View.GONE
|
||||
binding!!.layoutFlashSettingsContent.visibility = if (settingVo.flashTimes >= 0) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun initListeners() {
|
||||
binding!!.btnTest.setOnClickListener(this)
|
||||
binding!!.btnDel.setOnClickListener(this)
|
||||
binding!!.btnSave.setOnClickListener(this)
|
||||
binding!!.btnFilePicker.setOnClickListener(this)
|
||||
binding!!.xsbVolume.setOnSeekBarListener { _, _ ->
|
||||
checkSetting(true)
|
||||
}
|
||||
binding!!.xsbPlayTimes.setOnSeekBarListener { _, _ ->
|
||||
checkSetting(true)
|
||||
}
|
||||
binding!!.xsbRepeatTimes.setOnSeekBarListener { _, _ ->
|
||||
checkSetting(true)
|
||||
}
|
||||
binding!!.rgAlarmState.setOnCheckedChangeListener { _, checkedId ->
|
||||
binding!!.layoutAlarmSettings.visibility = if (checkedId == R.id.rb_start_alarm) View.VISIBLE else View.GONE
|
||||
binding!!.layoutVibrateSettings.visibility = if (checkedId == R.id.rb_start_alarm) View.VISIBLE else View.GONE
|
||||
binding!!.layoutFlashSettings.visibility = if (checkedId == R.id.rb_start_alarm) View.VISIBLE else View.GONE
|
||||
checkSetting(true)
|
||||
}
|
||||
binding!!.btInsertVibrationEffect1.setOnClickListener(this)
|
||||
binding!!.btInsertVibrationEffect2.setOnClickListener(this)
|
||||
binding!!.btInsertVibrationEffect3.setOnClickListener(this)
|
||||
binding!!.btInsertFlashEffect1.setOnClickListener(this)
|
||||
binding!!.btInsertFlashEffect2.setOnClickListener(this)
|
||||
}
|
||||
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
when (v.id) {
|
||||
R.id.bt_insert_vibration_effect_1 -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(binding!!.etVibrationEffect, "=")
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_vibration_effect_2 -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(binding!!.etVibrationEffect, "-")
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_vibration_effect_3 -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(binding!!.etVibrationEffect, "_")
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_flash_effect_1 -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(binding!!.etFlashEffect, "X")
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_flash_effect_2 -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(binding!!.etFlashEffect, "O")
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_file_picker -> {
|
||||
// 申请储存权限
|
||||
XXPermissions.with(this).permission(Permission.MANAGE_EXTERNAL_STORAGE).request(object : OnPermissionCallback {
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
val downloadPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path
|
||||
val fileList = findAudioFiles(downloadPath)
|
||||
if (fileList.isEmpty()) {
|
||||
XToastUtils.error(String.format(getString(R.string.download_music_first), downloadPath))
|
||||
return
|
||||
}
|
||||
MaterialDialog.Builder(requireContext()).title(getString(R.string.alarm_music)).content(String.format(getString(R.string.root_directory), downloadPath)).items(fileList).itemsCallbackSingleChoice(0) { _: MaterialDialog?, _: View?, _: Int, text: CharSequence ->
|
||||
val webPath = "$downloadPath/$text"
|
||||
binding!!.etMusicPath.setText(webPath)
|
||||
checkSetting(true)
|
||||
true // allow selection
|
||||
}.positiveText(R.string.select).negativeText(R.string.cancel).show()
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: List<String>, never: Boolean) {
|
||||
if (never) {
|
||||
XToastUtils.error(R.string.toast_denied_never)
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(requireContext(), permissions)
|
||||
} else {
|
||||
XToastUtils.error(R.string.toast_denied)
|
||||
}
|
||||
binding!!.etMusicPath.setText(getString(R.string.storage_permission_tips))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
R.id.btn_test -> {
|
||||
val permissions = arrayListOf<String>()
|
||||
permissions.add(Permission.WRITE_SETTINGS)
|
||||
if (binding!!.sbEnableFlash.isChecked) {
|
||||
permissions.add(Permission.CAMERA)
|
||||
}
|
||||
// 申请修改系统设置权限
|
||||
XXPermissions.with(this).permission(permissions).request(object : OnPermissionCallback {
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
mCountDownHelper?.start()
|
||||
try {
|
||||
val settingVo = checkSetting()
|
||||
Log.d(TAG, settingVo.toString())
|
||||
if (settingVo.playTimes < 0 && settingVo.repeatTimes < 0 && settingVo.flashTimes < 0) {
|
||||
XToastUtils.error(getString(R.string.alarm_settings_error))
|
||||
return
|
||||
}
|
||||
val taskAction = TaskSetting(TASK_ACTION_ALARM, getString(R.string.task_alarm), settingVo.description, Gson().toJson(settingVo), requestCode)
|
||||
val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
|
||||
val msgInfo = MsgInfo("task", getString(R.string.task_alarm), settingVo.description, Date(), getString(R.string.task_alarm))
|
||||
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build()
|
||||
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
|
||||
WorkManager.getInstance().enqueue(actionRequest)
|
||||
} catch (e: Exception) {
|
||||
mCountDownHelper?.finish()
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "onClick error: ${e.message}")
|
||||
XToastUtils.error(e.message.toString(), 30000)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: List<String>, never: Boolean) {
|
||||
if (never) {
|
||||
XToastUtils.error(R.string.toast_denied_never)
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(requireContext(), permissions)
|
||||
} else {
|
||||
XToastUtils.error(R.string.toast_denied)
|
||||
}
|
||||
binding!!.tvDescription.text = getString(R.string.write_settings_permission_tips)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_del -> {
|
||||
popToBack()
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_save -> {
|
||||
val permissions = arrayListOf<String>()
|
||||
permissions.add(Permission.WRITE_SETTINGS)
|
||||
if (binding!!.sbEnableFlash.isChecked) {
|
||||
permissions.add(Permission.CAMERA)
|
||||
}
|
||||
// 申请修改系统设置权限
|
||||
XXPermissions.with(this).permission(permissions).request(object : OnPermissionCallback {
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
val settingVo = checkSetting()
|
||||
if (settingVo.playTimes < 0 && settingVo.repeatTimes < 0 && settingVo.flashTimes < 0) {
|
||||
XToastUtils.error(getString(R.string.alarm_settings_error))
|
||||
return
|
||||
}
|
||||
val intent = Intent()
|
||||
intent.putExtra(KEY_BACK_DESCRIPTION_ACTION, settingVo.description)
|
||||
intent.putExtra(KEY_BACK_DATA_ACTION, Gson().toJson(settingVo))
|
||||
setFragmentResult(TASK_ACTION_ALARM, intent)
|
||||
popToBack()
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: List<String>, never: Boolean) {
|
||||
if (never) {
|
||||
XToastUtils.error(R.string.toast_denied_never)
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(requireContext(), permissions)
|
||||
} else {
|
||||
XToastUtils.error(R.string.toast_denied)
|
||||
}
|
||||
binding!!.tvDescription.text = getString(R.string.write_settings_permission_tips)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
XToastUtils.error(e.message.toString(), 30000)
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "onClick error: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
//检查设置
|
||||
@Suppress("SameParameterValue")
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun checkSetting(updateView: Boolean = false): AlarmSetting {
|
||||
val enableMusic = binding!!.sbEnableMusic.isChecked
|
||||
val enableVibrate = binding!!.sbEnableVibrate.isChecked
|
||||
val enableFlash = binding!!.sbEnableFlash.isChecked
|
||||
val volume = binding!!.xsbVolume.selectedNumber
|
||||
var playTimes = binding!!.xsbPlayTimes.selectedNumber
|
||||
val music = binding!!.etMusicPath.text.toString().trim()
|
||||
var repeatTimes = binding!!.xsbRepeatTimes.selectedNumber
|
||||
val vibrationEffect = binding!!.etVibrationEffect.text.toString().trim()
|
||||
var flashTimes = binding!!.xsbFlashTimes.selectedNumber
|
||||
var flashEffect = binding!!.etFlashEffect.text.toString().trim()
|
||||
val description = StringBuilder()
|
||||
val action = if (binding!!.rgAlarmState.checkedRadioButtonId == R.id.rb_start_alarm) {
|
||||
description.append(getString(R.string.start_alarm))
|
||||
if (enableMusic) {
|
||||
description.append(", ").append(getString(R.string.alarm_volume)).append(":").append(volume).append("%")
|
||||
description.append(", ").append(getString(R.string.alarm_play_times)).append(":").append(playTimes)
|
||||
if (music.isNotEmpty()) {
|
||||
description.append(", ").append(getString(R.string.alarm_music)).append(":").append(music)
|
||||
}
|
||||
} else {
|
||||
playTimes = -1
|
||||
}
|
||||
if (enableVibrate) {
|
||||
vibrationEffect.ifEmpty { "---___===___".also { binding!!.etVibrationEffect.setText(it) } }
|
||||
description.append(", ").append(getString(R.string.alarm_vibration_effect)).append(":").append(vibrationEffect)
|
||||
description.append(", ").append(getString(R.string.alarm_repeat_times)).append(":").append(repeatTimes)
|
||||
} else {
|
||||
repeatTimes = -1
|
||||
}
|
||||
if (enableFlash) {
|
||||
flashEffect.ifEmpty { "XXOOXXOO".also { binding!!.etFlashEffect.setText(it) } }
|
||||
flashEffect = flashEffect.toUpperCase(Locale.ROOT).replace("1", "X").replace("0", "O")
|
||||
description.append(", ").append(getString(R.string.alarm_flash_effect)).append(":").append(flashEffect)
|
||||
description.append(", ").append(getString(R.string.alarm_repeat_times)).append(":").append(flashTimes)
|
||||
} else {
|
||||
flashTimes = -1
|
||||
}
|
||||
"start"
|
||||
} else {
|
||||
description.append(getString(R.string.stop_alarm))
|
||||
"stop"
|
||||
}
|
||||
|
||||
if (updateView) {
|
||||
binding!!.tvDescription.text = description.toString()
|
||||
}
|
||||
|
||||
return AlarmSetting(description.toString(), action, volume, playTimes, music, repeatTimes, vibrationEffect, flashTimes, flashEffect)
|
||||
}
|
||||
|
||||
private fun findAudioFiles(directoryPath: String): List<String> {
|
||||
val audioFiles = mutableListOf<String>()
|
||||
val directory = File(directoryPath)
|
||||
|
||||
if (directory.exists() && directory.isDirectory) {
|
||||
directory.listFiles()?.let { files ->
|
||||
// 筛选出支持的音频文件
|
||||
files.filter { it.isFile && isSupportedAudioFile(it) }.forEach { audioFiles.add(it.name) }
|
||||
}
|
||||
}
|
||||
|
||||
return audioFiles
|
||||
}
|
||||
|
||||
private fun isSupportedAudioFile(file: File): Boolean {
|
||||
val supportedExtensions = listOf("mp3", "ogg", "wav")
|
||||
return supportedExtensions.any { it.equals(file.extension, ignoreCase = true) }
|
||||
}
|
||||
}
|
@ -103,7 +103,7 @@ class CleanerFragment : BaseFragment<FragmentTasksActionCleanerBinding?>(), View
|
||||
val taskAction = TaskSetting(TASK_ACTION_CLEANER, getString(R.string.task_cleaner), settingVo.description, Gson().toJson(settingVo), requestCode)
|
||||
val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
|
||||
val msgInfo = MsgInfo("task", getString(R.string.task_cleaner), settingVo.description, Date(), getString(R.string.task_cleaner))
|
||||
val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build()
|
||||
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build()
|
||||
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
|
||||
WorkManager.getInstance().enqueue(actionRequest)
|
||||
} catch (e: Exception) {
|
||||
@ -141,7 +141,7 @@ class CleanerFragment : BaseFragment<FragmentTasksActionCleanerBinding?>(), View
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun checkSetting(): CleanerSetting {
|
||||
val days = binding!!.xsbDays.selectedNumber
|
||||
val description = "自动删除${days}天前的转发记录"
|
||||
val description = String.format(getString(R.string.task_cleaner_desc), days)
|
||||
return CleanerSetting(description, days)
|
||||
}
|
||||
}
|
@ -110,10 +110,15 @@ class FrpcFragment : BaseFragment<FragmentTasksActionFrpcBinding?>(), View.OnCli
|
||||
Log.d(TAG, "initViews settingVo:$settingVo")
|
||||
}
|
||||
|
||||
//初始化发送通道下拉框
|
||||
//初始化Frpc下拉框
|
||||
initFrpc()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun initListeners() {
|
||||
binding!!.btnTest.setOnClickListener(this)
|
||||
@ -133,7 +138,7 @@ class FrpcFragment : BaseFragment<FragmentTasksActionFrpcBinding?>(), View.OnCli
|
||||
val taskAction = TaskSetting(TASK_ACTION_FRPC, getString(R.string.task_frpc), settingVo.description, Gson().toJson(settingVo), requestCode)
|
||||
val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
|
||||
val msgInfo = MsgInfo("task", getString(R.string.task_frpc), settingVo.description, Date(), getString(R.string.task_frpc))
|
||||
val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build()
|
||||
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build()
|
||||
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
|
||||
WorkManager.getInstance().enqueue(actionRequest)
|
||||
} catch (e: Exception) {
|
||||
|
@ -98,6 +98,11 @@ class HttpServerFragment : BaseFragment<FragmentTasksActionHttpServerBinding?>()
|
||||
binding!!.sbApiQueryBattery.isChecked = settingVo.enableApiBatteryQuery
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun initListeners() {
|
||||
binding!!.btnTest.setOnClickListener(this)
|
||||
@ -145,7 +150,7 @@ class HttpServerFragment : BaseFragment<FragmentTasksActionHttpServerBinding?>()
|
||||
val taskAction = TaskSetting(TASK_ACTION_HTTPSERVER, getString(R.string.task_http_server), settingVo.description, Gson().toJson(settingVo), requestCode)
|
||||
val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
|
||||
val msgInfo = MsgInfo("task", getString(R.string.task_http_server), settingVo.description, Date(), getString(R.string.task_http_server))
|
||||
val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build()
|
||||
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build()
|
||||
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
|
||||
WorkManager.getInstance().enqueue(actionRequest)
|
||||
} catch (e: Exception) {
|
||||
|
@ -5,7 +5,8 @@ import android.content.Intent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import android.widget.AdapterView
|
||||
import android.widget.CompoundButton
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@ -25,7 +26,23 @@ import com.idormy.sms.forwarder.database.entity.Sender
|
||||
import com.idormy.sms.forwarder.databinding.FragmentTasksActionNotificationBinding
|
||||
import com.idormy.sms.forwarder.entity.MsgInfo
|
||||
import com.idormy.sms.forwarder.entity.TaskSetting
|
||||
import com.idormy.sms.forwarder.utils.*
|
||||
import com.idormy.sms.forwarder.utils.CHECK_IS
|
||||
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_ALL
|
||||
import com.idormy.sms.forwarder.utils.CommonUtils
|
||||
import com.idormy.sms.forwarder.utils.DataProvider
|
||||
import com.idormy.sms.forwarder.utils.FILED_TRANSPOND_ALL
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_ACTION
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_ACTION
|
||||
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_ACTION
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.SENDER_LOGIC_ALL
|
||||
import com.idormy.sms.forwarder.utils.SENDER_LOGIC_UNTIL_FAIL
|
||||
import com.idormy.sms.forwarder.utils.SENDER_LOGIC_UNTIL_SUCCESS
|
||||
import com.idormy.sms.forwarder.utils.STATUS_OFF
|
||||
import com.idormy.sms.forwarder.utils.STATUS_ON
|
||||
import com.idormy.sms.forwarder.utils.TASK_ACTION_NOTIFICATION
|
||||
import com.idormy.sms.forwarder.utils.TaskWorker
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.idormy.sms.forwarder.workers.ActionWorker
|
||||
import com.xuexiang.xaop.annotation.SingleClick
|
||||
import com.xuexiang.xpage.annotation.Page
|
||||
@ -41,8 +58,7 @@ import io.reactivex.SingleObserver
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.*
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
|
||||
@Page(name = "Notification")
|
||||
@Suppress("PrivatePropertyName", "DEPRECATION")
|
||||
@ -139,19 +155,18 @@ class NotificationFragment : BaseFragment<FragmentTasksActionNotificationBinding
|
||||
|
||||
//初始化发送通道下拉框
|
||||
initSenderSpinner()
|
||||
|
||||
//创建标签按钮
|
||||
CommonUtils.createTagButtons(requireContext(), binding!!.glSmsTemplate, binding!!.etSmsTemplate, ruleType)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun initListeners() {
|
||||
binding!!.btnSilentPeriod.setOnClickListener(this)
|
||||
binding!!.btInsertSender.setOnClickListener(this)
|
||||
binding!!.btInsertContent.setOnClickListener(this)
|
||||
binding!!.btInsertSenderApp.setOnClickListener(this)
|
||||
binding!!.btInsertUid.setOnClickListener(this)
|
||||
binding!!.btInsertTitleApp.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)
|
||||
@ -191,7 +206,6 @@ class NotificationFragment : BaseFragment<FragmentTasksActionNotificationBinding
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
val etSmsTemplate: EditText = binding!!.etSmsTemplate
|
||||
when (v.id) {
|
||||
R.id.btn_silent_period -> {
|
||||
OptionsPickerBuilder(context, OnOptionsSelectListener { _: View?, options1: Int, options2: Int, _: Int ->
|
||||
@ -207,51 +221,6 @@ class NotificationFragment : BaseFragment<FragmentTasksActionNotificationBinding
|
||||
}
|
||||
}
|
||||
|
||||
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_uid -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_uid))
|
||||
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 -> {
|
||||
mCountDownHelper?.start()
|
||||
try {
|
||||
@ -260,7 +229,7 @@ class NotificationFragment : BaseFragment<FragmentTasksActionNotificationBinding
|
||||
val taskAction = TaskSetting(TASK_ACTION_NOTIFICATION, getString(R.string.task_notification), description, Gson().toJson(settingVo), requestCode)
|
||||
val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
|
||||
val msgInfo = MsgInfo("task", getString(R.string.task_notification), description, Date(), getString(R.string.task_notification))
|
||||
val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build()
|
||||
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build()
|
||||
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
|
||||
WorkManager.getInstance().enqueue(actionRequest)
|
||||
} catch (e: Exception) {
|
||||
@ -484,4 +453,4 @@ class NotificationFragment : BaseFragment<FragmentTasksActionNotificationBinding
|
||||
return 0
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,171 @@
|
||||
package com.idormy.sms.forwarder.fragment.action
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.google.gson.Gson
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.core.BaseFragment
|
||||
import com.idormy.sms.forwarder.databinding.FragmentTasksActionResendBinding
|
||||
import com.idormy.sms.forwarder.entity.MsgInfo
|
||||
import com.idormy.sms.forwarder.entity.TaskSetting
|
||||
import com.idormy.sms.forwarder.entity.action.ResendSetting
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_ACTION
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_ACTION
|
||||
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_ACTION
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.TASK_ACTION_RESEND
|
||||
import com.idormy.sms.forwarder.utils.TaskWorker
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.idormy.sms.forwarder.workers.ActionWorker
|
||||
import com.xuexiang.xaop.annotation.SingleClick
|
||||
import com.xuexiang.xpage.annotation.Page
|
||||
import com.xuexiang.xrouter.annotation.AutoWired
|
||||
import com.xuexiang.xrouter.launcher.XRouter
|
||||
import com.xuexiang.xui.utils.CountDownButtonHelper
|
||||
import com.xuexiang.xui.widget.actionbar.TitleBar
|
||||
import java.util.Date
|
||||
|
||||
@Page(name = "Resend")
|
||||
@Suppress("PrivatePropertyName", "DEPRECATION")
|
||||
class ResendFragment : BaseFragment<FragmentTasksActionResendBinding?>(), View.OnClickListener {
|
||||
|
||||
private val TAG: String = ResendFragment::class.java.simpleName
|
||||
private var titleBar: TitleBar? = null
|
||||
private var mCountDownHelper: CountDownButtonHelper? = null
|
||||
|
||||
@JvmField
|
||||
@AutoWired(name = KEY_EVENT_DATA_ACTION)
|
||||
var eventData: String? = null
|
||||
|
||||
override fun initArgs() {
|
||||
XRouter.getInstance().inject(this)
|
||||
}
|
||||
|
||||
override fun viewBindingInflate(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup,
|
||||
): FragmentTasksActionResendBinding {
|
||||
return FragmentTasksActionResendBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun initTitle(): TitleBar? {
|
||||
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_resend)
|
||||
return titleBar
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化控件
|
||||
*/
|
||||
override fun initViews() {
|
||||
//测试按钮增加倒计时,避免重复点击
|
||||
mCountDownHelper = CountDownButtonHelper(binding!!.btnTest, 1)
|
||||
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
|
||||
override fun onCountDown(time: Int) {
|
||||
binding!!.btnTest.text = String.format(getString(R.string.seconds_n), time)
|
||||
}
|
||||
|
||||
override fun onFinished() {
|
||||
binding!!.btnTest.text = getString(R.string.test)
|
||||
}
|
||||
})
|
||||
|
||||
var settingVo = ResendSetting(getString(R.string.task_resend_tips), 1, listOf(0))
|
||||
Log.d(TAG, "initViews eventData:$eventData")
|
||||
if (eventData != null) {
|
||||
settingVo = Gson().fromJson(eventData, ResendSetting::class.java)
|
||||
Log.d(TAG, "initViews settingVo:$settingVo")
|
||||
}
|
||||
binding!!.xsbHours.setDefaultValue(settingVo.hours)
|
||||
settingVo.statusList.forEach { item ->
|
||||
when (item) {
|
||||
0 -> binding!!.scbFailed.isChecked = true
|
||||
1 -> binding!!.scbProcessing.isChecked = true
|
||||
2 -> binding!!.scbSuccess.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun initListeners() {
|
||||
binding!!.btnTest.setOnClickListener(this)
|
||||
binding!!.btnDel.setOnClickListener(this)
|
||||
binding!!.btnSave.setOnClickListener(this)
|
||||
}
|
||||
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
when (v.id) {
|
||||
R.id.btn_test -> {
|
||||
mCountDownHelper?.start()
|
||||
try {
|
||||
val settingVo = checkSetting()
|
||||
Log.d(TAG, settingVo.toString())
|
||||
val taskAction = TaskSetting(TASK_ACTION_RESEND, getString(R.string.task_resend), settingVo.description, Gson().toJson(settingVo), requestCode)
|
||||
val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
|
||||
val msgInfo = MsgInfo("task", getString(R.string.task_resend), settingVo.description, Date(), getString(R.string.task_resend))
|
||||
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build()
|
||||
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
|
||||
WorkManager.getInstance().enqueue(actionRequest)
|
||||
} catch (e: Exception) {
|
||||
mCountDownHelper?.finish()
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "onClick error: ${e.message}")
|
||||
XToastUtils.error(e.message.toString(), 30000)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_del -> {
|
||||
popToBack()
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_save -> {
|
||||
val settingVo = checkSetting()
|
||||
val intent = Intent()
|
||||
intent.putExtra(KEY_BACK_DESCRIPTION_ACTION, settingVo.description)
|
||||
intent.putExtra(KEY_BACK_DATA_ACTION, Gson().toJson(settingVo))
|
||||
setFragmentResult(TASK_ACTION_RESEND, intent)
|
||||
popToBack()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
XToastUtils.error(e.message.toString(), 30000)
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "onClick error: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
//检查设置
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun checkSetting(): ResendSetting {
|
||||
val hours = binding!!.xsbHours.selectedNumber
|
||||
val statusList = mutableListOf<Int>()
|
||||
val statusStrList = mutableListOf<String>()
|
||||
if (binding!!.scbFailed.isChecked) {
|
||||
statusList.add(0)
|
||||
statusStrList.add(getString(R.string.failed))
|
||||
}
|
||||
if (binding!!.scbProcessing.isChecked) {
|
||||
statusList.add(1)
|
||||
statusStrList.add(getString(R.string.processing))
|
||||
}
|
||||
if (binding!!.scbSuccess.isChecked) {
|
||||
statusList.add(2)
|
||||
statusStrList.add(getString(R.string.success))
|
||||
}
|
||||
if (statusList.isEmpty()) {
|
||||
throw Exception(getString(R.string.task_resend_error))
|
||||
}
|
||||
val description = String.format(getString(R.string.task_resend_desc), hours, statusStrList.joinToString("/"))
|
||||
return ResendSetting(description, hours, statusList)
|
||||
}
|
||||
}
|
@ -119,6 +119,11 @@ class RuleFragment : BaseFragment<FragmentTasksActionRuleBinding?>(), View.OnCli
|
||||
initRule()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun initListeners() {
|
||||
binding!!.btnTest.setOnClickListener(this)
|
||||
@ -138,7 +143,7 @@ class RuleFragment : BaseFragment<FragmentTasksActionRuleBinding?>(), View.OnCli
|
||||
val taskAction = TaskSetting(TASK_ACTION_RULE, getString(R.string.task_rule), settingVo.description, Gson().toJson(settingVo), requestCode)
|
||||
val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
|
||||
val msgInfo = MsgInfo("task", getString(R.string.task_rule), settingVo.description, Date(), getString(R.string.task_rule))
|
||||
val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build()
|
||||
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build()
|
||||
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
|
||||
WorkManager.getInstance().enqueue(actionRequest)
|
||||
} catch (e: Exception) {
|
||||
@ -251,7 +256,8 @@ class RuleFragment : BaseFragment<FragmentTasksActionRuleBinding?>(), View.OnCli
|
||||
ruleSpinnerList.clear()
|
||||
ruleListAll = ruleList as MutableList<Rule>
|
||||
for (rule in ruleList) {
|
||||
val name = if (rule.name.length > 20) rule.name.substring(0, 19) else rule.name
|
||||
var name = rule.getName()
|
||||
if (name.length > 20) name = name.substring(0, 19)
|
||||
val icon = when (rule.type) {
|
||||
"sms" -> R.drawable.auto_task_icon_sms
|
||||
"call" -> R.drawable.auto_task_icon_incall
|
||||
|
@ -114,6 +114,11 @@ class SendSmsFragment : BaseFragment<FragmentTasksActionSendSmsBinding?>(), View
|
||||
binding!!.etMsgContent.setText(msgContent)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun initListeners() {
|
||||
binding!!.btnTest.setOnClickListener(this)
|
||||
@ -146,7 +151,7 @@ class SendSmsFragment : BaseFragment<FragmentTasksActionSendSmsBinding?>(), View
|
||||
val taskAction = TaskSetting(TASK_ACTION_SENDSMS, getString(R.string.task_sendsms), settingVo.description, Gson().toJson(settingVo), requestCode)
|
||||
val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
|
||||
val msgInfo = MsgInfo("task", getString(R.string.task_sendsms), settingVo.description, Date(), getString(R.string.task_sendsms))
|
||||
val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build()
|
||||
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build()
|
||||
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
|
||||
WorkManager.getInstance().enqueue(actionRequest)
|
||||
} catch (e: Exception) {
|
||||
@ -191,8 +196,8 @@ class SendSmsFragment : BaseFragment<FragmentTasksActionSendSmsBinding?>(), View
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun checkSetting(): SmsSetting {
|
||||
phoneNumbers = binding!!.etPhoneNumbers.text.toString().trim()
|
||||
if (!getString(R.string.phone_numbers_regex).toRegex().matches(phoneNumbers)) {
|
||||
throw Exception(getString(R.string.phone_numbers_error))
|
||||
if (!getString(R.string.phone_numbers_with_tag_regex).toRegex().matches(phoneNumbers)) {
|
||||
throw Exception(getString(R.string.phone_numbers_with_tag_error))
|
||||
}
|
||||
|
||||
msgContent = binding!!.etMsgContent.text.toString().trim()
|
||||
|
@ -120,6 +120,11 @@ class SenderFragment : BaseFragment<FragmentTasksActionSenderBinding?>(), View.O
|
||||
initSender()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun initListeners() {
|
||||
binding!!.btnTest.setOnClickListener(this)
|
||||
@ -139,7 +144,7 @@ class SenderFragment : BaseFragment<FragmentTasksActionSenderBinding?>(), View.O
|
||||
val taskAction = TaskSetting(TASK_ACTION_SENDER, getString(R.string.task_sender), settingVo.description, Gson().toJson(settingVo), requestCode)
|
||||
val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
|
||||
val msgInfo = MsgInfo("task", getString(R.string.task_sender), settingVo.description, Date(), getString(R.string.task_sender))
|
||||
val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build()
|
||||
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build()
|
||||
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
|
||||
WorkManager.getInstance().enqueue(actionRequest)
|
||||
} catch (e: Exception) {
|
||||
|
@ -3,9 +3,7 @@ package com.idormy.sms.forwarder.fragment.action
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.location.Criteria
|
||||
import android.text.Editable
|
||||
import android.text.TextUtils
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -148,6 +146,11 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
|
||||
binding!!.xsbDuplicateMessagesLimits.setDefaultValue(settingVo.duplicateMessagesLimits)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun initListeners() {
|
||||
binding!!.btnTest.setOnClickListener(this)
|
||||
@ -159,7 +162,6 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
|
||||
|
||||
binding!!.sbEnableSms.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
if (isChecked) {
|
||||
//检查权限是否获取
|
||||
XXPermissions.with(this)
|
||||
// 接收 WAP 推送消息
|
||||
.permission(Permission.RECEIVE_WAP_PUSH)
|
||||
@ -170,7 +172,8 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
|
||||
// 发送短信
|
||||
//.permission(Permission.SEND_SMS)
|
||||
// 读取短信
|
||||
.permission(Permission.READ_SMS).request(object : OnPermissionCallback {
|
||||
.permission(Permission.READ_SMS)
|
||||
.request(object : OnPermissionCallback {
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
if (all) {
|
||||
XToastUtils.info(R.string.toast_granted_all)
|
||||
@ -195,7 +198,6 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
|
||||
|
||||
binding!!.sbEnablePhone.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
if (isChecked) {
|
||||
//检查权限是否获取
|
||||
XXPermissions.with(this)
|
||||
// 读取电话状态
|
||||
.permission(Permission.READ_PHONE_STATE)
|
||||
@ -204,7 +206,8 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
|
||||
// 读取通话记录
|
||||
.permission(Permission.READ_CALL_LOG)
|
||||
// 读取联系人
|
||||
.permission(Permission.READ_CONTACTS).request(object : OnPermissionCallback {
|
||||
.permission(Permission.READ_CONTACTS)
|
||||
.request(object : OnPermissionCallback {
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
if (all) {
|
||||
XToastUtils.info(R.string.toast_granted_all)
|
||||
@ -229,69 +232,67 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
|
||||
|
||||
binding!!.sbEnableAppNotify.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
if (isChecked) {
|
||||
//检查权限是否获取
|
||||
XXPermissions.with(this).permission(Permission.BIND_NOTIFICATION_LISTENER_SERVICE).request(OnPermissionCallback { _, allGranted ->
|
||||
if (!allGranted) {
|
||||
binding!!.sbEnableAppNotify.isChecked = false
|
||||
XToastUtils.error(R.string.tips_notification_listener)
|
||||
return@OnPermissionCallback
|
||||
}
|
||||
XXPermissions.with(this)
|
||||
.permission(Permission.BIND_NOTIFICATION_LISTENER_SERVICE)
|
||||
.request(OnPermissionCallback { _, allGranted ->
|
||||
if (!allGranted) {
|
||||
binding!!.sbEnableAppNotify.isChecked = false
|
||||
XToastUtils.error(R.string.tips_notification_listener)
|
||||
return@OnPermissionCallback
|
||||
}
|
||||
|
||||
binding!!.sbEnableAppNotify.isChecked = true
|
||||
CommonUtils.toggleNotificationListenerService(requireContext())
|
||||
})
|
||||
binding!!.sbEnableAppNotify.isChecked = true
|
||||
CommonUtils.toggleNotificationListenerService(requireContext())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
binding!!.sbEnableLocation.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
if (isChecked) {
|
||||
XXPermissions.with(this).permission(Permission.ACCESS_COARSE_LOCATION).permission(Permission.ACCESS_FINE_LOCATION).permission(Permission.ACCESS_BACKGROUND_LOCATION).request(object : OnPermissionCallback {
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: List<String>, never: Boolean) {
|
||||
if (never) {
|
||||
XToastUtils.error(R.string.toast_denied_never)
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(requireContext(), permissions)
|
||||
} else {
|
||||
XToastUtils.error(R.string.toast_denied)
|
||||
XXPermissions.with(this)
|
||||
.permission(Permission.ACCESS_COARSE_LOCATION)
|
||||
.permission(Permission.ACCESS_FINE_LOCATION)
|
||||
.permission(Permission.ACCESS_BACKGROUND_LOCATION)
|
||||
.request(object : OnPermissionCallback {
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
}
|
||||
binding!!.sbEnableLocation.isChecked = false
|
||||
}
|
||||
})
|
||||
|
||||
override fun onDenied(permissions: List<String>, never: Boolean) {
|
||||
if (never) {
|
||||
XToastUtils.error(R.string.toast_denied_never)
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(requireContext(), permissions)
|
||||
} else {
|
||||
XToastUtils.error(R.string.toast_denied)
|
||||
}
|
||||
binding!!.sbEnableLocation.isChecked = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
//设置位置更新最小时间间隔(单位:毫秒); 默认间隔:10000毫秒,最小间隔:1000毫秒
|
||||
binding!!.etMinInterval.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
val changedText = s.toString()
|
||||
if (changedText.isEmpty() || changedText == "0") {
|
||||
binding!!.etMinInterval.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (!hasFocus) {
|
||||
val inputText = binding!!.etMinInterval.text.toString()
|
||||
if (inputText.isEmpty() || inputText == "0") {
|
||||
binding!!.etMinInterval.setText("1")
|
||||
binding!!.etMinInterval.setSelection(binding!!.etMinInterval.text.length) // 将光标移至文本末尾
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
//设置位置更新最小距离(单位:米);默认距离:0米
|
||||
binding!!.etMinDistance.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
val changedText = s.toString()
|
||||
if (changedText.isEmpty()) {
|
||||
binding!!.etMinDistance.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (!hasFocus) {
|
||||
val inputText = binding!!.etMinDistance.text.toString()
|
||||
if (inputText.isEmpty()) {
|
||||
binding!!.etMinDistance.setText("0")
|
||||
binding!!.etMinDistance.setSelection(binding!!.etMinInterval.text.length) // 将光标移至文本末尾
|
||||
return
|
||||
binding!!.etMinDistance.setSelection(binding!!.etMinDistance.text.length) // 将光标移至文本末尾
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
binding!!.sbEnableSmsCommand.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
|
||||
if (isChecked) {
|
||||
//检查权限是否获取
|
||||
XXPermissions.with(this)
|
||||
// 系统设置
|
||||
.permission(Permission.WRITE_SETTINGS)
|
||||
@ -300,7 +301,8 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
|
||||
// 发送短信
|
||||
.permission(Permission.SEND_SMS)
|
||||
// 读取短信
|
||||
.permission(Permission.READ_SMS).request(object : OnPermissionCallback {
|
||||
.permission(Permission.READ_SMS)
|
||||
.request(object : OnPermissionCallback {
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
if (all) {
|
||||
XToastUtils.info(R.string.toast_granted_all)
|
||||
@ -337,7 +339,7 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
|
||||
val taskAction = TaskSetting(TASK_ACTION_SETTINGS, getString(R.string.task_settings), settingVo.description, Gson().toJson(settingVo), requestCode)
|
||||
val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
|
||||
val msgInfo = MsgInfo("task", getString(R.string.task_settings), settingVo.description, Date(), getString(R.string.task_settings))
|
||||
val actionData = Data.Builder().putLong(TaskWorker.taskId, 0).putString(TaskWorker.taskActions, taskActionsJson).putString(TaskWorker.msgInfo, Gson().toJson(msgInfo)).build()
|
||||
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build()
|
||||
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
|
||||
WorkManager.getInstance().enqueue(actionRequest)
|
||||
} catch (e: Exception) {
|
||||
@ -425,7 +427,7 @@ class SettingsFragment : BaseFragment<FragmentTasksActionSettingsBinding?>(), Vi
|
||||
if (enableSms) enableList.add(getString(R.string.forward_sms)) else disableList.add(getString(R.string.forward_sms))
|
||||
|
||||
val enablePhone = binding!!.sbEnablePhone.isChecked
|
||||
if (enablePhone) enableList.add(getString(R.string.forward_missed_calls)) else disableList.add(getString(R.string.forward_missed_calls))
|
||||
if (enablePhone) enableList.add(getString(R.string.forward_calls)) else disableList.add(getString(R.string.forward_calls))
|
||||
val enableCallType1 = binding!!.scbCallType1.isChecked
|
||||
val enableCallType2 = binding!!.scbCallType2.isChecked
|
||||
val enableCallType3 = binding!!.scbCallType3.isChecked
|
||||
|
@ -0,0 +1,303 @@
|
||||
package com.idormy.sms.forwarder.fragment.action
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.google.gson.Gson
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.adapter.TaskRecyclerAdapter
|
||||
import com.idormy.sms.forwarder.adapter.base.ItemMoveCallback
|
||||
import com.idormy.sms.forwarder.adapter.spinner.TaskSpinnerAdapter
|
||||
import com.idormy.sms.forwarder.adapter.spinner.TaskSpinnerItem
|
||||
import com.idormy.sms.forwarder.core.BaseFragment
|
||||
import com.idormy.sms.forwarder.core.Core
|
||||
import com.idormy.sms.forwarder.database.entity.Task
|
||||
import com.idormy.sms.forwarder.databinding.FragmentTasksActionTaskBinding
|
||||
import com.idormy.sms.forwarder.entity.MsgInfo
|
||||
import com.idormy.sms.forwarder.entity.TaskSetting
|
||||
import com.idormy.sms.forwarder.entity.action.TaskActionSetting
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_ACTION
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_ACTION
|
||||
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_ACTION
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.STATUS_OFF
|
||||
import com.idormy.sms.forwarder.utils.TASK_ACTION_TASK
|
||||
import com.idormy.sms.forwarder.utils.TaskWorker
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.idormy.sms.forwarder.workers.ActionWorker
|
||||
import com.xuexiang.xaop.annotation.SingleClick
|
||||
import com.xuexiang.xpage.annotation.Page
|
||||
import com.xuexiang.xrouter.annotation.AutoWired
|
||||
import com.xuexiang.xrouter.launcher.XRouter
|
||||
import com.xuexiang.xui.utils.CountDownButtonHelper
|
||||
import com.xuexiang.xui.widget.actionbar.TitleBar
|
||||
import com.xuexiang.xutil.resource.ResUtils.getDrawable
|
||||
import io.reactivex.SingleObserver
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.util.Date
|
||||
|
||||
@Page(name = "Task")
|
||||
@Suppress("PrivatePropertyName", "DEPRECATION")
|
||||
class TaskActionFragment : BaseFragment<FragmentTasksActionTaskBinding?>(), View.OnClickListener {
|
||||
|
||||
private val TAG: String = TaskActionFragment::class.java.simpleName
|
||||
private var titleBar: TitleBar? = null
|
||||
private var mCountDownHelper: CountDownButtonHelper? = null
|
||||
|
||||
//所有自动任务下拉框
|
||||
private var taskListAll = mutableListOf<Task>()
|
||||
private val taskSpinnerList = mutableListOf<TaskSpinnerItem>()
|
||||
private lateinit var taskSpinnerAdapter: TaskSpinnerAdapter<*>
|
||||
|
||||
//已选自动任务列表
|
||||
private var taskId = 0L
|
||||
private var taskListSelected = mutableListOf<Task>()
|
||||
private lateinit var taskRecyclerView: RecyclerView
|
||||
private lateinit var taskRecyclerAdapter: TaskRecyclerAdapter
|
||||
|
||||
@JvmField
|
||||
@AutoWired(name = KEY_EVENT_DATA_ACTION)
|
||||
var eventData: String? = null
|
||||
|
||||
override fun initArgs() {
|
||||
XRouter.getInstance().inject(this)
|
||||
}
|
||||
|
||||
override fun viewBindingInflate(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup,
|
||||
): FragmentTasksActionTaskBinding {
|
||||
return FragmentTasksActionTaskBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun initTitle(): TitleBar? {
|
||||
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_task)
|
||||
return titleBar
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化控件
|
||||
*/
|
||||
override fun initViews() {
|
||||
//测试按钮增加倒计时,避免重复点击
|
||||
mCountDownHelper = CountDownButtonHelper(binding!!.btnTest, 1)
|
||||
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
|
||||
override fun onCountDown(time: Int) {
|
||||
binding!!.btnTest.text = String.format(getString(R.string.seconds_n), time)
|
||||
}
|
||||
|
||||
override fun onFinished() {
|
||||
binding!!.btnTest.text = getString(R.string.test)
|
||||
//获取自动任务列表
|
||||
getTaskList()
|
||||
}
|
||||
})
|
||||
|
||||
Log.d(TAG, "initViews eventData:$eventData")
|
||||
if (eventData != null) {
|
||||
val settingVo = Gson().fromJson(eventData, TaskActionSetting::class.java)
|
||||
binding!!.rgStatus.check(if (settingVo.status == 1) R.id.rb_status_enable else R.id.rb_status_disable)
|
||||
Log.d(TAG, settingVo.taskList.toString())
|
||||
settingVo.taskList.forEach {
|
||||
taskId = it.id
|
||||
taskListSelected.add(it)
|
||||
}
|
||||
Log.d(TAG, "initViews settingVo:$settingVo")
|
||||
}
|
||||
|
||||
//初始化自动任务下拉框
|
||||
initTask()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun initListeners() {
|
||||
binding!!.btnTest.setOnClickListener(this)
|
||||
binding!!.btnDel.setOnClickListener(this)
|
||||
binding!!.btnSave.setOnClickListener(this)
|
||||
}
|
||||
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
when (v.id) {
|
||||
R.id.btn_test -> {
|
||||
mCountDownHelper?.start()
|
||||
try {
|
||||
val settingVo = checkSetting()
|
||||
Log.d(TAG, settingVo.toString())
|
||||
val taskAction = TaskSetting(TASK_ACTION_TASK, getString(R.string.task_task), settingVo.description, Gson().toJson(settingVo), requestCode)
|
||||
val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
|
||||
val msgInfo = MsgInfo("task", getString(R.string.task_task), settingVo.description, Date(), getString(R.string.task_task))
|
||||
val actionData = Data.Builder().putLong(TaskWorker.TASK_ID, 0).putString(TaskWorker.TASK_ACTIONS, taskActionsJson).putString(TaskWorker.MSG_INFO, Gson().toJson(msgInfo)).build()
|
||||
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
|
||||
WorkManager.getInstance().enqueue(actionRequest)
|
||||
} catch (e: Exception) {
|
||||
mCountDownHelper?.finish()
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "onClick error: ${e.message}")
|
||||
XToastUtils.error(e.message.toString(), 30000)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_del -> {
|
||||
popToBack()
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_save -> {
|
||||
val settingVo = checkSetting()
|
||||
val intent = Intent()
|
||||
intent.putExtra(KEY_BACK_DESCRIPTION_ACTION, settingVo.description)
|
||||
intent.putExtra(KEY_BACK_DATA_ACTION, Gson().toJson(settingVo))
|
||||
setFragmentResult(TASK_ACTION_TASK, intent)
|
||||
popToBack()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
XToastUtils.error(e.message.toString(), 30000)
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "onClick error: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
//初始化自动任务
|
||||
@SuppressLint("SetTextI18n", "NotifyDataSetChanged")
|
||||
private fun initTask() {
|
||||
//初始化自动任务下拉框
|
||||
binding!!.spTask.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long ->
|
||||
try {
|
||||
val item = taskSpinnerAdapter.getItemSource(position) as TaskSpinnerItem
|
||||
taskId = item.id!!
|
||||
if (taskId > 0L) {
|
||||
taskListSelected.forEach {
|
||||
if (taskId == it.id) {
|
||||
XToastUtils.warning(getString(R.string.task_contains_tips))
|
||||
return@setOnItemClickListener
|
||||
}
|
||||
}
|
||||
taskListAll.forEach {
|
||||
if (taskId == it.id) {
|
||||
taskListSelected.add(it)
|
||||
}
|
||||
}
|
||||
taskRecyclerAdapter.notifyDataSetChanged()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
XToastUtils.error(e.message.toString())
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化已选自动任务列表 RecyclerView 和 Adapter
|
||||
taskRecyclerView = binding!!.recyclerTasks
|
||||
taskRecyclerAdapter = TaskRecyclerAdapter(taskListSelected, { position ->
|
||||
taskListSelected.removeAt(position)
|
||||
taskRecyclerAdapter.notifyItemRemoved(position)
|
||||
taskRecyclerAdapter.notifyItemRangeChanged(position, taskListSelected.size) // 更新索引
|
||||
})
|
||||
taskRecyclerView.apply {
|
||||
layoutManager = LinearLayoutManager(requireContext())
|
||||
adapter = taskRecyclerAdapter
|
||||
}
|
||||
val taskMoveCallback = ItemMoveCallback(object : ItemMoveCallback.Listener {
|
||||
override fun onItemMove(fromPosition: Int, toPosition: Int) {
|
||||
Log.d(TAG, "onItemMove: $fromPosition $toPosition")
|
||||
taskRecyclerAdapter.onItemMove(fromPosition, toPosition)
|
||||
taskListSelected = taskRecyclerAdapter.itemList
|
||||
}
|
||||
|
||||
override fun onDragFinished() {
|
||||
taskListSelected = taskRecyclerAdapter.itemList
|
||||
//taskRecyclerAdapter.notifyDataSetChanged()
|
||||
Log.d(TAG, "onDragFinished: $taskListSelected")
|
||||
}
|
||||
})
|
||||
val taskTouchHelper = ItemTouchHelper(taskMoveCallback)
|
||||
taskTouchHelper.attachToRecyclerView(taskRecyclerView)
|
||||
taskRecyclerAdapter.setTouchHelper(taskTouchHelper)
|
||||
|
||||
//获取自动任务列表
|
||||
getTaskList()
|
||||
}
|
||||
|
||||
//获取自动任务列表
|
||||
private fun getTaskList() {
|
||||
Core.task.getAll().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(object : SingleObserver<List<Task>> {
|
||||
override fun onSubscribe(d: Disposable) {}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "getTaskList error: ${e.message}")
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onSuccess(taskList: List<Task>) {
|
||||
if (taskList.isEmpty()) {
|
||||
XToastUtils.error(R.string.add_task_first)
|
||||
return
|
||||
}
|
||||
|
||||
taskSpinnerList.clear()
|
||||
taskListAll = taskList as MutableList<Task>
|
||||
for (task in taskList) {
|
||||
val name = if (task.name.length > 20) task.name.substring(0, 19) else task.name
|
||||
taskSpinnerList.add(TaskSpinnerItem(name, getDrawable(if (STATUS_OFF == task.status) task.greyImageId else task.imageId), task.id, task.status))
|
||||
}
|
||||
taskSpinnerAdapter = TaskSpinnerAdapter(taskSpinnerList).setIsFilterKey(true).setFilterColor("#EF5362").setBackgroundSelector(R.drawable.selector_custom_spinner_bg)
|
||||
binding!!.spTask.setAdapter(taskSpinnerAdapter)
|
||||
//taskSpinnerAdapter.notifyDataSetChanged()
|
||||
|
||||
//更新taskListSelected的状态与名称
|
||||
taskListSelected.forEach {
|
||||
taskListAll.forEach { task ->
|
||||
if (it.id == task.id) {
|
||||
//it.name = task.name
|
||||
it.status = task.status
|
||||
}
|
||||
}
|
||||
}
|
||||
taskRecyclerAdapter.notifyDataSetChanged()
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//检查设置
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun checkSetting(): TaskActionSetting {
|
||||
if (taskListSelected.isEmpty() || taskId == 0L) {
|
||||
throw Exception(getString(R.string.new_task_first))
|
||||
}
|
||||
|
||||
val description = StringBuilder()
|
||||
val status: Int
|
||||
if (binding!!.rgStatus.checkedRadioButtonId == R.id.rb_status_enable) {
|
||||
status = 1
|
||||
description.append(getString(R.string.enable))
|
||||
} else {
|
||||
status = 0
|
||||
description.append(getString(R.string.disable))
|
||||
}
|
||||
description.append(getString(R.string.menu_tasks)).append(", ").append(getString(R.string.specified_task)).append(": ")
|
||||
description.append(taskListSelected.joinToString(",") { "[${it.id}]${it.name}" })
|
||||
|
||||
return TaskActionSetting(description.toString(), status, taskListSelected)
|
||||
}
|
||||
}
|
@ -149,9 +149,9 @@ class CallQueryFragment : BaseFragment<FragmentClientCallQueryBinding?>() {
|
||||
|
||||
//搜索框
|
||||
binding!!.searchView.findViewById<View>(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE
|
||||
binding!!.searchView.setVoiceSearch(true)
|
||||
//binding!!.searchView.setVoiceSearch(true)
|
||||
binding!!.searchView.setEllipsize(true)
|
||||
binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions))
|
||||
//binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions))
|
||||
binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
SnackbarUtils.Indefinite(view, String.format(getString(R.string.search_keyword), query)).info()
|
||||
|
@ -20,14 +20,24 @@ import com.idormy.sms.forwarder.core.BaseFragment
|
||||
import com.idormy.sms.forwarder.databinding.FragmentClientCloneBinding
|
||||
import com.idormy.sms.forwarder.entity.CloneInfo
|
||||
import com.idormy.sms.forwarder.server.model.BaseResponse
|
||||
import com.idormy.sms.forwarder.utils.*
|
||||
import com.idormy.sms.forwarder.utils.AppUtils
|
||||
import com.idormy.sms.forwarder.utils.Base64
|
||||
import com.idormy.sms.forwarder.utils.CommonUtils
|
||||
import com.idormy.sms.forwarder.utils.HttpServerUtils
|
||||
import com.idormy.sms.forwarder.utils.KEY_DEFAULT_SELECTION
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.RSACrypt
|
||||
import com.idormy.sms.forwarder.utils.SM4Crypt
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.xuexiang.xaop.annotation.SingleClick
|
||||
import com.xuexiang.xhttp2.XHttp
|
||||
import com.xuexiang.xhttp2.cache.model.CacheMode
|
||||
import com.xuexiang.xhttp2.callback.SimpleCallBack
|
||||
import com.xuexiang.xhttp2.exception.ApiException
|
||||
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.CountDownButtonHelper
|
||||
import com.xuexiang.xui.widget.actionbar.TitleBar
|
||||
@ -38,7 +48,7 @@ import com.xuexiang.xutil.file.FileIOUtils
|
||||
import com.xuexiang.xutil.file.FileUtils
|
||||
import com.xuexiang.xutil.resource.ResUtils.getStringArray
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
|
||||
@Suppress("PrivatePropertyName")
|
||||
@Page(name = "一键换新机")
|
||||
@ -52,6 +62,14 @@ class CloneFragment : BaseFragment<FragmentClientCloneBinding?>(), View.OnClickL
|
||||
private var exportCountDownHelper: CountDownButtonHelper? = null
|
||||
private var importCountDownHelper: CountDownButtonHelper? = null
|
||||
|
||||
@JvmField
|
||||
@AutoWired(name = KEY_DEFAULT_SELECTION)
|
||||
var defaultSelection: Int = 0
|
||||
|
||||
override fun initArgs() {
|
||||
XRouter.getInstance().inject(this)
|
||||
}
|
||||
|
||||
override fun viewBindingInflate(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup,
|
||||
@ -102,6 +120,12 @@ class CloneFragment : BaseFragment<FragmentClientCloneBinding?>(), View.OnClickL
|
||||
binding!!.layoutOffline.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
//通用设置界面跳转时只使用离线模式
|
||||
if (defaultSelection == 1) {
|
||||
binding!!.tabBar.visibility = View.GONE
|
||||
binding!!.layoutNetwork.visibility = View.GONE
|
||||
binding!!.layoutOffline.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
//按钮增加倒计时,避免重复点击
|
||||
pushCountDownHelper = CountDownButtonHelper(binding!!.btnPush, SettingUtils.requestTimeout)
|
||||
|
@ -131,9 +131,9 @@ class ContactQueryFragment : BaseFragment<FragmentClientContactQueryBinding?>()
|
||||
|
||||
//搜索框
|
||||
binding!!.searchView.findViewById<View>(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE
|
||||
binding!!.searchView.setVoiceSearch(true)
|
||||
//binding!!.searchView.setVoiceSearch(true)
|
||||
binding!!.searchView.setEllipsize(true)
|
||||
binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions))
|
||||
//binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions))
|
||||
binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
SnackbarUtils.Indefinite(view, String.format(getString(R.string.search_keyword), query)).info()
|
||||
|
@ -137,9 +137,9 @@ class SmsQueryFragment : BaseFragment<FragmentClientSmsQueryBinding?>() {
|
||||
|
||||
//搜索框
|
||||
binding!!.searchView.findViewById<View>(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE
|
||||
binding!!.searchView.setVoiceSearch(true)
|
||||
//binding!!.searchView.setVoiceSearch(true)
|
||||
binding!!.searchView.setEllipsize(true)
|
||||
binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions))
|
||||
//binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions))
|
||||
binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
SnackbarUtils.Indefinite(view, String.format(getString(R.string.search_keyword), query)).info()
|
||||
|
@ -0,0 +1,325 @@
|
||||
package com.idormy.sms.forwarder.fragment.condition
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.gson.Gson
|
||||
import com.hjq.permissions.OnPermissionCallback
|
||||
import com.hjq.permissions.Permission
|
||||
import com.hjq.permissions.XXPermissions
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.adapter.BluetoothRecyclerAdapter
|
||||
import com.idormy.sms.forwarder.core.BaseFragment
|
||||
import com.idormy.sms.forwarder.databinding.FragmentTasksConditionBluetoothBinding
|
||||
import com.idormy.sms.forwarder.entity.condition.BluetoothSetting
|
||||
import com.idormy.sms.forwarder.service.BluetoothScanService
|
||||
import com.idormy.sms.forwarder.utils.ACTION_START
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_CONDITION
|
||||
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_CONDITION
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.idormy.sms.forwarder.utils.TASK_CONDITION_BLUETOOTH
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.xuexiang.xaop.annotation.SingleClick
|
||||
import com.xuexiang.xpage.annotation.Page
|
||||
import com.xuexiang.xrouter.annotation.AutoWired
|
||||
import com.xuexiang.xrouter.launcher.XRouter
|
||||
import com.xuexiang.xui.utils.CountDownButtonHelper
|
||||
import com.xuexiang.xui.widget.actionbar.TitleBar
|
||||
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
|
||||
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
|
||||
|
||||
|
||||
@Page(name = "Bluetooth")
|
||||
@Suppress("PrivatePropertyName", "SameParameterValue", "DEPRECATION")
|
||||
class BluetoothFragment : BaseFragment<FragmentTasksConditionBluetoothBinding?>(), View.OnClickListener {
|
||||
|
||||
private val TAG: String = BluetoothFragment::class.java.simpleName
|
||||
private var titleBar: TitleBar? = null
|
||||
private var mCountDownHelper: CountDownButtonHelper? = null
|
||||
|
||||
private lateinit var bluetoothAdapter: BluetoothAdapter
|
||||
private lateinit var bluetoothRecyclerAdapter: BluetoothRecyclerAdapter
|
||||
private var discoveredDevices: MutableList<BluetoothDevice> = mutableListOf()
|
||||
private val bluetoothReceiver = object : BroadcastReceiver() {
|
||||
@SuppressLint("MissingPermission", "NotifyDataSetChanged")
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
val action: String? = intent?.action
|
||||
|
||||
when (action) {
|
||||
BluetoothDevice.ACTION_FOUND -> {
|
||||
val device: BluetoothDevice? = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
|
||||
device?.let {
|
||||
Log.d(TAG, "Discovered device: ${it.name} - ${it.address}")
|
||||
if (!discoveredDevices.contains(it)) {
|
||||
discoveredDevices.add(it)
|
||||
bluetoothRecyclerAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
|
||||
Log.d(TAG, "Bluetooth scan finished, discoveredDevices: $discoveredDevices")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmField
|
||||
@AutoWired(name = KEY_EVENT_DATA_CONDITION)
|
||||
var eventData: String? = null
|
||||
|
||||
override fun initArgs() {
|
||||
XRouter.getInstance().inject(this)
|
||||
}
|
||||
|
||||
override fun viewBindingInflate(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup,
|
||||
): FragmentTasksConditionBluetoothBinding {
|
||||
return FragmentTasksConditionBluetoothBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun initTitle(): TitleBar? {
|
||||
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_bluetooth)
|
||||
return titleBar
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化控件
|
||||
*/
|
||||
override fun initViews() {
|
||||
//测试按钮增加倒计时,避免重复点击
|
||||
mCountDownHelper = CountDownButtonHelper(binding!!.btnStartDiscovery, 12)
|
||||
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
|
||||
override fun onCountDown(time: Int) {
|
||||
binding!!.btnStartDiscovery.text = String.format(getString(R.string.seconds_n), time)
|
||||
}
|
||||
|
||||
override fun onFinished() {
|
||||
requireActivity().unregisterReceiver(bluetoothReceiver)
|
||||
binding!!.btnStartDiscovery.text = getString(R.string.start_discovery)
|
||||
}
|
||||
})
|
||||
|
||||
binding!!.rgBluetoothAction.setOnCheckedChangeListener { _, checkedId ->
|
||||
Log.d(TAG, "rgBluetoothState checkedId:$checkedId")
|
||||
when (checkedId) {
|
||||
R.id.rb_action_state_changed -> {
|
||||
binding!!.layoutBluetoothState.visibility = View.VISIBLE
|
||||
binding!!.layoutDiscoveryFinished.visibility = View.GONE
|
||||
binding!!.layoutDeviceAddress.visibility = View.GONE
|
||||
}
|
||||
|
||||
R.id.rb_action_discovery_finished -> {
|
||||
binding!!.layoutBluetoothState.visibility = View.GONE
|
||||
binding!!.layoutDiscoveryFinished.visibility = View.VISIBLE
|
||||
binding!!.layoutDeviceAddress.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding!!.layoutBluetoothState.visibility = View.GONE
|
||||
binding!!.layoutDiscoveryFinished.visibility = View.GONE
|
||||
binding!!.layoutDeviceAddress.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
checkSetting(true)
|
||||
}
|
||||
|
||||
Log.d(TAG, "initViews eventData:$eventData")
|
||||
if (eventData != null) {
|
||||
val settingVo = Gson().fromJson(eventData, BluetoothSetting::class.java)
|
||||
Log.d(TAG, "initViews settingVo:$settingVo")
|
||||
binding!!.tvDescription.text = settingVo.description
|
||||
binding!!.rgBluetoothAction.check(settingVo.getActionCheckId())
|
||||
binding!!.rgBluetoothState.check(settingVo.getStateCheckId())
|
||||
binding!!.rgDiscoveryResult.check(settingVo.getResultCheckId())
|
||||
binding!!.etDeviceAddress.setText(settingVo.device)
|
||||
} else {
|
||||
binding!!.rgBluetoothAction.check(R.id.rb_action_state_changed)
|
||||
binding!!.rgBluetoothState.check(R.id.rb_state_on)
|
||||
binding!!.rgDiscoveryResult.check(R.id.rb_discovered)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun initListeners() {
|
||||
binding!!.btnStartDiscovery.setOnClickListener(this)
|
||||
binding!!.btnDel.setOnClickListener(this)
|
||||
binding!!.btnSave.setOnClickListener(this)
|
||||
binding!!.rgBluetoothState.setOnCheckedChangeListener { _, _ ->
|
||||
checkSetting(true)
|
||||
}
|
||||
binding!!.rgDiscoveryResult.setOnCheckedChangeListener { _, _ ->
|
||||
checkSetting(true)
|
||||
}
|
||||
binding!!.etDeviceAddress.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
checkSetting(true)
|
||||
}
|
||||
})
|
||||
|
||||
binding!!.recyclerDevices.layoutManager = LinearLayoutManager(requireContext())
|
||||
bluetoothRecyclerAdapter = BluetoothRecyclerAdapter(discoveredDevices, { position ->
|
||||
val device = discoveredDevices[position]
|
||||
binding!!.etDeviceAddress.setText(device.address)
|
||||
})
|
||||
binding!!.recyclerDevices.adapter = bluetoothRecyclerAdapter
|
||||
|
||||
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
if (bluetoothAdapter == null) {
|
||||
XToastUtils.error(getString(R.string.bluetooth_not_supported))
|
||||
return
|
||||
}
|
||||
|
||||
// 启动蓝牙搜索
|
||||
// startBluetoothDiscovery()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
|
||||
if (bluetoothReceiver.isOrderedBroadcast) {
|
||||
requireActivity().unregisterReceiver(bluetoothReceiver)
|
||||
}
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
when (v.id) {
|
||||
|
||||
R.id.btn_start_discovery -> {
|
||||
if (!SettingUtils.enableBluetooth) {
|
||||
MaterialDialog.Builder(requireContext())
|
||||
.iconRes(R.drawable.auto_task_icon_location)
|
||||
.title(R.string.enable_bluetooth)
|
||||
.content(R.string.enable_bluetooth_dialog)
|
||||
.cancelable(false)
|
||||
.positiveText(R.string.lab_yes)
|
||||
.negativeText(R.string.lab_no)
|
||||
.onPositive { _: MaterialDialog?, _: DialogAction? ->
|
||||
XXPermissions.with(this)
|
||||
.permission(Permission.BLUETOOTH_SCAN)
|
||||
.permission(Permission.BLUETOOTH_CONNECT)
|
||||
.permission(Permission.BLUETOOTH_ADVERTISE)
|
||||
.permission(Permission.ACCESS_FINE_LOCATION)
|
||||
.request(object : OnPermissionCallback {
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
startBluetoothDiscovery()
|
||||
Log.d(TAG, "onGranted: permissions=$permissions, all=$all")
|
||||
if (!all) {
|
||||
XToastUtils.warning(getString(R.string.toast_granted_part))
|
||||
}
|
||||
SettingUtils.enableBluetooth = true
|
||||
val serviceIntent = Intent(requireContext(), BluetoothScanService::class.java)
|
||||
serviceIntent.action = ACTION_START
|
||||
requireContext().startService(serviceIntent)
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: List<String>, never: Boolean) {
|
||||
Log.e(TAG, "onDenied: permissions=$permissions, never=$never")
|
||||
if (never) {
|
||||
XToastUtils.error(getString(R.string.toast_denied_never))
|
||||
XXPermissions.startPermissionActivity(requireContext(), permissions)
|
||||
} else {
|
||||
XToastUtils.error(getString(R.string.toast_denied))
|
||||
}
|
||||
}
|
||||
})
|
||||
}.show()
|
||||
return
|
||||
}
|
||||
|
||||
startBluetoothDiscovery()
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_del -> {
|
||||
popToBack()
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_save -> {
|
||||
val settingVo = checkSetting()
|
||||
val intent = Intent()
|
||||
intent.putExtra(KEY_BACK_DESCRIPTION_CONDITION, settingVo.description)
|
||||
intent.putExtra(KEY_BACK_DATA_CONDITION, Gson().toJson(settingVo))
|
||||
setFragmentResult(TASK_CONDITION_BLUETOOTH, intent)
|
||||
popToBack()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
XToastUtils.error(e.message.toString(), 30000)
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "onClick error:$e")
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission", "NotifyDataSetChanged")
|
||||
private fun startBluetoothDiscovery() {
|
||||
try {
|
||||
mCountDownHelper?.start()
|
||||
|
||||
if (bluetoothAdapter.isDiscovering) {
|
||||
bluetoothAdapter.cancelDiscovery()
|
||||
}
|
||||
|
||||
// 注册广播接收器
|
||||
val filter = IntentFilter().apply {
|
||||
addAction(BluetoothDevice.ACTION_FOUND)
|
||||
addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
|
||||
}
|
||||
requireActivity().registerReceiver(bluetoothReceiver, filter)
|
||||
|
||||
discoveredDevices.clear()
|
||||
bluetoothRecyclerAdapter.notifyDataSetChanged()
|
||||
bluetoothAdapter.startDiscovery()
|
||||
} catch (e: Exception) {
|
||||
mCountDownHelper?.finish()
|
||||
XToastUtils.error(e.message.toString(), 30000)
|
||||
Log.e(TAG, "startBluetoothDiscovery error:$e")
|
||||
}
|
||||
}
|
||||
|
||||
//检查设置
|
||||
private fun checkSetting(updateView: Boolean = false): BluetoothSetting {
|
||||
val actionCheckId = binding!!.rgBluetoothAction.checkedRadioButtonId
|
||||
val deviceAddress = binding!!.etDeviceAddress.text.toString().trim()
|
||||
if (actionCheckId != R.id.rb_action_state_changed &&
|
||||
(deviceAddress.isEmpty() || !BluetoothAdapter.checkBluetoothAddress(deviceAddress))
|
||||
) {
|
||||
if (updateView) {
|
||||
binding!!.etDeviceAddress.error = getString(R.string.mac_error)
|
||||
} else {
|
||||
throw Exception(getString(R.string.invalid_bluetooth_mac_address))
|
||||
}
|
||||
} else {
|
||||
binding!!.etDeviceAddress.error = null
|
||||
}
|
||||
|
||||
val stateCheckId = binding!!.rgBluetoothState.checkedRadioButtonId
|
||||
val resultCheckId = binding!!.rgDiscoveryResult.checkedRadioButtonId
|
||||
val settingVo = BluetoothSetting(actionCheckId, stateCheckId, resultCheckId, deviceAddress)
|
||||
if (updateView) {
|
||||
binding!!.tvDescription.text = settingVo.description
|
||||
}
|
||||
|
||||
return settingVo
|
||||
}
|
||||
}
|
@ -72,6 +72,7 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
|
||||
private val yearList: List<String> = (2020..2099).map { String.format("%d", it) }
|
||||
private var selectedYearList = ""
|
||||
|
||||
private val regexNum = Regex("\\d+")
|
||||
private var second = "*"
|
||||
private var minute = "*"
|
||||
private var hour = "*"
|
||||
@ -141,6 +142,11 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
|
||||
initYearInputHelper()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun initListeners() {
|
||||
binding!!.btnTest.setOnClickListener(this)
|
||||
@ -350,47 +356,51 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
|
||||
expression = "$second $minute $hour $day $month $week $year"
|
||||
Log.d(TAG, "afterSecondChanged expression:$expression")
|
||||
CronExpression.validateExpression(expression)
|
||||
|
||||
when {
|
||||
second == "*" -> {
|
||||
binding!!.rbSecondTypeAll.isChecked = true
|
||||
}
|
||||
|
||||
second.contains("/") -> {
|
||||
val secondsArray = second.split("/")
|
||||
binding!!.etSecondIntervalStart.setText(secondsArray.getOrNull(0) ?: "0")
|
||||
binding!!.etSecondInterval.setText(secondsArray.getOrNull(1) ?: "1")
|
||||
binding!!.rbSecondTypeInterval.isChecked = true
|
||||
}
|
||||
|
||||
second.contains(",") -> {
|
||||
val secondsList = restoreMergedItems(second, "%02d")
|
||||
Log.d(TAG, "secondsList:$secondsList")
|
||||
binding!!.flowlayoutMultiSelectSecond.setSelectedItems(secondsList)
|
||||
binding!!.rbSecondTypeAssigned.isChecked = true
|
||||
selectedSecondList = secondsList.joinToString(",")
|
||||
}
|
||||
|
||||
second.contains("-") -> {
|
||||
val secondsArray = second.split("-")
|
||||
binding!!.etSecondCyclicFrom.setText(secondsArray.getOrNull(0) ?: "00")
|
||||
binding!!.etSecondCyclicTo.setText(secondsArray.getOrNull(1) ?: "59")
|
||||
binding!!.rbSecondTypeCyclic.isChecked = true
|
||||
}
|
||||
|
||||
regexNum.matches(second) && secondsList.indexOf(String.format("%02d", second.toInt())) != -1 -> {
|
||||
binding!!.flowlayoutMultiSelectSecond.setSelectedItems(String.format("%02d", second.toInt()))
|
||||
binding!!.rbSecondTypeAssigned.isChecked = true
|
||||
selectedSecondList = second
|
||||
}
|
||||
|
||||
else -> {
|
||||
second = "*"
|
||||
binding!!.etSecond.setText(second)
|
||||
binding!!.rbSecondTypeAll.isChecked = true
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
second = "*"
|
||||
binding!!.etSecond.setText(second)
|
||||
binding!!.rbSecondTypeAll.isChecked = true
|
||||
XToastUtils.error("Cron表达式无效:" + e.message, 30000)
|
||||
return
|
||||
}
|
||||
|
||||
when {
|
||||
second == "*" -> {
|
||||
binding!!.rbSecondTypeAll.isChecked = true
|
||||
}
|
||||
|
||||
second.contains("/") -> {
|
||||
val secondsArray = second.split("/")
|
||||
binding!!.etSecondIntervalStart.setText(secondsArray.getOrNull(0) ?: "0")
|
||||
binding!!.etSecondInterval.setText(secondsArray.getOrNull(1) ?: "1")
|
||||
binding!!.rbSecondTypeInterval.isChecked = true
|
||||
}
|
||||
|
||||
second.contains(",") -> {
|
||||
val secondsList = restoreMergedItems(second, "%02d")
|
||||
Log.d(TAG, "secondsList:$secondsList")
|
||||
binding!!.flowlayoutMultiSelectSecond.setSelectedItems(secondsList)
|
||||
binding!!.rbSecondTypeAssigned.isChecked = true
|
||||
selectedSecondList = secondsList.joinToString(",")
|
||||
}
|
||||
|
||||
second.contains("-") -> {
|
||||
val secondsArray = second.split("-")
|
||||
binding!!.etSecondCyclicFrom.setText(secondsArray.getOrNull(0) ?: "00")
|
||||
binding!!.etSecondCyclicTo.setText(secondsArray.getOrNull(1) ?: "59")
|
||||
binding!!.rbSecondTypeCyclic.isChecked = true
|
||||
}
|
||||
|
||||
secondsList.indexOf(String.format("%02d", second.toInt())) != -1 -> {
|
||||
binding!!.flowlayoutMultiSelectSecond.setSelectedItems(String.format("%02d", second.toInt()))
|
||||
binding!!.rbSecondTypeAssigned.isChecked = true
|
||||
selectedSecondList = second
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding!!.rbSecondTypeAll.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -513,47 +523,51 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
|
||||
expression = "$second $minute $hour $day $month $week $year"
|
||||
Log.d(TAG, "afterMinuteChanged expression:$expression")
|
||||
CronExpression.validateExpression(expression)
|
||||
|
||||
when {
|
||||
minute == "*" -> {
|
||||
binding!!.rbMinuteTypeAll.isChecked = true
|
||||
}
|
||||
|
||||
minute.contains("/") -> {
|
||||
val minutesArray = minute.split("/")
|
||||
binding!!.etMinuteIntervalStart.setText(minutesArray.getOrNull(0) ?: "0")
|
||||
binding!!.etMinuteInterval.setText(minutesArray.getOrNull(1) ?: "1")
|
||||
binding!!.rbMinuteTypeInterval.isChecked = true
|
||||
}
|
||||
|
||||
minute.contains(",") -> {
|
||||
val minutesList = restoreMergedItems(minute, "%02d")
|
||||
Log.d(TAG, "minutesList:$minutesList")
|
||||
binding!!.flowlayoutMultiSelectMinute.setSelectedItems(minutesList)
|
||||
binding!!.rbMinuteTypeAssigned.isChecked = true
|
||||
selectedMinuteList = minutesList.joinToString(",")
|
||||
}
|
||||
|
||||
minute.contains("-") -> {
|
||||
val minutesArray = minute.split("-")
|
||||
binding!!.etMinuteCyclicFrom.setText(minutesArray.getOrNull(0) ?: "00")
|
||||
binding!!.etMinuteCyclicTo.setText(minutesArray.getOrNull(1) ?: "59")
|
||||
binding!!.rbMinuteTypeCyclic.isChecked = true
|
||||
}
|
||||
|
||||
regexNum.matches(minute) && minutesList.indexOf(String.format("%02d", minute.toInt())) != -1 -> {
|
||||
binding!!.flowlayoutMultiSelectMinute.setSelectedItems(String.format("%02d", minute.toInt()))
|
||||
binding!!.rbMinuteTypeAssigned.isChecked = true
|
||||
selectedMinuteList = minute
|
||||
}
|
||||
|
||||
else -> {
|
||||
minute = "*"
|
||||
binding!!.etMinute.setText(minute)
|
||||
binding!!.rbMinuteTypeAll.isChecked = true
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
minute = "*"
|
||||
binding!!.etMinute.setText(minute)
|
||||
binding!!.rbMinuteTypeAll.isChecked = true
|
||||
XToastUtils.error("Cron表达式无效:" + e.message, 30000)
|
||||
return
|
||||
}
|
||||
|
||||
when {
|
||||
minute == "*" -> {
|
||||
binding!!.rbMinuteTypeAll.isChecked = true
|
||||
}
|
||||
|
||||
minute.contains("/") -> {
|
||||
val minutesArray = minute.split("/")
|
||||
binding!!.etMinuteIntervalStart.setText(minutesArray.getOrNull(0) ?: "0")
|
||||
binding!!.etMinuteInterval.setText(minutesArray.getOrNull(1) ?: "1")
|
||||
binding!!.rbMinuteTypeInterval.isChecked = true
|
||||
}
|
||||
|
||||
minute.contains(",") -> {
|
||||
val minutesList = restoreMergedItems(minute, "%02d")
|
||||
Log.d(TAG, "minutesList:$minutesList")
|
||||
binding!!.flowlayoutMultiSelectMinute.setSelectedItems(minutesList)
|
||||
binding!!.rbMinuteTypeAssigned.isChecked = true
|
||||
selectedMinuteList = minutesList.joinToString(",")
|
||||
}
|
||||
|
||||
minute.contains("-") -> {
|
||||
val minutesArray = minute.split("-")
|
||||
binding!!.etMinuteCyclicFrom.setText(minutesArray.getOrNull(0) ?: "00")
|
||||
binding!!.etMinuteCyclicTo.setText(minutesArray.getOrNull(1) ?: "59")
|
||||
binding!!.rbMinuteTypeCyclic.isChecked = true
|
||||
}
|
||||
|
||||
minutesList.indexOf(String.format("%02d", minute.toInt())) != -1 -> {
|
||||
binding!!.flowlayoutMultiSelectMinute.setSelectedItems(String.format("%02d", minute.toInt()))
|
||||
binding!!.rbMinuteTypeAssigned.isChecked = true
|
||||
selectedMinuteList = minute
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding!!.rbMinuteTypeAll.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -676,47 +690,51 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
|
||||
expression = "$second $minute $hour $day $month $week $year"
|
||||
Log.d(TAG, "afterHourChanged expression:$expression")
|
||||
CronExpression.validateExpression(expression)
|
||||
|
||||
when {
|
||||
hour == "*" -> {
|
||||
binding!!.rbHourTypeAll.isChecked = true
|
||||
}
|
||||
|
||||
hour.contains("/") -> {
|
||||
val hoursArray = hour.split("/")
|
||||
binding!!.etHourIntervalStart.setText(hoursArray.getOrNull(0) ?: "0")
|
||||
binding!!.etHourInterval.setText(hoursArray.getOrNull(1) ?: "1")
|
||||
binding!!.rbHourTypeInterval.isChecked = true
|
||||
}
|
||||
|
||||
hour.contains(",") -> {
|
||||
val hoursList = restoreMergedItems(hour, "%02d")
|
||||
Log.d(TAG, "hoursList:$hoursList")
|
||||
binding!!.flowlayoutMultiSelectHour.setSelectedItems(hoursList)
|
||||
binding!!.rbHourTypeAssigned.isChecked = true
|
||||
selectedHourList = hoursList.joinToString(",")
|
||||
}
|
||||
|
||||
hour.contains("-") -> {
|
||||
val hoursArray = hour.split("-")
|
||||
binding!!.etHourCyclicFrom.setText(hoursArray.getOrNull(0) ?: "00")
|
||||
binding!!.etHourCyclicTo.setText(hoursArray.getOrNull(1) ?: "23")
|
||||
binding!!.rbHourTypeCyclic.isChecked = true
|
||||
}
|
||||
|
||||
regexNum.matches(hour) && hoursList.indexOf(String.format("%02d", hour.toInt())) != -1 -> {
|
||||
binding!!.flowlayoutMultiSelectHour.setSelectedItems(String.format("%02d", hour.toInt()))
|
||||
binding!!.rbHourTypeAssigned.isChecked = true
|
||||
selectedHourList = hour
|
||||
}
|
||||
|
||||
else -> {
|
||||
hour = "*"
|
||||
binding!!.etHour.setText(hour)
|
||||
binding!!.rbHourTypeAll.isChecked = true
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
hour = "*"
|
||||
binding!!.etHour.setText(hour)
|
||||
binding!!.rbHourTypeAll.isChecked = true
|
||||
XToastUtils.error("Cron表达式无效:" + e.message, 30000)
|
||||
return
|
||||
}
|
||||
|
||||
when {
|
||||
hour == "*" -> {
|
||||
binding!!.rbHourTypeAll.isChecked = true
|
||||
}
|
||||
|
||||
hour.contains("/") -> {
|
||||
val hoursArray = hour.split("/")
|
||||
binding!!.etHourIntervalStart.setText(hoursArray.getOrNull(0) ?: "0")
|
||||
binding!!.etHourInterval.setText(hoursArray.getOrNull(1) ?: "1")
|
||||
binding!!.rbHourTypeInterval.isChecked = true
|
||||
}
|
||||
|
||||
hour.contains(",") -> {
|
||||
val hoursList = restoreMergedItems(hour, "%02d")
|
||||
Log.d(TAG, "hoursList:$hoursList")
|
||||
binding!!.flowlayoutMultiSelectHour.setSelectedItems(hoursList)
|
||||
binding!!.rbHourTypeAssigned.isChecked = true
|
||||
selectedHourList = hoursList.joinToString(",")
|
||||
}
|
||||
|
||||
hour.contains("-") -> {
|
||||
val hoursArray = hour.split("-")
|
||||
binding!!.etHourCyclicFrom.setText(hoursArray.getOrNull(0) ?: "00")
|
||||
binding!!.etHourCyclicTo.setText(hoursArray.getOrNull(1) ?: "23")
|
||||
binding!!.rbHourTypeCyclic.isChecked = true
|
||||
}
|
||||
|
||||
hoursList.indexOf(String.format("%02d", hour.toInt())) != -1 -> {
|
||||
binding!!.flowlayoutMultiSelectHour.setSelectedItems(String.format("%02d", hour.toInt()))
|
||||
binding!!.rbHourTypeAssigned.isChecked = true
|
||||
selectedHourList = hour
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding!!.rbHourTypeAll.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -866,66 +884,70 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
|
||||
expression = "$second $minute $hour $day $month $week $year"
|
||||
Log.d(TAG, "afterDayChanged expression:$expression")
|
||||
CronExpression.validateExpression(expression)
|
||||
|
||||
when {
|
||||
day == "*" -> {
|
||||
binding!!.rbDayTypeAll.isChecked = true
|
||||
}
|
||||
|
||||
day == "?" -> {
|
||||
binding!!.rbDayTypeNotAssigned.isChecked = true
|
||||
}
|
||||
|
||||
day == "L" -> {
|
||||
binding!!.rbDayTypeLastDayOfMonth.isChecked = true
|
||||
}
|
||||
|
||||
day == "LW" -> {
|
||||
binding!!.rbDayTypeLastDayOfMonthRecentDay.isChecked = true
|
||||
return
|
||||
}
|
||||
|
||||
day.endsWith("W") -> {
|
||||
binding!!.rbDayTypeRecentDay.isChecked = true
|
||||
binding!!.etRecentDay.setText(day.removeSuffix("W"))
|
||||
return
|
||||
}
|
||||
|
||||
day.contains("/") -> {
|
||||
val dayArray = day.split("/")
|
||||
binding!!.etDayIntervalStart.setText(dayArray.getOrNull(0) ?: "0")
|
||||
binding!!.etDayInterval.setText(dayArray.getOrNull(1) ?: "1")
|
||||
binding!!.rbDayTypeInterval.isChecked = true
|
||||
}
|
||||
|
||||
day.contains(",") -> {
|
||||
val dayList = restoreMergedItems(day, "%d")
|
||||
Log.d(TAG, "dayList:$dayList")
|
||||
binding!!.flowlayoutMultiSelectDay.setSelectedItems(dayList)
|
||||
binding!!.rbDayTypeAssigned.isChecked = true
|
||||
selectedDayList = dayList.joinToString(",")
|
||||
}
|
||||
|
||||
day.contains("-") -> {
|
||||
val dayArray = day.split("-")
|
||||
binding!!.etDayCyclicFrom.setText(dayArray.getOrNull(0) ?: "1")
|
||||
binding!!.etDayCyclicTo.setText(dayArray.getOrNull(1) ?: "31")
|
||||
binding!!.rbDayTypeCyclic.isChecked = true
|
||||
}
|
||||
|
||||
regexNum.matches(day) && dayList.indexOf(String.format("%d", day.toInt())) != -1 -> {
|
||||
binding!!.flowlayoutMultiSelectDay.setSelectedItems(String.format("%d", day.toInt()))
|
||||
binding!!.rbDayTypeAssigned.isChecked = true
|
||||
selectedDayList = day
|
||||
}
|
||||
|
||||
else -> {
|
||||
day = "*"
|
||||
binding!!.etDay.setText(day)
|
||||
binding!!.rbDayTypeAll.isChecked = true
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
day = "*"
|
||||
binding!!.etDay.setText(day)
|
||||
binding!!.rbDayTypeAll.isChecked = true
|
||||
XToastUtils.error("Cron表达式无效:" + e.message, 30000)
|
||||
return
|
||||
}
|
||||
|
||||
when {
|
||||
day == "*" -> {
|
||||
binding!!.rbDayTypeAll.isChecked = true
|
||||
}
|
||||
|
||||
day == "?" -> {
|
||||
binding!!.rbDayTypeNotAssigned.isChecked = true
|
||||
}
|
||||
|
||||
day == "L" -> {
|
||||
binding!!.rbDayTypeLastDayOfMonth.isChecked = true
|
||||
}
|
||||
|
||||
day == "LW" -> {
|
||||
binding!!.rbDayTypeLastDayOfMonthRecentDay.isChecked = true
|
||||
return
|
||||
}
|
||||
|
||||
day.endsWith("W") -> {
|
||||
binding!!.rbDayTypeRecentDay.isChecked = true
|
||||
binding!!.etRecentDay.setText(day.removeSuffix("W"))
|
||||
return
|
||||
}
|
||||
|
||||
day.contains("/") -> {
|
||||
val dayArray = day.split("/")
|
||||
binding!!.etDayIntervalStart.setText(dayArray.getOrNull(0) ?: "0")
|
||||
binding!!.etDayInterval.setText(dayArray.getOrNull(1) ?: "1")
|
||||
binding!!.rbDayTypeInterval.isChecked = true
|
||||
}
|
||||
|
||||
day.contains(",") -> {
|
||||
val dayList = restoreMergedItems(day, "%d")
|
||||
Log.d(TAG, "dayList:$dayList")
|
||||
binding!!.flowlayoutMultiSelectDay.setSelectedItems(dayList)
|
||||
binding!!.rbDayTypeAssigned.isChecked = true
|
||||
selectedDayList = dayList.joinToString(",")
|
||||
}
|
||||
|
||||
day.contains("-") -> {
|
||||
val dayArray = day.split("-")
|
||||
binding!!.etDayCyclicFrom.setText(dayArray.getOrNull(0) ?: "1")
|
||||
binding!!.etDayCyclicTo.setText(dayArray.getOrNull(1) ?: "31")
|
||||
binding!!.rbDayTypeCyclic.isChecked = true
|
||||
}
|
||||
|
||||
dayList.indexOf(String.format("%d", day.toInt())) != -1 -> {
|
||||
binding!!.flowlayoutMultiSelectDay.setSelectedItems(String.format("%d", day.toInt()))
|
||||
binding!!.rbDayTypeAssigned.isChecked = true
|
||||
selectedDayList = day
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding!!.rbDayTypeAll.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1048,47 +1070,51 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
|
||||
expression = "$second $minute $hour $day $month $week $year"
|
||||
Log.d(TAG, "afterMonthChanged expression:$expression")
|
||||
CronExpression.validateExpression(expression)
|
||||
|
||||
when {
|
||||
month == "*" -> {
|
||||
binding!!.rbMonthTypeAll.isChecked = true
|
||||
}
|
||||
|
||||
month.contains("/") -> {
|
||||
val monthArray = month.split("/")
|
||||
binding!!.etMonthIntervalStart.setText(monthArray.getOrNull(0) ?: "0")
|
||||
binding!!.etMonthInterval.setText(monthArray.getOrNull(1) ?: "1")
|
||||
binding!!.rbMonthTypeInterval.isChecked = true
|
||||
}
|
||||
|
||||
month.contains(",") -> {
|
||||
val monthList = restoreMergedItems(month, "%d")
|
||||
Log.d(TAG, "monthList:$monthList")
|
||||
binding!!.flowlayoutMultiSelectMonth.setSelectedItems(monthList)
|
||||
binding!!.rbMonthTypeAssigned.isChecked = true
|
||||
selectedMonthList = monthList.joinToString(",")
|
||||
}
|
||||
|
||||
month.contains("-") -> {
|
||||
val monthArray = month.split("-")
|
||||
binding!!.etMonthCyclicFrom.setText(monthArray.getOrNull(0) ?: "1")
|
||||
binding!!.etMonthCyclicTo.setText(monthArray.getOrNull(1) ?: "31")
|
||||
binding!!.rbMonthTypeCyclic.isChecked = true
|
||||
}
|
||||
|
||||
monthList.indexOf(month) != -1 -> {
|
||||
binding!!.flowlayoutMultiSelectMonth.setSelectedItems(month)
|
||||
binding!!.rbMonthTypeAssigned.isChecked = true
|
||||
selectedMonthList = month
|
||||
}
|
||||
|
||||
else -> {
|
||||
month = "*"
|
||||
binding!!.etMonth.setText(month)
|
||||
binding!!.rbMonthTypeAll.isChecked = true
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
month = "*"
|
||||
binding!!.etMonth.setText(month)
|
||||
binding!!.rbMonthTypeAll.isChecked = true
|
||||
XToastUtils.error("Cron表达式无效:" + e.message, 30000)
|
||||
return
|
||||
}
|
||||
|
||||
when {
|
||||
month == "*" -> {
|
||||
binding!!.rbMonthTypeAll.isChecked = true
|
||||
}
|
||||
|
||||
month.contains("/") -> {
|
||||
val monthArray = month.split("/")
|
||||
binding!!.etMonthIntervalStart.setText(monthArray.getOrNull(0) ?: "0")
|
||||
binding!!.etMonthInterval.setText(monthArray.getOrNull(1) ?: "1")
|
||||
binding!!.rbMonthTypeInterval.isChecked = true
|
||||
}
|
||||
|
||||
month.contains(",") -> {
|
||||
val monthList = restoreMergedItems(month, "%d")
|
||||
Log.d(TAG, "monthList:$monthList")
|
||||
binding!!.flowlayoutMultiSelectMonth.setSelectedItems(monthList)
|
||||
binding!!.rbMonthTypeAssigned.isChecked = true
|
||||
selectedMonthList = monthList.joinToString(",")
|
||||
}
|
||||
|
||||
month.contains("-") -> {
|
||||
val monthArray = month.split("-")
|
||||
binding!!.etMonthCyclicFrom.setText(monthArray.getOrNull(0) ?: "1")
|
||||
binding!!.etMonthCyclicTo.setText(monthArray.getOrNull(1) ?: "31")
|
||||
binding!!.rbMonthTypeCyclic.isChecked = true
|
||||
}
|
||||
|
||||
monthList.indexOf(month) != -1 -> {
|
||||
binding!!.flowlayoutMultiSelectMonth.setSelectedItems(month)
|
||||
binding!!.rbMonthTypeAssigned.isChecked = true
|
||||
selectedMonthList = month
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding!!.rbMonthTypeAll.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1247,56 +1273,60 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
|
||||
expression = "$second $minute $hour $day $month $week $year"
|
||||
Log.d(TAG, "afterWeekChanged expression:$expression")
|
||||
CronExpression.validateExpression(expression)
|
||||
|
||||
when {
|
||||
week == "*" -> {
|
||||
binding!!.rbWeekTypeAll.isChecked = true
|
||||
}
|
||||
|
||||
week == "?" -> {
|
||||
binding!!.rbWeekTypeNotAssigned.isChecked = true
|
||||
}
|
||||
|
||||
week.contains(",") -> {
|
||||
val weekList = restoreMergedItems(week, "%d")
|
||||
Log.d(TAG, "weekList:$weekList")
|
||||
binding!!.flowlayoutMultiSelectWeek.setSelectedItems(weekList)
|
||||
binding!!.rbWeekTypeAssigned.isChecked = true
|
||||
selectedWeekList = weekList.joinToString(",")
|
||||
}
|
||||
|
||||
week.contains("-") -> {
|
||||
val weekArray = week.split("-")
|
||||
binding!!.etWeekCyclicFrom.setText(weekArray.getOrNull(0) ?: "1")
|
||||
binding!!.etWeekCyclicTo.setText(weekArray.getOrNull(1) ?: "31")
|
||||
binding!!.rbWeekTypeCyclic.isChecked = true
|
||||
}
|
||||
|
||||
week.contains("#") -> {
|
||||
val weekArray = week.split("#")
|
||||
binding!!.etWhichWeekOfMonth.setText(weekArray.getOrNull(0) ?: "1")
|
||||
binding!!.etWhichDayOfWeek.setText(weekArray.getOrNull(1) ?: "1")
|
||||
binding!!.rbWeekTypeWeeksOfWeek.isChecked = true
|
||||
}
|
||||
|
||||
weekList.indexOf(week) != -1 -> {
|
||||
binding!!.flowlayoutMultiSelectWeek.setSelectedItems(week)
|
||||
binding!!.rbWeekTypeAssigned.isChecked = true
|
||||
selectedWeekList = week
|
||||
}
|
||||
|
||||
week.endsWith("L") -> {
|
||||
binding!!.rbWeekTypeLastWeekOfMonth.isChecked = true
|
||||
binding!!.etLastWeekOfMonth.setText(week.removeSuffix("L"))
|
||||
}
|
||||
|
||||
else -> {
|
||||
week = "*"
|
||||
binding!!.etWeek.setText(week)
|
||||
binding!!.rbWeekTypeAll.isChecked = true
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
week = "*"
|
||||
binding!!.etWeek.setText(week)
|
||||
binding!!.rbWeekTypeAll.isChecked = true
|
||||
XToastUtils.error("Cron表达式无效:" + e.message, 30000)
|
||||
return
|
||||
}
|
||||
|
||||
when {
|
||||
week == "*" -> {
|
||||
binding!!.rbWeekTypeAll.isChecked = true
|
||||
}
|
||||
|
||||
week == "?" -> {
|
||||
binding!!.rbWeekTypeNotAssigned.isChecked = true
|
||||
}
|
||||
|
||||
week.contains(",") -> {
|
||||
val weekList = restoreMergedItems(week, "%d")
|
||||
Log.d(TAG, "weekList:$weekList")
|
||||
binding!!.flowlayoutMultiSelectWeek.setSelectedItems(weekList)
|
||||
binding!!.rbWeekTypeAssigned.isChecked = true
|
||||
selectedWeekList = weekList.joinToString(",")
|
||||
}
|
||||
|
||||
week.contains("-") -> {
|
||||
val weekArray = week.split("-")
|
||||
binding!!.etWeekCyclicFrom.setText(weekArray.getOrNull(0) ?: "1")
|
||||
binding!!.etWeekCyclicTo.setText(weekArray.getOrNull(1) ?: "31")
|
||||
binding!!.rbWeekTypeCyclic.isChecked = true
|
||||
}
|
||||
|
||||
week.contains("#") -> {
|
||||
val weekArray = week.split("#")
|
||||
binding!!.etWhichWeekOfMonth.setText(weekArray.getOrNull(0) ?: "1")
|
||||
binding!!.etWhichDayOfWeek.setText(weekArray.getOrNull(1) ?: "1")
|
||||
binding!!.rbWeekTypeWeeksOfWeek.isChecked = true
|
||||
}
|
||||
|
||||
weekList.indexOf(week) != -1 -> {
|
||||
binding!!.flowlayoutMultiSelectWeek.setSelectedItems(week)
|
||||
binding!!.rbWeekTypeAssigned.isChecked = true
|
||||
selectedWeekList = week
|
||||
}
|
||||
|
||||
week.endsWith("L") -> {
|
||||
binding!!.rbWeekTypeLastWeekOfMonth.isChecked = true
|
||||
binding!!.etLastWeekOfMonth.setText(week.removeSuffix("L"))
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding!!.rbWeekTypeAll.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1419,51 +1449,55 @@ class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.On
|
||||
expression = "$second $minute $hour $day $month $week $year"
|
||||
Log.d(TAG, "afterYearChanged expression:$expression")
|
||||
CronExpression.validateExpression(expression)
|
||||
|
||||
when {
|
||||
year == "*" -> {
|
||||
binding!!.rbYearTypeAll.isChecked = true
|
||||
}
|
||||
|
||||
year == "?" -> {
|
||||
binding!!.rbYearTypeNotAssigned.isChecked = true
|
||||
}
|
||||
|
||||
year.contains("/") -> {
|
||||
val yearArray = year.split("/")
|
||||
binding!!.etYearIntervalStart.setText(yearArray.getOrNull(0) ?: "2023")
|
||||
binding!!.etYearInterval.setText(yearArray.getOrNull(1) ?: "2")
|
||||
binding!!.rbYearTypeInterval.isChecked = true
|
||||
}
|
||||
|
||||
year.contains(",") -> {
|
||||
val yearList = restoreMergedItems(year, "%d")
|
||||
Log.d(TAG, "yearList:$yearList")
|
||||
binding!!.flowlayoutMultiSelectYear.setSelectedItems(yearList)
|
||||
binding!!.rbYearTypeAssigned.isChecked = true
|
||||
selectedYearList = yearList.joinToString(",")
|
||||
}
|
||||
|
||||
year.contains("-") -> {
|
||||
val yearArray = year.split("-")
|
||||
binding!!.etYearCyclicFrom.setText(yearArray.getOrNull(0) ?: "1970")
|
||||
binding!!.etYearCyclicTo.setText(yearArray.getOrNull(1) ?: "2099")
|
||||
binding!!.rbYearTypeCyclic.isChecked = true
|
||||
}
|
||||
|
||||
yearList.indexOf(year) != -1 -> {
|
||||
binding!!.flowlayoutMultiSelectYear.setSelectedItems(year)
|
||||
binding!!.rbYearTypeAssigned.isChecked = true
|
||||
selectedYearList = year
|
||||
}
|
||||
|
||||
else -> {
|
||||
year = "*"
|
||||
binding!!.etYear.setText(year)
|
||||
binding!!.rbYearTypeAll.isChecked = true
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
year = "*"
|
||||
binding!!.etYear.setText(year)
|
||||
binding!!.rbYearTypeAll.isChecked = true
|
||||
XToastUtils.error("Cron表达式无效:" + e.message, 30000)
|
||||
return
|
||||
}
|
||||
|
||||
when {
|
||||
year == "*" -> {
|
||||
binding!!.rbYearTypeAll.isChecked = true
|
||||
}
|
||||
|
||||
year == "?" -> {
|
||||
binding!!.rbYearTypeNotAssigned.isChecked = true
|
||||
}
|
||||
|
||||
year.contains("/") -> {
|
||||
val yearArray = year.split("/")
|
||||
binding!!.etYearIntervalStart.setText(yearArray.getOrNull(0) ?: "2023")
|
||||
binding!!.etYearInterval.setText(yearArray.getOrNull(1) ?: "2")
|
||||
binding!!.rbYearTypeInterval.isChecked = true
|
||||
}
|
||||
|
||||
year.contains(",") -> {
|
||||
val yearList = restoreMergedItems(year, "%d")
|
||||
Log.d(TAG, "yearList:$yearList")
|
||||
binding!!.flowlayoutMultiSelectYear.setSelectedItems(yearList)
|
||||
binding!!.rbYearTypeAssigned.isChecked = true
|
||||
selectedYearList = yearList.joinToString(",")
|
||||
}
|
||||
|
||||
year.contains("-") -> {
|
||||
val yearArray = year.split("-")
|
||||
binding!!.etYearCyclicFrom.setText(yearArray.getOrNull(0) ?: "1970")
|
||||
binding!!.etYearCyclicTo.setText(yearArray.getOrNull(1) ?: "2099")
|
||||
binding!!.rbYearTypeCyclic.isChecked = true
|
||||
}
|
||||
|
||||
yearList.indexOf(year) != -1 -> {
|
||||
binding!!.flowlayoutMultiSelectYear.setSelectedItems(year)
|
||||
binding!!.rbYearTypeAssigned.isChecked = true
|
||||
selectedYearList = year
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding!!.rbYearTypeAll.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,6 @@ package com.idormy.sms.forwarder.fragment.condition
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -14,6 +12,7 @@ import com.idormy.sms.forwarder.core.BaseFragment
|
||||
import com.idormy.sms.forwarder.databinding.FragmentTasksConditionLeaveAddressBinding
|
||||
import com.idormy.sms.forwarder.entity.condition.LocationSetting
|
||||
import com.idormy.sms.forwarder.service.LocationService
|
||||
import com.idormy.sms.forwarder.utils.ACTION_START
|
||||
import com.idormy.sms.forwarder.utils.HttpServerUtils
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_CONDITION
|
||||
@ -98,13 +97,11 @@ class LeaveAddressFragment : BaseFragment<FragmentTasksConditionLeaveAddressBind
|
||||
binding!!.btnDel.setOnClickListener(this)
|
||||
binding!!.btnSave.setOnClickListener(this)
|
||||
binding!!.btnCurrentCoordinates.setOnClickListener(this)
|
||||
binding!!.etLongitude.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
binding!!.etLongitude.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (!hasFocus) {
|
||||
try {
|
||||
val changedText = s.toString()
|
||||
if (changedText.isEmpty()) {
|
||||
val inputText = binding!!.etLongitude.text.toString()
|
||||
if (inputText.isEmpty()) {
|
||||
binding!!.etLongitude.setText("0")
|
||||
binding!!.etLongitude.setSelection(binding!!.etLongitude.text.length) // 将光标移至文本末尾
|
||||
} else {
|
||||
@ -112,17 +109,15 @@ class LeaveAddressFragment : BaseFragment<FragmentTasksConditionLeaveAddressBind
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "afterTextChanged error:$e")
|
||||
Log.e(TAG, "etLongitude error:$e")
|
||||
}
|
||||
}
|
||||
})
|
||||
binding!!.etLatitude.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
}
|
||||
binding!!.etLatitude.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (!hasFocus) {
|
||||
try {
|
||||
val changedText = s.toString()
|
||||
if (changedText.isEmpty()) {
|
||||
val inputText = binding!!.etLatitude.text.toString()
|
||||
if (inputText.isEmpty()) {
|
||||
binding!!.etLatitude.setText("0")
|
||||
binding!!.etLatitude.setSelection(binding!!.etLatitude.text.length) // 将光标移至文本末尾
|
||||
} else {
|
||||
@ -130,17 +125,15 @@ class LeaveAddressFragment : BaseFragment<FragmentTasksConditionLeaveAddressBind
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "afterTextChanged error:$e")
|
||||
Log.e(TAG, "etLatitude error:$e")
|
||||
}
|
||||
}
|
||||
})
|
||||
binding!!.etDistance.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
}
|
||||
binding!!.etDistance.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (!hasFocus) {
|
||||
try {
|
||||
val changedText = s.toString()
|
||||
if (changedText.isEmpty()) {
|
||||
val inputText = binding!!.etDistance.text.toString()
|
||||
if (inputText.isEmpty()) {
|
||||
binding!!.etDistance.setText("1")
|
||||
binding!!.etDistance.setSelection(binding!!.etDistance.text.length) // 将光标移至文本末尾
|
||||
} else {
|
||||
@ -148,22 +141,20 @@ class LeaveAddressFragment : BaseFragment<FragmentTasksConditionLeaveAddressBind
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "afterTextChanged error:$e")
|
||||
Log.e(TAG, "etDistance error:$e")
|
||||
}
|
||||
}
|
||||
})
|
||||
binding!!.etAddress.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
}
|
||||
binding!!.etAddress.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (!hasFocus) {
|
||||
try {
|
||||
checkSetting(true)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "afterTextChanged error:$e")
|
||||
Log.e(TAG, "etAddress error:$e")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@SingleClick
|
||||
@ -172,12 +163,19 @@ class LeaveAddressFragment : BaseFragment<FragmentTasksConditionLeaveAddressBind
|
||||
when (v.id) {
|
||||
R.id.btn_current_coordinates -> {
|
||||
if (!App.LocationClient.isStarted()) {
|
||||
MaterialDialog.Builder(requireContext()).iconRes(R.drawable.auto_task_icon_location).title(R.string.enable_location).content(R.string.enable_location_dialog).cancelable(false).positiveText(R.string.lab_yes).negativeText(R.string.lab_no).onPositive { _: MaterialDialog?, _: DialogAction? ->
|
||||
SettingUtils.enableLocation = true
|
||||
val serviceIntent = Intent(requireContext(), LocationService::class.java)
|
||||
serviceIntent.action = "START"
|
||||
requireContext().startService(serviceIntent)
|
||||
}.show()
|
||||
MaterialDialog.Builder(requireContext())
|
||||
.iconRes(R.drawable.auto_task_icon_location)
|
||||
.title(R.string.enable_location)
|
||||
.content(R.string.enable_location_dialog)
|
||||
.cancelable(false)
|
||||
.positiveText(R.string.lab_yes)
|
||||
.negativeText(R.string.lab_no)
|
||||
.onPositive { _: MaterialDialog?, _: DialogAction? ->
|
||||
SettingUtils.enableLocation = true
|
||||
val serviceIntent = Intent(requireContext(), LocationService::class.java)
|
||||
serviceIntent.action = ACTION_START
|
||||
requireContext().startService(serviceIntent)
|
||||
}.show()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -54,12 +54,15 @@ class LockScreenFragment : BaseFragment<FragmentTasksConditionLockScreenBinding?
|
||||
*/
|
||||
override fun initViews() {
|
||||
binding!!.rgAction.setOnCheckedChangeListener { _, checkedId ->
|
||||
if (checkedId == R.id.rb_action_screen_off) {
|
||||
binding!!.xsbTimeAfterScreenOff.visibility = View.VISIBLE
|
||||
binding!!.xsbTimeAfterScreenOn.visibility = View.GONE
|
||||
} else {
|
||||
binding!!.xsbTimeAfterScreenOff.visibility = View.GONE
|
||||
binding!!.xsbTimeAfterScreenOn.visibility = View.VISIBLE
|
||||
binding!!.xsbTimeAfterScreenOff.visibility = View.GONE
|
||||
binding!!.xsbTimeAfterScreenLocked.visibility = View.GONE
|
||||
binding!!.xsbTimeAfterScreenOn.visibility = View.GONE
|
||||
binding!!.xsbTimeAfterScreenUnlocked.visibility = View.GONE
|
||||
when (checkedId) {
|
||||
R.id.rb_action_screen_on -> binding!!.xsbTimeAfterScreenOn.visibility = View.VISIBLE
|
||||
R.id.rb_action_screen_unlocked -> binding!!.xsbTimeAfterScreenUnlocked.visibility = View.VISIBLE
|
||||
R.id.rb_action_screen_locked -> binding!!.xsbTimeAfterScreenLocked.visibility = View.VISIBLE
|
||||
else -> binding!!.xsbTimeAfterScreenOff.visibility = View.VISIBLE
|
||||
}
|
||||
checkSetting(true)
|
||||
}
|
||||
@ -71,10 +74,15 @@ class LockScreenFragment : BaseFragment<FragmentTasksConditionLockScreenBinding?
|
||||
binding!!.tvDescription.text = settingVo.description
|
||||
binding!!.xsbTimeAfterScreenOff.setDefaultValue(settingVo.timeAfterScreenOff)
|
||||
binding!!.xsbTimeAfterScreenOn.setDefaultValue(settingVo.timeAfterScreenOn)
|
||||
binding!!.xsbTimeAfterScreenLocked.setDefaultValue(settingVo.timeAfterScreenLocked)
|
||||
binding!!.xsbTimeAfterScreenUnlocked.setDefaultValue(settingVo.timeAfterScreenUnlocked)
|
||||
binding!!.rgAction.check(settingVo.getActionCheckId())
|
||||
binding!!.sbCheckAgain.isChecked = settingVo.checkAgain
|
||||
} else {
|
||||
binding!!.xsbTimeAfterScreenOff.setDefaultValue(0)
|
||||
binding!!.xsbTimeAfterScreenOn.setDefaultValue(0)
|
||||
binding!!.xsbTimeAfterScreenLocked.setDefaultValue(0)
|
||||
binding!!.xsbTimeAfterScreenUnlocked.setDefaultValue(0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,6 +96,15 @@ class LockScreenFragment : BaseFragment<FragmentTasksConditionLockScreenBinding?
|
||||
binding!!.xsbTimeAfterScreenOn.setOnSeekBarListener { _, _ ->
|
||||
checkSetting(true)
|
||||
}
|
||||
binding!!.xsbTimeAfterScreenLocked.setOnSeekBarListener { _, _ ->
|
||||
checkSetting(true)
|
||||
}
|
||||
binding!!.xsbTimeAfterScreenUnlocked.setOnSeekBarListener { _, _ ->
|
||||
checkSetting(true)
|
||||
}
|
||||
binding!!.sbCheckAgain.setOnCheckedChangeListener { _, _ ->
|
||||
checkSetting(true)
|
||||
}
|
||||
}
|
||||
|
||||
@SingleClick
|
||||
@ -123,7 +140,10 @@ class LockScreenFragment : BaseFragment<FragmentTasksConditionLockScreenBinding?
|
||||
val actionCheckId = binding!!.rgAction.checkedRadioButtonId
|
||||
val timeAfterScreenOff = binding!!.xsbTimeAfterScreenOff.selectedNumber
|
||||
val timeAfterScreenOn = binding!!.xsbTimeAfterScreenOn.selectedNumber
|
||||
val settingVo = LockScreenSetting(actionCheckId, timeAfterScreenOff, timeAfterScreenOn)
|
||||
val timeAferScreenLocked = binding!!.xsbTimeAfterScreenLocked.selectedNumber
|
||||
val timeAfterScreenUnlocked = binding!!.xsbTimeAfterScreenUnlocked.selectedNumber
|
||||
val checkAgain = binding!!.sbCheckAgain.isChecked
|
||||
val settingVo = LockScreenSetting(actionCheckId, timeAfterScreenOff, timeAfterScreenOn, timeAferScreenLocked, timeAfterScreenUnlocked, checkAgain)
|
||||
|
||||
if (updateView) {
|
||||
binding!!.tvDescription.text = settingVo.description
|
||||
|
@ -0,0 +1,484 @@
|
||||
package com.idormy.sms.forwarder.fragment.condition
|
||||
|
||||
import android.content.Intent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.EditText
|
||||
import android.widget.RadioGroup
|
||||
import android.widget.TextView
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.google.gson.Gson
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.App.Companion.CALL_TYPE_MAP
|
||||
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.core.BaseFragment
|
||||
import com.idormy.sms.forwarder.database.entity.Rule
|
||||
import com.idormy.sms.forwarder.databinding.FragmentTasksConditionMsgBinding
|
||||
import com.idormy.sms.forwarder.entity.MsgInfo
|
||||
import com.idormy.sms.forwarder.utils.CHECK_CONTAIN
|
||||
import com.idormy.sms.forwarder.utils.CHECK_END_WITH
|
||||
import com.idormy.sms.forwarder.utils.CHECK_IS
|
||||
import com.idormy.sms.forwarder.utils.CHECK_NOT_CONTAIN
|
||||
import com.idormy.sms.forwarder.utils.CHECK_REGEX
|
||||
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_1
|
||||
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_2
|
||||
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_ALL
|
||||
import com.idormy.sms.forwarder.utils.CHECK_START_WITH
|
||||
import com.idormy.sms.forwarder.utils.CommonUtils
|
||||
import com.idormy.sms.forwarder.utils.EVENT_LOAD_APP_LIST
|
||||
import com.idormy.sms.forwarder.utils.FILED_CALL_TYPE
|
||||
import com.idormy.sms.forwarder.utils.FILED_INFORM_CONTENT
|
||||
import com.idormy.sms.forwarder.utils.FILED_MSG_CONTENT
|
||||
import com.idormy.sms.forwarder.utils.FILED_MULTI_MATCH
|
||||
import com.idormy.sms.forwarder.utils.FILED_PACKAGE_NAME
|
||||
import com.idormy.sms.forwarder.utils.FILED_PHONE_NUM
|
||||
import com.idormy.sms.forwarder.utils.FILED_TRANSPOND_ALL
|
||||
import com.idormy.sms.forwarder.utils.FILED_UID
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_CONDITION
|
||||
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_CONDITION
|
||||
import com.idormy.sms.forwarder.utils.KEY_EVENT_PARAMS_CONDITION
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.PhoneUtils
|
||||
import com.idormy.sms.forwarder.utils.STATUS_ON
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.idormy.sms.forwarder.utils.TASK_CONDITION_APP
|
||||
import com.idormy.sms.forwarder.utils.TASK_CONDITION_CALL
|
||||
import com.idormy.sms.forwarder.utils.TASK_CONDITION_SMS
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.idormy.sms.forwarder.workers.LoadAppListWorker
|
||||
import com.jeremyliao.liveeventbus.LiveEventBus
|
||||
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.widget.actionbar.TitleBar
|
||||
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
|
||||
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
|
||||
import com.xuexiang.xui.widget.spinner.materialspinner.MaterialSpinner
|
||||
import com.xuexiang.xutil.XUtil
|
||||
import com.xuexiang.xutil.resource.ResUtils.getColors
|
||||
import java.util.Date
|
||||
|
||||
@Page(name = "Msg")
|
||||
@Suppress("PrivatePropertyName")
|
||||
class MsgFragment : BaseFragment<FragmentTasksConditionMsgBinding?>(), View.OnClickListener {
|
||||
|
||||
private val TAG: String = MsgFragment::class.java.simpleName
|
||||
private var titleBar: TitleBar? = null
|
||||
|
||||
private var callType = 1
|
||||
private var callTypeIndex = 0
|
||||
private var resultCode: Int = TASK_CONDITION_SMS
|
||||
|
||||
//已安装App信息列表
|
||||
private val appListSpinnerList = ArrayList<AppListAdapterItem>()
|
||||
private lateinit var appListSpinnerAdapter: AppListSpinnerAdapter<*>
|
||||
private val appListObserver = Observer { it: String ->
|
||||
Log.d(TAG, "EVENT_LOAD_APP_LIST: $it")
|
||||
initAppSpinner()
|
||||
}
|
||||
|
||||
@JvmField
|
||||
@AutoWired(name = KEY_EVENT_PARAMS_CONDITION)
|
||||
var ruleType: String = "sms"
|
||||
|
||||
@JvmField
|
||||
@AutoWired(name = KEY_EVENT_DATA_CONDITION)
|
||||
var eventData: String? = null
|
||||
|
||||
override fun initArgs() {
|
||||
XRouter.getInstance().inject(this)
|
||||
}
|
||||
|
||||
override fun viewBindingInflate(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup,
|
||||
): FragmentTasksConditionMsgBinding {
|
||||
return FragmentTasksConditionMsgBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun initTitle(): TitleBar? {
|
||||
titleBar = super.initTitle()!!.setImmersive(false)
|
||||
return titleBar
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化控件
|
||||
*/
|
||||
override fun initViews() {
|
||||
when (ruleType) {
|
||||
"app" -> {
|
||||
resultCode = TASK_CONDITION_APP
|
||||
titleBar?.setTitle(R.string.task_app)
|
||||
binding!!.ivTaskApp.visibility = View.VISIBLE
|
||||
binding!!.layoutSimSlot.visibility = View.GONE
|
||||
binding!!.rbPhone.visibility = View.GONE
|
||||
binding!!.rbCallType.visibility = View.GONE
|
||||
binding!!.rbContent.visibility = View.GONE
|
||||
binding!!.tvMuRuleTips.setText(R.string.mu_rule_app_tips)
|
||||
//初始化APP下拉列表
|
||||
initAppSpinner()
|
||||
//监听已安装App信息列表加载完成事件
|
||||
LiveEventBus.get(EVENT_LOAD_APP_LIST, String::class.java).observeStickyForever(appListObserver)
|
||||
}
|
||||
|
||||
"call" -> {
|
||||
resultCode = TASK_CONDITION_CALL
|
||||
titleBar?.setTitle(R.string.task_call)
|
||||
binding!!.ivTaskCall.visibility = View.VISIBLE
|
||||
binding!!.rbContent.visibility = View.GONE
|
||||
binding!!.rbPackageName.visibility = View.GONE
|
||||
binding!!.rbUid.visibility = View.GONE
|
||||
binding!!.rbInformContent.visibility = View.GONE
|
||||
binding!!.tvMuRuleTips.setText(R.string.mu_rule_call_tips)
|
||||
|
||||
//通话类型:1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
|
||||
binding!!.spCallType.setItems(CALL_TYPE_MAP.values.toList())
|
||||
binding!!.spCallType.setOnItemSelectedListener { _: MaterialSpinner?, _: Int, _: Long, item: Any ->
|
||||
CALL_TYPE_MAP.forEach {
|
||||
if (it.value == item) callType = it.key.toInt()
|
||||
}
|
||||
}
|
||||
binding!!.spCallType.setOnNothingSelectedListener {
|
||||
callType = 1
|
||||
callTypeIndex = 0
|
||||
binding!!.spCallType.selectedIndex = callTypeIndex
|
||||
}
|
||||
binding!!.spCallType.selectedIndex = callTypeIndex
|
||||
}
|
||||
|
||||
else -> {
|
||||
titleBar?.setTitle(R.string.task_sms)
|
||||
binding!!.ivTaskSms.visibility = View.VISIBLE
|
||||
binding!!.rbCallType.visibility = View.GONE
|
||||
binding!!.rbPackageName.visibility = View.GONE
|
||||
binding!!.rbUid.visibility = View.GONE
|
||||
binding!!.rbInformContent.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun initListeners() {
|
||||
binding!!.btnTest.setOnClickListener(this)
|
||||
binding!!.btnDel.setOnClickListener(this)
|
||||
binding!!.btnSave.setOnClickListener(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!!.spCallType.visibility = View.GONE
|
||||
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!!.spCallType.visibility = View.GONE
|
||||
binding!!.tvMuRuleTips.visibility = View.VISIBLE
|
||||
binding!!.layoutMatchType.visibility = View.GONE
|
||||
binding!!.layoutMatchValue.visibility = View.VISIBLE
|
||||
binding!!.etValue.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
R.id.rb_call_type -> {
|
||||
binding!!.rgCheck.check(R.id.rb_is)
|
||||
binding!!.tvMuRuleTips.visibility = View.GONE
|
||||
binding!!.layoutMatchType.visibility = View.GONE
|
||||
binding!!.layoutMatchValue.visibility = View.VISIBLE
|
||||
binding!!.etValue.visibility = View.GONE
|
||||
binding!!.spCallType.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding!!.spCallType.visibility = View.GONE
|
||||
binding!!.tvMuRuleTips.visibility = View.GONE
|
||||
binding!!.layoutMatchType.visibility = View.VISIBLE
|
||||
binding!!.layoutMatchValue.visibility = View.VISIBLE
|
||||
binding!!.etValue.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)
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "initViews eventData:$eventData")
|
||||
if (eventData != null) {
|
||||
val rule = Gson().fromJson(eventData, Rule::class.java)
|
||||
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)
|
||||
if (ruleType == "call" && rule.filed == FILED_CALL_TYPE) {
|
||||
callType = rule.value.toInt()
|
||||
callTypeIndex = callType - 1
|
||||
binding!!.spCallType.selectedIndex = callTypeIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
when (v.id) {
|
||||
R.id.btn_test -> {
|
||||
val ruleNew = checkForm()
|
||||
testRule(ruleNew)
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_del -> {
|
||||
popToBack()
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_save -> {
|
||||
val settingVo = checkForm()
|
||||
val intent = Intent()
|
||||
intent.putExtra(KEY_BACK_DESCRIPTION_CONDITION, settingVo.description)
|
||||
intent.putExtra(KEY_BACK_DATA_CONDITION, Gson().toJson(settingVo))
|
||||
setFragmentResult(resultCode, intent)
|
||||
popToBack()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
XToastUtils.error(e.message.toString())
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
//初始化APP下拉列表
|
||||
private fun initAppSpinner() {
|
||||
if (ruleType != "app") return
|
||||
|
||||
//未开启异步获取已安装App信息开关时,规则编辑不显示已安装APP下拉框
|
||||
if (!SettingUtils.enableLoadUserAppList && !SettingUtils.enableLoadSystemAppList) return
|
||||
|
||||
if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) {
|
||||
XToastUtils.info(getString(R.string.loading_app_list))
|
||||
val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build()
|
||||
WorkManager.getInstance(XUtil.getContext()).enqueue(request)
|
||||
return
|
||||
}
|
||||
|
||||
appListSpinnerList.clear()
|
||||
if (SettingUtils.enableLoadUserAppList) {
|
||||
for (appInfo in App.UserAppList) {
|
||||
if (TextUtils.isEmpty(appInfo.packageName)) continue
|
||||
appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
|
||||
}
|
||||
}
|
||||
if (SettingUtils.enableLoadSystemAppList) {
|
||||
for (appInfo in App.SystemAppList) {
|
||||
if (TextUtils.isEmpty(appInfo.packageName)) continue
|
||||
appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
|
||||
}
|
||||
}
|
||||
|
||||
//列表为空也不显示下拉框
|
||||
if (appListSpinnerList.isEmpty()) return
|
||||
|
||||
appListSpinnerAdapter = AppListSpinnerAdapter(appListSpinnerList).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 = 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
|
||||
|
||||
}
|
||||
|
||||
//提交前检查表单
|
||||
private fun checkForm(): Rule {
|
||||
val filed = when (binding!!.rgFiled.checkedRadioButtonId) {
|
||||
R.id.rb_content -> FILED_MSG_CONTENT
|
||||
R.id.rb_phone -> FILED_PHONE_NUM
|
||||
R.id.rb_call_type -> FILED_CALL_TYPE
|
||||
R.id.rb_package_name -> FILED_PACKAGE_NAME
|
||||
R.id.rb_uid -> FILED_UID
|
||||
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
|
||||
}
|
||||
var value = binding!!.etValue.text.toString().trim()
|
||||
if (FILED_CALL_TYPE == filed) {
|
||||
value = callType.toString()
|
||||
if (callType !in 1..6) {
|
||||
throw Exception(getString(R.string.invalid_call_type))
|
||||
}
|
||||
} else 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 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
|
||||
}
|
||||
|
||||
return Rule(0, ruleType, filed, check, value, 0, "", "", simSlot, STATUS_ON, Date(), listOf())
|
||||
}
|
||||
|
||||
//检查多重匹配规则是否正确
|
||||
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 testRule(rule: Rule) {
|
||||
val dialogTest = View.inflate(requireContext(), R.layout.dialog_rule_test, null)
|
||||
val tvSimSlot = dialogTest.findViewById<TextView>(R.id.tv_sim_slot)
|
||||
val rgSimSlot = dialogTest.findViewById<RadioGroup>(R.id.rg_sim_slot)
|
||||
val tvFrom = dialogTest.findViewById<TextView>(R.id.tv_from)
|
||||
val etFrom = dialogTest.findViewById<EditText>(R.id.et_from)
|
||||
val tvTitle = dialogTest.findViewById<TextView>(R.id.tv_title)
|
||||
val etTitle = dialogTest.findViewById<EditText>(R.id.et_title)
|
||||
val tvContent = dialogTest.findViewById<TextView>(R.id.tv_content)
|
||||
val etContent = dialogTest.findViewById<EditText>(R.id.et_content)
|
||||
//通话类型
|
||||
val tvCallType = dialogTest.findViewById<TextView>(R.id.tv_call_type)
|
||||
val spCallType = dialogTest.findViewById<MaterialSpinner>(R.id.sp_call_type)
|
||||
var callTypeTest = callType
|
||||
var callTypeIndexTest = callTypeIndex
|
||||
|
||||
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)
|
||||
tvCallType.visibility = View.GONE
|
||||
spCallType.visibility = View.GONE
|
||||
} else if ("call" == ruleType) {
|
||||
tvContent.visibility = View.GONE
|
||||
etContent.visibility = View.GONE
|
||||
tvCallType.visibility = View.VISIBLE
|
||||
spCallType.visibility = View.VISIBLE
|
||||
spCallType.setItems(CALL_TYPE_MAP.values.toList())
|
||||
spCallType.setOnItemSelectedListener { _: MaterialSpinner?, _: Int, _: Long, item: Any ->
|
||||
CALL_TYPE_MAP.forEach {
|
||||
if (it.value == item) callTypeTest = it.key.toInt()
|
||||
}
|
||||
}
|
||||
spCallType.setOnNothingSelectedListener {
|
||||
callTypeTest = callType
|
||||
callTypeIndexTest = callTypeIndex
|
||||
spCallType.selectedIndex = callTypeIndexTest
|
||||
}
|
||||
spCallType.selectedIndex = callTypeIndexTest
|
||||
}
|
||||
|
||||
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(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 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 subId = when (simSlot) {
|
||||
0 -> SettingUtils.subidSim1
|
||||
1 -> SettingUtils.subidSim2
|
||||
else -> 0
|
||||
}
|
||||
|
||||
val msg = StringBuilder()
|
||||
if (ruleType == "call") {
|
||||
val phoneNumber = etFrom.text.toString()
|
||||
val contacts = PhoneUtils.getContactByNumber(phoneNumber)
|
||||
val contactName = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number)
|
||||
msg.append(getString(R.string.contact)).append(contactName).append("\n")
|
||||
msg.append(getString(R.string.mandatory_type))
|
||||
msg.append(CALL_TYPE_MAP[callType.toString()] ?: getString(R.string.unknown_call))
|
||||
} else {
|
||||
msg.append(etContent.text.toString())
|
||||
}
|
||||
|
||||
val msgInfo = MsgInfo(ruleType, etFrom.text.toString(), msg.toString(), Date(), simInfo, simSlot, subId, callTypeTest)
|
||||
if (!rule.checkMsg(msgInfo)) {
|
||||
throw Exception(getString(R.string.unmatched_rule))
|
||||
}
|
||||
|
||||
XToastUtils.success(getString(R.string.matched_rule))
|
||||
|
||||
} catch (e: Exception) {
|
||||
XToastUtils.error(e.message.toString())
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package com.idormy.sms.forwarder.fragment.condition
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
@ -58,7 +59,7 @@ class NetworkFragment : BaseFragment<FragmentTasksConditionNetworkBinding?>(), V
|
||||
|
||||
binding!!.rgNetworkState.setOnCheckedChangeListener { _, checkedId ->
|
||||
Log.d(TAG, "rgNetworkState checkedId:$checkedId")
|
||||
binding!!.layoutDataSimSlot.visibility = if (checkedId == R.id.rb_net_mobile) View.VISIBLE else View.GONE
|
||||
binding!!.layoutDataSimSlot.visibility = if (checkedId == R.id.rb_net_mobile && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) View.VISIBLE else View.GONE
|
||||
binding!!.layoutWifiSsid.visibility = if (checkedId == R.id.rb_net_wifi) View.VISIBLE else View.GONE
|
||||
checkSetting(true)
|
||||
}
|
||||
|
@ -2,8 +2,6 @@ package com.idormy.sms.forwarder.fragment.condition
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -14,6 +12,7 @@ import com.idormy.sms.forwarder.core.BaseFragment
|
||||
import com.idormy.sms.forwarder.databinding.FragmentTasksConditionToAddressBinding
|
||||
import com.idormy.sms.forwarder.entity.condition.LocationSetting
|
||||
import com.idormy.sms.forwarder.service.LocationService
|
||||
import com.idormy.sms.forwarder.utils.ACTION_START
|
||||
import com.idormy.sms.forwarder.utils.HttpServerUtils
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_CONDITION
|
||||
@ -98,13 +97,11 @@ class ToAddressFragment : BaseFragment<FragmentTasksConditionToAddressBinding?>(
|
||||
binding!!.btnDel.setOnClickListener(this)
|
||||
binding!!.btnSave.setOnClickListener(this)
|
||||
binding!!.btnCurrentCoordinates.setOnClickListener(this)
|
||||
binding!!.etLongitude.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
binding!!.etLongitude.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (!hasFocus) {
|
||||
try {
|
||||
val changedText = s.toString()
|
||||
if (changedText.isEmpty()) {
|
||||
val inputText = binding!!.etLongitude.text.toString()
|
||||
if (inputText.isEmpty()) {
|
||||
binding!!.etLongitude.setText("0")
|
||||
binding!!.etLongitude.setSelection(binding!!.etLongitude.text.length) // 将光标移至文本末尾
|
||||
} else {
|
||||
@ -112,17 +109,15 @@ class ToAddressFragment : BaseFragment<FragmentTasksConditionToAddressBinding?>(
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "afterTextChanged error:$e")
|
||||
Log.e(TAG, "etLongitude error:$e")
|
||||
}
|
||||
}
|
||||
})
|
||||
binding!!.etLatitude.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
}
|
||||
binding!!.etLatitude.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (!hasFocus) {
|
||||
try {
|
||||
val changedText = s.toString()
|
||||
if (changedText.isEmpty()) {
|
||||
val inputText = binding!!.etLatitude.text.toString()
|
||||
if (inputText.isEmpty()) {
|
||||
binding!!.etLatitude.setText("0")
|
||||
binding!!.etLatitude.setSelection(binding!!.etLatitude.text.length) // 将光标移至文本末尾
|
||||
} else {
|
||||
@ -130,17 +125,15 @@ class ToAddressFragment : BaseFragment<FragmentTasksConditionToAddressBinding?>(
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "afterTextChanged error:$e")
|
||||
Log.e(TAG, "etLatitude error:$e")
|
||||
}
|
||||
}
|
||||
})
|
||||
binding!!.etDistance.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
}
|
||||
binding!!.etDistance.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (!hasFocus) {
|
||||
try {
|
||||
val changedText = s.toString()
|
||||
if (changedText.isEmpty()) {
|
||||
val inputText = binding!!.etDistance.text.toString()
|
||||
if (inputText.isEmpty()) {
|
||||
binding!!.etDistance.setText("1")
|
||||
binding!!.etDistance.setSelection(binding!!.etDistance.text.length) // 将光标移至文本末尾
|
||||
} else {
|
||||
@ -148,22 +141,20 @@ class ToAddressFragment : BaseFragment<FragmentTasksConditionToAddressBinding?>(
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "afterTextChanged error:$e")
|
||||
Log.e(TAG, "etDistance error:$e")
|
||||
}
|
||||
}
|
||||
})
|
||||
binding!!.etAddress.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
}
|
||||
binding!!.etAddress.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (!hasFocus) {
|
||||
try {
|
||||
checkSetting(true)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "afterTextChanged error:$e")
|
||||
Log.e(TAG, "etAddress error:$e")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@SingleClick
|
||||
@ -175,7 +166,7 @@ class ToAddressFragment : BaseFragment<FragmentTasksConditionToAddressBinding?>(
|
||||
MaterialDialog.Builder(requireContext()).iconRes(R.drawable.auto_task_icon_location).title(R.string.enable_location).content(R.string.enable_location_dialog).cancelable(false).positiveText(R.string.lab_yes).negativeText(R.string.lab_no).onPositive { _: MaterialDialog?, _: DialogAction? ->
|
||||
SettingUtils.enableLocation = true
|
||||
val serviceIntent = Intent(requireContext(), LocationService::class.java)
|
||||
serviceIntent.action = "START"
|
||||
serviceIntent.action = ACTION_START
|
||||
requireContext().startService(serviceIntent)
|
||||
}.show()
|
||||
return
|
||||
|
@ -4,9 +4,10 @@ import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.gson.Gson
|
||||
import com.idormy.sms.forwarder.App.Companion.BARK_ENCRYPTION_ALGORITHM_MAP
|
||||
import com.idormy.sms.forwarder.App.Companion.BARK_LEVEL_MAP
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.core.BaseFragment
|
||||
import com.idormy.sms.forwarder.core.Core
|
||||
@ -53,20 +54,6 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
|
||||
private var mCountDownHelper: CountDownButtonHelper? = null
|
||||
private var barkLevel: String = "active" //通知级别
|
||||
private var transformation: String = "none" //加密算法
|
||||
private val BARK_LEVEL_MAP = mapOf(
|
||||
"active" to getString(R.string.bark_level_active),
|
||||
"timeSensitive" to getString(R.string.bark_level_timeSensitive),
|
||||
"passive" to getString(R.string.bark_level_passive)
|
||||
)
|
||||
private val BARK_ENCRYPTION_ALGORITHM_MAP = mapOf(
|
||||
"none" to getString(R.string.bark_encryption_algorithm_none),
|
||||
"AES128/CBC/PKCS7Padding" to "AES128/CBC/PKCS7Padding",
|
||||
"AES128/ECB/PKCS7Padding" to "AES128/ECB/PKCS7Padding",
|
||||
"AES192/CBC/PKCS7Padding" to "AES192/CBC/PKCS7Padding",
|
||||
"AES192/ECB/PKCS7Padding" to "AES192/ECB/PKCS7Padding",
|
||||
"AES256/CBC/PKCS7Padding" to "AES256/CBC/PKCS7Padding",
|
||||
"AES256/ECB/PKCS7Padding" to "AES256/ECB/PKCS7Padding",
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@AutoWired(name = KEY_SENDER_ID)
|
||||
@ -136,6 +123,10 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
|
||||
}
|
||||
binding!!.spEncryptionAlgorithm.selectedIndex = 0
|
||||
|
||||
//创建标签按钮
|
||||
CommonUtils.createTagButtons(requireContext(), binding!!.glTitleTemplate, binding!!.etTitleTemplate)
|
||||
CommonUtils.createTagButtons(requireContext(), binding!!.glAutoCopyTemplate, binding!!.etAutoCopyTemplate)
|
||||
|
||||
//新增
|
||||
if (senderId <= 0) {
|
||||
titleBar?.setSubTitle(getString(R.string.add_sender))
|
||||
@ -168,6 +159,7 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
|
||||
binding!!.etServer.setText(settingVo.server)
|
||||
binding!!.etGroup.setText(settingVo.group)
|
||||
binding!!.etIcon.setText(settingVo.icon)
|
||||
binding!!.sbCall.isChecked = settingVo.call == "1"
|
||||
binding!!.etSound.setText(settingVo.sound)
|
||||
binding!!.etBadge.setText(settingVo.badge)
|
||||
binding!!.etUrl.setText(settingVo.url)
|
||||
@ -190,10 +182,6 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
|
||||
}
|
||||
|
||||
override fun initListeners() {
|
||||
binding!!.btInsertSender.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)
|
||||
@ -203,27 +191,7 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
val etTitleTemplate: EditText = binding!!.etTitleTemplate
|
||||
when (v.id) {
|
||||
R.id.bt_insert_sender -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_extra -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_time -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_device_name -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_test -> {
|
||||
mCountDownHelper?.start()
|
||||
@ -293,6 +261,7 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
|
||||
if (!CommonUtils.checkUrl(icon, true)) {
|
||||
throw Exception(getString(R.string.invalid_bark_icon))
|
||||
}
|
||||
val call = if (binding!!.sbCall.isChecked) "1" else "0"
|
||||
val sound = binding!!.etSound.text.toString().trim()
|
||||
val badge = binding!!.etBadge.text.toString().trim()
|
||||
val url = binding!!.etUrl.text.toString().trim()
|
||||
@ -300,6 +269,7 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
|
||||
throw Exception(getString(R.string.invalid_bark_url))
|
||||
}
|
||||
val title = binding!!.etTitleTemplate.text.toString().trim()
|
||||
val autoCopy = binding!!.etAutoCopyTemplate.text.toString().trim()
|
||||
val key = binding!!.etEncryptionKey.text.toString().trim()
|
||||
val iv = binding!!.etEncryptionIv.text.toString().trim()
|
||||
if (transformation.startsWith("AES128") && key.length != 16) {
|
||||
@ -313,7 +283,7 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
|
||||
throw Exception(getString(R.string.bark_encryption_key_error4))
|
||||
}
|
||||
|
||||
return BarkSetting(server, group, icon, sound, badge, url, barkLevel, title, transformation, key, iv)
|
||||
return BarkSetting(server, group, icon, sound, badge, url, barkLevel, title, transformation, key, iv, call, autoCopy)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@ -321,4 +291,4 @@ class BarkFragment : BaseFragment<FragmentSendersBarkBinding?>(), View.OnClickLi
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.EditText
|
||||
import android.widget.RadioGroup
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.gson.Gson
|
||||
@ -96,6 +95,9 @@ class DingtalkGroupRobotFragment : BaseFragment<FragmentSendersDingtalkGroupRobo
|
||||
}
|
||||
})
|
||||
|
||||
//创建标签按钮
|
||||
CommonUtils.createTagButtons(requireContext(), binding!!.glTitleTemplate, binding!!.etTitleTemplate)
|
||||
|
||||
//新增
|
||||
if (senderId <= 0) {
|
||||
titleBar?.setSubTitle(getString(R.string.add_sender))
|
||||
@ -138,10 +140,6 @@ class DingtalkGroupRobotFragment : BaseFragment<FragmentSendersDingtalkGroupRobo
|
||||
}
|
||||
|
||||
override fun initListeners() {
|
||||
binding!!.btInsertSender.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)
|
||||
@ -168,27 +166,7 @@ class DingtalkGroupRobotFragment : BaseFragment<FragmentSendersDingtalkGroupRobo
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
val etTitleTemplate: EditText = binding!!.etTitleTemplate
|
||||
when (v.id) {
|
||||
R.id.bt_insert_sender -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_extra -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_time -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_device_name -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_test -> {
|
||||
mCountDownHelper?.start()
|
||||
@ -269,4 +247,4 @@ class DingtalkGroupRobotFragment : BaseFragment<FragmentSendersDingtalkGroupRobo
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.EditText
|
||||
import android.widget.RadioGroup
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.gson.Gson
|
||||
@ -19,7 +18,15 @@ import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel
|
||||
import com.idormy.sms.forwarder.databinding.FragmentSendersDingtalkInnerRobotBinding
|
||||
import com.idormy.sms.forwarder.entity.MsgInfo
|
||||
import com.idormy.sms.forwarder.entity.setting.DingtalkInnerRobotSetting
|
||||
import com.idormy.sms.forwarder.utils.*
|
||||
import com.idormy.sms.forwarder.utils.CommonUtils
|
||||
import com.idormy.sms.forwarder.utils.EVENT_TOAST_ERROR
|
||||
import com.idormy.sms.forwarder.utils.KEY_SENDER_CLONE
|
||||
import com.idormy.sms.forwarder.utils.KEY_SENDER_ID
|
||||
import com.idormy.sms.forwarder.utils.KEY_SENDER_TEST
|
||||
import com.idormy.sms.forwarder.utils.KEY_SENDER_TYPE
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.idormy.sms.forwarder.utils.sender.DingtalkInnerRobotUtils
|
||||
import com.jeremyliao.liveeventbus.LiveEventBus
|
||||
import com.xuexiang.xaop.annotation.SingleClick
|
||||
@ -35,7 +42,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.net.Proxy
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
|
||||
@Page(name = "钉钉企业机器人")
|
||||
@Suppress("PrivatePropertyName")
|
||||
@ -90,6 +97,9 @@ class DingtalkInnerRobotFragment : BaseFragment<FragmentSendersDingtalkInnerRobo
|
||||
}
|
||||
})
|
||||
|
||||
//创建标签按钮
|
||||
CommonUtils.createTagButtons(requireContext(), binding!!.glTitleTemplate, binding!!.etTitleTemplate)
|
||||
|
||||
//新增
|
||||
if (senderId <= 0) {
|
||||
titleBar?.setSubTitle(getString(R.string.add_sender))
|
||||
@ -137,10 +147,6 @@ class DingtalkInnerRobotFragment : BaseFragment<FragmentSendersDingtalkInnerRobo
|
||||
}
|
||||
|
||||
override fun initListeners() {
|
||||
binding!!.btInsertSender.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)
|
||||
@ -176,27 +182,7 @@ class DingtalkInnerRobotFragment : BaseFragment<FragmentSendersDingtalkInnerRobo
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
val etTitleTemplate: EditText = binding!!.etTitleTemplate
|
||||
when (v.id) {
|
||||
R.id.bt_insert_sender -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_extra -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_time -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_device_name -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_test -> {
|
||||
mCountDownHelper?.start()
|
||||
@ -295,4 +281,4 @@ class DingtalkInnerRobotFragment : BaseFragment<FragmentSendersDingtalkInnerRobo
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,20 @@
|
||||
package com.idormy.sms.forwarder.fragment.senders
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Environment
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.gson.Gson
|
||||
import com.hjq.permissions.OnPermissionCallback
|
||||
import com.hjq.permissions.Permission
|
||||
import com.hjq.permissions.XXPermissions
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.core.BaseFragment
|
||||
import com.idormy.sms.forwarder.core.Core
|
||||
@ -16,6 +24,7 @@ import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel
|
||||
import com.idormy.sms.forwarder.databinding.FragmentSendersEmailBinding
|
||||
import com.idormy.sms.forwarder.entity.MsgInfo
|
||||
import com.idormy.sms.forwarder.entity.setting.EmailSetting
|
||||
import com.idormy.sms.forwarder.utils.Base64
|
||||
import com.idormy.sms.forwarder.utils.CommonUtils
|
||||
import com.idormy.sms.forwarder.utils.EVENT_TOAST_ERROR
|
||||
import com.idormy.sms.forwarder.utils.KEY_SENDER_CLONE
|
||||
@ -41,6 +50,14 @@ import io.reactivex.SingleObserver
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.pgpainless.PGPainless
|
||||
import org.pgpainless.key.info.KeyRingInfo
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.security.KeyStore
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.Date
|
||||
|
||||
@Page(name = "Email")
|
||||
@ -52,6 +69,11 @@ class EmailFragment : BaseFragment<FragmentSendersEmailBinding?>(), View.OnClick
|
||||
private val viewModel by viewModels<SenderViewModel> { BaseViewModelFactory(context) }
|
||||
private var mCountDownHelper: CountDownButtonHelper? = null
|
||||
private var mailType: String = getString(R.string.other_mail_type) //邮箱类型
|
||||
private var recipientItemMap: MutableMap<Int, LinearLayout> = mutableMapOf()
|
||||
private val downloadPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path
|
||||
|
||||
//加密协议: S/MIME、OpenPGP、Plain(不传证书)
|
||||
private var encryptionProtocol: String = "Plain"
|
||||
|
||||
@JvmField
|
||||
@AutoWired(name = KEY_SENDER_ID)
|
||||
@ -98,7 +120,6 @@ class EmailFragment : BaseFragment<FragmentSendersEmailBinding?>(), View.OnClick
|
||||
})
|
||||
|
||||
val mailTypeArray = getStringArray(R.array.MailType)
|
||||
Log.d(TAG, mailTypeArray.toString())
|
||||
binding!!.spMailType.setOnItemSelectedListener { _: MaterialSpinner?, position: Int, _: Long, item: Any ->
|
||||
mailType = item.toString()
|
||||
//XToastUtils.warning(mailType)
|
||||
@ -112,6 +133,43 @@ class EmailFragment : BaseFragment<FragmentSendersEmailBinding?>(), View.OnClick
|
||||
binding!!.spMailType.selectedIndex = mailTypeArray.size - 1
|
||||
binding!!.layoutServiceSetting.visibility = View.VISIBLE
|
||||
|
||||
binding!!.rgEncryptionProtocol.setOnCheckedChangeListener { _, checkedId ->
|
||||
when (checkedId) {
|
||||
R.id.rb_encryption_protocol_smime -> {
|
||||
encryptionProtocol = "S/MIME"
|
||||
binding!!.layoutSenderKeystore.visibility = View.VISIBLE
|
||||
binding!!.tvSenderKeystore.text = getString(R.string.sender_smime_keystore)
|
||||
binding!!.tvEmailTo.text = getString(R.string.email_to_smime)
|
||||
binding!!.tvEmailToTips.text = getString(R.string.email_to_smime_tips)
|
||||
}
|
||||
|
||||
R.id.rb_encryption_protocol_openpgp -> {
|
||||
encryptionProtocol = "OpenPGP"
|
||||
binding!!.layoutSenderKeystore.visibility = View.VISIBLE
|
||||
binding!!.tvSenderKeystore.text = getString(R.string.sender_openpgp_keystore)
|
||||
binding!!.tvEmailTo.text = getString(R.string.email_to_openpgp)
|
||||
binding!!.tvEmailToTips.text = getString(R.string.email_to_openpgp_tips)
|
||||
}
|
||||
|
||||
else -> {
|
||||
encryptionProtocol = "Plain"
|
||||
binding!!.layoutSenderKeystore.visibility = View.GONE
|
||||
binding!!.tvEmailTo.text = getString(R.string.email_to)
|
||||
binding!!.tvEmailToTips.text = getString(R.string.email_to_tips)
|
||||
}
|
||||
}
|
||||
|
||||
//遍历 layout_recipients 子元素,设置 layout_recipient_keystore 可见性
|
||||
for (recipientItem in recipientItemMap.values) {
|
||||
val layoutRecipientKeystore = recipientItem.findViewById<LinearLayout>(R.id.layout_recipient_keystore)
|
||||
layoutRecipientKeystore.visibility = if (encryptionProtocol == "Plain") View.GONE else View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
//创建标签按钮
|
||||
CommonUtils.createTagButtons(requireContext(), binding!!.glTitleTemplate, binding!!.etTitleTemplate)
|
||||
CommonUtils.createTagButtons(requireContext(), binding!!.glNickname, binding!!.etNickname)
|
||||
|
||||
//新增
|
||||
if (senderId <= 0) {
|
||||
titleBar?.setSubTitle(getString(R.string.add_sender))
|
||||
@ -142,7 +200,11 @@ class EmailFragment : BaseFragment<FragmentSendersEmailBinding?>(), View.OnClick
|
||||
Log.d(TAG, settingVo.toString())
|
||||
if (settingVo != null) {
|
||||
if (!TextUtils.isEmpty(settingVo.mailType)) {
|
||||
mailType = settingVo.mailType.toString()
|
||||
mailType = settingVo.mailType
|
||||
//TODO: 替换mailType为当前语言,避免切换语言后失效,历史包袱怎么替换比较优雅?
|
||||
if (mailType == "other" || mailType == "其他邮箱" || mailType == "其他郵箱") {
|
||||
mailType = getString(R.string.other_mail_type)
|
||||
}
|
||||
binding!!.spMailType.setSelectedItem(mailType)
|
||||
if (mailType != getString(R.string.other_mail_type)) {
|
||||
binding!!.layoutServiceSetting.visibility = View.GONE
|
||||
@ -155,73 +217,46 @@ class EmailFragment : BaseFragment<FragmentSendersEmailBinding?>(), View.OnClick
|
||||
binding!!.etPort.setText(settingVo.port)
|
||||
binding!!.sbSsl.isChecked = settingVo.ssl == true
|
||||
binding!!.sbStartTls.isChecked = settingVo.startTls == true
|
||||
binding!!.etToEmail.setText(settingVo.toEmail)
|
||||
binding!!.etTitleTemplate.setText(settingVo.title)
|
||||
encryptionProtocol = settingVo.encryptionProtocol
|
||||
binding!!.rgEncryptionProtocol.check(settingVo.getEncryptionProtocolCheckId())
|
||||
if (settingVo.recipients.isNotEmpty()) {
|
||||
for ((email, cert) in settingVo.recipients) {
|
||||
addRecipientItem(email, cert)
|
||||
}
|
||||
} else {
|
||||
//兼容旧版本
|
||||
val emails = settingVo.toEmail.split(",")
|
||||
if (emails.isNotEmpty()) {
|
||||
for (email in emails.toTypedArray()) {
|
||||
addRecipientItem(email)
|
||||
}
|
||||
}
|
||||
}
|
||||
binding!!.etSenderKeystore.setText(settingVo.keystore)
|
||||
binding!!.etSenderPassword.setText(settingVo.password)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun initListeners() {
|
||||
binding!!.btInsertSenderToNickname.setOnClickListener(this)
|
||||
binding!!.btInsertExtraToNickname.setOnClickListener(this)
|
||||
binding!!.btInsertTimeToNickname.setOnClickListener(this)
|
||||
binding!!.btInsertDeviceNameToNickname.setOnClickListener(this)
|
||||
binding!!.btInsertSender.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!!.btnAddRecipient.setOnClickListener {
|
||||
addRecipientItem()
|
||||
}
|
||||
binding!!.btnSenderKeystorePicker.setOnClickListener {
|
||||
pickCert(binding!!.etSenderKeystore)
|
||||
}
|
||||
LiveEventBus.get(KEY_SENDER_TEST, String::class.java).observe(this) { mCountDownHelper?.finish() }
|
||||
}
|
||||
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
val etNickname: EditText = binding!!.etNickname
|
||||
val etTitleTemplate: EditText = binding!!.etTitleTemplate
|
||||
when (v.id) {
|
||||
R.id.bt_insert_sender_to_nickname -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etNickname, getString(R.string.tag_from))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_extra_to_nickname -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etNickname, getString(R.string.tag_card_slot))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_time_to_nickname -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etNickname, getString(R.string.tag_receive_time))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_device_name_to_nickname -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etNickname, getString(R.string.tag_device_name))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_sender -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_extra -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_time -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_device_name -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_test -> {
|
||||
mCountDownHelper?.start()
|
||||
@ -284,21 +319,288 @@ class EmailFragment : BaseFragment<FragmentSendersEmailBinding?>(), View.OnClick
|
||||
private fun checkSetting(): EmailSetting {
|
||||
val fromEmail = binding!!.etFromEmail.text.toString().trim()
|
||||
val pwd = binding!!.etPwd.text.toString().trim()
|
||||
val nickname = binding!!.etNickname.text.toString().trim()
|
||||
val host = binding!!.etHost.text.toString().trim()
|
||||
val port = binding!!.etPort.text.toString().trim()
|
||||
val ssl = binding!!.sbSsl.isChecked
|
||||
val startTls = binding!!.sbStartTls.isChecked
|
||||
val toEmail = binding!!.etToEmail.text.toString().trim()
|
||||
val title = binding!!.etTitleTemplate.text.toString().trim()
|
||||
if (TextUtils.isEmpty(fromEmail) || TextUtils.isEmpty(pwd) || TextUtils.isEmpty(toEmail)) {
|
||||
val recipients = getRecipientsFromRecipientItemMap()
|
||||
if (TextUtils.isEmpty(fromEmail) || TextUtils.isEmpty(pwd) || recipients.isEmpty()) {
|
||||
throw Exception(getString(R.string.invalid_email))
|
||||
}
|
||||
for ((email, cert) in recipients) {
|
||||
if (!CommonUtils.checkEmail(email)) {
|
||||
throw Exception(String.format(getString(R.string.invalid_recipient_email), email))
|
||||
}
|
||||
Log.d(TAG, "email: $email, cert: $cert")
|
||||
when (encryptionProtocol) {
|
||||
"S/MIME" -> {
|
||||
when {
|
||||
cert.first.isNotEmpty() && cert.second.isNotEmpty() -> {
|
||||
try {
|
||||
// 判断是否有效的PKCS12私钥证书
|
||||
val fileInputStream = if (cert.first.startsWith("/")) {
|
||||
FileInputStream(cert.first)
|
||||
} else {
|
||||
val decodedBytes = Base64.decode(cert.first)
|
||||
ByteArrayInputStream(decodedBytes)
|
||||
}
|
||||
val keyStore = KeyStore.getInstance("PKCS12")
|
||||
keyStore.load(fileInputStream, cert.second.toCharArray())
|
||||
val alias = keyStore.aliases().nextElement()
|
||||
val recipientPublicKey = keyStore.getCertificate(alias) as X509Certificate
|
||||
Log.d(TAG, "PKCS12 Certificate: $recipientPublicKey")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw Exception(String.format(getString(R.string.invalid_pkcs12_certificate), email))
|
||||
}
|
||||
}
|
||||
|
||||
cert.first.isNotEmpty() && cert.second.isEmpty() -> {
|
||||
try {
|
||||
// 判断是否有效的X.509公钥证书
|
||||
val fileInputStream = if (cert.first.startsWith("/")) {
|
||||
FileInputStream(cert.first)
|
||||
} else {
|
||||
val decodedBytes = Base64.decode(cert.first)
|
||||
ByteArrayInputStream(decodedBytes)
|
||||
}
|
||||
val certFactory = CertificateFactory.getInstance("X.509")
|
||||
val recipientPublicKey = certFactory.generateCertificate(fileInputStream) as X509Certificate
|
||||
Log.d(TAG, "X.509 Certificate: $recipientPublicKey")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw Exception(String.format(getString(R.string.invalid_x509_certificate), email))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"OpenPGP" -> {
|
||||
when {
|
||||
cert.first.isNotEmpty() && cert.second.isNotEmpty() -> {
|
||||
try {
|
||||
//从私钥证书文件提取公钥
|
||||
val recipientPrivateKeyStream = if (cert.first.startsWith("/")) {
|
||||
FileInputStream(cert.first)
|
||||
} else {
|
||||
val decodedBytes = Base64.decode(cert.first)
|
||||
ByteArrayInputStream(decodedBytes)
|
||||
}
|
||||
val recipientPGPSecretKeyRing = PGPainless.readKeyRing().secretKeyRing(recipientPrivateKeyStream)
|
||||
val recipientPGPPublicKeyRing = PGPainless.extractCertificate(recipientPGPSecretKeyRing!!)
|
||||
val keyInfo = KeyRingInfo(recipientPGPPublicKeyRing)
|
||||
Log.d(TAG, "recipientPGPPublicKeyRing: $keyInfo")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw Exception(String.format(getString(R.string.invalid_x509_certificate), email))
|
||||
}
|
||||
}
|
||||
|
||||
cert.first.isNotEmpty() && cert.second.isEmpty() -> {
|
||||
try {
|
||||
//从证书文件提取公钥
|
||||
val recipientPublicKeyStream = if (cert.first.startsWith("/")) {
|
||||
FileInputStream(cert.first)
|
||||
} else {
|
||||
val decodedBytes = Base64.decode(cert.first)
|
||||
ByteArrayInputStream(decodedBytes)
|
||||
}
|
||||
val recipientPGPPublicKeyRing = PGPainless.readKeyRing().publicKeyRing(recipientPublicKeyStream)
|
||||
val keyInfo = KeyRingInfo(recipientPGPPublicKeyRing!!)
|
||||
Log.d(TAG, "recipientPGPPublicKeyRing: $keyInfo")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw Exception(String.format(getString(R.string.invalid_x509_certificate), email))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val host = binding!!.etHost.text.toString().trim()
|
||||
val port = binding!!.etPort.text.toString().trim()
|
||||
if (mailType == getString(R.string.other_mail_type) && (TextUtils.isEmpty(host) || TextUtils.isEmpty(port))) {
|
||||
throw Exception(getString(R.string.invalid_email_server))
|
||||
}
|
||||
|
||||
return EmailSetting(mailType, fromEmail, pwd, nickname, host, port, ssl, startTls, toEmail, title)
|
||||
val nickname = binding!!.etNickname.text.toString().trim()
|
||||
val ssl = binding!!.sbSsl.isChecked
|
||||
val startTls = binding!!.sbStartTls.isChecked
|
||||
val title = binding!!.etTitleTemplate.text.toString().trim()
|
||||
val keystore = binding!!.etSenderKeystore.text.toString().trim()
|
||||
val password = binding!!.etSenderPassword.text.toString().trim()
|
||||
if (keystore.isNotEmpty()) {
|
||||
val senderPrivateKeyStream = if (keystore.startsWith("/")) {
|
||||
FileInputStream(keystore)
|
||||
} else {
|
||||
val decodedBytes = Base64.decode(keystore)
|
||||
ByteArrayInputStream(decodedBytes)
|
||||
}
|
||||
if (senderPrivateKeyStream.available() <= 0) {
|
||||
throw Exception(getString(R.string.invalid_sender_keystore))
|
||||
}
|
||||
when (encryptionProtocol) {
|
||||
"S/MIME" -> {
|
||||
try {
|
||||
// 判断是否有效的PKCS12私钥证书
|
||||
val keyStore = KeyStore.getInstance("PKCS12")
|
||||
keyStore.load(senderPrivateKeyStream, password.toCharArray())
|
||||
val alias = keyStore.aliases().nextElement()
|
||||
val certificate = keyStore.getCertificate(alias) as X509Certificate
|
||||
Log.d(TAG, "PKCS12 Certificate: $certificate")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw Exception(getString(R.string.invalid_sender_keystore))
|
||||
}
|
||||
}
|
||||
|
||||
"OpenPGP" -> {
|
||||
try {
|
||||
val senderPGPSecretKeyRing = PGPainless.readKeyRing().secretKeyRing(senderPrivateKeyStream)
|
||||
val keyInfo = KeyRingInfo(senderPGPSecretKeyRing!!)
|
||||
Log.d(TAG, "senderPGPSecretKeyRing: $keyInfo")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw Exception(getString(R.string.invalid_sender_keystore))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return EmailSetting(mailType, fromEmail, pwd, nickname, host, port, ssl, startTls, title, recipients, "", keystore, password, encryptionProtocol)
|
||||
}
|
||||
|
||||
//recipient序号
|
||||
private var recipientItemId = 0
|
||||
|
||||
/**
|
||||
* 动态增删recipient
|
||||
*
|
||||
* @param email recipient的email
|
||||
* @param cert recipient的cert,为空则不设置
|
||||
*/
|
||||
private fun addRecipientItem(email: String = "", cert: Any? = null) {
|
||||
val itemAddRecipient = View.inflate(requireContext(), R.layout.item_add_recipient, null) as LinearLayout
|
||||
val etRecipientEmail = itemAddRecipient.findViewById<EditText>(R.id.et_recipient_email)
|
||||
val etRecipientKeystore = itemAddRecipient.findViewById<EditText>(R.id.et_recipient_keystore)
|
||||
val etRecipientPassword = itemAddRecipient.findViewById<EditText>(R.id.et_recipient_password)
|
||||
etRecipientEmail.setText(email)
|
||||
Log.d(TAG, "cert: $cert")
|
||||
when (cert) {
|
||||
is String -> etRecipientKeystore.setText(cert)
|
||||
is Pair<*, *> -> {
|
||||
Log.d(TAG, "cert.first: ${cert.first}")
|
||||
Log.d(TAG, "cert.second: ${cert.second}")
|
||||
etRecipientKeystore.setText(cert.first.toString())
|
||||
etRecipientPassword.setText(cert.second.toString())
|
||||
}
|
||||
}
|
||||
|
||||
val ivDel = itemAddRecipient.findViewById<ImageView>(R.id.iv_del)
|
||||
ivDel.tag = recipientItemId
|
||||
ivDel.setOnClickListener {
|
||||
val itemId = it.tag as Int
|
||||
binding!!.layoutRecipients.removeView(recipientItemMap[itemId])
|
||||
recipientItemMap.remove(itemId)
|
||||
}
|
||||
|
||||
val btnFilePicker = itemAddRecipient.findViewById<Button>(R.id.btn_file_picker)
|
||||
btnFilePicker.tag = recipientItemId
|
||||
btnFilePicker.setOnClickListener {
|
||||
val itemId = it.tag as Int
|
||||
val etKeyStore = recipientItemMap[itemId]!!.findViewById<EditText>(R.id.et_recipient_keystore)
|
||||
pickCert(etKeyStore)
|
||||
}
|
||||
|
||||
val layoutRecipientKeystore = itemAddRecipient.findViewById<LinearLayout>(R.id.layout_recipient_keystore)
|
||||
layoutRecipientKeystore.visibility = if (encryptionProtocol == "Plain") View.GONE else View.VISIBLE
|
||||
|
||||
binding!!.layoutRecipients.addView(itemAddRecipient)
|
||||
recipientItemMap[recipientItemId] = itemAddRecipient
|
||||
recipientItemId++
|
||||
}
|
||||
|
||||
/**
|
||||
* 从EditText控件中获取全部recipients
|
||||
*
|
||||
* @return 全部recipients
|
||||
*/
|
||||
private fun getRecipientsFromRecipientItemMap(): MutableMap<String, Pair<String, String>> {
|
||||
val recipients: MutableMap<String, Pair<String, String>> = mutableMapOf()
|
||||
for (recipientItem in recipientItemMap.values) {
|
||||
val etRecipientEmail = recipientItem.findViewById<EditText>(R.id.et_recipient_email)
|
||||
val etRecipientKeystore = recipientItem.findViewById<EditText>(R.id.et_recipient_keystore)
|
||||
val etRecipientPassword = recipientItem.findViewById<EditText>(R.id.et_recipient_password)
|
||||
val email = etRecipientEmail.text.toString().trim()
|
||||
val keystore = etRecipientKeystore.text.toString().trim()
|
||||
val password = etRecipientPassword.text.toString().trim()
|
||||
recipients[email] = Pair(keystore, password)
|
||||
}
|
||||
Log.d(TAG, "recipients: $recipients")
|
||||
return recipients
|
||||
}
|
||||
|
||||
//选择证书文件
|
||||
private fun pickCert(etKeyStore: EditText) {
|
||||
XXPermissions.with(this)
|
||||
.permission(Permission.MANAGE_EXTERNAL_STORAGE)
|
||||
.request(object : OnPermissionCallback {
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
val fileList = findSupportedFiles(downloadPath)
|
||||
if (fileList.isEmpty()) {
|
||||
XToastUtils.error(String.format(getString(R.string.download_certificate_first), downloadPath))
|
||||
return
|
||||
}
|
||||
MaterialDialog.Builder(requireContext())
|
||||
.title(getString(R.string.keystore_base64))
|
||||
.content(String.format(getString(R.string.root_directory), downloadPath))
|
||||
.items(fileList)
|
||||
.itemsCallbackSingleChoice(0) { _: MaterialDialog?, _: View?, _: Int, text: CharSequence ->
|
||||
val webPath = "$downloadPath/$text"
|
||||
etKeyStore.setText(convertCertToBase64String(webPath))
|
||||
true // allow selection
|
||||
}
|
||||
.positiveText(R.string.select)
|
||||
.negativeText(R.string.cancel)
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: List<String>, never: Boolean) {
|
||||
if (never) {
|
||||
XToastUtils.error(R.string.toast_denied_never)
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(requireContext(), permissions)
|
||||
} else {
|
||||
XToastUtils.error(R.string.toast_denied)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun findSupportedFiles(directoryPath: String): List<String> {
|
||||
val audioFiles = mutableListOf<String>()
|
||||
val directory = File(directoryPath)
|
||||
|
||||
if (directory.exists() && directory.isDirectory) {
|
||||
directory.listFiles()?.let { files ->
|
||||
files.filter { it.isFile && isSupportedFile(it) }.forEach { audioFiles.add(it.name) }
|
||||
}
|
||||
}
|
||||
|
||||
return audioFiles
|
||||
}
|
||||
|
||||
private fun isSupportedFile(file: File): Boolean {
|
||||
val supportedExtensions = if (encryptionProtocol == "OpenPGP") {
|
||||
listOf("asc", "pgp")
|
||||
} else {
|
||||
listOf("pfx", "p12", "pem", "cer", "crt", "der")
|
||||
}
|
||||
return supportedExtensions.any { it.equals(file.extension, ignoreCase = true) }
|
||||
}
|
||||
|
||||
private fun convertCertToBase64String(pfxFilePath: String): String {
|
||||
val pfxInputStream = FileInputStream(pfxFilePath)
|
||||
val pfxBytes = pfxInputStream.readBytes()
|
||||
return Base64.encode(pfxBytes)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@ -306,4 +608,4 @@ class EmailFragment : BaseFragment<FragmentSendersEmailBinding?>(), View.OnClick
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonParser
|
||||
@ -105,6 +104,10 @@ class FeishuAppFragment : BaseFragment<FragmentSendersFeishuAppBinding?>(), View
|
||||
}
|
||||
}
|
||||
|
||||
//创建标签按钮
|
||||
CommonUtils.createTagButtons(requireContext(), binding!!.glTitleTemplate, binding!!.etTitleTemplate)
|
||||
CommonUtils.createTagButtons(requireContext(), binding!!.glMessageCard, binding!!.etMessageCard)
|
||||
|
||||
//新增
|
||||
if (senderId <= 0) {
|
||||
titleBar?.setSubTitle(getString(R.string.add_sender))
|
||||
@ -150,15 +153,6 @@ class FeishuAppFragment : BaseFragment<FragmentSendersFeishuAppBinding?>(), View
|
||||
}
|
||||
|
||||
override fun initListeners() {
|
||||
binding!!.btInsertSenderToTitle.setOnClickListener(this)
|
||||
binding!!.btInsertExtraToTitle.setOnClickListener(this)
|
||||
binding!!.btInsertTimeToTitle.setOnClickListener(this)
|
||||
binding!!.btInsertDeviceNameToTitle.setOnClickListener(this)
|
||||
binding!!.btInsertSender.setOnClickListener(this)
|
||||
binding!!.btInsertContent.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)
|
||||
@ -168,53 +162,7 @@ class FeishuAppFragment : BaseFragment<FragmentSendersFeishuAppBinding?>(), View
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
val etTitleTemplate: EditText = binding!!.etTitleTemplate
|
||||
val etMessageCard: EditText = binding!!.etMessageCard
|
||||
when (v.id) {
|
||||
R.id.bt_insert_sender_to_title -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_extra_to_title -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_time_to_title -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_device_name_to_title -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_sender -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_from))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_content -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_sms))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_extra -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_card_slot))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_time -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_receive_time))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_device_name -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_device_name))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_test -> {
|
||||
mCountDownHelper?.start()
|
||||
@ -315,4 +263,4 @@ class FeishuAppFragment : BaseFragment<FragmentSendersFeishuAppBinding?>(), View
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonParser
|
||||
@ -105,6 +104,10 @@ class FeishuFragment : BaseFragment<FragmentSendersFeishuBinding?>(), View.OnCli
|
||||
}
|
||||
}
|
||||
|
||||
//创建标签按钮
|
||||
CommonUtils.createTagButtons(requireContext(), binding!!.glTitleTemplate, binding!!.etTitleTemplate)
|
||||
CommonUtils.createTagButtons(requireContext(), binding!!.glMessageCard, binding!!.etMessageCard)
|
||||
|
||||
//新增
|
||||
if (senderId <= 0) {
|
||||
titleBar?.setSubTitle(getString(R.string.add_sender))
|
||||
@ -145,15 +148,6 @@ class FeishuFragment : BaseFragment<FragmentSendersFeishuBinding?>(), View.OnCli
|
||||
}
|
||||
|
||||
override fun initListeners() {
|
||||
binding!!.btInsertSenderToTitle.setOnClickListener(this)
|
||||
binding!!.btInsertExtraToTitle.setOnClickListener(this)
|
||||
binding!!.btInsertTimeToTitle.setOnClickListener(this)
|
||||
binding!!.btInsertDeviceNameToTitle.setOnClickListener(this)
|
||||
binding!!.btInsertSender.setOnClickListener(this)
|
||||
binding!!.btInsertContent.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)
|
||||
@ -163,53 +157,7 @@ class FeishuFragment : BaseFragment<FragmentSendersFeishuBinding?>(), View.OnCli
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
val etTitleTemplate: EditText = binding!!.etTitleTemplate
|
||||
val etMessageCard: EditText = binding!!.etMessageCard
|
||||
when (v.id) {
|
||||
R.id.bt_insert_sender_to_title -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_extra_to_title -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_time_to_title -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_device_name_to_title -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_sender -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_from))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_content -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_sms))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_extra -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_card_slot))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_time -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_receive_time))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_device_name -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etMessageCard, getString(R.string.tag_device_name))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_test -> {
|
||||
mCountDownHelper?.start()
|
||||
@ -295,4 +243,4 @@ class FeishuFragment : BaseFragment<FragmentSendersFeishuBinding?>(), View.OnCli
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.gson.Gson
|
||||
import com.idormy.sms.forwarder.R
|
||||
@ -94,6 +93,9 @@ class GotifyFragment : BaseFragment<FragmentSendersGotifyBinding?>(), View.OnCli
|
||||
}
|
||||
})
|
||||
|
||||
//创建标签按钮
|
||||
CommonUtils.createTagButtons(requireContext(), binding!!.glTitleTemplate, binding!!.etTitleTemplate)
|
||||
|
||||
//新增
|
||||
if (senderId <= 0) {
|
||||
titleBar?.setSubTitle(getString(R.string.add_sender))
|
||||
@ -132,10 +134,6 @@ class GotifyFragment : BaseFragment<FragmentSendersGotifyBinding?>(), View.OnCli
|
||||
}
|
||||
|
||||
override fun initListeners() {
|
||||
binding!!.btInsertSender.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)
|
||||
@ -145,27 +143,7 @@ class GotifyFragment : BaseFragment<FragmentSendersGotifyBinding?>(), View.OnCli
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
val etTitleTemplate: EditText = binding!!.etTitleTemplate
|
||||
when (v.id) {
|
||||
R.id.bt_insert_sender -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_extra -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_time -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_device_name -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_test -> {
|
||||
mCountDownHelper?.start()
|
||||
@ -242,4 +220,4 @@ class GotifyFragment : BaseFragment<FragmentSendersGotifyBinding?>(), View.OnCli
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import android.widget.RadioGroup
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.gson.Gson
|
||||
@ -95,6 +94,9 @@ class PushplusFragment : BaseFragment<FragmentSendersPushplusBinding?>(), View.O
|
||||
}
|
||||
})
|
||||
|
||||
//创建标签按钮
|
||||
CommonUtils.createTagButtons(requireContext(), binding!!.glTitleTemplate, binding!!.etTitleTemplate)
|
||||
|
||||
//新增
|
||||
if (senderId <= 0) {
|
||||
titleBar?.setSubTitle(getString(R.string.add_sender))
|
||||
@ -143,10 +145,6 @@ class PushplusFragment : BaseFragment<FragmentSendersPushplusBinding?>(), View.O
|
||||
}
|
||||
|
||||
override fun initListeners() {
|
||||
binding!!.btInsertSender.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)
|
||||
@ -165,27 +163,7 @@ class PushplusFragment : BaseFragment<FragmentSendersPushplusBinding?>(), View.O
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
val etTitleTemplate: EditText = binding!!.etTitleTemplate
|
||||
when (v.id) {
|
||||
R.id.bt_insert_sender -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_extra -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_time -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.bt_insert_device_name -> {
|
||||
CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name))
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_test -> {
|
||||
mCountDownHelper?.start()
|
||||
@ -272,4 +250,4 @@ class PushplusFragment : BaseFragment<FragmentSendersPushplusBinding?>(), View.O
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -226,4 +226,4 @@ class ServerchanFragment : BaseFragment<FragmentSendersServerchanBinding?>(), Vi
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -251,4 +251,4 @@ class SmsFragment : BaseFragment<FragmentSendersSmsBinding?>(), View.OnClickList
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -262,4 +262,4 @@ class SocketFragment : BaseFragment<FragmentSendersSocketBinding?>(), View.OnCli
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -133,6 +133,7 @@ class TelegramFragment : BaseFragment<FragmentSendersTelegramBinding?>(), View.O
|
||||
binding!!.sbProxyAuthenticator.isChecked = settingVo.proxyAuthenticator == true
|
||||
binding!!.etProxyUsername.setText(settingVo.proxyUsername)
|
||||
binding!!.etProxyPassword.setText(settingVo.proxyPassword)
|
||||
binding!!.rgParseMode.check(settingVo.getParseModeCheckId())
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -251,8 +252,13 @@ class TelegramFragment : BaseFragment<FragmentSendersTelegramBinding?>(), View.O
|
||||
}
|
||||
|
||||
val method = if (binding!!.rgMethod.checkedRadioButtonId == R.id.rb_method_get) "GET" else "POST"
|
||||
val parseMode = when (binding!!.rgParseMode.checkedRadioButtonId) {
|
||||
R.id.rb_parse_mode_text -> "TEXT"
|
||||
R.id.rb_parse_mode_markdown -> "MarkdownV2"
|
||||
else -> "HTML"
|
||||
}
|
||||
|
||||
return TelegramSetting(method, apiToken, chatId, proxyType, proxyHost, proxyPort, proxyAuthenticator, proxyUsername, proxyPassword)
|
||||
return TelegramSetting(method, apiToken, chatId, proxyType, proxyHost, proxyPort, proxyAuthenticator, proxyUsername, proxyPassword, parseMode)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@ -260,4 +266,4 @@ class TelegramFragment : BaseFragment<FragmentSendersTelegramBinding?>(), View.O
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user