Compare commits

...

409 Commits

Author SHA1 Message Date
7546c2a1a4
Fix test pipeline
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m17s
Build Docker and Deploy / Run Unit-Tests (push) Successful in 2m38s
Build Docker and Deploy / Deploy to Server (push) Successful in 9s
2024-09-20 21:23:26 +02:00
d21d775764
Add ListSenderNames api route and use params.Add(..) in Filter classes
Some checks failed
Build Docker and Deploy / Run Unit-Tests (push) Failing after 10s
Build Docker and Deploy / Build Docker Container (push) Successful in 1m14s
Build Docker and Deploy / Deploy to Server (push) Has been skipped
2024-09-20 20:37:55 +02:00
352f1ca0d1
Fully switch away from mattn sqlite to glebarez sqlite 2024-09-20 17:21:32 +02:00
584a9e983f
Add tests [TestListSenderNames] [TestListUserSenderNames] 2024-09-20 16:33:45 +02:00
5dd94eca38
Add test stage to pipeline 2024-09-20 15:39:15 +02:00
d8c06e3de2
Fix test [TestListMessagesFilterChannel] 2024-09-20 15:36:16 +02:00
3adeadf6fb
Work on implementing search filter in app [WIP] 2024-09-19 19:46:46 +02:00
9d35916280
Fix missing field in clients struct and non-partial fcmtoken index (also streamline db migrations)
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m52s
Build Docker and Deploy / Deploy to Server (push) Successful in 7s
2024-09-17 22:26:45 +02:00
4c7632a144
Set delivery to FAILURE if [client|user|message|channel] no longer exists
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m52s
Build Docker and Deploy / Deploy to Server (push) Successful in 6s
2024-09-17 20:49:10 +02:00
e329e13a02
Auto-delete clients when FB returns UNREGISTERED
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m53s
Build Docker and Deploy / Deploy to Server (push) Successful in 7s
2024-09-16 20:11:28 +02:00
7ddaf5d9aa
Migrate deliveries.next_delivery from type:string to type:int (SCNTime)
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m46s
Build Docker and Deploy / Deploy to Server (push) Successful in 6s
2024-09-16 18:26:28 +02:00
5da4c3d3b9
Fix dbConverter error when unmarshalling (failed) deliveries
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m48s
Build Docker and Deploy / Deploy to Server (push) Successful in 5s
2024-09-16 17:55:13 +02:00
fb1560a1f5
go generate
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 2m19s
Build Docker and Deploy / Deploy to Server (push) Successful in 13s
2024-09-16 16:46:26 +02:00
61d62f736c
Merge branch 'flutter_app' 2024-09-16 16:23:46 +02:00
77362f1651
Merge branch 'refactor_server' 2024-09-16 16:23:15 +02:00
fbb9cf68ab
Fix SQLITE_BUSY retry logic 2024-09-16 15:35:24 +02:00
ce641a3ffe
Implement in-application mutex to reduce DB_LOCKED errors 2024-09-16 15:17:20 +02:00
527a659a1b
Refactor models to use single struct per entity 2024-09-15 21:26:36 +02:00
6d432b9de4
Fix TestRequestLogAPI 2024-09-15 17:38:05 +02:00
23a9506dda
TODO's 2024-08-06 18:10:26 +02:00
be05bfadec
Fix TestRequestLogAPI 2024-07-17 13:39:44 +02:00
1971b07d39
new app icon [WIP] 2024-07-16 22:54:39 +02:00
ca05d6e3cc
Refactor server to go-sqlite and ginext [WIP] 2024-07-16 18:06:26 +02:00
c204dc5a8b
Refactor server to go-sqlite and ginext [WIP] 2024-07-16 17:20:00 +02:00
55d0dea835
Refactor server to go-sqlite and ginext [WIP] 2024-07-16 17:20:00 +02:00
e93d125431
Properly handle click actions on notifications 2024-07-13 01:05:32 +02:00
74a935f6f1
Fix scn-requests box not being open in _onBackgroundMessage 2024-07-13 00:17:22 +02:00
be7035978b
channel_message_list 2024-07-13 00:11:13 +02:00
778451fa4c
channel list fixes 2024-07-12 23:08:56 +02:00
89d1e0f641
edit displayName/descriptionName of channel 2024-06-26 14:54:34 +02:00
1f9b65652d
get channel->lastMessage from cache before hot-loading 2024-06-25 20:54:03 +02:00
2b23404461
channel_view page 2024-06-25 20:49:40 +02:00
e2dbe8866d
Channel List/view WIP 2024-06-25 12:00:34 +02:00
7dad61dbbb
Fix re-layout in message_view after data is loaded 2024-06-23 13:31:10 +02:00
9542405512
fix linebreaks in message.title in channel_list_item 2024-06-18 17:36:41 +02:00
59d28d3c49
auto-refresh message-list on FB message receive 2024-06-17 23:23:35 +02:00
600f3365f6
Disabled didPopNext() refresh of message_list 2024-06-17 22:54:45 +02:00
5b8a1e86e0
Save user+client in Prefs and only background-fetch them on startup 2024-06-17 22:53:03 +02:00
c8bc7665f7
Fix background messages in release-build 2024-06-17 22:26:48 +02:00
0bbe5fc7fa
Working on message search+filter 2024-06-16 01:46:27 +02:00
e9ea573e33
Notifications (android via local) work 2024-06-15 21:29:51 +02:00
e6fbf85e6e
Add channel_id to fcm data
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 2m8s
Build Docker and Deploy / Deploy to Server (push) Successful in 6s
2024-06-15 20:13:17 +02:00
b68f3bdb23
Merge branch 'flutter_app'
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 2m37s
Build Docker and Deploy / Deploy to Server (push) Successful in 12s
2024-06-15 18:24:49 +02:00
63c25317eb
Server: switch android back to data-only notifications 2024-06-15 18:24:33 +02:00
b0a6736f70
Prep flutter for android data-messages 2024-06-15 18:24:18 +02:00
c68a53e4cd
background-refresh after resume() 2024-06-15 17:19:23 +02:00
c4773d7b97
Add icon 2024-06-15 17:06:26 +02:00
eea219a205
More background refreshing 2024-06-15 16:35:02 +02:00
35ab9a26c0
Cache messages, use cache if exists, load in background 2024-06-15 15:56:50 +02:00
9c366399df
better message_item layout 2024-06-15 12:37:38 +02:00
8126327b95
Add LinearProgressIndicator for background queries 2024-06-13 17:00:08 +02:00
beb1005710
Refactor routing methods to Navi helper class 2024-06-13 15:42:39 +02:00
431d91a380
show sender-user in message_view 2024-06-12 01:17:00 +02:00
35771c11e7
add tests for /preview/* routes
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m46s
Build Docker and Deploy / Deploy to Server (push) Successful in 6s
2024-06-12 01:08:02 +02:00
2ccdb8b238
Lock /preview/* routes behind Any-Auth 2024-06-12 00:43:07 +02:00
dac268f40b
Merge branch 'flutter_app'
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m59s
Build Docker and Deploy / Deploy to Server (push) Successful in 7s
2024-06-12 00:36:12 +02:00
80b1351bd2
Add /preview/* routes 2024-06-12 00:35:06 +02:00
64709920f7
more message view stuff 2024-06-08 20:20:09 +02:00
b00ba83370
dart analyze 2024-06-08 14:16:07 +02:00
243a274480
fixes and ui.dart 2024-06-08 12:55:58 +02:00
95d51c82e9
a bit of work on the message page 2024-06-07 23:46:27 +02:00
549311535c
Fix linux build (does not support fcm) 2024-06-04 08:20:28 +02:00
8e0c8e825b
Delete temp files 2024-06-04 07:30:58 +02:00
3bae320e07
Remove collapse_key from android fcm request
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 2m6s
Build Docker and Deploy / Deploy to Server (push) Successful in 7s
2024-06-03 17:58:55 +02:00
ac299ec7ba
Better client/login/authState handling
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 2m58s
Build Docker and Deploy / Deploy to Server (push) Successful in 32s
2024-06-02 17:09:57 +02:00
ec506a7f9e
Add missing exerr.Init() to scnserver 2024-06-02 16:27:03 +02:00
e397f009b9
Notifications with new Flutter app [Kinda work!]
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m56s
Build Docker and Deploy / Deploy to Server (push) Successful in 5s
2024-06-01 15:37:59 +02:00
0560330f68
Fix 4->5 migration
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 2m1s
Build Docker and Deploy / Deploy to Server (push) Successful in 7s
2024-06-01 14:15:47 +02:00
a078ecaf7e
Merge branch 'flutter_app'
Some checks failed
Build Docker and Deploy / Build Docker Container (push) Successful in 2m18s
Build Docker and Deploy / Deploy to Server (push) Failing after 1m5s
2024-06-01 14:03:21 +02:00
d662a6c426
Implement login 2024-06-01 14:00:16 +02:00
16d97ad08f
Refactor client.descriptionName to client.name 2024-06-01 13:45:28 +02:00
189c3ab273
Add route "/users/:uid/keys/current" 2024-06-01 13:22:56 +02:00
0b7fb533da
FCM kinda works [does not receive notifications] 2024-06-01 03:06:02 +02:00
4c02afb957
Add description_name to clients 2024-06-01 01:01:58 +02:00
7553e1f51e
Upgrade goext 2024-06-01 00:25:56 +02:00
d5d89ee93a
Switch to better notification library 2024-05-31 23:56:07 +02:00
dfcee5dfc7
Implement FCM [WIP] 2024-05-31 15:22:27 +02:00
0ad82bb248
better account page (logged in) 2024-05-27 17:21:29 +02:00
38e1c1133d
Merge branch 'master' into flutter_app 2024-05-26 19:33:44 +02:00
97a834ae6a
Increase RequestMaxRetry and decrease RequestRetrySleep (also remove CreateChanel struct from 77cfe750)
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m37s
Build Docker and Deploy / Deploy to Server (push) Successful in 10s
2024-05-26 19:33:19 +02:00
6b7bf600f8
channel list 2024-05-26 19:24:19 +02:00
f9dbbf4638
fix initial load bug 2024-05-26 18:34:42 +02:00
dae5182f90
added logs 2024-05-26 00:20:25 +02:00
51e89ce901
fix imports 2024-05-25 22:06:43 +02:00
72be45feb4
linter and lint fixes 2024-05-25 21:36:05 +02:00
7e347a70c2
Hive, requestlog, etc 2024-05-25 18:09:39 +02:00
227d7871c2
add sqlite dep 2024-05-23 20:05:55 +02:00
f5813a5489
debug view && priority in listview 2024-05-23 17:41:51 +02:00
34925b6678
Implement message_list 2024-05-21 23:20:34 +02:00
20358a700a
Upgrade dependencies 2024-05-21 20:54:23 +02:00
373683bbf0
flutter android build 2024-03-02 00:26:47 +01:00
77cfe75043 Added Optional channel description name to channel creation, adjusted tests
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 2m22s
Build Docker and Deploy / Deploy to Server (push) Successful in 11s
2024-02-24 12:20:41 +01:00
56d49d8c5e
channel and message lists 2024-02-18 17:36:58 +01:00
1286a5d848
send page qr code 2024-02-18 16:23:10 +01:00
463e7ee287
persistance 2024-02-11 02:20:48 +01:00
46897cc51b
basic api access, state managment etc 2024-02-11 01:08:51 +01:00
306d9a006a
basic setup (layout + icons) 2024-02-10 21:46:25 +01:00
7e798eb606
init flutter project 2024-02-10 18:29:41 +01:00
0bc064b4ba
Update scnsend script: "bugfix for big files in --scnsend-read-body-from-file" 2023-12-31 15:15:27 +01:00
35a97be4c4
Fix NPE in compatHandler 2023-12-01 22:05:36 +01:00
9f3e183d72
Update goext to v0.0.291 2023-10-26 13:14:11 +02:00
51f5f1005a
Switch to new Swaggo Makefile template
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m57s
Build Docker and Deploy / Deploy to Server (push) Successful in 12s
2023-10-17 16:47:50 +02:00
0a380f861e
Add scn_send.sh to repo 2023-10-14 21:37:00 +02:00
b712ad3488
Use better go guard in Makefile::clean
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 3m44s
Build Docker and Deploy / Deploy to Server (push) Successful in 12s
2023-08-16 09:48:28 +02:00
9f656bdefe
Refactor message sending into logic package (+ more tests for uptime-kuma)
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m33s
Build Docker and Deploy / Deploy to Server (push) Successful in 7s
2023-08-12 19:07:39 +02:00
a4a651229c
Added gitea-actions workflow
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m43s
Build Docker and Deploy / Deploy to Server (push) Successful in 7s
2023-08-12 15:51:14 +02:00
4773800f23
remove old PHP project 2023-08-12 11:14:32 +02:00
bef0b8189e
uptime kuma webhook endpoint 2023-08-12 11:14:32 +02:00
674714f0f3
Return more data in /users/{uid} 2023-07-30 16:53:46 +02:00
ee9e858584
Increase pro quota and bodysize 2023-07-30 16:37:39 +02:00
165c6d8614
Refactor API of /api/v2/users/{uid}/subscriptions 2023-07-30 15:58:37 +02:00
8a6719fc19
Remove message.owner_user_id field and implement db migrations 2023-07-27 17:44:06 +02:00
308361a834
Prevent deleting messages of subscribed-only channels 2023-07-27 15:23:56 +02:00
44df964f6f
todos 2023-07-04 11:31:52 +02:00
56bf266919
fix scn_send script with non-urlencoded data 2023-06-26 14:49:14 +02:00
f3658d6636
fix wrong data in compat_ids (requery.php) 2023-06-23 11:50:18 +02:00
1bb37eec30
TODO's 2023-06-18 14:28:58 +02:00
59511b2345
Fix bug in migration script 2023-06-18 14:16:37 +02:00
5b7bc02c61
Fix validation in web form 2023-06-18 13:25:00 +02:00
b329f537e7
Fix message_sent html 2023-06-18 13:19:51 +02:00
5879e81759
Enable RequestLog on dev/stag/prod 2023-06-18 13:11:48 +02:00
f4e88bef77
Fix NPE in compat-ack 2023-06-18 13:09:36 +02:00
b3ec45309c
Insert exclam on compat clients if message uses old channel syntax 2023-06-18 11:59:26 +02:00
2fbc892898
various fixes in scn_send script 2023-06-18 04:45:28 +02:00
c46190c3fc
Support x-www-form-urlencoded form-data 2023-06-18 03:46:01 +02:00
860e540de1
Better CreateKey API (make all_channels and channels optional) 2023-06-18 02:54:41 +02:00
8cde286cac
Fix failing tests 2023-06-18 02:36:44 +02:00
90830fe384
Fix empty string in channels field in GetKeys route 2023-06-18 02:34:04 +02:00
686f89f75d
change URL to simplecloudnotifier.de 2023-06-18 02:22:29 +02:00
4210af5680
Properly implement compat unack_count 2023-06-18 02:09:05 +02:00
aefc368cfd
Send compat-msgid to compat clients (BF old android client) 2023-06-18 01:55:58 +02:00
67218d8045
Added a few logs 2023-06-18 01:36:34 +02:00
c05deb3a41
allow \n in private-key envs 2023-06-18 01:29:13 +02:00
43d0107fb5
Update goext (fix bool env parsing) 2023-06-18 01:18:33 +02:00
ece7612f9d
more migration fixes 2023-06-18 00:49:29 +02:00
a9809d90cb
fix exception in js-send (logic.js) 2023-06-18 00:25:10 +02:00
bbc9a79996
Fix bug in migration script 2023-06-18 00:24:53 +02:00
b71f1885ec
Fix wrongly named env variables 2023-06-17 23:40:46 +02:00
885aad2047
Update migrationscript 2023-06-17 23:23:54 +02:00
7121afab08
Fix [TestQuotaExceededNoPro, TestQuotaExceededPro] 2023-06-17 20:16:02 +02:00
d9a4c4ffd6
Fix [TestChannelMessageCounter] 2023-06-17 20:08:39 +02:00
fb826919a6
added linter 2023-06-14 15:03:37 +02:00
22720169a2
Tests[TestUserMessageCounter, TestTokenKeysMessageCounter, TestChannelMessageCounter] 2023-06-10 03:41:54 +02:00
7fefd251db
Update TODO.md 2023-06-10 00:15:42 +02:00
5de4f67344
use nil-safe json renderer in ginresp 2023-06-09 21:37:30 +02:00
d396a12d68
fix missing error on missing json header 2023-06-09 20:14:30 +02:00
3888c91a6b
Switch to multi-stage Dockerbuild 2023-06-06 18:52:03 +02:00
562bac6987
Move enum-generate to goext 2023-06-05 13:37:49 +02:00
e825b4dd85
Update PrimaryHash3 2023-05-30 14:25:55 +02:00
08587b7a7a
Tests[**Subscription**] 2023-05-29 01:51:51 +02:00
0daca2cf8f
added UpdateClient route 2023-05-28 23:25:18 +02:00
3a9b15c2be
Use sq.InsertSingle to insert entities 2023-05-28 22:27:38 +02:00
e9b4db0f1c
Validate db schema before startup 2023-05-28 19:59:57 +02:00
312a31ce9e
Fix missing certificates in docker 2023-05-28 17:54:53 +02:00
d4a8a2e720
Tests[SendWithAdminKey, SendWithSendKey, SendWithReadKey, SendWithPermissionSendKey] 2023-05-28 17:38:19 +02:00
dcb4f253d8
fix swagger errors 2023-05-28 17:04:44 +02:00
d0a04bae84
Move to multistage Dockerfile 2023-05-28 16:03:14 +02:00
34ac96edd7
Tests[...]:
- SearchMessageFTSMulti
 - ListMessagesFilteredChannels
 - ListMessagesFilteredChannelIDs
 - ListMessagesFilteredSenders
 - ListMessagesFilteredTime
 - ListMessagesFilteredPriority
 - ListMessagesFilteredKeyTokens
2023-05-28 15:46:46 +02:00
b42ce84c3e
Added [Channels, ChannelIDs, Senders, TimeBefore, TimeAfter, Priority, KeyTokens] filter to ListMessages 2023-05-28 14:05:53 +02:00
2db779b44f
split api.go into multiple files 2023-05-28 13:39:20 +02:00
397bfe78aa
Remove Channel/Username normalization (except TrimSpace) 2023-05-28 13:14:05 +02:00
efaad3f97c
Fix RequestLogCollectorJob sometimes not properly shutting down 2023-05-28 12:31:14 +02:00
624c613bd1
Tests[ListChannelMessagesOfUnsubscribed, ListChannelMessagesOfUnconfirmed1, ListChannelMessagesOfUnconfirmed2, ListChannelMessagesOfRevokedConfirmation] 2023-05-28 03:38:33 +02:00
07b0632c95
Tests[ListChannelSubscriptions] 2023-05-28 03:16:29 +02:00
3d1e6cfa17
Tests[ListSubscribedChannelMessages] 2023-05-28 02:50:55 +02:00
3db636d41a
Tests[ListChannelMessages] 2023-05-28 02:40:24 +02:00
2053b8f07f
Tests[ListMessages, ListMessagesPaginated, ListMessagesPaginatedInvalid] 2023-05-28 02:27:15 +02:00
b1681b53e4
Tests[TokenKeys, TokenKeysInitial, TokenKeysCreate, TokenKeysUpdate, TokenKeysDelete, TokenKeysDeleteSelf, TokenKeysDowngradeSelf, TokenKeysPermissions] 2023-05-28 01:39:55 +02:00
03f60ff316
Tests[RequestLogSimple] 2023-05-27 23:54:14 +02:00
b2df0a5a02
Send channel as prefix for compat clients 2023-05-27 20:02:16 +02:00
8826cb0312
Save used keytoken in messages 2023-05-27 18:51:20 +02:00
a0c72f5b94
Add keytoken explanation to api_more.html 2023-05-27 18:16:32 +02:00
7d9a58ae54
Fix swagger 2023-05-27 17:51:56 +02:00
fd72b512f8
Directly use pygmentize in Makefile (for scn script in Website) 2023-05-27 17:42:06 +02:00
28c2721036
Use gotestsum for make test 2023-05-27 15:39:07 +02:00
a1788bf75a
use swaggo 1.8.12 2023-04-25 20:08:30 +02:00
b1bd278f9b
Add KeyToken authorization 2023-04-21 21:45:16 +02:00
16f6ab4861
re-implement ack behaviour from version 1.0 for compat 2023-02-03 22:51:03 +01:00
01934e29b1
todos 2023-02-03 20:12:41 +01:00
d1cefb0150
API versioning ( basePath == /api/v2/* ) 2023-01-27 10:04:06 +01:00
27b189d33a
BF 2023-01-24 13:52:11 +01:00
e05d88682a
Tests[ListClients] 2023-01-18 21:56:37 +01:00
2a5f1f5f7e
Tests[CompatUpdate, CompatUpdateFCM] 2023-01-17 23:10:38 +01:00
e7a45d9a05
Tests[UgradeUserToPro, DowngradeUserToNonPro, FailedUgradeUserToPro, FailToCreateProUser, ReuseProToken] 2023-01-17 22:56:04 +01:00
ec9a326002
Tests[CompatUpgrade] 2023-01-17 22:32:13 +01:00
23c7729fcf
Tests[CompatRegisterPro] 2023-01-17 22:03:27 +01:00
7fcd324299
Tests[CompatRequery] 2023-01-17 21:47:53 +01:00
1633449638
Tests[CompatExpand] 2023-01-17 21:30:53 +01:00
57231a1406
Tests[CompatAck] 2023-01-17 21:14:42 +01:00
2eb6292733
improve AssertEqual to handle annoying json.Unmarshal type conversions... 2023-01-17 20:41:45 +01:00
ff24493ff3
Tests[CompatRegister, CompatInfo] 2023-01-17 20:27:20 +01:00
3d602af135
Tests[TestSendCompatMessageByQuery, TestSendCompatMessageByFormData] 2023-01-16 20:29:49 +01:00
590665a5e9
create compat-id for messages && TestCreateCompatUser 2023-01-16 18:53:22 +01:00
89fd0dfed7
create migration script for old data 2023-01-15 06:30:30 +01:00
82bc887767
Move to string-ids for all entities (compat translation for existing data) 2023-01-14 00:48:51 +01:00
acd7de0dee
cherry-pick caller logprint fix from psycho-backend 2023-01-13 17:51:55 +01:00
e737cd9d5c
requests-log db 2023-01-13 17:17:17 +01:00
0ec7a9d274
add methods for (some) missing test cases 2023-01-13 12:54:19 +01:00
e49d9159e4
Added freely editable description_name field to channel 2023-01-13 12:43:20 +01:00
3343285761
goext 55 2023-01-06 02:03:10 +01:00
14bba38324
migrate to multiple sqlite db files ( primary + requests + logs ) 2023-01-06 00:39:21 +01:00
679277d59e
catch panic in gin 2022-12-28 00:32:15 +01:00
cebb2ae2b6
small cleanups 2022-12-23 20:27:21 +01:00
56d9f977ae
Tests[ListChannelsDefault, ListChannelsOwned, ListChannelsSubscribedAny, ListChannelsAllAny, ListChannelsSubscribed, ListChannelsAll] 2022-12-22 17:29:59 +01:00
984470b47d
Fix sql-preprocessor leading to deadlocks in parallel requests 2022-12-22 16:51:04 +01:00
0112d681ac
Fix SQL unmarshalling of optional nested structs (LEFT JOIN) 2022-12-22 12:43:40 +01:00
0cb2a977a0
Save internal_name and display_name in channel 2022-12-22 11:22:36 +01:00
f65c231ba0
Properly shutdown database on SIGTERM 2022-12-22 10:21:10 +01:00
dbc014f819
Added a SQL-Preprocessor - this way we can unmarshal recursive structures (LEFT JOIN etc) 2022-12-21 18:14:13 +01:00
bbf7962e29
move server/* to scnserver/* 2022-12-21 12:35:56 +01:00
2b4d77bab4
Cleaner swagger routes 2022-12-21 11:03:31 +01:00
8582674b44
Refactoring 2022-12-20 13:55:09 +01:00
f7675be834
Use multiple DB connections but retry failed requests 2022-12-20 09:52:33 +01:00
00d77e508d
Fix TestSendParallel by using only a single DB connection
see https://github.com/mattn/go-sqlite3/issues/274
see https://github.com/mattn/go-sqlite3/issues/209
see https://stackoverflow.com/questions/32479071/sqlite3-error-database-is-locked-in-golang
2022-12-20 09:22:18 +01:00
e90cfe34e9
switch to new registry image-name 2022-12-16 14:57:17 +01:00
54dfd535a4
Move parseConfOverride() to goext 2022-12-16 01:07:48 +01:00
5a02eb6d18
Prefix all config key with SCN_* 2022-12-14 18:46:26 +01:00
97fc9319d1
Fix wrong env keys in config.go 2022-12-14 18:43:32 +01:00
03b4acd13e
Tests[CreateProUser] 2022-12-14 18:38:30 +01:00
86f06a3c6a
Tests[CreateChannel, CreateChannelNameTooLong, ChannelNameNormalization] 2022-12-14 18:27:22 +01:00
06e8d2a6e2
Tests[SendLongContentPro] 2022-12-14 18:18:02 +01:00
99f248a8ce
Tests[SendParallel] (skipped for now) 2022-12-14 18:08:03 +01:00
c7aaa6ad98
Tests[QuotaExeededPro] 2022-12-14 17:56:14 +01:00
cb5ce66c1a
Tests[QuotaExeededNoPro] 2022-12-14 17:56:03 +01:00
0750bf1d8a
cleanup test local-url 2022-12-14 17:02:18 +01:00
203360e8b5
Tests[SendToTooLongChannel] 2022-12-14 16:57:08 +01:00
ef1844109f
Tests[SendToManualChannel] 2022-12-14 16:38:01 +01:00
de6ad35f60
new Endpoint: CreateChannel(*) 2022-12-14 14:29:59 +01:00
fbb289dedf
Added error descriptions to swagger 2022-12-14 14:27:41 +01:00
f1e87170f0
Tests[SendToNewChannel] 2022-12-14 14:30:34 +01:00
66ecad27a7
Only soft-delete messages 2022-12-14 12:29:55 +01:00
98b1e8bd80
move ScanAll/ScanSingle in sq package 2022-12-11 03:14:42 +01:00
26cd1533b4
Tests[SearchMessageFTSSimple] 2022-12-11 02:47:23 +01:00
3692b915f3
Messagefilter (+FTS) [WIP] 2022-12-10 03:38:48 +01:00
06788c3e12
TestData-Factory [WIP] 2022-12-09 00:40:50 +01:00
edfcdd1135
TestData-Factory [WIP] 2022-12-09 00:13:10 +01:00
dd2f3baa0c
Properly close db cursors after use 2022-12-08 11:31:52 +01:00
7db70e392b
Simplify fts table schema 2022-12-07 23:43:52 +01:00
0cae24a612
Move sq + ParseDurShortString() to goext and change conf values by env 2022-12-07 23:32:58 +01:00
8db0fa37db
Move to own sql abstraction on top of jmoiron/sqlx 2022-12-07 22:11:44 +01:00
d27e3d9a91
Made sqlite tables strict (type checked) 2022-12-07 22:11:07 +01:00
fa5a4107a6
Added FTS5 table to schema (full-text-search) 2022-12-07 22:10:46 +01:00
234188c4d4
Tests[SendCompat] 2022-12-01 14:45:31 +01:00
9b700581f3
Tests[SendSimpleMessageAlt1] 2022-12-01 14:30:46 +01:00
12db23d076
Improve test performance (better waiting logic until http server is up) 2022-11-30 23:46:28 +01:00
fd182f0abb
Fix timeout in ReadSchema/GetMeta etc method (fixes /health call taking 2 seconds) 2022-11-30 22:59:33 +01:00
7eab74e65c
Tests[SendWithTimestamp, SendInvalidTimestamp] 2022-11-30 22:29:12 +01:00
e0ecd4d9ff
Tests[SendInvalidPriority] 2022-11-30 21:51:48 +01:00
1ca09c16d3
Tests[SendWithPriority] 2022-11-30 21:39:14 +01:00
a7df476e79
Tests[SendIdempotent] 2022-11-30 21:17:29 +01:00
4e5eac6178
Tests[SendLongContent, LongContent, LongTitle] 2022-11-30 20:59:01 +01:00
91a6808ad2
Tests[SendWithSendername] 2022-11-30 20:47:43 +01:00
11a6517156
Tests[SendContentMessage] 2022-11-30 20:39:04 +01:00
7aa7eb234d
Tests[SendSimpleMessageQuery, SendSimpleMessageForm, SendSimpleMessageFormAndQuery, SendSimpleMessageJSONAndQuery] 2022-11-30 20:23:31 +01:00
62d7df9710
Tests[TestSendSimpleMessageJSON] 2022-11-30 17:58:04 +01:00
0ff1188c3d
Added swagger themes 2022-11-30 16:46:55 +01:00
b6e8d037a0
Add json tags to query structs (otherwise swag does not get the correct names) 2022-11-30 16:46:14 +01:00
7a11b2c76f
Tests[UpdateUsername, RecreateKeys, DeleteUser] 2022-11-30 13:57:55 +01:00
7f56dbdbfa
Tests[GetClient, CreateClient, DeleteClient, ReuseFCM] 2022-11-30 12:40:03 +01:00
df4eb15df8
Tests[CreateUser] 2022-11-30 10:35:05 +01:00
ac9ae06cc8
Save SenderName || SenderIP per message 2022-11-29 11:07:15 +01:00
464cf3ec7e
Better error message on missing envs 2022-11-26 17:03:26 +01:00
bf0ce5c963
dark-mode 2022-11-26 16:30:30 +01:00
3a0c65a849
Added google androidpublisher/v3 api to verify google purchase tokens 2022-11-25 22:42:21 +01:00
6d80638cf8
CreateUser test 2022-11-24 12:53:27 +01:00
37e09d6532
cleanup swagger 2022-11-23 22:12:47 +01:00
8ea3fdcfef
tests (boilerplate) 2022-11-23 20:21:49 +01:00
1bc847cdc9
tags/grouping for API 2022-11-23 19:32:23 +01:00
03c35d6446
update HTML with new methods 2023-06-18 04:07:13 +02:00
d5aea1a828
README 2022-11-21 18:46:55 +01:00
f17ddb4ace
switch to debian base-image (no more static linking) 2022-11-20 22:18:48 +01:00
0cc6e27267
Use ID types 2022-11-20 22:18:24 +01:00
ca58aa782d
Routes to refresh user and channel keys 2022-11-20 21:35:08 +01:00
e8671e8650
Selector param for ListChannels() 2022-11-20 21:15:06 +01:00
d46601be5c
CreateMessage() 2022-11-20 20:34:18 +01:00
d30e2cefc0
firebase via REST (less dependencies) 2023-06-18 04:06:52 +02:00
08a93551e7
DeliveryRetryJob 2022-11-20 15:40:22 +01:00
c2899fd727
swagger doku for compat methods 2022-11-20 13:18:09 +01:00
5ec66e1777
cleanup 2022-11-20 12:59:43 +01:00
516809cd02
Dockerfile, CONF_NS and fix sqlite3 under alpine 2022-11-20 03:41:38 +01:00
0d3526221d
replace PHP in html with js & bugfixes 2022-11-20 03:18:23 +01:00
728b12107f
compat methods 2022-11-20 01:28:32 +01:00
b56c021356
ListChannelMessages() 2022-11-20 00:30:30 +01:00
80f3b982d2
ListMessages() 2022-11-20 00:21:59 +01:00
0d641b727f
CreateSubscription(), UpdateSubscription(), GetMessage(), DeleteMessage() 2022-11-19 23:16:54 +01:00
8278c059ad
fix context in methods.go 2022-11-19 17:15:46 +01:00
7af0ff5413
TODO's 2022-11-19 17:09:23 +01:00
5c2877bdb8
ListChannels(), GetChannel(), ListUserSubscriptions(), ListChannelSubscriptions(), GetSubscription(), CancelSubscription() 2022-11-19 17:07:30 +01:00
85bfe79115
SendMessage() 2022-11-19 16:29:14 +01:00
fb37f94c0a
firebase implementation 2022-11-19 15:11:36 +01:00
e53f40866e
DeleteClient() 2022-11-19 12:59:25 +01:00
650ba20e5d
AddClient() 2022-11-19 12:56:44 +01:00
6e01c41c22
GetClient() 2022-11-19 12:50:41 +01:00
f555f0f1cf
ListClients() 2022-11-19 12:47:23 +01:00
35ef2175bc
UpdateUser() works 2022-11-18 23:33:07 +01:00
55f53deadf
GetUser() works 2022-11-18 23:12:37 +01:00
5991631bfa
POST:/users works 2022-11-18 21:25:40 +01:00
34a27d9ca4
schema 3.0 2022-11-17 21:26:52 +01:00
1671490485
implement a bit of the register.php call (and the DB schema) 2022-11-13 22:31:28 +01:00
0e58a5c5f0
added template for new golang backend 2022-11-13 19:25:44 +01:00
bfb-vserver-wwwdata
bd11d7973c server BF 2022-10-17 21:35:06 +02:00
f3b5b09ed0
A few code fixes 2020-11-04 10:08:06 +01:00
ce641bf7d2
Update dependencies 2020-11-03 14:41:20 +01:00
f1c7314dca
better message for ERR::CONTENT_TOO_LONG 2020-08-03 13:25:49 +02:00
019408dc6d
use LONGTEXT for content column 2020-04-21 10:45:03 +02:00
jenkins
cdba3540c2 [Jenkins] Increment version 2020-03-05 15:29:40 +00:00
885d997ff3
fix exceptions in register.php 2020-03-05 16:26:58 +01:00
5118ab3cbf
Fix notifications not click-able 2020-03-05 16:20:25 +01:00
jenkins
2812377f5c [Jenkins] Increment version 2020-01-05 22:19:08 +00:00
93b40a9c7f
skip version code 20 2020-01-05 23:18:20 +01:00
b95ddcc811
backup/export und import 2020-01-05 22:59:57 +01:00
90ba3c1134
fix api urls 2020-01-05 22:33:58 +01:00
05174958b2
Added php example 2019-06-30 21:46:28 +02:00
jenkins
24be9b2013 [Jenkins] Increment version 2018-12-14 22:09:01 +01:00
29ce4b727c
More fixes for paid mode 2018-12-14 22:07:43 +01:00
jenkins
f178019ffe [Jenkins] Increment version 2018-12-14 19:32:22 +01:00
0a1b948042
Stupid bug -.- 2018-12-14 19:23:36 +01:00
jenkins
74cbfb235e [Jenkins] Increment version 2018-12-14 18:38:12 +01:00
63c4104a89
Add recieved messages to ComLog 2018-12-14 18:35:51 +01:00
2597287b1e
Fixed wrong pro-mode reset 2018-12-14 18:30:03 +01:00
jenkins
316156f0f0 [Jenkins] Increment version 2018-12-11 13:55:38 +01:00
5286e869cc
config for preview-line-count 2018-12-11 13:53:47 +01:00
d6dcf28d89
Share+Delete Button 2018-12-11 13:22:39 +01:00
1d983b9ac0
Collapse message on click-again 2018-12-11 12:31:44 +01:00
e525221010
Show Querylog via hidden shit 2018-12-11 12:25:10 +01:00
4cde4703f2
fixed exception in requery.php 2018-12-11 10:40:30 +01:00
4a07e58c16
fix expand + requery 2018-11-25 21:03:05 +01:00
b12356575a
Description text correction 2018-11-25 18:11:42 +01:00
77f571de7d
Send timestamp with request 2018-11-25 18:02:25 +01:00
jenkins
f5eef7563b [Jenkins] Increment version 2018-11-23 19:40:31 +01:00
741c09b1e4
fixed TabLayout animation 2018-11-23 19:35:22 +01:00
0c0d7d181f
enable light in notification channel (android-oreo) + shorter vibrate 2018-11-23 18:48:46 +01:00
9304da9422
fix NPE in SettingsFrame 2018-11-23 18:42:10 +01:00
jenkins
d7afdd00f2 [Jenkins] Increment version 2018-11-19 18:43:36 +01:00
b780ccea1c
wrong notification channel name 2018-11-19 18:41:56 +01:00
jenkins
5d8e871871 [Jenkins] Increment version 2018-11-19 12:31:22 +01:00
4655d688c9
README 2018-11-18 17:19:15 +01:00
8263c0ad95
phone image 2018-11-18 17:12:44 +01:00
a701afd09b
OWA tracking 2018-11-18 03:34:13 +01:00
jenkins
1e02d8c01f [Jenkins] Increment version 2018-11-18 03:24:46 +01:00
e4651375aa
fix qr url 2018-11-18 03:23:22 +01:00
jenkins
afce4c6391 [Jenkins] Increment version 2018-11-18 00:15:21 +01:00
e95d0cb010
added disable warning 2018-11-18 00:13:36 +01:00
f717355519
use "messages" everywhere instead of "notifications" 2018-11-18 00:11:30 +01:00
3368e514ca
red quota icon if OOQ 2018-11-18 00:02:00 +01:00
9dca27177f
fixed js logic for errors 2018-11-17 23:59:57 +01:00
c63274f7a9
show subtext on android-o 2018-11-17 23:48:39 +01:00
6c2d2d2345
fixed requery only getting first message 2018-11-17 22:51:02 +01:00
a6fbaa192f
config example 2018-11-17 19:28:44 +01:00
jenkins
a01f156535 [Jenkins] Increment version 2018-11-17 19:00:13 +01:00
3b021c09dc
notification icons 2018-11-17 18:58:18 +01:00
287176c881
screenshots 2018-11-17 18:21:53 +01:00
84eeb5a002
red high-prio icons 2018-11-17 18:21:25 +01:00
b892532023
send big content over FCM 2018-11-17 17:59:43 +01:00
jenkins
56cdc52bb4 [Jenkins] Increment version 2018-11-17 17:19:59 +01:00
9ef6b1dd91
swipe-to-delete setting 2018-11-17 17:18:15 +01:00
6f7585323b
fixed notification being overwritten 2018-11-17 17:06:34 +01:00
92135e64a3
css 2018-11-17 17:00:14 +01:00
28efeea30b
message delete problems 2018-11-17 16:45:42 +01:00
a8a074907b
fixed update when in message-list 2018-11-17 16:02:31 +01:00
6c29ec9820
max-size = 2048 chars (FCM restrictions) 2018-11-17 15:44:44 +01:00
jenkins
8ec23144ca [Jenkins] Increment version 2018-11-17 14:28:04 +01:00
b703853b28
send.bash example 2018-11-17 14:26:29 +01:00
6f7529fc9b
fixed duplicate-recieve 2018-11-17 14:01:51 +01:00
27b45098f0
ups 2018-11-17 13:46:23 +01:00
21f66d99cd
fixed timestamp format in requery 2018-11-17 13:44:42 +01:00
1745051e6e
fixed info.php return value 2018-11-17 12:51:20 +01:00
jenkins
b395e054e6 [Jenkins] Increment version 2018-11-17 03:25:54 +01:00
90f4270b74
expand on click 2018-11-17 03:24:10 +01:00
f68d75c226
swipe to delete 2018-11-17 03:09:18 +01:00
9cf5133469
get nonack on info-call 2018-11-17 02:09:43 +01:00
75929aad48
rename php files 2018-11-17 01:30:41 +01:00
dca149ec4e
move api to sub-dir 2018-11-17 01:29:12 +01:00
75a7b97d24
audio playback on notification stream 2018-11-17 01:21:53 +01:00
eb62873fb6
Android O repeat sound 2018-11-17 00:52:44 +01:00
f8effe7d1e
no LED with android O 2018-11-17 00:24:22 +01:00
87a3d34315
scroll to newest message 2018-11-17 00:07:41 +01:00
0f38ad6e5c
default_quota=50 2018-11-16 23:53:47 +01:00
0ec6ab71b2
fixed layout wrap in settings 2018-11-16 23:51:46 +01:00
b42204c87d
fixed NPE on message recieve if app is not active 2018-11-16 23:28:48 +01:00
53cb259ec1
stupid sound shit 2018-11-13 18:11:17 +01:00
0150cc9e8e
force POST in send.php 2018-11-13 16:23:04 +01:00
20214473f1
html styling 2018-11-13 16:06:22 +01:00
jenkins
586ac07ef3 [Jenkins] Increment version 2018-11-12 18:27:08 +01:00
66f82f26d5
channel bullshit 2018-11-12 18:25:41 +01:00
6cb2aa00fb
intermed 2018-11-12 17:23:19 +01:00
96a236803e
simplify settings layout 2018-11-12 16:29:43 +01:00
9d24044eb3
center pro message 2018-11-12 16:11:31 +01:00
90248bcb54
confirm-dialog 2018-11-12 16:04:59 +01:00
a3b2a1b14f
only one account per fcm_token (2) 2018-11-12 16:01:06 +01:00
b21b159b95
fix user_key reset in promode 2018-11-12 15:58:59 +01:00
93ec261dc0
ack messages on recieve 2018-11-12 15:51:06 +01:00
ae246e9219
favicon 2018-11-12 15:28:07 +01:00
3f18fdd35a
only one account per fcm_token 2018-11-12 15:22:21 +01:00
faf5207478
fixed wrong sql key 2018-11-12 15:11:26 +01:00
71f003dd66
fix error in send.php 2018-11-12 15:07:48 +01:00
3f85ab514e
prevent purchase re-use 2018-11-12 14:57:54 +01:00
9eb5a6b1b9
fix NPE 2018-11-12 14:57:44 +01:00
8e26cd6078
fixed order verify 2018-11-12 14:49:09 +01:00
36b9263730
fix json_decode problems 2018-11-12 14:24:11 +01:00
3d29fecaec
gracefully handle user_not_found 2018-11-12 14:23:50 +01:00
jenkins
92ac05f1e3 [Jenkins] Increment version 2018-11-12 13:06:38 +01:00
cae6ff6271
implement usr_msg_id 2018-11-12 13:03:56 +01:00
2163ae4dbf
index_more.php 2018-11-12 12:41:59 +01:00
083945852b
in-app-purchase: pro-mode 2018-11-12 00:28:57 +01:00
jenkins
e69b1232d7 [Jenkins] Increment version 2018-11-11 19:38:22 +01:00
ac681065fa
billing permission 2018-11-11 19:36:58 +01:00
90d8b46179
actually use settings 2018-10-22 16:29:37 +02:00
jenkins
280b5ca4aa [Jenkins] Increment version 2018-10-22 15:29:28 +02:00
656 changed files with 559564 additions and 1739 deletions

View File

@ -0,0 +1,83 @@
# 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
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
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
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
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

22
README.md Normal file
View File

@ -0,0 +1,22 @@
SimpleCloudNotifier [![Get it in Google Play](data/README/badge_google.png)](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
![](store/screenshot_1.png) ![](store/screenshot_2.png) ![](store/screenshot_3.png)

View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WizardSettings">
<option name="children">
<map>
<entry key="vectorWizard">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="vectorAssetStep">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="assetSourceType" value="FILE" />
<entry key="outputName" value="priority_low" />
<entry key="sourceFile" value="C:\Users\Mike\Downloads\Low Priority-595b40b75ba036ed117d9842.svg" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</component>
</project>

View File

@ -1,29 +1,113 @@
<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>
<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>

View 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>

View 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>

40
android/.idea/jarRepositories.xml generated Normal file
View 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
View 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>

263
android/.idea/other.xml generated Normal file
View File

@ -0,0 +1,263 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="direct_access_persist.xml">
<option name="deviceSelectionList">
<list>
<PersistentDeviceSelectionData>
<option name="api" value="27" />
<option name="brand" value="DOCOMO" />
<option name="codename" value="F01L" />
<option name="id" value="F01L" />
<option name="manufacturer" value="FUJITSU" />
<option name="name" value="F-01L" />
<option name="screenDensity" value="360" />
<option name="screenX" value="720" />
<option name="screenY" value="1280" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="28" />
<option name="brand" value="DOCOMO" />
<option name="codename" value="SH-01L" />
<option name="id" value="SH-01L" />
<option name="manufacturer" value="SHARP" />
<option name="name" value="AQUOS sense2 SH-01L" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2160" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="samsung" />
<option name="codename" value="a51" />
<option name="id" value="a51" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy A51" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="akita" />
<option name="id" value="akita" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="b0q" />
<option name="id" value="b0q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S22 Ultra" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3088" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="32" />
<option name="brand" value="google" />
<option name="codename" value="bluejay" />
<option name="id" value="bluejay" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 6a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="29" />
<option name="brand" value="samsung" />
<option name="codename" value="crownqlteue" />
<option name="id" value="crownqlteue" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Note9" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2220" />
<option name="screenY" value="1080" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="dm3q" />
<option name="id" value="dm3q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S23 Ultra" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3088" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="felix" />
<option name="id" value="felix" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="felix_camera" />
<option name="id" value="felix_camera" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold (Camera-enabled)" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="gts8uwifi" />
<option name="id" value="gts8uwifi" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Tab S8 Ultra" />
<option name="screenDensity" value="320" />
<option name="screenX" value="1848" />
<option name="screenY" value="2960" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="husky" />
<option name="id" value="husky" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8 Pro" />
<option name="screenDensity" value="390" />
<option name="screenX" value="1008" />
<option name="screenY" value="2244" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="motorola" />
<option name="codename" value="java" />
<option name="id" value="java" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="G20" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="lynx" />
<option name="id" value="lynx" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 7a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="google" />
<option name="codename" value="oriole" />
<option name="id" value="oriole" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 6" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="panther" />
<option name="id" value="panther" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 7" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="samsung" />
<option name="codename" value="q2q" />
<option name="id" value="q2q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold3" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1768" />
<option name="screenY" value="2208" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="q5q" />
<option name="id" value="q5q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold5" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1812" />
<option name="screenY" value="2176" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="google" />
<option name="codename" value="r11" />
<option name="id" value="r11" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Watch" />
<option name="screenDensity" value="320" />
<option name="screenX" value="384" />
<option name="screenY" value="384" />
<option name="type" value="WEAR_OS" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="google" />
<option name="codename" value="redfin" />
<option name="id" value="redfin" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 5" />
<option name="screenDensity" value="440" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="shiba" />
<option name="id" value="shiba" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="tangorpro" />
<option name="id" value="tangorpro" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Tablet" />
<option name="screenDensity" value="320" />
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="29" />
<option name="brand" value="samsung" />
<option name="codename" value="x1q" />
<option name="id" value="x1q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S20" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1440" />
<option name="screenY" value="3200" />
</PersistentDeviceSelectionData>
</list>
</option>
</component>
</project>

View File

@ -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>

View File

@ -1,7 +1,7 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
compileSdkVersion 30
def versionPropsFile = file('version.properties')
def vNumber
@ -16,7 +16,7 @@ android {
defaultConfig {
applicationId "com.blackforestbytes.simplecloudnotifier"
minSdkVersion 21
targetSdkVersion 28
targetSdkVersion 30
versionCode vNumber
versionName vName
}
@ -35,75 +35,82 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.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.0.0'
implementation 'com.google.firebase:firebase-core:16.0.4'
implementation 'com.google.firebase:firebase-messaging:17.3.4'
implementation 'com.google.android.gms:play-services-ads:17.0.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:3.10.0'
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'
task updateVersion << {
def lastTag = ['git', 'describe', "--abbrev=0", "--tags"].execute().text.trim()
tasks.register("updateVersion") {
group = 'Custom'
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) }
doLast {
def lastTag = ['git', 'describe', "--abbrev=0", "--tags"].execute().text.trim()
def matcher = lastTag =~ /^v([0-9]+)\.([0-9]+)\.([0-9]+)$/
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) }
if (!matcher.matches()) throw new Exception("Last Tag ('" + lastTag + "') has invalid format :(")
def matcher = lastTag =~ /^v([0-9]+)\.([0-9]+)\.([0-9]+)$/
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 (!matcher.matches()) throw new Exception("Last Tag ('" + lastTag + "') has invalid format :(")
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()
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 (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 ""
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()
vCode++
new File(".do_publish_prod_release").createNewFile()
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 ""
versionProps['VERSION_NAME'] = vName.toString()
versionProps['VERSION_CODE'] = vCode.toString()
vCode++
new File(".do_publish_prod_release").createNewFile()
versionPropsFile.newWriter().withCloseable { w -> versionProps.store(w, null) }
} else {
println ""
println "==============================================================="
println "(!) This is a new beta release - create deployment trigger file"
println "==============================================================="
println ""
versionProps['VERSION_NAME'] = vName.toString()
versionProps['VERSION_CODE'] = vCode.toString()
vCode++
new File(".do_publish_beta_release").createNewFile()
versionPropsFile.newWriter().withCloseable { w -> versionProps.store(w, null) }
} else {
println ""
println "==============================================================="
println "(!) This is a new beta release - create deployment trigger file"
println "==============================================================="
println ""
versionProps['VERSION_NAME'] = vName.toString()
versionProps['VERSION_CODE'] = vCode.toString()
vCode++
new File(".do_publish_beta_release").createNewFile()
versionPropsFile.newWriter().withCloseable { w -> versionProps.store(w, null) }
versionProps['VERSION_NAME'] = vName.toString()
versionProps['VERSION_CODE'] = vCode.toString()
versionPropsFile.newWriter().withCloseable { w -> versionProps.store(w, null) }
}
}
}

View File

@ -3,36 +3,66 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.blackforestbytes.simplecloudnotifier">
<uses-permission android:name="android.permission.INTERNET"/>
<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:name="SCNApp"
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>
<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"/>
<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>
<service android:name=".service.FBMService" android:exported="false">
<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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -4,6 +4,8 @@ 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;
@ -18,7 +20,7 @@ import androidx.lifecycle.ProcessLifecycleOwner;
public class SCNApp extends Application implements LifecycleObserver
{
private static SCNApp instance;
private static WeakReference<MainActivity> mainActivity;
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");
@ -96,4 +98,7 @@ public class SCNApp extends Application implements LifecycleObserver
{
isBackground = false;
}
}
}
//TODO: Config for collapsed line count
//TODO: Sometimes ads but promode

View File

@ -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());
}
}
}

View File

@ -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;
}
}

View File

@ -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() { }
}

View File

@ -0,0 +1,10 @@
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
public class NInt
{
public int Value;
public NInt(int v) { Value = v; }
private NInt() { }
}

View File

@ -0,0 +1,6 @@
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
public final class Nothing
{
public final static Nothing Inst = new Nothing();
}

View File

@ -0,0 +1,11 @@
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
public class Tuple1<T1>
{
public final T1 Item1;
public Tuple1(T1 i1)
{
Item1 = i1;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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) {
}
};
}
}

View File

@ -0,0 +1,9 @@
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
@FunctionalInterface
public interface Func0to0 {
Func0to0 EMPTY = ()->{};
void invoke();
}

View File

@ -0,0 +1,6 @@
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
@FunctionalInterface
public interface Func0to1<TResult> {
TResult invoke();
}

View File

@ -0,0 +1,8 @@
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
import java.io.IOException;
@FunctionalInterface
public interface Func0to1WithIOException<TResult> {
TResult invoke() throws IOException;
}

View File

@ -0,0 +1,6 @@
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
@FunctionalInterface
public interface Func1to0<TInput1> {
void invoke(TInput1 value);
}

View File

@ -0,0 +1,6 @@
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
@FunctionalInterface
public interface Func1to1<TInput1, TResult> {
TResult invoke(TInput1 value);
}

View File

@ -0,0 +1,6 @@
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
@FunctionalInterface
public interface Func2to0<TInput1, TInput2> {
void invoke(TInput1 value1, TInput2 value2);
}

View File

@ -0,0 +1,6 @@
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
@FunctionalInterface
public interface Func2to1<TInput1, TInput2, TResult> {
TResult invoke(TInput1 value1, TInput2 value2);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -9,7 +9,10 @@ import java.util.TimeZone;
public class CMessage
{
public final long Timestamp ;
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;
@ -21,8 +24,9 @@ public class CMessage
_format.setTimeZone(TimeZone.getDefault());
}
public CMessage(long t, String mt, String mc, PriorityEnum p)
public CMessage(long id, long t, String mt, String mc, PriorityEnum p)
{
SCN_ID = id;
Timestamp = t;
Title = mt;
Content = mc;

View File

@ -3,15 +3,23 @@ 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<>();
@ -28,54 +36,93 @@ public class CMessageList
private CMessageList()
{
Messages = new ArrayList<>();
reloadPrefs();
}
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
int count = sharedPref.getInt("message_count", 0);
for (int i=0; i < count; i++)
public void reloadPrefs()
{
synchronized (msg_lock)
{
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));
Messages = new ArrayList<>();
AllAcks = new HashSet<>();
Messages.add(new CMessage(time, title, content, prio));
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 time, final String title, final String content, final PriorityEnum pe)
public CMessage add(final long scnid, final long time, final String title, final String content, final PriorityEnum pe)
{
CMessage msg = new CMessage(time, title, content, 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);
SharedPreferences.Editor e = sharedPref.edit();
synchronized (msg_lock)
{
Messages.add(msg);
AllAcks.add(Long.toHexString(msg.SCN_ID));
Messages.add(msg);
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);
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
}
e.apply();
for (WeakReference<MessageAdapter> ref : _listener)
{
MessageAdapter a = ref.get();
if (a == null) continue;
a.customNotifyItemInserted(count);
a.customNotifyDataSetChanged();
a.scrollToTop();
}
CleanUpListener();
});
if (!run)
{
Messages.add(new CMessage(time, title, content, pe));
fullSave();
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;
@ -97,33 +144,52 @@ public class CMessageList
public void fullSave()
{
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++)
synchronized (msg_lock)
{
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);
}
CollectionHelper.sort_inplace(Messages, (a,b) -> Long.compare(a.Timestamp, b.Timestamp));
e.apply();
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)
{
if (pos < 0 || pos >= Messages.size()) return null;
return Messages.get(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()
{
return Messages.size();
synchronized (msg_lock)
{
return Messages.size();
}
}
public void register(MessageAdapter adp)
@ -139,4 +205,33 @@ public class CMessageList
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
}
}

View File

@ -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; // ????
}
}

View File

@ -1,17 +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 = false;
public String SoundName = "";
public String SoundSource = Uri.EMPTY.toString();
public boolean RepeatSound = false;
public boolean EnableSound;
public String SoundName;
public String SoundSource;
public boolean RepeatSound;
public boolean EnableLED = false;
public int LEDColor = Color.BLUE;
public boolean ForceVolume;
public int ForceVolumeValue;
public boolean EnableVibration = false;
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;
}
}

View File

@ -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());
}
}
}

View File

@ -7,19 +7,27 @@ import android.util.Log;
import android.view.View;
import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.google.firebase.iid.FirebaseInstanceId;
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 SCNSettings _inst = null;
private static volatile SCNSettings _inst = null;
public static SCNSettings inst()
{
synchronized (_lock)
SCNSettings local = _inst;
if (local == null)
{
if (_inst != null) return _inst;
return _inst = new SCNSettings();
synchronized (_lock)
{
local = _inst;
if (local == null) _inst = local = new SCNSettings();
}
}
return local;
}
// ------------------------------------------------------------
@ -36,18 +44,29 @@ public class SCNSettings
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();
public final NotificationSettings PriorityNorm = new NotificationSettings();
public final NotificationSettings PriorityHigh = new NotificationSettings();
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);
@ -57,33 +76,44 @@ public class SCNSettings
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.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.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.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()
@ -91,39 +121,50 @@ public class SCNSettings
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.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("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: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: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: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();
}
@ -133,65 +174,100 @@ public class SCNSettings
return user_id>=0 && user_key != null && !user_key.isEmpty();
}
public String createOnlineURL()
public String createOnlineURL(boolean longurl)
{
if (!isConnected()) return ServerCommunication.BASE_URL + "index.php";
return ServerCommunication.BASE_URL + "index.php?preset_user_id="+user_id+"&preset_user_key="+user_key;
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)
public void setServerToken(String token, View loader, boolean force)
{
if (isConnected())
{
fcm_token_local = token;
save();
if (!fcm_token_local.equals(fcm_token_server)) ServerCommunication.update(user_id, user_key, fcm_token_local, loader);
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);
ServerCommunication.register(fcm_token_local, loader, promode_local, promode_token);
updateProState(loader);
}
}
public void work(Activity a)
// called at app start
public void work(Activity a, boolean force)
{
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
FirebaseInstallations.getInstance().getId().addOnSuccessListener(a, newToken ->
{
String newToken = instanceIdResult.getToken();
Log.e("FB::GetInstanceId", newToken);
SCNSettings.inst().setServerToken(newToken, null);
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.update(user_id, user_key, loader);
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
{
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
// get token then register
FirebaseInstallations.getInstance().getId().addOnSuccessListener(a, newToken ->
{
String newToken = instanceIdResult.getToken();
Log.e("FB::GetInstanceId", newToken);
SCNSettings.inst().setServerToken(newToken, loader);
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);
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);
}
}

View File

@ -4,11 +4,18 @@ 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;
@ -19,18 +26,20 @@ import okhttp3.ResponseBody;
public class ServerCommunication
{
public static final String BASE_URL = /*SCNApp.LOCAL_DEBUG ? "http://localhost:1010/" : */"https://scn.blackforestbytes.com/";
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)
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)
.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()
@ -38,43 +47,46 @@ public class ServerCommunication
@Override
public void onFailure(Call call, IOException e)
{
e.printStackTrace();
SCNApp.showToast("Communication with server failed", 4000);
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");
String r = responseBody.string();
Log.d("Server::Response", r);
r = responseBody.string();
Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json.getBoolean("success"))
if (!json_bool(json, "success"))
{
SCNApp.showToast(json.getString("message"), 4000);
SCNApp.showToast(json_str(json, "message"), 4000);
handleNonSuccess("register", call, response, r);
return;
}
SCNSettings.inst().user_id = json.getInt("user_id");
SCNSettings.inst().user_key = json.getString("user_key");
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.getInt("quota");
SCNSettings.inst().quota_max = json.getInt("quota_max");
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)
{
e.printStackTrace();
SCNApp.showToast("Communication with server failed", 4000);
handleError("register", call, response, r, false, e);
}
finally
{
@ -85,12 +97,11 @@ public class ServerCommunication
}
catch (Exception e)
{
e.printStackTrace();
SCNApp.showToast("Communication with server failed", 4000);
handleError("register", null, null, Str.Empty, false, e);
}
}
public static void update(int id, String key, String token, View loader)
public static void updateFCMToken(int id, String key, String token, View loader)
{
try
{
@ -103,42 +114,46 @@ public class ServerCommunication
@Override
public void onFailure(Call call, IOException e)
{
e.printStackTrace();
SCNApp.showToast("Communication with server failed", 4000);
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");
String r = responseBody.string();
Log.d("Server::Response", r);
r = responseBody.string();
Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json.getBoolean("success"))
if (!json_bool(json, "success"))
{
SCNApp.showToast(json.getString("message"), 4000);
SCNApp.showToast(json_str(json, "message"), 4000);
handleNonSuccess("update<1>", call, response, r);
return;
}
SCNSettings.inst().user_id = json.getInt("user_id");
SCNSettings.inst().user_key = json.getString("user_key");
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.getInt("quota");
SCNSettings.inst().quota_max = json.getInt("quota_max");
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)
{
e.printStackTrace();
handleError("update<1>", call, response, r, false, e);
SCNApp.showToast("Communication with server failed", 4000);
}
finally
@ -150,12 +165,11 @@ public class ServerCommunication
}
catch (Exception e)
{
e.printStackTrace();
SCNApp.showToast("Communication with server failed", 4000);
handleError("update<1>", null, null, Str.Empty, false, e);
}
}
public static void update(int id, String key, View loader)
public static void resetSecret(int id, String key, View loader)
{
try
{
@ -165,39 +179,51 @@ public class ServerCommunication
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
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) {
try (ResponseBody responseBody = response.body()) {
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");
String r = responseBody.string();
Log.d("Server::Response", r);
r = responseBody.string();
Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json.getBoolean("success")) {
SCNApp.showToast(json.getString("message"), 4000);
if (!json_bool(json, "success")) {
SCNApp.showToast(json_str(json, "message"), 4000);
handleNonSuccess("update<2>", call, response, r);
return;
}
SCNSettings.inst().user_id = json.getInt("user_id");
SCNSettings.inst().user_key = json.getString("user_key");
SCNSettings.inst().quota_curr = json.getInt("quota");
SCNSettings.inst().quota_max = json.getInt("quota_max");
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();
} catch (Exception e) {
e.printStackTrace();
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 {
}
finally
{
SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE);
});
@ -207,8 +233,7 @@ public class ServerCommunication
}
catch (Exception e)
{
e.printStackTrace();
SCNApp.showToast("Communication with server failed", 4000);
handleError("update<2>", null, null, Str.Empty, false, e);
}
}
@ -223,40 +248,146 @@ public class ServerCommunication
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
SCNApp.showToast("Communication with server failed", 4000);
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) {
try (ResponseBody responseBody = response.body()) {
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");
String r = responseBody.string();
Log.d("Server::Response", r);
r = responseBody.string();
Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json.getBoolean("success")) {
SCNApp.showToast(json.getString("message"), 4000);
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.getInt("user_id");
SCNSettings.inst().quota_curr = json.getInt("quota");
SCNSettings.inst().quota_max = json.getInt("quota_max");
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();
} catch (Exception e) {
e.printStackTrace();
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 {
}
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);
});
@ -266,8 +397,290 @@ public class ServerCommunication
}
catch (Exception e)
{
e.printStackTrace();
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());
}
}
}

