mirror of
https://github.com/pppscn/SmsForwarder
synced 2025-08-03 01:17:41 +08:00
Compare commits
364 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 | ||
![]() |
125695133d | ||
![]() |
c0afc9f529 | ||
![]() |
44ab934873 | ||
![]() |
9c11bc5b9b | ||
![]() |
4e13ad699b | ||
![]() |
cbc4373a3a | ||
![]() |
03797c75ce | ||
![]() |
72952d940d | ||
![]() |
ba356e63b3 | ||
![]() |
c7166ae3ba | ||
![]() |
1ce9f33a65 | ||
![]() |
0daa2d4655 | ||
![]() |
0d26a0de70 | ||
![]() |
e736bd2c2a | ||
![]() |
8c897971bf | ||
![]() |
8deafd586f | ||
![]() |
c0329f51b0 | ||
![]() |
6792a779c3 | ||
![]() |
d48b9d826a | ||
![]() |
93e1a6292e | ||
![]() |
3f413043d7 | ||
![]() |
c5ee0b8d91 | ||
![]() |
42eb316798 | ||
![]() |
ef9c80de7e | ||
![]() |
ca1235f0b3 | ||
![]() |
9a43cb504e | ||
![]() |
7d0657f65c | ||
![]() |
d01e17f147 | ||
![]() |
19da500749 | ||
![]() |
388d94a7cf | ||
![]() |
0f83526ece | ||
![]() |
2930d2ed17 | ||
![]() |
0c950c835a | ||
![]() |
e69716affc | ||
![]() |
ebcc4bbdbc | ||
![]() |
ca5a12b350 | ||
![]() |
28f24ef73b | ||
![]() |
be36ada07f | ||
![]() |
81e1cd4729 | ||
![]() |
046dd8edbe | ||
![]() |
a6d1a13d44 | ||
![]() |
06ce0112a9 | ||
![]() |
1da0257c8c | ||
![]() |
2b4468b669 | ||
![]() |
d3fc481d0c | ||
![]() |
6473b3eb7c | ||
![]() |
70d685ee93 | ||
![]() |
bceebbddd4 | ||
![]() |
0faa6bf26a | ||
![]() |
5ca161629f | ||
![]() |
16e4037c73 | ||
![]() |
abe7863a76 | ||
![]() |
41c0e1923a | ||
![]() |
1302bf9e20 | ||
![]() |
f288f5a6dc | ||
![]() |
d4ac2ed38e | ||
![]() |
0d77eac6ce | ||
![]() |
d8553ef793 | ||
![]() |
b94a25c09d | ||
![]() |
22df7592f7 | ||
![]() |
b5517e3270 | ||
![]() |
33995b2ebd | ||
![]() |
437716cd4e | ||
![]() |
2233c0032f | ||
![]() |
eb20d8ea05 | ||
![]() |
e03a9b8198 | ||
![]() |
51129509f9 | ||
![]() |
5381151ac3 | ||
![]() |
95e9f22816 | ||
![]() |
5fb9f1b75c | ||
![]() |
af63302df6 | ||
![]() |
51149c95cd | ||
![]() |
0a651b2da2 | ||
![]() |
1d1fb747fc | ||
![]() |
d10d831685 | ||
![]() |
eab6c5b049 | ||
![]() |
0c380caebd | ||
![]() |
d4c7b1f731 | ||
![]() |
4c3fcf45f6 | ||
![]() |
e9bfb9eca4 | ||
![]() |
ac74a183cd | ||
![]() |
9ac3548719 | ||
![]() |
478be66f45 | ||
![]() |
7f4794e9ae | ||
![]() |
2eda4f567b | ||
![]() |
7129de4a55 | ||
![]() |
e8b444e22d | ||
![]() |
3dd2e41123 | ||
![]() |
5741cdfe96 | ||
![]() |
5f64d9462e | ||
![]() |
74346b7291 | ||
![]() |
8eeb2b1cc2 | ||
![]() |
87f7ee9c13 | ||
![]() |
0767082ee6 | ||
![]() |
ec7801a015 | ||
![]() |
4a95770553 | ||
![]() |
2cccb9b4fa | ||
![]() |
354393a231 | ||
![]() |
2bebb44fb8 | ||
![]() |
0bb562b43d | ||
![]() |
103b1f5839 | ||
![]() |
703ca25e7c | ||
![]() |
65df38564f | ||
![]() |
ca24c078c2 | ||
![]() |
852d327076 | ||
![]() |
6fe7b41baa | ||
![]() |
2575363c38 | ||
![]() |
74c461f917 | ||
![]() |
24d152cf87 | ||
![]() |
00195e6c85 | ||
![]() |
83bca041c9 | ||
![]() |
2bee1dad87 | ||
![]() |
3c0bead575 | ||
![]() |
3edf6cc7bc | ||
![]() |
47b8efc8b4 | ||
![]() |
26f157403a | ||
![]() |
6b6f8ecfa5 | ||
![]() |
a9eaa8c791 | ||
![]() |
2c9065a743 | ||
![]() |
930fa3f7da | ||
![]() |
652a9c68d7 | ||
![]() |
2b96dca226 | ||
![]() |
65c3246ed3 | ||
![]() |
ec57b0228a | ||
![]() |
1bc2668ab2 | ||
![]() |
f5de522967 | ||
![]() |
8953981d4e | ||
![]() |
8250049439 | ||
![]() |
9272ba45b8 | ||
![]() |
d6b39b09c2 | ||
![]() |
cdf0cae0cf | ||
![]() |
42794a5b2c | ||
![]() |
862477e15c | ||
![]() |
c7ead43a29 | ||
![]() |
1d5f538fd7 | ||
![]() |
fb98b21b1b | ||
![]() |
2c7bb2a87d | ||
![]() |
0eab5dcaa0 | ||
![]() |
17830f10df | ||
![]() |
1a527a3fc8 | ||
![]() |
621ab87463 | ||
![]() |
a0c3ead33e | ||
![]() |
9a0861ded3 | ||
![]() |
20096e2ae3 | ||
![]() |
51b77d4c85 | ||
![]() |
fded1d1e3c | ||
![]() |
197fb7ac36 | ||
![]() |
7194c9ba3f | ||
![]() |
0efae57f4d | ||
![]() |
a0b697d99b | ||
![]() |
872a4cec01 | ||
![]() |
9c6f404190 | ||
![]() |
6571775a0f | ||
![]() |
dd798e42cc | ||
![]() |
032837615f | ||
![]() |
b4bd1872a9 | ||
![]() |
edb5ef48a2 | ||
![]() |
f468b4187b | ||
![]() |
4ee2cbf906 | ||
![]() |
593100a143 | ||
![]() |
ab668220fc | ||
![]() |
8245536b79 | ||
![]() |
d2a668f635 | ||
![]() |
11aead738a | ||
![]() |
565795a843 | ||
![]() |
419766b47a | ||
![]() |
631e9950bd | ||
![]() |
481f634230 | ||
![]() |
11a03f3056 | ||
![]() |
ffc022e0a8 | ||
![]() |
1e1dd8e3fd | ||
![]() |
f9ddbd7261 | ||
![]() |
9f41ff7d0b | ||
![]() |
7bc7bfa514 | ||
![]() |
3f28080958 | ||
![]() |
1630fb18e9 | ||
![]() |
0b3b39a802 | ||
![]() |
b20a7f2391 | ||
![]() |
6ef83f131e | ||
![]() |
04d8c9015a | ||
![]() |
b79d3d8493 | ||
![]() |
b4870207d1 | ||
![]() |
e1660fe9bb | ||
![]() |
992fc2eb8c | ||
![]() |
65e861ba62 | ||
![]() |
442c29fd3d | ||
![]() |
6eb2ca2b1b | ||
![]() |
5387202e36 | ||
![]() |
5f55d20c83 | ||
![]() |
4d7146bd7b | ||
![]() |
1b67930220 | ||
![]() |
d144ccead0 | ||
![]() |
1fe9362b4f | ||
![]() |
875efa08ae | ||
![]() |
91d8910d21 | ||
![]() |
f99dc6bbba | ||
![]() |
b29a9775d0 | ||
![]() |
6634b5aedf | ||
![]() |
d46d8e9c27 | ||
![]() |
538f440b3d | ||
![]() |
a624ca5e8e | ||
![]() |
50960c94c8 | ||
![]() |
52c67dbb73 | ||
![]() |
581c0c2ef2 | ||
![]() |
1b93aeb857 | ||
![]() |
5f7679807f | ||
![]() |
0aba25ffe2 | ||
![]() |
c2ab087155 | ||
![]() |
35151aa924 | ||
![]() |
668847c91f | ||
![]() |
3430dbbe8e | ||
![]() |
c00e61ece1 | ||
![]() |
ff2be5553f | ||
![]() |
66e324bbbb | ||
![]() |
c2df047dc1 | ||
![]() |
73ce800e09 | ||
![]() |
cabad8b14e | ||
![]() |
fc45b7fc5b | ||
![]() |
a21e604571 | ||
![]() |
9d75554df0 | ||
![]() |
74cbddc192 | ||
![]() |
8418155826 | ||
![]() |
22ad631f06 | ||
![]() |
e3af5fcd6e | ||
![]() |
851e514056 | ||
![]() |
5f4d5f6cc8 | ||
![]() |
242fb332bd | ||
![]() |
e0d358a16b | ||
![]() |
657eb41547 | ||
![]() |
33cbc841b3 | ||
![]() |
b407e11530 | ||
![]() |
17df392ae4 | ||
![]() |
84d321dba8 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1,3 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
custom: 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"]
|
||||
|
1
.github/ISSUE_TEMPLATE/config.yml
vendored
1
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,3 +1,4 @@
|
||||
#file: noinspection YAMLSchemaValidation
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: SmsForwarder 使用流程与问题排查流程
|
||||
|
@ -99,7 +99,7 @@ body:
|
||||
- type: textarea
|
||||
id: stack
|
||||
attributes:
|
||||
label: 提供报错堆栈
|
||||
label: 提供报错堆栈【请提供logcat抓取的日志,参考: https://blog.csdn.net/m0_64776928/article/details/126005119 】
|
||||
description: Provide a stack trace
|
||||
placeholder: 根据需要提供,此项不强制 (as needed, this is not mandatory)
|
||||
- type: textarea
|
||||
|
18
.github/workflows/Release.yml
vendored
18
.github/workflows/Release.yml
vendored
@ -20,15 +20,17 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# 检出代码
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
# 设置jdk环境为11
|
||||
- name: set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
java-package: jdk
|
||||
# 获取打包秘钥
|
||||
- name: Checkout Android Keystore
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: pppscn/keystore
|
||||
token: ${{ secrets.TOKEN }} # 连接仓库的token,需要单独配置
|
||||
@ -47,16 +49,10 @@ jobs:
|
||||
release_name: SmsForwarder ${{ github.ref }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
# 存档打包的文件
|
||||
# - name: Archive production artifacts
|
||||
# uses: actions/upload-artifact@v2
|
||||
# with:
|
||||
# name: build
|
||||
# path: app/build/outputs #将打包之后的文件全部上传(里面会有混淆的map文件)
|
||||
# 上传至release的资源
|
||||
- name: Upload release binaries
|
||||
uses: alexellis/upload-assets@0.2.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
asset_paths: '["./app/build/outputs/apk/release/SmsForwarder_release_*"]'
|
||||
asset_paths: '["./build/app/outputs/apk/release/SmsF_*"]'
|
||||
|
100
.github/workflows/Weekly_Build.yml
vendored
100
.github/workflows/Weekly_Build.yml
vendored
@ -14,26 +14,30 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
output: "${{ github.workspace }}/build/app/outputs/apk/release"
|
||||
steps:
|
||||
# 检出代码
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
# 删除旧的工作流
|
||||
- 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 # 全部删除只留正在跑的一条
|
||||
delete_workflow_pattern: 'Weekly Build'
|
||||
# 设置jdk环境为11
|
||||
- name: set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
java-package: jdk
|
||||
# 获取打包秘钥
|
||||
- name: Checkout Android Keystore
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: pppscn/keystore
|
||||
token: ${{ secrets.TOKEN }} # 连接仓库的token,需要单独配置
|
||||
@ -41,10 +45,84 @@ jobs:
|
||||
# 打包release
|
||||
- name: Build with Gradle
|
||||
run: bash ./gradlew assembleRelease
|
||||
# 存档打包的文件
|
||||
- name: Archive production artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
# 自动发布预览计划
|
||||
- name: Parse output-metadata.json and upload APKs to XUpdate
|
||||
run: |
|
||||
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@v4
|
||||
with:
|
||||
name: SmsForwarder Weekly Build
|
||||
path: app/build/outputs/apk/release/*.apk
|
||||
if-no-files-found: error
|
||||
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@v4
|
||||
with:
|
||||
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@v4
|
||||
with:
|
||||
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@v4
|
||||
with:
|
||||
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@v4
|
||||
with:
|
||||
name: "SmsF_${{ env.ver_name }}_500${{ env.ver_code }}_x86_64_release.apk"
|
||||
path: "${{ env.output }}/SmsF_*_x86_64_release.apk"
|
56
.gitignore
vendored
56
.gitignore
vendored
@ -1,37 +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
|
||||
/pic/*.drawio
|
||||
/pic/*.vsdx
|
||||
/psd
|
||||
/pic/*.psd
|
||||
|
49
README.md
49
README.md
@ -10,17 +10,21 @@
|
||||
|
||||
短信转发器——不仅只转发短信,备用机必备神器!
|
||||
|
||||
监控Android手机短信、来电、APP通知,并根据指定规则转发到其他手机:钉钉群自定义机器人、钉钉企业内机器人、企业微信群机器人、飞书机器人、企业微信应用消息、邮箱、bark、webhook、Telegram机器人、Server酱、PushPlus、手机短信等。
|
||||
监控Android手机短信、来电、APP通知,并根据指定规则转发到其他手机:钉钉群自定义机器人、钉钉企业内机器人、企业微信群机器人、企业微信应用消息、飞书群机器人、飞书企业应用、邮箱、bark、webhook、Tele****机器人、Server酱、PushPlus、手机短信等。
|
||||
|
||||
包括主动控制服务端与客户端,让你轻松远程发短信、查短信、查通话、查话簿、查电量等。(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 ~ 12.0
|
||||
**升级操作提示:**
|
||||
- `加入SmsF预览体验计划`后在线更新(`关于软件`页面开启,`v3.3.0_240305+`适用)
|
||||
- 手动下载:https://github.com/pppscn/SmsForwarder/actions/workflows/Weekly_Build.yml
|
||||
|
||||
--------
|
||||
|
||||
@ -32,19 +36,21 @@
|
||||
|
||||
* 如果任何单位或个人认为该项目的代码/APK可能涉嫌侵犯其权利,则应及时通知并提供身份证明,所有权证明,我们将在收到认证文件后删除相关代码/APK。
|
||||
|
||||
* 隐私声明:SmsForwarder 不会收集任何您的隐私数据!!!APP启动时发送版本信息发送到友盟统计;手动检查新版本时发送版本号用于检查新版本;除此之外,没有任何数据!!!
|
||||
* 隐私声明: **SmsForwarder 不会收集任何您的隐私数据!!!** APP启动时发送版本信息发送到友盟统计;手动检查新版本时发送版本号用于检查新版本;除此之外,没有任何数据!!!
|
||||
|
||||
* 防诈提醒: `SmsForwarder`完全免费开源,请您在 [打赏](https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427) 前务必确认是否出于自愿?本项目不参与任何刷单返利担保!**请您远离刷单返利陷阱,谨防网络诈骗!**
|
||||
|
||||
--------
|
||||
|
||||
## 工作流程:
|
||||
|
||||

|
||||

|
||||
|
||||
--------
|
||||
|
||||
## 界面预览:
|
||||
|
||||

|
||||

|
||||
|
||||
更多截图参见 https://github.com/pppscn/SmsForwarder/wiki
|
||||
|
||||
@ -58,8 +64,6 @@
|
||||
|
||||
> ⚠ 网盘下载:https://wws.lanzoui.com/b025yl86h 访问密码:`pppscn`
|
||||
|
||||
> ⚠ 酷安应用市场:https://www.coolapk.com/apk/com.idormy.sms.forwarder
|
||||
|
||||
--------
|
||||
|
||||
## 使用文档【新用户必看!】
|
||||
@ -68,7 +72,7 @@
|
||||
|
||||
> ⚠ Gitee Wiki:https://gitee.com/pp/SmsForwarder/wikis/pages
|
||||
|
||||

|
||||

|
||||
|
||||
--------
|
||||
|
||||
@ -77,14 +81,15 @@
|
||||
+ 提交issues 或 pr
|
||||
+ 加入交流群(群内都是机油互帮互助,禁止发任何与SmsForwarder使用无关的内容)
|
||||
|
||||
| 钉钉客户群 | QQ机油互助交流1群:562854376 | QQ机油互助交流2群:31330492 | 企业微信群 |
|
||||
| ---- | ---- | ---- | ---- |
|
||||
|  |  |  |  |
|
||||
|
||||
PS.如果QQ群已满员,请看群简介加入其他群
|
||||
| TG Group |
|
||||
|:---------------------------------------------------:|
|
||||
|  |
|
||||
| [+QBZgnL_fxYM0NjE9](https://t.me/+QBZgnL_fxYM0NjE9) |
|
||||
|
||||
## 感谢
|
||||
|
||||
> [感谢所有赞助本项目的热心网友 --> 打赏名单](https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427)
|
||||
|
||||
> 本项目得到以下项目的支持与帮助,在此表示衷心的感谢!
|
||||
|
||||
+ https://github.com/xiaoyuanhost/TranspondSms (项目原型)
|
||||
@ -94,13 +99,21 @@ PS.如果QQ群已满员,请看群简介加入其他群
|
||||
+ https://github.com/mainfunx/frpc_android (内网穿透)
|
||||
+ https://github.com/gyf-dev/Cactus (保活措施)
|
||||
+ https://github.com/yanzhenjie/AndServer (HttpServer)
|
||||
+ [<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="zoom:50%;" />](https://jb.gg/OpenSourceSupport) (License Certificate for JetBrains All Products Pack)
|
||||
+ https://github.com/jenly1314/Location (Location)
|
||||
+ https://gitee.com/xuankaicat/kmnkt (socket通信)
|
||||
+ [<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>
|
||||
|
||||
--------
|
||||
|
||||
|
45
README_en.md
45
README_en.md
@ -10,9 +10,21 @@
|
||||
|
||||
SmsForwarder - Not only forwarding text messages, but also a must-have for backup devices!
|
||||
|
||||
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, Feishi Bot, E-mail, Bark, Webhook, Telegram Bot, ServerChan, PushPlus, SMS, etc.
|
||||
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
|
||||
|
||||
@ -50,14 +62,14 @@ See more screenshots:https://github.com/pppscn/SmsForwarder/wiki
|
||||
|
||||
> ⚠ Internet storage: https://wws.lanzoui.com/b025yl86h, access password: `pppscn`
|
||||
|
||||
> ⚠ CoolAPK.com: https://www.coolapk.com/apk/com.idormy.sms.forwarder
|
||||
|
||||
## Manual
|
||||
|
||||
> ⚠ GitHub: https://github.com/pppscn/SmsForwarder/wiki
|
||||
|
||||
> ⚠ Gitee: https://gitee.com/pp/SmsForwarder/wikis/pages
|
||||
|
||||

|
||||
|
||||
--------
|
||||
|
||||
## Feedback and suggestions:
|
||||
@ -65,14 +77,15 @@ See more screenshots:https://github.com/pppscn/SmsForwarder/wiki
|
||||
+ Submit an issue or Pull Request.
|
||||
+ Join group chat (only Chinese groups/channels available currently)
|
||||
|
||||
| DingTalk | QQ user group #1: 562854376 | QQ user group #2: 31330492 | WeCom |
|
||||
| ---- | ---- | ---- | ---- |
|
||||
|  |  |  |  |
|
||||
|
||||
PS.If the QQ group is full, please see the group introduction to join other groups
|
||||
| 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)
|
||||
|
||||
> Thanks to the projects below, `SmsForwarder` won't exists without them!
|
||||
|
||||
+ https://github.com/xiaoyuanhost/TranspondSms (Foundation of `SmsForwarder`)
|
||||
@ -82,13 +95,21 @@ PS.If the QQ group is full, please see the group introduction to join other grou
|
||||
+ https://github.com/mainfunx/frpc_android (reverse proxy)
|
||||
+ https://github.com/gyf-dev/Cactus (Keep Alive)
|
||||
+ https://github.com/yanzhenjie/AndServer (HttpServer)
|
||||
+ [<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="zoom:50%;" />](https://jb.gg/OpenSourceSupport) (License Certificate for JetBrains All Products Pack)
|
||||
+ https://github.com/jenly1314/Location (Location)
|
||||
+ https://gitee.com/xuankaicat/kmnkt (socket)
|
||||
+ [<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>
|
||||
|
||||
--------
|
||||
|
||||
|
230
app/build.gradle
230
app/build.gradle
@ -1,3 +1,7 @@
|
||||
//file:noinspection DependencyNotationArgument
|
||||
import groovy.json.JsonBuilder
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
@ -5,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()
|
||||
@ -19,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
|
||||
@ -32,16 +52,25 @@ 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
|
||||
//vectorDrawables.useSupportLibrary = true
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
@ -71,8 +100,14 @@ android {
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
// 调试模式开关
|
||||
debuggable false
|
||||
jniDebuggable false
|
||||
// 移除无用的资源
|
||||
shrinkResources true
|
||||
// 代码混淆开关
|
||||
minifyEnabled true
|
||||
// 混淆配置
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
if (isNeedPackage.toBoolean()) {
|
||||
signingConfig signingConfigs.release
|
||||
@ -94,8 +129,14 @@ android {
|
||||
}
|
||||
}
|
||||
debug {
|
||||
minifyEnabled true
|
||||
// 调试模式开关
|
||||
debuggable true
|
||||
jniDebuggable true
|
||||
// 移除无用的资源
|
||||
shrinkResources true
|
||||
// 代码混淆开关
|
||||
minifyEnabled true
|
||||
// 混淆配置
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
if (isNeedPackage.toBoolean()) {
|
||||
signingConfig signingConfigs.release
|
||||
@ -116,13 +157,6 @@ android {
|
||||
buildConfigField "String", "APP_ID_UMENG", '"60254fc7425ec25f10f4293e"'
|
||||
}
|
||||
}
|
||||
/*debug {
|
||||
debuggable true
|
||||
minifyEnabled false
|
||||
|
||||
signingConfig signingConfigs.debug
|
||||
buildConfigField "String", "APP_ID_UMENG", '"60254fc7425ec25f10f4293e"'
|
||||
}*/
|
||||
}
|
||||
|
||||
//ABI配置——按CPU架构分别打包
|
||||
@ -137,33 +171,51 @@ android {
|
||||
def abiCodes = ['universal': 1, 'armeabi-v7a': 2, 'arm64-v8a': 3, 'x86': 4, 'x86_64': 5]
|
||||
packagingOptions {
|
||||
//去除FrpcLib的so,用时下载并动态加载
|
||||
if (isNeedPackage.toBoolean()) {
|
||||
if (excludeFrpclib.toBoolean()) {
|
||||
exclude 'lib/armeabi-v7a/libgojni.so'
|
||||
exclude 'lib/arm64-v8a/libgojni.so'
|
||||
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.all { variant ->
|
||||
// Assigns a different version code for each output APK.
|
||||
variant.outputs.each {
|
||||
output ->
|
||||
def date = new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+08"))
|
||||
|
||||
android.applicationVariants.configureEach { variant ->
|
||||
variant.outputs.each { output ->
|
||||
//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 = "SmsForwarder_${variant.name}_${versionName}_${output.versionCode}_${date}_${abiName}.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
bundle {
|
||||
language {
|
||||
enableSplit = false
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@ -171,7 +223,30 @@ android {
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
lint {
|
||||
abortOnError false
|
||||
}
|
||||
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 {
|
||||
@ -179,16 +254,22 @@ dependencies {
|
||||
//frpc
|
||||
implementation files('libs/frpclib.aar')
|
||||
|
||||
//MQTT协议
|
||||
implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5")
|
||||
|
||||
testImplementation deps.junit
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation deps.espresso.core
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.8.0'
|
||||
implementation "androidx.activity:activity-ktx:1.5.1"
|
||||
implementation "androidx.fragment:fragment-ktx:1.5.1"
|
||||
//noinspection GradleDependency
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
//noinspection GradleDependency
|
||||
implementation 'androidx.activity:activity-ktx:1.6.1'
|
||||
//noinspection GradleDependency
|
||||
implementation 'androidx.fragment:fragment-ktx:1.5.5'
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
|
||||
//分包
|
||||
implementation deps.androidx.multidex
|
||||
@ -196,18 +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'//选填
|
||||
//腾讯的键值对存储mmkv:https://github.com/Tencent/MMKV
|
||||
implementation 'com.tencent:mmkv:1.2.13'
|
||||
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.5.0'
|
||||
implementation 'com.umeng.umsdk:asms:1.6.3'
|
||||
//友盟统计
|
||||
implementation 'com.umeng.umsdk:common:9.6.8'
|
||||
implementation 'com.umeng.umsdk:asms:1.8.6'
|
||||
|
||||
//预加载占位控件
|
||||
implementation 'me.samlss:broccoli:1.0.0'
|
||||
@ -216,57 +295,106 @@ dependencies {
|
||||
implementation 'com.zzhoujay.richtext:richtext:3.0.8'
|
||||
|
||||
//美团多渠道打包
|
||||
implementation 'com.meituan.android.walle:library:1.1.6'
|
||||
//implementation 'com.meituan.android.walle:library:1.1.6'
|
||||
|
||||
api("androidx.work:work-multiprocess:2.7.1")
|
||||
api("androidx.work:work-runtime-ktx:2.7.1")
|
||||
def work_version = '2.8.1'
|
||||
//noinspection GradleDependency
|
||||
api("androidx.work:work-multiprocess:$work_version")
|
||||
//noinspection GradleDependency
|
||||
api("androidx.work:work-runtime-ktx:$work_version")
|
||||
|
||||
//Android Room
|
||||
def room_version = '2.4.3'
|
||||
def room_version = '2.5.2'
|
||||
//noinspection GradleDependency
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
//noinspection GradleDependency
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
//noinspection GradleDependency
|
||||
implementation "androidx.room:room-paging:$room_version"
|
||||
//noinspection GradleDependency
|
||||
implementation "androidx.room:room-rxjava2:$room_version"
|
||||
//noinspection KaptUsageInsteadOfKsp
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
|
||||
//CodeView:https://github.com/AmrDeveloper/CodeView
|
||||
implementation 'com.github.AmrDeveloper:CodeView:1.3.5'
|
||||
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"
|
||||
//noinspection GradleDependency
|
||||
implementation "androidx.paging:paging-runtime-ktx:$paging_version"
|
||||
// alternatively - without Android dependencies for tests
|
||||
//noinspection GradleDependency
|
||||
testImplementation "androidx.paging:paging-common-ktx:$paging_version"
|
||||
|
||||
//权限请求框架:https://github.com/getActivity/XXPermissions
|
||||
implementation 'com.github.getActivity:XXPermissions:15.0'
|
||||
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
|
||||
implementation 'com.gyf.cactus:cactus:1.1.3-beta13'
|
||||
|
||||
//HTTP服务器:https://github.com/yanzhenjie/AndServer
|
||||
implementation 'com.yanzhenjie.andserver:api:2.1.10'
|
||||
kapt 'com.yanzhenjie.andserver:processor:2.1.10'
|
||||
implementation 'cn.ppps.andserver:api:2.1.12'
|
||||
kapt 'cn.ppps.andserver:processor:2.1.12'
|
||||
|
||||
//Location 是一个通过 Android 自带的 LocationManager 来实现的定位功能:https://github.com/jenly1314/Location
|
||||
implementation 'com.github.pppscn:location:1.0.0'
|
||||
|
||||
//Partial implementation of Quartz Cron Java for Android: https://github.com/gatewayapps/crondroid
|
||||
implementation 'gatewayapps.crondroid:crondroid:1.0.0'
|
||||
//Java Parser For Cron Expressions: https://github.com/grahamar/cron-parser
|
||||
implementation 'net.redhogs.cronparser:cron-parser-core:3.5'
|
||||
|
||||
//侧边栏菜单:https://github.com/yarolegovich/SlidingRootNav
|
||||
implementation 'com.yarolegovich:sliding-root-nav:1.1.1'
|
||||
}
|
||||
//自动添加X-Library依赖
|
||||
apply from: 'x-library.gradle'
|
||||
//walle多渠道打包
|
||||
apply from: 'multiple-channel.gradle'
|
||||
|
||||
//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.
Binary file not shown.
51
app/proguard-rules.pro
vendored
51
app/proguard-rules.pro
vendored
@ -4,13 +4,13 @@
|
||||
#包名不混合大小写
|
||||
-dontusemixedcaseclassnames
|
||||
#不去忽略非公共的库类
|
||||
-dontskipnonpubliclibraryclasses
|
||||
#-dontskipnonpubliclibraryclasses
|
||||
# 指定不去忽略非公共的库的类的成员
|
||||
-dontskipnonpubliclibraryclassmembers
|
||||
#-dontskipnonpubliclibraryclassmembers
|
||||
#优化 不优化输入的类文件
|
||||
-dontoptimize
|
||||
#预校验
|
||||
-dontpreverify
|
||||
#-dontpreverify
|
||||
#混淆时是否记录日志
|
||||
-verbose
|
||||
# 混淆时所采用的算法
|
||||
@ -22,7 +22,7 @@
|
||||
|
||||
##记录生成的日志数据,gradle build时在本项目根目录输出##
|
||||
#apk 包内所有 class 的内部结构
|
||||
-dump class_files.txt
|
||||
#-dump class_files.txt
|
||||
#未混淆的类和成员
|
||||
-printseeds seeds.txt
|
||||
#列出从 apk 中删除的代码
|
||||
@ -138,12 +138,12 @@
|
||||
-keep class microsoft.aspnet.signalr.** { *; }
|
||||
|
||||
# 极光推送混淆
|
||||
-dontoptimize
|
||||
-dontpreverify
|
||||
-dontwarn cn.jpush.**
|
||||
-keep class cn.jpush.** { *; }
|
||||
-dontwarn cn.jiguang.**
|
||||
-keep class cn.jiguang.** { *; }
|
||||
#-dontoptimize
|
||||
#-dontpreverify
|
||||
#-dontwarn cn.jpush.**
|
||||
#-keep class cn.jpush.** { *; }
|
||||
#-dontwarn cn.jiguang.**
|
||||
#-keep class cn.jiguang.** { *; }
|
||||
|
||||
# 数据库框架OrmLite
|
||||
-keepattributes *DatabaseField*
|
||||
@ -166,8 +166,6 @@
|
||||
-dontwarn com.squareup.okhttp3.**
|
||||
-keep class com.squareup.okhttp3.** { *;}
|
||||
-dontwarn okio.**
|
||||
-dontwarn javax.annotation.Nullable
|
||||
-dontwarn javax.annotation.ParametersAreNonnullByDefault
|
||||
-dontwarn javax.annotation.**
|
||||
|
||||
#如果用到Gson解析包的,直接添加下面这几行就能成功混淆,不然会报错
|
||||
@ -208,8 +206,6 @@
|
||||
#}
|
||||
|
||||
-dontwarn okio.**
|
||||
-dontwarn javax.annotation.Nullable
|
||||
-dontwarn javax.annotation.ParametersAreNonnullByDefault
|
||||
-dontwarn javax.annotation.**
|
||||
|
||||
# fastjson
|
||||
@ -286,3 +282,30 @@
|
||||
-keep class javax.activation.** { *;}
|
||||
-keep class com.smailnet.emailkit.** { *;}
|
||||
-keep class com.idormy.sms.forwarder.utils.mail.** {*;}
|
||||
-keep class com.gitee.xuankaicat.kmnkt.** {*;}
|
||||
-keep class org.eclipse.paho.client.** {*;}
|
||||
|
||||
-keep public class com.xuexiang.xrouter.routes.**{*;}
|
||||
-keep class * implements com.xuexiang.xrouter.facade.template.ISyringe{*;}
|
||||
# 如果使用了 byType 的方式获取 Service,需添加下面规则,保护接口
|
||||
-keep interface * implements com.xuexiang.xrouter.facade.template.IProvider
|
||||
# 如果使用了 单类注入,即不定义接口实现 IProvider,需添加下面规则,保护实现
|
||||
-keep class * implements com.xuexiang.xrouter.facade.template.IProvider
|
||||
|
||||
-dontwarn com.alipay.sdk.**
|
||||
-dontwarn com.android.org.conscrypt.**
|
||||
-dontwarn java.awt.image.**
|
||||
-dontwarn javax.lang.model.**
|
||||
-dontwarn javax.naming.**
|
||||
-dontwarn javax.naming.directory.**
|
||||
|
||||
# This is generated automatically by the Android Gradle plugin.
|
||||
-dontwarn org.joda.convert.**
|
||||
-dontwarn org.slf4j.impl.**
|
||||
|
||||
# MultiLanguages
|
||||
-keep class com.hjq.language.** {*;}
|
||||
|
||||
# crontab解析
|
||||
-keep class gatewayapps.crondroid.** { *; }
|
||||
-keep class net.redhogs.cronparser.** { *; }
|
||||
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 xuexiangjys(xuexiangjys@163.com)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.idormy.sms.forwarder
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see [Testing documentation](http://d.android.com/tools/testing)
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
Assert.assertEquals("com.idormy.sms.forwarder", appContext.packageName)
|
||||
}
|
||||
}
|
@ -1,17 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.idormy.sms.forwarder"
|
||||
android:installLocation="internalOnly">
|
||||
|
||||
<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" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<!-- 授予应用程序访问系统开机事件的权限 -->
|
||||
<uses-permission
|
||||
@ -19,10 +29,14 @@
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_SMS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_MMS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
|
||||
<uses-permission android:name="android.permission.READ_SMS" />
|
||||
<uses-permission android:name="android.permission.SEND_SMS" />
|
||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||
@ -38,6 +52,8 @@
|
||||
<uses-permission
|
||||
android:name="android.permission.BATTERY_STATS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.CANCEL_NOTIFICATIONS " />
|
||||
<uses-permission
|
||||
android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
@ -53,11 +69,23 @@
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_LOGS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
<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"
|
||||
android:allowBackup="true"
|
||||
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
|
||||
android:defaultToDeviceProtectedStorage="true"
|
||||
android:directBootAware="true"
|
||||
android:enableOnBackInvokedCallback="false"
|
||||
android:fullBackupContent="@xml/backup_descriptor"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
@ -70,8 +98,7 @@
|
||||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:windowSoftInputMode="adjustPan|stateHidden"
|
||||
tools:ignore="DataExtractionRules,LockedOrientationActivity,UnusedAttribute"
|
||||
tools:replace="android:allowBackup">
|
||||
tools:ignore="DataExtractionRules,LockedOrientationActivity,UnusedAttribute">
|
||||
|
||||
<meta-data
|
||||
android:name="ScopedStorage"
|
||||
@ -86,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" />
|
||||
@ -98,14 +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"
|
||||
tools:ignore="DiscouragedApi" />
|
||||
<!--通用浏览器-->
|
||||
<activity
|
||||
android:name=".core.webview.AgentWebActivity"
|
||||
@ -126,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
|
||||
@ -163,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
|
||||
@ -195,16 +232,21 @@
|
||||
android:value="640" />
|
||||
|
||||
<service
|
||||
android:name=".service.HttpService"
|
||||
android:enabled="true" />
|
||||
<service
|
||||
android:name=".service.BatteryService"
|
||||
android:enabled="true" />
|
||||
android:name=".service.BluetoothScanService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name=".service.ForegroundService"
|
||||
android:enabled="true" />
|
||||
<service
|
||||
android:name=".service.NotifyService"
|
||||
android:name=".service.HttpServerService"
|
||||
android:enabled="true" />
|
||||
<service
|
||||
android:name=".service.LocationService"
|
||||
android:enabled="true"
|
||||
android:foregroundServiceType="location" />
|
||||
<service
|
||||
android:name=".service.NotificationService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:label="@string/app_name"
|
||||
@ -215,7 +257,39 @@
|
||||
</service>
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.BootReceiver"
|
||||
android:name=".receiver.BatteryReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<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"
|
||||
android:directBootAware="true"
|
||||
android:exported="true"
|
||||
tools:ignore="IntentFilterExportedReceiver">
|
||||
@ -226,6 +300,52 @@
|
||||
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".receiver.CallReceiver"
|
||||
android:exported="true"
|
||||
tools:ignore="IntentFilterExportedReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PHONE_STATE" />
|
||||
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".receiver.LockScreenReceiver"
|
||||
android:exported="true">
|
||||
<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
|
||||
android:name=".receiver.NetworkChangeReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.READ_PHONE_STATE">
|
||||
<intent-filter>
|
||||
<action
|
||||
android:name="android.net.conn.CONNECTIVITY_CHANGE"
|
||||
tools:ignore="BatteryLife" />
|
||||
<action
|
||||
android:name="android.net.wifi.WIFI_STATE_CHANGED"
|
||||
tools:ignore="BatteryLife" />
|
||||
<action
|
||||
android:name="android.net.wifi.STATE_CHANGE"
|
||||
tools:ignore="BatteryLife" />
|
||||
<!--<action
|
||||
android:name="android.intent.action.DATA_CONNECTION_STATE_CHANGED"
|
||||
tools:ignore="BatteryLife" />-->
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".receiver.SimStateReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SIM_STATE_CHANGED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".receiver.SmsReceiver"
|
||||
android:exported="true"
|
||||
@ -238,15 +358,26 @@
|
||||
<!--短信广播-->
|
||||
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".receiver.PhoneStateReceiver"
|
||||
android:exported="true"
|
||||
tools:ignore="IntentFilterExportedReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PHONE_STATE" />
|
||||
<intent-filter android:priority="2147483647">
|
||||
<action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED" />
|
||||
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
|
||||
|
||||
<data android:mimeType="application/vnd.wap.mms-message" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false"
|
||||
tools:node="merge">
|
||||
<!-- If you are using androidx.startup to initialize other components -->
|
||||
<meta-data
|
||||
android:name="androidx.work.WorkManagerInitializer"
|
||||
android:value="androidx.startup"
|
||||
tools:node="remove" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -1,37 +1,37 @@
|
||||
软件许可及服务协议
|
||||
【重要须知】
|
||||
|
||||
【福州多米信息科技有限公司】(如下简称“多米科技”)在此特别提醒用户认真阅读、充分理解本《软件许可及服务协议》(下称“本协议”)。用户应认真阅读、充分理解本协议中各条款,特别涉及免除或者限制多米科技责任、争议解决和法律适用的条款。免除或者限制责任的条款将以粗体标识,您需要重点阅读。请您审慎阅读并选择接受或不接受本协议(未成年人应在法定监护人陪同下阅读)。您的下载、安装、使用本软件以及账号获取和登录等行为将视为对本协议的接受,并同意接受本协议各项条款的约束。
|
||||
【SmsForwarder·短信转发器开发者】(如下简称“SmsF开发者”)在此特别提醒用户认真阅读、充分理解本《软件许可及服务协议》(下称“本协议”)。用户应认真阅读、充分理解本协议中各条款,特别涉及免除或者限制SmsF开发者责任、争议解决和法律适用的条款。免除或者限制责任的条款将以粗体标识,您需要重点阅读。请您审慎阅读并选择接受或不接受本协议(未成年人应在法定监护人陪同下阅读)。您的下载、安装、使用本软件以及账号获取和登录等行为将视为对本协议的接受,并同意接受本协议各项条款的约束。
|
||||
|
||||
多米科技有权修订本协议,更新后的协议条款将公布于官网或软件,自公布之日起生效。用户可重新下载安装本软件或网站查阅最新版协议条款。在多米科技修改本协议条款后,如果用户不接受修改后的条款,请立即停止使用多米科技提供的“多米科技”软件和服务,用户继续使用多米科技提供的“多米科技”软件和服务将被视为已接受了修改后的协议。
|
||||
SmsF开发者有权修订本协议,更新后的协议条款将公布于官网或软件,自公布之日起生效。用户可重新下载安装本软件或网站查阅最新版协议条款。在SmsF开发者修改本协议条款后,如果用户不接受修改后的条款,请立即停止使用SmsF开发者提供的“SmsF开发者”软件和服务,用户继续使用SmsF开发者提供的“SmsF开发者”软件和服务将被视为已接受了修改后的协议。
|
||||
|
||||
一、总则
|
||||
|
||||
1.1. 本协议是您(如下也称“用户”)与多米科技及其运营合作单位(如下简称“合作单位”)之间关于用户下载、安装、使用多米科技“多米科技”软件(下称“本软件”)以及使用多米科技相关服务所订立的协议。
|
||||
1.1. 本协议是您(如下也称“用户”)与SmsF开发者及其运营合作单位(如下简称“合作单位”)之间关于用户下载、安装、使用SmsF开发者“SmsF开发者”软件(下称“本软件”)以及使用SmsF开发者相关服务所订立的协议。
|
||||
|
||||
1.2. 本软件及服务是多米科技提供的安装在包括但不限于移动智能终端设备上的软件和服务,为使用该智能终端的用户提供绑定、操作智能产品等服务等。
|
||||
1.2. 本软件及服务是SmsF开发者提供的安装在包括但不限于移动智能终端设备上的软件和服务,为使用该智能终端的用户提供绑定、操作智能产品等服务等。
|
||||
|
||||
1.3. 本软件及服务的所有权和运营权均归多米科技所有。
|
||||
1.3. 本软件及服务的所有权和运营权均归SmsF开发者所有。
|
||||
|
||||
二、软件授权范围
|
||||
|
||||
2.1. 多米科技就本软件给予用户一项个人的、不可转让、不可转授权以及非独占性的许可。
|
||||
2.1. SmsF开发者就本软件给予用户一项个人的、不可转让、不可转授权以及非独占性的许可。
|
||||
|
||||
2.2. 用户可以为非商业目的在单一台移动终端设备上安装、使用、显示、运行本软件。但用户不得为商业运营目的安装、使用、运行本软件,不可以对本软件或者本软件运行过程中释放到任何终端设备内存中的数据及本软件运行过程中客户端与服务器端的交互数据进行复制、更改、修改、挂接运行或创作任何衍生作品,形式包括但不限于使用插件、外挂或非经授权的第三方工具/服务接入本软件和相关系统。如果需要进行商业性的销售、复制和散发,例如软件预装和捆绑,必须获得多米科技的书面授权和许可。
|
||||
2.2. 用户可以为非商业目的在单一台移动终端设备上安装、使用、显示、运行本软件。但用户不得为商业运营目的安装、使用、运行本软件,不可以对本软件或者本软件运行过程中释放到任何终端设备内存中的数据及本软件运行过程中客户端与服务器端的交互数据进行复制、更改、修改、挂接运行或创作任何衍生作品,形式包括但不限于使用插件、外挂或非经授权的第三方工具/服务接入本软件和相关系统。如果需要进行商业性的销售、复制和散发,例如软件预装和捆绑,必须获得SmsF开发者的书面授权和许可。
|
||||
|
||||
2.3. 用户不得未经多米科技许可,将本软件安装在未经多米科技明示许可的其他终端设备上,包括但不限于机顶盒、游戏机、电视机、DVD机等。
|
||||
2.3. 用户不得未经SmsF开发者许可,将本软件安装在未经SmsF开发者明示许可的其他终端设备上,包括但不限于机顶盒、游戏机、电视机、DVD机等。
|
||||
|
||||
2.4. 用户可以为使用本软件及服务的目的复制本软件的一个副本,仅用作备份。备份副本必须包含原软件中含有的所有著作权信息。
|
||||
|
||||
2.5. 除本《协议》明示授权外,多米科技未授权给用户其他权利,若用户使用其他权利时须另外取得多米科技的书面同意。
|
||||
2.5. 除本《协议》明示授权外,SmsF开发者未授权给用户其他权利,若用户使用其他权利时须另外取得SmsF开发者的书面同意。
|
||||
|
||||
三、软件的获取、安装、升级
|
||||
|
||||
3.1. 用户应当按照多米科技的指定网站或指定方式下载安装本软件产品。谨防在非指定网站下载本软件,以免移动终端设备感染能破坏用户数据和获取用户隐私信息的恶意程序。如果用户从未经多米科技授权的第三方获取本软件或与本软件名称相同的安装程序,多米科技无法保证该软件能够正常使用,并对因此给您造成的损失不予负责。
|
||||
3.1. 用户应当按照SmsF开发者的指定网站或指定方式下载安装本软件产品。谨防在非指定网站下载本软件,以免移动终端设备感染能破坏用户数据和获取用户隐私信息的恶意程序。如果用户从未经SmsF开发者授权的第三方获取本软件或与本软件名称相同的安装程序,SmsF开发者无法保证该软件能够正常使用,并对因此给您造成的损失不予负责。
|
||||
|
||||
3.2. 用户必须选择与所安装终端设备相匹配的本软件版本,否则,由于软件与设备型号不相匹配所导致的任何软件问题、设备问题或损害,均由用户自行承担。
|
||||
|
||||
3.3. 为了改善用户体验、完善服务内容,多米科技有权不时地为您提供本软件替换、修改、升级版本,也有权为替换、修改或升级收取费用,但将收费提前征得您的同意。本软件为用户默认开通“升级提示”功能,视用户使用的软件版本差异,多米科技提供给用户自行选择是否需要开通此功能。软件新版本发布后,多米科技不保证旧版本软件的继续可用。
|
||||
3.3. 为了改善用户体验、完善服务内容,SmsF开发者有权不时地为您提供本软件替换、修改、升级版本,也有权为替换、修改或升级收取费用,但将收费提前征得您的同意。本软件为用户默认开通“升级提示”功能,视用户使用的软件版本差异,SmsF开发者提供给用户自行选择是否需要开通此功能。软件新版本发布后,SmsF开发者不保证旧版本软件的继续可用。
|
||||
|
||||
四、使用规范
|
||||
|
||||
@ -41,12 +41,12 @@
|
||||
4.1.2. 对本软件进行反向工程,如反汇编、反编译或者其他试图获得本软件的源代码;
|
||||
4.1.3. 通过修改或伪造软件运行中的指令、数据,增加、删减、变动软件的功能或运行效果,或者将用于上述用途的软件、方法进行运营或向公众传播,无论这些行为是否为商业目的;
|
||||
4.1.4. 使用本软件进行任何危害网络安全的行为,包括但不限于:使用未经许可的数据或进入未经许可的服务器/账户;未经允许进入公众网络或者他人操作系统并删除、修改、增加存储信息;未经许可企图探查、扫描、测试本软件的系统或网络的弱点或其它实施破坏网络安全的行为; 企图干涉、破坏本软件系统或网站的正常运行,故意传播恶意程序或病毒以及其他破坏干扰正常网络信息服务的行为;伪造TCP/IP数据包名称或部分名称;
|
||||
4.1.5. 用户通过非多米科技公司开发、授权或认可的第三方兼容软件、系统登录或使用本软件及服务,或制作、发布、传播上述工具;
|
||||
4.1.6. 未经多米科技书面同意,用户对软件及其中的信息擅自实施包括但不限于下列行为:使用、出租、出借、复制、修改、链接、转载、汇编、发表、出版,建立镜像站点、擅自借助本软件发展与之有关的衍生产品、作品、服务、插件、外挂、兼容、互联等;
|
||||
4.1.5. 用户通过非SmsF开发者公司开发、授权或认可的第三方兼容软件、系统登录或使用本软件及服务,或制作、发布、传播上述工具;
|
||||
4.1.6. 未经SmsF开发者书面同意,用户对软件及其中的信息擅自实施包括但不限于下列行为:使用、出租、出借、复制、修改、链接、转载、汇编、发表、出版,建立镜像站点、擅自借助本软件发展与之有关的衍生产品、作品、服务、插件、外挂、兼容、互联等;
|
||||
4.1.7. 利用本软件发表、传送、传播、储存违反当地法律法规的内容;
|
||||
4.1.8. 利用本软件发表、传送、传播、储存侵害他人知识产权、商业秘密等合法权利的内容;
|
||||
4.1.9. 利用本软件批量发表、传送、传播广告信息及垃圾信息;
|
||||
4.1.10. 其他以任何不合法的方式、为任何不合法的目的、或以任何与本协议许可使用不一致的方式使用本软件和多米科技提供的其他服务;
|
||||
4.1.10. 其他以任何不合法的方式、为任何不合法的目的、或以任何与本协议许可使用不一致的方式使用本软件和SmsF开发者提供的其他服务;
|
||||
4.2. 信息发布规范
|
||||
|
||||
4.2.1.您可使用本软件发表属于您原创或您有权发表的观点看法、数据、文字、信息、用户名、图片、照片、个人信息、音频、视频文件、链接等信息内容。您必须保证,您拥有您所上传信息内容的知识产权或已获得合法授权,您使用本软件及服务的任何行为未侵犯任何第三方之合法权益。
|
||||
@ -63,27 +63,27 @@
|
||||
|
||||
4.2.3.5.从事其他违反当地法律法规的行为。
|
||||
|
||||
4.2.4. 未经多米科技许可,您不得在本软件中进行任何诸如发布广告、销售商品的商业行为。
|
||||
4.2.4. 未经SmsF开发者许可,您不得在本软件中进行任何诸如发布广告、销售商品的商业行为。
|
||||
|
||||
4.3.您理解并同意:
|
||||
|
||||
4.3.1. 多米科技会对用户是否涉嫌违反上述使用规范做出认定,并根据认定结果中止、终止对您的使用许可或采取其他依本约定可采取的限制措施;
|
||||
4.3.2. 对于用户使用许可软件时发布的涉嫌违法或涉嫌侵犯他人合法权利或违反本协议的信息,多米科技会直接删除;
|
||||
4.3.3. 对于用户违反上述使用规范的行为对第三方造成损害的,您需要以自己的名义独立承担法律责任,并应确保多米科技免于因此产生损失或增加费用;
|
||||
4.3.4.若用户违反有关法律规定或协议约定,使多米科技遭受损失,或受到第三方的索赔,或受到行政管理机关的处罚,用户应当赔偿多米科技因此造成的损失和(或)发生的费用,包括合理的律师费、调查取证费用。
|
||||
4.3.1. SmsF开发者会对用户是否涉嫌违反上述使用规范做出认定,并根据认定结果中止、终止对您的使用许可或采取其他依本约定可采取的限制措施;
|
||||
4.3.2. 对于用户使用许可软件时发布的涉嫌违法或涉嫌侵犯他人合法权利或违反本协议的信息,SmsF开发者会直接删除;
|
||||
4.3.3. 对于用户违反上述使用规范的行为对第三方造成损害的,您需要以自己的名义独立承担法律责任,并应确保SmsF开发者免于因此产生损失或增加费用;
|
||||
4.3.4.若用户违反有关法律规定或协议约定,使SmsF开发者遭受损失,或受到第三方的索赔,或受到行政管理机关的处罚,用户应当赔偿SmsF开发者因此造成的损失和(或)发生的费用,包括合理的律师费、调查取证费用。
|
||||
五、服务风险及免责声明
|
||||
|
||||
5.1. 用户必须自行配备移动终端设备上网和使用电信增值业务所需的设备,自行负担个人移动终端设备上网或第三方(包括但不限于电信或移动通信提供商)收取的通讯费、信息费等有关费用。如涉及电信增值服务的,我们建议您与您的电信增值服务提供商确认相关的费用问题。
|
||||
|
||||
5.2. 用户因第三方如通讯线路故障、技术问题、网络、移动终端设备故障、系统不稳定性及其他各种不可抗力原因而遭受的一切损失,多米科技及合作单位不承担责任。
|
||||
5.2. 用户因第三方如通讯线路故障、技术问题、网络、移动终端设备故障、系统不稳定性及其他各种不可抗力原因而遭受的一切损失,SmsF开发者及合作单位不承担责任。
|
||||
|
||||
5.3. 本软件同大多数互联网软件一样,受包括但不限于用户原因、网络服务质量、社会环境等因素的差异影响,可能受到各种安全问题的侵扰,如他人利用用户的资料,造成现实生活中的骚扰;用户下载安装的其它软件或访问的其他网站中含有“特洛伊木马”等病毒,威胁到用户的终端设备信息和数据的安全,继而影响本软件的正常使用等等。用户应加强信息安全及使用者资料的保护意识,要注意加强密码保护,以免遭致损失和骚扰。
|
||||
|
||||
5.4. 因用户使用本软件或要求多米科技提供特定服务时,本软件可能会调用第三方系统或第三方软件支持用户的使用或访问,使用或访问的结果由该第三方提供,多米科技不保证通过第三方系统或第三方软件支持实现的结果的安全性、准确性、有效性及其他不确定的风险,由此若引发的任何争议及损害,多米科技不承担任何责任。
|
||||
5.4. 因用户使用本软件或要求SmsF开发者提供特定服务时,本软件可能会调用第三方系统或第三方软件支持用户的使用或访问,使用或访问的结果由该第三方提供,SmsF开发者不保证通过第三方系统或第三方软件支持实现的结果的安全性、准确性、有效性及其他不确定的风险,由此若引发的任何争议及损害,SmsF开发者不承担任何责任。
|
||||
|
||||
5.5. 多米科技特别提请用户注意,多米科技为了保障公司业务发展和调整的自主权,多米科技公司拥有随时修改或中断服务而不需通知用户的权利,多米科技行使修改或中断服务的权利不需对用户或任何第三方负责。
|
||||
5.5. SmsF开发者特别提请用户注意,SmsF开发者为了保障公司业务发展和调整的自主权,SmsF开发者公司拥有随时修改或中断服务而不需通知用户的权利,SmsF开发者行使修改或中断服务的权利不需对用户或任何第三方负责。
|
||||
|
||||
5.6. 除法律法规有明确规定外,我们将尽最大努力确保软件及其所涉及的技术及信息安全、有效、准确、可靠,但受限于现有技术,用户理解多米科技不能对此进行担保。
|
||||
5.6. 除法律法规有明确规定外,我们将尽最大努力确保软件及其所涉及的技术及信息安全、有效、准确、可靠,但受限于现有技术,用户理解SmsF开发者不能对此进行担保。
|
||||
|
||||
5.7. 由于用户因下述任一情况所引起或与此有关的人身伤害或附带的、间接的经济损害赔偿,包括但不限于利润损失、资料损失、业务中断的损害赔偿或其他商业损害赔偿或损失,需由用户自行承担:
|
||||
|
||||
@ -91,17 +91,17 @@
|
||||
5.7.2.第三方未经许可的使用软件或更改用户的数据;
|
||||
5.7.3.用户使用软件进行的行为产生的费用及损失;
|
||||
5.7.4.用户对软件的误解;
|
||||
5.7.5.非因多米科技的原因引起的与软件有关的其他损失。
|
||||
5.7.5.非因SmsF开发者的原因引起的与软件有关的其他损失。
|
||||
5.8. 用户与其他使用软件的用户之间通过软件进行的行为,因您受误导或欺骗而导致或可能导致的任何人身或经济上的伤害或损失,均由过错方依法承担所有责任。
|
||||
|
||||
六、知识产权声明
|
||||
|
||||
6.1. 多米科技是本软件的知识产权权利人。本软件的一切著作权、商标权、专利权、商业秘密等知识产权,以及与本软件相关的所有信息内容(包括但不限于文字、图片、音 频、视频、图表、界面设计、版面框架、有关数据或电子文档等)均受您所在当地法律法规和相应的国际条约保护,多米科技享有上述知识产权。
|
||||
6.1. SmsF开发者是本软件的知识产权权利人。本软件的一切著作权、商标权、专利权、商业秘密等知识产权,以及与本软件相关的所有信息内容(包括但不限于文字、图片、音 频、视频、图表、界面设计、版面框架、有关数据或电子文档等)均受您所在当地法律法规和相应的国际条约保护,SmsF开发者享有上述知识产权。
|
||||
|
||||
6.2 未经多米科技书面同意,用户不得为任何商业或非商业目的自行或许可任何第三方实施、利用、转让上述知识产权,多米科技保留追究上述行为法律责任的权利。
|
||||
6.2 未经SmsF开发者书面同意,用户不得为任何商业或非商业目的自行或许可任何第三方实施、利用、转让上述知识产权,SmsF开发者保留追究上述行为法律责任的权利。
|
||||
|
||||
七、协议变更
|
||||
|
||||
7.1. 多米科技有权在必要时修改本协议条款,协议条款一旦发生变动,将会在相关页面上公布修改后的协议条款。如果不同意所改动的内容,用户应主动取消此项服务。如果用户继续使用服务,则视为接受协议条款的变动。
|
||||
7.1. SmsF开发者有权在必要时修改本协议条款,协议条款一旦发生变动,将会在相关页面上公布修改后的协议条款。如果不同意所改动的内容,用户应主动取消此项服务。如果用户继续使用服务,则视为接受协议条款的变动。
|
||||
|
||||
7.2. 多米科技和合作公司有权按需要修改或变更所提供的收费服务、收费标准、收费方式、服务费及服务条款。多米科技在提供服务时,可能现在或日后对部分服务的用户开始收取一定的费用如用户拒绝支付该等费用,则不能在收费开始后继续使用相关的服务。多米科技和合作公司将尽最大努力通过电邮或其他方式通知用户有关的修改或变更。
|
||||
7.2. SmsF开发者和合作公司有权按需要修改或变更所提供的收费服务、收费标准、收费方式、服务费及服务条款。SmsF开发者在提供服务时,可能现在或日后对部分服务的用户开始收取一定的费用如用户拒绝支付该等费用,则不能在收费开始后继续使用相关的服务。SmsF开发者和合作公司将尽最大努力通过电邮或其他方式通知用户有关的修改或变更。
|
||||
|
@ -2,12 +2,20 @@
|
||||
"Code": 0,
|
||||
"Data": [
|
||||
{
|
||||
"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没找到答案的,再加入QQ互助交流群里提问,请清楚地描述问题,并给出对应的配置截图与相关日志,方便大家直观的判断问题! "
|
||||
"title": "短信转发器",
|
||||
"content": "本软件用于监控Android手机短信、来电、APP通知,并根据指定规则转发到其他设备!<br />\n请确认您是否清楚该软件的用途?!<br />\n否则,请立即卸载!"
|
||||
},
|
||||
{
|
||||
"title": "QQ互助交流群",
|
||||
"content": "<a href=\"http://qm.qq.com/cgi-bin/qm/qr?k=Mj5m39bqy6eodOImrFLI19Tdeqvv-9zf\">QQ互助交流①群</a><br /><a href=\"http://qm.qq.com/cgi-bin/qm/qr?k=jPXy4YaUzA7Uo0yPPbZXdkb66NS1smU_\">QQ互助交流②群</a><br /><a href=\"https://qm.qq.com/cgi-bin/qm/qr?k=itGVH4lB-HLGyJGTfP_5rjyCQj6kgIBt\">QQ互助交流③群</a><br /><a href=\"https://qm.qq.com/cgi-bin/qm/qr?k=83fYtikg2ARpUECsgJv9CcWTKQB74REK\">QQ互助交流④群</a><br /><a href=\"https://qm.qq.com/cgi-bin/qm/qr?k=CcamLcA-QVN-KqCDjeMZqdTx8IGlJrVx\">QQ互助交流⑤群</a>"
|
||||
"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://t.me/+QBZgnL_fxYM0NjE9\">Telegram 群组</a>"
|
||||
},
|
||||
{
|
||||
"title": "打赏名单",
|
||||
|
@ -3,54 +3,89 @@ 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
|
||||
import android.location.Geocoder
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
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
|
||||
import com.hjq.language.MultiLanguages
|
||||
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.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.service.BatteryService
|
||||
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.HttpService
|
||||
import com.idormy.sms.forwarder.utils.*
|
||||
import com.idormy.sms.forwarder.service.HttpServerService
|
||||
import com.idormy.sms.forwarder.service.LocationService
|
||||
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.xuexiang.xutil.app.AppUtils
|
||||
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("PrivatePropertyName")
|
||||
@Suppress("DEPRECATION")
|
||||
class App : Application(), CactusCallback, Configuration.Provider by Core {
|
||||
|
||||
val applicationScope = CoroutineScope(SupervisorJob())
|
||||
val database by lazy { AppDatabase.getInstance(this) }
|
||||
val frpcRepository by lazy { FrpcRepository(database.frpcDao()) }
|
||||
val msgRepository by lazy { MsgRepository(database.msgDao()) }
|
||||
val logsRepository by lazy { LogsRepository(database.logsDao()) }
|
||||
val ruleRepository by lazy { RuleRepository(database.ruleDao()) }
|
||||
val senderRepository by lazy { SenderRepository(database.senderDao()) }
|
||||
val taskRepository by lazy { TaskRepository(database.taskDao()) }
|
||||
|
||||
companion object {
|
||||
const val TAG: String = "SmsForwarder"
|
||||
@ -58,42 +93,88 @@ 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()
|
||||
|
||||
//已安装App信息
|
||||
var UserAppList: MutableList<AppUtils.AppInfo> = mutableListOf()
|
||||
var SystemAppList: MutableList<AppUtils.AppInfo> = mutableListOf()
|
||||
var LoadingAppList = false
|
||||
var UserAppList: MutableList<AppInfo> = mutableListOf()
|
||||
var SystemAppList: MutableList<AppInfo> = mutableListOf()
|
||||
|
||||
/**
|
||||
* @return 当前app是否是调试开发模式
|
||||
*/
|
||||
val isDebug: Boolean
|
||||
get() = BuildConfig.DEBUG
|
||||
|
||||
//Cactus结束时间
|
||||
val mEndDate = MutableLiveData<String>()
|
||||
|
||||
//Cactus上次存活时间
|
||||
val mLastTimer = MutableLiveData<String>()
|
||||
|
||||
//Cactus存活时间
|
||||
val mTimer = MutableLiveData<String>()
|
||||
|
||||
//Cactus运行状态
|
||||
val mStatus = MutableLiveData<Boolean>().apply { value = true }
|
||||
var isDebug: Boolean = BuildConfig.DEBUG
|
||||
|
||||
//Cactus相关
|
||||
val mEndDate = MutableLiveData<String>() //结束时间
|
||||
val mLastTimer = MutableLiveData<String>() //上次存活时间
|
||||
val mTimer = MutableLiveData<String>() //存活时间
|
||||
val mStatus = MutableLiveData<Boolean>().apply { value = true } //运行状态
|
||||
var mDisposable: Disposable? = null
|
||||
|
||||
//Location相关
|
||||
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) {
|
||||
super.attachBaseContext(base)
|
||||
//super.attachBaseContext(base)
|
||||
// 绑定语种
|
||||
super.attachBaseContext(MultiLanguages.attach(base))
|
||||
//解决4.x运行崩溃的问题
|
||||
MultiDex.install(this)
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
// 设置全局异常捕获
|
||||
val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
|
||||
throwable.printStackTrace()
|
||||
try {
|
||||
val logPath = this.cacheDir.absolutePath + "/logs"
|
||||
val logDir = File(logPath)
|
||||
if (!logDir.exists()) logDir.mkdirs()
|
||||
val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
|
||||
val currentDateTime = dateFormat.format(Date())
|
||||
val logFile = File(logPath, "crash_$currentDateTime.txt")
|
||||
BufferedWriter(FileWriter(logFile, true)).use { writer ->
|
||||
writer.append("$throwable\n")
|
||||
}
|
||||
} catch (ex: IOException) {
|
||||
ex.printStackTrace()
|
||||
}
|
||||
//使用默认的处理方式让APP停止运行
|
||||
defaultHandler?.uncaughtException(thread, throwable)
|
||||
}
|
||||
|
||||
try {
|
||||
context = applicationContext
|
||||
initLibs()
|
||||
@ -101,59 +182,87 @@ 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())
|
||||
}
|
||||
}
|
||||
|
||||
//启动前台服务
|
||||
val intent = Intent(this, ForegroundService::class.java)
|
||||
val foregroundServiceIntent = Intent(this, ForegroundService::class.java)
|
||||
foregroundServiceIntent.action = ACTION_START
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForegroundService(intent)
|
||||
startForegroundService(foregroundServiceIntent)
|
||||
} else {
|
||||
startService(intent)
|
||||
}
|
||||
|
||||
//电池状态监听
|
||||
val batteryServiceIntent = Intent(this, BatteryService::class.java)
|
||||
startService(batteryServiceIntent)
|
||||
|
||||
//异步获取所有已安装 App 信息
|
||||
if (SettingUtils.enableLoadAppList) {
|
||||
val enableLoadUserAppList = SettingUtils.enableLoadUserAppList
|
||||
val enableLoadSystemAppList = SettingUtils.enableLoadSystemAppList
|
||||
val get = GlobalScope.async(Dispatchers.IO) {
|
||||
val appInfoList = AppUtils.getAppsInfo()
|
||||
for (appInfo in appInfoList) {
|
||||
if (appInfo.isSystem && enableLoadSystemAppList) {
|
||||
SystemAppList.add(appInfo)
|
||||
} else if (enableLoadUserAppList) {
|
||||
UserAppList.add(appInfo)
|
||||
}
|
||||
}
|
||||
UserAppList.sortBy { appInfo -> appInfo.name }
|
||||
SystemAppList.sortBy { appInfo -> appInfo.name }
|
||||
}
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
runCatching {
|
||||
get.await()
|
||||
Log.d("GlobalScope", "AppUtils.getAppsInfo() Done")
|
||||
Log.d("GlobalScope", "UserAppList = $UserAppList")
|
||||
Log.d("GlobalScope", "SystemAppList = $SystemAppList")
|
||||
}.onFailure {
|
||||
Log.e("GlobalScope", it.message.toString())
|
||||
}
|
||||
}
|
||||
startService(foregroundServiceIntent)
|
||||
}
|
||||
|
||||
//启动HttpServer
|
||||
if (HttpServerUtils.enableServerAutorun) {
|
||||
startService(Intent(this, HttpService::class.java))
|
||||
Intent(this, HttpServerService::class.java).also {
|
||||
startService(it)
|
||||
}
|
||||
}
|
||||
|
||||
//启动LocationService
|
||||
if (SettingUtils.enableLocation) {
|
||||
val locationServiceIntent = Intent(this, LocationService::class.java)
|
||||
locationServiceIntent.action = ACTION_START
|
||||
startService(locationServiceIntent)
|
||||
}
|
||||
|
||||
//监听电量&充电状态变化
|
||||
val batteryReceiver = BatteryReceiver()
|
||||
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 {
|
||||
addAction(ConnectivityManager.CONNECTIVITY_ACTION)
|
||||
addAction(WifiManager.WIFI_STATE_CHANGED_ACTION)
|
||||
addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION)
|
||||
//addAction("android.intent.action.DATA_CONNECTION_STATE_CHANGED")
|
||||
}
|
||||
registerReceiver(networkReceiver, networkFilter)
|
||||
|
||||
//监听锁屏&解锁
|
||||
val lockScreenReceiver = LockScreenReceiver()
|
||||
val lockScreenFilter = IntentFilter().apply {
|
||||
addAction(Intent.ACTION_SCREEN_OFF)
|
||||
addAction(Intent.ACTION_SCREEN_ON)
|
||||
addAction(Intent.ACTION_USER_PRESENT)
|
||||
}
|
||||
registerReceiver(lockScreenReceiver, lockScreenFilter)
|
||||
|
||||
//Cactus 集成双进程前台服务,JobScheduler,onePix(一像素),WorkManager,无声音乐
|
||||
if (SettingUtils.enableCactus) {
|
||||
@ -173,7 +282,7 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
|
||||
setChannelId(FRONT_CHANNEL_ID) //渠道Id
|
||||
setChannelName(FRONT_CHANNEL_NAME) //渠道名
|
||||
setTitle(getString(R.string.app_name))
|
||||
setContent(SettingUtils.notifyContent.toString())
|
||||
setContent(SettingUtils.notifyContent)
|
||||
setSmallIcon(R.drawable.ic_forwarder)
|
||||
setLargeIcon(R.mipmap.ic_launcher)
|
||||
setPendingIntent(pendingIntent)
|
||||
@ -190,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回调")
|
||||
@ -206,6 +315,7 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "onCreate: $e")
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,14 +324,40 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
|
||||
*/
|
||||
private fun initLibs() {
|
||||
Core.init(this)
|
||||
// 转发历史工具类初始化
|
||||
HistoryUtils.init(this)
|
||||
// 配置文件初始化
|
||||
SharedPreference.init(applicationContext)
|
||||
// X系列基础库初始化
|
||||
XBasicLibInit.init(this)
|
||||
// 初始化日志打印
|
||||
isDebug = SettingUtils.enableDebugMode
|
||||
Log.init(applicationContext)
|
||||
// 转发历史工具类初始化
|
||||
HistoryUtils.init(applicationContext)
|
||||
// 版本更新初始化
|
||||
XUpdateInit.init(this)
|
||||
// 运营统计数据
|
||||
UMengInit.init(this)
|
||||
// 初始化语种切换框架
|
||||
MultiLanguages.init(this)
|
||||
// 设置语种变化监听器
|
||||
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")
|
||||
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")
|
||||
@ -238,13 +374,9 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
|
||||
}
|
||||
mLastTimer.postValue(dateFormat.format(Date(CactusSave.lastTimer * 1000)))
|
||||
mEndDate.postValue(CactusSave.endDate)
|
||||
mDisposable = Observable.interval(1, TimeUnit.SECONDS)
|
||||
.map {
|
||||
mDisposable = Observable.interval(1, TimeUnit.SECONDS).map {
|
||||
oldTimer + it
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { aLong ->
|
||||
}.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe { aLong ->
|
||||
CactusSave.timer = aLong
|
||||
CactusSave.date = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).run {
|
||||
format(Date())
|
||||
@ -263,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",
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -1,72 +1,95 @@
|
||||
package com.idormy.sms.forwarder.activity
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import android.widget.LinearLayout
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.gyf.cactus.ext.cactusUpdateNotification
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
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.WidgetItemAdapter
|
||||
import com.idormy.sms.forwarder.adapter.menu.DrawerAdapter
|
||||
import com.idormy.sms.forwarder.adapter.menu.DrawerItem
|
||||
import com.idormy.sms.forwarder.adapter.menu.SimpleItem
|
||||
import com.idormy.sms.forwarder.adapter.menu.SpaceItem
|
||||
import com.idormy.sms.forwarder.core.BaseActivity
|
||||
import com.idormy.sms.forwarder.core.webview.AgentWebActivity
|
||||
import com.idormy.sms.forwarder.database.AppDatabase
|
||||
import com.idormy.sms.forwarder.databinding.ActivityMainBinding
|
||||
import com.idormy.sms.forwarder.fragment.*
|
||||
import com.idormy.sms.forwarder.utils.*
|
||||
import com.idormy.sms.forwarder.fragment.AboutFragment
|
||||
import com.idormy.sms.forwarder.fragment.AppListFragment
|
||||
import com.idormy.sms.forwarder.fragment.ClientFragment
|
||||
import com.idormy.sms.forwarder.fragment.FrpcFragment
|
||||
import com.idormy.sms.forwarder.fragment.LogsFragment
|
||||
import com.idormy.sms.forwarder.fragment.RulesFragment
|
||||
import com.idormy.sms.forwarder.fragment.SendersFragment
|
||||
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
|
||||
import com.idormy.sms.forwarder.utils.FRPC_LIB_VERSION
|
||||
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.sdkinit.XUpdateInit
|
||||
import com.idormy.sms.forwarder.widget.GuideTipsDialog.Companion.showTips
|
||||
import com.idormy.sms.forwarder.widget.GuideTipsDialog.Companion.showTipsForce
|
||||
import com.idormy.sms.forwarder.workers.LoadAppListWorker
|
||||
import com.jeremyliao.liveeventbus.LiveEventBus
|
||||
import com.xuexiang.xaop.annotation.SingleClick
|
||||
import com.xuexiang.xhttp2.XHttp
|
||||
import com.xuexiang.xhttp2.callback.DownloadProgressCallBack
|
||||
import com.xuexiang.xhttp2.exception.ApiException
|
||||
import com.xuexiang.xpage.base.XPageFragment
|
||||
import com.xuexiang.xpage.core.PageOption
|
||||
import com.xuexiang.xpage.model.PageInfo
|
||||
import com.xuexiang.xui.adapter.FragmentAdapter
|
||||
import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder
|
||||
import com.xuexiang.xui.utils.DensityUtils
|
||||
import com.xuexiang.xui.XUI.getContext
|
||||
import com.xuexiang.xui.utils.ResUtils
|
||||
import com.xuexiang.xui.utils.ThemeUtils
|
||||
import com.xuexiang.xui.utils.ViewUtils
|
||||
import com.xuexiang.xui.utils.WidgetUtils
|
||||
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
|
||||
import com.xuexiang.xui.widget.dialog.materialdialog.GravityEnum
|
||||
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
|
||||
import com.xuexiang.xutil.common.CollectionUtils
|
||||
import com.xuexiang.xutil.file.FileUtils
|
||||
import frpclib.Frpclib
|
||||
import io.reactivex.CompletableObserver
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import com.xuexiang.xutil.net.NetworkUtils
|
||||
import com.yarolegovich.slidingrootnav.SlideGravity
|
||||
import com.yarolegovich.slidingrootnav.SlidingRootNav
|
||||
import com.yarolegovich.slidingrootnav.SlidingRootNavBuilder
|
||||
import com.yarolegovich.slidingrootnav.callback.DragStateListener
|
||||
import java.io.File
|
||||
|
||||
|
||||
@Suppress("DEPRECATION", "PrivatePropertyName")
|
||||
class MainActivity : BaseActivity<ActivityMainBinding?>(),
|
||||
View.OnClickListener,
|
||||
BottomNavigationView.OnNavigationItemSelectedListener,
|
||||
Toolbar.OnMenuItemClickListener,
|
||||
RecyclerViewHolder.OnItemClickListener<PageInfo> {
|
||||
@Suppress("PrivatePropertyName", "unused", "DEPRECATION")
|
||||
class MainActivity : BaseActivity<ActivityMainBinding?>(), DrawerAdapter.OnItemSelectedListener {
|
||||
|
||||
private val TAG: String = MainActivity::class.java.simpleName
|
||||
private lateinit var mTitles: Array<String>
|
||||
private var logsType: String = "sms"
|
||||
private var ruleType: String = "sms"
|
||||
private val POS_LOG = 0
|
||||
private val POS_RULE = 1
|
||||
private val POS_SENDER = 2
|
||||
private val POS_SETTING = 3
|
||||
private val POS_TASK = 5 //4为空行
|
||||
private val POS_SERVER = 6
|
||||
private val POS_CLIENT = 7
|
||||
private val POS_FRPC = 8
|
||||
private val POS_APPS = 9
|
||||
private val POS_HELP = 11 //10为空行
|
||||
private val POS_ABOUT = 12
|
||||
private var needToAppListFragment = false
|
||||
|
||||
private lateinit var mTabLayout: TabLayout
|
||||
private lateinit var mSlidingRootNav: SlidingRootNav
|
||||
private lateinit var mLLMenu: LinearLayout
|
||||
private lateinit var mMenuTitles: Array<String>
|
||||
private lateinit var mMenuIcons: Array<Drawable>
|
||||
private lateinit var mAdapter: DrawerAdapter
|
||||
|
||||
override fun viewBindingInflate(inflater: LayoutInflater?): ActivityMainBinding {
|
||||
return ActivityMainBinding.inflate(inflater!!)
|
||||
@ -74,9 +97,10 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(),
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
initViews()
|
||||
|
||||
initData()
|
||||
initListeners()
|
||||
initViews()
|
||||
initSlidingMenu(savedInstanceState)
|
||||
|
||||
//不在最近任务列表中显示
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && SettingUtils.enableExcludeFromRecents) {
|
||||
@ -88,6 +112,32 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//检查通知权限是否获取
|
||||
XXPermissions.with(this).permission(Permission.NOTIFICATION_SERVICE).permission(Permission.POST_NOTIFICATIONS).request(OnPermissionCallback { _, allGranted ->
|
||||
if (!allGranted) {
|
||||
XToastUtils.error(R.string.tips_notification)
|
||||
return@OnPermissionCallback
|
||||
}
|
||||
|
||||
//启动前台服务
|
||||
if (!ForegroundService.isRunning) {
|
||||
val serviceIntent = Intent(this, ForegroundService::class.java)
|
||||
serviceIntent.action = ACTION_START
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForegroundService(serviceIntent)
|
||||
} else {
|
||||
startService(serviceIntent)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
//监听已安装App信息列表加载完成事件
|
||||
LiveEventBus.get(EVENT_LOAD_APP_LIST, String::class.java).observe(this) {
|
||||
if (needToAppListFragment) {
|
||||
openNewPage(AppListFragment::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val isSupportSlideBack: Boolean
|
||||
@ -95,246 +145,47 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(),
|
||||
|
||||
private fun initViews() {
|
||||
WidgetUtils.clearActivityBackground(this)
|
||||
mTitles = ResUtils.getStringArray(R.array.home_titles)
|
||||
binding!!.includeMain.toolbar.title = mTitles[0]
|
||||
binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_logs)
|
||||
binding!!.includeMain.toolbar.setOnMenuItemClickListener(this)
|
||||
|
||||
//主页内容填充
|
||||
val fragments = arrayOf(
|
||||
LogsFragment(),
|
||||
RulesFragment(),
|
||||
SendersFragment(),
|
||||
SettingsFragment()
|
||||
)
|
||||
val adapter = FragmentAdapter(supportFragmentManager, fragments)
|
||||
binding!!.includeMain.viewPager.offscreenPageLimit = mTitles.size - 1
|
||||
binding!!.includeMain.viewPager.adapter = adapter
|
||||
|
||||
if (!SettingUtils.enableHelpTip) {
|
||||
val headerView = binding!!.navView.getHeaderView(0)
|
||||
val tvSlogan = headerView.findViewById<TextView>(R.id.tv_slogan)
|
||||
tvSlogan.visibility = View.GONE
|
||||
initTab()
|
||||
}
|
||||
|
||||
private fun initTab() {
|
||||
mTabLayout = binding!!.tabs
|
||||
WidgetUtils.addTabWithoutRipple(mTabLayout, getString(R.string.menu_logs), R.drawable.selector_icon_tabbar_logs)
|
||||
WidgetUtils.addTabWithoutRipple(mTabLayout, getString(R.string.menu_rules), R.drawable.selector_icon_tabbar_rules)
|
||||
WidgetUtils.addTabWithoutRipple(mTabLayout, getString(R.string.menu_senders), R.drawable.selector_icon_tabbar_senders)
|
||||
WidgetUtils.addTabWithoutRipple(mTabLayout, getString(R.string.menu_settings), R.drawable.selector_icon_tabbar_settings)
|
||||
WidgetUtils.setTabLayoutTextFont(mTabLayout)
|
||||
switchPage(LogsFragment::class.java)
|
||||
mTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||
needToAppListFragment = false
|
||||
mAdapter.setSelected(tab.position)
|
||||
when (tab.position) {
|
||||
POS_LOG -> switchPage(LogsFragment::class.java)
|
||||
POS_RULE -> switchPage(RulesFragment::class.java)
|
||||
POS_SENDER -> switchPage(SendersFragment::class.java)
|
||||
POS_SETTING -> switchPage(SettingsFragment::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab) {}
|
||||
override fun onTabReselected(tab: TabLayout.Tab) {}
|
||||
})
|
||||
}
|
||||
|
||||
private fun initData() {
|
||||
mMenuTitles = ResUtils.getStringArray(this, R.array.menu_titles)
|
||||
mMenuIcons = ResUtils.getDrawableArray(this, R.array.menu_icons)
|
||||
|
||||
//仅当开启自动检查且有网络时自动检查更新/获取提示
|
||||
if (SettingUtils.autoCheckUpdate && NetworkUtils.isHaveInternet()) {
|
||||
showTips(this)
|
||||
XUpdateInit.checkUpdate(this, false)
|
||||
}
|
||||
|
||||
fun initListeners() {
|
||||
val toggle = ActionBarDrawerToggle(
|
||||
this,
|
||||
binding!!.drawerLayout,
|
||||
binding!!.includeMain.toolbar,
|
||||
R.string.navigation_drawer_open,
|
||||
R.string.navigation_drawer_close
|
||||
)
|
||||
binding!!.drawerLayout.addDrawerListener(toggle)
|
||||
toggle.syncState()
|
||||
|
||||
//侧边栏点击事件
|
||||
binding!!.navView.setNavigationItemSelectedListener { menuItem: MenuItem ->
|
||||
if (menuItem.isCheckable) {
|
||||
binding!!.drawerLayout.closeDrawers()
|
||||
return@setNavigationItemSelectedListener handleNavigationItemSelected(menuItem)
|
||||
} else {
|
||||
when (menuItem.itemId) {
|
||||
R.id.nav_server -> openNewPage(ServerFragment::class.java)
|
||||
R.id.nav_client -> openNewPage(ClientFragment::class.java)
|
||||
R.id.nav_frpc -> {
|
||||
if (!FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so")) {
|
||||
MaterialDialog.Builder(this)
|
||||
.title(String.format(getString(R.string.frpclib_download_title), FRPC_LIB_VERSION))
|
||||
.content(R.string.download_frpc_tips)
|
||||
.positiveText(R.string.lab_yes)
|
||||
.negativeText(R.string.lab_no)
|
||||
.onPositive { _: MaterialDialog?, _: DialogAction? ->
|
||||
downloadFrpcLib()
|
||||
}
|
||||
.show()
|
||||
return@setNavigationItemSelectedListener false
|
||||
}
|
||||
|
||||
if (FRPC_LIB_VERSION == Frpclib.getVersion()) {
|
||||
openNewPage(FrpcFragment::class.java)
|
||||
} else {
|
||||
MaterialDialog.Builder(this)
|
||||
.title(R.string.frpclib_version_mismatch)
|
||||
.content(R.string.download_frpc_tips)
|
||||
.positiveText(R.string.lab_yes)
|
||||
.negativeText(R.string.lab_no)
|
||||
.onPositive { _: MaterialDialog?, _: DialogAction? ->
|
||||
downloadFrpcLib()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
R.id.nav_app_list -> openNewPage(AppListFragment::class.java)
|
||||
R.id.nav_logcat -> openNewPage(LogcatFragment::class.java)
|
||||
R.id.nav_help -> AgentWebActivity.goWeb(this, getString(R.string.url_help))
|
||||
R.id.nav_about -> openNewPage(AboutFragment::class.java)
|
||||
else -> XToastUtils.toast("Click:" + menuItem.title)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
//主页事件监听
|
||||
binding!!.includeMain.viewPager.addOnPageChangeListener(object :
|
||||
ViewPager.OnPageChangeListener {
|
||||
override fun onPageScrolled(
|
||||
position: Int,
|
||||
positionOffset: Float,
|
||||
positionOffsetPixels: Int,
|
||||
) {
|
||||
}
|
||||
|
||||
override fun onPageSelected(position: Int) {
|
||||
val item = binding!!.includeMain.bottomNavigation.menu.getItem(position)
|
||||
binding!!.includeMain.toolbar.title = item.title
|
||||
binding!!.includeMain.toolbar.menu.clear()
|
||||
when {
|
||||
item.title.equals(getString(R.string.menu_rules)) -> binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_rules)
|
||||
item.title.equals(getString(R.string.menu_senders)) -> binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_senders)
|
||||
item.title.equals(getString(R.string.menu_settings)) -> binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_settings)
|
||||
else -> binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_logs)
|
||||
}
|
||||
item.isChecked = true
|
||||
updateSideNavStatus(item)
|
||||
}
|
||||
|
||||
override fun onPageScrollStateChanged(state: Int) {}
|
||||
})
|
||||
binding!!.includeMain.bottomNavigation.setOnNavigationItemSelectedListener(this)
|
||||
|
||||
//tabBar分类切换
|
||||
LiveEventBus.get(EVENT_UPDATE_LOGS_TYPE, String::class.java).observe(this) { type: String ->
|
||||
logsType = type
|
||||
}
|
||||
LiveEventBus.get(EVENT_UPDATE_RULE_TYPE, String::class.java).observe(this) { type: String ->
|
||||
ruleType = type
|
||||
}
|
||||
|
||||
//更新通知栏文案
|
||||
LiveEventBus.get(EVENT_UPDATE_NOTIFY, String::class.java).observe(this) { notify: String ->
|
||||
cactusUpdateNotification {
|
||||
setContent(notify)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理侧边栏点击事件
|
||||
*
|
||||
* @param menuItem
|
||||
* @return
|
||||
*/
|
||||
private fun handleNavigationItemSelected(menuItem: MenuItem): Boolean {
|
||||
val index = CollectionUtils.arrayIndexOf(mTitles, menuItem.title)
|
||||
if (index != -1) {
|
||||
binding!!.includeMain.toolbar.title = menuItem.title
|
||||
binding!!.includeMain.viewPager.setCurrentItem(index, false)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_notifications -> {
|
||||
showTipsForce(this)
|
||||
}
|
||||
R.id.action_clear_logs -> {
|
||||
MaterialDialog.Builder(this)
|
||||
.content(R.string.delete_type_log_tips)
|
||||
.positiveText(R.string.lab_yes)
|
||||
.negativeText(R.string.lab_no)
|
||||
.onPositive { _: MaterialDialog?, _: DialogAction? ->
|
||||
AppDatabase.getInstance(this)
|
||||
.logsDao()
|
||||
.deleteAll(logsType)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : CompletableObserver {
|
||||
override fun onSubscribe(d: Disposable) {}
|
||||
override fun onComplete() {
|
||||
XToastUtils.success(R.string.delete_type_log_toast)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
e.message?.let { XToastUtils.error(it) }
|
||||
}
|
||||
})
|
||||
}
|
||||
.show()
|
||||
}
|
||||
R.id.action_add_sender -> {
|
||||
val dialog = BottomSheetDialog(this)
|
||||
val view: View = LayoutInflater.from(this).inflate(R.layout.dialog_sender_bottom_sheet, null)
|
||||
val recyclerView: RecyclerView = view.findViewById(R.id.recyclerView)
|
||||
|
||||
WidgetUtils.initGridRecyclerView(recyclerView, 4, DensityUtils.dp2px(1f))
|
||||
val widgetItemAdapter = WidgetItemAdapter(SENDER_FRAGMENT_LIST)
|
||||
widgetItemAdapter.setOnItemClickListener(this)
|
||||
recyclerView.adapter = widgetItemAdapter
|
||||
|
||||
dialog.setContentView(view)
|
||||
dialog.setCancelable(true)
|
||||
dialog.setCanceledOnTouchOutside(true)
|
||||
dialog.show()
|
||||
WidgetUtils.transparentBottomSheetDialogBackground(dialog)
|
||||
}
|
||||
R.id.action_add_rule -> {
|
||||
PageOption.to(RulesEditFragment::class.java)
|
||||
.putString(KEY_RULE_TYPE, ruleType)
|
||||
.setNewActivity(true)
|
||||
.open(this)
|
||||
}
|
||||
/*R.id.action_restore_settings -> {
|
||||
XToastUtils.success(logsType)
|
||||
}*/
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
}
|
||||
|
||||
//================Navigation================//
|
||||
/**
|
||||
* 底部导航栏点击事件
|
||||
*
|
||||
* @param menuItem
|
||||
* @return
|
||||
*/
|
||||
override fun onNavigationItemSelected(menuItem: MenuItem): Boolean {
|
||||
val index = CollectionUtils.arrayIndexOf(mTitles, menuItem.title)
|
||||
if (index != -1) {
|
||||
binding!!.includeMain.toolbar.title = menuItem.title
|
||||
binding!!.includeMain.viewPager.setCurrentItem(index, false)
|
||||
updateSideNavStatus(menuItem)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新侧边栏菜单选中状态
|
||||
*
|
||||
* @param menuItem
|
||||
*/
|
||||
private fun updateSideNavStatus(menuItem: MenuItem) {
|
||||
val side = binding!!.navView.menu.findItem(menuItem.itemId)
|
||||
if (side != null) {
|
||||
side.isChecked = true
|
||||
XUpdateInit.checkUpdate(this, false, SettingUtils.joinPreviewProgram)
|
||||
}
|
||||
}
|
||||
|
||||
//按返回键不退出回到桌面
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onBackPressed() {
|
||||
val intent = Intent(Intent.ACTION_MAIN)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
@ -342,18 +193,126 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(),
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
@SingleClick
|
||||
override fun onItemClick(itemView: View, widgetInfo: PageInfo, pos: Int) {
|
||||
try {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
PageOption.to(Class.forName(widgetInfo.classPath) as Class<XPageFragment>) //跳转的fragment
|
||||
.setNewActivity(true)
|
||||
.putInt(KEY_SENDER_TYPE, pos) //注意:目前刚好是这个顺序而已
|
||||
.open(this)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
XToastUtils.error(e.message.toString())
|
||||
fun openMenu() {
|
||||
mSlidingRootNav.openMenu()
|
||||
}
|
||||
|
||||
fun closeMenu() {
|
||||
mSlidingRootNav.closeMenu()
|
||||
}
|
||||
|
||||
fun isMenuOpen(): Boolean {
|
||||
return mSlidingRootNav.isMenuOpened
|
||||
}
|
||||
|
||||
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)
|
||||
ViewUtils.setVisibility(mLLMenu, false)
|
||||
mAdapter = DrawerAdapter(
|
||||
mutableListOf(
|
||||
createItemFor(POS_LOG).setChecked(true),
|
||||
createItemFor(POS_RULE),
|
||||
createItemFor(POS_SENDER),
|
||||
createItemFor(POS_SETTING),
|
||||
SpaceItem(15),
|
||||
createItemFor(POS_TASK),
|
||||
createItemFor(POS_SERVER),
|
||||
createItemFor(POS_CLIENT),
|
||||
createItemFor(POS_FRPC),
|
||||
createItemFor(POS_APPS),
|
||||
SpaceItem(15),
|
||||
createItemFor(POS_HELP),
|
||||
createItemFor(POS_ABOUT),
|
||||
)
|
||||
)
|
||||
mAdapter.setListener(this)
|
||||
val list: RecyclerView = findViewById(R.id.list)
|
||||
list.isNestedScrollingEnabled = false
|
||||
list.layoutManager = LinearLayoutManager(this)
|
||||
list.adapter = mAdapter
|
||||
mAdapter.setSelected(POS_LOG)
|
||||
mSlidingRootNav.isMenuLocked = false
|
||||
mSlidingRootNav.layout.addDragStateListener(object : DragStateListener {
|
||||
override fun onDragStart() {
|
||||
ViewUtils.setVisibility(mLLMenu, true)
|
||||
}
|
||||
|
||||
override fun onDragEnd(isMenuOpened: Boolean) {
|
||||
ViewUtils.setVisibility(mLLMenu, isMenuOpened)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onItemSelected(position: Int) {
|
||||
needToAppListFragment = false
|
||||
when (position) {
|
||||
POS_LOG, POS_RULE, POS_SENDER, POS_SETTING -> {
|
||||
val tab = mTabLayout.getTabAt(position)
|
||||
tab?.select()
|
||||
mSlidingRootNav.closeMenu()
|
||||
}
|
||||
|
||||
POS_TASK -> openNewPage(TasksFragment::class.java)
|
||||
POS_SERVER -> openNewPage(ServerFragment::class.java)
|
||||
POS_CLIENT -> openNewPage(ClientFragment::class.java)
|
||||
POS_FRPC -> {
|
||||
if (App.FrpclibInited) {
|
||||
openNewPage(FrpcFragment::class.java)
|
||||
return
|
||||
}
|
||||
|
||||
val title = if (!FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so")) {
|
||||
String.format(getString(R.string.frpclib_download_title), FRPC_LIB_VERSION)
|
||||
} else {
|
||||
getString(R.string.frpclib_version_mismatch)
|
||||
}
|
||||
|
||||
MaterialDialog.Builder(this)
|
||||
.title(title)
|
||||
.content(R.string.download_frpc_tips)
|
||||
.positiveText(R.string.lab_yes)
|
||||
.negativeText(R.string.lab_no)
|
||||
.onPositive { _: MaterialDialog?, _: DialogAction? ->
|
||||
downloadFrpcLib()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
POS_APPS -> {
|
||||
//检查读取应用列表权限是否获取
|
||||
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))
|
||||
POS_ABOUT -> openNewPage(AboutFragment::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createItemFor(position: Int): DrawerItem<*> {
|
||||
return SimpleItem(mMenuIcons[position], mMenuTitles[position])
|
||||
.withIconTint(ThemeUtils.resolveColor(this, R.attr.xui_config_color_content_text))
|
||||
.withTextTint(ThemeUtils.resolveColor(this, R.attr.xui_config_color_content_text))
|
||||
.withSelectedIconTint(ThemeUtils.getMainThemeColor(this))
|
||||
.withSelectedTextTint(ThemeUtils.getMainThemeColor(this))
|
||||
}
|
||||
|
||||
//动态加载FrpcLib
|
||||
@ -379,6 +338,7 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(),
|
||||
.build()
|
||||
|
||||
XHttp.downLoad(downloadUrl)
|
||||
.ignoreHttpsCert()
|
||||
.savePath(cacheDir.absolutePath)
|
||||
.execute(object : DownloadProgressCallBack<String?>() {
|
||||
override fun onStart() {
|
||||
@ -404,10 +364,16 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(),
|
||||
val destFile = File("$libPath/libgojni.so")
|
||||
FileUtils.moveFile(srcFile, destFile, null)
|
||||
|
||||
val intent: Intent? = packageManager.getLaunchIntentForPackage(packageName)
|
||||
intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
startActivity(intent)
|
||||
android.os.Process.killProcess(android.os.Process.myPid()) //杀掉以前进程
|
||||
MaterialDialog.Builder(this@MainActivity)
|
||||
.iconRes(R.drawable.ic_menu_frpc)
|
||||
.title(R.string.menu_frpc)
|
||||
.content(R.string.download_frpc_tips2)
|
||||
.cancelable(false)
|
||||
.positiveText(R.string.confirm)
|
||||
.onPositive { _: MaterialDialog?, _: DialogAction? ->
|
||||
restartApplication()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -1,14 +1,11 @@
|
||||
package com.idormy.sms.forwarder.activity
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.utils.CommonUtils.Companion.showPrivacyDialog
|
||||
import com.idormy.sms.forwarder.utils.MMKVUtils
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.isAgreePrivacy
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.isFirstOpen
|
||||
import com.xuexiang.xui.utils.KeyboardUtils
|
||||
import com.xuexiang.xui.widget.activity.BaseSplashActivity
|
||||
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
|
||||
@ -38,12 +35,6 @@ class SplashActivity : BaseSplashActivity(), CancelAdapt {
|
||||
* 启动页结束后的动作
|
||||
*/
|
||||
override fun onSplashFinished() {
|
||||
if (isFirstOpen) {
|
||||
isFirstOpen = false
|
||||
Log.d(TAG, "从SP迁移数据")
|
||||
MMKVUtils.importSharedPreferences(this)
|
||||
}
|
||||
|
||||
if (isAgreePrivacy) {
|
||||
whereToJump()
|
||||
} else {
|
||||
@ -56,7 +47,9 @@ class SplashActivity : BaseSplashActivity(), CancelAdapt {
|
||||
}
|
||||
|
||||
private fun whereToJump() {
|
||||
if (SettingUtils.enablePureClientMode) {
|
||||
if (SettingUtils.enablePureTaskMode) {
|
||||
ActivityUtils.startActivity(TaskActivity::class.java)
|
||||
} else if (SettingUtils.enablePureClientMode) {
|
||||
ActivityUtils.startActivity(ClientActivity::class.java)
|
||||
} else {
|
||||
ActivityUtils.startActivity(MainActivity::class.java)
|
||||
|
@ -0,0 +1,14 @@
|
||||
package com.idormy.sms.forwarder.activity
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.idormy.sms.forwarder.core.BaseActivity
|
||||
import com.idormy.sms.forwarder.fragment.TasksFragment
|
||||
|
||||
class TaskActivity : BaseActivity<ViewBinding?>() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
openPage(TasksFragment::class.java)
|
||||
}
|
||||
}
|
@ -3,11 +3,11 @@ package com.idormy.sms.forwarder.adapter
|
||||
import android.widget.ImageView
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.adapter.base.broccoli.BroccoliRecyclerAdapter
|
||||
import com.idormy.sms.forwarder.utils.AppInfo
|
||||
import com.idormy.sms.forwarder.utils.AppUtils
|
||||
import com.idormy.sms.forwarder.utils.PlaceholderHelper
|
||||
import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder
|
||||
import com.xuexiang.xui.widget.imageview.ImageLoader
|
||||
import com.xuexiang.xutil.app.AppUtils
|
||||
import com.xuexiang.xutil.app.AppUtils.AppInfo
|
||||
import me.samlss.broccoli.Broccoli
|
||||
|
||||
class AppListAdapter(
|
||||
@ -34,8 +34,9 @@ class AppListAdapter(
|
||||
ImageLoader.get().loadImage(ivAppIcon, model.icon)
|
||||
holder.text(R.id.tv_app_name, model.name)
|
||||
holder.text(R.id.tv_pkg_name, model.packageName)
|
||||
holder.text(R.id.tv_ver_name, model.versionName)
|
||||
//holder.text(R.id.tv_ver_code, model.versionCode)
|
||||
holder.text(R.id.tv_ver_name, "VER. " + model.versionName)
|
||||
//holder.text(R.id.tv_ver_code, model.versionCode.toString())
|
||||
holder.text(R.id.tv_uid, "UID. " + model.uid.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,13 +53,15 @@ class AppListAdapter(
|
||||
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_pkg_name)))
|
||||
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_ver_name)))
|
||||
//.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_ver_code)))
|
||||
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_uid)))
|
||||
} else {
|
||||
broccoli.addPlaceholders(
|
||||
holder.findView(R.id.iv_app_icon),
|
||||
holder.findView(R.id.tv_app_name),
|
||||
holder.findView(R.id.tv_pkg_name),
|
||||
holder.findView(R.id.tv_ver_name),
|
||||
//holder.findView(R.id.tv_ver_code)
|
||||
//holder.findView(R.id.tv_ver_code),
|
||||
holder.findView(R.id.tv_uid)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
@ -15,6 +16,7 @@ import com.idormy.sms.forwarder.databinding.AdapterFrpcsCardViewListItemBinding
|
||||
import com.xuexiang.xutil.resource.ResUtils.getColors
|
||||
import frpclib.Frpclib
|
||||
|
||||
@Suppress("EmptyMethod")
|
||||
class FrpcPagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter<Frpc, MyViewHolder>(diffCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
|
||||
@ -22,14 +24,15 @@ class FrpcPagingAdapter(private val itemClickListener: OnItemClickListener) : Pa
|
||||
return MyViewHolder(binding)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
if (item != null) {
|
||||
holder.binding.ivImage.setImageResource(R.drawable.ic_menu_frpc)
|
||||
holder.binding.ivAutorun.setImageResource(item.autorunImageId)
|
||||
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)
|
||||
@ -41,9 +44,9 @@ class FrpcPagingAdapter(private val itemClickListener: OnItemClickListener) : Pa
|
||||
}
|
||||
}
|
||||
|
||||
holder.binding.ivEdit.setImageResource(R.drawable.ic_edit)
|
||||
holder.binding.ivDelete.setImageResource(R.drawable.ic_delete)
|
||||
|
||||
holder.binding.ivCopy.setOnClickListener { view: View? ->
|
||||
itemClickListener.onItemClicked(view, item)
|
||||
}
|
||||
holder.binding.ivPlay.setOnClickListener { view: View? ->
|
||||
itemClickListener.onItemClicked(view, item)
|
||||
}
|
||||
|
@ -0,0 +1,110 @@
|
||||
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.Frpc
|
||||
import com.idormy.sms.forwarder.utils.STATUS_OFF
|
||||
import java.util.Collections
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class FrpcRecyclerAdapter(
|
||||
var itemList: MutableList<Frpc>,
|
||||
private var removeClickListener: ((Int) -> Unit)? = null,
|
||||
private var editClickListener: ((Int) -> Unit)? = null,
|
||||
) : RecyclerView.Adapter<FrpcRecyclerAdapter.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_frpc_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@FrpcRecyclerAdapter.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(frpc: Frpc) {
|
||||
image.setImageResource(frpc.imageId)
|
||||
status.setImageResource(
|
||||
when (frpc.status) {
|
||||
STATUS_OFF -> R.drawable.ic_stop
|
||||
else -> R.drawable.ic_start
|
||||
}
|
||||
)
|
||||
title.text = frpc.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() {}
|
||||
}
|
||||
|
@ -1,57 +0,0 @@
|
||||
package com.idormy.sms.forwarder.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.paging.PagingDataAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.idormy.sms.forwarder.adapter.LogsPagingAdapter.MyViewHolder
|
||||
import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender
|
||||
import com.idormy.sms.forwarder.database.entity.Sender
|
||||
import com.idormy.sms.forwarder.databinding.AdapterLogsCardViewListItemBinding
|
||||
import com.xuexiang.xutil.data.DateUtils
|
||||
|
||||
class LogsPagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter<LogsAndRuleAndSender, MyViewHolder>(diffCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
|
||||
val binding = AdapterLogsCardViewListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return MyViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
if (item != null) {
|
||||
holder.binding.tvFrom.text = item.logs.from
|
||||
holder.binding.tvTime.text = DateUtils.getFriendlyTimeSpanByNow(item.logs.time)
|
||||
holder.binding.tvContent.text = item.logs.content
|
||||
holder.binding.ivSenderImage.setImageResource(Sender.getImageId(item.relation.sender.type))
|
||||
holder.binding.ivStatusImage.setImageResource(item.logs.statusImageId)
|
||||
holder.binding.ivSimImage.setImageResource(item.logs.simImageId)
|
||||
|
||||
holder.binding.cardView.setOnClickListener { view: View? ->
|
||||
itemClickListener.onItemClicked(view, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MyViewHolder(val binding: AdapterLogsCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
interface OnItemClickListener {
|
||||
fun onItemClicked(view: View?, item: LogsAndRuleAndSender)
|
||||
fun onItemRemove(view: View?, id: Int)
|
||||
}
|
||||
|
||||
companion object {
|
||||
var diffCallback: DiffUtil.ItemCallback<LogsAndRuleAndSender> = object : DiffUtil.ItemCallback<LogsAndRuleAndSender>() {
|
||||
override fun areItemsTheSame(oldItem: LogsAndRuleAndSender, newItem: LogsAndRuleAndSender): Boolean {
|
||||
return oldItem.logs.id == newItem.logs.id
|
||||
}
|
||||
|
||||
@SuppressLint("DiffUtilEquals")
|
||||
override fun areContentsTheSame(oldItem: LogsAndRuleAndSender, newItem: LogsAndRuleAndSender): Boolean {
|
||||
return oldItem.logs === newItem.logs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package com.idormy.sms.forwarder.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.paging.PagingDataAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.adapter.MsgPagingAdapter.MyViewHolder
|
||||
import com.idormy.sms.forwarder.database.entity.LogsDetail
|
||||
import com.idormy.sms.forwarder.database.entity.MsgAndLogs
|
||||
import com.idormy.sms.forwarder.databinding.AdapterLogsCardViewListItemBinding
|
||||
import com.xuexiang.xutil.data.DateUtils
|
||||
|
||||
@Suppress("EmptyMethod")
|
||||
class MsgPagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter<MsgAndLogs, MyViewHolder>(diffCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
|
||||
val binding = AdapterLogsCardViewListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return MyViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
if (item != null) {
|
||||
holder.binding.tvFrom.text = item.msg.from
|
||||
holder.binding.tvTime.text = DateUtils.getFriendlyTimeSpanByNow(item.msg.time)
|
||||
holder.binding.tvContent.text = item.msg.content
|
||||
//holder.binding.ivSenderImage.setImageResource(Sender.getImageId(item.sender.type))
|
||||
//holder.binding.ivStatusImage.setImageResource(item.msg.statusImageId)
|
||||
holder.binding.ivSimImage.setImageResource(item.msg.simImageId)
|
||||
|
||||
holder.binding.layoutLogs.removeAllViews()
|
||||
for (logs in item.logsList) {
|
||||
val layoutSenderItem = View.inflate(App.context, R.layout.item_logs, null) as LinearLayout
|
||||
val ivSenderImage = layoutSenderItem.findViewById<ImageView>(R.id.iv_sender_image)
|
||||
val ivSenderStatus = layoutSenderItem.findViewById<ImageView>(R.id.iv_sender_status)
|
||||
val tvSenderName = layoutSenderItem.findViewById<TextView>(R.id.tv_sender_name)
|
||||
ivSenderImage.setImageResource(logs.senderImageId)
|
||||
ivSenderStatus.setImageResource(logs.statusImageId)
|
||||
tvSenderName.text = logs.senderName
|
||||
layoutSenderItem.setOnClickListener { view: View? ->
|
||||
itemClickListener.onLogsClicked(view, logs)
|
||||
}
|
||||
holder.binding.layoutLogs.addView(layoutSenderItem)
|
||||
}
|
||||
|
||||
holder.binding.cardView.setOnClickListener { view: View? ->
|
||||
itemClickListener.onItemClicked(view, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MyViewHolder(val binding: AdapterLogsCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
interface OnItemClickListener {
|
||||
fun onItemClicked(view: View?, item: MsgAndLogs)
|
||||
fun onLogsClicked(view: View?, item: LogsDetail)
|
||||
fun onItemRemove(view: View?, id: Int)
|
||||
}
|
||||
|
||||
companion object {
|
||||
var diffCallback: DiffUtil.ItemCallback<MsgAndLogs> = object : DiffUtil.ItemCallback<MsgAndLogs>() {
|
||||
override fun areItemsTheSame(oldItem: MsgAndLogs, newItem: MsgAndLogs): Boolean {
|
||||
return oldItem.msg.id == newItem.msg.id
|
||||
}
|
||||
|
||||
@SuppressLint("DiffUtilEquals")
|
||||
override fun areContentsTheSame(oldItem: MsgAndLogs, newItem: MsgAndLogs): Boolean {
|
||||
return oldItem.msg === newItem.msg
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,15 +4,20 @@ import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.paging.PagingDataAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.adapter.RulePagingAdapter.MyViewHolder
|
||||
import com.idormy.sms.forwarder.database.entity.RuleAndSender
|
||||
import com.idormy.sms.forwarder.database.entity.Rule
|
||||
import com.idormy.sms.forwarder.databinding.AdapterRulesCardViewListItemBinding
|
||||
|
||||
class RulePagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter<RuleAndSender, MyViewHolder>(diffCallback) {
|
||||
@Suppress("EmptyMethod")
|
||||
class RulePagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter<Rule, MyViewHolder>(diffCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
|
||||
val binding = AdapterRulesCardViewListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
@ -22,19 +27,22 @@ class RulePagingAdapter(private val itemClickListener: OnItemClickListener) : Pa
|
||||
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
if (item != null) {
|
||||
holder.binding.ivRuleImage.setImageResource(item.rule.imageId)
|
||||
holder.binding.ivRuleStatus.setImageResource(item.rule.statusImageId)
|
||||
holder.binding.tvRuleMatch.text = item.rule.ruleMatch
|
||||
holder.binding.ivSenderImage.setImageResource(item.sender.imageId)
|
||||
holder.binding.ivSenderStatus.setImageResource(item.sender.statusImageId)
|
||||
holder.binding.tvSenderName.text = item.sender.name
|
||||
holder.binding.ivRuleImage.setImageResource(item.imageId)
|
||||
holder.binding.ivRuleStatus.setImageResource(item.statusImageId)
|
||||
holder.binding.tvRuleMatch.text = item.getName(false)
|
||||
|
||||
holder.binding.layoutSenders.removeAllViews()
|
||||
for (sender in item.senderList) {
|
||||
val layoutSenderItem = View.inflate(App.context, R.layout.item_sender, null) as LinearLayout
|
||||
val ivSenderImage = layoutSenderItem.findViewById<ImageView>(R.id.iv_sender_image)
|
||||
val ivSenderStatus = layoutSenderItem.findViewById<ImageView>(R.id.iv_sender_status)
|
||||
val tvSenderName = layoutSenderItem.findViewById<TextView>(R.id.tv_sender_name)
|
||||
ivSenderImage.setImageResource(sender.imageId)
|
||||
ivSenderStatus.setImageResource(sender.statusImageId)
|
||||
tvSenderName.text = sender.name
|
||||
holder.binding.layoutSenders.addView(layoutSenderItem)
|
||||
}
|
||||
|
||||
/*holder.binding.cardView.setOnClickListener { view: View? ->
|
||||
itemClickListener.onItemClicked(view, item)
|
||||
}*/
|
||||
holder.binding.ivCopy.setImageResource(R.drawable.ic_copy)
|
||||
holder.binding.ivEdit.setImageResource(R.drawable.ic_edit)
|
||||
holder.binding.ivDelete.setImageResource(R.drawable.ic_delete)
|
||||
holder.binding.ivCopy.setOnClickListener { view: View? ->
|
||||
itemClickListener.onItemClicked(view, item)
|
||||
}
|
||||
@ -49,19 +57,19 @@ class RulePagingAdapter(private val itemClickListener: OnItemClickListener) : Pa
|
||||
|
||||
class MyViewHolder(val binding: AdapterRulesCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
interface OnItemClickListener {
|
||||
fun onItemClicked(view: View?, item: RuleAndSender)
|
||||
fun onItemClicked(view: View?, item: Rule)
|
||||
fun onItemRemove(view: View?, id: Int)
|
||||
}
|
||||
|
||||
companion object {
|
||||
var diffCallback: DiffUtil.ItemCallback<RuleAndSender> = object : DiffUtil.ItemCallback<RuleAndSender>() {
|
||||
override fun areItemsTheSame(oldItem: RuleAndSender, newItem: RuleAndSender): Boolean {
|
||||
return oldItem.rule.id == newItem.rule.id
|
||||
var diffCallback: DiffUtil.ItemCallback<Rule> = object : DiffUtil.ItemCallback<Rule>() {
|
||||
override fun areItemsTheSame(oldItem: Rule, newItem: Rule): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
@SuppressLint("DiffUtilEquals")
|
||||
override fun areContentsTheSame(oldItem: RuleAndSender, newItem: RuleAndSender): Boolean {
|
||||
return oldItem.rule === newItem.rule
|
||||
override fun areContentsTheSame(oldItem: Rule, newItem: Rule): Boolean {
|
||||
return oldItem === newItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,110 @@
|
||||
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.Rule
|
||||
import java.util.Collections
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class RuleRecyclerAdapter(
|
||||
var itemList: MutableList<Rule>,
|
||||
private var removeClickListener: ((Int) -> Unit)? = null,
|
||||
private var editClickListener: ((Int) -> Unit)? = null,
|
||||
) : RecyclerView.Adapter<RuleRecyclerAdapter.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_rule_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@RuleRecyclerAdapter.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(rule: Rule) {
|
||||
val icon = when (rule.type) {
|
||||
"sms" -> R.drawable.auto_task_icon_sms
|
||||
"call" -> R.drawable.auto_task_icon_incall
|
||||
"app" -> R.drawable.auto_task_icon_start_activity
|
||||
else -> R.drawable.auto_task_icon_sms
|
||||
}
|
||||
image.setImageResource(icon)
|
||||
status.setImageResource(rule.statusImageId)
|
||||
title.text = rule.getName()
|
||||
}
|
||||
|
||||
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() {}
|
||||
}
|
||||
|
@ -7,11 +7,11 @@ import android.view.ViewGroup
|
||||
import androidx.paging.PagingDataAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.adapter.SenderPagingAdapter.MyViewHolder
|
||||
import com.idormy.sms.forwarder.database.entity.Sender
|
||||
import com.idormy.sms.forwarder.databinding.AdapterSendersCardViewListItemBinding
|
||||
|
||||
@Suppress("EmptyMethod")
|
||||
class SenderPagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter<Sender, MyViewHolder>(diffCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
|
||||
@ -26,12 +26,6 @@ class SenderPagingAdapter(private val itemClickListener: OnItemClickListener) :
|
||||
holder.binding.ivStatus.setImageResource(item.statusImageId)
|
||||
holder.binding.tvName.text = item.name
|
||||
|
||||
/*holder.binding.cardView.setOnClickListener { view: View? ->
|
||||
itemClickListener.onItemClicked(view, item)
|
||||
}*/
|
||||
holder.binding.ivCopy.setImageResource(R.drawable.ic_copy)
|
||||
holder.binding.ivEdit.setImageResource(R.drawable.ic_edit)
|
||||
holder.binding.ivDelete.setImageResource(R.drawable.ic_delete)
|
||||
holder.binding.ivCopy.setOnClickListener { view: View? ->
|
||||
itemClickListener.onItemClicked(view, item)
|
||||
}
|
||||
|
@ -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.Sender
|
||||
import java.util.Collections
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class SenderRecyclerAdapter(
|
||||
var itemList: MutableList<Sender>,
|
||||
private var removeClickListener: ((Int) -> Unit)? = null,
|
||||
private var editClickListener: ((Int) -> Unit)? = null,
|
||||
) : RecyclerView.Adapter<SenderRecyclerAdapter.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_sender_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@SenderRecyclerAdapter.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(sender: Sender) {
|
||||
image.setImageResource(sender.imageId)
|
||||
status.setImageResource(sender.statusImageId)
|
||||
title.text = sender.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,133 @@
|
||||
package com.idormy.sms.forwarder.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import androidx.paging.PagingDataAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.gson.Gson
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.adapter.TaskPagingAdapter.MyViewHolder
|
||||
import com.idormy.sms.forwarder.database.entity.Task
|
||||
import com.idormy.sms.forwarder.databinding.AdapterTasksCardViewListItemBinding
|
||||
import com.idormy.sms.forwarder.entity.TaskSetting
|
||||
import com.xuexiang.xutil.data.DateUtils
|
||||
|
||||
@Suppress("EmptyMethod")
|
||||
class TaskPagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter<Task, MyViewHolder>(diffCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
|
||||
val binding = AdapterTasksCardViewListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return MyViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
if (item != null) {
|
||||
// 任务类型:<1000为任务模板,>=1000为自定义任务
|
||||
if (item.type >= 1000) {
|
||||
holder.binding.layoutImage.visibility = View.GONE
|
||||
|
||||
holder.binding.tvTime.text = DateUtils.getFriendlyTimeSpanByNow(item.lastExecTime.time)
|
||||
|
||||
//遍历conditions显示图标
|
||||
holder.binding.layoutConditionsIcons.removeAllViews()
|
||||
if (item.conditions.isNotEmpty()) {
|
||||
val conditionList = Gson().fromJson(item.conditions, Array<TaskSetting>::class.java).toMutableList()
|
||||
for (condition in conditionList) {
|
||||
val layoutConditionItem = View.inflate(App.context, R.layout.item_setting, null) as LinearLayout
|
||||
val ivConditionIcon = layoutConditionItem.findViewById<ImageView>(R.id.iv_setting_icon)
|
||||
if (item.status == 0) {
|
||||
ivConditionIcon.setImageResource(condition.greyIconId)
|
||||
} else {
|
||||
ivConditionIcon.setImageResource(condition.iconId)
|
||||
}
|
||||
holder.binding.layoutConditionsIcons.addView(layoutConditionItem)
|
||||
}
|
||||
}
|
||||
|
||||
//遍历actions显示图标
|
||||
holder.binding.layoutActionsIcons.removeAllViews()
|
||||
if (item.actions.isNotEmpty()) {
|
||||
val actionList = Gson().fromJson(item.actions, Array<TaskSetting>::class.java).toMutableList()
|
||||
for (action in actionList) {
|
||||
val layoutActionItem = View.inflate(App.context, R.layout.item_setting, null) as LinearLayout
|
||||
val ivActionIcon = layoutActionItem.findViewById<ImageView>(R.id.iv_setting_icon)
|
||||
if (item.status == 0) {
|
||||
ivActionIcon.setImageResource(action.greyIconId)
|
||||
} else {
|
||||
ivActionIcon.setImageResource(action.iconId)
|
||||
}
|
||||
holder.binding.layoutActionsIcons.addView(layoutActionItem)
|
||||
}
|
||||
}
|
||||
|
||||
holder.binding.ivEdit.setOnClickListener { view: View? ->
|
||||
itemClickListener.onItemClicked(view, item)
|
||||
}
|
||||
holder.binding.ivDelete.setOnClickListener { view: View? ->
|
||||
itemClickListener.onItemClicked(view, item)
|
||||
}
|
||||
|
||||
if (item.status == 0) {
|
||||
holder.binding.ivArrow.setImageResource(R.drawable.auto_task_icon_left_arrow_grey)
|
||||
holder.binding.sbEnable.isChecked = false
|
||||
} else {
|
||||
holder.binding.ivArrow.setImageResource(R.drawable.auto_task_icon_left_arrow)
|
||||
holder.binding.sbEnable.isChecked = true
|
||||
}
|
||||
holder.binding.sbEnable.setOnClickListener { view: View? ->
|
||||
itemClickListener.onItemClicked(view, item)
|
||||
}
|
||||
//不能用 setOnCheckedChangeListener,否则会导致切换时状态错乱
|
||||
/*holder.binding.sbEnable.setOnCheckedChangeListener { view: View, isChecked ->
|
||||
item.status = if (isChecked) 1 else 0
|
||||
itemClickListener.onItemClicked(view, item)
|
||||
}*/
|
||||
} else {
|
||||
holder.binding.layoutImage.visibility = View.VISIBLE
|
||||
holder.binding.layoutIcons.visibility = View.GONE
|
||||
if (item.status == 0) {
|
||||
holder.binding.ivArrow.setImageResource(R.drawable.auto_task_icon_left_arrow_grey)
|
||||
holder.binding.ivImage.setImageResource(item.greyImageId)
|
||||
} else {
|
||||
holder.binding.ivArrow.setImageResource(R.drawable.auto_task_icon_left_arrow)
|
||||
holder.binding.ivImage.setImageResource(item.imageId)
|
||||
}
|
||||
holder.binding.ivStatus.setImageResource(item.statusImageId)
|
||||
holder.binding.ivEdit.visibility = View.GONE
|
||||
holder.binding.ivDelete.visibility = View.GONE
|
||||
holder.binding.sbEnable.visibility = View.GONE
|
||||
}
|
||||
holder.binding.tvName.text = item.name
|
||||
holder.binding.tvDescription.text = item.description
|
||||
holder.binding.ivCopy.setOnClickListener { view: View? ->
|
||||
itemClickListener.onItemClicked(view, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MyViewHolder(val binding: AdapterTasksCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
interface OnItemClickListener {
|
||||
fun onItemClicked(view: View?, item: Task)
|
||||
fun onItemRemove(view: View?, id: Int)
|
||||
}
|
||||
|
||||
companion object {
|
||||
var diffCallback: DiffUtil.ItemCallback<Task> = object : DiffUtil.ItemCallback<Task>() {
|
||||
override fun areItemsTheSame(oldItem: Task, newItem: Task): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
@SuppressLint("DiffUtilEquals")
|
||||
override fun areContentsTheSame(oldItem: Task, newItem: Task): Boolean {
|
||||
return oldItem === newItem
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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,103 @@
|
||||
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.entity.TaskSetting
|
||||
import java.util.Collections
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class TaskSettingAdapter(
|
||||
var itemList: MutableList<TaskSetting>,
|
||||
private var removeClickListener: ((Int) -> Unit)? = null,
|
||||
private var editClickListener: ((Int) -> Unit)? = null,
|
||||
) : RecyclerView.Adapter<TaskSettingAdapter.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_setting_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@TaskSettingAdapter.touchHelper = touchHelper
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
|
||||
private val icon: ImageView = itemView.findViewById(R.id.iv_icon)
|
||||
private val title: TextView = itemView.findViewById(R.id.tv_title)
|
||||
private val description: TextView = itemView.findViewById(R.id.tv_description)
|
||||
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(taskSetting: TaskSetting) {
|
||||
icon.setImageResource(taskSetting.iconId)
|
||||
title.text = taskSetting.title
|
||||
description.text = taskSetting.description
|
||||
}
|
||||
|
||||
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,34 @@
|
||||
package com.idormy.sms.forwarder.adapter.base
|
||||
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class ItemMoveCallback(private val listener: Listener) : ItemTouchHelper.Callback() {
|
||||
|
||||
interface Listener {
|
||||
fun onItemMove(fromPosition: Int, toPosition: Int)
|
||||
fun onDragFinished()
|
||||
}
|
||||
|
||||
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
|
||||
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||
return makeMovementFlags(dragFlags, 0)
|
||||
}
|
||||
|
||||
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
|
||||
listener.onItemMove(viewHolder.adapterPosition, target.adapterPosition)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
// Swiping is not needed for this example
|
||||
}
|
||||
|
||||
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
||||
super.onSelectedChanged(viewHolder, actionState)
|
||||
if (actionState == ItemTouchHelper.ACTION_STATE_IDLE) {
|
||||
listener.onDragFinished()
|
||||
}
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ import me.samlss.broccoli.Broccoli
|
||||
* @author xuexiang
|
||||
* @since 2021/1/9 4:52 PM
|
||||
*/
|
||||
@Suppress("unused")
|
||||
abstract class BroccoliSimpleDelegateAdapter<T> : SimpleDelegateAdapter<T> {
|
||||
/**
|
||||
* 是否已经加载成功
|
||||
|
@ -15,7 +15,7 @@ import com.alibaba.android.vlayout.DelegateAdapter
|
||||
* @since 2020/3/20 12:17 AM
|
||||
*/
|
||||
@Suppress("unused")
|
||||
abstract class XDelegateAdapter<T, V : RecyclerView.ViewHolder?> : DelegateAdapter.Adapter<V> {
|
||||
abstract class XDelegateAdapter<T, V : RecyclerView.ViewHolder> : DelegateAdapter.Adapter<V> {
|
||||
/**
|
||||
* 数据源
|
||||
*/
|
||||
@ -36,7 +36,7 @@ abstract class XDelegateAdapter<T, V : RecyclerView.ViewHolder?> : DelegateAdapt
|
||||
}
|
||||
|
||||
constructor(data: Array<T>?) {
|
||||
if (data != null && data.isNotEmpty()) {
|
||||
if (!data.isNullOrEmpty()) {
|
||||
mData.addAll(listOf(*data))
|
||||
}
|
||||
}
|
||||
@ -180,7 +180,7 @@ abstract class XDelegateAdapter<T, V : RecyclerView.ViewHolder?> : DelegateAdapt
|
||||
*/
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun refresh(array: Array<T>?): XDelegateAdapter<*, *> {
|
||||
if (array != null && array.isNotEmpty()) {
|
||||
if (!array.isNullOrEmpty()) {
|
||||
mData.clear()
|
||||
mData.addAll(listOf(*array))
|
||||
selectPosition = -1
|
||||
@ -212,7 +212,7 @@ abstract class XDelegateAdapter<T, V : RecyclerView.ViewHolder?> : DelegateAdapt
|
||||
*/
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun loadMore(array: Array<T>?): XDelegateAdapter<*, *> {
|
||||
if (array != null && array.isNotEmpty()) {
|
||||
if (!array.isNullOrEmpty()) {
|
||||
mData.addAll(listOf(*array))
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
@ -0,0 +1,83 @@
|
||||
package com.idormy.sms.forwarder.adapter.menu
|
||||
|
||||
import android.util.SparseArray
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
@Suppress("LeakingThis", "UNCHECKED_CAST")
|
||||
class DrawerAdapter(private val items: List<DrawerItem<out ViewHolder>>) : RecyclerView.Adapter<DrawerAdapter.ViewHolder>() {
|
||||
|
||||
private val viewTypes: MutableMap<Class<out DrawerItem<*>>, Int> = HashMap()
|
||||
private val holderFactories = SparseArray<DrawerItem<*>>()
|
||||
|
||||
private var listener: OnItemSelectedListener? = null
|
||||
|
||||
init {
|
||||
processViewTypes()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val holder = holderFactories.get(viewType).createViewHolder(parent)
|
||||
holder.adapter = this
|
||||
return holder
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
(items[position] as DrawerItem<ViewHolder>).bindViewHolder(holder)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
override fun getItemViewType(position: Int): Int = viewTypes[items[position]::class.java] ?: -1
|
||||
|
||||
private fun processViewTypes() {
|
||||
var type = 0
|
||||
items.forEach { item ->
|
||||
if (!viewTypes.containsKey(item::class.java)) {
|
||||
viewTypes[item::class.java] = type
|
||||
holderFactories.put(type, item)
|
||||
type++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setSelected(position: Int) {
|
||||
val newChecked = items[position]
|
||||
if (!newChecked.isSelectable()) return
|
||||
|
||||
items.forEachIndexed { index, item ->
|
||||
if (item.isChecked()) {
|
||||
item.setChecked(false)
|
||||
notifyItemChanged(index)
|
||||
return@forEachIndexed
|
||||
}
|
||||
}
|
||||
|
||||
newChecked.setChecked(true)
|
||||
notifyItemChanged(position)
|
||||
|
||||
listener?.onItemSelected(position)
|
||||
}
|
||||
|
||||
fun setListener(listener: OnItemSelectedListener?) {
|
||||
this.listener = listener
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
abstract class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
|
||||
var adapter: DrawerAdapter? = null
|
||||
|
||||
init {
|
||||
itemView.setOnClickListener(this)
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
adapter?.setSelected(adapterPosition)
|
||||
}
|
||||
}
|
||||
|
||||
interface OnItemSelectedListener {
|
||||
fun onItemSelected(position: Int)
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.idormy.sms.forwarder.adapter.menu
|
||||
|
||||
import android.view.ViewGroup
|
||||
|
||||
abstract class DrawerItem<T : DrawerAdapter.ViewHolder> {
|
||||
private var isChecked = false
|
||||
|
||||
abstract fun createViewHolder(parent: ViewGroup): T
|
||||
abstract fun bindViewHolder(holder: T)
|
||||
|
||||
fun setChecked(checked: Boolean): DrawerItem<T> {
|
||||
isChecked = checked
|
||||
return this
|
||||
}
|
||||
|
||||
fun isChecked(): Boolean = isChecked
|
||||
|
||||
open fun isSelectable(): Boolean = true
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.idormy.sms.forwarder.adapter.menu
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.idormy.sms.forwarder.R
|
||||
|
||||
class SimpleItem(
|
||||
private val icon: Drawable,
|
||||
private val title: String,
|
||||
private var selectedItemIconTint: Int = 0,
|
||||
private var selectedItemTextTint: Int = 0,
|
||||
private var normalItemIconTint: Int = 0,
|
||||
private var normalItemTextTint: Int = 0
|
||||
) : DrawerItem<SimpleItem.ViewHolder>() {
|
||||
|
||||
override fun createViewHolder(parent: ViewGroup): ViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val v = inflater.inflate(R.layout.menu_item_option, parent, false)
|
||||
return ViewHolder(v)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(holder: ViewHolder) {
|
||||
holder.title.text = title
|
||||
holder.icon.setImageDrawable(icon)
|
||||
|
||||
holder.title.setTextColor(if (isChecked()) selectedItemTextTint else normalItemTextTint)
|
||||
holder.icon.setColorFilter(if (isChecked()) selectedItemIconTint else normalItemIconTint)
|
||||
}
|
||||
|
||||
fun withSelectedIconTint(selectedItemIconTint: Int): SimpleItem = apply {
|
||||
this.selectedItemIconTint = selectedItemIconTint
|
||||
}
|
||||
|
||||
fun withSelectedTextTint(selectedItemTextTint: Int): SimpleItem = apply {
|
||||
this.selectedItemTextTint = selectedItemTextTint
|
||||
}
|
||||
|
||||
fun withIconTint(normalItemIconTint: Int): SimpleItem = apply {
|
||||
this.normalItemIconTint = normalItemIconTint
|
||||
}
|
||||
|
||||
fun withTextTint(normalItemTextTint: Int): SimpleItem = apply {
|
||||
this.normalItemTextTint = normalItemTextTint
|
||||
}
|
||||
|
||||
class ViewHolder(itemView: View) : DrawerAdapter.ViewHolder(itemView) {
|
||||
val icon: ImageView = itemView.findViewById(R.id.icon)
|
||||
val title: TextView = itemView.findViewById(R.id.title)
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.idormy.sms.forwarder.adapter.menu
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
||||
class SpaceItem(private val spaceDp: Int) : DrawerItem<SpaceItem.ViewHolder>() {
|
||||
|
||||
override fun createViewHolder(parent: ViewGroup): ViewHolder {
|
||||
val context: Context = parent.context
|
||||
val view = View(context).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, (context.resources.displayMetrics.density * spaceDp).toInt()
|
||||
)
|
||||
}
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(holder: ViewHolder) {}
|
||||
|
||||
override fun isSelectable(): Boolean = false
|
||||
|
||||
class ViewHolder(itemView: View) : DrawerAdapter.ViewHolder(itemView)
|
||||
}
|
@ -1,28 +1,13 @@
|
||||
package com.idormy.sms.forwarder.adapter.spinner
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.xuexiang.xui.utils.ResUtils
|
||||
|
||||
@Suppress("unused")
|
||||
class AppListAdapterItem {
|
||||
|
||||
var name: String = ""
|
||||
var icon: Drawable? = null
|
||||
class AppListAdapterItem(
|
||||
var name: String = "",
|
||||
var icon: Drawable? = null,
|
||||
var packageName: String? = null
|
||||
//var packagePath: String? = null
|
||||
//var versionName: String? = null
|
||||
//var versionCode: Int = 0
|
||||
//var isSystem: Boolean = false
|
||||
|
||||
|
||||
constructor(name: String, icon: Drawable?, packageName: String?) {
|
||||
this.name = name
|
||||
this.icon = icon
|
||||
this.packageName = packageName
|
||||
}
|
||||
|
||||
constructor(name: String) : this(name, null, null)
|
||||
constructor(name: String, drawableId: Int, packageName: String) : this(name, ResUtils.getDrawable(drawableId), packageName)
|
||||
) {
|
||||
|
||||
// 注意:自定义实体需要重写对象的 toString 方法
|
||||
override fun toString(): String {
|
||||
@ -34,12 +19,8 @@ class AppListAdapterItem {
|
||||
return AppListAdapterItem(name)
|
||||
}
|
||||
|
||||
fun arrayof(title: Array<String>): Array<AppListAdapterItem?> {
|
||||
val array = arrayOfNulls<AppListAdapterItem>(title.size)
|
||||
for (i in array.indices) {
|
||||
array[i] = AppListAdapterItem(title[i])
|
||||
}
|
||||
return array
|
||||
fun arrayOf(vararg titles: String): Array<AppListAdapterItem> {
|
||||
return titles.map { AppListAdapterItem(it) }.toTypedArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.text.Html
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@ -14,11 +13,12 @@ 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.xuexiang.xui.utils.CollectionUtils
|
||||
import com.xuexiang.xui.widget.spinner.editspinner.BaseEditSpinnerAdapter
|
||||
import com.xuexiang.xui.widget.spinner.editspinner.EditSpinnerFilter
|
||||
|
||||
@Suppress("unused", "NAME_SHADOWING", "SENSELESS_COMPARISON", "DEPRECATION")
|
||||
@Suppress("unused", "NAME_SHADOWING", "DEPRECATION")
|
||||
class AppListSpinnerAdapter<T> : BaseEditSpinnerAdapter<T>, EditSpinnerFilter {
|
||||
/**
|
||||
* 选项的文字颜色
|
||||
@ -99,6 +99,7 @@ class AppListSpinnerAdapter<T> : BaseEditSpinnerAdapter<T>, EditSpinnerFilter {
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e("AppListSpinnerAdapter", "onFilter: ${e.message}")
|
||||
}
|
||||
}
|
||||
Log.d("AppListSpinnerAdapter", "mDisplayData = $mDisplayData")
|
||||
@ -131,7 +132,6 @@ class AppListSpinnerAdapter<T> : BaseEditSpinnerAdapter<T>, EditSpinnerFilter {
|
||||
return this
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
private class ViewHolder(convertView: View, @ColorInt textColor: Int, textSize: Float, @DrawableRes backgroundSelector: Int) {
|
||||
val iconView: ImageView = convertView.findViewById(R.id.iv_icon)
|
||||
|
@ -0,0 +1,171 @@
|
||||
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 FrpcSpinnerAdapter<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 FrpcSpinnerItem
|
||||
holder.iconView.setImageDrawable(item.icon)
|
||||
holder.statusView.setImageDrawable(
|
||||
getDrawable(
|
||||
/*when (item.autorun) {
|
||||
STATUS_ON -> R.drawable.ic_autorun
|
||||
else -> R.drawable.ic_manual
|
||||
}*/
|
||||
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("FrpcSpinnerAdapter", "keyword = $keyword")
|
||||
Log.d("FrpcSpinnerAdapter", "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("FrpcSpinnerAdapter", "onFilter error: ${e.message}")
|
||||
}
|
||||
}
|
||||
Log.d("FrpcSpinnerAdapter", "mDisplayData = $mDisplayData")
|
||||
notifyDataSetChanged()
|
||||
return mDisplayData.size > 0
|
||||
}
|
||||
|
||||
fun setTextColor(@ColorInt textColor: Int): FrpcSpinnerAdapter<*> {
|
||||
mTextColor = textColor
|
||||
return this
|
||||
}
|
||||
|
||||
fun setTextSize(textSize: Float): FrpcSpinnerAdapter<*> {
|
||||
mTextSize = textSize
|
||||
return this
|
||||
}
|
||||
|
||||
fun setBackgroundSelector(@DrawableRes backgroundSelector: Int): FrpcSpinnerAdapter<*> {
|
||||
mBackgroundSelector = backgroundSelector
|
||||
return this
|
||||
}
|
||||
|
||||
fun setFilterColor(filterColor: String): FrpcSpinnerAdapter<*> {
|
||||
mFilterColor = filterColor
|
||||
return this
|
||||
}
|
||||
|
||||
fun setIsFilterKey(isFilterKey: Boolean): FrpcSpinnerAdapter<*> {
|
||||
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,55 @@
|
||||
package com.idormy.sms.forwarder.adapter.spinner
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
@Suppress("unused")
|
||||
class FrpcSpinnerItem(
|
||||
var title: CharSequence,
|
||||
var icon: Drawable? = null,
|
||||
var uid: String = "",
|
||||
//var autorun: Int? = 1,
|
||||
var status: Int? = 1
|
||||
) {
|
||||
|
||||
fun setTitle(title: CharSequence): FrpcSpinnerItem {
|
||||
this.title = title
|
||||
return this
|
||||
}
|
||||
|
||||
fun setIcon(icon: Drawable?): FrpcSpinnerItem {
|
||||
this.icon = icon
|
||||
return this
|
||||
}
|
||||
|
||||
fun setUid(uid: String): FrpcSpinnerItem {
|
||||
this.uid = uid
|
||||
return this
|
||||
}
|
||||
|
||||
/*fun setAutorun(autorun: Int): FrpcSpinnerItem {
|
||||
this.autorun = autorun
|
||||
return this
|
||||
}*/
|
||||
|
||||
fun setStatus(status: Int): FrpcSpinnerItem {
|
||||
this.status = status
|
||||
return this
|
||||
}
|
||||
|
||||
// 注意:自定义实体需要重写对象的 toString 方法
|
||||
override fun toString(): String {
|
||||
return title.toString()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun of(title: CharSequence): FrpcSpinnerItem {
|
||||
return FrpcSpinnerItem(title)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun arrayOf(vararg titles: CharSequence): Array<FrpcSpinnerItem> {
|
||||
return titles.map { FrpcSpinnerItem(it) }.toTypedArray()
|
||||
}
|
||||
}
|
||||
}
|
@ -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 RuleSpinnerAdapter<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 RuleSpinnerItem
|
||||
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("RuleSpinnerAdapter", "keyword = $keyword")
|
||||
Log.d("RuleSpinnerAdapter", "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("RuleSpinnerAdapter", "onFilter error: ${e.message}")
|
||||
}
|
||||
}
|
||||
Log.d("RuleSpinnerAdapter", "mDisplayData = $mDisplayData")
|
||||
notifyDataSetChanged()
|
||||
return mDisplayData.size > 0
|
||||
}
|
||||
|
||||
fun setTextColor(@ColorInt textColor: Int): RuleSpinnerAdapter<*> {
|
||||
mTextColor = textColor
|
||||
return this
|
||||
}
|
||||
|
||||
fun setTextSize(textSize: Float): RuleSpinnerAdapter<*> {
|
||||
mTextSize = textSize
|
||||
return this
|
||||
}
|
||||
|
||||
fun setBackgroundSelector(@DrawableRes backgroundSelector: Int): RuleSpinnerAdapter<*> {
|
||||
mBackgroundSelector = backgroundSelector
|
||||
return this
|
||||
}
|
||||
|
||||
fun setFilterColor(filterColor: String): RuleSpinnerAdapter<*> {
|
||||
mFilterColor = filterColor
|
||||
return this
|
||||
}
|
||||
|
||||
fun setIsFilterKey(isFilterKey: Boolean): RuleSpinnerAdapter<*> {
|
||||
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 RuleSpinnerItem(
|
||||
var title: CharSequence,
|
||||
var icon: Drawable? = null,
|
||||
var id: Long? = 0L,
|
||||
var status: Int? = 1
|
||||
) {
|
||||
|
||||
fun setTitle(title: CharSequence): RuleSpinnerItem {
|
||||
this.title = title
|
||||
return this
|
||||
}
|
||||
|
||||
fun setIcon(icon: Drawable?): RuleSpinnerItem {
|
||||
this.icon = icon
|
||||
return this
|
||||
}
|
||||
|
||||
fun setId(id: Long): RuleSpinnerItem {
|
||||
this.id = id
|
||||
return this
|
||||
}
|
||||
|
||||
fun setStatus(status: Int): RuleSpinnerItem {
|
||||
this.status = status
|
||||
return this
|
||||
}
|
||||
|
||||
// 注意:自定义实体需要重写对象的 toString 方法
|
||||
override fun toString(): String {
|
||||
return title.toString()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun of(title: CharSequence): RuleSpinnerItem {
|
||||
return RuleSpinnerItem(title)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun arrayOf(vararg titles: CharSequence): Array<RuleSpinnerItem> {
|
||||
return titles.map { RuleSpinnerItem(it) }.toTypedArray()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
package com.idormy.sms.forwarder.adapter.spinner
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.xuexiang.xui.utils.ResUtils
|
||||
|
||||
@Suppress("unused")
|
||||
class SenderAdapterItem {
|
||||
|
||||
//标题内容
|
||||
var title: CharSequence
|
||||
|
||||
//图标
|
||||
var icon: Drawable? = null
|
||||
|
||||
//ID
|
||||
var id: Long? = 0L
|
||||
|
||||
//状态
|
||||
var status: Int? = 1
|
||||
|
||||
constructor(title: CharSequence) {
|
||||
this.title = title
|
||||
}
|
||||
|
||||
constructor(title: CharSequence, icon: Drawable?) {
|
||||
this.title = title
|
||||
this.icon = icon
|
||||
}
|
||||
|
||||
constructor(title: CharSequence, icon: Drawable?, id: Long?) {
|
||||
this.title = title
|
||||
this.icon = icon
|
||||
this.id = id
|
||||
}
|
||||
|
||||
constructor(title: CharSequence, icon: Drawable?, id: Long?, status: Int?) {
|
||||
this.title = title
|
||||
this.icon = icon
|
||||
this.id = id
|
||||
this.status = status
|
||||
}
|
||||
|
||||
constructor(title: CharSequence, drawableId: Int) : this(title, ResUtils.getDrawable(drawableId))
|
||||
constructor(title: CharSequence, drawableId: Int, id: Long) : this(title, ResUtils.getDrawable(drawableId), id)
|
||||
constructor(title: CharSequence, drawableId: Int, id: Long, status: Int) : this(title, ResUtils.getDrawable(drawableId), id, status)
|
||||
constructor(context: Context?, titleId: Int, drawableId: Int) : this(ResUtils.getString(titleId), ResUtils.getDrawable(context, drawableId))
|
||||
constructor(context: Context?, titleId: Int, drawableId: Int, id: Long) : this(ResUtils.getString(titleId), ResUtils.getDrawable(context, drawableId), id)
|
||||
constructor(context: Context?, titleId: Int, drawableId: Int, id: Long, status: Int) : this(ResUtils.getString(titleId), ResUtils.getDrawable(context, drawableId), id, status)
|
||||
constructor(context: Context?, title: CharSequence, drawableId: Int) : this(title, ResUtils.getDrawable(context, drawableId))
|
||||
constructor(context: Context?, title: CharSequence, drawableId: Int, id: Long) : this(title, ResUtils.getDrawable(context, drawableId), id)
|
||||
constructor(context: Context?, title: CharSequence, drawableId: Int, id: Long, status: Int) : this(title, ResUtils.getDrawable(context, drawableId), id, status)
|
||||
|
||||
fun setStatus(status: Int): SenderAdapterItem {
|
||||
this.status = status
|
||||
return this
|
||||
}
|
||||
|
||||
fun setId(id: Long): SenderAdapterItem {
|
||||
this.id = id
|
||||
return this
|
||||
}
|
||||
|
||||
fun setTitle(title: CharSequence): SenderAdapterItem {
|
||||
this.title = title
|
||||
return this
|
||||
}
|
||||
|
||||
fun setIcon(icon: Drawable?): SenderAdapterItem {
|
||||
this.icon = icon
|
||||
return this
|
||||
}
|
||||
|
||||
//注意:自定义实体需要重写对象的toString方法
|
||||
override fun toString(): String {
|
||||
return title.toString()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun of(title: CharSequence): SenderAdapterItem {
|
||||
return SenderAdapterItem(title)
|
||||
}
|
||||
|
||||
fun arrayof(title: Array<CharSequence>): Array<SenderAdapterItem?> {
|
||||
val array = arrayOfNulls<SenderAdapterItem>(title.size)
|
||||
for (i in array.indices) {
|
||||
array[i] = SenderAdapterItem(title[i])
|
||||
}
|
||||
return array
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.text.Html
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@ -14,13 +13,14 @@ 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.utils.ResUtils
|
||||
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", "SENSELESS_COMPARISON", "DEPRECATION")
|
||||
@Suppress("unused", "NAME_SHADOWING", "DEPRECATION")
|
||||
class SenderSpinnerAdapter<T> : BaseEditSpinnerAdapter<T>, EditSpinnerFilter {
|
||||
/**
|
||||
* 选项的文字颜色
|
||||
@ -71,13 +71,13 @@ class SenderSpinnerAdapter<T> : BaseEditSpinnerAdapter<T>, EditSpinnerFilter {
|
||||
} else {
|
||||
holder = convertView.tag as ViewHolder
|
||||
}
|
||||
val item = CollectionUtils.getListItem(mDataSource, mIndexs[position]) as SenderAdapterItem
|
||||
val item = CollectionUtils.getListItem(mDataSource, mIndexs[position]) as SenderSpinnerItem
|
||||
holder.iconView.setImageDrawable(item.icon)
|
||||
holder.statusView.setImageDrawable(
|
||||
ResUtils.getDrawable(
|
||||
getDrawable(
|
||||
when (item.status) {
|
||||
STATUS_OFF -> R.drawable.icon_off
|
||||
else -> R.drawable.icon_on
|
||||
STATUS_OFF -> R.drawable.ic_stop
|
||||
else -> R.drawable.ic_start
|
||||
}
|
||||
)
|
||||
)
|
||||
@ -109,6 +109,7 @@ class SenderSpinnerAdapter<T> : BaseEditSpinnerAdapter<T>, EditSpinnerFilter {
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e("SenderSpinnerAdapter", "onFilter error: ${e.message}")
|
||||
}
|
||||
}
|
||||
Log.d("SenderSpinnerAdapter", "mDisplayData = $mDisplayData")
|
||||
@ -141,7 +142,6 @@ class SenderSpinnerAdapter<T> : BaseEditSpinnerAdapter<T>, EditSpinnerFilter {
|
||||
return this
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
private class ViewHolder(convertView: View, @ColorInt textColor: Int, textSize: Float, @DrawableRes backgroundSelector: Int) {
|
||||
val iconView: ImageView = convertView.findViewById(R.id.iv_icon)
|
||||
|
@ -0,0 +1,49 @@
|
||||
package com.idormy.sms.forwarder.adapter.spinner
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
@Suppress("unused")
|
||||
class SenderSpinnerItem(
|
||||
var title: CharSequence,
|
||||
var icon: Drawable? = null,
|
||||
var id: Long? = 0L,
|
||||
var status: Int? = 1
|
||||
) {
|
||||
|
||||
fun setTitle(title: CharSequence): SenderSpinnerItem {
|
||||
this.title = title
|
||||
return this
|
||||
}
|
||||
|
||||
fun setIcon(icon: Drawable?): SenderSpinnerItem {
|
||||
this.icon = icon
|
||||
return this
|
||||
}
|
||||
|
||||
fun setId(id: Long): SenderSpinnerItem {
|
||||
this.id = id
|
||||
return this
|
||||
}
|
||||
|
||||
fun setStatus(status: Int): SenderSpinnerItem {
|
||||
this.status = status
|
||||
return this
|
||||
}
|
||||
|
||||
// 注意:自定义实体需要重写对象的 toString 方法
|
||||
override fun toString(): String {
|
||||
return title.toString()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun of(title: CharSequence): SenderSpinnerItem {
|
||||
return SenderSpinnerItem(title)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun arrayOf(vararg titles: CharSequence): Array<SenderSpinnerItem> {
|
||||
return titles.map { SenderSpinnerItem(it) }.toTypedArray()
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -5,14 +5,20 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.hjq.language.MultiLanguages
|
||||
import com.idormy.sms.forwarder.utils.EVENT_TOAST_ERROR
|
||||
import com.idormy.sms.forwarder.utils.EVENT_TOAST_INFO
|
||||
import com.idormy.sms.forwarder.utils.EVENT_TOAST_SUCCESS
|
||||
import com.idormy.sms.forwarder.utils.EVENT_TOAST_WARNING
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.jeremyliao.liveeventbus.LiveEventBus
|
||||
import com.xuexiang.xpage.base.XPageActivity
|
||||
import com.xuexiang.xpage.base.XPageFragment
|
||||
import com.xuexiang.xpage.core.CoreSwitchBean
|
||||
import com.xuexiang.xrouter.facade.service.SerializationService
|
||||
import com.xuexiang.xrouter.launcher.XRouter
|
||||
import com.xuexiang.xui.utils.ResUtils
|
||||
import com.xuexiang.xui.widget.slideback.SlideBack
|
||||
import io.github.inflationx.viewpump.ViewPumpContextWrapper
|
||||
import com.xuexiang.xutil.resource.ResUtils.isRtl
|
||||
|
||||
/**
|
||||
* 基础容器Activity
|
||||
@ -20,7 +26,7 @@ import io.github.inflationx.viewpump.ViewPumpContextWrapper
|
||||
* @author XUE
|
||||
* @since 2019/3/22 11:21
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate", "UNCHECKED_CAST")
|
||||
@Suppress("MemberVisibilityCanBePrivate", "UNCHECKED_CAST", "DEPRECATION", "EmptyMethod")
|
||||
open class BaseActivity<Binding : ViewBinding?> : XPageActivity() {
|
||||
/**
|
||||
* 获取Binding
|
||||
@ -35,7 +41,10 @@ open class BaseActivity<Binding : ViewBinding?> : XPageActivity() {
|
||||
|
||||
override fun attachBaseContext(newBase: Context) {
|
||||
//注入字体
|
||||
super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase))
|
||||
//super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase))
|
||||
// 绑定语种
|
||||
//super.attachBaseContext(ViewPumpContextWrapper.wrap(MultiLanguages.attach(newBase)))
|
||||
super.attachBaseContext(MultiLanguages.attach(newBase))
|
||||
}
|
||||
|
||||
override fun getCustomRootView(): View? {
|
||||
@ -47,6 +56,20 @@ open class BaseActivity<Binding : ViewBinding?> : XPageActivity() {
|
||||
initStatusBarStyle()
|
||||
super.onCreate(savedInstanceState)
|
||||
registerSlideBack()
|
||||
|
||||
//用于接收各种事件的吐司
|
||||
LiveEventBus.get(EVENT_TOAST_ERROR, String::class.java).observe(this) { msg: String ->
|
||||
XToastUtils.error(msg, 15000)
|
||||
}
|
||||
LiveEventBus.get(EVENT_TOAST_SUCCESS, String::class.java).observe(this) { msg: String ->
|
||||
XToastUtils.success(msg)
|
||||
}
|
||||
LiveEventBus.get(EVENT_TOAST_INFO, String::class.java).observe(this) { msg: String ->
|
||||
XToastUtils.info(msg)
|
||||
}
|
||||
LiveEventBus.get(EVENT_TOAST_WARNING, String::class.java).observe(this) { msg: String ->
|
||||
XToastUtils.warning(msg, 10000)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -121,7 +144,7 @@ open class BaseActivity<Binding : ViewBinding?> : XPageActivity() {
|
||||
if (isSupportSlideBack) {
|
||||
SlideBack.with(this)
|
||||
.haveScroll(true)
|
||||
.edgeMode(if (ResUtils.isRtl()) SlideBack.EDGE_RIGHT else SlideBack.EDGE_LEFT)
|
||||
.edgeMode(if (isRtl()) SlideBack.EDGE_RIGHT else SlideBack.EDGE_LEFT)
|
||||
.callBack { popPage() }
|
||||
.register()
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import com.xuexiang.xui.widget.actionbar.TitleUtils
|
||||
* @author xuexiang
|
||||
* @since 2018/11/22 上午11:26
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
abstract class BaseContainerFragment : XPageContainerListFragment() {
|
||||
override fun initPage() {
|
||||
initTitle()
|
||||
|
@ -35,7 +35,7 @@ import java.lang.reflect.Type
|
||||
* @author xuexiang
|
||||
* @since 2018/5/25 下午3:44
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
@Suppress("MemberVisibilityCanBePrivate", "EmptyMethod")
|
||||
abstract class BaseFragment<Binding : ViewBinding?> : XPageFragment() {
|
||||
private var mIProgressLoader: IProgressLoader? = null
|
||||
|
||||
@ -204,27 +204,35 @@ abstract class BaseFragment<Binding : ViewBinding?> : XPageFragment() {
|
||||
is Int -> {
|
||||
option.putInt(key, value)
|
||||
}
|
||||
|
||||
is Float -> {
|
||||
option.putFloat(key, value)
|
||||
}
|
||||
|
||||
is String -> {
|
||||
option.putString(key, value)
|
||||
}
|
||||
|
||||
is Boolean -> {
|
||||
option.putBoolean(key, value)
|
||||
}
|
||||
|
||||
is Long -> {
|
||||
option.putLong(key, value)
|
||||
}
|
||||
|
||||
is Double -> {
|
||||
option.putDouble(key, value)
|
||||
}
|
||||
|
||||
is Parcelable -> {
|
||||
option.putParcelable(key, value)
|
||||
}
|
||||
|
||||
is Serializable -> {
|
||||
option.putSerializable(key, value)
|
||||
}
|
||||
|
||||
else -> {
|
||||
option.putString(key, serializeObject(value))
|
||||
}
|
||||
|
@ -1,47 +1,26 @@
|
||||
package com.idormy.sms.forwarder.core
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.work.Configuration
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.BuildConfig
|
||||
import com.idormy.sms.forwarder.database.repository.FrpcRepository
|
||||
import com.idormy.sms.forwarder.database.repository.LogsRepository
|
||||
import com.idormy.sms.forwarder.database.repository.MsgRepository
|
||||
import com.idormy.sms.forwarder.database.repository.RuleRepository
|
||||
import com.idormy.sms.forwarder.database.repository.SenderRepository
|
||||
import com.idormy.sms.forwarder.service.ForegroundService
|
||||
import com.idormy.sms.forwarder.database.repository.TaskRepository
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object Core : Configuration.Provider {
|
||||
lateinit var app: Application
|
||||
val frpc: FrpcRepository by lazy { (app as App).frpcRepository }
|
||||
val msg: MsgRepository by lazy { (app as App).msgRepository }
|
||||
val logs: LogsRepository by lazy { (app as App).logsRepository }
|
||||
val rule: RuleRepository by lazy { (app as App).ruleRepository }
|
||||
val sender: SenderRepository by lazy { (app as App).senderRepository }
|
||||
/*
|
||||
val telephonyManager: TelephonyManager by lazy { app.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager }
|
||||
val smsManager: SmsManager by lazy { app.getSystemService(SmsManager::class.java) }
|
||||
val subscriptionManager: SubscriptionManager by lazy {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
SubscriptionManager.from(app)
|
||||
} else {
|
||||
app.getSystemService(SubscriptionManager::class.java)
|
||||
}
|
||||
}
|
||||
val user by lazy { app.getSystemService<UserManager>()!! }*/
|
||||
|
||||
|
||||
/*val directBootAware: Boolean get() = directBootSupported && dataStore.canToggleLocked
|
||||
val directBootSupported by lazy {
|
||||
Build.VERSION.SDK_INT >= 24 && try {
|
||||
app.getSystemService<DevicePolicyManager>()?.storageEncryptionStatus ==
|
||||
DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER
|
||||
} catch (_: RuntimeException) {
|
||||
false
|
||||
}
|
||||
}*/
|
||||
val task: TaskRepository by lazy { (app as App).taskRepository }
|
||||
|
||||
fun init(app: Application) {
|
||||
this.app = app
|
||||
@ -55,6 +34,4 @@ object Core : Configuration.Provider {
|
||||
setTaskExecutor { (app as App).applicationScope.launch { it.run() } }
|
||||
}.build()
|
||||
}
|
||||
|
||||
fun startService() = ContextCompat.startForegroundService(app, Intent(app, ForegroundService::class.java))
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -10,9 +10,18 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.webkit.*
|
||||
import android.view.Gravity
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.DownloadListener
|
||||
import android.webkit.WebChromeClient
|
||||
import android.webkit.WebResourceError
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
@ -20,7 +29,9 @@ import android.widget.TextView
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.just.agentweb.action.PermissionInterceptor
|
||||
import com.just.agentweb.core.AgentWeb
|
||||
@ -44,7 +55,13 @@ import com.xuexiang.xutil.net.JsonUtil
|
||||
* @author xuexiang
|
||||
* @since 2019/1/4 下午11:13
|
||||
*/
|
||||
@Suppress("unused", "MemberVisibilityCanBePrivate", "ProtectedInFinal", "NAME_SHADOWING", "UNUSED_PARAMETER", "OVERRIDE_DEPRECATION")
|
||||
@Suppress(
|
||||
"unused",
|
||||
"ProtectedInFinal",
|
||||
"NAME_SHADOWING",
|
||||
"UNUSED_PARAMETER",
|
||||
"OVERRIDE_DEPRECATION"
|
||||
)
|
||||
class AgentWebFragment : Fragment(), FragmentKeyDown {
|
||||
private var mBackImageView: ImageView? = null
|
||||
private var mLineView: View? = null
|
||||
@ -89,7 +106,7 @@ class AgentWebFragment : Fragment(), FragmentKeyDown {
|
||||
.ready() //设置 WebSettings。
|
||||
//WebView载入该url地址的页面并显示。
|
||||
.go(url)
|
||||
if (com.idormy.sms.forwarder.App.isDebug) {
|
||||
if (App.isDebug) {
|
||||
AgentWebConfig.debug()
|
||||
}
|
||||
|
||||
@ -140,6 +157,7 @@ class AgentWebFragment : Fragment(), FragmentKeyDown {
|
||||
if (!mAgentWeb!!.back()) {
|
||||
this.requireActivity().finish()
|
||||
}
|
||||
|
||||
R.id.iv_finish -> this.requireActivity().finish()
|
||||
R.id.iv_more -> showPoPup(v)
|
||||
else -> {}
|
||||
@ -372,11 +390,11 @@ class AgentWebFragment : Fragment(), FragmentKeyDown {
|
||||
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
|
||||
Log.i(TAG, "mUrl:$url onPageStarted target:$url")
|
||||
timer[url] = System.currentTimeMillis()
|
||||
if (url == url) {
|
||||
pageNavigator(View.GONE)
|
||||
} else {
|
||||
//if (url == url) {
|
||||
// pageNavigator(View.GONE)
|
||||
//} else {
|
||||
pageNavigator(View.VISIBLE)
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
@ -452,24 +470,28 @@ class AgentWebFragment : Fragment(), FragmentKeyDown {
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
R.id.copy -> {
|
||||
if (mAgentWeb != null) {
|
||||
mAgentWeb!!.webCreator.webView.url?.let { toCopy(context, it) }
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
R.id.default_browser -> {
|
||||
if (mAgentWeb != null) {
|
||||
mAgentWeb!!.webCreator.webView.url?.let { openBrowser(it) }
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
R.id.share -> {
|
||||
if (mAgentWeb != null) {
|
||||
mAgentWeb!!.webCreator.webView.url?.let { shareWebUrl(it) }
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@ -495,7 +517,8 @@ class AgentWebFragment : Fragment(), FragmentKeyDown {
|
||||
* @param text
|
||||
*/
|
||||
private fun toCopy(context: Context?, text: String) {
|
||||
val manager = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val manager =
|
||||
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
manager.setPrimaryClip(ClipData.newPlainText(null, text))
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,6 @@ import com.just.agentweb.core.AgentWeb
|
||||
* @author xuexiang
|
||||
* @since 2019/5/28 10:22
|
||||
*/
|
||||
@Suppress("unused")
|
||||
abstract class BaseWebViewFragment : BaseFragment<ViewBinding?>() {
|
||||
private var mAgentWeb: AgentWeb? = null
|
||||
|
||||
|
@ -11,7 +11,7 @@ import android.webkit.WebView
|
||||
* 修复 Android 5.0 & 5.1 打开 WebView 闪退问题:
|
||||
* 参阅 https://stackoverflow.com/questions/41025200/android-view-inflateexception-error-inflating-class-android-webkit-webview
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@Suppress("unused", "DEPRECATION")
|
||||
class LollipopFixedWebView : WebView {
|
||||
constructor(context: Context) : super(getFixedContext(context))
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(getFixedContext(context), attrs)
|
||||
|
@ -1,8 +1,8 @@
|
||||
package com.idormy.sms.forwarder.core.webview
|
||||
|
||||
import android.util.Log
|
||||
import android.webkit.JsResult
|
||||
import android.webkit.WebView
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.just.agentweb.core.client.MiddlewareWebChromeBase
|
||||
|
||||
/**
|
||||
|
@ -2,16 +2,16 @@ package com.idormy.sms.forwarder.core.webview
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.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.xui.utils.ResUtils
|
||||
import java.util.*
|
||||
import com.xuexiang.xutil.resource.ResUtils.getStringArray
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* 【网络请求、加载】
|
||||
@ -47,6 +47,7 @@ import java.util.*
|
||||
*
|
||||
* 这里主要是做去广告的工作
|
||||
*/
|
||||
@Suppress("UNUSED_PARAMETER", "DEPRECATION", "OVERRIDE_DEPRECATION")
|
||||
open class MiddlewareWebViewClient : MiddlewareWebClientBase() {
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
|
||||
@ -120,7 +121,7 @@ open class MiddlewareWebViewClient : MiddlewareWebClientBase() {
|
||||
* @return
|
||||
*/
|
||||
private fun hasAdUrl(url: String): Boolean {
|
||||
val adUrls = ResUtils.getStringArray(R.array.adBlockUrl)
|
||||
val adUrls = getStringArray(R.array.adBlockUrl)
|
||||
for (adUrl in adUrls) {
|
||||
if (url.contains(adUrl)) {
|
||||
return true
|
||||
|
@ -2,7 +2,7 @@ package com.idormy.sms.forwarder.core.webview
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import android.webkit.WebView
|
||||
import com.just.agentweb.core.web.AgentWebUIControllerImplBase
|
||||
import java.lang.ref.WeakReference
|
||||
|
@ -7,8 +7,8 @@ import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.xuexiang.xui.utils.ResUtils
|
||||
import com.xuexiang.xui.widget.dialog.DialogLoader
|
||||
import com.xuexiang.xutil.XUtil
|
||||
import com.xuexiang.xutil.app.ActivityUtils
|
||||
@ -27,7 +27,7 @@ class WebViewInterceptDialog : AppCompatActivity(), DialogInterface.OnDismissLis
|
||||
DialogLoader.getInstance().showConfirmDialog(
|
||||
this,
|
||||
getOpenTitle(url),
|
||||
ResUtils.getString(R.string.lab_yes),
|
||||
getString(R.string.lab_yes),
|
||||
{ dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
if (isAppLink(url)) {
|
||||
@ -36,16 +36,16 @@ class WebViewInterceptDialog : AppCompatActivity(), DialogInterface.OnDismissLis
|
||||
openApp(url)
|
||||
}
|
||||
},
|
||||
ResUtils.getString(R.string.lab_no)
|
||||
getString(R.string.lab_no)
|
||||
) { dialog: DialogInterface, _: Int -> dialog.dismiss() }.setOnDismissListener(this)
|
||||
}
|
||||
|
||||
private fun getOpenTitle(url: String): String {
|
||||
val scheme = getScheme(url)
|
||||
return if ("mqqopensdkapi" == scheme) {
|
||||
ResUtils.getString(R.string.lab_open_qq_app)
|
||||
getString(R.string.lab_open_qq_app)
|
||||
} else {
|
||||
ResUtils.getString(R.string.lab_open_third_app)
|
||||
getString(R.string.lab_open_third_app)
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,6 +55,7 @@ class WebViewInterceptDialog : AppCompatActivity(), DialogInterface.OnDismissLis
|
||||
return intent.scheme
|
||||
} catch (e: URISyntaxException) {
|
||||
e.printStackTrace()
|
||||
Log.e("WebViewInterceptDialog", e.toString())
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -9,14 +9,25 @@ import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import android.view.*
|
||||
import android.webkit.*
|
||||
import android.view.Gravity
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.DownloadListener
|
||||
import android.webkit.WebChromeClient
|
||||
import android.webkit.WebResourceError
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.fragment.app.Fragment
|
||||
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.FragmentAgentwebBinding
|
||||
@ -97,7 +108,7 @@ class XPageWebViewFragment : BaseFragment<FragmentAgentwebBinding?>(), View.OnCl
|
||||
.ready() //设置 WebSettings。
|
||||
//WebView载入该url地址的页面并显示。
|
||||
.go(url)
|
||||
if (com.idormy.sms.forwarder.App.isDebug) {
|
||||
if (App.isDebug) {
|
||||
AgentWebConfig.debug()
|
||||
}
|
||||
pageNavigator(View.GONE)
|
||||
@ -254,7 +265,7 @@ class XPageWebViewFragment : BaseFragment<FragmentAgentwebBinding?>(), View.OnCl
|
||||
*
|
||||
* @return IAgentWebSettings
|
||||
*/
|
||||
val settings: IAgentWebSettings<*>
|
||||
private val settings: IAgentWebSettings<*>
|
||||
get() = object : AbsAgentWebSettings() {
|
||||
private val mAgentWeb: AgentWeb? = null
|
||||
override fun bindAgentWebSupport(agentWeb: AgentWeb) {
|
||||
@ -360,11 +371,11 @@ class XPageWebViewFragment : BaseFragment<FragmentAgentwebBinding?>(), View.OnCl
|
||||
|
||||
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap) {
|
||||
mTimer[url] = System.currentTimeMillis()
|
||||
if (url == url) {
|
||||
pageNavigator(View.GONE)
|
||||
} else {
|
||||
//if (url == url) {
|
||||
// pageNavigator(View.GONE)
|
||||
//} else {
|
||||
pageNavigator(View.VISIBLE)
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
@ -420,24 +431,28 @@ class XPageWebViewFragment : BaseFragment<FragmentAgentwebBinding?>(), View.OnCl
|
||||
}
|
||||
return@OnMenuItemClickListener true
|
||||
}
|
||||
|
||||
R.id.copy -> {
|
||||
if (mAgentWeb != null) {
|
||||
mAgentWeb!!.webCreator.webView.url?.let { toCopy(context, it) }
|
||||
}
|
||||
return@OnMenuItemClickListener true
|
||||
}
|
||||
|
||||
R.id.default_browser -> {
|
||||
if (mAgentWeb != null) {
|
||||
mAgentWeb!!.webCreator.webView.url?.let { openBrowser(it) }
|
||||
}
|
||||
return@OnMenuItemClickListener true
|
||||
}
|
||||
|
||||
R.id.share -> {
|
||||
if (mAgentWeb != null) {
|
||||
mAgentWeb!!.webCreator.webView.url?.let { shareWebUrl(it) }
|
||||
}
|
||||
return@OnMenuItemClickListener true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@ -480,7 +495,8 @@ class XPageWebViewFragment : BaseFragment<FragmentAgentwebBinding?>(), View.OnCl
|
||||
* @param text
|
||||
*/
|
||||
private fun toCopy(context: Context?, text: String) {
|
||||
val manager = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val manager =
|
||||
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
manager.setPrimaryClip(ClipData.newPlainText(null, text))
|
||||
}
|
||||
|
||||
|
@ -9,28 +9,37 @@ import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import com.idormy.sms.forwarder.database.dao.FrpcDao
|
||||
import com.idormy.sms.forwarder.database.dao.LogsDao
|
||||
import com.idormy.sms.forwarder.database.dao.MsgDao
|
||||
import com.idormy.sms.forwarder.database.dao.RuleDao
|
||||
import com.idormy.sms.forwarder.database.dao.SenderDao
|
||||
import com.idormy.sms.forwarder.database.dao.TaskDao
|
||||
import com.idormy.sms.forwarder.database.entity.Frpc
|
||||
import com.idormy.sms.forwarder.database.entity.Logs
|
||||
import com.idormy.sms.forwarder.database.entity.LogsDetail
|
||||
import com.idormy.sms.forwarder.database.entity.Msg
|
||||
import com.idormy.sms.forwarder.database.entity.Rule
|
||||
import com.idormy.sms.forwarder.database.entity.Sender
|
||||
import com.idormy.sms.forwarder.database.ext.Converters
|
||||
import com.idormy.sms.forwarder.database.entity.Task
|
||||
import com.idormy.sms.forwarder.database.ext.ConvertersDate
|
||||
import com.idormy.sms.forwarder.utils.DATABASE_NAME
|
||||
import java.util.concurrent.Executors
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.idormy.sms.forwarder.utils.TAG_LIST
|
||||
|
||||
@Database(
|
||||
entities = [Frpc::class, Logs::class, Rule::class, Sender::class],
|
||||
version = 10,
|
||||
entities = [Frpc::class, Msg::class, Logs::class, Rule::class, Sender::class, Task::class],
|
||||
views = [LogsDetail::class],
|
||||
version = 20,
|
||||
exportSchema = false
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
@TypeConverters(ConvertersDate::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
abstract fun frpcDao(): FrpcDao
|
||||
abstract fun msgDao(): MsgDao
|
||||
abstract fun logsDao(): LogsDao
|
||||
abstract fun ruleDao(): RuleDao
|
||||
abstract fun senderDao(): SenderDao
|
||||
abstract fun taskDao(): TaskDao
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
@ -43,9 +52,10 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
}
|
||||
|
||||
private fun buildDatabase(context: Context): AppDatabase {
|
||||
return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, DATABASE_NAME)
|
||||
.allowMainThreadQueries() //TODO:允许主线程访问,后面再优化
|
||||
.addCallback(object : RoomDatabase.Callback() {
|
||||
val builder = Room.databaseBuilder(
|
||||
context.applicationContext, AppDatabase::class.java, DATABASE_NAME
|
||||
).allowMainThreadQueries() //TODO:允许主线程访问,后面再优化
|
||||
.addCallback(object : Callback() {
|
||||
override fun onCreate(db: SupportSQLiteDatabase) {
|
||||
//fillInDb(context.applicationContext)
|
||||
db.execSQL(
|
||||
@ -56,7 +66,7 @@ server_addr = 88.88.88.88
|
||||
#frps服务端公网端口
|
||||
server_port = 8888
|
||||
#可选,建议启用
|
||||
token = 888888888
|
||||
token = 88888888
|
||||
#连接服务端的超时时间(增大时间避免frpc在网络未就绪的情况下启动失败)
|
||||
dial_server_timeout = 60
|
||||
#第一次登陆失败后是否退出
|
||||
@ -67,7 +77,7 @@ login_fail_exit = false
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 5000
|
||||
#只要修改下面这一行
|
||||
#只要修改下面这一行(frps所在服务器必须暴露的公网端口)
|
||||
remote_port = 5000
|
||||
|
||||
#[二选一即可]每台机器不可重复,通过 http://smsf.demo.com 访问
|
||||
@ -75,14 +85,13 @@ remote_port = 5000
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 5000
|
||||
#只要修改下面这一行
|
||||
#只要修改下面这一行(在frps端将域名反代到vhost_http_port)
|
||||
custom_domains = smsf.demo.com
|
||||
', 0, '1651334400000')
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
})
|
||||
.addMigrations(
|
||||
}).addMigrations(
|
||||
MIGRATION_1_2,
|
||||
MIGRATION_2_3,
|
||||
MIGRATION_3_4,
|
||||
@ -92,11 +101,25 @@ custom_domains = smsf.demo.com
|
||||
MIGRATION_7_8,
|
||||
MIGRATION_8_9,
|
||||
MIGRATION_9_10,
|
||||
MIGRATION_10_11,
|
||||
MIGRATION_11_12,
|
||||
MIGRATION_12_13,
|
||||
MIGRATION_13_14,
|
||||
MIGRATION_14_15,
|
||||
MIGRATION_15_16,
|
||||
MIGRATION_16_17,
|
||||
MIGRATION_17_18,
|
||||
MIGRATION_18_19,
|
||||
MIGRATION_19_20,
|
||||
)
|
||||
.setQueryCallback({ sqlQuery, bindArgs ->
|
||||
|
||||
/*if (BuildConfig.DEBUG) {
|
||||
builder.setQueryCallback({ sqlQuery, bindArgs ->
|
||||
println("SQL_QUERY: $sqlQuery\nBIND_ARGS: $bindArgs")
|
||||
}, Executors.newSingleThreadExecutor())
|
||||
.build()
|
||||
}*/
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
//转发日志添加SIM卡槽信息
|
||||
@ -175,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 = 888888888
|
||||
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
|
||||
#只要修改下面这一行
|
||||
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
|
||||
|
||||
#[二选一即可]每台机器的 name 和 customDomains 不可重复,通过 http://smsf.demo.com 访问
|
||||
[[proxies]]
|
||||
#同一个frps下,多台设备的 name 不可重复
|
||||
name = "SmsForwarder-HTTP-001"
|
||||
type = "http"
|
||||
localPort = 5000
|
||||
#只要修改下面这一行(在frps端将域名反代到vhost_http_port)
|
||||
customDomains = ["smsf.demo.com"]
|
||||
|
||||
#[二选一即可]每台机器不可重复,通过 http://smsf.demo.com 访问
|
||||
[SmsForwarder-HTTP]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 5000
|
||||
#只要修改下面这一行
|
||||
custom_domains = smsf.demo.com
|
||||
', 0, '1651334400000')
|
||||
""".trimIndent()
|
||||
)
|
||||
@ -270,6 +298,168 @@ CREATE TABLE "Sender" (
|
||||
}
|
||||
}
|
||||
|
||||
//转发日志添加SIM卡槽ID
|
||||
private val MIGRATION_10_11 = object : Migration(10, 11) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("Alter table Logs add column sub_id INTEGER NOT NULL DEFAULT 0")
|
||||
}
|
||||
}
|
||||
|
||||
//单个转发规则可绑定多个发送通道
|
||||
private val MIGRATION_11_12 = object : Migration(11, 12) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("Alter table Logs add column sender_id INTEGER NOT NULL DEFAULT 0")
|
||||
database.execSQL("Update Logs Set sender_id = (Select sender_id from Rule where Logs.rule_id = Rule.id)")
|
||||
database.execSQL("Alter table Rule add column sender_list TEXT NOT NULL DEFAULT ''")
|
||||
database.execSQL("Update Rule set sender_list = sender_id")
|
||||
database.execSQL("CREATE INDEX \"index_Rule_sender_ids\" ON \"Rule\" ( \"sender_list\" ASC)")
|
||||
//删除字段:sender_id
|
||||
/*database.execSQL("Create table Rule_t as Select id,type,filed,check,value,sender_list,sms_template,regex_replace,sim_slot,status,time from Rule where 1 = 1")
|
||||
database.execSQL("Drop table Rule")
|
||||
database.execSQL("Alter table Rule_t rename to Rule")
|
||||
database.execSQL("CREATE UNIQUE INDEX \"index_Rule_id\" ON \"Rule\" ( \"id\" ASC)")*/
|
||||
}
|
||||
}
|
||||
|
||||
//转发规则添加发送通道逻辑
|
||||
private val MIGRATION_12_13 = object : Migration(12, 13) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("Alter table Rule add column sender_logic TEXT NOT NULL DEFAULT 'ALL'")
|
||||
}
|
||||
}
|
||||
|
||||
//分割Logs表
|
||||
private val MIGRATION_13_14 = object : Migration(13, 14) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
//database.execSQL("Create table Msg as Select id,type,`from`,content,(case when sim_info like 'SIM1%' then '0' when sim_info like 'SIM2%' then '1' else '-1' end) as sim_slot,sim_info,sub_id,time from Logs where 1 = 1")
|
||||
database.execSQL(
|
||||
"""
|
||||
CREATE TABLE "Msg" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"type" TEXT NOT NULL DEFAULT 'sms',
|
||||
"from" TEXT NOT NULL DEFAULT '',
|
||||
"content" TEXT NOT NULL DEFAULT '',
|
||||
"sim_slot" INTEGER NOT NULL DEFAULT -1,
|
||||
"sim_info" TEXT NOT NULL DEFAULT '',
|
||||
"sub_id" INTEGER NOT NULL DEFAULT 0,
|
||||
"time" INTEGER NOT NULL
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
database.execSQL("INSERT INTO Msg (id,type,`from`,content,sim_slot,sim_info,sub_id,time) Select id,type,`from`,content,(case when sim_info like 'SIM1%' then '0' when sim_info like 'SIM2%' then '1' else '-1' end) as sim_slot,sim_info,sub_id,time from Logs where 1 = 1")
|
||||
database.execSQL("CREATE UNIQUE INDEX \"index_Msg_id\" ON \"Msg\" ( \"id\" ASC)")
|
||||
database.execSQL("ALTER TABLE Logs RENAME TO Logs_old")
|
||||
//database.execSQL("Create table Logs_new as Select id,id as msg_id,rule_id,sender_id,forward_status,forward_response,time from Logs where 1 = 1")
|
||||
database.execSQL(
|
||||
"""
|
||||
CREATE TABLE "Logs" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"type" TEXT NOT NULL DEFAULT 'sms',
|
||||
"msg_id" INTEGER NOT NULL DEFAULT 0,
|
||||
"rule_id" INTEGER NOT NULL DEFAULT 0,
|
||||
"sender_id" INTEGER NOT NULL DEFAULT 0,
|
||||
"forward_status" INTEGER NOT NULL DEFAULT 1,
|
||||
"forward_response" TEXT NOT NULL DEFAULT '',
|
||||
"time" INTEGER NOT NULL,
|
||||
FOREIGN KEY ("msg_id") REFERENCES "Msg" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY ("rule_id") REFERENCES "Rule" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY ("sender_id") REFERENCES "Sender" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
""".trimIndent()
|
||||
)
|
||||
database.execSQL("INSERT INTO Logs (id,type,msg_id,rule_id,sender_id,forward_status,forward_response,time) SELECT id,type,id as msg_id,rule_id,sender_id,forward_status,forward_response,time FROM Logs_old")
|
||||
database.execSQL("DROP TABLE Logs_old")
|
||||
database.execSQL("CREATE UNIQUE INDEX \"index_Logs_id\" ON \"Logs\" ( \"id\" ASC)")
|
||||
database.execSQL("CREATE INDEX \"index_Logs_msg_id\" ON \"Logs\" ( \"msg_id\" ASC)")
|
||||
database.execSQL("CREATE INDEX \"index_Logs_rule_id\" ON \"Logs\" ( \"rule_id\" ASC)")
|
||||
database.execSQL("CREATE INDEX \"index_Logs_sender_id\" ON \"Logs\" ( \"sender_id\" ASC)")
|
||||
}
|
||||
}
|
||||
|
||||
// 定义数据库迁移配置
|
||||
private val MIGRATION_14_15 = object : Migration(14, 15) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
// 这里新建一个视图(视图名称要用两个半角的间隔号括起来)
|
||||
database.execSQL("CREATE VIEW `LogsDetail` AS SELECT LOGS.id,LOGS.type,LOGS.msg_id,LOGS.rule_id,LOGS.sender_id,LOGS.forward_status,LOGS.forward_response,LOGS.TIME,Rule.filed AS rule_filed,Rule.`check` AS rule_check,Rule.value AS rule_value,Rule.sim_slot AS rule_sim_slot,Sender.type AS sender_type,Sender.NAME AS sender_name FROM LOGS LEFT JOIN Rule ON LOGS.rule_id = Rule.id LEFT JOIN Sender ON LOGS.sender_id = Sender.id")
|
||||
}
|
||||
}
|
||||
|
||||
//免打扰(禁用转发)时间段
|
||||
private val MIGRATION_15_16 = object : Migration(15, 16) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("Alter table rule add column silent_period_start INTEGER NOT NULL DEFAULT 0 ")
|
||||
database.execSQL("Alter table rule add column silent_period_end INTEGER NOT NULL DEFAULT 0 ")
|
||||
}
|
||||
}
|
||||
|
||||
//通话类型:1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
|
||||
private val MIGRATION_16_17 = object : Migration(16, 17) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("Alter table Msg add column call_type INTEGER NOT NULL DEFAULT 0")
|
||||
}
|
||||
}
|
||||
|
||||
//自动化任务
|
||||
private val MIGRATION_17_18 = object : Migration(17, 18) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL(
|
||||
"""
|
||||
CREATE TABLE "Task" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"type" INTEGER NOT NULL DEFAULT 1,
|
||||
"name" TEXT NOT NULL DEFAULT '',
|
||||
"description" TEXT NOT NULL DEFAULT '',
|
||||
"conditions" TEXT NOT NULL DEFAULT '',
|
||||
"actions" TEXT NOT NULL DEFAULT '',
|
||||
"last_exec_time" INTEGER NOT NULL,
|
||||
"next_exec_time" INTEGER NOT NULL,
|
||||
"status" INTEGER NOT NULL DEFAULT 1
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//自定义模板可用变量统一成英文标签
|
||||
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 '' ")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,14 @@
|
||||
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.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.Frpc
|
||||
import io.reactivex.Single
|
||||
|
||||
@ -17,6 +24,9 @@ interface FrpcDao {
|
||||
@Query("DELETE FROM Frpc where uid=:uid")
|
||||
fun delete(uid: String)
|
||||
|
||||
@Query("DELETE FROM Frpc")
|
||||
fun deleteAll()
|
||||
|
||||
@Update
|
||||
fun update(frpc: Frpc)
|
||||
|
||||
@ -24,16 +34,26 @@ interface FrpcDao {
|
||||
fun get(uid: String): Single<Frpc>
|
||||
|
||||
//TODO:允许主线程访问,后面再优化
|
||||
@Query("SELECT * FROM Frpc where uid=:uid")
|
||||
fun getOne(uid: String): Frpc
|
||||
|
||||
@Query("SELECT * FROM Frpc where autorun=1")
|
||||
fun getAutorun(): List<Frpc>
|
||||
|
||||
//使用 ORDER BY 子句和 instr() 函数按照列表中 uid 的顺序返回结果
|
||||
//@Query("SELECT * FROM Frpc WHERE uid IN (:uids) ORDER BY instr(:instr, uid)")
|
||||
//fun getByUids(uids: List<String>, instr: String): List<Frpc>
|
||||
@Query("SELECT * FROM Frpc WHERE uid IN (:uids)")
|
||||
fun getByUids(uids: List<String>): List<Frpc>
|
||||
|
||||
@Query("SELECT * FROM Frpc ORDER BY time DESC")
|
||||
fun pagingSource(): PagingSource<Int, Frpc>
|
||||
|
||||
//TODO:允许主线程访问,后面再优化
|
||||
@Query("SELECT * FROM Frpc ORDER BY time ASC")
|
||||
fun getAll(): List<Frpc>
|
||||
@Query("SELECT * FROM Frpc ORDER BY time DESC")
|
||||
fun getAll(): Single<List<Frpc>>
|
||||
|
||||
@Transaction
|
||||
@RawQuery(observedEntities = [Frpc::class])
|
||||
fun getAllRaw(query: SupportSQLiteQuery): List<Frpc>
|
||||
|
||||
@Query("DELETE FROM Frpc")
|
||||
fun deleteAll()
|
||||
}
|
@ -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.Logs
|
||||
import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender
|
||||
import io.reactivex.Completable
|
||||
@ -22,19 +30,12 @@ interface LogsDao {
|
||||
@Query("DELETE FROM Logs where type=:type")
|
||||
fun deleteAll(type: String): Completable
|
||||
|
||||
@Query("DELETE FROM Logs")
|
||||
fun deleteAll()
|
||||
|
||||
@Update
|
||||
fun update(logs: Logs): Completable
|
||||
|
||||
@Query("SELECT * FROM Logs where id=:id")
|
||||
fun get(id: Long): Single<Logs>
|
||||
|
||||
@Query("SELECT count(*) FROM Logs where type=:type and forward_status=:forwardStatus")
|
||||
fun count(type: String, forwardStatus: Int): Single<Int>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM Logs WHERE type = :type ORDER BY id DESC")
|
||||
fun pagingSource(type: String): PagingSource<Int, LogsAndRuleAndSender>
|
||||
|
||||
@Query(
|
||||
"UPDATE Logs SET forward_status=:status" +
|
||||
",forward_response=CASE WHEN (trim(forward_response) = '' or trim(forward_response) = 'ok')" +
|
||||
@ -44,4 +45,31 @@ interface LogsDao {
|
||||
" where id=:id"
|
||||
)
|
||||
fun updateStatus(id: Long, status: Int, response: String): Int
|
||||
|
||||
@Query(
|
||||
"UPDATE Logs SET forward_response=CASE WHEN (trim(forward_response) = '' or trim(forward_response) = 'ok')" +
|
||||
" THEN :response" +
|
||||
" ELSE forward_response || '\n' || :response" +
|
||||
" END" +
|
||||
" where id=:id"
|
||||
)
|
||||
fun updateResponse(id: Long, response: String): Int
|
||||
|
||||
@Query("SELECT * FROM Logs where id=:id")
|
||||
fun get(id: Long): Single<Logs>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM Logs where id=:id")
|
||||
fun getOne(id: Long): LogsAndRuleAndSender
|
||||
|
||||
@Query("SELECT count(*) FROM Logs where type=:type and forward_status=:forwardStatus")
|
||||
fun count(type: String, forwardStatus: Int): Single<Int>
|
||||
|
||||
@Transaction
|
||||
@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>
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package com.idormy.sms.forwarder.database.dao
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
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
|
||||
import io.reactivex.Single
|
||||
|
||||
@Dao
|
||||
interface MsgDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insert(msg: Msg): Long
|
||||
|
||||
@Delete
|
||||
fun delete(msg: Msg): Completable
|
||||
|
||||
@Query("DELETE FROM Msg where id=:id")
|
||||
fun delete(id: Long)
|
||||
|
||||
@RawQuery
|
||||
fun deleteAll(sql: SupportSQLiteQuery): Int
|
||||
|
||||
@Query("DELETE FROM Msg")
|
||||
fun deleteAll()
|
||||
|
||||
@Query("DELETE FROM Msg where time<:time")
|
||||
fun deleteTimeAgo(time: Long)
|
||||
|
||||
@Update
|
||||
fun update(msg: Msg): Completable
|
||||
|
||||
@Query("SELECT * FROM Msg where id=:id")
|
||||
fun get(id: Long): Single<Msg>
|
||||
|
||||
@Query("SELECT count(*) FROM Msg where type=:type")
|
||||
fun count(type: String): Single<Int>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM Msg WHERE type = :type ORDER BY id DESC")
|
||||
fun pagingSource(type: String): PagingSource<Int, MsgAndLogs>
|
||||
|
||||
@Transaction
|
||||
@RawQuery(observedEntities = [MsgAndLogs::class])
|
||||
fun pagingSource(query: SupportSQLiteQuery): PagingSource<Int, MsgAndLogs>
|
||||
|
||||
}
|
@ -1,9 +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.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.Rule
|
||||
import com.idormy.sms.forwarder.database.entity.RuleAndSender
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Single
|
||||
|
||||
@ -19,34 +25,37 @@ interface RuleDao {
|
||||
@Query("DELETE FROM Rule where id=:id")
|
||||
fun delete(id: Long)
|
||||
|
||||
@Query("DELETE FROM Rule")
|
||||
fun deleteAll()
|
||||
|
||||
@Update
|
||||
fun update(rule: Rule)
|
||||
|
||||
@Query("UPDATE Rule SET status=:status WHERE id IN (:ids)")
|
||||
fun updateStatusByIds(ids: List<Long>, status: Int)
|
||||
|
||||
@Query("SELECT * FROM Rule where id=:id")
|
||||
fun get(id: Long): Single<Rule>
|
||||
|
||||
@Query("SELECT * FROM Rule where id=:id")
|
||||
fun getOne(id: Long): Rule
|
||||
|
||||
@Query("SELECT count(*) FROM Rule where type=:type and status=:status")
|
||||
fun count(type: String, status: Int): Single<Int>
|
||||
|
||||
/*@Query(
|
||||
"SELECT Rule.*," +
|
||||
"Sender.name as sender_name,Sender.type as sender_type" +
|
||||
" FROM Rule" +
|
||||
" LEFT JOIN Sender ON Rule.sender_id = Sender.id" +
|
||||
" where Rule.type=:type" +
|
||||
" ORDER BY Rule.time DESC"
|
||||
)
|
||||
fun pagingSource(type: String): PagingSource<Int, Rule>*/
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM Rule where type=:type ORDER BY id DESC")
|
||||
fun pagingSource(type: String): PagingSource<Int, RuleAndSender>
|
||||
fun pagingSource(type: String): PagingSource<Int, Rule>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM Rule where type=:type and status=:status and (sim_slot='ALL' or sim_slot=:simSlot)")
|
||||
suspend fun getRuleAndSender(type: String, status: Int, simSlot: String): List<RuleAndSender>
|
||||
fun getRuleList(type: String, status: Int, simSlot: String): List<Rule>
|
||||
|
||||
@Transaction
|
||||
@RawQuery(observedEntities = [Rule::class])
|
||||
fun getAllRaw(query: SupportSQLiteQuery): List<Rule>
|
||||
|
||||
@Query("SELECT * FROM Rule ORDER BY id DESC")
|
||||
fun getAll(): Single<List<Rule>>
|
||||
|
||||
//TODO:允许主线程访问,后面再优化
|
||||
@Query("SELECT * FROM Rule ORDER BY id ASC")
|
||||
fun getAll(): List<Rule>
|
||||
}
|
@ -1,7 +1,14 @@
|
||||
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.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.Sender
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Single
|
||||
@ -19,12 +26,27 @@ interface SenderDao {
|
||||
@Query("DELETE FROM Sender where id=:id")
|
||||
fun delete(id: Long)
|
||||
|
||||
@Query("DELETE FROM Sender")
|
||||
fun deleteAll()
|
||||
|
||||
@Update
|
||||
fun update(sender: Sender)
|
||||
|
||||
@Query("UPDATE Sender SET status=:status WHERE id IN (:ids)")
|
||||
fun updateStatusByIds(ids: List<Long>, status: Int)
|
||||
|
||||
@Query("SELECT * FROM Sender where id=:id")
|
||||
fun get(id: Long): Single<Sender>
|
||||
|
||||
@Query("SELECT * FROM Sender where id=:id")
|
||||
fun getOne(id: Long): Sender
|
||||
|
||||
//使用 ORDER BY 子句和 instr() 函数按照列表中 ID 的顺序返回结果
|
||||
//@Query("SELECT * FROM Sender WHERE id IN (:ids) ORDER BY instr(:instr, id)")
|
||||
//fun getByIds(ids: List<Long>, instr: String): List<Sender>
|
||||
@Query("SELECT * FROM Sender WHERE id IN (:ids)")
|
||||
fun getByIds(ids: List<Long>): List<Sender>
|
||||
|
||||
@Query("SELECT count(*) FROM Sender where type=:type and status=:status")
|
||||
fun count(type: String, status: Int): Single<Int>
|
||||
|
||||
@ -34,14 +56,11 @@ interface SenderDao {
|
||||
@Query("SELECT * FROM Sender ORDER BY id DESC")
|
||||
fun getAll(): Single<List<Sender>>
|
||||
|
||||
@Transaction
|
||||
@RawQuery(observedEntities = [Sender::class])
|
||||
fun getAllRaw(query: SupportSQLiteQuery): List<Sender>
|
||||
|
||||
@Query("SELECT COUNT(id) FROM Sender WHERE status = 1")
|
||||
fun getOnCount(): Flow<Long>
|
||||
|
||||
//TODO:允许主线程访问,后面再优化
|
||||
@Query("SELECT * FROM Sender ORDER BY id ASC")
|
||||
fun getAll2(): List<Sender>
|
||||
|
||||
@Query("DELETE FROM Sender")
|
||||
fun deleteAll()
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.idormy.sms.forwarder.database.dao
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.Dao
|
||||
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.Task
|
||||
import io.reactivex.Single
|
||||
import java.util.Date
|
||||
|
||||
@Dao
|
||||
interface TaskDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(task: Task): Long
|
||||
|
||||
@Query("DELETE FROM Task WHERE id = :taskId")
|
||||
fun delete(taskId: Long)
|
||||
|
||||
@Query("DELETE FROM Task")
|
||||
fun deleteAll()
|
||||
|
||||
@Update
|
||||
fun update(task: Task)
|
||||
|
||||
@Query("UPDATE Task SET last_exec_time = :lastExecTime, next_exec_time = :nextExecTime, status = :status WHERE id = :taskId")
|
||||
fun updateExecTime(taskId: Long, lastExecTime: Date, nextExecTime: Date, status: Int)
|
||||
|
||||
@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>
|
||||
|
||||
@Query("SELECT * FROM Task where id=:id")
|
||||
suspend fun getOne(id: Long): Task?
|
||||
|
||||
@Query("SELECT * FROM Task where type < 1000 ORDER BY id DESC")
|
||||
fun pagingSourceFixed(): PagingSource<Int, Task>
|
||||
|
||||
@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>
|
||||
|
||||
@Query("SELECT * FROM Task WHERE status = 1 AND type = :taskType")
|
||||
fun getByType(taskType: Int): List<Task>
|
||||
|
||||
}
|
@ -5,34 +5,28 @@ 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
|
||||
import frpclib.Frpclib
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
|
||||
@Parcelize
|
||||
@Entity(tableName = "Frpc")
|
||||
data class Frpc(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "uid") var uid: String,
|
||||
@ColumnInfo(name = "name") var name: String,
|
||||
@ColumnInfo(name = "config") var config: String,
|
||||
@ColumnInfo(name = "uid") var uid: String = "",
|
||||
@ColumnInfo(name = "name") var name: String = "",
|
||||
@ColumnInfo(name = "config") var config: String = "",
|
||||
@ColumnInfo(name = "autorun", defaultValue = "0") var autorun: Int = 0,
|
||||
@ColumnInfo(name = "time") var time: Date = Date(),
|
||||
@Ignore var connecting: Boolean = false,
|
||||
) : Parcelable {
|
||||
constructor() : this("", "", "", 0, Date(), false)
|
||||
|
||||
@Ignore
|
||||
constructor(config: String) : this("", "", config, 0, Date(), false)
|
||||
|
||||
@Ignore
|
||||
constructor(uid: String, name: String, config: String) : this(uid, name, config, 0, Date(), false)
|
||||
|
||||
fun setConnecting(connecting: Boolean): Frpc {
|
||||
this.connecting = connecting
|
||||
return this
|
||||
}
|
||||
val imageId: Int
|
||||
get() = R.drawable.ic_menu_frpc
|
||||
|
||||
val autorunImageId: Int
|
||||
get() = when (autorun) {
|
||||
@ -40,4 +34,7 @@ data class Frpc(
|
||||
else -> R.drawable.ic_manual
|
||||
}
|
||||
|
||||
val status: Int
|
||||
get() = if (connecting || (App.FrpclibInited && Frpclib.isRunning(uid))) STATUS_ON else STATUS_OFF
|
||||
|
||||
}
|
@ -1,61 +1,55 @@
|
||||
package com.idormy.sms.forwarder.database.entity
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.*
|
||||
import com.idormy.sms.forwarder.R
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
|
||||
@Parcelize
|
||||
@Entity(
|
||||
tableName = "Logs",
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = Msg::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["msg_id"],
|
||||
onDelete = ForeignKey.CASCADE, //级联操作
|
||||
onUpdate = ForeignKey.CASCADE //级联操作
|
||||
),
|
||||
ForeignKey(
|
||||
entity = Rule::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["rule_id"],
|
||||
onDelete = ForeignKey.CASCADE, //级联操作
|
||||
onUpdate = ForeignKey.CASCADE //级联操作
|
||||
)
|
||||
),
|
||||
ForeignKey(
|
||||
entity = Sender::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["sender_id"],
|
||||
onDelete = ForeignKey.CASCADE, //级联操作
|
||||
onUpdate = ForeignKey.CASCADE //级联操作
|
||||
),
|
||||
],
|
||||
indices = [
|
||||
Index(value = ["id"], unique = true),
|
||||
Index(value = ["rule_id"])
|
||||
Index(value = ["msg_id"]),
|
||||
Index(value = ["rule_id"]),
|
||||
Index(value = ["sender_id"]),
|
||||
]
|
||||
)
|
||||
data class Logs(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = "id") var id: Long,
|
||||
@ColumnInfo(name = "type", defaultValue = "sms") var type: String,
|
||||
@ColumnInfo(name = "from", defaultValue = "") var from: String,
|
||||
@ColumnInfo(name = "content", defaultValue = "") var content: String,
|
||||
@ColumnInfo(name = "msg_id", defaultValue = "0") var msgId: Long = 0,
|
||||
@ColumnInfo(name = "rule_id", defaultValue = "0") var ruleId: Long = 0,
|
||||
@ColumnInfo(name = "sim_info", defaultValue = "") var simInfo: String = "",
|
||||
@ColumnInfo(name = "sender_id", defaultValue = "0") var senderId: Long = 0,
|
||||
@ColumnInfo(name = "forward_status", defaultValue = "1") var forwardStatus: Int = 1,
|
||||
@ColumnInfo(name = "forward_response", defaultValue = "") var forwardResponse: String = "",
|
||||
@ColumnInfo(name = "time") var time: Date = Date(),
|
||||
) : Parcelable {
|
||||
|
||||
val simImageId: Int
|
||||
get() {
|
||||
if (simInfo.isNotEmpty()) {
|
||||
if (simInfo.replace("-", "").startsWith("SIM2")) {
|
||||
return R.drawable.ic_sim2 //mipmap
|
||||
} else if (simInfo.replace("-", "").startsWith("SIM1")) {
|
||||
return R.drawable.ic_sim1
|
||||
}
|
||||
}
|
||||
return R.drawable.ic_sim
|
||||
}
|
||||
|
||||
val statusImageId: Int
|
||||
get() {
|
||||
if (forwardStatus == 1) {
|
||||
return R.drawable.ic_round_warning
|
||||
} else if (forwardStatus == 2) {
|
||||
return R.drawable.ic_round_check
|
||||
}
|
||||
return R.drawable.ic_round_cancel
|
||||
}
|
||||
|
||||
}
|
||||
) : Parcelable
|
@ -9,10 +9,24 @@ import kotlinx.parcelize.Parcelize
|
||||
data class LogsAndRuleAndSender(
|
||||
@Embedded val logs: Logs,
|
||||
|
||||
@Relation(
|
||||
entity = Msg::class,
|
||||
parentColumn = "msg_id",
|
||||
entityColumn = "id"
|
||||
)
|
||||
val msg: Msg,
|
||||
|
||||
@Relation(
|
||||
entity = Rule::class,
|
||||
parentColumn = "rule_id",
|
||||
entityColumn = "id"
|
||||
)
|
||||
val relation: RuleAndSender,
|
||||
val rule: Rule,
|
||||
|
||||
@Relation(
|
||||
entity = Sender::class,
|
||||
parentColumn = "sender_id",
|
||||
entityColumn = "id"
|
||||
)
|
||||
val sender: Sender,
|
||||
) : Parcelable
|
||||
|
@ -0,0 +1,75 @@
|
||||
package com.idormy.sms.forwarder.database.entity
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.DatabaseView
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.utils.TYPE_BARK
|
||||
import com.idormy.sms.forwarder.utils.TYPE_DINGTALK_GROUP_ROBOT
|
||||
import com.idormy.sms.forwarder.utils.TYPE_DINGTALK_INNER_ROBOT
|
||||
import com.idormy.sms.forwarder.utils.TYPE_EMAIL
|
||||
import com.idormy.sms.forwarder.utils.TYPE_FEISHU
|
||||
import com.idormy.sms.forwarder.utils.TYPE_FEISHU_APP
|
||||
import com.idormy.sms.forwarder.utils.TYPE_GOTIFY
|
||||
import com.idormy.sms.forwarder.utils.TYPE_PUSHPLUS
|
||||
import com.idormy.sms.forwarder.utils.TYPE_SERVERCHAN
|
||||
import com.idormy.sms.forwarder.utils.TYPE_SMS
|
||||
import com.idormy.sms.forwarder.utils.TYPE_SOCKET
|
||||
import com.idormy.sms.forwarder.utils.TYPE_TELEGRAM
|
||||
import com.idormy.sms.forwarder.utils.TYPE_URL_SCHEME
|
||||
import com.idormy.sms.forwarder.utils.TYPE_WEBHOOK
|
||||
import com.idormy.sms.forwarder.utils.TYPE_WEWORK_AGENT
|
||||
import com.idormy.sms.forwarder.utils.TYPE_WEWORK_ROBOT
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.Date
|
||||
|
||||
@Parcelize
|
||||
@DatabaseView("SELECT LOGS.id,LOGS.type,LOGS.msg_id,LOGS.rule_id,LOGS.sender_id,LOGS.forward_status,LOGS.forward_response,LOGS.TIME,Rule.filed AS rule_filed,Rule.`check` AS rule_check,Rule.value AS rule_value,Rule.sim_slot AS rule_sim_slot,Sender.type AS sender_type,Sender.NAME AS sender_name FROM LOGS LEFT JOIN Rule ON LOGS.rule_id = Rule.id LEFT JOIN Sender ON LOGS.sender_id = Sender.id")
|
||||
data class LogsDetail(
|
||||
@ColumnInfo(name = "id") var id: Long,
|
||||
@ColumnInfo(name = "type", defaultValue = "sms") var type: String,
|
||||
@ColumnInfo(name = "msg_id", defaultValue = "0") var msgId: Long = 0,
|
||||
@ColumnInfo(name = "rule_id", defaultValue = "0") var ruleId: Long = 0,
|
||||
@ColumnInfo(name = "sender_id", defaultValue = "0") var senderId: Long = 0,
|
||||
@ColumnInfo(name = "forward_status", defaultValue = "1") var forwardStatus: Int = 1,
|
||||
@ColumnInfo(name = "forward_response", defaultValue = "") var forwardResponse: String = "",
|
||||
@ColumnInfo(name = "time") var time: Date = Date(),
|
||||
@ColumnInfo(name = "rule_filed", defaultValue = "") var ruleFiled: String,
|
||||
@ColumnInfo(name = "rule_check", defaultValue = "") var ruleCheck: String,
|
||||
@ColumnInfo(name = "rule_value", defaultValue = "") var ruleValue: String,
|
||||
@ColumnInfo(name = "rule_sim_slot", defaultValue = "") var ruleSimSlot: String,
|
||||
@ColumnInfo(name = "sender_type", defaultValue = "1") var senderType: Int = 1,
|
||||
@ColumnInfo(name = "sender_name", defaultValue = "") var senderName: String,
|
||||
) : Parcelable {
|
||||
|
||||
val statusImageId: Int
|
||||
get() {
|
||||
if (forwardStatus == 1) {
|
||||
return R.drawable.ic_round_warning
|
||||
} else if (forwardStatus == 2) {
|
||||
return R.drawable.ic_round_check
|
||||
}
|
||||
return R.drawable.ic_round_cancel
|
||||
}
|
||||
|
||||
val senderImageId: Int
|
||||
get() = when (senderType) {
|
||||
TYPE_DINGTALK_GROUP_ROBOT -> R.drawable.icon_dingtalk
|
||||
TYPE_EMAIL -> R.drawable.icon_email
|
||||
TYPE_BARK -> R.drawable.icon_bark
|
||||
TYPE_WEBHOOK -> R.drawable.icon_webhook
|
||||
TYPE_WEWORK_ROBOT -> R.drawable.icon_wework_robot
|
||||
TYPE_WEWORK_AGENT -> R.drawable.icon_wework_agent
|
||||
TYPE_SERVERCHAN -> R.drawable.icon_serverchan
|
||||
TYPE_TELEGRAM -> R.drawable.icon_telegram
|
||||
TYPE_FEISHU -> R.drawable.icon_feishu
|
||||
TYPE_PUSHPLUS -> R.drawable.icon_pushplus
|
||||
TYPE_GOTIFY -> R.drawable.icon_gotify
|
||||
TYPE_SMS -> R.drawable.icon_sms
|
||||
TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner
|
||||
TYPE_FEISHU_APP -> R.drawable.icon_feishu_app
|
||||
TYPE_URL_SCHEME -> R.drawable.icon_url_scheme
|
||||
TYPE_SOCKET -> R.drawable.icon_socket
|
||||
else -> R.drawable.icon_sms
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package com.idormy.sms.forwarder.database.entity
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import com.idormy.sms.forwarder.R
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.Date
|
||||
|
||||
@Parcelize
|
||||
@Entity(
|
||||
tableName = "Msg",
|
||||
indices = [
|
||||
Index(value = ["id"], unique = true)
|
||||
]
|
||||
)
|
||||
data class Msg(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = "id") var id: Long,
|
||||
@ColumnInfo(name = "type", defaultValue = "sms") var type: String,
|
||||
@ColumnInfo(name = "from", defaultValue = "") var from: String,
|
||||
@ColumnInfo(name = "content", defaultValue = "") var content: String,
|
||||
@ColumnInfo(name = "sim_slot", defaultValue = "-1") var simSlot: Int = -1, //卡槽id:-1=获取失败、0=卡槽1、1=卡槽2
|
||||
@ColumnInfo(name = "sim_info", defaultValue = "") var simInfo: String = "",
|
||||
@ColumnInfo(name = "sub_id", defaultValue = "0") var subId: Int = 0,
|
||||
//通话类型:1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
|
||||
@ColumnInfo(name = "call_type", defaultValue = "0") var callType: Int = 0,
|
||||
@ColumnInfo(name = "time") var time: Date = Date(),
|
||||
) : Parcelable {
|
||||
|
||||
val simImageId: Int
|
||||
get() {
|
||||
return when {
|
||||
type == "app" -> R.drawable.ic_app
|
||||
simSlot == 0 -> R.drawable.ic_sim1
|
||||
simSlot == 1 -> R.drawable.ic_sim2
|
||||
simInfo.isNotEmpty() && simInfo.replace("-", "").startsWith("SIM2") -> R.drawable.ic_sim2
|
||||
simInfo.isNotEmpty() && simInfo.replace("-", "").startsWith("SIM1") -> R.drawable.ic_sim1
|
||||
else -> R.drawable.ic_sim
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.idormy.sms.forwarder.database.entity
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Relation
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class MsgAndLogs(
|
||||
@Embedded val msg: Msg,
|
||||
|
||||
@Relation(
|
||||
parentColumn = "id",
|
||||
entityColumn = "msg_id"
|
||||
)
|
||||
val logsList: List<LogsDetail>
|
||||
) : Parcelable
|
@ -1,12 +1,17 @@
|
||||
package com.idormy.sms.forwarder.database.entity
|
||||
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
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
|
||||
import com.idormy.sms.forwarder.utils.*
|
||||
import com.xuexiang.xui.utils.ResUtils.getString
|
||||
import com.xuexiang.xutil.resource.ResUtils.getString
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
@ -26,9 +31,11 @@ import java.util.regex.PatternSyntaxException
|
||||
],
|
||||
indices = [
|
||||
Index(value = ["id"], unique = true),
|
||||
Index(value = ["sender_id"])
|
||||
Index(value = ["sender_id"]),
|
||||
Index(value = ["sender_list"])
|
||||
]
|
||||
)
|
||||
@TypeConverters(ConvertersSenderList::class)
|
||||
data class Rule(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = "id") var id: Long,
|
||||
@ -42,31 +49,88 @@ data class Rule(
|
||||
@ColumnInfo(name = "sim_slot", defaultValue = "ALL") var simSlot: String = "",
|
||||
@ColumnInfo(name = "status", defaultValue = "1") var status: Int = 1,
|
||||
@ColumnInfo(name = "time") var time: Date = Date(),
|
||||
@ColumnInfo(name = "sender_list", defaultValue = "") var senderList: List<Sender>,
|
||||
@ColumnInfo(name = "sender_logic", defaultValue = "ALL") var senderLogic: String = "ALL",
|
||||
//免打扰(禁用转发)时间段
|
||||
@ColumnInfo(name = "silent_period_start", defaultValue = "0") var silentPeriodStart: Int = 0,
|
||||
@ColumnInfo(name = "silent_period_end", defaultValue = "0") var silentPeriodEnd: Int = 0,
|
||||
@ColumnInfo(name = "silent_day_of_week", defaultValue = "") var silentDayOfWeek: String = "",
|
||||
) : Parcelable {
|
||||
|
||||
companion object {
|
||||
val TAG: String = Rule::class.java.simpleName
|
||||
|
||||
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 (filed == null || filed == FILED_TRANSPOND_ALL) {
|
||||
sb.append(getString(R.string.rule_all_fw_to))
|
||||
} else {
|
||||
sb.append(getString(R.string.rule_when)).append(FILED_MAP[filed]).append(CHECK_MAP[check]).append(value).append(getString(R.string.rule_fw_to))
|
||||
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))
|
||||
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))
|
||||
|
||||
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 ruleMatch: String
|
||||
val description: String
|
||||
get() {
|
||||
val simStr = if ("app" == type) "" else SIM_SLOT_MAP[simSlot].toString() + getString(R.string.rule_card)
|
||||
return if (filed == FILED_TRANSPOND_ALL) {
|
||||
simStr + getString(R.string.rule_all_fw_to)
|
||||
val blank = if (App.isNeedSpaceBetweenWords) " " else ""
|
||||
val card = SIM_SLOT_MAP[simSlot].toString() + blank + getString(R.string.rule_card) + blank
|
||||
val sb = StringBuilder()
|
||||
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)
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
fun getName(appendSenderList: Boolean = true): String {
|
||||
return if (appendSenderList) {
|
||||
getRuleMatch(type, filed, check, value, simSlot, senderList)
|
||||
} else {
|
||||
simStr + getString(R.string.rule_when) + FILED_MAP[filed] + CHECK_MAP[check] + value + getString(R.string.rule_fw_to)
|
||||
getRuleMatch(type, filed, check, value, simSlot, null)
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,8 +147,16 @@ data class Rule(
|
||||
|
||||
val statusImageId: Int
|
||||
get() = when (status) {
|
||||
STATUS_OFF -> R.drawable.icon_off
|
||||
else -> R.drawable.icon_on
|
||||
STATUS_OFF -> R.drawable.ic_stop
|
||||
else -> R.drawable.ic_start
|
||||
}
|
||||
|
||||
fun getSenderLogicCheckId(): Int {
|
||||
return when (senderLogic) {
|
||||
SENDER_LOGIC_UNTIL_FAIL -> R.id.rb_sender_logic_until_fail
|
||||
SENDER_LOGIC_UNTIL_SUCCESS -> R.id.rb_sender_logic_until_success
|
||||
else -> R.id.rb_sender_logic_all
|
||||
}
|
||||
}
|
||||
|
||||
fun getSimSlotCheckId(): Int {
|
||||
@ -99,7 +171,9 @@ data class Rule(
|
||||
return when (filed) {
|
||||
FILED_MSG_CONTENT -> R.id.rb_content
|
||||
FILED_PHONE_NUM -> R.id.rb_phone
|
||||
FILED_CALL_TYPE -> R.id.rb_call_type
|
||||
FILED_PACKAGE_NAME -> R.id.rb_package_name
|
||||
FILED_UID -> R.id.rb_uid
|
||||
FILED_INFORM_CONTENT -> R.id.rb_inform_content
|
||||
FILED_MULTI_MATCH -> R.id.rb_multi_match
|
||||
else -> R.id.rb_transpond_all
|
||||
@ -128,6 +202,8 @@ data class Rule(
|
||||
when (this.filed) {
|
||||
FILED_TRANSPOND_ALL -> mixChecked = true
|
||||
FILED_PHONE_NUM, FILED_PACKAGE_NAME -> mixChecked = checkValue(msg.from)
|
||||
FILED_UID -> mixChecked = checkValue(msg.uid.toString())
|
||||
FILED_CALL_TYPE -> mixChecked = checkValue(msg.callType.toString())
|
||||
FILED_MSG_CONTENT, FILED_INFORM_CONTENT -> mixChecked = checkValue(msg.content)
|
||||
FILED_MULTI_MATCH -> mixChecked = RuleLineUtils.checkRuleLines(msg, this.value)
|
||||
else -> {}
|
||||
@ -139,42 +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)
|
||||
}
|
||||
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)
|
||||
if (msgValue == null) return false
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
}
|
@ -5,9 +5,25 @@ import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.utils.*
|
||||
import com.idormy.sms.forwarder.utils.STATUS_OFF
|
||||
import com.idormy.sms.forwarder.utils.TYPE_BARK
|
||||
import com.idormy.sms.forwarder.utils.TYPE_DINGTALK_GROUP_ROBOT
|
||||
import com.idormy.sms.forwarder.utils.TYPE_DINGTALK_INNER_ROBOT
|
||||
import com.idormy.sms.forwarder.utils.TYPE_EMAIL
|
||||
import com.idormy.sms.forwarder.utils.TYPE_FEISHU
|
||||
import com.idormy.sms.forwarder.utils.TYPE_FEISHU_APP
|
||||
import com.idormy.sms.forwarder.utils.TYPE_GOTIFY
|
||||
import com.idormy.sms.forwarder.utils.TYPE_PUSHPLUS
|
||||
import com.idormy.sms.forwarder.utils.TYPE_SERVERCHAN
|
||||
import com.idormy.sms.forwarder.utils.TYPE_SMS
|
||||
import com.idormy.sms.forwarder.utils.TYPE_SOCKET
|
||||
import com.idormy.sms.forwarder.utils.TYPE_TELEGRAM
|
||||
import com.idormy.sms.forwarder.utils.TYPE_URL_SCHEME
|
||||
import com.idormy.sms.forwarder.utils.TYPE_WEBHOOK
|
||||
import com.idormy.sms.forwarder.utils.TYPE_WEWORK_AGENT
|
||||
import com.idormy.sms.forwarder.utils.TYPE_WEWORK_ROBOT
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
|
||||
@Parcelize
|
||||
@Entity(tableName = "Sender")
|
||||
@ -20,26 +36,6 @@ data class Sender(
|
||||
@ColumnInfo(name = "status", defaultValue = "1") var status: Int = 1,
|
||||
@ColumnInfo(name = "time") var time: Date = Date(),
|
||||
) : Parcelable {
|
||||
companion object {
|
||||
|
||||
fun getImageId(type: Int): Int = when (type) {
|
||||
TYPE_DINGTALK_GROUP_ROBOT -> R.drawable.icon_dingtalk
|
||||
TYPE_EMAIL -> R.drawable.icon_email
|
||||
TYPE_BARK -> R.drawable.icon_bark
|
||||
TYPE_WEBHOOK -> R.drawable.icon_webhook
|
||||
TYPE_WEWORK_ROBOT -> R.drawable.icon_wework_robot
|
||||
TYPE_WEWORK_AGENT -> R.drawable.icon_wework_agent
|
||||
TYPE_SERVERCHAN -> R.drawable.icon_serverchan
|
||||
TYPE_TELEGRAM -> R.drawable.icon_telegram
|
||||
TYPE_FEISHU -> R.drawable.icon_feishu
|
||||
TYPE_PUSHPLUS -> R.drawable.icon_pushplus
|
||||
TYPE_GOTIFY -> R.drawable.icon_gotify
|
||||
TYPE_SMS -> R.drawable.icon_sms
|
||||
TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner
|
||||
else -> R.drawable.icon_sms
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val imageId: Int
|
||||
get() = when (type) {
|
||||
@ -56,13 +52,16 @@ data class Sender(
|
||||
TYPE_GOTIFY -> R.drawable.icon_gotify
|
||||
TYPE_SMS -> R.drawable.icon_sms
|
||||
TYPE_DINGTALK_INNER_ROBOT -> R.drawable.icon_dingtalk_inner
|
||||
TYPE_FEISHU_APP -> R.drawable.icon_feishu_app
|
||||
TYPE_URL_SCHEME -> R.drawable.icon_url_scheme
|
||||
TYPE_SOCKET -> R.drawable.icon_socket
|
||||
else -> R.drawable.icon_sms
|
||||
}
|
||||
|
||||
val statusImageId: Int
|
||||
get() = when (status) {
|
||||
STATUS_OFF -> R.drawable.icon_off
|
||||
else -> R.drawable.icon_on
|
||||
STATUS_OFF -> R.drawable.ic_stop
|
||||
else -> R.drawable.ic_start
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.idormy.sms.forwarder.database.entity
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.utils.STATUS_OFF
|
||||
import com.idormy.sms.forwarder.utils.task.TaskUtils
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.Date
|
||||
|
||||
@Parcelize
|
||||
@Entity(tableName = "Task")
|
||||
data class Task(
|
||||
@PrimaryKey(autoGenerate = true) var id: Long = 0,
|
||||
@ColumnInfo(name = "type", defaultValue = "1") var type: Int = 1, // 任务类型:<1000为任务模板,>=1000为自定义任务
|
||||
@ColumnInfo(name = "name", defaultValue = "") val name: String = "", // 任务名称
|
||||
@ColumnInfo(name = "description", defaultValue = "") val description: String = "", // 任务描述
|
||||
@ColumnInfo(name = "conditions", defaultValue = "") val conditions: String = "", // 触发条件
|
||||
@ColumnInfo(name = "actions", defaultValue = "") val actions: String = "", // 执行动作
|
||||
@ColumnInfo(name = "status", defaultValue = "1") var status: Int = 1, // 任务状态
|
||||
@ColumnInfo(name = "last_exec_time") var lastExecTime: Date = Date(), // 上次执行时间
|
||||
@ColumnInfo(name = "next_exec_time") var nextExecTime: Date = Date(), // 下次执行时间
|
||||
) : Parcelable {
|
||||
|
||||
val imageId: Int
|
||||
get() = TaskUtils.getTypeImageId(type)
|
||||
|
||||
val greyImageId: Int
|
||||
get() = TaskUtils.getTypeGreyImageId(type)
|
||||
|
||||
val statusImageId: Int
|
||||
get() = when (status) {
|
||||
STATUS_OFF -> R.drawable.ic_stop
|
||||
else -> R.drawable.ic_start
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
package com.idormy.sms.forwarder.database.ext
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
|
||||
@Suppress("unused")
|
||||
class Converters {
|
||||
class ConvertersDate {
|
||||
@TypeConverter
|
||||
fun fromTimestamp(value: Long?): Date? {
|
||||
return value?.let { Date(it) }
|
@ -0,0 +1,18 @@
|
||||
package com.idormy.sms.forwarder.database.ext
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import com.idormy.sms.forwarder.core.Core
|
||||
import com.idormy.sms.forwarder.database.entity.Sender
|
||||
|
||||
class ConvertersSenderList {
|
||||
|
||||
@TypeConverter
|
||||
fun stringToObject(value: String): List<Sender> {
|
||||
return Core.sender.getByIds(value.split(",").map { it.trim().toLong() }, value)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun objectToString(list: List<Sender>): String {
|
||||
return list.joinToString(",") { it.id.toString() }
|
||||
}
|
||||
}
|
@ -4,6 +4,5 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
|
||||
@Suppress("unused")
|
||||
fun <T> LifecycleOwner.observe(liveData: LiveData<T>?, observer: (T) -> Unit) =
|
||||
liveData?.observe(this, Observer(observer))
|
@ -1,36 +1,40 @@
|
||||
package com.idormy.sms.forwarder.database.repository
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||
import com.idormy.sms.forwarder.database.dao.FrpcDao
|
||||
import com.idormy.sms.forwarder.database.entity.Frpc
|
||||
import io.reactivex.Single
|
||||
|
||||
class FrpcRepository(
|
||||
private val frpcDao: FrpcDao,
|
||||
) {
|
||||
|
||||
var listener: Listener? = null
|
||||
class FrpcRepository(private val frpcDao: FrpcDao) {
|
||||
|
||||
@WorkerThread
|
||||
fun insert(frpc: Frpc) {
|
||||
frpcDao.insert(frpc)
|
||||
}
|
||||
fun insert(frpc: Frpc) = frpcDao.insert(frpc)
|
||||
|
||||
@WorkerThread
|
||||
fun delete(uid: String) {
|
||||
frpcDao.delete(uid)
|
||||
}
|
||||
fun delete(uid: String) = frpcDao.delete(uid)
|
||||
|
||||
@WorkerThread
|
||||
fun get(uid: String) = frpcDao.get(uid)
|
||||
fun deleteAll() = frpcDao.deleteAll()
|
||||
|
||||
@WorkerThread
|
||||
fun update(frpc: Frpc) = frpcDao.update(frpc)
|
||||
|
||||
//TODO:允许主线程访问,后面再优化
|
||||
val all: List<Frpc> = frpcDao.getAll()
|
||||
@WorkerThread
|
||||
fun get(uid: String) = frpcDao.get(uid)
|
||||
|
||||
fun deleteAll() {
|
||||
frpcDao.deleteAll()
|
||||
fun getAllNonCache(): List<Frpc> {
|
||||
val query = SimpleSQLiteQuery("SELECT * FROM Frpc ORDER BY time DESC")
|
||||
return frpcDao.getAllRaw(query)
|
||||
}
|
||||
|
||||
fun getAll(): Single<List<Frpc>> = frpcDao.getAll()
|
||||
|
||||
fun getAutorun(): List<Frpc> = frpcDao.getAutorun()
|
||||
|
||||
fun getByUids(uids: List<String>, instr: String): List<Frpc> {
|
||||
val frpcs = frpcDao.getByUids(uids)
|
||||
// 将结果按照 instr() 的顺序进行排序
|
||||
return frpcs.sortedBy { instr.indexOf(it.uid) }
|
||||
}
|
||||
|
||||
}
|
@ -1,19 +1,42 @@
|
||||
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
|
||||
|
||||
class LogsRepository(private val logsDao: LogsDao) {
|
||||
|
||||
@WorkerThread
|
||||
fun delete(id: Long) {
|
||||
logsDao.delete(id)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
suspend fun insert(logs: Logs): Long = logsDao.insert(logs)
|
||||
|
||||
@WorkerThread
|
||||
fun delete(id: Long) = logsDao.delete(id)
|
||||
|
||||
fun deleteAll() = logsDao.deleteAll()
|
||||
|
||||
@WorkerThread
|
||||
fun updateStatus(id: Long, status: Int, response: String): Int = logsDao.updateStatus(id, status, response)
|
||||
|
||||
@WorkerThread
|
||||
fun updateResponse(id: Long, response: String): Int = logsDao.updateResponse(id, response)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.idormy.sms.forwarder.database.repository
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import com.idormy.sms.forwarder.database.dao.MsgDao
|
||||
import com.idormy.sms.forwarder.database.entity.Msg
|
||||
|
||||
class MsgRepository(private val msgDao: MsgDao) {
|
||||
|
||||
@WorkerThread
|
||||
suspend fun insert(msg: Msg): Long = msgDao.insert(msg)
|
||||
|
||||
@WorkerThread
|
||||
fun delete(id: Long) = msgDao.delete(id)
|
||||
|
||||
fun deleteAll() = msgDao.deleteAll()
|
||||
|
||||
@WorkerThread
|
||||
fun deleteTimeAgo(time: Long) = msgDao.deleteTimeAgo(time)
|
||||
|
||||
}
|
@ -1,19 +1,17 @@
|
||||
package com.idormy.sms.forwarder.database.repository
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||
import com.idormy.sms.forwarder.database.dao.RuleDao
|
||||
import com.idormy.sms.forwarder.database.entity.Rule
|
||||
import io.reactivex.Single
|
||||
|
||||
class RuleRepository(
|
||||
private val ruleDao: RuleDao,
|
||||
) {
|
||||
class RuleRepository(private val ruleDao: RuleDao) {
|
||||
|
||||
var listener: Listener? = null
|
||||
private var listener: Listener? = null
|
||||
|
||||
@WorkerThread
|
||||
fun insert(rule: Rule) {
|
||||
ruleDao.insert(rule)
|
||||
}
|
||||
fun insert(rule: Rule) = ruleDao.insert(rule)
|
||||
|
||||
@WorkerThread
|
||||
fun delete(id: Long) {
|
||||
@ -21,14 +19,26 @@ class RuleRepository(
|
||||
ruleDao.delete(id)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun get(id: Long) = ruleDao.get(id)
|
||||
|
||||
suspend fun getRuleAndSender(type: String, status: Int, simSlot: String) = ruleDao.getRuleAndSender(type, status, simSlot)
|
||||
fun deleteAll() = ruleDao.deleteAll()
|
||||
|
||||
@WorkerThread
|
||||
fun update(rule: Rule) = ruleDao.update(rule)
|
||||
|
||||
//TODO:允许主线程访问,后面再优化
|
||||
val all: List<Rule> = ruleDao.getAll()
|
||||
fun updateStatusByIds(ids: List<Long>, status: Int) = ruleDao.updateStatusByIds(ids, status)
|
||||
|
||||
@WorkerThread
|
||||
fun get(id: Long) = ruleDao.get(id)
|
||||
|
||||
@WorkerThread
|
||||
fun getOne(id: Long) = ruleDao.getOne(id)
|
||||
|
||||
fun getAll(): Single<List<Rule>> = ruleDao.getAll()
|
||||
|
||||
fun getAllNonCache(): List<Rule> {
|
||||
val query = SimpleSQLiteQuery("SELECT * FROM Rule ORDER BY id ASC")
|
||||
return ruleDao.getAllRaw(query)
|
||||
}
|
||||
|
||||
fun getRuleList(type: String, status: Int, simSlot: String) = ruleDao.getRuleList(type, status, simSlot)
|
||||
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
package com.idormy.sms.forwarder.database.repository
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||
import com.idormy.sms.forwarder.database.dao.SenderDao
|
||||
import com.idormy.sms.forwarder.database.entity.Sender
|
||||
import io.reactivex.Single
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class SenderRepository(private val senderDao: SenderDao) {
|
||||
|
||||
var listener: Listener? = null
|
||||
private var listener: Listener? = null
|
||||
|
||||
@WorkerThread
|
||||
fun insert(sender: Sender) = senderDao.insert(sender)
|
||||
@ -18,17 +20,29 @@ class SenderRepository(private val senderDao: SenderDao) {
|
||||
senderDao.delete(id)
|
||||
}
|
||||
|
||||
fun get(id: Long) = senderDao.get(id)
|
||||
fun deleteAll() = senderDao.deleteAll()
|
||||
|
||||
fun update(sender: Sender) = senderDao.update(sender)
|
||||
|
||||
fun updateStatusByIds(ids: List<Long>, status: Int) = senderDao.updateStatusByIds(ids, status)
|
||||
|
||||
fun get(id: Long) = senderDao.get(id)
|
||||
|
||||
fun getOne(id: Long) = senderDao.getOne(id)
|
||||
|
||||
fun getByIds(ids: List<Long>, instr: String): List<Sender> {
|
||||
val senders = senderDao.getByIds(ids)
|
||||
// 将结果按照 instr() 的顺序进行排序
|
||||
return senders.sortedBy { instr.indexOf(it.id.toString()) }
|
||||
}
|
||||
|
||||
fun getAllNonCache(): List<Sender> {
|
||||
val query = SimpleSQLiteQuery("SELECT * FROM Sender ORDER BY id ASC")
|
||||
return senderDao.getAllRaw(query)
|
||||
}
|
||||
|
||||
fun getAll(): Single<List<Sender>> = senderDao.getAll()
|
||||
|
||||
val count: Flow<Long> = senderDao.getOnCount()
|
||||
|
||||
//TODO:允许主线程访问,后面再优化
|
||||
val all: List<Sender> = senderDao.getAll2()
|
||||
|
||||
fun deleteAll() {
|
||||
senderDao.deleteAll()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.idormy.sms.forwarder.database.repository
|
||||
|
||||
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) {
|
||||
|
||||
@WorkerThread
|
||||
fun insert(task: Task): Long = taskDao.insert(task)
|
||||
|
||||
@WorkerThread
|
||||
fun delete(id: Long) = taskDao.delete(id)
|
||||
|
||||
fun deleteAll() = taskDao.deleteAll()
|
||||
|
||||
fun update(task: Task) = taskDao.update(task)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fun getByType(type: Int): List<Task> = taskDao.getByType(type)
|
||||
|
||||
}
|
@ -17,21 +17,36 @@ class BaseViewModelFactory(private val context: Context?) : ViewModelProvider.Fa
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return FrpcViewModel(frpcDao) as T
|
||||
}
|
||||
|
||||
modelClass.isAssignableFrom(MsgViewModel::class.java) -> {
|
||||
val msgDao = AppDatabase.getInstance(context).msgDao()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return MsgViewModel(msgDao) as T
|
||||
}
|
||||
|
||||
modelClass.isAssignableFrom(LogsViewModel::class.java) -> {
|
||||
val logDao = AppDatabase.getInstance(context).logsDao()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return LogsViewModel(logDao) as T
|
||||
}
|
||||
|
||||
modelClass.isAssignableFrom(RuleViewModel::class.java) -> {
|
||||
val ruleDao = AppDatabase.getInstance(context).ruleDao()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return RuleViewModel(ruleDao) as T
|
||||
}
|
||||
|
||||
modelClass.isAssignableFrom(SenderViewModel::class.java) -> {
|
||||
val senderDao = AppDatabase.getInstance(context).senderDao()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return SenderViewModel(senderDao) as T
|
||||
}
|
||||
|
||||
modelClass.isAssignableFrom(TaskViewModel::class.java) -> {
|
||||
val taskDao = AppDatabase.getInstance(context).taskDao()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return TaskViewModel(taskDao) as T
|
||||
}
|
||||
}
|
||||
|
||||
throw IllegalArgumentException("Unknown ViewModel class")
|
||||
|
@ -1,20 +1,13 @@
|
||||
package com.idormy.sms.forwarder.database.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import com.idormy.sms.forwarder.database.dao.LogsDao
|
||||
import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender
|
||||
import com.idormy.sms.forwarder.database.ext.ioThread
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class LogsViewModel(private val dao: LogsDao) : ViewModel() {
|
||||
private var type: String = "sms"
|
||||
//private var type: String = "sms"
|
||||
|
||||
fun setType(type: String): LogsViewModel {
|
||||
/*fun setType(type: String): LogsViewModel {
|
||||
this.type = type
|
||||
return this
|
||||
}
|
||||
@ -27,7 +20,7 @@ class LogsViewModel(private val dao: LogsDao) : ViewModel() {
|
||||
)
|
||||
) {
|
||||
dao.pagingSource(type)
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
}.flow.cachedIn(viewModelScope)*/
|
||||
|
||||
fun delete(id: Long) = ioThread {
|
||||
dao.delete(id)
|
||||
|
@ -0,0 +1,101 @@
|
||||
package com.idormy.sms.forwarder.database.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import 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,
|
||||
enablePlaceholders = false,
|
||||
initialLoadSize = 10
|
||||
)
|
||||
) {
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
@ -8,7 +8,6 @@ import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import com.idormy.sms.forwarder.database.dao.RuleDao
|
||||
import com.idormy.sms.forwarder.database.entity.Rule
|
||||
import com.idormy.sms.forwarder.database.entity.RuleAndSender
|
||||
import com.idormy.sms.forwarder.database.ext.ioThread
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@ -20,7 +19,7 @@ class RuleViewModel(private val dao: RuleDao) : ViewModel() {
|
||||
return this
|
||||
}
|
||||
|
||||
val allRules: Flow<PagingData<RuleAndSender>> = Pager(
|
||||
val allRules: Flow<PagingData<Rule>> = Pager(
|
||||
config = PagingConfig(
|
||||
pageSize = 10,
|
||||
enablePlaceholders = false,
|
||||
|
@ -0,0 +1,45 @@
|
||||
package com.idormy.sms.forwarder.database.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import com.idormy.sms.forwarder.database.dao.TaskDao
|
||||
import com.idormy.sms.forwarder.database.entity.Task
|
||||
import com.idormy.sms.forwarder.database.ext.ioThread
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class TaskViewModel(private val dao: TaskDao) : ViewModel() {
|
||||
private var type: String = "mine"
|
||||
|
||||
fun setType(type: String): TaskViewModel {
|
||||
this.type = type
|
||||
return this
|
||||
}
|
||||
|
||||
val allTasks: Flow<PagingData<Task>> = Pager(
|
||||
config = PagingConfig(
|
||||
pageSize = 10,
|
||||
enablePlaceholders = false,
|
||||
initialLoadSize = 10
|
||||
)
|
||||
) {
|
||||
//TODO:根据条件查询,暂不使用
|
||||
//dao.pagingSource(type)
|
||||
if (type == "mine") dao.pagingSourceMine() else dao.pagingSourceFixed()
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
|
||||
fun insertOrUpdate(task: Task) = ioThread {
|
||||
if (task.id > 0) dao.update(task) else dao.insert(task)
|
||||
}
|
||||
|
||||
fun delete(id: Long) = ioThread {
|
||||
dao.delete(id)
|
||||
}
|
||||
|
||||
fun updateStatus(id: Long, status: Int) = ioThread {
|
||||
dao.updateStatus(id, status)
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package com.idormy.sms.forwarder.entity
|
||||
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.xuexiang.xui.utils.ResUtils
|
||||
import com.xuexiang.xutil.resource.ResUtils.getString
|
||||
import java.io.Serializable
|
||||
|
||||
data class BatteryInfo(
|
||||
@ -15,13 +15,13 @@ data class BatteryInfo(
|
||||
) : Serializable {
|
||||
override fun toString(): String {
|
||||
var msg = ""
|
||||
msg += "\n" + String.format(ResUtils.getString(R.string.battery_level), level)
|
||||
if (scale != "") msg += "\n" + String.format(ResUtils.getString(R.string.battery_scale), scale)
|
||||
if (voltage != "") msg += "\n" + String.format(ResUtils.getString(R.string.battery_voltage), voltage)
|
||||
if (temperature != "") msg += "\n" + String.format(ResUtils.getString(R.string.battery_temperature), temperature)
|
||||
msg += "\n" + String.format(ResUtils.getString(R.string.battery_status), status)
|
||||
msg += "\n" + String.format(ResUtils.getString(R.string.battery_health), health)
|
||||
msg += "\n" + String.format(ResUtils.getString(R.string.battery_plugged), plugged)
|
||||
msg += "\n" + String.format(getString(R.string.battery_level), level)
|
||||
if (scale != "") msg += "\n" + String.format(getString(R.string.battery_scale), scale)
|
||||
if (voltage != "") msg += "\n" + String.format(getString(R.string.battery_voltage), voltage)
|
||||
if (temperature != "") msg += "\n" + String.format(getString(R.string.battery_temperature), temperature)
|
||||
msg += "\n" + String.format(getString(R.string.battery_status), status)
|
||||
msg += "\n" + String.format(getString(R.string.battery_health), health)
|
||||
msg += "\n" + String.format(getString(R.string.battery_plugged), plugged)
|
||||
return msg
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ data class CallInfo(
|
||||
var dateLong: Long = 0L,
|
||||
//获取通话时长,值为多少秒
|
||||
var duration: Int = 0,
|
||||
//通话类型:1=呼入, 2=呼出, 3=未接
|
||||
//通话类型:1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
|
||||
var type: Int = 1,
|
||||
//被呼号码
|
||||
@SerializedName("via_number")
|
||||
@ -21,6 +21,9 @@ data class CallInfo(
|
||||
//卡槽ID: 0=Sim1, 1=Sim2, -1=获取失败
|
||||
@SerializedName("sim_id")
|
||||
var simId: Int = -1,
|
||||
//卡槽主键
|
||||
@SerializedName("sub_id")
|
||||
var subId: Int = 0,
|
||||
) : Serializable {
|
||||
|
||||
val typeImageId: Int
|
||||
|
@ -4,79 +4,28 @@ import com.google.gson.annotations.SerializedName
|
||||
import com.idormy.sms.forwarder.database.entity.Frpc
|
||||
import com.idormy.sms.forwarder.database.entity.Rule
|
||||
import com.idormy.sms.forwarder.database.entity.Sender
|
||||
import com.idormy.sms.forwarder.database.entity.Task
|
||||
import java.io.Serializable
|
||||
|
||||
data class CloneInfo(
|
||||
@SerializedName("version_code")
|
||||
var versionCode: Int = 0,
|
||||
|
||||
@SerializedName("version_name")
|
||||
var versionName: String? = null,
|
||||
@SerializedName("enable_sms")
|
||||
var enableSms: Boolean = false,
|
||||
@SerializedName("enable_phone")
|
||||
var enablePhone: Boolean = false,
|
||||
@SerializedName("call_type1")
|
||||
var callType1: Boolean = false,
|
||||
@SerializedName("call_type2")
|
||||
var callType2: Boolean = false,
|
||||
@SerializedName("call_type3")
|
||||
var callType3: Boolean = false,
|
||||
@SerializedName("enable_app_notify")
|
||||
var enableAppNotify: Boolean = false,
|
||||
@SerializedName("cancel_app_notify")
|
||||
var cancelAppNotify: Boolean = false,
|
||||
@SerializedName("enable_not_user_present")
|
||||
var enableNotUserPresent: Boolean = false,
|
||||
@SerializedName("enable_load_app_list")
|
||||
var enableLoadAppList: Boolean = false,
|
||||
@SerializedName("enable_load_user_app_list")
|
||||
var enableLoadUserAppList: Boolean = false,
|
||||
@SerializedName("enable_load_system_app_list")
|
||||
var enableLoadSystemAppList: Boolean = false,
|
||||
@SerializedName("duplicate_messages_limits")
|
||||
var duplicateMessagesLimits: Int = 0,
|
||||
@SerializedName("enable_battery_receiver")
|
||||
var enableBatteryReceiver: Boolean = false,
|
||||
@SerializedName("battery_level_min")
|
||||
var batteryLevelMin: Int = 0,
|
||||
@SerializedName("battery_level_max")
|
||||
var batteryLevelMax: Int = 0,
|
||||
@SerializedName("battery_level_once")
|
||||
var batteryLevelOnce: Boolean = false,
|
||||
@SerializedName("enable_battery_cron")
|
||||
var enableBatteryCron: Boolean = false,
|
||||
@SerializedName("battery_cron_start_time")
|
||||
var batteryCronStartTime: String? = null,
|
||||
@SerializedName("battery_cron_interval")
|
||||
var batteryCronInterval: Int = 0,
|
||||
@SerializedName("enable_exclude_from_recents")
|
||||
var enableExcludeFromRecents: Boolean = false,
|
||||
@SerializedName("enable_cactus")
|
||||
var enableCactus: Boolean = false,
|
||||
@SerializedName("enable_play_silence_music")
|
||||
var enablePlaySilenceMusic: Boolean = false,
|
||||
@SerializedName("enable_one_pixel_activity")
|
||||
var enableOnePixelActivity: Boolean = false,
|
||||
@SerializedName("request_retry_times")
|
||||
var requestRetryTimes: Int = 0,
|
||||
@SerializedName("request_delay_time")
|
||||
var requestDelayTime: Int = 0,
|
||||
@SerializedName("request_timeout")
|
||||
var requestTimeout: Int = 0,
|
||||
@SerializedName("notify_content")
|
||||
var notifyContent: String? = null,
|
||||
@SerializedName("enable_sms_template")
|
||||
var enableSmsTemplate: Boolean = false,
|
||||
@SerializedName("sms_template")
|
||||
var smsTemplate: String? = null,
|
||||
@SerializedName("enable_help_tip")
|
||||
var enableHelpTip: Boolean = false,
|
||||
@SerializedName("enable_pure_client_mode")
|
||||
var enablePureClientMode: Boolean = false,
|
||||
|
||||
@SerializedName("settings")
|
||||
var settings: String = "",
|
||||
|
||||
@SerializedName("sender_list")
|
||||
var senderList: List<Sender>? = null,
|
||||
|
||||
@SerializedName("rule_list")
|
||||
var ruleList: List<Rule>? = null,
|
||||
|
||||
@SerializedName("frpc_list")
|
||||
var frpcList: List<Frpc>? = null,
|
||||
|
||||
@SerializedName("task_list")
|
||||
var taskList: List<Task>? = null,
|
||||
) : Serializable
|
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