371 Commits

Author SHA1 Message Date
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
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
611 changed files with 554187 additions and 2392 deletions

View File

@@ -0,0 +1,43 @@
# 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_job:
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
deploy_job:
name: Deploy to Server
needs: [build_job]
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,28 +35,33 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
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.1.0'
implementation 'com.android.billingclient:billing:1.2'
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 << {
tasks.register("updateVersion") {
group = 'Custom'
doLast {
def lastTag = ['git', 'describe', "--abbrev=0", "--tags"].execute().text.trim()
def versionPropsFile = file('version.properties')
@@ -108,3 +113,4 @@ task updateVersion << {
versionPropsFile.newWriter().withCloseable { w -> versionProps.store(w, null) }
}
}
}

View File

@@ -1,40 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.blackforestbytes.simplecloudnotifier">
<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

@@ -5,6 +5,7 @@ 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;
@@ -19,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");
@@ -99,32 +100,5 @@ public class SCNApp extends Application implements LifecycleObserver
}
}
/*
==TODO==
[X] - Pro mode
[X] - no ads
[X] - more quota
[X] - restore pro mode
[X] - send pro state to server
[X] - prevent duplicate-send
[X] - send custom msg-id in API
[X] - prevent second ack on same msg-id
[X] - more in-depth API doc on website (?)
[X] - perhaps response codes in api (?)
[ ] - test notification channels
[ ] - publish (+ HN post ?)
[ ] - Use for mscom server errrors
[ ] - Use for bfb server errors
[ ] - Use for transmission state
[ ] - Message on connnection lost (seperate process - resend until succ)
[ ] - Message on connnection regained
[ ] - Message on seed-count changed
*/
//TODO: Config for collapsed line count
//TODO: Sometimes ads but promode

View File

@@ -24,6 +24,11 @@ public final class CollectionHelper
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);

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