View File

@ -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)
);
}
}

View File

@ -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);
}
}

View File

@ -4,13 +4,21 @@ 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
@ -25,6 +33,8 @@ public class FBMService extends FirebaseMessagingService
{
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());
@ -34,17 +44,20 @@ public class FBMService extends FirebaseMessagingService
String title = remoteMessage.getData().get("title");
String content = remoteMessage.getData().get("body");
PriorityEnum prio = PriorityEnum.parseAPI(remoteMessage.getData().get("priority"));
CMessage msg = CMessageList.inst().add(time, title, content, prio);
long scn_id = Long.parseLong(remoteMessage.getData().get("scn_msg_id"));
boolean trimmed = Boolean.parseBoolean(remoteMessage.getData().get("trimmed"));
if (SCNApp.isBackground())
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)
{
NotificationService.inst().show(msg);
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
{
SCNApp.showToast("Message recieved: " + title, Toast.LENGTH_LONG);
recieveData(time, title, content, prio, scn_id, false);
}
}
catch (Exception e)
@ -53,4 +66,27 @@ public class FBMService extends FirebaseMessagingService
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);
}
}

View File

@ -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);
}
}

View File

@ -1,23 +1,37 @@
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_ID = "CHAN_BFB_SCN_MESSAGES";
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;
@ -32,39 +46,222 @@ public class NotificationService
private NotificationService()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
Context ctxt = SCNApp.getContext();
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Push notifications", NotificationManager.IMPORTANCE_HIGH);
channel.setDescription("Messages from the API");
channel.setLightColor(Color.rgb(255, 0, 0));
channel.setVibrationPattern(new long[]{200});
channel.enableLights(true);
channel.enableVibration(true);
NotificationManager notificationManager = ctxt.getSystemService(NotificationManager.class);
if (notificationManager != null) notificationManager.createNotificationChannel(channel);
}
createChannels();
}
public void show(CMessage msg)
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();
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_bfb)
.setContentTitle(msg.Title)
.setContentText(msg.Content)
.setShowWhen(true)
.setWhen(msg.Timestamp)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true);
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 (mNotificationManager != null) mNotificationManager.notify(0, mBuilder.build());
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
}
}

