Compare commits
478 Commits
Author | SHA1 | Date | |
---|---|---|---|
7bbe321d3c | |||
9db49a4164 | |||
1d2f4f70c8 | |||
d1eecad059 | |||
d5e9c6ecc3 | |||
b91ddc172d | |||
5417796f3f | |||
78c895547e | |||
1f0f280286 | |||
b280465914 | |||
967ae915b2 | |||
24cd1692c6 | |||
63bc71c405 | |||
a43a3b441f | |||
ab4b40ab75 | |||
e9c5c5fb99 | |||
b989a8359e | |||
6ec1d80f49 | |||
c1e465020f | |||
b687464d59 | |||
3239a075fb | |||
8c0f0e3e8f | |||
aac34ef738 | |||
e96be86314 | |||
95353735b0 | |||
c0b8a8a3f4 | |||
301240b896 | |||
86b8c47ed5 | |||
bc99f46720 | |||
9c53cc52e9 | |||
e6709cd4af | |||
cdb92757aa | |||
3c5da802a7 | |||
05e2fcf185 | |||
8ebd95a4b8 | |||
80d4e18a23 | |||
cc672d2f20 | |||
1cf14e65a9 | |||
9b2e429d3d | |||
2f73a21a41 | |||
05eb37bc80 | |||
779c86d8ac | |||
d9a14c9973 | |||
7546c2a1a4 | |||
d21d775764 | |||
352f1ca0d1 | |||
584a9e983f | |||
5dd94eca38 | |||
d8c06e3de2 | |||
3adeadf6fb | |||
9d35916280 | |||
4c7632a144 | |||
e329e13a02 | |||
7ddaf5d9aa | |||
5da4c3d3b9 | |||
fb1560a1f5 | |||
61d62f736c | |||
77362f1651 | |||
fbb9cf68ab | |||
ce641a3ffe | |||
527a659a1b | |||
6d432b9de4 | |||
23a9506dda | |||
be05bfadec | |||
1971b07d39 | |||
ca05d6e3cc | |||
c204dc5a8b | |||
55d0dea835 | |||
e93d125431 | |||
74a935f6f1 | |||
be7035978b | |||
778451fa4c | |||
89d1e0f641 | |||
1f9b65652d | |||
2b23404461 | |||
e2dbe8866d | |||
7dad61dbbb | |||
9542405512 | |||
59d28d3c49 | |||
600f3365f6 | |||
5b8a1e86e0 | |||
c8bc7665f7 | |||
0bbe5fc7fa | |||
e9ea573e33 | |||
e6fbf85e6e | |||
b68f3bdb23 | |||
63c25317eb | |||
b0a6736f70 | |||
c68a53e4cd | |||
c4773d7b97 | |||
eea219a205 | |||
35ab9a26c0 | |||
9c366399df | |||
8126327b95 | |||
beb1005710 | |||
431d91a380 | |||
35771c11e7 | |||
2ccdb8b238 | |||
dac268f40b | |||
80b1351bd2 | |||
64709920f7 | |||
b00ba83370 | |||
243a274480 | |||
95d51c82e9 | |||
549311535c | |||
8e0c8e825b | |||
3bae320e07 | |||
ac299ec7ba | |||
ec506a7f9e | |||
e397f009b9 | |||
0560330f68 | |||
a078ecaf7e | |||
d662a6c426 | |||
16d97ad08f | |||
189c3ab273 | |||
0b7fb533da | |||
4c02afb957 | |||
7553e1f51e | |||
d5d89ee93a | |||
dfcee5dfc7 | |||
0ad82bb248 | |||
38e1c1133d | |||
97a834ae6a | |||
6b7bf600f8 | |||
f9dbbf4638 | |||
dae5182f90 | |||
51e89ce901 | |||
72be45feb4 | |||
7e347a70c2 | |||
227d7871c2 | |||
f5813a5489 | |||
34925b6678 | |||
20358a700a | |||
373683bbf0 | |||
77cfe75043 | |||
56d49d8c5e | |||
1286a5d848 | |||
463e7ee287 | |||
46897cc51b | |||
306d9a006a | |||
7e798eb606 | |||
0bc064b4ba | |||
35a97be4c4 | |||
9f3e183d72 | |||
51f5f1005a | |||
0a380f861e | |||
b712ad3488 | |||
9f656bdefe | |||
a4a651229c | |||
4773800f23 | |||
bef0b8189e | |||
674714f0f3 | |||
ee9e858584 | |||
165c6d8614 | |||
8a6719fc19 | |||
308361a834 | |||
44df964f6f | |||
56bf266919 | |||
f3658d6636 | |||
1bb37eec30 | |||
59511b2345 | |||
5b7bc02c61 | |||
b329f537e7 | |||
5879e81759 | |||
f4e88bef77 | |||
b3ec45309c | |||
2fbc892898 | |||
c46190c3fc | |||
860e540de1 | |||
8cde286cac | |||
90830fe384 | |||
686f89f75d | |||
4210af5680 | |||
aefc368cfd | |||
67218d8045 | |||
c05deb3a41 | |||
43d0107fb5 | |||
ece7612f9d | |||
a9809d90cb | |||
bbc9a79996 | |||
b71f1885ec | |||
885aad2047 | |||
7121afab08 | |||
d9a4c4ffd6 | |||
fb826919a6 | |||
22720169a2 | |||
7fefd251db | |||
5de4f67344 | |||
d396a12d68 | |||
3888c91a6b | |||
562bac6987 | |||
e825b4dd85 | |||
08587b7a7a | |||
0daca2cf8f | |||
3a9b15c2be | |||
e9b4db0f1c | |||
312a31ce9e | |||
d4a8a2e720 | |||
dcb4f253d8 | |||
d0a04bae84 | |||
34ac96edd7 | |||
b42ce84c3e | |||
2db779b44f | |||
397bfe78aa | |||
efaad3f97c | |||
624c613bd1 | |||
07b0632c95 | |||
3d1e6cfa17 | |||
3db636d41a | |||
2053b8f07f | |||
b1681b53e4 | |||
03f60ff316 | |||
b2df0a5a02 | |||
8826cb0312 | |||
a0c72f5b94 | |||
7d9a58ae54 | |||
fd72b512f8 | |||
28c2721036 | |||
a1788bf75a | |||
b1bd278f9b | |||
16f6ab4861 | |||
01934e29b1 | |||
d1cefb0150 | |||
27b189d33a | |||
e05d88682a | |||
2a5f1f5f7e | |||
e7a45d9a05 | |||
ec9a326002 | |||
23c7729fcf | |||
7fcd324299 | |||
1633449638 | |||
57231a1406 | |||
2eb6292733 | |||
ff24493ff3 | |||
3d602af135 | |||
590665a5e9 | |||
89fd0dfed7 | |||
82bc887767 | |||
acd7de0dee | |||
e737cd9d5c | |||
0ec7a9d274 | |||
e49d9159e4 | |||
3343285761 | |||
14bba38324 | |||
679277d59e | |||
cebb2ae2b6 | |||
56d9f977ae | |||
984470b47d | |||
0112d681ac | |||
0cb2a977a0 | |||
f65c231ba0 | |||
dbc014f819 | |||
bbf7962e29 | |||
2b4d77bab4 | |||
8582674b44 | |||
f7675be834 | |||
00d77e508d | |||
e90cfe34e9 | |||
54dfd535a4 | |||
5a02eb6d18 | |||
97fc9319d1 | |||
03b4acd13e | |||
86f06a3c6a | |||
06e8d2a6e2 | |||
99f248a8ce | |||
c7aaa6ad98 | |||
cb5ce66c1a | |||
0750bf1d8a | |||
203360e8b5 | |||
ef1844109f | |||
de6ad35f60 | |||
fbb289dedf | |||
f1e87170f0 | |||
66ecad27a7 | |||
98b1e8bd80 | |||
26cd1533b4 | |||
3692b915f3 | |||
06788c3e12 | |||
edfcdd1135 | |||
dd2f3baa0c | |||
7db70e392b | |||
0cae24a612 | |||
8db0fa37db | |||
d27e3d9a91 | |||
fa5a4107a6 | |||
234188c4d4 | |||
9b700581f3 | |||
12db23d076 | |||
fd182f0abb | |||
7eab74e65c | |||
e0ecd4d9ff | |||
1ca09c16d3 | |||
a7df476e79 | |||
4e5eac6178 | |||
91a6808ad2 | |||
11a6517156 | |||
7aa7eb234d | |||
62d7df9710 | |||
0ff1188c3d | |||
b6e8d037a0 | |||
7a11b2c76f | |||
7f56dbdbfa | |||
df4eb15df8 | |||
ac9ae06cc8 | |||
464cf3ec7e | |||
bf0ce5c963 | |||
3a0c65a849 | |||
6d80638cf8 | |||
37e09d6532 | |||
8ea3fdcfef | |||
1bc847cdc9 | |||
03c35d6446 | |||
d5aea1a828 | |||
f17ddb4ace | |||
0cc6e27267 | |||
ca58aa782d | |||
e8671e8650 | |||
d46601be5c | |||
d30e2cefc0 | |||
08a93551e7 | |||
c2899fd727 | |||
5ec66e1777 | |||
516809cd02 | |||
0d3526221d | |||
728b12107f | |||
b56c021356 | |||
80f3b982d2 | |||
0d641b727f | |||
8278c059ad | |||
7af0ff5413 | |||
5c2877bdb8 | |||
85bfe79115 | |||
fb37f94c0a | |||
e53f40866e | |||
650ba20e5d | |||
6e01c41c22 | |||
f555f0f1cf | |||
35ef2175bc | |||
55f53deadf | |||
5991631bfa | |||
34a27d9ca4 | |||
1671490485 | |||
0e58a5c5f0 | |||
![]() |
bd11d7973c | ||
f3b5b09ed0 | |||
ce641bf7d2 | |||
f1c7314dca | |||
019408dc6d | |||
![]() |
cdba3540c2 | ||
885d997ff3 | |||
5118ab3cbf | |||
![]() |
2812377f5c | ||
93b40a9c7f | |||
b95ddcc811 | |||
90ba3c1134 | |||
05174958b2 | |||
![]() |
24be9b2013 | ||
29ce4b727c | |||
![]() |
f178019ffe | ||
0a1b948042 | |||
![]() |
74cbfb235e | ||
63c4104a89 | |||
2597287b1e | |||
![]() |
316156f0f0 | ||
5286e869cc | |||
d6dcf28d89 | |||
1d983b9ac0 | |||
e525221010 | |||
4cde4703f2 | |||
4a07e58c16 | |||
b12356575a | |||
77f571de7d | |||
![]() |
f5eef7563b | ||
741c09b1e4 | |||
0c0d7d181f | |||
9304da9422 | |||
![]() |
d7afdd00f2 | ||
b780ccea1c | |||
![]() |
5d8e871871 | ||
4655d688c9 | |||
8263c0ad95 | |||
a701afd09b | |||
![]() |
1e02d8c01f | ||
e4651375aa | |||
![]() |
afce4c6391 | ||
e95d0cb010 | |||
f717355519 | |||
3368e514ca | |||
9dca27177f | |||
c63274f7a9 | |||
6c2d2d2345 | |||
a6fbaa192f | |||
![]() |
a01f156535 | ||
3b021c09dc | |||
287176c881 | |||
84eeb5a002 | |||
b892532023 | |||
![]() |
56cdc52bb4 | ||
9ef6b1dd91 | |||
6f7585323b | |||
92135e64a3 | |||
28efeea30b | |||
a8a074907b | |||
6c29ec9820 | |||
![]() |
8ec23144ca | ||
b703853b28 | |||
6f7529fc9b | |||
27b45098f0 | |||
21f66d99cd | |||
1745051e6e | |||
![]() |
b395e054e6 | ||
90f4270b74 | |||
f68d75c226 | |||
9cf5133469 | |||
75929aad48 | |||
dca149ec4e | |||
75a7b97d24 | |||
eb62873fb6 | |||
f8effe7d1e | |||
87a3d34315 | |||
0f38ad6e5c | |||
0ec6ab71b2 | |||
b42204c87d | |||
53cb259ec1 | |||
0150cc9e8e | |||
20214473f1 | |||
![]() |
586ac07ef3 | ||
66f82f26d5 | |||
6cb2aa00fb | |||
96a236803e | |||
9d24044eb3 | |||
90248bcb54 | |||
a3b2a1b14f | |||
b21b159b95 | |||
93ec261dc0 | |||
ae246e9219 | |||
3f18fdd35a | |||
faf5207478 | |||
71f003dd66 | |||
3f85ab514e | |||
9eb5a6b1b9 | |||
8e26cd6078 | |||
36b9263730 | |||
3d29fecaec | |||
![]() |
92ac05f1e3 | ||
cae6ff6271 | |||
2163ae4dbf | |||
083945852b | |||
![]() |
e69b1232d7 | ||
ac681065fa | |||
90d8b46179 | |||
![]() |
280b5ca4aa | ||
54a171bd5e | |||
c043367810 | |||
5494df1c56 | |||
f9d04e38a0 | |||
6dfd7e016b | |||
96a3cbc40a | |||
7261795c99 | |||
d6becd15c1 | |||
0b22a18088 | |||
39fcddfaf0 | |||
![]() |
38eaa0c132 | ||
e33243e589 | |||
26f04dec9e | |||
8b44df8636 | |||
3bb7386d72 | |||
5d4ea0e057 | |||
0b030406bb | |||
28ef5cb2f5 | |||
7ea0572d79 | |||
63633de256 | |||
a4cc8752ff | |||
543f359acd | |||
fcdb5217ee | |||
b6252a1c1a | |||
5fcd33e294 | |||
7ecb64fde7 |
101
.gitea/workflows/build_and_deploy.yml
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
|
||||||
|
# https://docs.gitea.com/next/usage/actions/quickstart
|
||||||
|
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
|
||||||
|
# https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
|
||||||
|
|
||||||
|
# Configurable with a few commit messages:
|
||||||
|
# - [skip-tests] Skip the test stage
|
||||||
|
# - [skip-deployment] Skip the deployment stage
|
||||||
|
# - [skip-ci] Skip all stages (the whole ci/cd)
|
||||||
|
#
|
||||||
|
|
||||||
|
name: Build Docker and Deploy
|
||||||
|
run-name: Build & Deploy ${{ gitea.ref }} on ${{ gitea.actor }}
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['master']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_server:
|
||||||
|
name: Build Docker Container
|
||||||
|
runs-on: bfb-cicd-latest
|
||||||
|
if: >-
|
||||||
|
!contains(github.event.head_commit.message, '[skip-ci]') &&
|
||||||
|
!contains(github.event.head_commit.message, '[skip-deployment]')
|
||||||
|
steps:
|
||||||
|
- run: echo -n "${{ secrets.DOCKER_REG_PASS }}" | docker login registry.blackforestbytes.com -u docker --password-stdin
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- run: cd "${{ gitea.workspace }}/scnserver" && make clean
|
||||||
|
- run: cd "${{ gitea.workspace }}/scnserver" && make docker
|
||||||
|
- run: cd "${{ gitea.workspace }}/scnserver" && make push-docker
|
||||||
|
|
||||||
|
test_server:
|
||||||
|
name: Run Unit-Tests
|
||||||
|
runs-on: bfb-cicd-latest
|
||||||
|
if: >-
|
||||||
|
!contains(github.event.head_commit.message, '[skip-ci]') &&
|
||||||
|
!contains(github.event.head_commit.message, '[skip-tests]')
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Get Commiter Info
|
||||||
|
id: commiter_info
|
||||||
|
run: |
|
||||||
|
echo "NAME=$( git log -n 1 --pretty=format:%an )" >> $GITHUB_OUTPUT
|
||||||
|
echo "MAIL=$( git log -n 1 --pretty=format:%ae )" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: '${{ gitea.workspace }}/scnserver/go.mod'
|
||||||
|
cache: false
|
||||||
|
|
||||||
|
- name: Print Go Version
|
||||||
|
run: go version
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: cd "${{ gitea.workspace }}/scnserver" && make dgi && make swagger && SCN_TEST_LOGLEVEL=WARN make test
|
||||||
|
|
||||||
|
- name: Send failure mail
|
||||||
|
if: failure()
|
||||||
|
uses: dawidd6/action-send-mail@v3
|
||||||
|
with:
|
||||||
|
server_address: smtp.fastmail.com
|
||||||
|
server_port: 465
|
||||||
|
secure: true
|
||||||
|
username: ${{secrets.MAIL_USERNAME}}
|
||||||
|
password: ${{secrets.MAIL_PASSWORD}}
|
||||||
|
subject: Pipeline on '${{ gitea.repository }}' failed
|
||||||
|
to: ${{ steps.commiter_info.outputs.MAIL }}
|
||||||
|
from: Gitea Actions <gitea_actions@blackforestbytes.de>
|
||||||
|
body: "Go to https://gogs.blackforestbytes.com/${{ gitea.repository }}/actions"
|
||||||
|
|
||||||
|
deploy_server:
|
||||||
|
name: Deploy to Server
|
||||||
|
needs: [build_server, test_server]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >-
|
||||||
|
!cancelled() &&
|
||||||
|
!contains(github.event.head_commit.message, '[skip-ci]') &&
|
||||||
|
!contains(github.event.head_commit.message, '[skip-deployment]') &&
|
||||||
|
needs.build_server.result == 'success' &&
|
||||||
|
(needs.test_server.result == 'skipped' || needs.test_server.result == 'success')
|
||||||
|
steps:
|
||||||
|
- name: Execute deploy on remote (via ssh)
|
||||||
|
uses: appleboy/ssh-action@v1.0.0
|
||||||
|
with:
|
||||||
|
host: simplecloudnotifier.de
|
||||||
|
username: bfb-deploy-bot
|
||||||
|
port: 4477
|
||||||
|
key: "${{ secrets.SSH_KEY_BFBDEPLOYBOT }}"
|
||||||
|
script: cd /var/docker/deploy-scripts/simplecloudnotifier && ./deploy.sh master "${{ gitea.sha }}" || exit 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
256
.gitignore
vendored
@ -1,255 +1 @@
|
|||||||
|
.aider*
|
||||||
### Android ###
|
|
||||||
# Built application files
|
|
||||||
*.apk
|
|
||||||
*.ap_
|
|
||||||
|
|
||||||
# Files for the ART/Dalvik VM
|
|
||||||
*.dex
|
|
||||||
|
|
||||||
# Java class files
|
|
||||||
*.class
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
bin/
|
|
||||||
gen/
|
|
||||||
out/
|
|
||||||
|
|
||||||
# Gradle files
|
|
||||||
.gradle/
|
|
||||||
build/
|
|
||||||
|
|
||||||
# Local configuration file (sdk path, etc)
|
|
||||||
local.properties
|
|
||||||
|
|
||||||
# Proguard folder generated by Eclipse
|
|
||||||
proguard/
|
|
||||||
|
|
||||||
# Log Files
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Android Studio Navigation editor temp files
|
|
||||||
.navigation/
|
|
||||||
|
|
||||||
# Android Studio captures folder
|
|
||||||
captures/
|
|
||||||
|
|
||||||
# Intellij
|
|
||||||
*.iml
|
|
||||||
.idea/workspace.xml
|
|
||||||
.idea/tasks.xml
|
|
||||||
.idea/gradle.xml
|
|
||||||
.idea/dictionaries
|
|
||||||
.idea/libraries
|
|
||||||
|
|
||||||
# External native build folder generated in Android Studio 2.2 and later
|
|
||||||
.externalNativeBuild
|
|
||||||
|
|
||||||
# Freeline
|
|
||||||
freeline.py
|
|
||||||
freeline/
|
|
||||||
freeline_project_description.json
|
|
||||||
|
|
||||||
### Android Patch ###
|
|
||||||
gen-external-apklibs
|
|
||||||
|
|
||||||
### AndroidStudio ###
|
|
||||||
# Covers files to be ignored for android development using Android Studio.
|
|
||||||
|
|
||||||
# Built application files
|
|
||||||
|
|
||||||
# Files for the ART/Dalvik VM
|
|
||||||
|
|
||||||
# Java class files
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
|
|
||||||
# Gradle files
|
|
||||||
.gradle
|
|
||||||
|
|
||||||
# Signing files
|
|
||||||
.signing/
|
|
||||||
|
|
||||||
# Local configuration file (sdk path, etc)
|
|
||||||
|
|
||||||
# Proguard folder generated by Eclipse
|
|
||||||
|
|
||||||
# Log Files
|
|
||||||
|
|
||||||
# Android Studio
|
|
||||||
/*/build/
|
|
||||||
/*/local.properties
|
|
||||||
/*/out
|
|
||||||
/*/*/build
|
|
||||||
/*/*/production
|
|
||||||
*.ipr
|
|
||||||
*~
|
|
||||||
*.swp
|
|
||||||
|
|
||||||
# Android Patch
|
|
||||||
|
|
||||||
# External native build folder generated in Android Studio 2.2 and later
|
|
||||||
|
|
||||||
# NDK
|
|
||||||
obj/
|
|
||||||
|
|
||||||
# IntelliJ IDEA
|
|
||||||
*.iws
|
|
||||||
/out/
|
|
||||||
|
|
||||||
# User-specific configurations
|
|
||||||
.idea/libraries/
|
|
||||||
.idea/.name
|
|
||||||
.idea/compiler.xml
|
|
||||||
.idea/copyright/profiles_settings.xml
|
|
||||||
.idea/encodings.xml
|
|
||||||
.idea/misc.xml
|
|
||||||
.idea/modules.xml
|
|
||||||
.idea/scopes/scope_settings.xml
|
|
||||||
.idea/vcs.xml
|
|
||||||
.idea/jsLibraryMappings.xml
|
|
||||||
.idea/datasources.xml
|
|
||||||
.idea/dataSources.ids
|
|
||||||
.idea/sqlDataSources.xml
|
|
||||||
.idea/dynamic.xml
|
|
||||||
.idea/uiDesigner.xml
|
|
||||||
|
|
||||||
# Legacy Eclipse project files
|
|
||||||
.classpath
|
|
||||||
.project
|
|
||||||
.cproject
|
|
||||||
.settings/
|
|
||||||
|
|
||||||
# Mobile Tools for Java (J2ME)
|
|
||||||
.mtj.tmp/
|
|
||||||
|
|
||||||
# Package Files #
|
|
||||||
*.war
|
|
||||||
*.ear
|
|
||||||
|
|
||||||
# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
|
|
||||||
hs_err_pid*
|
|
||||||
|
|
||||||
## Plugin-specific files:
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/mongoSettings.xml
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
### AndroidStudio Patch ###
|
|
||||||
|
|
||||||
!/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
### Intellij ###
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff:
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files:
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.xml
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
|
|
||||||
# Gradle:
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-debug/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin:
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
## File-based project format:
|
|
||||||
|
|
||||||
## Plugin-specific files:
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# Ruby plugin and RubyMine
|
|
||||||
/.rakeTasks
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
|
|
||||||
### Intellij Patch ###
|
|
||||||
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
|
||||||
|
|
||||||
# *.iml
|
|
||||||
# modules.xml
|
|
||||||
# .idea/misc.xml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# Sonarlint plugin
|
|
||||||
.idea/sonarlint
|
|
||||||
|
|
||||||
# End of https://www.gitignore.io/api/linux,windows,android,intellij,androidstudio
|
|
||||||
|
|
||||||
#### ANDROID STUDIO EXTRA #####
|
|
||||||
|
|
||||||
*.iml
|
|
||||||
.gradle
|
|
||||||
/local.properties
|
|
||||||
/.idea/libraries
|
|
||||||
/.idea/modules.xml
|
|
||||||
/.idea/workspace.xml
|
|
||||||
.DS_Store
|
|
||||||
/build
|
|
||||||
/captures
|
|
||||||
.externalNativeBuild
|
|
||||||
|
|
||||||
|
|
||||||
### Windows ###
|
|
||||||
# Windows thumbnail cache files
|
|
||||||
ehthumbs_vista.db
|
|
||||||
|
|
||||||
# Folder config file
|
|
||||||
Desktop.ini
|
|
||||||
|
|
||||||
# Recycle Bin used on file shares
|
|
||||||
$RECYCLE.BIN/
|
|
||||||
|
|
||||||
# Windows Installer files
|
|
||||||
*.cab
|
|
||||||
*.msi
|
|
||||||
*.msm
|
|
||||||
*.msp
|
|
||||||
|
|
||||||
# Windows shortcuts
|
|
||||||
*.lnk
|
|
||||||
|
|
||||||
#########################################################################################
|
|
||||||
# BLACKFORESTBYTES CUSTOM #
|
|
||||||
#########################################################################################
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
BIN
.idea/caches/build_file_checksums.ser
generated
29
.idea/codeStyles/Project.xml
generated
@ -1,29 +0,0 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<code_scheme name="Project" version="173">
|
|
||||||
<Objective-C-extensions>
|
|
||||||
<file>
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
|
||||||
</file>
|
|
||||||
<class>
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
|
||||||
</class>
|
|
||||||
<extensions>
|
|
||||||
<pair source="cpp" header="h" fileNamingConvention="NONE" />
|
|
||||||
<pair source="c" header="h" fileNamingConvention="NONE" />
|
|
||||||
</extensions>
|
|
||||||
</Objective-C-extensions>
|
|
||||||
</code_scheme>
|
|
||||||
</component>
|
|
12
.idea/runConfigurations.xml
generated
@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="RunConfigurationProducerService">
|
|
||||||
<option name="ignoredProducers">
|
|
||||||
<set>
|
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
|
||||||
</set>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
22
README.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
SimpleCloudNotifier [](https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier)
|
||||||
|
===================
|
||||||
|
|
||||||
|
> SimpleCloudNotifier is an app to display messages that you can send to your phone with simple POST requests.
|
||||||
|
>
|
||||||
|
> After you start the app it generates a UserID and a UserSecret.
|
||||||
|
> Now you can send your message to https://simplecloudnotifier.blackforestbytes.com/send.php and a notification will be pushed to your phone.
|
||||||
|
> (see https://simplecloudnotifier.blackforestbytes.com/ for an example with curl)
|
||||||
|
>
|
||||||
|
>
|
||||||
|
> Use it to
|
||||||
|
> - send yourself automated messages from cron jobs
|
||||||
|
> - notify yourself when long-running scripts finish
|
||||||
|
> - send server error messages directly to your phone
|
||||||
|
> - integrate with other online services
|
||||||
|
>
|
||||||
|
> The possibilities are endless*
|
||||||
|
>
|
||||||
|
> \* Disclaimer: Developer does not actually guarantee endless possibilities
|
||||||
|
|
||||||
|
|
||||||
|
  
|
251
android/.gitignore
vendored
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
|
||||||
|
### Android ###
|
||||||
|
# Built application files
|
||||||
|
*.apk
|
||||||
|
*.ap_
|
||||||
|
|
||||||
|
# Files for the ART/Dalvik VM
|
||||||
|
*.dex
|
||||||
|
|
||||||
|
# Java class files
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
out/
|
||||||
|
|
||||||
|
# Gradle files
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Local configuration file (sdk path, etc)
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# Proguard folder generated by Eclipse
|
||||||
|
proguard/
|
||||||
|
|
||||||
|
# Log Files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Android Studio Navigation editor temp files
|
||||||
|
.navigation/
|
||||||
|
|
||||||
|
# Android Studio captures folder
|
||||||
|
captures/
|
||||||
|
|
||||||
|
# Intellij
|
||||||
|
*.iml
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/tasks.xml
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/libraries
|
||||||
|
|
||||||
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
|
.externalNativeBuild
|
||||||
|
|
||||||
|
# Freeline
|
||||||
|
freeline.py
|
||||||
|
freeline/
|
||||||
|
freeline_project_description.json
|
||||||
|
|
||||||
|
### Android Patch ###
|
||||||
|
gen-external-apklibs
|
||||||
|
|
||||||
|
### AndroidStudio ###
|
||||||
|
# Covers files to be ignored for android development using Android Studio.
|
||||||
|
|
||||||
|
# Built application files
|
||||||
|
|
||||||
|
# Files for the ART/Dalvik VM
|
||||||
|
|
||||||
|
# Java class files
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
|
||||||
|
# Gradle files
|
||||||
|
.gradle
|
||||||
|
|
||||||
|
# Signing files
|
||||||
|
.signing/
|
||||||
|
|
||||||
|
# Local configuration file (sdk path, etc)
|
||||||
|
|
||||||
|
# Proguard folder generated by Eclipse
|
||||||
|
|
||||||
|
# Log Files
|
||||||
|
|
||||||
|
# Android Studio
|
||||||
|
/*/build/
|
||||||
|
/*/local.properties
|
||||||
|
/*/out
|
||||||
|
/*/*/build
|
||||||
|
/*/*/production
|
||||||
|
*.ipr
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# Android Patch
|
||||||
|
|
||||||
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
|
|
||||||
|
# NDK
|
||||||
|
obj/
|
||||||
|
|
||||||
|
# IntelliJ IDEA
|
||||||
|
*.iws
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# User-specific configurations
|
||||||
|
.idea/libraries/
|
||||||
|
.idea/.name
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/copyright/profiles_settings.xml
|
||||||
|
.idea/encodings.xml
|
||||||
|
.idea/misc.xml
|
||||||
|
.idea/modules.xml
|
||||||
|
.idea/scopes/scope_settings.xml
|
||||||
|
.idea/vcs.xml
|
||||||
|
.idea/jsLibraryMappings.xml
|
||||||
|
.idea/datasources.xml
|
||||||
|
.idea/dataSources.ids
|
||||||
|
.idea/sqlDataSources.xml
|
||||||
|
.idea/dynamic.xml
|
||||||
|
.idea/uiDesigner.xml
|
||||||
|
|
||||||
|
# Legacy Eclipse project files
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
.cproject
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
# Mobile Tools for Java (J2ME)
|
||||||
|
.mtj.tmp/
|
||||||
|
|
||||||
|
# Package Files #
|
||||||
|
*.war
|
||||||
|
*.ear
|
||||||
|
|
||||||
|
# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
|
||||||
|
hs_err_pid*
|
||||||
|
|
||||||
|
## Plugin-specific files:
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/mongoSettings.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
### AndroidStudio Patch ###
|
||||||
|
|
||||||
|
!/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
### Intellij ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff:
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files:
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.xml
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
|
||||||
|
# Gradle:
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-debug/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin:
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
## File-based project format:
|
||||||
|
|
||||||
|
## Plugin-specific files:
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# Ruby plugin and RubyMine
|
||||||
|
/.rakeTasks
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
|
||||||
|
### Intellij Patch ###
|
||||||
|
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||||
|
|
||||||
|
# *.iml
|
||||||
|
# modules.xml
|
||||||
|
# .idea/misc.xml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# Sonarlint plugin
|
||||||
|
.idea/sonarlint
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/linux,windows,android,intellij,androidstudio
|
||||||
|
|
||||||
|
#### ANDROID STUDIO EXTRA #####
|
||||||
|
|
||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea/libraries
|
||||||
|
/.idea/modules.xml
|
||||||
|
/.idea/workspace.xml
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
|
|
||||||
|
|
||||||
|
### Windows ###
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# BLACKFORESTBYTES CUSTOM #
|
||||||
|
#########################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
.idea/caches
|
||||||
|
app/release
|
6
android/.idea/AndroidProjectSystem.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidProjectSystem">
|
||||||
|
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||||
|
</component>
|
||||||
|
</project>
|
113
android/.idea/codeStyles/Project.xml
generated
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<codeStyleSettings language="XML">
|
||||||
|
<arrangement>
|
||||||
|
<rules>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:android</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:id</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>style</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
</rules>
|
||||||
|
</arrangement>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
10
android/.idea/deploymentTargetDropDown.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetDropDown">
|
||||||
|
<value>
|
||||||
|
<entry key="app">
|
||||||
|
<State />
|
||||||
|
</entry>
|
||||||
|
</value>
|
||||||
|
</component>
|
||||||
|
</project>
|
10
android/.idea/deploymentTargetSelector.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetSelector">
|
||||||
|
<selectionStates>
|
||||||
|
<SelectionState runConfigName="app">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
|
</selectionStates>
|
||||||
|
</component>
|
||||||
|
</project>
|
10
android/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
|
<option name="processCode" value="true" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
40
android/.idea/jarRepositories.xml
generated
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RemoteRepositoriesConfiguration">
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Maven Central repository" />
|
||||||
|
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="jboss.community" />
|
||||||
|
<option name="name" value="JBoss Community repository" />
|
||||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="maven2" />
|
||||||
|
<option name="name" value="maven2" />
|
||||||
|
<option name="url" value="https://dl.bintray.com/gericop/maven" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="maven3" />
|
||||||
|
<option name="name" value="maven3" />
|
||||||
|
<option name="url" value="https://maven.google.com" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="BintrayJCenter" />
|
||||||
|
<option name="name" value="BintrayJCenter" />
|
||||||
|
<option name="url" value="https://jcenter.bintray.com/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="maven" />
|
||||||
|
<option name="name" value="maven" />
|
||||||
|
<option name="url" value="https://jitpack.io" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="Google" />
|
||||||
|
<option name="name" value="Google" />
|
||||||
|
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
|
||||||
|
</remote-repository>
|
||||||
|
</component>
|
||||||
|
</project>
|
10
android/.idea/migrations.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectMigrations">
|
||||||
|
<option name="MigrateToGradleLocalJavaHome">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
17
android/.idea/runConfigurations.xml
generated
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RunConfigurationProducerService">
|
||||||
|
<option name="ignoredProducers">
|
||||||
|
<set>
|
||||||
|
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
0
app/.gitignore → android/app/.gitignore
vendored
117
android/app/build.gradle
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 30
|
||||||
|
|
||||||
|
def versionPropsFile = file('version.properties')
|
||||||
|
def vNumber
|
||||||
|
def vName
|
||||||
|
if (versionPropsFile.canRead()) {
|
||||||
|
Properties versionProps = new Properties()
|
||||||
|
new FileInputStream(versionPropsFile).withCloseable { stream -> versionProps.load(stream) }
|
||||||
|
vNumber = versionProps['VERSION_CODE'].toInteger()
|
||||||
|
vName = versionProps['VERSION_NAME'].toString()
|
||||||
|
} else throw new FileNotFoundException("Could not read version.properties!")
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.blackforestbytes.simplecloudnotifier"
|
||||||
|
minSdkVersion 21
|
||||||
|
targetSdkVersion 30
|
||||||
|
versionCode vNumber
|
||||||
|
versionName vName
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
targetCompatibility 1.8
|
||||||
|
sourceCompatibility 1.8
|
||||||
|
}
|
||||||
|
namespace 'com.blackforestbytes.simplecloudnotifier'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||||
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||||
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||||
|
|
||||||
|
implementation 'com.google.android.material:material:1.2.1'
|
||||||
|
implementation 'com.google.firebase:firebase-core:18.0.0'
|
||||||
|
implementation 'com.google.firebase:firebase-messaging:21.0.0'
|
||||||
|
implementation 'com.google.android.gms:play-services-ads:19.5.0'
|
||||||
|
implementation 'com.android.billingclient:billing:3.0.1'
|
||||||
|
|
||||||
|
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
|
||||||
|
implementation 'com.github.kenglxn.QRGen:android:2.5.0'
|
||||||
|
implementation "com.github.DeweyReed:UltimateMusicPicker:2.0.0"
|
||||||
|
implementation 'com.github.duanhong169:colorpicker:1.1.5'
|
||||||
|
|
||||||
|
implementation 'net.danlew:android.joda:2.10.7.1'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
|
||||||
|
tasks.register("updateVersion") {
|
||||||
|
group = 'Custom'
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
def lastTag = ['git', 'describe', "--abbrev=0", "--tags"].execute().text.trim()
|
||||||
|
|
||||||
|
def versionPropsFile = file('version.properties')
|
||||||
|
if (!versionPropsFile.canRead()) throw new FileNotFoundException("Could not read version.properties!")
|
||||||
|
Properties versionProps = new Properties()
|
||||||
|
new FileInputStream(versionPropsFile).withCloseable { fis -> versionProps.load(fis) }
|
||||||
|
|
||||||
|
def matcher = lastTag =~ /^v([0-9]+)\.([0-9]+)\.([0-9]+)$/
|
||||||
|
|
||||||
|
if (!matcher.matches()) throw new Exception("Last Tag ('" + lastTag + "') has invalid format :(")
|
||||||
|
|
||||||
|
def vName = (matcher[0][1] as Integer) + "." + (matcher[0][2] as Integer) + "." + (matcher[0][3] as Integer)
|
||||||
|
def vCode = versionProps['VERSION_CODE'] as Integer
|
||||||
|
|
||||||
|
if (new File(".do_publish_beta_release").exists()) new File(".do_publish_beta_release").delete()
|
||||||
|
if (new File(".do_publish_prod_release").exists()) new File(".do_publish_prod_release").delete()
|
||||||
|
|
||||||
|
if (vName == versionProps['VERSION_NAME'].toString()) {
|
||||||
|
println "This version was already built - skip deployment"
|
||||||
|
} else if (vName.endsWith(".0")) {
|
||||||
|
println ""
|
||||||
|
println "====================================================================="
|
||||||
|
println "====================================================================="
|
||||||
|
println "(!) This is a new PRODUCTION release - create deployment trigger file"
|
||||||
|
println "====================================================================="
|
||||||
|
println "====================================================================="
|
||||||
|
println ""
|
||||||
|
|
||||||
|
vCode++
|
||||||
|
new File(".do_publish_prod_release").createNewFile()
|
||||||
|
|
||||||
|
versionProps['VERSION_NAME'] = vName.toString()
|
||||||
|
versionProps['VERSION_CODE'] = vCode.toString()
|
||||||
|
|
||||||
|
versionPropsFile.newWriter().withCloseable { w -> versionProps.store(w, null) }
|
||||||
|
} else {
|
||||||
|
println ""
|
||||||
|
println "==============================================================="
|
||||||
|
println "(!) This is a new beta release - create deployment trigger file"
|
||||||
|
println "==============================================================="
|
||||||
|
println ""
|
||||||
|
|
||||||
|
vCode++
|
||||||
|
new File(".do_publish_beta_release").createNewFile()
|
||||||
|
|
||||||
|
versionProps['VERSION_NAME'] = vName.toString()
|
||||||
|
versionProps['VERSION_CODE'] = vCode.toString()
|
||||||
|
|
||||||
|
versionPropsFile.newWriter().withCloseable { w -> versionProps.store(w, null) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
android/app/google-services.json
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"project_info": {
|
||||||
|
"project_number": "232728961679",
|
||||||
|
"firebase_url": "https://simplecloudnotifier-ea7ef.firebaseio.com",
|
||||||
|
"project_id": "simplecloudnotifier-ea7ef",
|
||||||
|
"storage_bucket": "simplecloudnotifier-ea7ef.appspot.com"
|
||||||
|
},
|
||||||
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:232728961679:android:23c75317f79601c9",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.blackforestbytes.simplecloudnotifier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "232728961679-o7gig6f684mp1l1ok7719v3jf3csejc1.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "com.blackforestbytes.simplecloudnotifier",
|
||||||
|
"certificate_hash": "3bcafbd39256422f0cb51fd446a228c26543afb4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "232728961679-t1h2eo5keha2lrvhsvdr5kgbkbfkja0o.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyBasR6JLAjM5Ut0rPb0euE_9DdDoTkcvKQ"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"analytics_service": {
|
||||||
|
"status": 1
|
||||||
|
},
|
||||||
|
"appinvite_service": {
|
||||||
|
"status": 2,
|
||||||
|
"other_platform_oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "232728961679-t1h2eo5keha2lrvhsvdr5kgbkbfkja0o.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ads_service": {
|
||||||
|
"status": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration_version": "1"
|
||||||
|
}
|
67
android/app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="com.android.vending.BILLING" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".SCNApp"
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:icon="@drawable/icon"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/AppTheme"
|
||||||
|
tools:ignore="GoogleAppIndexingWarning">
|
||||||
|
<activity android:name=".view.MainActivity">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="com.blackforestbytes.simplecloudnotifier.fileprovider"
|
||||||
|
android:grantUriPermissions="true"
|
||||||
|
android:exported="false">
|
||||||
|
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" />
|
||||||
|
</provider>
|
||||||
|
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.firebase.messaging.default_notification_icon"
|
||||||
|
android:resource="@drawable/icon" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.firebase.messaging.default_notification_color"
|
||||||
|
android:resource="@color/colorAccent" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.ads.AD_MANAGER_APP"
|
||||||
|
android:value="true" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.ads.APPLICATION_ID"
|
||||||
|
android:value="ca-app-pub-3320562328966175~7579972005" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".service.FBMService"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".service.BroadcastReceiverService"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".view.debug.QueryLogActivity"
|
||||||
|
android:label="@string/title_activity_query_log"
|
||||||
|
android:theme="@style/AppTheme" />
|
||||||
|
<activity android:name=".view.debug.SingleQueryLogActivity" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
BIN
android/app/src/main/ic_bfb_raster-web.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
android/app/src/main/ic_notification_full-web.png
Normal file
After Width: | Height: | Size: 67 KiB |
@ -0,0 +1,104 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.android.billingclient.api.BillingClient;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.view.AccountFragment;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.view.TabAdapter;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Lifecycle;
|
||||||
|
import androidx.lifecycle.LifecycleObserver;
|
||||||
|
import androidx.lifecycle.OnLifecycleEvent;
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||||
|
|
||||||
|
public class SCNApp extends Application implements LifecycleObserver
|
||||||
|
{
|
||||||
|
private static SCNApp instance;
|
||||||
|
private static WeakReference<MainActivity> mainActivity = new WeakReference<>(null);
|
||||||
|
|
||||||
|
public static final boolean LOCAL_DEBUG = BuildConfig.DEBUG;
|
||||||
|
public static final boolean DEBUG = BuildConfig.DEBUG || !BuildConfig.VERSION_NAME.endsWith(".0");
|
||||||
|
public static final boolean RELEASE = !DEBUG;
|
||||||
|
|
||||||
|
private static boolean isBackground = true;
|
||||||
|
|
||||||
|
public SCNApp()
|
||||||
|
{
|
||||||
|
instance = this;
|
||||||
|
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Context getContext()
|
||||||
|
{
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MainActivity getMainActivity()
|
||||||
|
{
|
||||||
|
return mainActivity.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isBackground()
|
||||||
|
{
|
||||||
|
return isBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showToast(final String msg, final int duration)
|
||||||
|
{
|
||||||
|
final MainActivity a = mainActivity.get();
|
||||||
|
if (a != null)
|
||||||
|
{
|
||||||
|
a.runOnUiThread(() -> Toast.makeText(a, msg, duration).show());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean runOnUiThread(Runnable r)
|
||||||
|
{
|
||||||
|
final MainActivity a = mainActivity.get();
|
||||||
|
if (a != null) {a.runOnUiThread(r); return true;}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void refreshAccountTab()
|
||||||
|
{
|
||||||
|
runOnUiThread(() ->
|
||||||
|
{
|
||||||
|
MainActivity a = mainActivity.get();
|
||||||
|
if (a == null) return;
|
||||||
|
|
||||||
|
TabAdapter ta = a.adpTabs;
|
||||||
|
if (ta == null) return;
|
||||||
|
|
||||||
|
AccountFragment tf = ta.tab2;
|
||||||
|
if (tf == null) return;
|
||||||
|
|
||||||
|
tf.updateUI();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void register(MainActivity a)
|
||||||
|
{
|
||||||
|
mainActivity = new WeakReference<>(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||||
|
public void onAppBackgrounded()
|
||||||
|
{
|
||||||
|
isBackground = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnLifecycleEvent(Lifecycle.Event.ON_START)
|
||||||
|
public void onAppForegrounded()
|
||||||
|
{
|
||||||
|
isBackground = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Config for collapsed line count
|
||||||
|
//TODO: Sometimes ads but promode
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.android;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public final class ThreadUtils
|
||||||
|
{
|
||||||
|
public static void safeSleep(int millisMin, int millisMax)
|
||||||
|
{
|
||||||
|
safeSleep(millisMin + (int)(Math.random()*(millisMax-millisMin)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void safeSleep(int millis)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(millis);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
Log.d("ThreadUtils", e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.collections;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func1to1;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public final class CollectionHelper
|
||||||
|
{
|
||||||
|
public static <T, C> List<T> unique(List<T> input, Func1to1<T, C> mapping)
|
||||||
|
{
|
||||||
|
List<T> output = new ArrayList<>(input.size());
|
||||||
|
|
||||||
|
HashSet<C> seen = new HashSet<>();
|
||||||
|
|
||||||
|
for (T v : input) if (seen.add(mapping.invoke(v))) output.add(v);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> sort(List<T> input, Comparator<T> comparator)
|
||||||
|
{
|
||||||
|
List<T> output = new ArrayList<>(input);
|
||||||
|
Collections.sort(output, comparator);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> void sort_inplace(List<T> input, Comparator<T> comparator)
|
||||||
|
{
|
||||||
|
Collections.sort(input, comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U extends Comparable<U>> List<T> sort(List<T> input, Func1to1<T, U> mapper)
|
||||||
|
{
|
||||||
|
return sort(input, mapper, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, U extends Comparable<U>> List<T> sort(List<T> input, Func1to1<T, U> mapper, int sortMod)
|
||||||
|
{
|
||||||
|
List<T> output = new ArrayList<>(input);
|
||||||
|
Collections.sort(output, (o1, o2) -> sortMod * mapper.invoke(o1).compareTo(mapper.invoke(o2)));
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class IntRange
|
||||||
|
{
|
||||||
|
private int Start;
|
||||||
|
public int Start() { return Start; }
|
||||||
|
|
||||||
|
private int End;
|
||||||
|
public int End() { return End; }
|
||||||
|
|
||||||
|
public IntRange(int s, int e) { Start = s; End = e; }
|
||||||
|
|
||||||
|
private IntRange() { }
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class NInt
|
||||||
|
{
|
||||||
|
public int Value;
|
||||||
|
|
||||||
|
public NInt(int v) { Value = v; }
|
||||||
|
|
||||||
|
private NInt() { }
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public final class Nothing
|
||||||
|
{
|
||||||
|
public final static Nothing Inst = new Nothing();
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class Tuple1<T1>
|
||||||
|
{
|
||||||
|
public final T1 Item1;
|
||||||
|
|
||||||
|
public Tuple1(T1 i1)
|
||||||
|
{
|
||||||
|
Item1 = i1;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class Tuple2<T1, T2>
|
||||||
|
{
|
||||||
|
public final T1 Item1;
|
||||||
|
public final T2 Item2;
|
||||||
|
|
||||||
|
public Tuple2(T1 i1, T2 i2)
|
||||||
|
{
|
||||||
|
Item1 = i1;
|
||||||
|
Item2 = i2;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class Tuple3<T1, T2, T3>
|
||||||
|
{
|
||||||
|
public final T1 Item1;
|
||||||
|
public final T2 Item2;
|
||||||
|
public final T3 Item3;
|
||||||
|
|
||||||
|
public Tuple3(T1 i1, T2 i2, T3 i3)
|
||||||
|
{
|
||||||
|
Item1 = i1;
|
||||||
|
Item2 = i2;
|
||||||
|
Item3 = i3;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class Tuple4<T1, T2, T3, T4>
|
||||||
|
{
|
||||||
|
public final T1 Item1;
|
||||||
|
public final T2 Item2;
|
||||||
|
public final T3 Item3;
|
||||||
|
public final T4 Item4;
|
||||||
|
|
||||||
|
public Tuple4(T1 i1, T2 i2, T3 i3, T4 i4)
|
||||||
|
{
|
||||||
|
Item1 = i1;
|
||||||
|
Item2 = i2;
|
||||||
|
Item3 = i3;
|
||||||
|
Item4 = i4;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
|
||||||
|
|
||||||
|
public class Tuple5<T1, T2, T3, T4, T5>
|
||||||
|
{
|
||||||
|
public final T1 Item1;
|
||||||
|
public final T2 Item2;
|
||||||
|
public final T3 Item3;
|
||||||
|
public final T4 Item4;
|
||||||
|
public final T5 Item5;
|
||||||
|
|
||||||
|
public Tuple5(T1 i1, T2 i2, T3 i3, T4 i4, T5 i5)
|
||||||
|
{
|
||||||
|
Item1 = i1;
|
||||||
|
Item2 = i2;
|
||||||
|
Item3 = i3;
|
||||||
|
Item4 = i4;
|
||||||
|
Item5 = i5;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
|
||||||
|
public final class FI
|
||||||
|
{
|
||||||
|
private FI() throws InstantiationException { throw new InstantiationException(); }
|
||||||
|
|
||||||
|
public static SeekBar.OnSeekBarChangeListener SeekBarChanged(Func3to0<SeekBar, Integer, Boolean> action)
|
||||||
|
{
|
||||||
|
return new SeekBar.OnSeekBarChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||||
|
action.invoke(seekBar, progress, fromUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func0to0 {
|
||||||
|
|
||||||
|
Func0to0 EMPTY = ()->{};
|
||||||
|
|
||||||
|
void invoke();
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func0to1<TResult> {
|
||||||
|
TResult invoke();
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func0to1WithIOException<TResult> {
|
||||||
|
TResult invoke() throws IOException;
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func1to0<TInput1> {
|
||||||
|
void invoke(TInput1 value);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func1to1<TInput1, TResult> {
|
||||||
|
TResult invoke(TInput1 value);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func2to0<TInput1, TInput2> {
|
||||||
|
void invoke(TInput1 value1, TInput2 value2);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func2to1<TInput1, TInput2, TResult> {
|
||||||
|
TResult invoke(TInput1 value1, TInput2 value2);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func3to0<TInput1, TInput2, TInput3> {
|
||||||
|
void invoke(TInput1 value1, TInput2 value2, TInput3 value3);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func4to0<TInput1, TInput2, TInput3, TInput4> {
|
||||||
|
void invoke(TInput1 value1, TInput2 value2, TInput3 value3, TInput4 value4);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Func5to0<TInput1, TInput2, TInput3, TInput4, TInput5> {
|
||||||
|
void invoke(TInput1 value1, TInput2 value2, TInput3 value3, TInput4 value4, TInput5 value5);
|
||||||
|
}
|
@ -0,0 +1,231 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.string;
|
||||||
|
|
||||||
|
// from MonoSAMFramework.Portable.DebugTools.CompactJsonFormatter
|
||||||
|
public class CompactJsonFormatter
|
||||||
|
{
|
||||||
|
private static final String INDENT_STRING = " ";
|
||||||
|
|
||||||
|
public static String formatJSON(String str, int maxIndent)
|
||||||
|
{
|
||||||
|
int indent = 0;
|
||||||
|
boolean quoted = false;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
char last = ' ';
|
||||||
|
for (int i = 0; i < str.length(); i++)
|
||||||
|
{
|
||||||
|
char ch = str.charAt(i);
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case '\r':
|
||||||
|
case '\n':
|
||||||
|
break;
|
||||||
|
case '{':
|
||||||
|
case '[':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
indent++;
|
||||||
|
if (indent >= maxIndent) break;
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
last = ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '}':
|
||||||
|
case ']':
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
indent--;
|
||||||
|
if (indent + 1 >= maxIndent) { sb.append(ch); break; }
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
}
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
boolean escaped = false;
|
||||||
|
int index = i;
|
||||||
|
while (index > 0 && str.charAt(--index) == '\\')
|
||||||
|
escaped = !escaped;
|
||||||
|
if (!escaped)
|
||||||
|
quoted = !quoted;
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
if (indent >= maxIndent) { sb.append(' '); last = ' '; break; }
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ':':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted) { sb.append(" "); last = ' '; }
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
case '\t':
|
||||||
|
if (quoted)
|
||||||
|
{
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
}
|
||||||
|
else if (last != ' ')
|
||||||
|
{
|
||||||
|
sb.append(' ');
|
||||||
|
last = ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String compressJson(String str, int compressionLevel)
|
||||||
|
{
|
||||||
|
int indent = 0;
|
||||||
|
boolean quoted = false;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
char last = ' ';
|
||||||
|
int compress = 0;
|
||||||
|
for (int i = 0; i < str.length(); i++)
|
||||||
|
{
|
||||||
|
char ch = str.charAt(i);
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case '\r':
|
||||||
|
case '\n':
|
||||||
|
break;
|
||||||
|
case '{':
|
||||||
|
case '[':
|
||||||
|
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
if (compress == 0 && getJsonDepth(str, i) <= compressionLevel)
|
||||||
|
compress = 1;
|
||||||
|
else if (compress > 0)
|
||||||
|
compress++;
|
||||||
|
|
||||||
|
indent++;
|
||||||
|
if (compress > 0) break;
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
last = ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '}':
|
||||||
|
case ']':
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
indent--;
|
||||||
|
if (compress > 0) { compress--; sb.append(ch); break; }
|
||||||
|
compress--;
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
}
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
boolean escaped = false;
|
||||||
|
int index = i;
|
||||||
|
while (index > 0 && str.charAt(--index) == '\\')
|
||||||
|
escaped = !escaped;
|
||||||
|
if (!escaped)
|
||||||
|
quoted = !quoted;
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
if (compress > 0) { sb.append(' '); last = ' '; break; }
|
||||||
|
sb.append("\n");
|
||||||
|
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ':':
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
if (!quoted) { sb.append(" "); last = ' '; }
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
case '\t':
|
||||||
|
if (quoted)
|
||||||
|
{
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
}
|
||||||
|
else if (last != ' ')
|
||||||
|
{
|
||||||
|
sb.append(' ');
|
||||||
|
last = ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.append(ch);
|
||||||
|
last = ch;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getJsonDepth(String str, int i)
|
||||||
|
{
|
||||||
|
int maxindent = 0;
|
||||||
|
int indent = 0;
|
||||||
|
boolean quoted = false;
|
||||||
|
for (; i < str.length(); i++)
|
||||||
|
{
|
||||||
|
char ch = str.charAt(i);
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case '{':
|
||||||
|
case '[':
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
indent++;
|
||||||
|
maxindent = Math.max(indent, maxindent);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '}':
|
||||||
|
case ']':
|
||||||
|
if (!quoted)
|
||||||
|
{
|
||||||
|
indent--;
|
||||||
|
if (indent <= 0) return maxindent;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
boolean escaped = false;
|
||||||
|
int index = i;
|
||||||
|
while (index > 0 && str.charAt(--index) == '\\')
|
||||||
|
escaped = !escaped;
|
||||||
|
if (!escaped)
|
||||||
|
quoted = !quoted;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxindent;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.lib.string;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func1to1;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Str
|
||||||
|
{
|
||||||
|
public final static String Empty = "";
|
||||||
|
|
||||||
|
public static String format(String fmt, Object... data)
|
||||||
|
{
|
||||||
|
return MessageFormat.format(fmt, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String rformat(int fmtResId, Object... data)
|
||||||
|
{
|
||||||
|
Context inst = SCNApp.getContext();
|
||||||
|
if (inst == null)
|
||||||
|
{
|
||||||
|
Log.e("StringFormat", "rformat::NoInstance --> inst==null for" + fmtResId);
|
||||||
|
return "?ERR?";
|
||||||
|
}
|
||||||
|
|
||||||
|
return MessageFormat.format(inst.getResources().getString(fmtResId), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String firstLine(String content)
|
||||||
|
{
|
||||||
|
int idx = content.indexOf('\n');
|
||||||
|
if (idx == -1) return content;
|
||||||
|
|
||||||
|
if (idx == 0) return Str.Empty;
|
||||||
|
|
||||||
|
if (content.charAt(idx-1) == '\r') return content.substring(0, idx-1);
|
||||||
|
|
||||||
|
return content.substring(0, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isNullOrWhitespace(String str)
|
||||||
|
{
|
||||||
|
return str == null || str.length() == 0 || str.trim().length() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isNullOrEmpty(String str)
|
||||||
|
{
|
||||||
|
return str == null || str.length() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean equals(String a, String b)
|
||||||
|
{
|
||||||
|
if (a == null) return (b == null);
|
||||||
|
return a.equals(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String join(String sep, List<String> list)
|
||||||
|
{
|
||||||
|
StringBuilder b = new StringBuilder();
|
||||||
|
boolean first = true;
|
||||||
|
for (String v : list)
|
||||||
|
{
|
||||||
|
if (!first) b.append(sep);
|
||||||
|
b.append(v);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> String join(String sep, List<T> list, Func1to1<T, String> map)
|
||||||
|
{
|
||||||
|
StringBuilder b = new StringBuilder();
|
||||||
|
boolean first = true;
|
||||||
|
for (T v : list)
|
||||||
|
{
|
||||||
|
if (!first) b.append(sep);
|
||||||
|
b.append(map.invoke(v));
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Integer tryParseToInt(String s)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Integer.parseInt(s);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.model;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
public class CMessage
|
||||||
|
{
|
||||||
|
public boolean IsExpandedInAdapter = false;
|
||||||
|
|
||||||
|
public final long SCN_ID;
|
||||||
|
public final long Timestamp;
|
||||||
|
public final String Title;
|
||||||
|
public final String Content;
|
||||||
|
public final PriorityEnum Priority;
|
||||||
|
|
||||||
|
private static final SimpleDateFormat _format;
|
||||||
|
static
|
||||||
|
{
|
||||||
|
_format = new SimpleDateFormat("yyyy'-'MM'-'dd HH':'mm':'ss", Locale.getDefault());
|
||||||
|
_format.setTimeZone(TimeZone.getDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CMessage(long id, long t, String mt, String mc, PriorityEnum p)
|
||||||
|
{
|
||||||
|
SCN_ID = id;
|
||||||
|
Timestamp = t;
|
||||||
|
Title = mt;
|
||||||
|
Content = mc;
|
||||||
|
Priority = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SimpleDateFormat")
|
||||||
|
public String formatTimestamp()
|
||||||
|
{
|
||||||
|
return _format.format(new Date(Timestamp*1000));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,237 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.collections.CollectionHelper;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.view.MessageAdapter;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class CMessageList
|
||||||
|
{
|
||||||
|
private final Object msg_lock = new Object();
|
||||||
|
|
||||||
|
public ArrayList<CMessage> Messages;
|
||||||
|
public Set<String> AllAcks;
|
||||||
|
|
||||||
|
private ArrayList<WeakReference<MessageAdapter>> _listener = new ArrayList<>();
|
||||||
|
|
||||||
|
private final static Object _lock = new Object();
|
||||||
|
private static CMessageList _inst = null;
|
||||||
|
public static CMessageList inst()
|
||||||
|
{
|
||||||
|
synchronized (_lock)
|
||||||
|
{
|
||||||
|
if (_inst != null) return _inst;
|
||||||
|
return _inst = new CMessageList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CMessageList()
|
||||||
|
{
|
||||||
|
reloadPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reloadPrefs()
|
||||||
|
{
|
||||||
|
synchronized (msg_lock)
|
||||||
|
{
|
||||||
|
Messages = new ArrayList<>();
|
||||||
|
AllAcks = new HashSet<>();
|
||||||
|
|
||||||
|
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
|
||||||
|
int count = sharedPref.getInt("message_count", 0);
|
||||||
|
for (int i=0; i < count; i++)
|
||||||
|
{
|
||||||
|
long time = sharedPref.getLong("message["+i+"].timestamp", 0);
|
||||||
|
String title = sharedPref.getString("message["+i+"].title", "");
|
||||||
|
String content = sharedPref.getString("message["+i+"].content", "");
|
||||||
|
PriorityEnum prio = PriorityEnum.parseAPI(sharedPref.getInt("message["+i+"].priority", 1));
|
||||||
|
long scnid = sharedPref.getLong("message["+i+"].scnid", 0);
|
||||||
|
|
||||||
|
Messages.add(new CMessage(scnid, time, title, content, prio));
|
||||||
|
}
|
||||||
|
|
||||||
|
AllAcks = sharedPref.getStringSet("acks", new HashSet<>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CMessage add(final long scnid, final long time, final String title, final String content, final PriorityEnum pe)
|
||||||
|
{
|
||||||
|
CMessage msg = new CMessage(scnid, time, title, content, pe);
|
||||||
|
|
||||||
|
boolean run = SCNApp.runOnUiThread(() ->
|
||||||
|
{
|
||||||
|
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
|
||||||
|
int count = sharedPref.getInt("message_count", 0);
|
||||||
|
|
||||||
|
synchronized (msg_lock)
|
||||||
|
{
|
||||||
|
Messages.add(msg);
|
||||||
|
AllAcks.add(Long.toHexString(msg.SCN_ID));
|
||||||
|
|
||||||
|
while (Messages.size()>SCNSettings.inst().LocalCacheSize) Messages.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Messages.size()>1 && Messages.get(Messages.size()-2).Timestamp < msg.Timestamp)
|
||||||
|
{
|
||||||
|
// quick save
|
||||||
|
|
||||||
|
SharedPreferences.Editor e = sharedPref.edit();
|
||||||
|
|
||||||
|
e.putInt( "message_count", count+1);
|
||||||
|
e.putLong( "message["+count+"].timestamp", time);
|
||||||
|
e.putString("message["+count+"].title", title);
|
||||||
|
e.putString("message["+count+"].content", content);
|
||||||
|
e.putInt( "message["+count+"].priority", pe.ID);
|
||||||
|
e.putLong( "message["+count+"].scnid", scnid);
|
||||||
|
|
||||||
|
e.putStringSet("acks", AllAcks);
|
||||||
|
|
||||||
|
e.apply();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// full save
|
||||||
|
|
||||||
|
fullSave(); // does sort in here
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (WeakReference<MessageAdapter> ref : _listener)
|
||||||
|
{
|
||||||
|
MessageAdapter a = ref.get();
|
||||||
|
if (a == null) continue;
|
||||||
|
a.customNotifyDataSetChanged();
|
||||||
|
a.scrollToTop();
|
||||||
|
}
|
||||||
|
CleanUpListener();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!run)
|
||||||
|
{
|
||||||
|
synchronized (msg_lock)
|
||||||
|
{
|
||||||
|
Messages.add(new CMessage(scnid, time, title, content, pe));
|
||||||
|
AllAcks.add(Long.toHexString(msg.SCN_ID));
|
||||||
|
}
|
||||||
|
fullSave(); // does sort in here
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear()
|
||||||
|
{
|
||||||
|
Messages.clear();
|
||||||
|
fullSave();
|
||||||
|
|
||||||
|
for (WeakReference<MessageAdapter> ref : _listener)
|
||||||
|
{
|
||||||
|
MessageAdapter a = ref.get();
|
||||||
|
if (a == null) continue;
|
||||||
|
a.customNotifyDataSetChanged();
|
||||||
|
}
|
||||||
|
CleanUpListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fullSave()
|
||||||
|
{
|
||||||
|
synchronized (msg_lock)
|
||||||
|
{
|
||||||
|
CollectionHelper.sort_inplace(Messages, (a,b) -> Long.compare(a.Timestamp, b.Timestamp));
|
||||||
|
|
||||||
|
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor e = sharedPref.edit();
|
||||||
|
|
||||||
|
e.clear();
|
||||||
|
|
||||||
|
e.putInt("message_count", Messages.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < Messages.size(); i++)
|
||||||
|
{
|
||||||
|
e.putLong( "message["+i+"].timestamp", Messages.get(i).Timestamp);
|
||||||
|
e.putString("message["+i+"].title", Messages.get(i).Title);
|
||||||
|
e.putString("message["+i+"].content", Messages.get(i).Content);
|
||||||
|
e.putInt( "message["+i+"].priority", Messages.get(i).Priority.ID);
|
||||||
|
e.putLong( "message["+i+"].scnid", Messages.get(i).SCN_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.putStringSet("acks", AllAcks);
|
||||||
|
|
||||||
|
e.apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CMessage tryGet(int pos)
|
||||||
|
{
|
||||||
|
synchronized (msg_lock)
|
||||||
|
{
|
||||||
|
if (pos < 0 || pos >= Messages.size()) return null;
|
||||||
|
return Messages.get(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CMessage tryGetFromBack(int pos)
|
||||||
|
{
|
||||||
|
return tryGet(Messages.size() - pos - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size()
|
||||||
|
{
|
||||||
|
synchronized (msg_lock)
|
||||||
|
{
|
||||||
|
return Messages.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register(MessageAdapter adp)
|
||||||
|
{
|
||||||
|
_listener.add(new WeakReference<>(adp));
|
||||||
|
CleanUpListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanUpListener()
|
||||||
|
{
|
||||||
|
for (int i=_listener.size()-1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (_listener.get(i).get() == null) _listener.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAck(long id)
|
||||||
|
{
|
||||||
|
synchronized (msg_lock)
|
||||||
|
{
|
||||||
|
return AllAcks.contains(Long.toHexString(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CMessage removeFromBack(int pos)
|
||||||
|
{
|
||||||
|
CMessage r;
|
||||||
|
synchronized (msg_lock)
|
||||||
|
{
|
||||||
|
int index = Messages.size() - pos - 1;
|
||||||
|
r = Messages.remove(index);
|
||||||
|
}
|
||||||
|
fullSave(); // does sort in here
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insert(int index, CMessage item)
|
||||||
|
{
|
||||||
|
synchronized (msg_lock)
|
||||||
|
{
|
||||||
|
Messages.add(index, item);
|
||||||
|
}
|
||||||
|
fullSave(); // does sort in here
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.model;
|
||||||
|
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
|
|
||||||
|
public enum LogLevel
|
||||||
|
{
|
||||||
|
DEBUG,
|
||||||
|
INFO,
|
||||||
|
WARN,
|
||||||
|
ERROR;
|
||||||
|
|
||||||
|
public String toUIString()
|
||||||
|
{
|
||||||
|
switch (this)
|
||||||
|
{
|
||||||
|
case DEBUG: return "Debug";
|
||||||
|
case INFO: return "Info";
|
||||||
|
case WARN: return "Warning";
|
||||||
|
case ERROR: return "Error";
|
||||||
|
default: return "???";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getColor()
|
||||||
|
{
|
||||||
|
switch (this)
|
||||||
|
{
|
||||||
|
case DEBUG: return Color.GRAY;
|
||||||
|
case WARN: return Color.rgb(171, 145, 68);
|
||||||
|
case INFO: return Color.BLACK;
|
||||||
|
case ERROR: return Color.RED;
|
||||||
|
default: return Color.MAGENTA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int asInt()
|
||||||
|
{
|
||||||
|
switch (this)
|
||||||
|
{
|
||||||
|
case DEBUG: return 0;
|
||||||
|
case WARN: return 1;
|
||||||
|
case INFO: return 2;
|
||||||
|
case ERROR: return 3;
|
||||||
|
default: return 999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LogLevel fromInt(int i)
|
||||||
|
{
|
||||||
|
if (i == 0) return LogLevel.DEBUG;
|
||||||
|
if (i == 1) return LogLevel.WARN;
|
||||||
|
if (i == 2) return LogLevel.INFO;
|
||||||
|
if (i == 3) return LogLevel.ERROR;
|
||||||
|
|
||||||
|
return LogLevel.ERROR; // ????
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.model;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.media.RingtoneManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
public class NotificationSettings
|
||||||
|
{
|
||||||
|
public boolean EnableSound;
|
||||||
|
public String SoundName;
|
||||||
|
public String SoundSource;
|
||||||
|
public boolean RepeatSound;
|
||||||
|
|
||||||
|
public boolean ForceVolume;
|
||||||
|
public int ForceVolumeValue;
|
||||||
|
|
||||||
|
public boolean EnableLED;
|
||||||
|
public int LEDColor;
|
||||||
|
|
||||||
|
public boolean EnableVibration;
|
||||||
|
|
||||||
|
public NotificationSettings(PriorityEnum p)
|
||||||
|
{
|
||||||
|
EnableSound = (p == PriorityEnum.HIGH);
|
||||||
|
SoundName = "Default";
|
||||||
|
SoundSource = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION).toString();
|
||||||
|
RepeatSound = false;
|
||||||
|
EnableLED = (p == PriorityEnum.HIGH) || (p == PriorityEnum.NORMAL);
|
||||||
|
LEDColor = Color.BLUE;
|
||||||
|
EnableVibration = (p == PriorityEnum.HIGH) || (p == PriorityEnum.NORMAL);
|
||||||
|
ForceVolume = false;
|
||||||
|
ForceVolumeValue = 50;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.model;
|
||||||
|
|
||||||
|
public enum PriorityEnum
|
||||||
|
{
|
||||||
|
LOW(0),
|
||||||
|
NORMAL(1),
|
||||||
|
HIGH(2);
|
||||||
|
|
||||||
|
public final int ID;
|
||||||
|
|
||||||
|
PriorityEnum(int id) { ID = id; }
|
||||||
|
|
||||||
|
public static PriorityEnum parseAPI(String v) throws Exception
|
||||||
|
{
|
||||||
|
for (PriorityEnum p : values())
|
||||||
|
{
|
||||||
|
if (String.valueOf(p.ID).equals(v.trim())) return p;
|
||||||
|
}
|
||||||
|
throw new Exception("Invalid value for <PriorityEnum> : '"+v+"'");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PriorityEnum parseAPI(int v)
|
||||||
|
{
|
||||||
|
for (PriorityEnum p : values())
|
||||||
|
{
|
||||||
|
if (p.ID == v) return p;
|
||||||
|
}
|
||||||
|
return PriorityEnum.NORMAL;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.collections.CollectionHelper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class QueryLog
|
||||||
|
{
|
||||||
|
private final static int MAX_HISTORY_SIZE = 192;
|
||||||
|
|
||||||
|
private static QueryLog _instance;
|
||||||
|
public static QueryLog inst() { if (_instance == null) synchronized (QueryLog.class) { if (_instance == null) _instance = new QueryLog(); } return _instance; }
|
||||||
|
|
||||||
|
private QueryLog(){ reloadPrefs(); }
|
||||||
|
|
||||||
|
private final List<SingleQuery> history = new ArrayList<>();
|
||||||
|
|
||||||
|
public synchronized void add(SingleQuery r)
|
||||||
|
{
|
||||||
|
history.add(r);
|
||||||
|
while (history.size() > MAX_HISTORY_SIZE) history.remove(0);
|
||||||
|
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized List<SingleQuery> get()
|
||||||
|
{
|
||||||
|
List<SingleQuery> r = new ArrayList<>(history);
|
||||||
|
CollectionHelper.sort_inplace(r, (o1, o2) -> (-1) * o1.Timestamp.compareTo(o2.Timestamp));
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void save()
|
||||||
|
{
|
||||||
|
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("QueryLog", Context.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor e = sharedPref.edit();
|
||||||
|
|
||||||
|
e.clear();
|
||||||
|
|
||||||
|
e.putInt("history_count", history.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < history.size(); i++) history.get(i).save(e, "message["+(i+1000)+"]");
|
||||||
|
|
||||||
|
e.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void reloadPrefs()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Context c = SCNApp.getContext();
|
||||||
|
SharedPreferences sharedPref = c.getSharedPreferences("QueryLog", Context.MODE_PRIVATE);
|
||||||
|
int count = sharedPref.getInt("history_count", 0);
|
||||||
|
for (int i=0; i < count; i++) history.add(SingleQuery.load(sharedPref, "message["+(i+1000)+"]"));
|
||||||
|
|
||||||
|
CollectionHelper.sort_inplace(history, (o1, o2) -> (-1) * o1.Timestamp.compareTo(o2.Timestamp));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("SC:QL:Load", e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,273 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.model;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple3;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||||
|
import com.google.firebase.installations.FirebaseInstallations;
|
||||||
|
|
||||||
|
public class SCNSettings
|
||||||
|
{
|
||||||
|
private final static Object _lock = new Object();
|
||||||
|
private static volatile SCNSettings _inst = null;
|
||||||
|
public static SCNSettings inst()
|
||||||
|
{
|
||||||
|
SCNSettings local = _inst;
|
||||||
|
if (local == null)
|
||||||
|
{
|
||||||
|
synchronized (_lock)
|
||||||
|
{
|
||||||
|
local = _inst;
|
||||||
|
if (local == null) _inst = local = new SCNSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return local;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
public final static Integer[] CHOOSABLE_CACHE_SIZES = new Integer[]{20, 50, 100, 200, 500, 1000, 2000, 5000};
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
public int quota_curr;
|
||||||
|
public int quota_max;
|
||||||
|
public int user_id;
|
||||||
|
public String user_key;
|
||||||
|
|
||||||
|
public String fcm_token_local;
|
||||||
|
public String fcm_token_server;
|
||||||
|
|
||||||
|
public String promode_token;
|
||||||
|
public boolean promode_local;
|
||||||
|
public boolean promode_server;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
public boolean Enabled = true;
|
||||||
|
public int LocalCacheSize = 500;
|
||||||
|
public boolean EnableDeleteSwipe = false;
|
||||||
|
public int PreviewLineCount = 6;
|
||||||
|
|
||||||
|
public final NotificationSettings PriorityLow = new NotificationSettings(PriorityEnum.LOW);
|
||||||
|
public final NotificationSettings PriorityNorm = new NotificationSettings(PriorityEnum.NORMAL);
|
||||||
|
public final NotificationSettings PriorityHigh = new NotificationSettings(PriorityEnum.HIGH);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
public SCNSettings()
|
||||||
|
{
|
||||||
|
reloadPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reloadPrefs()
|
||||||
|
{
|
||||||
|
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("Config", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
quota_curr = sharedPref.getInt( "quota_curr", 0);
|
||||||
|
quota_max = sharedPref.getInt( "quota_max", 0);
|
||||||
|
user_id = sharedPref.getInt( "user_id", -1);
|
||||||
|
user_key = sharedPref.getString("user_key", "");
|
||||||
|
fcm_token_local = sharedPref.getString("fcm_token_local", "");
|
||||||
|
fcm_token_server = sharedPref.getString("fcm_token_server", "");
|
||||||
|
promode_local = sharedPref.getBoolean("promode_local", false);
|
||||||
|
promode_server = sharedPref.getBoolean("promode_server", false);
|
||||||
|
promode_token = sharedPref.getString("promode_token", "");
|
||||||
|
|
||||||
|
Enabled = sharedPref.getBoolean("app_enabled", Enabled);
|
||||||
|
LocalCacheSize = sharedPref.getInt("local_cache_size", LocalCacheSize);
|
||||||
|
EnableDeleteSwipe = sharedPref.getBoolean("do_del_swipe", EnableDeleteSwipe);
|
||||||
|
PreviewLineCount = sharedPref.getInt("preview_line_count", PreviewLineCount);
|
||||||
|
|
||||||
|
PriorityLow.EnableLED = sharedPref.getBoolean("priority_low:enabled_led", PriorityLow.EnableLED);
|
||||||
|
PriorityLow.EnableSound = sharedPref.getBoolean("priority_low:enabled_sound", PriorityLow.EnableSound);
|
||||||
|
PriorityLow.EnableVibration = sharedPref.getBoolean("priority_low:enabled_vibration", PriorityLow.EnableVibration);
|
||||||
|
PriorityLow.RepeatSound = sharedPref.getBoolean("priority_low:repeat_sound", PriorityLow.RepeatSound);
|
||||||
|
PriorityLow.SoundName = sharedPref.getString( "priority_low:sound_name", PriorityLow.SoundName);
|
||||||
|
PriorityLow.SoundSource = sharedPref.getString( "priority_low:sound_source", PriorityLow.SoundSource);
|
||||||
|
PriorityLow.LEDColor = sharedPref.getInt( "priority_low:led_color", PriorityLow.LEDColor);
|
||||||
|
PriorityLow.ForceVolume = sharedPref.getBoolean("priority_low:force_volume", PriorityLow.ForceVolume);
|
||||||
|
PriorityLow.ForceVolumeValue = sharedPref.getInt( "priority_low:force_volume_value", PriorityLow.ForceVolumeValue);
|
||||||
|
|
||||||
|
PriorityNorm.EnableLED = sharedPref.getBoolean("priority_norm:enabled_led", PriorityNorm.EnableLED);
|
||||||
|
PriorityNorm.EnableSound = sharedPref.getBoolean("priority_norm:enabled_sound", PriorityNorm.EnableSound);
|
||||||
|
PriorityNorm.EnableVibration = sharedPref.getBoolean("priority_norm:enabled_vibration", PriorityNorm.EnableVibration);
|
||||||
|
PriorityNorm.RepeatSound = sharedPref.getBoolean("priority_norm:repeat_sound", PriorityNorm.RepeatSound);
|
||||||
|
PriorityNorm.SoundName = sharedPref.getString( "priority_norm:sound_name", PriorityNorm.SoundName);
|
||||||
|
PriorityNorm.SoundSource = sharedPref.getString( "priority_norm:sound_source", PriorityNorm.SoundSource);
|
||||||
|
PriorityNorm.LEDColor = sharedPref.getInt( "priority_norm:led_color", PriorityNorm.LEDColor);
|
||||||
|
PriorityNorm.ForceVolume = sharedPref.getBoolean("priority_norm:force_volume", PriorityNorm.ForceVolume);
|
||||||
|
PriorityNorm.ForceVolumeValue = sharedPref.getInt( "priority_norm:force_volume_value", PriorityNorm.ForceVolumeValue);
|
||||||
|
|
||||||
|
PriorityHigh.EnableLED = sharedPref.getBoolean("priority_high:enabled_led", PriorityHigh.EnableLED);
|
||||||
|
PriorityHigh.EnableSound = sharedPref.getBoolean("priority_high:enabled_sound", PriorityHigh.EnableSound);
|
||||||
|
PriorityHigh.EnableVibration = sharedPref.getBoolean("priority_high:enabled_vibration", PriorityHigh.EnableVibration);
|
||||||
|
PriorityHigh.RepeatSound = sharedPref.getBoolean("priority_high:repeat_sound", PriorityHigh.RepeatSound);
|
||||||
|
PriorityHigh.SoundName = sharedPref.getString( "priority_high:sound_name", PriorityHigh.SoundName);
|
||||||
|
PriorityHigh.SoundSource = sharedPref.getString( "priority_high:sound_source", PriorityHigh.SoundSource);
|
||||||
|
PriorityHigh.LEDColor = sharedPref.getInt( "priority_high:led_color", PriorityHigh.LEDColor);
|
||||||
|
PriorityHigh.ForceVolume = sharedPref.getBoolean("priority_high:force_volume", PriorityHigh.ForceVolume);
|
||||||
|
PriorityHigh.ForceVolumeValue = sharedPref.getInt( "priority_high:force_volume_value", PriorityHigh.ForceVolumeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save()
|
||||||
|
{
|
||||||
|
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("Config", Context.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor e = sharedPref.edit();
|
||||||
|
|
||||||
|
e.putInt( "quota_curr", quota_curr);
|
||||||
|
e.putInt( "quota_max", quota_max);
|
||||||
|
e.putInt( "user_id", user_id);
|
||||||
|
e.putString( "user_key", user_key);
|
||||||
|
e.putString( "fcm_token_local", fcm_token_local);
|
||||||
|
e.putString( "fcm_token_server", fcm_token_server);
|
||||||
|
e.putBoolean("promode_local", promode_local);
|
||||||
|
e.putBoolean("promode_server", promode_server);
|
||||||
|
e.putString( "promode_token", promode_token);
|
||||||
|
|
||||||
|
e.putBoolean("app_enabled", Enabled);
|
||||||
|
e.putInt( "local_cache_size", LocalCacheSize);
|
||||||
|
e.putBoolean("do_del_swipe", EnableDeleteSwipe);
|
||||||
|
e.putInt( "preview_line_count", PreviewLineCount);
|
||||||
|
|
||||||
|
e.putBoolean("priority_low:enabled_led", PriorityLow.EnableLED);
|
||||||
|
e.putBoolean("priority_low:enabled_sound", PriorityLow.EnableSound);
|
||||||
|
e.putBoolean("priority_low:enabled_vibration", PriorityLow.EnableVibration);
|
||||||
|
e.putBoolean("priority_low:repeat_sound", PriorityLow.RepeatSound);
|
||||||
|
e.putString( "priority_low:sound_name", PriorityLow.SoundName);
|
||||||
|
e.putString( "priority_low:sound_source", PriorityLow.SoundSource);
|
||||||
|
e.putInt( "priority_low:led_color", PriorityLow.LEDColor);
|
||||||
|
e.putBoolean("priority_low:force_volume", PriorityLow.ForceVolume);
|
||||||
|
e.putInt( "priority_low:force_volume_value", PriorityLow.ForceVolumeValue);
|
||||||
|
|
||||||
|
e.putBoolean("priority_norm:enabled_led", PriorityNorm.EnableLED);
|
||||||
|
e.putBoolean("priority_norm:enabled_sound", PriorityNorm.EnableSound);
|
||||||
|
e.putBoolean("priority_norm:enabled_vibration", PriorityNorm.EnableVibration);
|
||||||
|
e.putBoolean("priority_norm:repeat_sound", PriorityNorm.RepeatSound);
|
||||||
|
e.putString( "priority_norm:sound_name", PriorityNorm.SoundName);
|
||||||
|
e.putString( "priority_norm:sound_source", PriorityNorm.SoundSource);
|
||||||
|
e.putInt( "priority_norm:led_color", PriorityNorm.LEDColor);
|
||||||
|
e.putBoolean("priority_norm:force_volume", PriorityNorm.ForceVolume);
|
||||||
|
e.putInt( "priority_norm:force_volume_value", PriorityNorm.ForceVolumeValue);
|
||||||
|
|
||||||
|
e.putBoolean("priority_high:enabled_led", PriorityHigh.EnableLED);
|
||||||
|
e.putBoolean("priority_high:enabled_sound", PriorityHigh.EnableSound);
|
||||||
|
e.putBoolean("priority_high:enabled_vibration", PriorityHigh.EnableVibration);
|
||||||
|
e.putBoolean("priority_high:repeat_sound", PriorityHigh.RepeatSound);
|
||||||
|
e.putString( "priority_high:sound_name", PriorityHigh.SoundName);
|
||||||
|
e.putString( "priority_high:sound_source", PriorityHigh.SoundSource);
|
||||||
|
e.putInt( "priority_high:led_color", PriorityHigh.LEDColor);
|
||||||
|
e.putBoolean("priority_high:force_volume", PriorityHigh.ForceVolume);
|
||||||
|
e.putInt( "priority_high:force_volume_value", PriorityHigh.ForceVolumeValue);
|
||||||
|
|
||||||
|
e.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected()
|
||||||
|
{
|
||||||
|
return user_id>=0 && user_key != null && !user_key.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String createOnlineURL(boolean longurl)
|
||||||
|
{
|
||||||
|
String base = longurl ? ServerCommunication.PAGE_URL_LONG : ServerCommunication.PAGE_URL_SHORT;
|
||||||
|
|
||||||
|
if (!isConnected()) return base;
|
||||||
|
return base + "index.php?preset_user_id="+user_id+"&preset_user_key="+user_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServerToken(String token, View loader, boolean force)
|
||||||
|
{
|
||||||
|
if (isConnected())
|
||||||
|
{
|
||||||
|
fcm_token_local = token;
|
||||||
|
save();
|
||||||
|
if (!fcm_token_local.equals(fcm_token_server) || force) ServerCommunication.updateFCMToken(user_id, user_key, fcm_token_local, loader);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fcm_token_local = token;
|
||||||
|
save();
|
||||||
|
ServerCommunication.register(fcm_token_local, loader, promode_local, promode_token);
|
||||||
|
updateProState(loader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// called at app start
|
||||||
|
public void work(Activity a, boolean force)
|
||||||
|
{
|
||||||
|
FirebaseInstallations.getInstance().getId().addOnSuccessListener(a, newToken ->
|
||||||
|
{
|
||||||
|
Log.d("FB::GetInstanceId", newToken);
|
||||||
|
SCNSettings.inst().setServerToken(newToken, null, force);
|
||||||
|
}).addOnCompleteListener(r ->
|
||||||
|
{
|
||||||
|
if (isConnected()) ServerCommunication.info(user_id, user_key, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
updateProState(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset account key
|
||||||
|
public void reset(View loader)
|
||||||
|
{
|
||||||
|
if (!isConnected()) return;
|
||||||
|
|
||||||
|
ServerCommunication.resetSecret(user_id, user_key, loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// refresh account data
|
||||||
|
public void refresh(View loader, Activity a)
|
||||||
|
{
|
||||||
|
if (isConnected())
|
||||||
|
{
|
||||||
|
ServerCommunication.info(user_id, user_key, loader);
|
||||||
|
|
||||||
|
if (promode_server != promode_local) updateProState(loader);
|
||||||
|
|
||||||
|
if (!Str.equals(fcm_token_local, fcm_token_server)) work(a, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// get token then register
|
||||||
|
FirebaseInstallations.getInstance().getId().addOnSuccessListener(a, newToken ->
|
||||||
|
{
|
||||||
|
Log.d("FB::GetInstanceId", newToken);
|
||||||
|
SCNSettings.inst().setServerToken(newToken, loader, false); // does register in here
|
||||||
|
}).addOnCompleteListener(r ->
|
||||||
|
{
|
||||||
|
if (isConnected()) ServerCommunication.info(user_id, user_key, null); // info again for safety
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateProState(View loader)
|
||||||
|
{
|
||||||
|
Tuple3<Boolean, Boolean, String> state = IABService.inst().getPurchaseCachedExtended(IABService.IAB_PRO_MODE);
|
||||||
|
if (!state.Item2) return; // not initialized
|
||||||
|
|
||||||
|
boolean promode_real = state.Item1;
|
||||||
|
|
||||||
|
if (promode_real != promode_local || promode_real != promode_server)
|
||||||
|
{
|
||||||
|
promode_local = promode_real;
|
||||||
|
promode_token = promode_real ? state.Item3 : "";
|
||||||
|
save();
|
||||||
|
|
||||||
|
updateProStateOnServer(loader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateProStateOnServer(View loader)
|
||||||
|
{
|
||||||
|
if (!isConnected()) return;
|
||||||
|
|
||||||
|
ServerCommunication.upgrade(user_id, user_key, loader, promode_local, promode_token);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,686 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.model;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func5to0;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.FBMService;
|
||||||
|
|
||||||
|
import org.joda.time.Instant;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONTokener;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.Callback;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
public class ServerCommunication
|
||||||
|
{
|
||||||
|
public static final String PAGE_URL_LONG = "https://simplecloudnotifier.blackforestbytes.com/";
|
||||||
|
public static final String PAGE_URL_SHORT = "https://scn.blackforestbytes.com/";
|
||||||
|
public static final String BASE_URL = "https://scn.blackforestbytes.com/api/";
|
||||||
|
|
||||||
|
private static final OkHttpClient client = new OkHttpClient();
|
||||||
|
|
||||||
|
private ServerCommunication(){ throw new Error("no."); }
|
||||||
|
|
||||||
|
public static void register(String token, View loader, boolean pro, String pro_token)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(BASE_URL + "register.php?fcm_token=" + token + "&pro=" + pro + "&pro_token=" + URLEncoder.encode(pro_token, "utf-8"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(new Callback()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e)
|
||||||
|
{
|
||||||
|
handleError("register", call, null, Str.Empty, true, e);
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response)
|
||||||
|
{
|
||||||
|
String r = Str.Empty;
|
||||||
|
try (ResponseBody responseBody = response.body())
|
||||||
|
{
|
||||||
|
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
|
||||||
|
if (responseBody == null) throw new IOException("No response");
|
||||||
|
|
||||||
|
r = responseBody.string();
|
||||||
|
Log.d("Server::Response", request.url().toString()+"\n"+r);
|
||||||
|
|
||||||
|
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||||
|
|
||||||
|
if (!json_bool(json, "success"))
|
||||||
|
{
|
||||||
|
SCNApp.showToast(json_str(json, "message"), 4000);
|
||||||
|
handleNonSuccess("register", call, response, r);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCNSettings.inst().user_id = json_int(json, "user_id");
|
||||||
|
SCNSettings.inst().user_key = json_str(json, "user_key");
|
||||||
|
SCNSettings.inst().fcm_token_server = token;
|
||||||
|
SCNSettings.inst().quota_curr = json_int(json, "quota");
|
||||||
|
SCNSettings.inst().quota_max = json_int(json, "quota_max");
|
||||||
|
SCNSettings.inst().promode_server = json_bool(json, "is_pro");
|
||||||
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
|
SCNApp.refreshAccountTab();
|
||||||
|
|
||||||
|
handleSuccess("register", call, response, r);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
handleError("register", call, response, r, false, e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
handleError("register", null, null, Str.Empty, false, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateFCMToken(int id, String key, String token, View loader)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(BASE_URL + "update.php?user_id="+id+"&user_key="+key+"&fcm_token="+token)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(new Callback()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e)
|
||||||
|
{
|
||||||
|
handleError("update<1>", call, null, Str.Empty, true, e);
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response)
|
||||||
|
{
|
||||||
|
String r = Str.Empty;
|
||||||
|
try (ResponseBody responseBody = response.body())
|
||||||
|
{
|
||||||
|
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
|
||||||
|
if (responseBody == null) throw new IOException("No response");
|
||||||
|
|
||||||
|
r = responseBody.string();
|
||||||
|
Log.d("Server::Response", request.url().toString()+"\n"+r);
|
||||||
|
|
||||||
|
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||||
|
|
||||||
|
if (!json_bool(json, "success"))
|
||||||
|
{
|
||||||
|
SCNApp.showToast(json_str(json, "message"), 4000);
|
||||||
|
handleNonSuccess("update<1>", call, response, r);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCNSettings.inst().user_id = json_int(json, "user_id");
|
||||||
|
SCNSettings.inst().user_key = json_str(json, "user_key");
|
||||||
|
SCNSettings.inst().fcm_token_server = token;
|
||||||
|
SCNSettings.inst().quota_curr = json_int(json, "quota");
|
||||||
|
SCNSettings.inst().quota_max = json_int(json, "quota_max");
|
||||||
|
SCNSettings.inst().promode_server = json_bool(json, "is_pro");
|
||||||
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
|
SCNApp.refreshAccountTab();
|
||||||
|
|
||||||
|
handleSuccess("update<1>", call, response, r);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
handleError("update<1>", call, response, r, false, e);
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
handleError("update<1>", null, null, Str.Empty, false, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void resetSecret(int id, String key, View loader)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(BASE_URL + "update.php?user_id=" + id + "&user_key=" + key)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e)
|
||||||
|
{
|
||||||
|
handleError("update<1>", call, null, Str.Empty, true, e);
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response)
|
||||||
|
{
|
||||||
|
String r = Str.Empty;
|
||||||
|
try (ResponseBody responseBody = response.body())
|
||||||
|
{
|
||||||
|
if (!response.isSuccessful())
|
||||||
|
throw new IOException("Unexpected code " + response);
|
||||||
|
if (responseBody == null) throw new IOException("No response");
|
||||||
|
|
||||||
|
r = responseBody.string();
|
||||||
|
Log.d("Server::Response", request.url().toString()+"\n"+r);
|
||||||
|
|
||||||
|
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||||
|
|
||||||
|
if (!json_bool(json, "success")) {
|
||||||
|
SCNApp.showToast(json_str(json, "message"), 4000);
|
||||||
|
handleNonSuccess("update<2>", call, response, r);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCNSettings.inst().user_id = json_int(json, "user_id");
|
||||||
|
SCNSettings.inst().user_key = json_str(json, "user_key");
|
||||||
|
SCNSettings.inst().quota_curr = json_int(json, "quota");
|
||||||
|
SCNSettings.inst().quota_max = json_int(json, "quota_max");
|
||||||
|
SCNSettings.inst().promode_server = json_bool(json, "is_pro");
|
||||||
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
|
SCNApp.refreshAccountTab();
|
||||||
|
|
||||||
|
handleSuccess("update<2>", call, response, r);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
handleError("update<2>", call, response, r, false, e);
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SCNApp.runOnUiThread(() -> {
|
||||||
|
if (loader != null) loader.setVisibility(View.GONE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
handleError("update<2>", null, null, Str.Empty, false, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void info(int id, String key, View loader)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(BASE_URL + "info.php?user_id=" + id + "&user_key=" + key)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e) {
|
||||||
|
handleError("info", call, null, Str.Empty, true, e);
|
||||||
|
SCNApp.runOnUiThread(() -> {
|
||||||
|
if (loader != null) loader.setVisibility(View.GONE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response)
|
||||||
|
{
|
||||||
|
String r = Str.Empty;
|
||||||
|
try (ResponseBody responseBody = response.body())
|
||||||
|
{
|
||||||
|
if (!response.isSuccessful())
|
||||||
|
throw new IOException("Unexpected code " + response);
|
||||||
|
if (responseBody == null) throw new IOException("No response");
|
||||||
|
|
||||||
|
r = responseBody.string();
|
||||||
|
Log.d("Server::Response", request.url().toString()+"\n"+r);
|
||||||
|
|
||||||
|
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||||
|
|
||||||
|
if (!json_bool(json, "success"))
|
||||||
|
{
|
||||||
|
SCNApp.showToast(json_str(json, "message"), 4000);
|
||||||
|
handleNonSuccess("info", call, response, r);
|
||||||
|
|
||||||
|
int errid = json.optInt("errid", 0);
|
||||||
|
|
||||||
|
if (errid == 201 || errid == 202 || errid == 203 || errid == 204)
|
||||||
|
{
|
||||||
|
// user not found or auth failed
|
||||||
|
|
||||||
|
SCNSettings.inst().user_id = -1;
|
||||||
|
SCNSettings.inst().user_key = "";
|
||||||
|
SCNSettings.inst().quota_curr = 0;
|
||||||
|
SCNSettings.inst().quota_max = 0;
|
||||||
|
SCNSettings.inst().promode_server = false;
|
||||||
|
SCNSettings.inst().fcm_token_server = "";
|
||||||
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
|
SCNApp.refreshAccountTab();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCNSettings.inst().user_id = json_int(json, "user_id");
|
||||||
|
SCNSettings.inst().quota_curr = json_int(json, "quota");
|
||||||
|
SCNSettings.inst().quota_max = json_int(json, "quota_max");
|
||||||
|
SCNSettings.inst().promode_server = json_bool(json, "is_pro");
|
||||||
|
if (!json_bool(json, "fcm_token_set")) SCNSettings.inst().fcm_token_server = "";
|
||||||
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
|
SCNApp.refreshAccountTab();
|
||||||
|
|
||||||
|
if (json_int(json, "unack_count")>0) ServerCommunication.requery(id, key, loader);
|
||||||
|
|
||||||
|
handleSuccess("info", call, response, r);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
handleError("info", call, response, r, false, e);
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
handleError("info", null, null, Str.Empty, false, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void requery(int id, String key, View loader)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(BASE_URL + "requery.php?user_id=" + id + "&user_key=" + key)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e) {
|
||||||
|
handleError("requery", call, null, Str.Empty, true, e);
|
||||||
|
SCNApp.runOnUiThread(() -> {
|
||||||
|
if (loader != null) loader.setVisibility(View.GONE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response)
|
||||||
|
{
|
||||||
|
String r = Str.Empty;
|
||||||
|
try (ResponseBody responseBody = response.body())
|
||||||
|
{
|
||||||
|
if (!response.isSuccessful())
|
||||||
|
throw new IOException("Unexpected code " + response);
|
||||||
|
if (responseBody == null) throw new IOException("No response");
|
||||||
|
|
||||||
|
r = responseBody.string();
|
||||||
|
Log.d("Server::Response", request.url().toString()+"\n"+r);
|
||||||
|
|
||||||
|
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||||
|
|
||||||
|
if (!json_bool(json, "success"))
|
||||||
|
{
|
||||||
|
SCNApp.showToast(json_str(json, "message"), 4000);
|
||||||
|
handleNonSuccess("requery", call, response, r);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = json_int(json, "count");
|
||||||
|
JSONArray arr = json.getJSONArray("data");
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
JSONObject o = arr.getJSONObject(i);
|
||||||
|
|
||||||
|
long time = json_lng(o, "timestamp");
|
||||||
|
String title = json_str(o, "title");
|
||||||
|
String content = json_str(o, "body");
|
||||||
|
PriorityEnum prio = PriorityEnum.parseAPI(json_int(o, "priority"));
|
||||||
|
long scn_id = json_lng(o, "scn_msg_id");
|
||||||
|
|
||||||
|
FBMService.recieveData(time, title, content, prio, scn_id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSuccess("requery", call, response, r);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
handleError("requery", call, response, r, false, e);
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SCNApp.runOnUiThread(() -> {
|
||||||
|
if (loader != null) loader.setVisibility(View.GONE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
handleError("requery", null, null, Str.Empty, false, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void upgrade(int id, String key, View loader, boolean pro, String pro_token)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(BASE_URL + "upgrade.php?user_id=" + id + "&user_key=" + key + "&pro=" + pro + "&pro_token=" + URLEncoder.encode(pro_token, "utf-8"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(new Callback()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e)
|
||||||
|
{
|
||||||
|
handleError("upgrade", call, null, Str.Empty, true, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response)
|
||||||
|
{
|
||||||
|
String r = Str.Empty;
|
||||||
|
try (ResponseBody responseBody = response.body())
|
||||||
|
{
|
||||||
|
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
|
||||||
|
if (responseBody == null) throw new IOException("No response");
|
||||||
|
|
||||||
|
r = responseBody.string();
|
||||||
|
Log.d("Server::Response", request.url().toString()+"\n"+r);
|
||||||
|
|
||||||
|
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||||
|
|
||||||
|
if (!json_bool(json, "success")) {
|
||||||
|
SCNApp.showToast(json_str(json, "message"), 4000);
|
||||||
|
handleNonSuccess("upgrade", call, response, r);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCNSettings.inst().user_id = json_int(json, "user_id");
|
||||||
|
SCNSettings.inst().quota_curr = json_int(json, "quota");
|
||||||
|
SCNSettings.inst().quota_max = json_int(json, "quota_max");
|
||||||
|
SCNSettings.inst().promode_server = json_bool(json, "is_pro");
|
||||||
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
|
SCNApp.refreshAccountTab();
|
||||||
|
|
||||||
|
handleSuccess("upgrade", call, response, r);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
handleError("upgrade", call, response, r, false, e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
handleError("upgrade", null, null, Str.Empty, false, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ack(int id, String key, long msg_scn_id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(BASE_URL + "ack.php?user_id=" + id + "&user_key=" + key + "&scn_msg_id=" + msg_scn_id)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(new Callback()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e)
|
||||||
|
{
|
||||||
|
handleError("ack", call, null, Str.Empty, true, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response)
|
||||||
|
{
|
||||||
|
String r = Str.Empty;
|
||||||
|
try (ResponseBody responseBody = response.body())
|
||||||
|
{
|
||||||
|
if (!response.isSuccessful())
|
||||||
|
throw new IOException("Unexpected code " + response);
|
||||||
|
if (responseBody == null) throw new IOException("No response");
|
||||||
|
|
||||||
|
r = responseBody.string();
|
||||||
|
Log.d("Server::Response", request.url().toString()+"\n"+r);
|
||||||
|
|
||||||
|
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||||
|
|
||||||
|
if (!json_bool(json, "success"))
|
||||||
|
{
|
||||||
|
SCNApp.showToast(json_str(json, "message"), 4000);
|
||||||
|
handleNonSuccess("ack", call, response, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSuccess("ack", call, response, r);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
handleError("ack", call, response, r, false, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
handleError("ack", null, null, Str.Empty, false, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void expand(int id, String key, long scn_msg_id, View loader, Func5to0<String, String, PriorityEnum, Long, Long> okResult)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(BASE_URL + "expand.php?user_id=" + id + "&user_key=" + key + "&scn_msg_id=" + scn_msg_id)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e) {
|
||||||
|
handleError("expand", call, null, Str.Empty, true, e);
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response)
|
||||||
|
{
|
||||||
|
String r = Str.Empty;
|
||||||
|
try (ResponseBody responseBody = response.body())
|
||||||
|
{
|
||||||
|
if (!response.isSuccessful())
|
||||||
|
throw new IOException("Unexpected code " + response);
|
||||||
|
if (responseBody == null) throw new IOException("No response");
|
||||||
|
|
||||||
|
r = responseBody.string();
|
||||||
|
Log.d("Server::Response", request.url().toString()+"\n"+r);
|
||||||
|
|
||||||
|
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||||
|
|
||||||
|
if (!json_bool(json, "success"))
|
||||||
|
{
|
||||||
|
SCNApp.showToast(json_str(json, "message"), 4000);
|
||||||
|
handleNonSuccess("expand", call, response, r);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject o = json.getJSONObject("data");
|
||||||
|
|
||||||
|
long time = json_lng(o, "timestamp");
|
||||||
|
String title = json_str(o, "title");
|
||||||
|
String content = json_str(o, "body");
|
||||||
|
PriorityEnum prio = PriorityEnum.parseAPI(json_int(o, "priority"));
|
||||||
|
long scn_id = json_lng(o, "scn_msg_id");
|
||||||
|
|
||||||
|
okResult.invoke(title, content, prio, time, scn_id);
|
||||||
|
|
||||||
|
handleSuccess("expand", call, response, r);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
handleError("expand", call, response, r, false, e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
handleError("expand", null, null, Str.Empty, false, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean json_bool(JSONObject o, String key) throws JSONException
|
||||||
|
{
|
||||||
|
Object v = o.get(key);
|
||||||
|
if (v instanceof Integer) return ((int)v) != 0;
|
||||||
|
if (v instanceof Boolean) return ((boolean)v);
|
||||||
|
if (v instanceof String) return !Str.equals(((String)v), "0") && !Str.equals(((String)v), "false");
|
||||||
|
|
||||||
|
return o.getBoolean(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int json_int(JSONObject o, String key) throws JSONException
|
||||||
|
{
|
||||||
|
return o.getInt(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long json_lng(JSONObject o, String key) throws JSONException
|
||||||
|
{
|
||||||
|
return o.getLong(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String json_str(JSONObject o, String key) throws JSONException
|
||||||
|
{
|
||||||
|
return o.getString(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleSuccess(String source, Call call, Response resp, String respBody)
|
||||||
|
{
|
||||||
|
Log.d("SC:"+source, respBody);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Instant i = Instant.now();
|
||||||
|
String s = source;
|
||||||
|
String u = call.request().url().toString();
|
||||||
|
int rc = resp.code();
|
||||||
|
String r = respBody;
|
||||||
|
LogLevel l = LogLevel.INFO;
|
||||||
|
|
||||||
|
SingleQuery q = new SingleQuery(l, i, s, u, r, rc, "SUCCESS");
|
||||||
|
QueryLog.inst().add(q);
|
||||||
|
}
|
||||||
|
catch (Exception e2)
|
||||||
|
{
|
||||||
|
Log.e("SC:HandleSuccess", e2.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleNonSuccess(String source, Call call, Response resp, String respBody)
|
||||||
|
{
|
||||||
|
Log.d("SC:"+source, respBody);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Instant i = Instant.now();
|
||||||
|
String s = source;
|
||||||
|
String u = call.request().url().toString();
|
||||||
|
int rc = resp.code();
|
||||||
|
String r = respBody;
|
||||||
|
LogLevel l = LogLevel.WARN;
|
||||||
|
|
||||||
|
SingleQuery q = new SingleQuery(l, i, s, u, r, rc, "NON-SUCCESS");
|
||||||
|
QueryLog.inst().add(q);
|
||||||
|
}
|
||||||
|
catch (Exception e2)
|
||||||
|
{
|
||||||
|
Log.e("SC:HandleSuccess", e2.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleError(String source, Call call, Response resp, String respBody, boolean isio, Exception e)
|
||||||
|
{
|
||||||
|
Log.e("SC:"+source, e.toString());
|
||||||
|
|
||||||
|
if (isio)
|
||||||
|
{
|
||||||
|
SCNApp.showToast("Can't connect to server", 3000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Instant i = Instant.now();
|
||||||
|
String s = source;
|
||||||
|
String u = (call==null)?Str.Empty:call.request().url().toString();
|
||||||
|
int rc = (resp==null)?-1:resp.code();
|
||||||
|
String r = respBody;
|
||||||
|
LogLevel l = isio?LogLevel.WARN:LogLevel.ERROR;
|
||||||
|
|
||||||
|
SingleQuery q = new SingleQuery(l, i, s, u, r, rc, e.toString());
|
||||||
|
QueryLog.inst().add(q);
|
||||||
|
}
|
||||||
|
catch (Exception e2)
|
||||||
|
{
|
||||||
|
Log.e("SC:HandleError", e2.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.model;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.BaseBundle;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||||
|
|
||||||
|
import org.joda.time.Instant;
|
||||||
|
|
||||||
|
public class SingleQuery
|
||||||
|
{
|
||||||
|
public final Instant Timestamp;
|
||||||
|
|
||||||
|
public final LogLevel Level;
|
||||||
|
public final String Name;
|
||||||
|
public final String URL;
|
||||||
|
public final String Response;
|
||||||
|
public final int ResponseCode;
|
||||||
|
public final String ExceptionString;
|
||||||
|
|
||||||
|
public SingleQuery(LogLevel l, Instant i, String n, String u, String r, int rc, String e)
|
||||||
|
{
|
||||||
|
Level=l;
|
||||||
|
Timestamp=i;
|
||||||
|
Name=n;
|
||||||
|
URL=u;
|
||||||
|
Response=r;
|
||||||
|
ResponseCode=rc;
|
||||||
|
ExceptionString=e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(SharedPreferences.Editor e, String base)
|
||||||
|
{
|
||||||
|
e.putInt(base+".Level", Level.asInt());
|
||||||
|
e.putLong(base+".Timestamp", Timestamp.getMillis());
|
||||||
|
e.putString(base+".Name", Name);
|
||||||
|
e.putString(base+".URL", URL);
|
||||||
|
e.putString(base+".Response", Response);
|
||||||
|
e.putInt(base+".ResponseCode", ResponseCode);
|
||||||
|
e.putString(base+".ExceptionString", ExceptionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(BaseBundle e, String base)
|
||||||
|
{
|
||||||
|
e.putInt(base+".Level", Level.asInt());
|
||||||
|
e.putLong(base+".Timestamp", Timestamp.getMillis());
|
||||||
|
e.putString(base+".Name", Name);
|
||||||
|
e.putString(base+".URL", URL);
|
||||||
|
e.putString(base+".Response", Response);
|
||||||
|
e.putInt(base+".ResponseCode", ResponseCode);
|
||||||
|
e.putString(base+".ExceptionString", ExceptionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SingleQuery load(SharedPreferences e, String base)
|
||||||
|
{
|
||||||
|
return new SingleQuery
|
||||||
|
(
|
||||||
|
LogLevel.fromInt(e.getInt(base+".Level", 0)),
|
||||||
|
new Instant(e.getLong(base+".Timestamp", 0)),
|
||||||
|
e.getString(base+".Name", Str.Empty),
|
||||||
|
e.getString(base+".URL", Str.Empty),
|
||||||
|
e.getString(base+".Response", Str.Empty),
|
||||||
|
e.getInt(base+".ResponseCode", -1),
|
||||||
|
e.getString(base+".ExceptionString", Str.Empty)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SingleQuery load(BaseBundle e, String base)
|
||||||
|
{
|
||||||
|
return new SingleQuery
|
||||||
|
(
|
||||||
|
LogLevel.fromInt(e.getInt(base+".Level", 0)),
|
||||||
|
new Instant(e.getLong(base+".Timestamp", 0)),
|
||||||
|
e.getString(base+".Name", Str.Empty),
|
||||||
|
e.getString(base+".URL", Str.Empty),
|
||||||
|
e.getString(base+".Response", Str.Empty),
|
||||||
|
e.getInt(base+".ResponseCode", -1),
|
||||||
|
e.getString(base+".ExceptionString", Str.Empty)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.service;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
|
||||||
|
|
||||||
|
public class BroadcastReceiverService extends BroadcastReceiver
|
||||||
|
{
|
||||||
|
public static final int NOTIF_SHOW_MAIN = 10021;
|
||||||
|
public static final int NOTIF_STOP_SOUND = 10022;
|
||||||
|
public static final String ID_KEY = "com.blackforestbytes.simplecloudnotifier.BroadcastID";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent)
|
||||||
|
{
|
||||||
|
if (intent == null) return;
|
||||||
|
Bundle extras = intent.getExtras();
|
||||||
|
if (extras == null) return;
|
||||||
|
int notificationId = extras.getInt(ID_KEY, 0);
|
||||||
|
|
||||||
|
if (notificationId == 0) return;
|
||||||
|
else if (notificationId == NOTIF_SHOW_MAIN) showMain(context);
|
||||||
|
else if (notificationId == NOTIF_STOP_SOUND) stopNotificationSound();
|
||||||
|
else return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopNotificationSound()
|
||||||
|
{
|
||||||
|
SoundService.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showMain(Context ctxt)
|
||||||
|
{
|
||||||
|
SoundService.stop();
|
||||||
|
|
||||||
|
Intent intent = new Intent(ctxt, MainActivity.class);
|
||||||
|
ctxt.startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.service;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.CMessage;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.LogLevel;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.PriorityEnum;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.ServerCommunication;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SingleQuery;
|
||||||
|
import com.google.firebase.messaging.FirebaseMessagingService;
|
||||||
|
import com.google.firebase.messaging.RemoteMessage;
|
||||||
|
|
||||||
|
import org.joda.time.Instant;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
public class FBMService extends FirebaseMessagingService
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onNewToken(String token)
|
||||||
|
{
|
||||||
|
Log.i("Firebase::NewToken", token);
|
||||||
|
SCNSettings.inst().setServerToken(token, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessageReceived(RemoteMessage remoteMessage)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!SCNSettings.inst().Enabled) return;
|
||||||
|
|
||||||
|
Log.i("FB::MessageReceived", "From: " + remoteMessage.getFrom());
|
||||||
|
Log.i("FB::MessageReceived", "Payload: " + remoteMessage.getData());
|
||||||
|
if (remoteMessage.getNotification() != null) Log.i("FB::MessageReceived", "Notify_Title: " + remoteMessage.getNotification().getTitle());
|
||||||
|
if (remoteMessage.getNotification() != null) Log.i("FB::MessageReceived", "Notify_Body: " + remoteMessage.getNotification().getBody());
|
||||||
|
|
||||||
|
long time = Long.parseLong(remoteMessage.getData().get("timestamp"));
|
||||||
|
String title = remoteMessage.getData().get("title");
|
||||||
|
String content = remoteMessage.getData().get("body");
|
||||||
|
PriorityEnum prio = PriorityEnum.parseAPI(remoteMessage.getData().get("priority"));
|
||||||
|
long scn_id = Long.parseLong(remoteMessage.getData().get("scn_msg_id"));
|
||||||
|
boolean trimmed = Boolean.parseBoolean(remoteMessage.getData().get("trimmed"));
|
||||||
|
|
||||||
|
|
||||||
|
SingleQuery q = new SingleQuery(LogLevel.INFO, Instant.now(), "FBM<recieve>", Str.Empty, new JSONObject(remoteMessage.getData()).toString(), 0, "SUCCESS");
|
||||||
|
QueryLog.inst().add(q);
|
||||||
|
|
||||||
|
if (trimmed)
|
||||||
|
{
|
||||||
|
ServerCommunication.expand(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id, null, (i1, i2, i3, i4, i5) -> recieveData(i4, i1, i2, i3, i5, false));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
recieveData(time, title, content, prio, scn_id, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("FB:Err", e.toString());
|
||||||
|
SCNApp.showToast("Recieved invalid message from server", Toast.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void recieveData(long time, String title, String content, PriorityEnum prio, long scn_id, boolean alwaysAck)
|
||||||
|
{
|
||||||
|
if (CMessageList.inst().isAck(scn_id))
|
||||||
|
{
|
||||||
|
Log.w("FB::MessageReceived", "Recieved ack-ed message: " + scn_id);
|
||||||
|
if (alwaysAck) ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CMessage msg = CMessageList.inst().add(scn_id, time, title, content, prio);
|
||||||
|
|
||||||
|
if (SCNApp.isBackground())
|
||||||
|
{
|
||||||
|
NotificationService.inst().showBackground(msg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NotificationService.inst().showForeground(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,305 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.service;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.android.billingclient.api.BillingClient;
|
||||||
|
import com.android.billingclient.api.BillingClientStateListener;
|
||||||
|
import com.android.billingclient.api.BillingFlowParams;
|
||||||
|
import com.android.billingclient.api.BillingResult;
|
||||||
|
import com.android.billingclient.api.Purchase;
|
||||||
|
import com.android.billingclient.api.PurchasesUpdatedListener;
|
||||||
|
import com.android.billingclient.api.SkuDetails;
|
||||||
|
import com.android.billingclient.api.SkuDetailsParams;
|
||||||
|
import com.android.billingclient.api.SkuDetailsResponseListener;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple2;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple3;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func0to0;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Dictionary;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import static androidx.constraintlayout.widget.Constraints.TAG;
|
||||||
|
|
||||||
|
public class IABService implements PurchasesUpdatedListener
|
||||||
|
{
|
||||||
|
public static final String IAB_PRO_MODE = "scn.pro.tier1";
|
||||||
|
|
||||||
|
private final static Object _lock = new Object();
|
||||||
|
private static IABService _inst = null;
|
||||||
|
public static IABService inst()
|
||||||
|
{
|
||||||
|
synchronized (_lock)
|
||||||
|
{
|
||||||
|
if (_inst != null) return _inst;
|
||||||
|
throw new Error("IABService == null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static void startup(MainActivity a)
|
||||||
|
{
|
||||||
|
synchronized (_lock)
|
||||||
|
{
|
||||||
|
_inst = new IABService(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SimplePurchaseState { YES, NO, UNINITIALIZED }
|
||||||
|
|
||||||
|
private BillingClient client;
|
||||||
|
private boolean isServiceConnected;
|
||||||
|
private final List<Purchase> purchases = new ArrayList<>();
|
||||||
|
private boolean _isInitialized = false;
|
||||||
|
|
||||||
|
private final Map<String, Boolean> _localCache= new HashMap<>();
|
||||||
|
|
||||||
|
public IABService(Context c)
|
||||||
|
{
|
||||||
|
_isInitialized = false;
|
||||||
|
|
||||||
|
loadCache();
|
||||||
|
|
||||||
|
client = BillingClient
|
||||||
|
.newBuilder(c)
|
||||||
|
.setListener(this)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
startServiceConnection(this::queryPurchases, false);
|
||||||
|
startServiceConnection(this::querySkuDetails, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reloadPrefs()
|
||||||
|
{
|
||||||
|
loadCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadCache()
|
||||||
|
{
|
||||||
|
_localCache.clear();
|
||||||
|
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("IAB", Context.MODE_PRIVATE);
|
||||||
|
int count = sharedPref.getInt("c", 0);
|
||||||
|
for (int i=0; i < count; i++)
|
||||||
|
{
|
||||||
|
String k = sharedPref.getString("["+i+"]->key", null);
|
||||||
|
boolean v = sharedPref.getBoolean("["+i+"]->value", false);
|
||||||
|
if (k==null)continue;
|
||||||
|
_localCache.put(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveCache()
|
||||||
|
{
|
||||||
|
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("IAB", Context.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor editor= sharedPref.edit();
|
||||||
|
|
||||||
|
editor.putInt("c", _localCache.size());
|
||||||
|
int i = 0;
|
||||||
|
for (Map.Entry<String, Boolean> e : _localCache.entrySet())
|
||||||
|
{
|
||||||
|
editor.putString("["+i+"]->key", e.getKey());
|
||||||
|
editor.putBoolean("["+i+"]->value", e.getValue());
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
private synchronized void updateCache(String k, boolean v)
|
||||||
|
{
|
||||||
|
if (_localCache.containsKey(k) && _localCache.get(k)==v) return;
|
||||||
|
|
||||||
|
_localCache.put(k, v);
|
||||||
|
saveCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void queryPurchases()
|
||||||
|
{
|
||||||
|
Func0to0 queryToExecute = () ->
|
||||||
|
{
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
Purchase.PurchasesResult purchasesResult = client.queryPurchases(BillingClient.SkuType.INAPP);
|
||||||
|
Log.i(TAG, "Querying purchases elapsed time: " + (System.currentTimeMillis() - time) + "ms");
|
||||||
|
|
||||||
|
if (purchasesResult.getResponseCode() == BillingClient.BillingResponseCode.OK)
|
||||||
|
{
|
||||||
|
for (Purchase p : Objects.requireNonNull(purchasesResult.getPurchasesList()))
|
||||||
|
{
|
||||||
|
handlePurchase(p, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_isInitialized = true;
|
||||||
|
|
||||||
|
boolean newProMode = getPurchaseCachedSimple(IAB_PRO_MODE);
|
||||||
|
if (newProMode != SCNSettings.inst().promode_local)
|
||||||
|
{
|
||||||
|
refreshProModeListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.w(TAG, "queryPurchases() got an error response code: " + purchasesResult.getResponseCode());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
executeServiceRequest(queryToExecute, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void querySkuDetails() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void purchase(Activity a, String id)
|
||||||
|
{
|
||||||
|
Func0to0 queryRequest = () -> {
|
||||||
|
// Query the purchase async
|
||||||
|
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
|
||||||
|
params.setSkusList(Collections.singletonList(id)).setType(BillingClient.SkuType.INAPP);
|
||||||
|
client.querySkuDetailsAsync(params.build(), (billingResult, skuDetailsList) ->
|
||||||
|
{
|
||||||
|
if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK || skuDetailsList == null || skuDetailsList.size() != 1)
|
||||||
|
{
|
||||||
|
SCNApp.showToast("Could not find product", Toast.LENGTH_SHORT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
executeServiceRequest(() ->
|
||||||
|
{
|
||||||
|
BillingFlowParams flowParams = BillingFlowParams
|
||||||
|
.newBuilder()
|
||||||
|
.setSkuDetails(skuDetailsList.get(0))
|
||||||
|
.build();
|
||||||
|
client.launchBillingFlow(a, flowParams);
|
||||||
|
}, true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
executeServiceRequest(queryRequest, false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeServiceRequest(Func0to0 runnable, final boolean userRequest)
|
||||||
|
{
|
||||||
|
if (isServiceConnected)
|
||||||
|
{
|
||||||
|
runnable.invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If billing service was disconnected, we try to reconnect 1 time.
|
||||||
|
// (feel free to introduce your retry policy here).
|
||||||
|
startServiceConnection(runnable, userRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void destroy()
|
||||||
|
{
|
||||||
|
if (client != null && client.isReady()) {
|
||||||
|
client.endConnection();
|
||||||
|
client = null;
|
||||||
|
isServiceConnected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> purchases)
|
||||||
|
{
|
||||||
|
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null)
|
||||||
|
{
|
||||||
|
for (Purchase purchase : purchases)
|
||||||
|
{
|
||||||
|
handlePurchase(purchase, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED && purchases != null)
|
||||||
|
{
|
||||||
|
for (Purchase purchase : purchases)
|
||||||
|
{
|
||||||
|
handlePurchase(purchase, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePurchase(Purchase purchase, boolean triggerUpdate)
|
||||||
|
{
|
||||||
|
Log.d(TAG, "Got a verified purchase: " + purchase);
|
||||||
|
|
||||||
|
purchases.add(purchase);
|
||||||
|
|
||||||
|
if (triggerUpdate) refreshProModeListener();
|
||||||
|
|
||||||
|
updateCache(purchase.getSku(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshProModeListener()
|
||||||
|
{
|
||||||
|
MainActivity ma = SCNApp.getMainActivity();
|
||||||
|
if (ma != null) ma.adpTabs.tab3.updateProState();
|
||||||
|
if (ma != null) ma.adpTabs.tab1.updateProState();
|
||||||
|
SCNSettings.inst().updateProState(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startServiceConnection(final Func0to0 executeOnSuccess, final boolean userRequest)
|
||||||
|
{
|
||||||
|
client.startConnection(new BillingClientStateListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onBillingSetupFinished(@NonNull BillingResult billingResult)
|
||||||
|
{
|
||||||
|
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK)
|
||||||
|
{
|
||||||
|
isServiceConnected = true;
|
||||||
|
if (executeOnSuccess != null) executeOnSuccess.invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (userRequest) SCNApp.showToast("Could not connect to google services", Toast.LENGTH_SHORT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBillingServiceDisconnected() {
|
||||||
|
isServiceConnected = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getPurchaseCachedSimple(String id)
|
||||||
|
{
|
||||||
|
return getPurchaseCachedExtended(id).Item1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
public Tuple3<Boolean, Boolean, String> getPurchaseCachedExtended(String id)
|
||||||
|
{
|
||||||
|
// <state, initialized, token>
|
||||||
|
|
||||||
|
if (!_isInitialized)
|
||||||
|
{
|
||||||
|
if (_localCache.containsKey(id) && _localCache.get(id)) return new Tuple3<>(true, false, Str.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Purchase p : purchases)
|
||||||
|
{
|
||||||
|
if (Str.equals(p.getSku(), id))
|
||||||
|
{
|
||||||
|
updateCache(id, true);
|
||||||
|
return new Tuple3<>(true, true, p.getPurchaseToken());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCache(id, false);
|
||||||
|
return new Tuple3<>(false, true, Str.Empty);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,267 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.service;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.VibrationEffect;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.CMessage;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.NotificationSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.PriorityEnum;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
|
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
|
public class NotificationService
|
||||||
|
{
|
||||||
|
private final static String CHANNEL_P0_ID = "CHAN_BFB_SCN_MESSAGES_P0";
|
||||||
|
private final static String CHANNEL_P1_ID = "CHAN_BFB_SCN_MESSAGES_P1";
|
||||||
|
private final static String CHANNEL_P2_ID = "CHAN_BFB_SCN_MESSAGES_P2";
|
||||||
|
|
||||||
|
private final static Object _lock = new Object();
|
||||||
|
private static NotificationService _inst = null;
|
||||||
|
public static NotificationService inst()
|
||||||
|
{
|
||||||
|
synchronized (_lock)
|
||||||
|
{
|
||||||
|
if (_inst != null) return _inst;
|
||||||
|
return _inst = new NotificationService();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationService()
|
||||||
|
{
|
||||||
|
createChannels();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createChannels()
|
||||||
|
{
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
|
||||||
|
|
||||||
|
Context ctxt = SCNApp.getContext();
|
||||||
|
NotificationManager notifman = ctxt.getSystemService(NotificationManager.class);
|
||||||
|
if (notifman == null) return;
|
||||||
|
|
||||||
|
{
|
||||||
|
NotificationChannel channel0 = notifman.getNotificationChannel(CHANNEL_P0_ID);
|
||||||
|
if (channel0 == null)
|
||||||
|
{
|
||||||
|
channel0 = new NotificationChannel(CHANNEL_P0_ID, "Push notifications (low priority)", NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
|
channel0.setDescription("Push notifications from the server with low priority.\nGo to the in-app settings to configure ringtone, volume and vibrations");
|
||||||
|
channel0.setSound(null, null);
|
||||||
|
channel0.setVibrationPattern(null);
|
||||||
|
channel0.setLightColor(Color.CYAN);
|
||||||
|
channel0.enableLights(true);
|
||||||
|
notifman.createNotificationChannel(channel0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
NotificationChannel channel1 = notifman.getNotificationChannel(CHANNEL_P1_ID);
|
||||||
|
if (channel1 == null)
|
||||||
|
{
|
||||||
|
channel1 = new NotificationChannel(CHANNEL_P1_ID, "Push notifications (normal priority)", NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
|
channel1.setDescription("Push notifications from the server with low priority.\nGo to the in-app settings to configure ringtone, volume and vibrations");
|
||||||
|
channel1.setSound(null, null);
|
||||||
|
channel1.setVibrationPattern(null);
|
||||||
|
channel1.setLightColor(Color.CYAN);
|
||||||
|
channel1.enableLights(true);
|
||||||
|
notifman.createNotificationChannel(channel1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
NotificationChannel channel2 = notifman.getNotificationChannel(CHANNEL_P2_ID);
|
||||||
|
if (channel2 == null)
|
||||||
|
{
|
||||||
|
channel2 = new NotificationChannel(CHANNEL_P2_ID, "Push notifications (high priority)", NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
|
channel2.setDescription("Push notifications from the server with low priority.\nGo to the in-app settings to configure ringtone, volume and vibrations");
|
||||||
|
channel2.setSound(null, null);
|
||||||
|
channel2.setVibrationPattern(null);
|
||||||
|
channel2.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
|
||||||
|
channel2.setLightColor(Color.CYAN);
|
||||||
|
channel2.enableLights(true);
|
||||||
|
notifman.createNotificationChannel(channel2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showForeground(CMessage msg)
|
||||||
|
{
|
||||||
|
SCNApp.showToast("Message recieved: " + msg.Title, Toast.LENGTH_LONG);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
NotificationSettings ns = SCNSettings.inst().PriorityNorm;
|
||||||
|
switch (msg.Priority)
|
||||||
|
{
|
||||||
|
case LOW: ns = SCNSettings.inst().PriorityLow; break;
|
||||||
|
case NORMAL: ns = SCNSettings.inst().PriorityNorm; break;
|
||||||
|
case HIGH: ns = SCNSettings.inst().PriorityHigh; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SoundService.play(ns.EnableSound, ns.SoundSource, ns.ForceVolume, ns.ForceVolumeValue, false);
|
||||||
|
|
||||||
|
if (ns.EnableVibration)
|
||||||
|
{
|
||||||
|
Vibrator v = (Vibrator) SCNApp.getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
v.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE));
|
||||||
|
} else {
|
||||||
|
v.vibrate(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showBackground(CMessage msg)
|
||||||
|
{
|
||||||
|
Context ctxt = SCNApp.getContext();
|
||||||
|
|
||||||
|
NotificationSettings ns = SCNSettings.inst().PriorityNorm;
|
||||||
|
switch (msg.Priority)
|
||||||
|
{
|
||||||
|
case LOW: ns = SCNSettings.inst().PriorityLow; break;
|
||||||
|
case NORMAL: ns = SCNSettings.inst().PriorityNorm; break;
|
||||||
|
case HIGH: ns = SCNSettings.inst().PriorityHigh; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
|
||||||
|
{
|
||||||
|
// old
|
||||||
|
showBackground_old(msg, ctxt, ns, msg.Priority);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// new
|
||||||
|
showBackground_new(msg, ctxt, ns, msg.Priority);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getChannel(PriorityEnum p)
|
||||||
|
{
|
||||||
|
switch (p)
|
||||||
|
{
|
||||||
|
case LOW: return CHANNEL_P0_ID;
|
||||||
|
case NORMAL: return CHANNEL_P1_ID;
|
||||||
|
case HIGH: return CHANNEL_P2_ID;
|
||||||
|
|
||||||
|
default: return CHANNEL_P0_ID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showBackground_old(CMessage msg, Context ctxt, NotificationSettings ns, PriorityEnum prio)
|
||||||
|
{
|
||||||
|
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, getChannel(prio));
|
||||||
|
mBuilder.setSmallIcon(R.drawable.ic_notification_white);
|
||||||
|
mBuilder.setLargeIcon(BitmapFactory.decodeResource(ctxt.getResources(), R.mipmap.ic_notification_full));
|
||||||
|
mBuilder.setContentTitle(msg.Title);
|
||||||
|
mBuilder.setContentText(msg.Content);
|
||||||
|
mBuilder.setShowWhen(true);
|
||||||
|
mBuilder.setWhen(msg.Timestamp * 1000);
|
||||||
|
mBuilder.setAutoCancel(true);
|
||||||
|
mBuilder.setCategory(Notification.CATEGORY_MESSAGE);
|
||||||
|
mBuilder.setGroup("com.blackforestbytes.simplecloudnotifier.notifications.group."+prio.toString());
|
||||||
|
|
||||||
|
if (msg.Priority == PriorityEnum.LOW) mBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
|
||||||
|
if (msg.Priority == PriorityEnum.NORMAL) mBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT);
|
||||||
|
if (msg.Priority == PriorityEnum.HIGH) mBuilder.setPriority(NotificationCompat.PRIORITY_HIGH);
|
||||||
|
if (ns.EnableVibration) mBuilder.setVibrate(new long[]{500});
|
||||||
|
if (ns.EnableLED) mBuilder.setLights(ns.LEDColor, 500, 500);
|
||||||
|
|
||||||
|
if (ns.EnableSound && !ns.SoundSource.isEmpty() && !ns.RepeatSound) mBuilder.setSound(Uri.parse(ns.SoundSource), AudioManager.STREAM_NOTIFICATION);
|
||||||
|
|
||||||
|
Intent intent = new Intent(ctxt, MainActivity.class);
|
||||||
|
PendingIntent pi = PendingIntent.getActivity(ctxt, 0, intent, 0);
|
||||||
|
mBuilder.setContentIntent(pi);
|
||||||
|
NotificationManager mNotificationManager = (NotificationManager) ctxt.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
|
if (ns.EnableSound && !ns.SoundSource.isEmpty() && ns.RepeatSound)
|
||||||
|
{
|
||||||
|
Intent intnt_stop = new Intent(SCNApp.getContext(), BroadcastReceiverService.class);
|
||||||
|
intnt_stop.putExtra(BroadcastReceiverService.ID_KEY, BroadcastReceiverService.NOTIF_STOP_SOUND);
|
||||||
|
PendingIntent pi_stop = PendingIntent.getBroadcast(SCNApp.getContext().getApplicationContext(), BroadcastReceiverService.NOTIF_STOP_SOUND, intnt_stop, 0);
|
||||||
|
mBuilder.addAction(new NotificationCompat.Action(-1, "Stop", pi_stop));
|
||||||
|
mBuilder.setDeleteIntent(pi_stop);
|
||||||
|
|
||||||
|
SoundService.play(ns.EnableSound, ns.SoundSource, ns.ForceVolume, ns.ForceVolumeValue, ns.RepeatSound);
|
||||||
|
}
|
||||||
|
|
||||||
|
Notification n = mBuilder.build();
|
||||||
|
|
||||||
|
if (mNotificationManager != null) mNotificationManager.notify((int)msg.SCN_ID, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
|
private void showBackground_new(CMessage msg, Context ctxt, NotificationSettings ns, PriorityEnum prio)
|
||||||
|
{
|
||||||
|
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, getChannel(prio));
|
||||||
|
mBuilder.setSmallIcon(R.drawable.ic_notification_white);
|
||||||
|
mBuilder.setLargeIcon(BitmapFactory.decodeResource(ctxt.getResources(), R.mipmap.ic_notification_full));
|
||||||
|
mBuilder.setContentTitle(msg.Title);
|
||||||
|
mBuilder.setContentText(msg.Content);
|
||||||
|
mBuilder.setShowWhen(true);
|
||||||
|
mBuilder.setWhen(msg.Timestamp * 1000);
|
||||||
|
mBuilder.setAutoCancel(true);
|
||||||
|
mBuilder.setCategory(Notification.CATEGORY_MESSAGE);
|
||||||
|
mBuilder.setGroup("com.blackforestbytes.simplecloudnotifier.notifications.group."+prio.toString());
|
||||||
|
|
||||||
|
if (ns.EnableLED) mBuilder.setLights(ns.LEDColor, 500, 500);
|
||||||
|
|
||||||
|
if (msg.Priority == PriorityEnum.LOW) mBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
|
||||||
|
if (msg.Priority == PriorityEnum.NORMAL) mBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT);
|
||||||
|
if (msg.Priority == PriorityEnum.HIGH) mBuilder.setPriority(NotificationCompat.PRIORITY_HIGH);
|
||||||
|
|
||||||
|
Intent intent = new Intent(ctxt, MainActivity.class);
|
||||||
|
PendingIntent pi = PendingIntent.getActivity(ctxt, 0, intent, 0);
|
||||||
|
mBuilder.setContentIntent(pi);
|
||||||
|
|
||||||
|
NotificationManager mNotificationManager = (NotificationManager) ctxt.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
if (mNotificationManager == null) return;
|
||||||
|
|
||||||
|
if (ns.EnableSound && !Str.isNullOrWhitespace(ns.SoundSource))
|
||||||
|
{
|
||||||
|
if (ns.RepeatSound)
|
||||||
|
{
|
||||||
|
Intent intnt_stop = new Intent(SCNApp.getContext(), BroadcastReceiverService.class);
|
||||||
|
intnt_stop.putExtra(BroadcastReceiverService.ID_KEY, BroadcastReceiverService.NOTIF_STOP_SOUND);
|
||||||
|
PendingIntent pi_stop = PendingIntent.getBroadcast(ctxt, BroadcastReceiverService.NOTIF_STOP_SOUND, intnt_stop, 0);
|
||||||
|
mBuilder.addAction(new NotificationCompat.Action(-1, "Stop", pi_stop));
|
||||||
|
mBuilder.setDeleteIntent(pi_stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
SoundService.play(ns.EnableSound, ns.SoundSource, ns.ForceVolume, ns.ForceVolumeValue, ns.RepeatSound);
|
||||||
|
}
|
||||||
|
|
||||||
|
Notification n = mBuilder.build();
|
||||||
|
n.flags |= Notification.FLAG_AUTO_CANCEL;
|
||||||
|
|
||||||
|
mNotificationManager.notify((int)msg.SCN_ID, n);
|
||||||
|
|
||||||
|
if (ns.EnableVibration)
|
||||||
|
{
|
||||||
|
Vibrator v = (Vibrator) SCNApp.getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
||||||
|
v.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE));
|
||||||
|
}
|
||||||
|
|
||||||
|
//if (ns.EnableLED) { } // no LED in Android-O -- configure via Channel
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.service;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.media.AudioAttributes;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.media.MediaPlayer;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class SoundService
|
||||||
|
{
|
||||||
|
private static MediaPlayer mpLast = null;
|
||||||
|
|
||||||
|
public static void play(boolean enableSound, String soundSource, boolean forceVolume, int forceVolumeValue, boolean loop)
|
||||||
|
{
|
||||||
|
if (!enableSound) return;
|
||||||
|
if (Str.isNullOrWhitespace(soundSource)) return;
|
||||||
|
|
||||||
|
stop();
|
||||||
|
|
||||||
|
if (forceVolume)
|
||||||
|
{
|
||||||
|
AudioManager aman = (AudioManager) SCNApp.getContext().getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
int maxVolume = aman.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION);
|
||||||
|
aman.setStreamVolume(AudioManager.STREAM_NOTIFICATION, (int)(maxVolume * (forceVolumeValue / 100.0)), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MediaPlayer player = new MediaPlayer();
|
||||||
|
player.setAudioAttributes(new AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_NOTIFICATION).build());
|
||||||
|
player.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
|
||||||
|
player.setDataSource(SCNApp.getContext(), Uri.parse(soundSource));
|
||||||
|
player.setLooping(loop);
|
||||||
|
player.setOnCompletionListener( mp -> { mp.stop(); mp.release(); });
|
||||||
|
player.setOnSeekCompleteListener(mp -> { mp.stop(); mp.release(); });
|
||||||
|
player.prepare();
|
||||||
|
player.start();
|
||||||
|
mpLast = player;
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
Log.e("Sound::play", e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stop()
|
||||||
|
{
|
||||||
|
if (mpLast != null && mpLast.isPlaying()) { mpLast.stop(); mpLast.release(); mpLast = null; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
|
||||||
|
public class MaxHeightScrollView extends ScrollView
|
||||||
|
{
|
||||||
|
public int maxHeight = Integer.MAX_VALUE;//dp
|
||||||
|
|
||||||
|
public MaxHeightScrollView(Context context)
|
||||||
|
{
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaxHeightScrollView(Context context, AttributeSet attrs)
|
||||||
|
{
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView, 0, 0);
|
||||||
|
try {
|
||||||
|
maxHeight = a.getInteger(R.styleable.MaxHeightScrollView_maxHeightOverride, Integer.MAX_VALUE);
|
||||||
|
} finally {
|
||||||
|
a.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr)
|
||||||
|
{
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
|
||||||
|
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView, 0, 0);
|
||||||
|
try {
|
||||||
|
maxHeight = a.getInteger(R.styleable.MaxHeightScrollView_maxHeightOverride, Integer.MAX_VALUE);
|
||||||
|
} finally {
|
||||||
|
a.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||||
|
{
|
||||||
|
heightMeasureSpec = MeasureSpec.makeMeasureSpec(dpToPx(getResources(), maxHeight), MeasureSpec.AT_MOST);
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int dpToPx(Resources res, int dp)
|
||||||
|
{
|
||||||
|
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, res.getDisplayMetrics());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.util;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.view.MessageAdapter;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
public class MessageAdapterTouchHelper extends ItemTouchHelper.SimpleCallback
|
||||||
|
{
|
||||||
|
private MessageAdapterTouchHelperListener listener;
|
||||||
|
|
||||||
|
private int dir = 0;
|
||||||
|
|
||||||
|
public MessageAdapterTouchHelper(int dragDirs, int swipeDirs, MessageAdapterTouchHelperListener listener)
|
||||||
|
{
|
||||||
|
super(dragDirs, swipeDirs);
|
||||||
|
this.dir = swipeDirs;
|
||||||
|
this.listener = listener;
|
||||||
|
updateEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateEnabled()
|
||||||
|
{
|
||||||
|
int sdir = SCNSettings.inst().EnableDeleteSwipe ? ItemTouchHelper.LEFT : 0;
|
||||||
|
if (dir == sdir) return;
|
||||||
|
setDefaultSwipeDirs(dir = sdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState)
|
||||||
|
{
|
||||||
|
if (viewHolder != null)
|
||||||
|
{
|
||||||
|
final View foregroundView = ((MessageAdapter.MessagePresenter) viewHolder).viewForeground;
|
||||||
|
|
||||||
|
getDefaultUIUtil().onSelected(foregroundView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChildDrawOver(@NonNull Canvas c, @NonNull RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)
|
||||||
|
{
|
||||||
|
final View foregroundView = ((MessageAdapter.MessagePresenter) viewHolder).viewForeground;
|
||||||
|
getDefaultUIUtil().onDrawOver(c, recyclerView, foregroundView, dX, dY, actionState, isCurrentlyActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder)
|
||||||
|
{
|
||||||
|
final View foregroundView = ((MessageAdapter.MessagePresenter) viewHolder).viewForeground;
|
||||||
|
getDefaultUIUtil().clearView(foregroundView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)
|
||||||
|
{
|
||||||
|
final View foregroundView = ((MessageAdapter.MessagePresenter) viewHolder).viewForeground;
|
||||||
|
|
||||||
|
getDefaultUIUtil().onDraw(c, recyclerView, foregroundView, dX, dY, actionState, isCurrentlyActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction)
|
||||||
|
{
|
||||||
|
listener.onSwiped(viewHolder, direction, viewHolder.getAdapterPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int convertToAbsoluteDirection(int flags, int layoutDirection)
|
||||||
|
{
|
||||||
|
return super.convertToAbsoluteDirection(flags, layoutDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface MessageAdapterTouchHelperListener
|
||||||
|
{
|
||||||
|
void onSwiped(RecyclerView.ViewHolder viewHolder, int direction, int position);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.util;
|
||||||
|
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
|
||||||
|
public abstract class TextChangedListener<T> implements TextWatcher {
|
||||||
|
private T target;
|
||||||
|
|
||||||
|
public TextChangedListener(T target) {
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) {
|
||||||
|
this.onTextChanged(target, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void onTextChanged(T target, Editable s);
|
||||||
|
}
|
@ -0,0 +1,156 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.view;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.content.ClipboardManager;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
|
||||||
|
import net.glxn.qrgen.android.QRCode;
|
||||||
|
import net.glxn.qrgen.core.image.ImageType;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import static android.content.Context.CLIPBOARD_SERVICE;
|
||||||
|
|
||||||
|
public class AccountFragment extends Fragment
|
||||||
|
{
|
||||||
|
public AccountFragment()
|
||||||
|
{
|
||||||
|
// Required empty public constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
View v = inflater.inflate(R.layout.fragment_account, container, false);
|
||||||
|
|
||||||
|
updateUI(v);
|
||||||
|
|
||||||
|
v.findViewById(R.id.btnCopyUserID).setOnClickListener(cv ->
|
||||||
|
{
|
||||||
|
ClipboardManager clipboard = (ClipboardManager) cv.getContext().getSystemService(CLIPBOARD_SERVICE);
|
||||||
|
clipboard.setPrimaryClip(ClipData.newPlainText("UserID", String.valueOf(SCNSettings.inst().user_id)));
|
||||||
|
SCNApp.showToast("Copied userID to clipboard", 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
v.findViewById(R.id.btnCopyUserKey).setOnClickListener(cv ->
|
||||||
|
{
|
||||||
|
ClipboardManager clipboard = (ClipboardManager) cv.getContext().getSystemService(CLIPBOARD_SERVICE);
|
||||||
|
clipboard.setPrimaryClip(ClipData.newPlainText("UserKey", String.valueOf(SCNSettings.inst().user_key)));
|
||||||
|
SCNApp.showToast("Copied key to clipboard", 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
v.findViewById(R.id.btnAccountReset).setOnClickListener(cv ->
|
||||||
|
{
|
||||||
|
Activity a = getActivity();
|
||||||
|
if (a == null) return;
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(a);
|
||||||
|
|
||||||
|
builder.setTitle("Confirm");
|
||||||
|
builder.setMessage("Reset account key?");
|
||||||
|
|
||||||
|
builder.setPositiveButton("YES", (dialog, which) -> {
|
||||||
|
View lpnl = v.findViewById(R.id.loadingPanel);
|
||||||
|
lpnl.setVisibility(View.VISIBLE);
|
||||||
|
SCNSettings.inst().reset(lpnl);
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.setNegativeButton("NO", (dialog, which) -> dialog.dismiss());
|
||||||
|
|
||||||
|
AlertDialog alert = builder.create();
|
||||||
|
alert.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
v.findViewById(R.id.btnClearLocalStorage).setOnClickListener(cv ->
|
||||||
|
{
|
||||||
|
Activity a = getActivity();
|
||||||
|
if (a == null) return;
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(a);
|
||||||
|
|
||||||
|
builder.setTitle("Confirm");
|
||||||
|
builder.setMessage("Clear local messages?");
|
||||||
|
|
||||||
|
builder.setPositiveButton("YES", (dialog, which) -> {
|
||||||
|
CMessageList.inst().clear();
|
||||||
|
SCNApp.showToast("Messages cleared", 1000);
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.setNegativeButton("NO", (dialog, which) -> dialog.dismiss());
|
||||||
|
|
||||||
|
AlertDialog alert = builder.create();
|
||||||
|
alert.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
v.findViewById(R.id.btnQR).setOnClickListener(cv ->
|
||||||
|
{
|
||||||
|
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(SCNSettings.inst().createOnlineURL(true)));
|
||||||
|
startActivity(browserIntent);
|
||||||
|
});
|
||||||
|
|
||||||
|
v.findViewById(R.id.btnRefresh).setOnClickListener(cv ->
|
||||||
|
{
|
||||||
|
View lpnl = v.findViewById(R.id.loadingPanel);
|
||||||
|
lpnl.setVisibility(View.VISIBLE);
|
||||||
|
SCNSettings.inst().refresh(lpnl, getActivity());
|
||||||
|
});
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateUI()
|
||||||
|
{
|
||||||
|
updateUI(getView());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
public void updateUI(View v)
|
||||||
|
{
|
||||||
|
if (v == null) return;
|
||||||
|
TextView tvUserID = v.findViewById(R.id.tvUserID);
|
||||||
|
TextView tvUserKey = v.findViewById(R.id.tvUserKey);
|
||||||
|
TextView tvQuota = v.findViewById(R.id.tvQuota);
|
||||||
|
ImageView ivQuota = v.findViewById(R.id.ic_img_quota);
|
||||||
|
ImageButton btnQR = v.findViewById(R.id.btnQR);
|
||||||
|
|
||||||
|
SCNSettings s = SCNSettings.inst();
|
||||||
|
|
||||||
|
if (s.isConnected())
|
||||||
|
{
|
||||||
|
tvUserID.setText(String.valueOf(s.user_id));
|
||||||
|
tvUserKey.setText(s.user_key);
|
||||||
|
tvQuota.setText(String.format("%d / %d", s.quota_curr, s.quota_max));
|
||||||
|
btnQR.setImageBitmap(QRCode.from(s.createOnlineURL(false)).to(ImageType.PNG).withSize(512, 512).bitmap());
|
||||||
|
ivQuota.setColorFilter(s.quota_curr>=s.quota_max ? Color.rgb(200, 0, 0) : Color.rgb(128, 128, 128));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tvUserID.setText(R.string.str_not_connected);
|
||||||
|
tvUserKey.setText(R.string.str_not_connected);
|
||||||
|
tvQuota.setText(R.string.str_not_connected);
|
||||||
|
btnQR.setImageResource(R.drawable.qr_default);
|
||||||
|
ivQuota.setColorFilter(0x80_80_80);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,224 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.view.debug.QueryLogActivity;
|
||||||
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.viewpager.widget.PagerAdapter;
|
||||||
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity
|
||||||
|
{
|
||||||
|
public TabAdapter adpTabs;
|
||||||
|
public RelativeLayout layoutRoot;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
QueryLog.inst();
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
NotificationService.inst();
|
||||||
|
CMessageList.inst();
|
||||||
|
|
||||||
|
layoutRoot = findViewById(R.id.layoutRoot);
|
||||||
|
|
||||||
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
toolbar.setOnClickListener(this::onToolbackClicked);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
ViewPager viewPager = findViewById(R.id.pager);
|
||||||
|
PagerAdapter adapter = adpTabs = new TabAdapter(getSupportFragmentManager());
|
||||||
|
viewPager.setAdapter(adapter);
|
||||||
|
|
||||||
|
TabLayout tabLayout = findViewById(R.id.tab_layout);
|
||||||
|
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
|
||||||
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
|
|
||||||
|
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { /* */ }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageSelected(int position)
|
||||||
|
{
|
||||||
|
if (position != 2) adpTabs.tab3.onViewpagerHide();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageScrollStateChanged(int state) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
SCNApp.register(this);
|
||||||
|
IABService.startup(this);
|
||||||
|
SCNSettings.inst().work(this, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop()
|
||||||
|
{
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
SCNSettings.inst().save();
|
||||||
|
CMessageList.inst().fullSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy()
|
||||||
|
{
|
||||||
|
super.onDestroy();
|
||||||
|
|
||||||
|
CMessageList.inst().fullSave();
|
||||||
|
IABService.inst().destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int clickCount = 0;
|
||||||
|
private long lastClick = 0;
|
||||||
|
private void onToolbackClicked(View v)
|
||||||
|
{
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (now - lastClick > 200) clickCount=0;
|
||||||
|
clickCount++;
|
||||||
|
lastClick = now;
|
||||||
|
|
||||||
|
if (clickCount == 4) startActivity(new Intent(this, QueryLogActivity.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
if(requestCode == 1991 && resultCode == RESULT_OK)
|
||||||
|
{
|
||||||
|
Uri uri = data.getData(); //The uri with the location of the file
|
||||||
|
|
||||||
|
Context ctxt = this;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ObjectInputStream stream = new ObjectInputStream(getContentResolver().openInputStream(uri));
|
||||||
|
|
||||||
|
Map<String, ?> d1 = (Map<String, ?>)stream.readObject();
|
||||||
|
Map<String, ?> d2 = (Map<String, ?>)stream.readObject();
|
||||||
|
Map<String, ?> d3 = (Map<String, ?>)stream.readObject();
|
||||||
|
Map<String, ?> d4 = (Map<String, ?>)stream.readObject();
|
||||||
|
|
||||||
|
stream.close();
|
||||||
|
|
||||||
|
runOnUiThread(() ->
|
||||||
|
{
|
||||||
|
|
||||||
|
SharedPreferences.Editor e1 = ctxt.getSharedPreferences("Config", Context.MODE_PRIVATE).edit();
|
||||||
|
SharedPreferences.Editor e2 = ctxt.getSharedPreferences("IAB", Context.MODE_PRIVATE).edit();
|
||||||
|
SharedPreferences.Editor e3 = ctxt.getSharedPreferences("CMessageList", Context.MODE_PRIVATE).edit();
|
||||||
|
SharedPreferences.Editor e4 = ctxt.getSharedPreferences("QueryLog", Context.MODE_PRIVATE).edit();
|
||||||
|
|
||||||
|
e1.clear();
|
||||||
|
for (Map.Entry<String, ?> entry : d1.entrySet())
|
||||||
|
{
|
||||||
|
if (entry.getValue() instanceof String) e1.putString(entry.getKey(), (String)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Boolean) e1.putBoolean(entry.getKey(), (Boolean)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Float) e1.putFloat(entry.getKey(), (Float)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Integer) e1.putInt(entry.getKey(), (Integer)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Long) e1.putLong(entry.getKey(), (Long)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Set<?>) e1.putStringSet(entry.getKey(), (Set<String>)entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
e2.clear();
|
||||||
|
for (Map.Entry<String, ?> entry : d2.entrySet())
|
||||||
|
{
|
||||||
|
if (entry.getValue() instanceof String) e2.putString(entry.getKey(), (String)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Boolean) e2.putBoolean(entry.getKey(), (Boolean)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Float) e2.putFloat(entry.getKey(), (Float)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Integer) e2.putInt(entry.getKey(), (Integer)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Long) e2.putLong(entry.getKey(), (Long)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Set<?>) e2.putStringSet(entry.getKey(), (Set<String>)entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
e2.clear();
|
||||||
|
for (Map.Entry<String, ?> entry : d3.entrySet())
|
||||||
|
{
|
||||||
|
if (entry.getValue() instanceof String) e3.putString(entry.getKey(), (String)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Boolean) e3.putBoolean(entry.getKey(), (Boolean)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Float) e3.putFloat(entry.getKey(), (Float)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Integer) e3.putInt(entry.getKey(), (Integer)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Long) e3.putLong(entry.getKey(), (Long)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Set<?>) e3.putStringSet(entry.getKey(), (Set<String>)entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
e4.clear();
|
||||||
|
for (Map.Entry<String, ?> entry : d4.entrySet())
|
||||||
|
{
|
||||||
|
if (entry.getValue() instanceof String) e4.putString(entry.getKey(), (String)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Boolean) e4.putBoolean(entry.getKey(), (Boolean)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Float) e4.putFloat(entry.getKey(), (Float)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Integer) e4.putInt(entry.getKey(), (Integer)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Long) e4.putLong(entry.getKey(), (Long)entry.getValue());
|
||||||
|
if (entry.getValue() instanceof Set<?>) e4.putStringSet(entry.getKey(), (Set<String>)entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
e1.apply();
|
||||||
|
e2.apply();
|
||||||
|
e3.apply();
|
||||||
|
e4.apply();
|
||||||
|
|
||||||
|
|
||||||
|
SCNSettings.inst().reloadPrefs();
|
||||||
|
IABService.inst().reloadPrefs();
|
||||||
|
CMessageList.inst().reloadPrefs();
|
||||||
|
QueryLog.inst().reloadPrefs();
|
||||||
|
|
||||||
|
|
||||||
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
ViewPager viewPager = findViewById(R.id.pager);
|
||||||
|
PagerAdapter adapter = adpTabs = new TabAdapter(getSupportFragmentManager());
|
||||||
|
viewPager.setAdapter(adapter);
|
||||||
|
|
||||||
|
TabLayout tabLayout = findViewById(R.id.tab_layout);
|
||||||
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
|
|
||||||
|
|
||||||
|
SCNSettings.inst().work(this, true);
|
||||||
|
|
||||||
|
SCNApp.showToast("Backup imported", Toast.LENGTH_LONG);
|
||||||
|
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("Import:Err", e.toString());
|
||||||
|
SCNApp.showToast("Import failed", Toast.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,226 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.view;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.CMessage;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.google.android.material.button.MaterialButton;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
public class MessageAdapter extends RecyclerView.Adapter
|
||||||
|
{
|
||||||
|
private final View vNoElements;
|
||||||
|
private final LinearLayoutManager manLayout;
|
||||||
|
private final RecyclerView viewRecycler;
|
||||||
|
|
||||||
|
private WeakHashMap<MessagePresenter, Boolean> viewHolders = new WeakHashMap<>();
|
||||||
|
|
||||||
|
public MessageAdapter(View noElementsView, LinearLayoutManager layout, RecyclerView recycler)
|
||||||
|
{
|
||||||
|
vNoElements = noElementsView;
|
||||||
|
manLayout = layout;
|
||||||
|
viewRecycler = recycler;
|
||||||
|
CMessageList.inst().register(this);
|
||||||
|
|
||||||
|
vNoElements.setVisibility(getItemCount()>0 ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
|
||||||
|
{
|
||||||
|
View myView = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_card, parent, false);
|
||||||
|
return new MessagePresenter(myView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position)
|
||||||
|
{
|
||||||
|
CMessage msg = CMessageList.inst().tryGetFromBack(position);
|
||||||
|
MessagePresenter view = (MessagePresenter) holder;
|
||||||
|
view.setMessage(msg, position);
|
||||||
|
|
||||||
|
viewHolders.put(view, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder)
|
||||||
|
{
|
||||||
|
if (holder instanceof MessagePresenter) viewHolders.remove(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount()
|
||||||
|
{
|
||||||
|
return CMessageList.inst().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void customNotifyItemInserted(int idx)
|
||||||
|
{
|
||||||
|
notifyItemInserted(idx);
|
||||||
|
vNoElements.setVisibility(getItemCount()>0 ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void customNotifyDataSetChanged()
|
||||||
|
{
|
||||||
|
notifyDataSetChanged();
|
||||||
|
vNoElements.setVisibility(getItemCount()>0 ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void scrollToTop()
|
||||||
|
{
|
||||||
|
manLayout.smoothScrollToPosition(viewRecycler, null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CMessage removeItem(int position)
|
||||||
|
{
|
||||||
|
CMessage i = CMessageList.inst().removeFromBack(position);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restoreItem(CMessage item, int position)
|
||||||
|
{
|
||||||
|
CMessageList.inst().insert(position, item);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MessagePresenter extends RecyclerView.ViewHolder implements View.OnClickListener
|
||||||
|
{
|
||||||
|
private TextView tvTimestamp;
|
||||||
|
private TextView tvTitle;
|
||||||
|
private TextView tvMessage;
|
||||||
|
private ImageView ivPriority;
|
||||||
|
|
||||||
|
public RelativeLayout viewForeground;
|
||||||
|
public RelativeLayout viewBackground;
|
||||||
|
|
||||||
|
public MaterialButton btnShare;
|
||||||
|
public MaterialButton btnDelete;
|
||||||
|
|
||||||
|
private CMessage data;
|
||||||
|
private int datapos;
|
||||||
|
|
||||||
|
MessagePresenter(View itemView)
|
||||||
|
{
|
||||||
|
super(itemView);
|
||||||
|
tvTimestamp = itemView.findViewById(R.id.tvTimestamp);
|
||||||
|
tvTitle = itemView.findViewById(R.id.tvTitle);
|
||||||
|
tvMessage = itemView.findViewById(R.id.tvMessage);
|
||||||
|
ivPriority = itemView.findViewById(R.id.ivPriority);
|
||||||
|
viewForeground = itemView.findViewById(R.id.layoutFront);
|
||||||
|
viewBackground = itemView.findViewById(R.id.layoutBack);
|
||||||
|
btnShare = itemView.findViewById(R.id.btnShare);
|
||||||
|
btnDelete = itemView.findViewById(R.id.btnDelete);
|
||||||
|
|
||||||
|
itemView.setOnClickListener(this);
|
||||||
|
tvTimestamp.setOnClickListener(this);
|
||||||
|
tvTitle.setOnClickListener(this);
|
||||||
|
tvMessage.setOnClickListener(this);
|
||||||
|
ivPriority.setOnClickListener(this);
|
||||||
|
viewForeground.setOnClickListener(this);
|
||||||
|
|
||||||
|
btnShare.setOnClickListener(v ->
|
||||||
|
{
|
||||||
|
if (data == null) return;
|
||||||
|
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
|
||||||
|
sharingIntent.setType("text/plain");
|
||||||
|
sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, data.Title);
|
||||||
|
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, data.Content);
|
||||||
|
SCNApp.getMainActivity().startActivity(Intent.createChooser(sharingIntent, "Share message"));
|
||||||
|
|
||||||
|
});
|
||||||
|
btnDelete.setOnClickListener(v -> { if (data != null) SCNApp.getMainActivity().adpTabs.tab1.deleteMessage(datapos); });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMessage(CMessage msg, int pos)
|
||||||
|
{
|
||||||
|
tvTimestamp.setText(msg.formatTimestamp());
|
||||||
|
tvTitle.setText(msg.Title);
|
||||||
|
tvMessage.setText(msg.Content);
|
||||||
|
|
||||||
|
switch (msg.Priority)
|
||||||
|
{
|
||||||
|
case LOW:
|
||||||
|
ivPriority.setVisibility(View.VISIBLE);
|
||||||
|
ivPriority.setImageResource(R.drawable.priority_low);
|
||||||
|
ivPriority.setColorFilter(Color.rgb(176, 176, 176));
|
||||||
|
break;
|
||||||
|
case NORMAL:
|
||||||
|
ivPriority.setVisibility(View.GONE);
|
||||||
|
ivPriority.setColorFilter(Color.rgb(176, 176, 176));
|
||||||
|
break;
|
||||||
|
case HIGH:
|
||||||
|
ivPriority.setVisibility(View.VISIBLE);
|
||||||
|
ivPriority.setImageResource(R.drawable.priority_high);
|
||||||
|
ivPriority.setColorFilter(Color.rgb(200, 0, 0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = msg;
|
||||||
|
datapos = pos;
|
||||||
|
|
||||||
|
if (msg.IsExpandedInAdapter) expand(true); else collapse(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expand(boolean force)
|
||||||
|
{
|
||||||
|
if (data != null && data.IsExpandedInAdapter && !force) return;
|
||||||
|
if (data != null) data.IsExpandedInAdapter = true;
|
||||||
|
if (tvMessage != null) tvMessage.setMaxLines(9999);
|
||||||
|
if (btnDelete != null) btnDelete.setVisibility(View.VISIBLE);
|
||||||
|
if (btnShare != null) btnShare.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private int norm(int i) { return (i<=0)?0:((i>9999)?9999:i); }
|
||||||
|
|
||||||
|
private void collapse(boolean force)
|
||||||
|
{
|
||||||
|
if (data != null && !data.IsExpandedInAdapter && !force) return;
|
||||||
|
if (data != null) data.IsExpandedInAdapter = false;
|
||||||
|
if (tvMessage != null) tvMessage.setMaxLines(norm(SCNSettings.inst().PreviewLineCount));
|
||||||
|
if (btnDelete != null) btnDelete.setVisibility(View.GONE);
|
||||||
|
if (btnShare != null) btnShare.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v)
|
||||||
|
{
|
||||||
|
if (data.IsExpandedInAdapter)
|
||||||
|
{
|
||||||
|
collapse(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (MessagePresenter holder : MessageAdapter.this.viewHolders.keySet())
|
||||||
|
{
|
||||||
|
if (holder == null) continue;
|
||||||
|
if (holder == this) continue;
|
||||||
|
holder.collapse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
expand(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.view;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.CMessage;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.util.MessageAdapterTouchHelper;
|
||||||
|
import com.google.android.gms.ads.doubleclick.PublisherAdRequest;
|
||||||
|
import com.google.android.gms.ads.doubleclick.PublisherAdView;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
public class NotificationsFragment extends Fragment implements MessageAdapterTouchHelper.MessageAdapterTouchHelperListener
|
||||||
|
{
|
||||||
|
private PublisherAdView adView;
|
||||||
|
private MessageAdapter adpMessages;
|
||||||
|
|
||||||
|
public MessageAdapterTouchHelper touchHelper;
|
||||||
|
|
||||||
|
public NotificationsFragment()
|
||||||
|
{
|
||||||
|
// Required empty public constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
View v = inflater.inflate(R.layout.fragment_notifications, container, false);
|
||||||
|
|
||||||
|
RecyclerView rvMessages = v.findViewById(R.id.rvMessages);
|
||||||
|
LinearLayoutManager lman = new LinearLayoutManager(this.getContext(), RecyclerView.VERTICAL, false);
|
||||||
|
rvMessages.setLayoutManager(lman);
|
||||||
|
rvMessages.setAdapter(adpMessages = new MessageAdapter(v.findViewById(R.id.tvNoElements), lman, rvMessages));
|
||||||
|
|
||||||
|
ItemTouchHelper.SimpleCallback itemTouchHelperCallback = touchHelper = new MessageAdapterTouchHelper(0, ItemTouchHelper.LEFT, this);
|
||||||
|
new ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(rvMessages);
|
||||||
|
|
||||||
|
adView = v.findViewById(R.id.adBanner);
|
||||||
|
PublisherAdRequest adRequest = new PublisherAdRequest.Builder().build();
|
||||||
|
adView.loadAd(adRequest);
|
||||||
|
|
||||||
|
adView.setVisibility(SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateProState()
|
||||||
|
{
|
||||||
|
if (adView != null) adView.setVisibility(IABService.inst().getPurchaseCachedSimple(IABService.IAB_PRO_MODE) ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction, int position)
|
||||||
|
{
|
||||||
|
if (viewHolder instanceof MessageAdapter.MessagePresenter)
|
||||||
|
{
|
||||||
|
deleteMessage(viewHolder.getAdapterPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteMessage(int pos)
|
||||||
|
{
|
||||||
|
final int deletedIndex = pos;
|
||||||
|
|
||||||
|
final CMessage deletedItem = adpMessages.removeItem(pos);
|
||||||
|
String name = deletedItem.Title;
|
||||||
|
|
||||||
|
Snackbar snackbar = Snackbar.make(SCNApp.getMainActivity().layoutRoot, name + " removed", Snackbar.LENGTH_LONG);
|
||||||
|
snackbar.setAction("UNDO", view -> adpMessages.restoreItem(deletedItem, deletedIndex));
|
||||||
|
snackbar.setActionTextColor(Color.YELLOW);
|
||||||
|
snackbar.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateDeleteSwipeEnabled()
|
||||||
|
{
|
||||||
|
if (touchHelper != null) touchHelper.updateEnabled();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,581 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.view;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.media.AudioAttributes;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.media.MediaPlayer;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
import android.widget.Spinner;
|
||||||
|
import android.widget.Switch;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.FileProvider;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.lambda.FI;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.IABService;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.util.TextChangedListener;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import top.defaults.colorpicker.ColorPickerPopup;
|
||||||
|
import xyz.aprildown.ultimatemusicpicker.MusicPickerListener;
|
||||||
|
import xyz.aprildown.ultimatemusicpicker.UltimateMusicPicker;
|
||||||
|
|
||||||
|
public class SettingsFragment extends Fragment implements MusicPickerListener
|
||||||
|
{
|
||||||
|
private Switch prefAppEnabled;
|
||||||
|
private Spinner prefLocalCacheSize;
|
||||||
|
private Button prefUpgradeAccount;
|
||||||
|
private TextView prefUpgradeAccount_msg;
|
||||||
|
private TextView prefUpgradeAccount_info;
|
||||||
|
private Switch prefEnableDeleteSwipe;
|
||||||
|
private EditText prefPreviewLineCount;
|
||||||
|
|
||||||
|
private Switch prefMsgLowEnableSound;
|
||||||
|
private TextView prefMsgLowRingtone_value;
|
||||||
|
private View prefMsgLowRingtone_container;
|
||||||
|
private Switch prefMsgLowRepeatSound;
|
||||||
|
private Switch prefMsgLowEnableLED;
|
||||||
|
private View prefMsgLowLedColor_container;
|
||||||
|
private ImageView prefMsgLowLedColor_value;
|
||||||
|
private Switch prefMsgLowEnableVibrations;
|
||||||
|
private Switch prefMsgLowForceVolume;
|
||||||
|
private SeekBar prefMsgLowVolume;
|
||||||
|
private ImageView prefMsgLowVolumeTest;
|
||||||
|
|
||||||
|
private Switch prefMsgNormEnableSound;
|
||||||
|
private TextView prefMsgNormRingtone_value;
|
||||||
|
private View prefMsgNormRingtone_container;
|
||||||
|
private Switch prefMsgNormRepeatSound;
|
||||||
|
private Switch prefMsgNormEnableLED;
|
||||||
|
private View prefMsgNormLedColor_container;
|
||||||
|
private ImageView prefMsgNormLedColor_value;
|
||||||
|
private Switch prefMsgNormEnableVibrations;
|
||||||
|
private Switch prefMsgNormForceVolume;
|
||||||
|
private SeekBar prefMsgNormVolume;
|
||||||
|
private ImageView prefMsgNormVolumeTest;
|
||||||
|
|
||||||
|
private Switch prefMsgHighEnableSound;
|
||||||
|
private TextView prefMsgHighRingtone_value;
|
||||||
|
private View prefMsgHighRingtone_container;
|
||||||
|
private Switch prefMsgHighRepeatSound;
|
||||||
|
private Switch prefMsgHighEnableLED;
|
||||||
|
private View prefMsgHighLedColor_container;
|
||||||
|
private ImageView prefMsgHighLedColor_value;
|
||||||
|
private Switch prefMsgHighEnableVibrations;
|
||||||
|
private Switch prefMsgHighForceVolume;
|
||||||
|
private SeekBar prefMsgHighVolume;
|
||||||
|
private ImageView prefMsgHighVolumeTest;
|
||||||
|
|
||||||
|
private Button prefBtnImport;
|
||||||
|
private Button prefBtnExport;
|
||||||
|
|
||||||
|
private int musicPickerSwitch = -1;
|
||||||
|
|
||||||
|
private MediaPlayer[] mPlayers = new MediaPlayer[3];
|
||||||
|
|
||||||
|
public SettingsFragment()
|
||||||
|
{
|
||||||
|
// Required empty public constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
View v = inflater.inflate(R.layout.fragment_settings, container, false);
|
||||||
|
|
||||||
|
initFields(v);
|
||||||
|
updateUI();
|
||||||
|
initListener();
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initFields(View v)
|
||||||
|
{
|
||||||
|
prefAppEnabled = v.findViewById(R.id.prefAppEnabled);
|
||||||
|
prefLocalCacheSize = v.findViewById(R.id.prefLocalCacheSize);
|
||||||
|
prefUpgradeAccount = v.findViewById(R.id.prefUpgradeAccount);
|
||||||
|
prefUpgradeAccount_msg = v.findViewById(R.id.prefUpgradeAccount2);
|
||||||
|
prefUpgradeAccount_info = v.findViewById(R.id.prefUpgradeAccount_info);
|
||||||
|
prefEnableDeleteSwipe = v.findViewById(R.id.prefEnableDeleteSwipe);
|
||||||
|
prefPreviewLineCount = v.findViewById(R.id.prefPreviewLineCount);
|
||||||
|
|
||||||
|
prefMsgLowEnableSound = v.findViewById(R.id.prefMsgLowEnableSound);
|
||||||
|
prefMsgLowRingtone_value = v.findViewById(R.id.prefMsgLowRingtone_value);
|
||||||
|
prefMsgLowRingtone_container = v.findViewById(R.id.prefMsgLowRingtone_container);
|
||||||
|
prefMsgLowRepeatSound = v.findViewById(R.id.prefMsgLowRepeatSound);
|
||||||
|
prefMsgLowEnableLED = v.findViewById(R.id.prefMsgLowEnableLED);
|
||||||
|
prefMsgLowLedColor_value = v.findViewById(R.id.prefMsgLowLedColor_value);
|
||||||
|
prefMsgLowLedColor_container = v.findViewById(R.id.prefMsgLowLedColor_container);
|
||||||
|
prefMsgLowEnableVibrations = v.findViewById(R.id.prefMsgLowEnableVibrations);
|
||||||
|
prefMsgLowForceVolume = v.findViewById(R.id.prefMsgLowForceVolume);
|
||||||
|
prefMsgLowVolume = v.findViewById(R.id.prefMsgLowVolume);
|
||||||
|
prefMsgLowVolumeTest = v.findViewById(R.id.btnLowVolumeTest);
|
||||||
|
|
||||||
|
prefMsgNormEnableSound = v.findViewById(R.id.prefMsgNormEnableSound);
|
||||||
|
prefMsgNormRingtone_value = v.findViewById(R.id.prefMsgNormRingtone_value);
|
||||||
|
prefMsgNormRingtone_container = v.findViewById(R.id.prefMsgNormRingtone_container);
|
||||||
|
prefMsgNormRepeatSound = v.findViewById(R.id.prefMsgNormRepeatSound);
|
||||||
|
prefMsgNormEnableLED = v.findViewById(R.id.prefMsgNormEnableLED);
|
||||||
|
prefMsgNormLedColor_value = v.findViewById(R.id.prefMsgNormLedColor_value);
|
||||||
|
prefMsgNormLedColor_container = v.findViewById(R.id.prefMsgNormLedColor_container);
|
||||||
|
prefMsgNormEnableVibrations = v.findViewById(R.id.prefMsgNormEnableVibrations);
|
||||||
|
prefMsgNormForceVolume = v.findViewById(R.id.prefMsgNormForceVolume);
|
||||||
|
prefMsgNormVolume = v.findViewById(R.id.prefMsgNormVolume);
|
||||||
|
prefMsgNormVolumeTest = v.findViewById(R.id.btnNormVolumeTest);
|
||||||
|
|
||||||
|
prefMsgHighEnableSound = v.findViewById(R.id.prefMsgHighEnableSound);
|
||||||
|
prefMsgHighRingtone_value = v.findViewById(R.id.prefMsgHighRingtone_value);
|
||||||
|
prefMsgHighRingtone_container = v.findViewById(R.id.prefMsgHighRingtone_container);
|
||||||
|
prefMsgHighRepeatSound = v.findViewById(R.id.prefMsgHighRepeatSound);
|
||||||
|
prefMsgHighEnableLED = v.findViewById(R.id.prefMsgHighEnableLED);
|
||||||
|
prefMsgHighLedColor_value = v.findViewById(R.id.prefMsgHighLedColor_value);
|
||||||
|
prefMsgHighLedColor_container = v.findViewById(R.id.prefMsgHighLedColor_container);
|
||||||
|
prefMsgHighEnableVibrations = v.findViewById(R.id.prefMsgHighEnableVibrations);
|
||||||
|
prefMsgHighForceVolume = v.findViewById(R.id.prefMsgHighForceVolume);
|
||||||
|
prefMsgHighVolume = v.findViewById(R.id.prefMsgHighVolume);
|
||||||
|
prefMsgHighVolumeTest = v.findViewById(R.id.btnHighVolumeTest);
|
||||||
|
|
||||||
|
prefBtnExport = v.findViewById(R.id.prefExport);
|
||||||
|
prefBtnImport = v.findViewById(R.id.prefImport);
|
||||||
|
|
||||||
|
ArrayAdapter<Integer> plcsa = new ArrayAdapter<>(v.getContext(), android.R.layout.simple_spinner_item, SCNSettings.CHOOSABLE_CACHE_SIZES);
|
||||||
|
plcsa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
prefLocalCacheSize.setAdapter(plcsa);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
private void updateUI()
|
||||||
|
{
|
||||||
|
SCNSettings s = SCNSettings.inst();
|
||||||
|
Context c = getContext();
|
||||||
|
if (c == null) return;
|
||||||
|
|
||||||
|
if (prefAppEnabled.isChecked() != s.Enabled) prefAppEnabled.setChecked(s.Enabled);
|
||||||
|
if (prefEnableDeleteSwipe.isChecked() != s.EnableDeleteSwipe) prefEnableDeleteSwipe.setChecked(s.EnableDeleteSwipe);
|
||||||
|
if (!prefPreviewLineCount.getText().toString().equals(Integer.toString(s.PreviewLineCount))) prefPreviewLineCount.setText(Integer.toString(s.PreviewLineCount));
|
||||||
|
|
||||||
|
prefUpgradeAccount.setVisibility( SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE);
|
||||||
|
prefUpgradeAccount_info.setVisibility(SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE);
|
||||||
|
prefUpgradeAccount_msg.setVisibility( SCNSettings.inst().promode_local ? View.VISIBLE : View.GONE );
|
||||||
|
|
||||||
|
if (prefLocalCacheSize.getSelectedItemPosition() != getCacheSizeIndex(s.LocalCacheSize)) prefLocalCacheSize.setSelection(getCacheSizeIndex(s.LocalCacheSize));
|
||||||
|
|
||||||
|
if (prefMsgLowEnableSound.isChecked() != s.PriorityLow.EnableSound) prefMsgLowEnableSound.setChecked(s.PriorityLow.EnableSound);
|
||||||
|
if (!prefMsgLowRingtone_value.getText().equals(s.PriorityLow.SoundName)) prefMsgLowRingtone_value.setText(s.PriorityLow.SoundName);
|
||||||
|
if (prefMsgLowRepeatSound.isChecked() != s.PriorityLow.RepeatSound) prefMsgLowRepeatSound.setChecked(s.PriorityLow.RepeatSound);
|
||||||
|
if (prefMsgLowEnableLED.isChecked() != s.PriorityLow.EnableLED) prefMsgLowEnableLED.setChecked(s.PriorityLow.EnableLED);
|
||||||
|
prefMsgLowLedColor_value.setColorFilter(s.PriorityLow.LEDColor);
|
||||||
|
if (prefMsgLowEnableVibrations.isChecked() != s.PriorityLow.EnableVibration) prefMsgLowEnableVibrations.setChecked(s.PriorityLow.EnableVibration);
|
||||||
|
if (prefMsgLowForceVolume.isChecked() != s.PriorityLow.ForceVolume) prefMsgLowForceVolume.setChecked(s.PriorityLow.ForceVolume);
|
||||||
|
if (prefMsgLowVolume.getMax() != 100) prefMsgLowVolume.setMax(100);
|
||||||
|
if (prefMsgLowVolume.getProgress() != s.PriorityLow.ForceVolumeValue) prefMsgLowVolume.setProgress(s.PriorityLow.ForceVolumeValue);
|
||||||
|
if (prefMsgLowVolume.isEnabled() != s.PriorityLow.ForceVolume) prefMsgLowVolume.setEnabled(s.PriorityLow.ForceVolume);
|
||||||
|
if (prefMsgLowVolumeTest.isEnabled() != s.PriorityLow.ForceVolume) prefMsgLowVolumeTest.setEnabled(s.PriorityLow.ForceVolume);
|
||||||
|
if (s.PriorityLow.ForceVolume) prefMsgLowVolumeTest.setColorFilter(null); else prefMsgLowVolumeTest.setColorFilter(Color.argb(150,200,200,200));
|
||||||
|
|
||||||
|
if (prefMsgNormEnableSound.isChecked() != s.PriorityNorm.EnableSound) prefMsgNormEnableSound.setChecked(s.PriorityNorm.EnableSound);
|
||||||
|
if (!prefMsgNormRingtone_value.getText().equals(s.PriorityNorm.SoundName)) prefMsgNormRingtone_value.setText(s.PriorityNorm.SoundName);
|
||||||
|
if (prefMsgNormRepeatSound.isChecked() != s.PriorityNorm.RepeatSound) prefMsgNormRepeatSound.setChecked(s.PriorityNorm.RepeatSound);
|
||||||
|
if (prefMsgNormEnableLED.isChecked() != s.PriorityNorm.EnableLED) prefMsgNormEnableLED.setChecked(s.PriorityNorm.EnableLED);
|
||||||
|
prefMsgNormLedColor_value.setColorFilter(s.PriorityNorm.LEDColor);
|
||||||
|
if (prefMsgNormEnableVibrations.isChecked() != s.PriorityNorm.EnableVibration) prefMsgNormEnableVibrations.setChecked(s.PriorityNorm.EnableVibration);
|
||||||
|
if (prefMsgNormForceVolume.isChecked() != s.PriorityNorm.ForceVolume) prefMsgNormForceVolume.setChecked(s.PriorityNorm.ForceVolume);
|
||||||
|
if (prefMsgNormVolume.getMax() != 100) prefMsgNormVolume.setMax(100);
|
||||||
|
if (prefMsgNormVolume.getProgress() != s.PriorityNorm.ForceVolumeValue) prefMsgNormVolume.setProgress(s.PriorityNorm.ForceVolumeValue);
|
||||||
|
if (prefMsgNormVolume.isEnabled() != s.PriorityNorm.ForceVolume) prefMsgNormVolume.setEnabled(s.PriorityNorm.ForceVolume);
|
||||||
|
if (prefMsgNormVolumeTest.isEnabled() != s.PriorityNorm.ForceVolume) prefMsgNormVolumeTest.setEnabled(s.PriorityNorm.ForceVolume);
|
||||||
|
if (s.PriorityNorm.ForceVolume) prefMsgNormVolumeTest.setColorFilter(null); else prefMsgNormVolumeTest.setColorFilter(Color.argb(150,200,200,200));
|
||||||
|
|
||||||
|
if (prefMsgHighEnableSound.isChecked() != s.PriorityHigh.EnableSound) prefMsgHighEnableSound.setChecked(s.PriorityHigh.EnableSound);
|
||||||
|
if (!prefMsgHighRingtone_value.getText().equals(s.PriorityHigh.SoundName)) prefMsgHighRingtone_value.setText(s.PriorityHigh.SoundName);
|
||||||
|
if (prefMsgHighRepeatSound.isChecked() != s.PriorityHigh.RepeatSound) prefMsgHighRepeatSound.setChecked(s.PriorityHigh.RepeatSound);
|
||||||
|
if (prefMsgHighEnableLED.isChecked() != s.PriorityHigh.EnableLED) prefMsgHighEnableLED.setChecked(s.PriorityHigh.EnableLED);
|
||||||
|
prefMsgHighLedColor_value.setColorFilter(s.PriorityHigh.LEDColor);
|
||||||
|
if (prefMsgHighEnableVibrations.isChecked() != s.PriorityHigh.EnableVibration) prefMsgHighEnableVibrations.setChecked(s.PriorityHigh.EnableVibration);
|
||||||
|
if (prefMsgHighForceVolume.isChecked() != s.PriorityHigh.ForceVolume) prefMsgHighForceVolume.setChecked(s.PriorityHigh.ForceVolume);
|
||||||
|
if (prefMsgHighVolume.getMax() != 100) prefMsgHighVolume.setMax(100);
|
||||||
|
if (prefMsgHighVolume.getProgress() != s.PriorityHigh.ForceVolumeValue) prefMsgHighVolume.setProgress(s.PriorityHigh.ForceVolumeValue);
|
||||||
|
if (prefMsgHighVolume.isEnabled() != s.PriorityHigh.ForceVolume) prefMsgHighVolume.setEnabled(s.PriorityHigh.ForceVolume);
|
||||||
|
if (prefMsgHighVolumeTest.isEnabled() != s.PriorityHigh.ForceVolume) prefMsgHighVolumeTest.setEnabled(s.PriorityHigh.ForceVolume);
|
||||||
|
if (s.PriorityHigh.ForceVolume) prefMsgHighVolumeTest.setColorFilter(null); else prefMsgHighVolumeTest.setColorFilter(Color.argb(150,200,200,200));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initListener()
|
||||||
|
{
|
||||||
|
SCNSettings s = SCNSettings.inst();
|
||||||
|
|
||||||
|
prefAppEnabled.setOnCheckedChangeListener((a,b) -> { boolean prev=s.Enabled; s.Enabled=b; saveAndUpdate(); updateEnabled(prev, b); });
|
||||||
|
prefEnableDeleteSwipe.setOnCheckedChangeListener((a,b) -> { s.EnableDeleteSwipe=b; saveAndUpdate(); });
|
||||||
|
prefPreviewLineCount.addTextChangedListener(new TextChangedListener<EditText>(prefPreviewLineCount) {
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(EditText target, Editable ed) {
|
||||||
|
if (!ed.toString().isEmpty()) try { s.PreviewLineCount=Integer.parseInt(ed.toString()); saveAndUpdate(); } catch (Exception e) { /* */ }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
prefLocalCacheSize.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
|
||||||
|
{
|
||||||
|
@Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
s.LocalCacheSize = prefLocalCacheSize.getSelectedItemPosition()>=0 ? SCNSettings.CHOOSABLE_CACHE_SIZES[prefLocalCacheSize.getSelectedItemPosition()] : 100;
|
||||||
|
saveAndUpdate();
|
||||||
|
}
|
||||||
|
@Override public void onNothingSelected(AdapterView<?> parent) { /* */ }
|
||||||
|
});
|
||||||
|
|
||||||
|
prefUpgradeAccount.setOnClickListener(a -> onUpgradeAccount());
|
||||||
|
|
||||||
|
prefBtnExport.setOnClickListener(a -> onExport());
|
||||||
|
prefBtnImport.setOnClickListener(a -> onImport());
|
||||||
|
|
||||||
|
prefMsgLowEnableSound.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.EnableSound=b; saveAndUpdate(); });
|
||||||
|
prefMsgLowRingtone_container.setOnClickListener(a -> chooseRingtoneLow());
|
||||||
|
prefMsgLowRepeatSound.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.RepeatSound=b; saveAndUpdate(); });
|
||||||
|
prefMsgLowEnableLED.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.EnableLED=b; saveAndUpdate(); });
|
||||||
|
prefMsgLowLedColor_container.setOnClickListener(a -> chooseLEDColorLow());
|
||||||
|
prefMsgLowEnableVibrations.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.EnableVibration=b; saveAndUpdate(); });
|
||||||
|
prefMsgLowForceVolume.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.ForceVolume=b; saveAndUpdate(); });
|
||||||
|
prefMsgLowVolume.setOnSeekBarChangeListener(FI.SeekBarChanged((a,b,c) -> { if (c) { s.PriorityLow.ForceVolumeValue=b; saveAndUpdate(); updateVolume(0, b); } }));
|
||||||
|
prefMsgLowVolumeTest.setOnClickListener((v) -> { if (s.PriorityLow.ForceVolume) playTestSound(0, prefMsgLowVolumeTest, s.PriorityLow.SoundSource, s.PriorityLow.ForceVolumeValue); });
|
||||||
|
|
||||||
|
prefMsgNormEnableSound.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.EnableSound=b; saveAndUpdate(); });
|
||||||
|
prefMsgNormRingtone_container.setOnClickListener(a -> chooseRingtoneNorm());
|
||||||
|
prefMsgNormRepeatSound.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.RepeatSound=b; saveAndUpdate(); });
|
||||||
|
prefMsgNormEnableLED.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.EnableLED=b; saveAndUpdate(); });
|
||||||
|
prefMsgNormLedColor_container.setOnClickListener(a -> chooseLEDColorNorm());
|
||||||
|
prefMsgNormEnableVibrations.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.EnableVibration=b; saveAndUpdate(); });
|
||||||
|
prefMsgNormForceVolume.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.ForceVolume=b; saveAndUpdate(); });
|
||||||
|
prefMsgNormVolume.setOnSeekBarChangeListener(FI.SeekBarChanged((a,b,c) -> { if (c) { s.PriorityNorm.ForceVolumeValue=b; saveAndUpdate(); updateVolume(1, b); } }));
|
||||||
|
prefMsgNormVolumeTest.setOnClickListener((v) -> { if (s.PriorityNorm.ForceVolume) playTestSound(1, prefMsgNormVolumeTest, s.PriorityNorm.SoundSource, s.PriorityNorm.ForceVolumeValue); });
|
||||||
|
|
||||||
|
prefMsgHighEnableSound.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.EnableSound=b; saveAndUpdate(); });
|
||||||
|
prefMsgHighRingtone_container.setOnClickListener(a -> chooseRingtoneHigh());
|
||||||
|
prefMsgHighRepeatSound.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.RepeatSound=b; saveAndUpdate(); });
|
||||||
|
prefMsgHighEnableLED.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.EnableLED=b; saveAndUpdate(); });
|
||||||
|
prefMsgHighLedColor_container.setOnClickListener(a -> chooseLEDColorHigh());
|
||||||
|
prefMsgHighEnableVibrations.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.EnableVibration=b; saveAndUpdate(); });
|
||||||
|
prefMsgHighForceVolume.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.ForceVolume=b; saveAndUpdate(); });
|
||||||
|
prefMsgHighVolume.setOnSeekBarChangeListener(FI.SeekBarChanged((a,b,c) -> { if (c) { s.PriorityHigh.ForceVolumeValue=b; saveAndUpdate(); updateVolume(2, b); } }));
|
||||||
|
prefMsgHighVolumeTest.setOnClickListener((v) -> { if (s.PriorityHigh.ForceVolume) playTestSound(2, prefMsgHighVolumeTest, s.PriorityHigh.SoundSource, s.PriorityHigh.ForceVolumeValue); });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onExport()
|
||||||
|
{
|
||||||
|
Context ctxt = getContext();
|
||||||
|
if (ctxt == null) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File outputDir = ctxt.getCacheDir(); // context being the Activity pointer
|
||||||
|
File outputFile = File.createTempFile("scn_export_", ".dat", outputDir);
|
||||||
|
|
||||||
|
ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream(outputFile));
|
||||||
|
|
||||||
|
Map<String, ?> d1 = ctxt.getSharedPreferences("Config", Context.MODE_PRIVATE).getAll();
|
||||||
|
Map<String, ?> d2 = ctxt.getSharedPreferences("IAB", Context.MODE_PRIVATE).getAll();
|
||||||
|
Map<String, ?> d3 = ctxt.getSharedPreferences("CMessageList", Context.MODE_PRIVATE).getAll();
|
||||||
|
Map<String, ?> d4 = ctxt.getSharedPreferences("QueryLog", Context.MODE_PRIVATE).getAll();
|
||||||
|
|
||||||
|
output.writeObject(d1);
|
||||||
|
output.writeObject(d2);
|
||||||
|
output.writeObject(d3);
|
||||||
|
output.writeObject(d4);
|
||||||
|
|
||||||
|
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||||
|
|
||||||
|
Uri uri = FileProvider.getUriForFile(ctxt, "com.blackforestbytes.simplecloudnotifier.fileprovider", outputFile);
|
||||||
|
intent.putExtra(Intent.EXTRA_STREAM, uri);
|
||||||
|
intent.setType("*/*");
|
||||||
|
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
|
||||||
|
startActivity(Intent.createChooser(intent, "Export"));
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
Log.e("Export:Err", e.toString());
|
||||||
|
SCNApp.showToast("Export failed", Toast.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onImport()
|
||||||
|
{
|
||||||
|
SCNApp.getMainActivity().setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
Intent intent = new Intent()
|
||||||
|
.setType("*/*")
|
||||||
|
.setAction(Intent.ACTION_GET_CONTENT);
|
||||||
|
|
||||||
|
((MainActivity)getActivity()).startActivityForResult(Intent.createChooser(intent, "Select a file"), 1991);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEnabled(boolean prev, boolean now)
|
||||||
|
{
|
||||||
|
if (!prev && now)
|
||||||
|
{
|
||||||
|
SCNApp.showToast("SimpleCloudNotifier is now enabled", Toast.LENGTH_SHORT);
|
||||||
|
}
|
||||||
|
else if (prev && !now)
|
||||||
|
{
|
||||||
|
SCNApp.showToast("SimpleCloudNotifier is now disabled\nYou won't recieve new messages.", Toast.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateVolume(int idx, int volume)
|
||||||
|
{
|
||||||
|
if (mPlayers[idx] != null && mPlayers[idx].isPlaying())
|
||||||
|
{
|
||||||
|
AudioManager aman = (AudioManager) SCNApp.getContext().getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
int maxVolume = aman.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION);
|
||||||
|
aman.setStreamVolume(AudioManager.STREAM_NOTIFICATION, (int)(maxVolume * (volume / 100.0)), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopSound(final int idx, final ImageView iv)
|
||||||
|
{
|
||||||
|
if (mPlayers[idx] != null && mPlayers[idx].isPlaying())
|
||||||
|
{
|
||||||
|
mPlayers[idx].stop();
|
||||||
|
mPlayers[idx].release();
|
||||||
|
iv.setImageResource(R.drawable.ic_play);
|
||||||
|
mPlayers[idx] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void playTestSound(final int idx, final ImageView iv, String src, int volume)
|
||||||
|
{
|
||||||
|
if (mPlayers[idx] != null && mPlayers[idx].isPlaying())
|
||||||
|
{
|
||||||
|
mPlayers[idx].stop();
|
||||||
|
mPlayers[idx].release();
|
||||||
|
iv.setImageResource(R.drawable.ic_play);
|
||||||
|
mPlayers[idx] = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Str.isNullOrWhitespace(src)) return;
|
||||||
|
if (volume == 0) return;
|
||||||
|
|
||||||
|
Context ctxt = getContext();
|
||||||
|
if (ctxt == null) return;
|
||||||
|
|
||||||
|
iv.setImageResource(R.drawable.ic_pause);
|
||||||
|
|
||||||
|
AudioManager aman = (AudioManager) SCNApp.getContext().getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
int maxVolume = aman.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION);
|
||||||
|
aman.setStreamVolume(AudioManager.STREAM_NOTIFICATION, (int)(maxVolume * (volume / 100.0)), 0);
|
||||||
|
|
||||||
|
MediaPlayer player = mPlayers[idx] = new MediaPlayer();
|
||||||
|
player.setAudioAttributes(new AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_NOTIFICATION).build());
|
||||||
|
player.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
player.setDataSource(ctxt, Uri.parse(src));
|
||||||
|
player.setLooping(false);
|
||||||
|
player.setOnCompletionListener( mp -> SCNApp.runOnUiThread(() -> { mp.stop(); iv.setImageResource(R.drawable.ic_play); mPlayers[idx]=null; mp.release(); }));
|
||||||
|
player.setOnSeekCompleteListener(mp -> SCNApp.runOnUiThread(() -> { mp.stop(); iv.setImageResource(R.drawable.ic_play); mPlayers[idx]=null; mp.release(); }));
|
||||||
|
player.prepare();
|
||||||
|
player.start();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
Log.e("SFRAG:play", e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveAndUpdate()
|
||||||
|
{
|
||||||
|
SCNSettings.inst().save();
|
||||||
|
updateUI();
|
||||||
|
SCNApp.getMainActivity().adpTabs.tab1.updateDeleteSwipeEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onUpgradeAccount()
|
||||||
|
{
|
||||||
|
IABService.inst().purchase(getActivity(), IABService.IAB_PRO_MODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateProState()
|
||||||
|
{
|
||||||
|
boolean pmode = IABService.inst().getPurchaseCachedSimple(IABService.IAB_PRO_MODE);
|
||||||
|
|
||||||
|
if (prefUpgradeAccount != null) prefUpgradeAccount.setVisibility( pmode ? View.GONE : View.VISIBLE);
|
||||||
|
if (prefUpgradeAccount_info != null) prefUpgradeAccount_info.setVisibility(pmode ? View.GONE : View.VISIBLE);
|
||||||
|
if (prefUpgradeAccount_msg != null) prefUpgradeAccount_msg.setVisibility( pmode ? View.VISIBLE : View.GONE );
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getCacheSizeIndex(int value)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < SCNSettings.CHOOSABLE_CACHE_SIZES.length; i++)
|
||||||
|
{
|
||||||
|
if (SCNSettings.CHOOSABLE_CACHE_SIZES[i] == value) return i;
|
||||||
|
}
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void chooseRingtoneLow()
|
||||||
|
{
|
||||||
|
musicPickerSwitch = 1;
|
||||||
|
UltimateMusicPicker ump = new UltimateMusicPicker();
|
||||||
|
ump.windowTitle("Choose notification sound");
|
||||||
|
ump.removeSilent();
|
||||||
|
ump.streamType(AudioManager.STREAM_NOTIFICATION);
|
||||||
|
ump.ringtone();
|
||||||
|
ump.notification();
|
||||||
|
ump.alarm();
|
||||||
|
ump.music();
|
||||||
|
if (!SCNSettings.inst().PriorityLow.SoundSource.isEmpty())ump.selectUri(Uri.parse(SCNSettings.inst().PriorityLow.SoundSource));
|
||||||
|
ump.goWithDialog(getChildFragmentManager());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void chooseRingtoneNorm()
|
||||||
|
{
|
||||||
|
musicPickerSwitch = 2;
|
||||||
|
UltimateMusicPicker ump = new UltimateMusicPicker();
|
||||||
|
ump.windowTitle("Choose notification sound");
|
||||||
|
ump.removeSilent();
|
||||||
|
ump.streamType(AudioManager.STREAM_NOTIFICATION);
|
||||||
|
ump.ringtone();
|
||||||
|
ump.notification();
|
||||||
|
ump.alarm();
|
||||||
|
ump.music();
|
||||||
|
if (!SCNSettings.inst().PriorityNorm.SoundSource.isEmpty())ump.defaultUri(Uri.parse(SCNSettings.inst().PriorityNorm.SoundSource));
|
||||||
|
ump.goWithDialog(getChildFragmentManager());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void chooseRingtoneHigh()
|
||||||
|
{
|
||||||
|
musicPickerSwitch = 3;
|
||||||
|
UltimateMusicPicker ump = new UltimateMusicPicker();
|
||||||
|
ump.windowTitle("Choose notification sound");
|
||||||
|
ump.removeSilent();
|
||||||
|
ump.streamType(AudioManager.STREAM_NOTIFICATION);
|
||||||
|
ump.ringtone();
|
||||||
|
ump.notification();
|
||||||
|
ump.alarm();
|
||||||
|
ump.music();
|
||||||
|
if (!SCNSettings.inst().PriorityHigh.SoundSource.isEmpty())ump.defaultUri(Uri.parse(SCNSettings.inst().PriorityHigh.SoundSource));
|
||||||
|
ump.goWithDialog(getChildFragmentManager());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void chooseLEDColorLow()
|
||||||
|
{
|
||||||
|
new ColorPickerPopup.Builder(getContext())
|
||||||
|
.initialColor(SCNSettings.inst().PriorityLow.LEDColor) // Set initial color
|
||||||
|
.enableBrightness(true) // Enable brightness slider or not
|
||||||
|
.okTitle("Choose")
|
||||||
|
.cancelTitle("Cancel")
|
||||||
|
.showIndicator(true)
|
||||||
|
.showValue(false)
|
||||||
|
.build()
|
||||||
|
.show(getView(), new ColorPickerPopup.ColorPickerObserver()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onColorPicked(int color) {
|
||||||
|
SCNSettings.inst().PriorityLow.LEDColor = color;
|
||||||
|
saveAndUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onColor(int color, boolean fromUser) { }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void chooseLEDColorNorm()
|
||||||
|
{
|
||||||
|
new ColorPickerPopup.Builder(getContext())
|
||||||
|
.initialColor(SCNSettings.inst().PriorityNorm.LEDColor) // Set initial color
|
||||||
|
.enableBrightness(true) // Enable brightness slider or not
|
||||||
|
.okTitle("Choose")
|
||||||
|
.cancelTitle("Cancel")
|
||||||
|
.showIndicator(true)
|
||||||
|
.showValue(false)
|
||||||
|
.build()
|
||||||
|
.show(getView(), new ColorPickerPopup.ColorPickerObserver()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onColorPicked(int color) {
|
||||||
|
SCNSettings.inst().PriorityNorm.LEDColor = color;
|
||||||
|
saveAndUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onColor(int color, boolean fromUser) { }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void chooseLEDColorHigh()
|
||||||
|
{
|
||||||
|
new ColorPickerPopup.Builder(getContext())
|
||||||
|
.initialColor(SCNSettings.inst().PriorityHigh.LEDColor) // Set initial color
|
||||||
|
.enableBrightness(true) // Enable brightness slider or not
|
||||||
|
.okTitle("Choose")
|
||||||
|
.cancelTitle("Cancel")
|
||||||
|
.showIndicator(true)
|
||||||
|
.showValue(false)
|
||||||
|
.build()
|
||||||
|
.show(getView(), new ColorPickerPopup.ColorPickerObserver()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onColorPicked(int color) {
|
||||||
|
SCNSettings.inst().PriorityHigh.LEDColor = color;
|
||||||
|
saveAndUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onColor(int color, boolean fromUser) { }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMusicPick(@NotNull Uri uri, @NotNull String s)
|
||||||
|
{
|
||||||
|
if (musicPickerSwitch == 1) { SCNSettings.inst().PriorityLow.SoundSource =uri.toString(); SCNSettings.inst().PriorityLow.SoundName =s; saveAndUpdate(); }
|
||||||
|
if (musicPickerSwitch == 2) { SCNSettings.inst().PriorityNorm.SoundSource=uri.toString(); SCNSettings.inst().PriorityNorm.SoundName=s; saveAndUpdate(); }
|
||||||
|
if (musicPickerSwitch == 3) { SCNSettings.inst().PriorityHigh.SoundSource=uri.toString(); SCNSettings.inst().PriorityHigh.SoundName=s; saveAndUpdate(); }
|
||||||
|
|
||||||
|
musicPickerSwitch = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPickCanceled()
|
||||||
|
{
|
||||||
|
musicPickerSwitch = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onViewpagerHide()
|
||||||
|
{
|
||||||
|
stopSound(0, prefMsgLowVolumeTest);
|
||||||
|
stopSound(1, prefMsgNormVolumeTest);
|
||||||
|
stopSound(2, prefMsgHighVolumeTest);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.view;
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||||
|
|
||||||
|
public class TabAdapter extends FragmentStatePagerAdapter {
|
||||||
|
|
||||||
|
public NotificationsFragment tab1 = new NotificationsFragment();
|
||||||
|
public AccountFragment tab2 = new AccountFragment();
|
||||||
|
public SettingsFragment tab3 = new SettingsFragment();
|
||||||
|
|
||||||
|
public TabAdapter(FragmentManager fm)
|
||||||
|
{
|
||||||
|
super(fm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment getItem(int position) {
|
||||||
|
|
||||||
|
switch (position) {
|
||||||
|
case 0:
|
||||||
|
return tab1;
|
||||||
|
case 1:
|
||||||
|
return tab2;
|
||||||
|
case 2:
|
||||||
|
return tab3;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getPageTitle(int position)
|
||||||
|
{
|
||||||
|
switch (position)
|
||||||
|
{
|
||||||
|
case 0: return "Messages";
|
||||||
|
case 1: return "Account";
|
||||||
|
case 2: return "Settings";
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.view.debug;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SingleQuery;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import android.widget.ListView;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
|
||||||
|
public class QueryLogActivity extends AppCompatActivity
|
||||||
|
{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_querylog);
|
||||||
|
|
||||||
|
ListView lvMain = findViewById(R.id.lvQueryList);
|
||||||
|
SingleQuery[] arr = QueryLog.inst().get().toArray(new SingleQuery[0]);
|
||||||
|
QueryLogAdapter a = new QueryLogAdapter(this, arr);
|
||||||
|
lvMain.setAdapter(a);
|
||||||
|
|
||||||
|
lvMain.setOnItemClickListener((parent, view, position, id) ->
|
||||||
|
{
|
||||||
|
if (position >= 0 && position < arr.length)
|
||||||
|
{
|
||||||
|
Intent i = new Intent(QueryLogActivity.this, SingleQueryLogActivity.class);
|
||||||
|
Bundle b = new Bundle();
|
||||||
|
arr[position].save(b, "data");
|
||||||
|
i.putExtra("query", b);
|
||||||
|
startActivity(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.view.debug;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SingleQuery;
|
||||||
|
|
||||||
|
import org.joda.time.format.DateTimeFormat;
|
||||||
|
import org.joda.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
public class QueryLogAdapter extends ArrayAdapter<SingleQuery>
|
||||||
|
{
|
||||||
|
public static DateTimeFormatter UI_FULLTIME_FORMATTER = DateTimeFormat.forPattern("HH:mm:ss");
|
||||||
|
|
||||||
|
public QueryLogAdapter(@NonNull Context context, @NonNull SingleQuery[] objects)
|
||||||
|
{
|
||||||
|
super(context, R.layout.adapter_querylog, objects);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, @NonNull ViewGroup parent)
|
||||||
|
{
|
||||||
|
View v = convertView;
|
||||||
|
|
||||||
|
if (v == null) {
|
||||||
|
LayoutInflater vi;
|
||||||
|
vi = LayoutInflater.from(getContext());
|
||||||
|
v = vi.inflate(R.layout.adapter_querylog, parent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleQuery p = getItem(position);
|
||||||
|
|
||||||
|
if (p != null)
|
||||||
|
{
|
||||||
|
TextView tt1 = v.findViewById(R.id.list_item_debuglogrow_time);
|
||||||
|
if (tt1 != null) tt1.setText(p.Timestamp.toString(UI_FULLTIME_FORMATTER));
|
||||||
|
if (tt1 != null) tt1.setTextColor(Color.BLACK);
|
||||||
|
|
||||||
|
TextView tt2 = v.findViewById(R.id.list_item_debuglogrow_level);
|
||||||
|
if (tt2 != null) tt2.setText(p.Level.toUIString());
|
||||||
|
if (tt2 != null) tt2.setTextColor(Color.BLACK);
|
||||||
|
|
||||||
|
TextView tt3 = v.findViewById(R.id.list_item_debuglogrow_info);
|
||||||
|
if (tt3 != null) tt3.setText("");
|
||||||
|
if (tt3 != null) tt3.setTextColor(Color.BLUE);
|
||||||
|
|
||||||
|
TextView tt4 = v.findViewById(R.id.list_item_debuglogrow_id);
|
||||||
|
if (tt4 != null) tt4.setText(p.Name);
|
||||||
|
if (tt4 != null) tt4.setTextColor(p.Level.getColor());
|
||||||
|
|
||||||
|
TextView tt5 = v.findViewById(R.id.list_item_debuglogrow_message);
|
||||||
|
if (tt5 != null) tt5.setText(p.ExceptionString.length()> 40 ? p.ExceptionString.substring(0, 40-3)+"..." : p.ExceptionString);
|
||||||
|
if (tt5 != null) tt5.setTextColor(p.Level.getColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.view.debug;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.lib.string.CompactJsonFormatter;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SingleQuery;
|
||||||
|
|
||||||
|
import org.joda.time.format.DateTimeFormat;
|
||||||
|
import org.joda.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class SingleQueryLogActivity extends AppCompatActivity
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
protected void onCreate(Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_singlequerylog);
|
||||||
|
|
||||||
|
SingleQuery q = SingleQuery.load(getIntent().getBundleExtra("query"), "data");
|
||||||
|
|
||||||
|
this.<TextView>findViewById(R.id.tvQL_Timestamp).setText(q.Timestamp.toString(DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")));
|
||||||
|
this.<TextView>findViewById(R.id.tvQL_Level).setText(q.Level.toUIString());
|
||||||
|
this.<TextView>findViewById(R.id.tvQL_Level).setTextColor(q.Level.getColor());
|
||||||
|
this.<TextView>findViewById(R.id.tvQL_Name).setText(q.Name);
|
||||||
|
this.<TextView>findViewById(R.id.tvQL_URL).setText(q.URL.replace("?", "\r\n?").replace("&", "\r\n&"));
|
||||||
|
this.<TextView>findViewById(R.id.tvQL_Response).setText(CompactJsonFormatter.formatJSON(q.Response, 999));
|
||||||
|
this.<TextView>findViewById(R.id.tvQL_ResponseCode).setText(Integer.toString(q.ResponseCode));
|
||||||
|
this.<TextView>findViewById(R.id.tvQL_ExceptionString).setText(q.ExceptionString);
|
||||||
|
}
|
||||||
|
}
|
BIN
android/app/src/main/res/drawable-hdpi/ic_notification_white.png
Normal file
After Width: | Height: | Size: 472 B |
BIN
android/app/src/main/res/drawable-mdpi/ic_notification_white.png
Normal file
After Width: | Height: | Size: 320 B |
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
|
||||||
|
<solid android:color="@android:color/white" />
|
||||||
|
<stroke android:width="1dip" android:color="#888888"/>
|
||||||
|
</shape>
|
After Width: | Height: | Size: 551 B |
After Width: | Height: | Size: 949 B |
After Width: | Height: | Size: 1.0 KiB |
6
android/app/src/main/res/drawable/circle.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<vector android:height="24dp" android:viewportHeight="1000"
|
||||||
|
android:viewportWidth="1000" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000"
|
||||||
|
android:pathData="M500,500m-500,0a500,500 0,1 1,1000 0a500,500 0,1 1,-1000 0"
|
||||||
|
android:strokeColor="#000000" android:strokeWidth="1"/>
|
||||||
|
</vector>
|
6
android/app/src/main/res/drawable/ic_copy.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<vector android:height="24dp" android:viewportHeight="22"
|
||||||
|
android:viewportWidth="21" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#000000" android:fillType="evenOdd"
|
||||||
|
android:pathData="M14.5,0L2.5,0C1.4,0 0.5,0.9 0.5,2L0.5,16L2.5,16L2.5,2L14.5,2L14.5,0L14.5,0ZM17.5,4L6.5,4C5.4,4 4.5,4.9 4.5,6L4.5,20C4.5,21.1 5.4,22 6.5,22L17.5,22C18.6,22 19.5,21.1 19.5,20L19.5,6C19.5,4.9 18.6,4 17.5,4L17.5,4ZM17.5,20L6.5,20L6.5,6L17.5,6L17.5,20L17.5,20Z"
|
||||||
|
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||||
|
</vector>
|
7
android/app/src/main/res/drawable/ic_fuel.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<vector android:height="24dp" android:viewportHeight="100"
|
||||||
|
android:viewportWidth="100" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillAlpha="1" android:fillColor="#000000"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="m22.917,12.5c-2.308,0 -4.167,1.858 -4.167,4.167l0,66.667c0,2.083 2.083,4.167 4.167,4.167l37.5,0c2.083,0 4.167,-2.083 4.167,-4.167l0,-20.833 2.083,0c2.083,0 2.083,2.083 2.083,2.083l0,8.333c0,4.167 2.083,6.25 6.25,6.25 4.167,0 6.25,-2.083 6.25,-6.25 0,-4.861 0,-18.75 0,-22.917 0,-4.167 -8.333,-8.333 -8.333,-12.5l0,-12.5 -4.167,0 -4.167,-4.167 0,-4.167c0,-2.308 -1.858,-4.167 -4.167,-4.167zM27.083,20.833 L56.25,20.833 56.25,37.5 27.083,37.5zM64.583,33.333 L68.75,33.333c0,0 0,3.472 0,6.25 0,4.167 8.333,8.333 8.333,12.5l0,20.833C77.083,75 75,75 75,75c0,0 -2.083,0 -2.083,-2.083 0,0 0,-8.333 0,-10.417 0,-2.083 -2.083,-4.167 -4.167,-4.167 -1.389,0 -4.167,0 -4.167,0z"
|
||||||
|
android:strokeColor="#00000000" android:strokeWidth="2"/>
|
||||||
|
</vector>
|
4
android/app/src/main/res/drawable/ic_key.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<vector android:height="24dp" android:viewportHeight="48"
|
||||||
|
android:viewportWidth="48" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M25.3,20c-1.65,-4.66 -6.08,-8 -11.3,-8 -6.63,0 -12,5.37 -12,12s5.37,12 12,12c5.22,0 9.65,-3.34 11.3,-8h8.7v8h8v-8h4v-8h-20.7zM14,28c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4z"/>
|
||||||
|
</vector>
|
16
android/app/src/main/res/drawable/ic_pause.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<vector
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportHeight="438.536"
|
||||||
|
android:viewportWidth="438.536"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF3F51B5"
|
||||||
|
android:pathData="M164.453,0H18.276C13.324,0 9.041,1.807 5.425,5.424C1.808,9.04 0.001,13.322 0.001,18.271v401.991c0,4.948 1.807,9.233 5.424,12.847c3.619,3.617 7.902,5.428 12.851,5.428h146.181c4.949,0 9.231,-1.811 12.847,-5.428c3.617,-3.613 5.424,-7.898 5.424,-12.847V18.271c0,-4.952 -1.807,-9.231 -5.428,-12.847C173.685,1.807 169.402,0 164.453,0z"/>
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF3F51B5"
|
||||||
|
android:pathData="M433.113,5.424C429.496,1.807 425.215,0 420.267,0H274.086c-4.949,0 -9.237,1.807 -12.847,5.424c-3.621,3.615 -5.432,7.898 -5.432,12.847v401.991c0,4.948 1.811,9.233 5.432,12.847c3.609,3.617 7.897,5.428 12.847,5.428h146.181c4.948,0 9.229,-1.811 12.847,-5.428c3.614,-3.613 5.421,-7.898 5.421,-12.847V18.271C438.534,13.319 436.73,9.04 433.113,5.424z"/>
|
||||||
|
|
||||||
|
</vector>
|
12
android/app/src/main/res/drawable/ic_play.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<vector
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="41.999"
|
||||||
|
android:viewportWidth="41.999"
|
||||||
|
android:width="24dp"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF3F51B5"
|
||||||
|
android:pathData="M36.068,20.176l-29,-20C6.761,-0.035 6.363,-0.057 6.035,0.114C5.706,0.287 5.5,0.627 5.5,0.999v40c0,0.372 0.206,0.713 0.535,0.886c0.146,0.076 0.306,0.114 0.465,0.114c0.199,0 0.397,-0.06 0.568,-0.177l29,-20c0.271,-0.187 0.432,-0.494 0.432,-0.823S36.338,20.363 36.068,20.176z"/>
|
||||||
|
|
||||||
|
</vector>
|
6
android/app/src/main/res/drawable/ic_refresh.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<vector
|
||||||
|
android:width="24dp" android:height="24dp"
|
||||||
|
android:viewportWidth="16" android:viewportHeight="16"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#231F20" android:pathData="M14,8c-0.609,0-0.898,0.43-1,0.883C12.635,10.516,11.084,13,8,13c-0.757,0-1.473-0.172-2.114-0.474L6.414,12 C6.773,11.656,7,11.445,7,11c0-0.523-0.438-1-1-1H3c-0.609,0-1,0.492-1,1v3c0,0.541,0.428,1,1,1c0.484,0,0.688-0.273,1-0.594 l0.408-0.407C5.458,14.632,6.685,15,8,15c4.99,0,7-4.75,7-5.938C15,8.336,14.469,8,14,8z M3,7.117C3.365,5.485,4.916,3,8,3 c0.757,0,1.473,0.171,2.114,0.473L9.586,4C9.227,4.344,9,4.555,9,5c0,0.523,0.438,1,1,1h3c0.609,0,1-0.492,1-1V2 c0-0.541-0.428-1-1-1c-0.484,0-0.688,0.273-1,0.594l-0.408,0.407C10.542,1.368,9.315,1,8,1C3.01,1,1,5.75,1,6.938 C1,7.664,1.531,8,2,8C2.609,8,2.898,7.57,3,7.117z"/>
|
||||||
|
</vector>
|
10
android/app/src/main/res/drawable/ic_share_small.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="12sp"
|
||||||
|
android:height="12sp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
|
||||||
|
</vector>
|
16
android/app/src/main/res/drawable/ic_trash.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<vector
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportHeight="53"
|
||||||
|
android:viewportWidth="53"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:pathData="M42.943,6H33.5V3c0,-1.654 -1.346,-3 -3,-3h-8c-1.654,0 -3,1.346 -3,3v3h-9.443C8.096,6 6.5,7.596 6.5,9.557V14h2h36h2V9.557C46.5,7.596 44.904,6 42.943,6zM31.5,6h-10V3c0,-0.552 0.449,-1 1,-1h8c0.551,0 1,0.448 1,1V6z"/>
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:pathData="M8.5,49.271C8.5,51.327 10.173,53 12.229,53h28.541c2.057,0 3.729,-1.673 3.729,-3.729V16h-36V49.271z"/>
|
||||||
|
|
||||||
|
</vector>
|
16
android/app/src/main/res/drawable/ic_trash_small.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<vector
|
||||||
|
android:height="12sp"
|
||||||
|
android:width="12sp"
|
||||||
|
android:viewportHeight="53"
|
||||||
|
android:viewportWidth="53"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:pathData="M42.943,6H33.5V3c0,-1.654 -1.346,-3 -3,-3h-8c-1.654,0 -3,1.346 -3,3v3h-9.443C8.096,6 6.5,7.596 6.5,9.557V14h2h36h2V9.557C46.5,7.596 44.904,6 42.943,6zM31.5,6h-10V3c0,-0.552 0.449,-1 1,-1h8c0.551,0 1,0.448 1,1V6z"/>
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:pathData="M8.5,49.271C8.5,51.327 10.173,53 12.229,53h28.541c2.057,0 3.729,-1.673 3.729,-3.729V16h-36V49.271z"/>
|
||||||
|
|
||||||
|
</vector>
|
12
android/app/src/main/res/drawable/ic_user.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M7,8a5,6 0,1 0,10 0a5,6 0,1 0,-10 0z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M21.8,19.1c-0.9,-1.8 -2.6,-3.3 -4.8,-4.2c-0.6,-0.2 -1.3,-0.2 -1.8,0.1c-1,0.6 -2,0.9 -3.2,0.9s-2.2,-0.3 -3.2,-0.9C8.3,14.8 7.6,14.7 7,15c-2.2,0.9 -3.9,2.4 -4.8,4.2C1.5,20.5 2.6,22 4.1,22h15.8C21.4,22 22.5,20.5 21.8,19.1z"/>
|
||||||
|
</vector>
|
12
android/app/src/main/res/drawable/ic_volume.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<vector
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="459"
|
||||||
|
android:viewportWidth="459"
|
||||||
|
android:width="24dp"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF757575"
|
||||||
|
android:pathData="M0,153v153h102l127.5,127.5v-408L102,153H0zM344.25,229.5c0,-45.9 -25.5,-84.15 -63.75,-102v204C318.75,313.65 344.25,275.4 344.25,229.5zM280.5,5.1v53.55C354.45,81.6 408,147.899 408,229.5S354.45,377.4 280.5,400.35V453.9C382.5,430.949 459,339.15 459,229.5C459,119.85 382.5,28.049 280.5,5.1z"/>
|
||||||
|
|
||||||
|
</vector>
|
BIN
android/app/src/main/res/drawable/icon.png
Normal file
After Width: | Height: | Size: 114 KiB |
9
android/app/src/main/res/drawable/priority_high.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M21.4,10.6l-8,-8c-0.8,-0.8 -2,-0.8 -2.8,0l-8,8c-0.8,0.8 -0.8,2 0,2.8l8,8c0.8,0.8 2,0.8 2.8,0l8,-8C22.2,12.6 22.2,11.4 21.4,10.6zM13,17h-2v-2h2V17zM13,13h-2V7h2V13z"/>
|
||||||
|
</vector>
|
9
android/app/src/main/res/drawable/priority_low.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M21.4,10.6l-8,-8c-0.8,-0.8 -2,-0.8 -2.8,0l-8,8c-0.8,0.8 -0.8,2 0,2.8l8,8c0.8,0.8 2,0.8 2.8,0l8,-8C22.2,12.6 22.2,11.4 21.4,10.6zM12,17l-3,-3h2V7h2v7h2L12,17z"/>
|
||||||
|
</vector>
|
422
android/app/src/main/res/drawable/qr_default.xml
Normal file
@ -0,0 +1,422 @@
|
|||||||
|
<vector
|
||||||
|
android:width="24dp" android:height="24dp"
|
||||||
|
android:viewportWidth="296" android:viewportHeight="296"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,40h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,40h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M88,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M88,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M88,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M88,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M88,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M88,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M88,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,40h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,40h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,88h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,88h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,40h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,88h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,88h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,88h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,40h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,88h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,88h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,88h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,40h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,40h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,248h8v8h-8z"/>
|
||||||
|
</vector>
|
36
android/app/src/main/res/layout/activity_main.xml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout android:id="@+id/layoutRoot"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
app:titleTextColor="@color/colorOnPrimary"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
android:elevation="6dp"
|
||||||
|
android:minHeight="?attr/actionBarSize" />
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/tab_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/toolbar"
|
||||||
|
app:titleTextColor="@color/colorOnPrimary"
|
||||||
|
app:tabTextColor="@color/colorOnPrimary"
|
||||||
|
app:tabSelectedTextColor="@color/colorSecondary"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
android:elevation="6dp"
|
||||||
|
android:minHeight="?attr/actionBarSize" />
|
||||||
|
|
||||||
|
<androidx.viewpager.widget.ViewPager
|
||||||
|
android:id="@+id/pager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:layout_below="@id/tab_layout"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
13
android/app/src/main/res/layout/activity_querylog.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/layoutRoot"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/lvQueryList"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
240
android/app/src/main/res/layout/activity_singlequerylog.xml
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:padding="4sp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_margin="2dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:text="Server Query" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_margin="2dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:text="Timestamp" />
|
||||||
|
|
||||||
|
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
|
||||||
|
app:maxHeightOverride="100"
|
||||||
|
android:background="@drawable/simple_black_border"
|
||||||
|
android:layout_margin="2dip"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvQL_Timestamp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:padding="1dip"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:text="" />
|
||||||
|
|
||||||
|
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_margin="2dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:text="Level" />
|
||||||
|
|
||||||
|
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
|
||||||
|
app:maxHeightOverride="100"
|
||||||
|
android:background="@drawable/simple_black_border"
|
||||||
|
android:layout_margin="2dip"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvQL_Level"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:padding="1dip"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:text="" />
|
||||||
|
|
||||||
|
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_margin="2dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:text="Name" />
|
||||||
|
|
||||||
|
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
|
||||||
|
app:maxHeightOverride="100"
|
||||||
|
android:layout_margin="2dip"
|
||||||
|
android:background="@drawable/simple_black_border"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvQL_Name"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:padding="1dip"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:text="" />
|
||||||
|
|
||||||
|
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_margin="2dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:text="URL" />
|
||||||
|
|
||||||
|
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
|
||||||
|
app:maxHeightOverride="100"
|
||||||
|
android:layout_margin="2dip"
|
||||||
|
android:background="@drawable/simple_black_border"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvQL_URL"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:padding="1dip"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:text=""/>
|
||||||
|
|
||||||
|
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_margin="2dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:text="ResponeCode" />
|
||||||
|
|
||||||
|
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
|
||||||
|
app:maxHeightOverride="64"
|
||||||
|
android:layout_margin="2dip"
|
||||||
|
android:background="@drawable/simple_black_border"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvQL_ResponseCode"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:padding="1dip"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:text="" />
|
||||||
|
|
||||||
|
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_margin="2dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:text="Response" />
|
||||||
|
|
||||||
|
|
||||||
|
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
|
||||||
|
app:maxHeightOverride="100"
|
||||||
|
android:layout_margin="2dip"
|
||||||
|
android:background="@drawable/simple_black_border"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvQL_Response"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:padding="1dip"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:text="" />
|
||||||
|
|
||||||
|
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_margin="2dip"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:text="Exception" />
|
||||||
|
|
||||||
|
<com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView
|
||||||
|
app:maxHeightOverride="100"
|
||||||
|
android:layout_margin="2dip"
|
||||||
|
android:background="@drawable/simple_black_border"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvQL_ExceptionString"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:padding="1dip"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:text="" />
|
||||||
|
|
||||||
|
</com.blackforestbytes.simplecloudnotifier.util.MaxHeightScrollView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
50
android/app/src/main/res/layout/adapter_querylog.xml
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/list_item_imagerow"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="horizontal" >
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="96dp"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/list_item_debuglogrow_time"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/list_item_debuglogrow_level"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/list_item_debuglogrow_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/list_item_debuglogrow_id"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/list_item_debuglogrow_message"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|