@@ -9,6 +9,9 @@ import java.util.TimeZone;
public class CMessage
{
public boolean IsExpandedInAdapter = false;
public final long SCN_ID;
public final long Timestamp;
public final String Title;
public final String Content;
@@ -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<>();
@@ -27,8 +35,16 @@ public class CMessageList
}
private CMessageList()
{
reloadPrefs();
}
public void reloadPrefs()
{
synchronized (msg_lock)
{
Messages = new ArrayList<>();
AllAcks = new HashSet<>();
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
int count = sharedPref.getInt("message_count", 0);
@@ -38,47 +54,75 @@ public class CMessageList
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(time, title, content, prio));
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));
while (Messages.size()>SCNSettings.inst().LocalCacheSize) Messages.remove(0);
}
if (Messages.size()>1 && Messages.get(Messages.size()-2).Timestamp < msg.Timestamp)
{
// quick save
SharedPreferences.Editor e = sharedPref.edit();
e.putInt( "message_count", count+1);
e.putLong( "message["+count+"].timestamp", time);
e.putString("message["+count+"].title", title);
e.putString("message["+count+"].content", content);
e.putInt( "message["+count+"].priority", pe.ID);
e.putLong( "message["+count+"].scnid", scnid);
e.putStringSet("acks", AllAcks);
e.apply();
}
else
{
// full save
fullSave(); // does sort in here
}
for (WeakReference<MessageAdapter> ref : _listener)
{
MessageAdapter a = ref.get();
if (a == null) continue;
a.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;
@@ -100,6 +144,10 @@ public class CMessageList
public void fullSave()
{
synchronized (msg_lock)
{
CollectionHelper.sort_inplace(Messages, (a,b) -> Long.compare(a.Timestamp, b.Timestamp));
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
SharedPreferences.Editor e = sharedPref.edit();
@@ -113,21 +161,36 @@ public class CMessageList
e.putString("message["+i+"].title", Messages.get(i).Title);
e.putString("message["+i+"].content", Messages.get(i).Content);
e.putInt( "message["+i+"].priority", Messages.get(i).Priority.ID);
e.putLong( "message["+i+"].scnid", Messages.get(i).SCN_ID);
}
e.putStringSet("acks", AllAcks);
e.apply();
}
}
public CMessage tryGet(int pos)
{
synchronized (msg_lock)
{
if (pos < 0 || pos >= Messages.size()) return null;
return Messages.get(pos);
}
}
public CMessage tryGetFromBack(int pos)
{
return tryGet(Messages.size() - pos - 1);
}
public int size()
{
synchronized (msg_lock)
{
return Messages.size();
}
}
public void register(MessageAdapter adp)
{
@@ -142,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

@@ -6,23 +6,29 @@ import android.content.SharedPreferences;
import android.util.Log;
import android.view.View;
import com.android.billingclient.api.Purchase;
import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple3;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.service.IABService;
import com.google.firebase.iid.FirebaseInstanceId;
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()
{
SCNSettings local = _inst;
if (local == null)
{
synchronized (_lock)
{
if (_inst != null) return _inst;
return _inst = new SCNSettings();
local = _inst;
if (local == null) _inst = local = new SCNSettings();
}
}
return local;
}
// ------------------------------------------------------------
@@ -46,14 +52,21 @@ public class SCNSettings
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);
@@ -69,6 +82,8 @@ public class SCNSettings
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);
@@ -77,6 +92,8 @@ public class SCNSettings
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);
@@ -85,6 +102,8 @@ public class SCNSettings
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);
@@ -93,6 +112,8 @@ public class SCNSettings
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()
@@ -106,9 +127,14 @@ public class SCNSettings
e.putString( "user_key", user_key);
e.putString( "fcm_token_local", fcm_token_local);
e.putString( "fcm_token_server", fcm_token_server);
e.putBoolean("promode_local", promode_local);
e.putBoolean("promode_server", promode_server);
e.putString( "promode_token", promode_token);
e.putBoolean("app_enabled", Enabled);
e.putInt( "local_cache_size", LocalCacheSize);
e.putBoolean("do_del_swipe", EnableDeleteSwipe);
e.putInt( "preview_line_count", PreviewLineCount);
e.putBoolean("priority_low:enabled_led", PriorityLow.EnableLED);
e.putBoolean("priority_low:enabled_sound", PriorityLow.EnableSound);
@@ -117,6 +143,8 @@ public class SCNSettings
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);
@@ -125,6 +153,8 @@ public class SCNSettings
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);
@@ -133,6 +163,8 @@ public class SCNSettings
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();
}
@@ -142,19 +174,21 @@ 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
{
@@ -166,13 +200,12 @@ public class SCNSettings
}
// called at app start
public void work(Activity a)
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);
@@ -186,7 +219,7 @@ public class SCNSettings
{
if (!isConnected()) return;
ServerCommunication.update(user_id, user_key, loader);
ServerCommunication.resetSecret(user_id, user_key, loader);
}
// refresh account data
@@ -195,19 +228,18 @@ public class SCNSettings
if (isConnected())
{
ServerCommunication.info(user_id, user_key, loader);
if (promode_server != promode_local)
{
updateProState(loader);
}
if (promode_server != promode_local) updateProState(loader);
if (!Str.equals(fcm_token_local, fcm_token_server)) work(a, false);
}
else
{
// get token then register
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, loader); // does register in here
Log.d("FB::GetInstanceId", newToken);
SCNSettings.inst().setServerToken(newToken, loader, false); // does register in here
}).addOnCompleteListener(r ->
{
if (isConnected()) ServerCommunication.info(user_id, user_key, null); // info again for safety
@@ -217,14 +249,17 @@ public class SCNSettings
public void updateProState(View loader)
{
Purchase purch = IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE);
boolean promode_real = (purch != null);
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();
promode_token = promode_real ? purch.getPurchaseToken() : "";
updateProStateOnServer(loader);
}
}

View File