View File

@ -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; }
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -1,15 +1,19 @@
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;
@ -21,6 +25,7 @@ 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;
@ -55,20 +60,52 @@ public class AccountFragment extends Fragment
v.findViewById(R.id.btnAccountReset).setOnClickListener(cv ->
{
View lpnl = v.findViewById(R.id.loadingPanel);
lpnl.setVisibility(View.VISIBLE);
SCNSettings.inst().reset(lpnl);
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 ->
{
CMessageList.inst().clear();
SCNApp.showToast("Notifications cleared", 1000);
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()));
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(SCNSettings.inst().createOnlineURL(true)));
startActivity(browserIntent);
});
@ -91,10 +128,11 @@ public class AccountFragment extends Fragment
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);
ImageButton btnQR = v.findViewById(R.id.btnQR);
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();
@ -103,7 +141,8 @@ public class AccountFragment extends Fragment
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()).to(ImageType.PNG).withSize(512, 512).bitmap());
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
{
@ -111,6 +150,7 @@ public class AccountFragment extends Fragment
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);
}
}
}

View File

@ -1,36 +1,56 @@
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 org.jetbrains.annotations.NotNull;
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);
@ -41,9 +61,25 @@ public class MainActivity extends AppCompatActivity
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.setupWithViewPager(viewPager);
SCNApp.register(this);
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { /* */ }
SCNSettings.inst().work(this);
@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
@ -51,6 +87,138 @@ public class MainActivity extends AppCompatActivity
{
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);
}
}
}
}

View File

@ -1,25 +1,44 @@
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;
public MessageAdapter(View noElementsView)
private WeakHashMap<MessagePresenter, Boolean> viewHolders = new WeakHashMap<>();
public MessageAdapter(View noElementsView, LinearLayoutManager layout, RecyclerView recycler)
{
vNoElements = noElementsView;
vNoElements = noElementsView;
manLayout = layout;
viewRecycler = recycler;
CMessageList.inst().register(this);
vNoElements.setVisibility(getItemCount()>0 ? View.GONE : View.VISIBLE);
@ -36,9 +55,17 @@ public class MessageAdapter extends RecyclerView.Adapter
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position)
{
CMessage msg = CMessageList.inst().tryGet(position);
CMessage msg = CMessageList.inst().tryGetFromBack(position);
MessagePresenter view = (MessagePresenter) holder;
view.setMessage(msg);
view.setMessage(msg, position);
viewHolders.put(view, true);
}
@Override
public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder)
{
if (holder instanceof MessagePresenter) viewHolders.remove(holder);
}
@Override
@ -59,26 +86,74 @@ public class MessageAdapter extends RecyclerView.Adapter
vNoElements.setVisibility(getItemCount()>0 ? View.GONE : View.VISIBLE);
}
private class MessagePresenter extends RecyclerView.ViewHolder implements View.OnClickListener
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);
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)
void setMessage(CMessage msg, int pos)
{
tvTimestamp.setText(msg.formatTimestamp());
tvTitle.setText(msg.Title);
@ -89,23 +164,63 @@ public class MessageAdapter extends RecyclerView.Adapter
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)
{
//SCNApp.showToast(data.Title, Toast.LENGTH_LONG);
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);
}
}
}