@@ -4,7 +4,13 @@ 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;
@@ -20,7 +26,9 @@ 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();
@@ -39,44 +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().promode_server = json.getBoolean("is_pro");
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
{
@@ -87,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
{
@@ -105,43 +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().promode_server = json.getBoolean("is_pro");
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
@@ -153,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
{
@@ -168,40 +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().promode_server = json.getBoolean("is_pro");
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);
});
@@ -211,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);
}
}
@@ -227,41 +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);
return;
}
if (!json_bool(json, "success"))
{
SCNApp.showToast(json_str(json, "message"), 4000);
handleNonSuccess("info", call, response, r);
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().promode_server = json.getBoolean("is_pro");
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();
} catch (Exception e) {
e.printStackTrace();
}
return;
}
SCNSettings.inst().user_id = json_int(json, "user_id");
SCNSettings.inst().quota_curr = json_int(json, "quota");
SCNSettings.inst().quota_max = json_int(json, "quota_max");
SCNSettings.inst().promode_server = json_bool(json, "is_pro");
if (!json_bool(json, "fcm_token_set")) SCNSettings.inst().fcm_token_server = "";
SCNSettings.inst().save();
SCNApp.refreshAccountTab();
if (json_int(json, "unack_count")>0) ServerCommunication.requery(id, key, loader);
handleSuccess("info", call, response, r);
}
catch (Exception e)
{
handleError("info", call, response, r, false, e);
SCNApp.showToast("Communication with server failed", 4000);
} finally {
}
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);
});
@@ -271,8 +397,7 @@ public class ServerCommunication
}
catch (Exception e)
{
e.printStackTrace();
SCNApp.showToast("Communication with server failed", 4000);
handleError("requery", null, null, Str.Empty, false, e);
}
}
@@ -286,42 +411,50 @@ public class ServerCommunication
.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() {
client.newCall(request).enqueue(new Callback()
{
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
SCNApp.showToast("Communication with server failed", 4000);
public void onFailure(Call call, IOException e)
{
handleError("upgrade", call, null, Str.Empty, true, e);
}
@Override
public void onResponse(Call call, Response response) {
try (ResponseBody responseBody = response.body()) {
if (!response.isSuccessful())
throw new IOException("Unexpected code " + response);
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("upgrade", 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().promode_server = json.getBoolean("is_pro");
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();
} catch (Exception e) {
e.printStackTrace();
SCNApp.showToast("Communication with server failed", 4000);
} finally {
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); });
}
}
@@ -329,9 +462,225 @@ public class ServerCommunication
}
catch (Exception e)
{
e.printStackTrace();
SCNApp.showToast("Communication with server failed", 4000);
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
@@ -36,9 +44,39 @@ 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"));
long scn_id = Long.parseLong(remoteMessage.getData().get("scn_msg_id"));
boolean trimmed = Boolean.parseBoolean(remoteMessage.getData().get("trimmed"));
CMessage msg = CMessageList.inst().add(time, title, content, prio);
SingleQuery q = new SingleQuery(LogLevel.INFO, Instant.now(), "FBM<recieve>", Str.Empty, new JSONObject(remoteMessage.getData()).toString(), 0, "SUCCESS");
QueryLog.inst().add(q);
if (trimmed)
{
ServerCommunication.expand(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id, null, (i1, i2, i3, i4, i5) -> recieveData(i4, i1, i2, i3, i5, false));
}
else
{
recieveData(time, title, content, prio, scn_id, false);
}
}
catch (Exception e)
{
Log.e("FB:Err", e.toString());
SCNApp.showToast("Recieved invalid message from server", Toast.LENGTH_LONG);
}
}
public static void recieveData(long time, String title, String content, PriorityEnum prio, long scn_id, boolean alwaysAck)
{
if (CMessageList.inst().isAck(scn_id))
{
Log.w("FB::MessageReceived", "Recieved ack-ed message: " + scn_id);
if (alwaysAck) ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id);
return;
}
CMessage msg = CMessageList.inst().add(scn_id, time, title, content, prio);
if (SCNApp.isBackground())
{
@@ -48,11 +86,7 @@ public class FBMService extends FirebaseMessagingService
{
NotificationService.inst().showForeground(msg);
}
}
catch (Exception e)
{
Log.e("FB:Err", e.toString());
SCNApp.showToast("Recieved invalid message from server", Toast.LENGTH_LONG);
}
ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id);
}
}

View File