View File

@ -1,21 +1,34 @@
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
public class NotificationsFragment extends Fragment implements MessageAdapterTouchHelper.MessageAdapterTouchHelperListener
{
private PublisherAdView adView;
private MessageAdapter adpMessages;
public MessageAdapterTouchHelper touchHelper;
public NotificationsFragment()
{
// Required empty public constructor
@ -27,14 +40,51 @@ public class NotificationsFragment extends Fragment
View v = inflater.inflate(R.layout.fragment_notifications, container, false);
RecyclerView rvMessages = v.findViewById(R.id.rvMessages);
rvMessages.setLayoutManager(new LinearLayoutManager(this.getContext(), RecyclerView.VERTICAL, true));
rvMessages.setAdapter(new MessageAdapter(v.findViewById(R.id.tvNoElements)));
LinearLayoutManager lman = new LinearLayoutManager(this.getContext(), RecyclerView.VERTICAL, false);
rvMessages.setLayoutManager(lman);
rvMessages.setAdapter(adpMessages = new MessageAdapter(v.findViewById(R.id.tvNoElements), lman, rvMessages));
PublisherAdView mPublisherAdView = v.findViewById(R.id.adBanner);
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();
mPublisherAdView.loadAd(adRequest);
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();
}
}

View File

@ -1,27 +1,50 @@
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 androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
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;
@ -31,6 +54,10 @@ 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;
@ -40,6 +67,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
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;
@ -49,6 +79,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
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;
@ -58,9 +91,17 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
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
@ -83,6 +124,10 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
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);
@ -92,6 +137,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
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);
@ -101,6 +149,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
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);
@ -110,8 +161,19 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
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();
@ -119,11 +181,14 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
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));
ArrayAdapter<Integer> plcsa = new ArrayAdapter<>(c, android.R.layout.simple_spinner_item, SCNSettings.CHOOSABLE_CACHE_SIZES);
plcsa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
prefLocalCacheSize.setAdapter(plcsa);
prefLocalCacheSize.setSelection(getCacheSizeIndex(s.LocalCacheSize));
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);
@ -131,6 +196,12 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
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);
@ -138,6 +209,12 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
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);
@ -145,13 +222,26 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
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) -> { s.Enabled=b; saveAndUpdate(); });
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()
{
@ -164,12 +254,18 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
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());
@ -177,6 +273,9 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
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());
@ -184,17 +283,154 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
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()
{
//TODO
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)
@ -212,7 +448,7 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
UltimateMusicPicker ump = new UltimateMusicPicker();
ump.windowTitle("Choose notification sound");
ump.removeSilent();
ump.streamType(AudioManager.STREAM_ALARM);
ump.streamType(AudioManager.STREAM_NOTIFICATION);
ump.ringtone();
ump.notification();
ump.alarm();
@ -227,7 +463,7 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
UltimateMusicPicker ump = new UltimateMusicPicker();
ump.windowTitle("Choose notification sound");
ump.removeSilent();
ump.streamType(AudioManager.STREAM_ALARM);
ump.streamType(AudioManager.STREAM_NOTIFICATION);
ump.ringtone();
ump.notification();
ump.alarm();
@ -242,7 +478,7 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
UltimateMusicPicker ump = new UltimateMusicPicker();
ump.windowTitle("Choose notification sound");
ump.removeSilent();
ump.streamType(AudioManager.STREAM_ALARM);
ump.streamType(AudioManager.STREAM_NOTIFICATION);
ump.ringtone();
ump.notification();
ump.alarm();
@ -335,4 +571,11 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
{
musicPickerSwitch = -1;
}
public void onViewpagerHide()
{
stopSound(0, prefMsgLowVolumeTest);
stopSound(1, prefMsgNormVolumeTest);
stopSound(2, prefMsgHighVolumeTest);
}
}

View File

@ -35,7 +35,7 @@ public class TabAdapter extends FragmentStatePagerAdapter {
{
switch (position)
{
case 0: return "Notifications";
case 0: return "Messages";
case 1: return "Account";
case 2: return "Settings";
default: return null;

View File

@ -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);
}
});
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,15 +0,0 @@
<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"/>
<path android:fillColor="#ffffff" android:pathData="M300,694L700,694L500,136L300,694"/>
<path android:fillColor="#ffffff" android:pathData="M473,559L527,559L527,774L473,774"/>
<path android:fillColor="#000000" android:pathData="M376,640L624,640L500,295L376,640"/>
<path android:fillColor="#ffffff" android:pathData="M100,730L500,730L300,172L100,730"/>
<path android:fillColor="#000000" android:pathData="M176,676L424,676L300,331L176,676"/>
<path android:fillColor="#ffffff" android:pathData="M273,595L327,595L327,810L273,810"/>
<path android:fillColor="#ffffff" android:pathData="M500,730L900,730L700,172L500,730"/>
<path android:fillColor="#000000" android:pathData="M576,676L824,676L700,331L576,676"/>
<path android:fillColor="#ffffff" android:pathData="M673,595L727,595L727,810L673,810"/>
</vector>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@ -1,30 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
<RelativeLayout android:id="@+id/layoutRoot"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:showIn="@layout/activity_main">
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"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
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"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
android:minHeight="?attr/actionBarSize" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"