@@ -2,23 +2,37 @@ 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;
@@ -45,18 +59,72 @@ public class IABService implements PurchasesUpdatedListener
}
}
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()
@@ -67,14 +135,16 @@ public class IABService implements PurchasesUpdatedListener
Purchase.PurchasesResult purchasesResult = client.queryPurchases(BillingClient.SkuType.INAPP);
Log.i(TAG, "Querying purchases elapsed time: " + (System.currentTimeMillis() - time) + "ms");
if (purchasesResult.getResponseCode() == BillingClient.BillingResponse.OK)
if (purchasesResult.getResponseCode() == BillingClient.BillingResponseCode.OK)
{
for (Purchase p : purchasesResult.getPurchasesList())
for (Purchase p : Objects.requireNonNull(purchasesResult.getPurchasesList()))
{
handlePurchase(p);
handlePurchase(p, false);
}
boolean newProMode = getPurchaseCached(IAB_PRO_MODE) != null;
_isInitialized = true;
boolean newProMode = getPurchaseCachedSimple(IAB_PRO_MODE);
if (newProMode != SCNSettings.inst().promode_local)
{
refreshProModeListener();
@@ -89,20 +159,39 @@ public class IABService implements PurchasesUpdatedListener
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()
.setSku(id)
.setType(BillingClient.SkuType.INAPP) // SkuType.SUB for subscription
.setSkuDetails(skuDetailsList.get(0))
.build();
client.launchBillingFlow(a, flowParams);
}, true);
});
};
executeServiceRequest(queryRequest, false);
}
private void executeServiceRequest(Func0to0 runnable, final boolean userRequest) {
private void executeServiceRequest(Func0to0 runnable, final boolean userRequest)
{
if (isServiceConnected)
{
runnable.invoke();
@@ -124,34 +213,37 @@ public class IABService implements PurchasesUpdatedListener
}
@Override
public void onPurchasesUpdated(int responseCode, @Nullable List<Purchase> purchases)
public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> purchases)
{
if (responseCode == BillingClient.BillingResponse.OK && purchases != null)
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null)
{
for (Purchase purchase : purchases)
{
handlePurchase(purchase);
handlePurchase(purchase, true);
}
}
else if (responseCode == BillingClient.BillingResponse.ITEM_ALREADY_OWNED && purchases != null)
else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED && purchases != null)
{
for (Purchase purchase : purchases)
{
handlePurchase(purchase);
handlePurchase(purchase, true);
}
}
}
private void handlePurchase(Purchase purchase)
private void handlePurchase(Purchase purchase, boolean triggerUpdate)
{
Log.d(TAG, "Got a verified purchase: " + purchase);
purchases.add(purchase);
refreshProModeListener();
if (triggerUpdate) refreshProModeListener();
updateCache(purchase.getSku(), true);
}
private void refreshProModeListener() {
private void refreshProModeListener()
{
MainActivity ma = SCNApp.getMainActivity();
if (ma != null) ma.adpTabs.tab3.updateProState();
if (ma != null) ma.adpTabs.tab1.updateProState();
@@ -163,9 +255,9 @@ public class IABService implements PurchasesUpdatedListener
client.startConnection(new BillingClientStateListener()
{
@Override
public void onBillingSetupFinished(@BillingClient.BillingResponse int billingResponseCode)
public void onBillingSetupFinished(@NonNull BillingResult billingResult)
{
if (billingResponseCode == BillingClient.BillingResponse.OK)
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK)
{
isServiceConnected = true;
if (executeOnSuccess != null) executeOnSuccess.invoke();
@@ -183,13 +275,31 @@ public class IABService implements PurchasesUpdatedListener
});
}
public Purchase getPurchaseCached(String id)
public boolean getPurchaseCachedSimple(String id)
{
for (Purchase p : purchases)
{
if (Str.equals(p.getSku(), id)) return p;
return getPurchaseCachedExtended(id).Item1;
}
return null;
@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

@@ -6,26 +6,32 @@ 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_LOW = "CHAN_BFB_SCN_MESSAGES_LOW";
private final static String CHANNEL_ID_NORM = "CHAN_BFB_SCN_MESSAGES_NORM";
private final static String CHANNEL_ID_HIGH = "CHAN_BFB_SCN_MESSAGES_HIGH";
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;
@@ -40,10 +46,10 @@ public class NotificationService
private NotificationService()
{
updateChannels();
createChannels();
}
public void updateChannels()
private void createChannels()
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
@@ -51,81 +57,211 @@ public class NotificationService
NotificationManager notifman = ctxt.getSystemService(NotificationManager.class);
if (notifman == null) return;
NotificationChannel channelLow = notifman.getNotificationChannel(CHANNEL_ID_LOW);
if (channelLow == null) notifman.createNotificationChannel(channelLow = new NotificationChannel(CHANNEL_ID_LOW, "Push notifications (low priority)", NotificationManager.IMPORTANCE_LOW));
NotificationChannel channelNorm = notifman.getNotificationChannel(CHANNEL_ID_NORM);
if (channelNorm == null) notifman.createNotificationChannel(channelNorm = new NotificationChannel(CHANNEL_ID_NORM, "Push notifications (normal priority)", NotificationManager.IMPORTANCE_DEFAULT));
NotificationChannel channelHigh = notifman.getNotificationChannel(CHANNEL_ID_HIGH);
if (channelHigh == null) notifman.createNotificationChannel(channelHigh = new NotificationChannel(CHANNEL_ID_HIGH, "Push notifications (high priority)", NotificationManager.IMPORTANCE_HIGH));
channelLow.setDescription("Messages from the API with priority set to low");
channelLow.enableLights(SCNSettings.inst().PriorityLow.EnableLED);
channelLow.setLightColor(SCNSettings.inst().PriorityLow.LEDColor);
channelLow.enableVibration(SCNSettings.inst().PriorityLow.EnableVibration);
channelLow.setVibrationPattern(new long[]{200});
channelNorm.setDescription("Messages from the API with priority set to normal");
channelNorm.enableLights(SCNSettings.inst().PriorityNorm.EnableLED);
channelNorm.setLightColor(SCNSettings.inst().PriorityNorm.LEDColor);
channelNorm.enableVibration(SCNSettings.inst().PriorityNorm.EnableVibration);
channelNorm.setVibrationPattern(new long[]{200});
channelHigh.setDescription("Messages from the API with priority set to high");
channelHigh.enableLights(SCNSettings.inst().PriorityHigh.EnableLED);
channelHigh.setLightColor(SCNSettings.inst().PriorityHigh.LEDColor);
channelHigh.enableVibration(SCNSettings.inst().PriorityHigh.EnableVibration);
channelHigh.setVibrationPattern(new long[]{200});
channelLow.setBypassDnd(true);
channelLow.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
{
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();
String channel = CHANNEL_ID_NORM;
NotificationSettings ns = SCNSettings.inst().PriorityNorm;
switch (msg.Priority)
{
case LOW: ns = SCNSettings.inst().PriorityLow; channel = CHANNEL_ID_LOW; break;
case NORMAL: ns = SCNSettings.inst().PriorityNorm; channel = CHANNEL_ID_NORM; break;
case HIGH: ns = SCNSettings.inst().PriorityHigh; channel = CHANNEL_ID_HIGH; break;
case LOW: ns = SCNSettings.inst().PriorityLow; break;
case NORMAL: ns = SCNSettings.inst().PriorityNorm; break;
case HIGH: ns = SCNSettings.inst().PriorityHigh; break;
}
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, channel);
mBuilder.setSmallIcon(R.drawable.ic_bfb);
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);
mBuilder.setWhen(msg.Timestamp * 1000);
mBuilder.setAutoCancel(true);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
{
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[]{200});
if (ns.EnableVibration) mBuilder.setVibrate(new long[]{500});
if (ns.EnableLED) mBuilder.setLights(ns.LEDColor, 500, 500);
}
if (ns.EnableSound && !ns.SoundSource.isEmpty())
{
mBuilder.setSound(Uri.parse(ns.SoundSource), AudioManager.STREAM_ALARM);
}
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);
Notification n = mBuilder.build();
if (ns.EnableSound && !ns.SoundSource.isEmpty() && ns.RepeatSound) n.flags |= Notification.FLAG_INSISTENT;
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);
if (mNotificationManager != null) mNotificationManager.notify(0, n);
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 ->
{
Activity a = getActivity();
if (a == null) return;
AlertDialog.Builder builder = new AlertDialog.Builder(a);
builder.setTitle("Confirm");
builder.setMessage("Reset account key?");
builder.setPositiveButton("YES", (dialog, which) -> {
View lpnl = v.findViewById(R.id.loadingPanel);
lpnl.setVisibility(View.VISIBLE);
SCNSettings.inst().reset(lpnl);
dialog.dismiss();
});
builder.setNegativeButton("NO", (dialog, which) -> dialog.dismiss());
AlertDialog alert = builder.create();
alert.show();
});
v.findViewById(R.id.btnClearLocalStorage).setOnClickListener(cv ->
{
Activity a = getActivity();
if (a == null) return;
AlertDialog.Builder builder = new AlertDialog.Builder(a);
builder.setTitle("Confirm");
builder.setMessage("Clear local messages?");
builder.setPositiveButton("YES", (dialog, which) -> {
CMessageList.inst().clear();
SCNApp.showToast("Notifications cleared", 1000);
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);
});
@@ -94,6 +131,7 @@ public class AccountFragment extends Fragment
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,13 +1,23 @@
package com.blackforestbytes.simplecloudnotifier.view;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.blackforestbytes.simplecloudnotifier.R;
import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
import com.blackforestbytes.simplecloudnotifier.service.IABService;
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
import com.blackforestbytes.simplecloudnotifier.view.debug.QueryLogActivity;
import com.google.android.material.tabs.TabLayout;
import androidx.appcompat.app.AppCompatActivity;
@@ -15,20 +25,32 @@ 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);
@@ -39,9 +61,25 @@ public class MainActivity extends AppCompatActivity
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.setupWithViewPager(viewPager);
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { /* */ }
@Override
public void onPageSelected(int position)
{
if (position != 2) adpTabs.tab3.onViewpagerHide();
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
SCNApp.register(this);
IABService.startup(this);
SCNSettings.inst().work(this);
SCNSettings.inst().work(this, true);
}
@Override
@@ -61,4 +99,126 @@ public class MainActivity extends AppCompatActivity
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;
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,14 +86,39 @@ 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)
{
@@ -75,10 +127,33 @@ public class MessageAdapter extends RecyclerView.Adapter
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,24 +1,33 @@
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()
{
@@ -31,8 +40,12 @@ 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));
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();
@@ -45,6 +58,33 @@ public class NotificationsFragment extends Fragment
public void updateProState()
{
adView.setVisibility(IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE) != null ? View.GONE : View.VISIBLE);
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,30 +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.android.billingclient.api.Purchase;
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.service.NotificationService;
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;
@@ -36,6 +56,8 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
private Button prefUpgradeAccount;
private TextView prefUpgradeAccount_msg;
private TextView prefUpgradeAccount_info;
private Switch prefEnableDeleteSwipe;
private EditText prefPreviewLineCount;
private Switch prefMsgLowEnableSound;
private TextView prefMsgLowRingtone_value;
@@ -45,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;
@@ -54,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;
@@ -63,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
@@ -90,6 +126,8 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
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);
@@ -99,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);
@@ -108,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);
@@ -117,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();
@@ -126,15 +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));
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 );
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));
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);
@@ -142,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);
@@ -149,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);
@@ -156,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()
{
@@ -175,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());
@@ -188,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());
@@ -195,13 +283,140 @@ 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();
NotificationService.inst().updateChannels();
SCNApp.getMainActivity().adpTabs.tab1.updateDeleteSwipeEnabled();
}
private void onUpgradeAccount()
@@ -211,11 +426,11 @@ public class SettingsFragment extends Fragment implements MusicPickerListener
public void updateProState()
{
Purchase p = IABService.inst().getPurchaseCached(IABService.IAB_PRO_MODE);
boolean pmode = IABService.inst().getPurchaseCachedSimple(IABService.IAB_PRO_MODE);
prefUpgradeAccount.setVisibility( p != null ? View.GONE : View.VISIBLE);
prefUpgradeAccount_info.setVisibility(p != null ? View.GONE : View.VISIBLE);
prefUpgradeAccount_msg.setVisibility( p != null ? View.VISIBLE : View.GONE );
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)
@@ -233,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();
@@ -248,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();
@@ -263,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();
@@ -356,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: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"
@@ -96,16 +86,75 @@
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<LinearLayout
android:orientation="vertical"
<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"
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">
<Button
<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_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" />
@@ -151,23 +200,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<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/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"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:layout_height="wrap_content"/>
<View
android:layout_width="match_parent"
@@ -176,20 +216,15 @@
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
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">
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp"
android:gravity="center">
<TextView
android:id="@+id/tvMsgLowRingtone"
@@ -206,9 +241,8 @@
android:spinnerMode="dialog"
android:text="Whatever"
tools:ignore="HardcodedText" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
@@ -217,22 +251,67 @@
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"
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
@@ -242,23 +321,14 @@
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:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<View
android:layout_width="match_parent"
@@ -308,23 +378,15 @@
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/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" />
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
@@ -349,23 +411,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<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/prefMsgNormEnableSound"
android:text="@string/str_msg_enablesound"
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" />
<View
android:layout_width="match_parent"
@@ -374,20 +427,15 @@
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
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">
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp"
android:gravity="center">
<TextView
android:id="@+id/tvMsgNormRingtone"
@@ -406,6 +454,58 @@
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_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
@@ -415,23 +515,14 @@
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/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>
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<View
android:layout_width="match_parent"
@@ -440,23 +531,14 @@
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:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<View
android:layout_width="match_parent"
@@ -506,23 +588,14 @@
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/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>
@@ -547,23 +620,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<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/prefMsgHighEnableSound"
android:text="@string/str_msg_enablesound"
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" />
<View
android:layout_width="match_parent"
@@ -572,18 +636,15 @@
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<androidx.constraintlayout.widget.ConstraintLayout
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: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">
@@ -604,6 +665,51 @@
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_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
@@ -613,23 +719,14 @@
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/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>
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<View
android:layout_width="match_parent"
@@ -638,23 +735,14 @@
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:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<View
android:layout_width="match_parent"
@@ -704,28 +792,37 @@
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/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,9 +1,43 @@
<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">
<RelativeLayout
android:id="@+id/layoutBack"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/card_margin"
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" />
<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"
@@ -55,8 +89,8 @@
app:layout_constraintRight_toLeftOf="@+id/ivPriority"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_margin="4sp"
android:ellipsize="none"
android:maxLines="32"
android:ellipsize="end"
android:maxLines="6"
android:scrollHorizontally="false"
android:text="asdasd asdasd asdasd a" />
@@ -74,8 +108,47 @@
android:paddingTop="3dp"
android:contentDescription="@string/desc_priority_icon" />
<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>
</LinearLayout>
</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,23 +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 @@
#Sun Nov 11 19:37:57 CET 2018
VERSION_NAME=0.0.5
VERSION_CODE=5
#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.2.0'
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>