View 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>

View 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>

View 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>

View File

@ -198,19 +198,27 @@
app:layout_constraintBottom_toTopOf="@+id/btnAccountReset"
app:layout_constraintTop_toBottomOf="@+id/ic_img_quota" />
<Button
<com.google.android.material.button.MaterialButton
android:id="@+id/btnAccountReset"
app:cornerRadius="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:backgroundTint="#fa315b"
android:text="@string/str_reset_account"
app:layout_constraintBottom_toTopOf="@+id/btnClearLocalStorage" />
<Button
<com.google.android.material.button.MaterialButton
android:id="@+id/btnClearLocalStorage"
app:cornerRadius="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Clear Messages"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:backgroundTint="#607D8B"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@ -4,7 +4,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="TooManyViews"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.SettingsFragment">
@ -33,23 +32,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout
<Switch
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:id="@+id/prefAppEnabled"
android:text="@string/str_enabled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch
android:id="@+id/prefAppEnabled"
android:text="@string/str_enabled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:minHeight="48dp" />
<View
android:layout_width="match_parent"
@ -67,7 +57,7 @@
<TextView
android:id="@+id/tvLocalCacheSize"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/str_localcachesize"
android:textColor="#000"
@ -89,6 +79,22 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<Switch
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:id="@+id/prefEnableDeleteSwipe"
android:text="@string/str_deleteswipe"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
@ -103,17 +109,74 @@
android:layout_height="wrap_content"
android:minHeight="48dp">
<Button
android:id="@+id/prefUpgradeAccount"
android:text="@string/str_upgrade_account"
android:layout_width="match_parent"
<TextView
android:id="@+id/tvPreviewLineCount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/str_previewlinecount"
android:textColor="#000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/prefPreviewLineCount"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:minWidth="64dp"
android:id="@+id/prefPreviewLineCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="number"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:importantForAutofill="no"
tools:ignore="LabelFor,UnusedAttribute" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<LinearLayout
android:orientation="vertical"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center|center"
android:minHeight="48dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/prefUpgradeAccount"
app:cornerRadius="0dp"
android:backgroundTint="#4CAF50"
android:text="@string/str_upgrade_account"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/prefUpgradeAccount_info"
android:textAlignment="center"
android:text="@string/str_promode_info"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/prefUpgradeAccount2"
android:textColor="#FF4D00"
android:textStyle="bold"
android:textAlignment="center"
android:text="@string/str_promode"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
@ -137,22 +200,118 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout
<Switch
android:id="@+id/prefMsgLowEnableSound"
android:text="@string/str_msg_enablesound"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<LinearLayout
android:id="@+id/prefMsgLowRingtone_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
android:orientation="vertical"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp"
android:gravity="center">
<Switch
android:id="@+id/prefMsgLowEnableSound"
android:text="@string/str_msg_enablesound"
<TextView
android:id="@+id/tvMsgLowRingtone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/str_notificationsound"
android:textColor="#000" />
<TextView
android:id="@+id/prefMsgLowRingtone_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="64dp"
android:spinnerMode="dialog"
android:text="Whatever"
tools:ignore="HardcodedText" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<Switch
android:id="@+id/prefMsgLowRepeatSound"
android:text="@string/str_repeatnotificationsound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<Switch
android:id="@+id/prefMsgLowForceVolume"
android:text="@string/str_forcevolume"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:orientation="horizontal"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp">
<ImageView
android:id="@+id/icnLowVolume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/volume_icon"
android:src="@drawable/ic_volume"
app:layout_constraintStart_toStartOf="parent" />
<SeekBar
android:id="@+id/prefMsgLowVolume"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnLowVolumeTest"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/icnLowVolume"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/btnLowVolumeTest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_play"
app:layout_constraintEnd_toEndOf="parent"
android:contentDescription="@string/play_test_sound" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
@ -162,89 +321,14 @@
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
<Switch
android:id="@+id/prefMsgLowEnableLED"
android:text="@string/str_enable_led"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp">
<LinearLayout
android:id="@+id/prefMsgLowRingtone_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tvMsgLowRingtone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/str_notificationsound"
android:textColor="#000" />
<TextView
android:id="@+id/prefMsgLowRingtone_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="64dp"
android:spinnerMode="dialog"
android:text="Whatever"
tools:ignore="HardcodedText" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch
android:id="@+id/prefMsgLowRepeatSound"
android:text="@string/str_repeatnotificationsound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch
android:id="@+id/prefMsgLowEnableLED"
android:text="@string/str_enable_led"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:minHeight="48dp" />
<View
android:layout_width="match_parent"
@ -294,23 +378,15 @@
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
<Switch
android:id="@+id/prefMsgLowEnableVibrations"
android:text="@string/str_enable_vibration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<Switch
android:id="@+id/prefMsgLowEnableVibrations"
android:text="@string/str_enable_vibration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
@ -335,22 +411,101 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
<Switch
android:id="@+id/prefMsgNormEnableSound"
android:text="@string/str_msg_enablesound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<Switch
android:id="@+id/prefMsgNormEnableSound"
android:text="@string/str_msg_enablesound"
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<LinearLayout
android:id="@+id/prefMsgNormRingtone_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp"
android:gravity="center">
<TextView
android:id="@+id/tvMsgNormRingtone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/str_notificationsound"
android:textColor="#000" />
<TextView
android:id="@+id/prefMsgNormRingtone_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="64dp"
android:spinnerMode="dialog"
android:text="Whatever"
tools:ignore="HardcodedText" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<Switch
android:id="@+id/prefMsgNormForceVolume"
android:text="@string/str_forcevolume"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:orientation="horizontal"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp">
<ImageView
android:id="@+id/icnNormVolume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/volume_icon"
android:src="@drawable/ic_volume"
app:layout_constraintStart_toStartOf="parent" />
<SeekBar
android:id="@+id/prefMsgNormVolume"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnNormVolumeTest"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/icnNormVolume"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/btnNormVolumeTest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_play"
app:layout_constraintEnd_toEndOf="parent"
android:contentDescription="@string/play_test_sound" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
@ -360,39 +515,14 @@
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
<Switch
android:id="@+id/prefMsgNormRepeatSound"
android:text="@string/str_repeatnotificationsound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp">
<LinearLayout
android:id="@+id/prefMsgNormRingtone_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tvMsgNormRingtone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/str_notificationsound"
android:textColor="#000" />
<TextView
android:id="@+id/prefMsgNormRingtone_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="64dp"
android:spinnerMode="dialog"
android:text="Whatever"
tools:ignore="HardcodedText" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
android:minHeight="48dp" />
<View
android:layout_width="match_parent"
@ -401,48 +531,14 @@
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
<Switch
android:id="@+id/prefMsgNormEnableLED"
android:text="@string/str_enable_led"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch
android:id="@+id/prefMsgNormRepeatSound"
android:text="@string/str_repeatnotificationsound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch
android:id="@+id/prefMsgNormEnableLED"
android:text="@string/str_enable_led"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:minHeight="48dp" />
<View
android:layout_width="match_parent"
@ -492,23 +588,14 @@
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
<Switch
android:id="@+id/prefMsgNormEnableVibrations"
android:text="@string/str_enable_vibration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch
android:id="@+id/prefMsgNormEnableVibrations"
android:text="@string/str_enable_vibration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
</LinearLayout>
@ -533,22 +620,96 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
<Switch
android:id="@+id/prefMsgHighEnableSound"
android:text="@string/str_msg_enablesound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<Switch
android:id="@+id/prefMsgHighEnableSound"
android:text="@string/str_msg_enablesound"
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<LinearLayout
android:id="@+id/prefMsgHighRingtone_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp"
android:orientation="vertical"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tvMsgHighRingtone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/str_notificationsound"
android:textColor="#000" />
<TextView
android:id="@+id/prefMsgHighRingtone_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="64dp"
android:spinnerMode="dialog"
android:text="Whatever"
tools:ignore="HardcodedText" />
</LinearLayout>
<Switch
android:id="@+id/prefMsgHighForceVolume"
android:text="@string/str_forcevolume"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:orientation="horizontal"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp">
<ImageView
android:id="@+id/icnHighVolume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/volume_icon"
android:src="@drawable/ic_volume"
app:layout_constraintStart_toStartOf="parent" />
<SeekBar
android:id="@+id/prefMsgHighVolume"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnHighVolumeTest"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/icnHighVolume"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/btnHighVolumeTest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_play"
app:layout_constraintEnd_toEndOf="parent"
android:contentDescription="@string/play_test_sound" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
@ -558,39 +719,14 @@
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
<Switch
android:id="@+id/prefMsgHighRepeatSound"
android:text="@string/str_repeatnotificationsound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp">
<LinearLayout
android:id="@+id/prefMsgHighRingtone_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tvMsgHighRingtone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/str_notificationsound"
android:textColor="#000" />
<TextView
android:id="@+id/prefMsgHighRingtone_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="64dp"
android:spinnerMode="dialog"
android:text="Whatever"
tools:ignore="HardcodedText" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
android:minHeight="48dp" />
<View
android:layout_width="match_parent"
@ -599,48 +735,14 @@
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
<Switch
android:id="@+id/prefMsgHighEnableLED"
android:text="@string/str_enable_led"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch
android:id="@+id/prefMsgHighRepeatSound"
android:text="@string/str_repeatnotificationsound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch
android:id="@+id/prefMsgHighEnableLED"
android:text="@string/str_enable_led"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:minHeight="48dp" />
<View
android:layout_width="match_parent"
@ -690,28 +792,37 @@
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
<Switch
android:id="@+id/prefMsgHighEnableVibrations"
android:text="@string/str_enable_vibration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<Switch
android:id="@+id/prefMsgHighEnableVibrations"
android:text="@string/str_enable_vibration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/prefExport"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
app:cornerRadius="0dp"
android:backgroundTint="#444444"
android:text="@string/export_settings" />
<com.google.android.material.button.MaterialButton
android:id="@+id/prefImport"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
app:cornerRadius="0dp"
android:backgroundTint="#666666"
android:text="@string/import_settings" />
</LinearLayout>
</ScrollView>