17
androidExportReader/.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View File

@@ -0,0 +1,11 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="JavadocReference" enabled="true" level="WARNING" enabled_by_default="true" editorAttributes="WARNING_ATTRIBUTES" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

View File

@@ -0,0 +1,25 @@
<?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="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://jitpack.io" />
</remote-repository>
</component>
</project>

10
androidExportReader/.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_18" default="true" project-jdk-name="openjdk-18" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

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

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@@ -0,0 +1,47 @@
buildscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath 'gradle.plugin.com.github.johnrengelman:shadow:7.1.2'
}
}
plugins {
id 'java'
id("com.github.johnrengelman.shadow") version "7.1.2"
id 'application'
}
group 'com.blackforestbytes'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
maven { url "https://jitpack.io" }
}
application {
mainClass = 'com.blackforestbytes.Main'
}
jar {
manifest {
attributes 'Main-Class': application.mainClass
}
}
tasks.jar {
manifest.attributes["Main-Class"] = application.mainClass
}
dependencies {
implementation 'com.github.RalleYTN:SimpleJSON:2.1.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
test {
useJUnitPlatform()
}

Binary file not shown.

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

240
androidExportReader/gradlew vendored Executable file
View File

@@ -0,0 +1,240 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

91
androidExportReader/gradlew.bat vendored Normal file
View File

@@ -0,0 +1,91 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,2 @@
rootProject.name = 'androidExportReader'

View File

@@ -0,0 +1,104 @@
package com.blackforestbytes;
import de.ralleytn.simple.json.JSONArray;
import de.ralleytn.simple.json.JSONFormatter;
import de.ralleytn.simple.json.JSONObject;
import java.io.ObjectInputStream;
import java.net.URI;
import java.nio.file.FileSystems;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class Main {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
if (args.length != 1) {
System.err.println("call with ./androidExportConvert scn_export.dat");
return;
}
try {
var path = FileSystems.getDefault().getPath(args[0]).normalize().toAbsolutePath().toUri().toURL();
ObjectInputStream stream = new ObjectInputStream(path.openStream());
Map<String, ?> d1 = new HashMap<>((Map<String, ?>)stream.readObject());
Map<String, ?> d2 = new HashMap<>((Map<String, ?>)stream.readObject());
Map<String, ?> d3 = new HashMap<>((Map<String, ?>)stream.readObject());
Map<String, ?> d4 = new HashMap<>((Map<String, ?>)stream.readObject());
stream.close();
JSONObject root = new JSONObject();
var subConfig = new JSONObject();
var subIAB = new JSONArray();
var subCMessageList = new JSONArray();
var subAcks = new JSONArray();
var subQueryLog = new JSONArray();
for (Map.Entry<String, ?> entry : d1.entrySet())
{
if (entry.getValue() instanceof String) subConfig.put(entry.getKey(), (String)entry.getValue());
if (entry.getValue() instanceof Boolean) subConfig.put(entry.getKey(), (Boolean)entry.getValue());
if (entry.getValue() instanceof Float) subConfig.put(entry.getKey(), (Float)entry.getValue());
if (entry.getValue() instanceof Integer) subConfig.put(entry.getKey(), (Integer)entry.getValue());
if (entry.getValue() instanceof Long) subConfig.put(entry.getKey(), (Long)entry.getValue());
if (entry.getValue() instanceof Set<?>) subConfig.put(entry.getKey(), ((Set<String>)entry.getValue()).toArray());
}
for (int i = 0; i < (Integer)d2.get("c"); i++) {
var obj = new JSONObject();
obj.put("key", d2.get("["+i+"]->key"));
obj.put("value", d2.get("["+i+"]->value"));
subIAB.add(obj);
}
for (int i = 0; i < (Integer)d3.get("message_count"); i++) {
if (d3.get("message["+i+"].scnid") == null)
throw new Exception("ONF");
var obj = new JSONObject();
obj.put("timestamp", d3.get("message["+i+"].timestamp"));
obj.put("title", d3.get("message["+i+"].title"));
obj.put("content", d3.get("message["+i+"].content"));
obj.put("priority", d3.get("message["+i+"].priority"));
obj.put("scnid", d3.get("message["+i+"].scnid"));
subCMessageList.add(obj);
}
subAcks.addAll(((Set<String>)d3.get("acks")).stream().map(p -> Long.decode("0x"+p)).toList());
for (int i = 0; i < (Integer)d4.get("history_count"); i++) {
if (d4.get("message["+(i+1000)+"].Name") == null)
throw new Exception("ONF");
var obj = new JSONObject();
obj.put("Level", d4.get("message["+(i+1000)+"].Level"));
obj.put("Timestamp", d4.get("message["+(i+1000)+"].Timestamp"));
obj.put("Name", d4.get("message["+(i+1000)+"].Name"));
obj.put("URL", d4.get("message["+(i+1000)+"].URL"));
obj.put("Response", d4.get("message["+(i+1000)+"].Response"));
obj.put("ResponseCode", d4.get("message["+(i+1000)+"].ResponseCode"));
obj.put("ExceptionString", d4.get("message["+(i+1000)+"].ExceptionString"));
subQueryLog.add(obj);
}
root.put("config", subConfig);
root.put("iab", subIAB);
root.put("cmessagelist", subCMessageList);
root.put("acks", subAcks);
root.put("querylog", subQueryLog);
System.out.println(new JSONFormatter().format(root.toString()));
} catch (Exception e) {
e.printStackTrace();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
data/README/badge_apple.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

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