View File

@ -1,80 +1,154 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.cardview.widget.CardView
android:id="@+id/card_view"
<RelativeLayout
android:id="@+id/layoutBack"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_height="match_parent"
android:layout_margin="@dimen/card_margin"
android:elevation="3dp"
card_view:cardCornerRadius="@dimen/card_album_radius">
android:background="@color/bg_row_background">
<ImageView
android:id="@+id/delete_icon"
android:layout_width="@dimen/ic_delete"
android:layout_height="@dimen/ic_delete"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="@dimen/padd_10"
android:src="@drawable/ic_trash"
android:contentDescription="@string/delete" />
<androidx.constraintlayout.widget.ConstraintLayout
android:background="#FFFFFFFF"
android:layout_margin="3dp"
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="@dimen/padd_10"
android:layout_toLeftOf="@id/delete_icon"
android:text="@string/delete"
android:textColor="#fff"
android:textSize="13dp" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/layoutFront"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.cardview.widget.CardView
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/card_margin"
android:elevation="3dp"
card_view:cardCornerRadius="@dimen/card_album_radius">
<TextView
android:id="@+id/tvTimestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:textSize="12sp"
android:textStyle="italic"
android:text="2018-09-11 20:22:32" />
<androidx.constraintlayout.widget.ConstraintLayout
android:background="#FFFFFFFF"
android:layout_margin="3dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tvTimestamp"
android:layout_marginEnd="4sp"
android:textSize="16sp"
android:textColor="@color/colorBlack"
android:textStyle="bold"
android:ellipsize="none"
android:maxLines="6"
<TextView
android:id="@+id/tvTimestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:textSize="12sp"
android:textStyle="italic"
android:text="Message from me"/>
android:text="2018-09-11 20:22:32" />
<TextView
android:id="@+id/tvMessage"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
app:layout_constraintRight_toLeftOf="@+id/ivPriority"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_margin="4sp"
android:ellipsize="none"
android:maxLines="32"
android:scrollHorizontally="false"
<TextView
android:id="@+id/tvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tvTimestamp"
android:layout_marginEnd="4sp"
android:textSize="16sp"
android:textColor="@color/colorBlack"
android:textStyle="bold"
android:ellipsize="none"
android:maxLines="6"
android:text="asdasd asdasd asdasd a" />
android:text="Message from me"/>
<ImageView
android:id="@+id/ivPriority"
android:visibility="gone"
android:layout_width="24dp"
android:layout_height="24dp"
app:layout_constraintTop_toBottomOf="@+id/tvTimestamp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_margin="4sp"
android:paddingTop="3dp"
android:contentDescription="@string/desc_priority_icon" />
<TextView
android:id="@+id/tvMessage"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
app:layout_constraintRight_toLeftOf="@+id/ivPriority"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_margin="4sp"
android:ellipsize="end"
android:maxLines="6"
android:scrollHorizontally="false"
</androidx.constraintlayout.widget.ConstraintLayout>
android:text="asdasd asdasd asdasd a" />
</androidx.cardview.widget.CardView>
<ImageView
android:id="@+id/ivPriority"
android:tint="#BBB"
android:visibility="gone"
android:layout_width="24dp"
android:layout_height="24dp"
app:layout_constraintTop_toBottomOf="@+id/tvTimestamp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_margin="4sp"
android:paddingTop="3dp"
android:contentDescription="@string/desc_priority_icon" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnShare"
style="@style/Widget.MaterialComponents.Button.Icon"
app:cornerRadius="0dp"
app:icon="@drawable/ic_share_small"
android:layout_width="wrap_content"
android:layout_height="25dp"
android:layout_marginEnd="8sp"
android:insetTop="0dp"
android:insetBottom="0dp"
android:insetLeft="0dp"
android:insetRight="0dp"
android:textSize="12sp"
card_view:layout_constraintTop_toBottomOf="@id/tvMessage"
app:layout_constraintRight_toLeftOf="@id/btnDelete"
app:layout_constraintBottom_toBottomOf="parent"
android:backgroundTint="#03A9F4"
android:text="Share" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDelete"
style="@style/Widget.MaterialComponents.Button.Icon"
app:cornerRadius="0dp"
app:icon="@drawable/ic_trash_small"
android:layout_width="wrap_content"
android:layout_height="25dp"
android:insetTop="0dp"
android:insetBottom="0dp"
android:insetLeft="0dp"
android:insetRight="0dp"
android:textSize="12sp"
card_view:layout_constraintTop_toBottomOf="@id/tvMessage"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:backgroundTint="#F44336"
android:text="Delete"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
</FrameLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MaxHeightScrollView">
<attr name="maxHeightOverride" format="integer" />
</declare-styleable>
</resources>

View File

@ -6,5 +6,10 @@
<color name="colorHeader">#3F51B5</color>
<color name="colorHeaderForeground">#FFFFFF</color>
<color name="colorOnPrimary">#ecf0f1</color>
<color name="colorSecondary">#FF5722</color>
<color name="colorBlack">#000</color>
<color name="bg_row_background">#fa315b</color>
</resources>

View File

@ -2,4 +2,9 @@
<resources>
<dimen name="card_margin">5dp</dimen>
<dimen name="card_album_radius">0dp</dimen>
<dimen name="padd_10">10dp</dimen>
<dimen name="ic_delete">30dp</dimen>
<dimen name="thumbnail">90dp</dimen>
<dimen name="fab_margin">16dp</dimen>
</resources>

View File

@ -11,21 +11,32 @@
<string name="ic_img_fuel_desc">Icon Fuel</string>
<string name="str_qr_code">QR Code</string>
<string name="str_reset_account">Reset Account</string>
<string name="no_notifications">No notifications</string>
<string name="no_notifications">No messages</string>
<string name="str_not_connected">not connected</string>
<string name="str_reload">reload</string>
<string name="desc_priority_icon">Priority icon</string>
<string name="str_common_settings">Common Settings</string>
<string name="str_enabled">Enabled</string>
<string name="str_localcachesize">Remember the last x notifications locally</string>
<string name="str_header_prio0">Notifications (low priority)</string>
<string name="str_header_prio1">Notifications (normal priority)</string>
<string name="str_header_prio2">Notifications (high priority)</string>
<string name="str_localcachesize">Remember the last x messages locally</string>
<string name="str_header_prio0">Notifications (priority 0 - Low)</string>
<string name="str_header_prio1">Notifications (priority 1 - Normal)</string>
<string name="str_header_prio2">Notifications (priority 2 - High)</string>
<string name="str_msg_enablesound">Enable notification sound</string>
<string name="str_notificationsound">Notification sound</string>
<string name="str_repeatnotificationsound">Repeat notification sound</string>
<string name="str_forcevolume">Automatically set the volume</string>
<string name="str_enable_led">Enable notification light</string>
<string name="str_ledcolor">Notification light color</string>
<string name="str_enable_vibration">Enable notification vibration</string>
<string name="str_upgrade_account">Upgrade account</string>
<string name="str_deleteswipe">Delete messages by swiping left</string>
<string name="str_previewlinecount">Number of visibile lines in collapsed messages</string>
<string name="str_promode">Thank you for supporting the app and using the pro mode</string>
<string name="str_promode_info">Increase your daily quota, remove the ad banner and support the developer (that\'s me)</string>
<string name="volume_icon">Volume icon</string>
<string name="play_test_sound">Play test sound</string>
<string name="delete">DELETE</string>
<string name="title_activity_query_log">QueryLogActivity</string>
<string name="import_settings">Import settings</string>
<string name="export_settings">Export settings</string>
</resources>

View File

@ -1,13 +1,20 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<!-- your app branding color for the app bar -->
<item name="colorPrimary">#3F51B5</item>
<!-- darker variant for the status bar and contextual app bars -->
<item name="colorPrimaryDark">#303F9F</item>
<!-- theme UI controls like checkboxes and text fields -->
<item name="colorAccent">#FF4081</item>
<item name="colorAccent">#FF5722</item>
<item name="colorSecondary">#FF5722</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.MaterialComponents.Light" />
</resources>

View File

@ -0,0 +1,7 @@
<paths>
<files-path path="/" name="files" />
<cache-path path="/" name="cache" />
<external-path path="/" name="external" />
<external-files-path path="/" name="external-files" />
<external-cache-path path="/" name="external-cache" />
</paths>

View File

@ -1,3 +1,3 @@
#Sat Oct 20 18:59:02 CEST 2018
VERSION_NAME=0.0.3
VERSION_CODE=3
#Thu Mar 05 15:29:10 UTC 2020
VERSION_NAME=1.8.0
VERSION_CODE=23

View File

@ -7,8 +7,8 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'com.google.gms:google-services:4.0.1'
classpath 'com.android.tools.build:gradle:4.1.0'
classpath 'com.google.gms:google-services:4.3.4'
}
}

View File

@ -1,6 +1,6 @@
#Wed Sep 26 22:10:14 CEST 2018
#Tue Nov 03 14:10:19 CET 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip

58
androidExportReader/.gitignore vendored Normal file
View File

@ -0,0 +1,58 @@
# Created by https://www.toptal.com/developers/gitignore/api/java,gradle
# Edit at https://www.toptal.com/developers/gitignore?templates=java,gradle
### Java ###
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
### Gradle ###
.gradle
**/build/
!src/**/build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Avoid ignore Gradle wrappper properties
!gradle-wrapper.properties
# Cache of project
.gradletasknamecache
# Eclipse Gradle plugin generated files
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
### Gradle Patch ###
# Java heap dump
*.hprof
# End of https://www.toptal.com/developers/gitignore/api/java,gradle

8
androidExportReader/.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

6
androidExportReader/.idea/compiler.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="18" />
</component>
</project>

Some files were not shown because too many files have changed in this diff Show More