321 Commits

Author SHA1 Message Date
5b381eb3b9 Init react-native project 2023-11-18 19:34:08 +01:00
9f3e183d72 Update goext to v0.0.291 2023-10-26 13:14:11 +02:00
51f5f1005a Switch to new Swaggo Makefile template
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m57s
Build Docker and Deploy / Deploy to Server (push) Successful in 12s
2023-10-17 16:47:50 +02:00
0a380f861e Add scn_send.sh to repo 2023-10-14 21:37:00 +02:00
b712ad3488 Use better go guard in Makefile::clean
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 3m44s
Build Docker and Deploy / Deploy to Server (push) Successful in 12s
2023-08-16 09:48:28 +02:00
9f656bdefe Refactor message sending into logic package (+ more tests for uptime-kuma)
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m33s
Build Docker and Deploy / Deploy to Server (push) Successful in 7s
2023-08-12 19:07:39 +02:00
a4a651229c Added gitea-actions workflow
All checks were successful
Build Docker and Deploy / Build Docker Container (push) Successful in 1m43s
Build Docker and Deploy / Deploy to Server (push) Successful in 7s
2023-08-12 15:51:14 +02:00
4773800f23 remove old PHP project 2023-08-12 11:14:32 +02:00
bef0b8189e uptime kuma webhook endpoint 2023-08-12 11:14:32 +02:00
674714f0f3 Return more data in /users/{uid} 2023-07-30 16:53:46 +02:00
ee9e858584 Increase pro quota and bodysize 2023-07-30 16:37:39 +02:00
165c6d8614 Refactor API of /api/v2/users/{uid}/subscriptions 2023-07-30 15:58:37 +02:00
8a6719fc19 Remove message.owner_user_id field and implement db migrations 2023-07-27 17:44:06 +02:00
308361a834 Prevent deleting messages of subscribed-only channels 2023-07-27 15:23:56 +02:00
44df964f6f todos 2023-07-04 11:31:52 +02:00
56bf266919 fix scn_send script with non-urlencoded data 2023-06-26 14:49:14 +02:00
f3658d6636 fix wrong data in compat_ids (requery.php) 2023-06-23 11:50:18 +02:00
1bb37eec30 TODO's 2023-06-18 14:28:58 +02:00
59511b2345 Fix bug in migration script 2023-06-18 14:16:37 +02:00
5b7bc02c61 Fix validation in web form 2023-06-18 13:25:00 +02:00
b329f537e7 Fix message_sent html 2023-06-18 13:19:51 +02:00
5879e81759 Enable RequestLog on dev/stag/prod 2023-06-18 13:11:48 +02:00
f4e88bef77 Fix NPE in compat-ack 2023-06-18 13:09:36 +02:00
b3ec45309c Insert exclam on compat clients if message uses old channel syntax 2023-06-18 11:59:26 +02:00
2fbc892898 various fixes in scn_send script 2023-06-18 04:45:28 +02:00
c46190c3fc Support x-www-form-urlencoded form-data 2023-06-18 03:46:01 +02:00
860e540de1 Better CreateKey API (make all_channels and channels optional) 2023-06-18 02:54:41 +02:00
8cde286cac Fix failing tests 2023-06-18 02:36:44 +02:00
90830fe384 Fix empty string in channels field in GetKeys route 2023-06-18 02:34:04 +02:00
686f89f75d change URL to simplecloudnotifier.de 2023-06-18 02:22:29 +02:00
4210af5680 Properly implement compat unack_count 2023-06-18 02:09:05 +02:00
aefc368cfd Send compat-msgid to compat clients (BF old android client) 2023-06-18 01:55:58 +02:00
67218d8045 Added a few logs 2023-06-18 01:36:34 +02:00
c05deb3a41 allow \n in private-key envs 2023-06-18 01:29:13 +02:00
43d0107fb5 Update goext (fix bool env parsing) 2023-06-18 01:18:33 +02:00
ece7612f9d more migration fixes 2023-06-18 00:49:29 +02:00
a9809d90cb fix exception in js-send (logic.js) 2023-06-18 00:25:10 +02:00
bbc9a79996 Fix bug in migration script 2023-06-18 00:24:53 +02:00
b71f1885ec Fix wrongly named env variables 2023-06-17 23:40:46 +02:00
885aad2047 Update migrationscript 2023-06-17 23:23:54 +02:00
7121afab08 Fix [TestQuotaExceededNoPro, TestQuotaExceededPro] 2023-06-17 20:16:02 +02:00
d9a4c4ffd6 Fix [TestChannelMessageCounter] 2023-06-17 20:08:39 +02:00
fb826919a6 added linter 2023-06-14 15:03:37 +02:00
22720169a2 Tests[TestUserMessageCounter, TestTokenKeysMessageCounter, TestChannelMessageCounter] 2023-06-10 03:41:54 +02:00
7fefd251db Update TODO.md 2023-06-10 00:15:42 +02:00
5de4f67344 use nil-safe json renderer in ginresp 2023-06-09 21:37:30 +02:00
d396a12d68 fix missing error on missing json header 2023-06-09 20:14:30 +02:00
3888c91a6b Switch to multi-stage Dockerbuild 2023-06-06 18:52:03 +02:00
562bac6987 Move enum-generate to goext 2023-06-05 13:37:49 +02:00
e825b4dd85 Update PrimaryHash3 2023-05-30 14:25:55 +02:00
08587b7a7a Tests[**Subscription**] 2023-05-29 01:51:51 +02:00
0daca2cf8f added UpdateClient route 2023-05-28 23:25:18 +02:00
3a9b15c2be Use sq.InsertSingle to insert entities 2023-05-28 22:27:38 +02:00
e9b4db0f1c Validate db schema before startup 2023-05-28 19:59:57 +02:00
312a31ce9e Fix missing certificates in docker 2023-05-28 17:54:53 +02:00
d4a8a2e720 Tests[SendWithAdminKey, SendWithSendKey, SendWithReadKey, SendWithPermissionSendKey] 2023-05-28 17:38:19 +02:00
dcb4f253d8 fix swagger errors 2023-05-28 17:04:44 +02:00
d0a04bae84 Move to multistage Dockerfile 2023-05-28 16:03:14 +02:00
34ac96edd7 Tests[...]:
- SearchMessageFTSMulti
 - ListMessagesFilteredChannels
 - ListMessagesFilteredChannelIDs
 - ListMessagesFilteredSenders
 - ListMessagesFilteredTime
 - ListMessagesFilteredPriority
 - ListMessagesFilteredKeyTokens
2023-05-28 15:46:46 +02:00
b42ce84c3e Added [Channels, ChannelIDs, Senders, TimeBefore, TimeAfter, Priority, KeyTokens] filter to ListMessages 2023-05-28 14:05:53 +02:00
2db779b44f split api.go into multiple files 2023-05-28 13:39:20 +02:00
397bfe78aa Remove Channel/Username normalization (except TrimSpace) 2023-05-28 13:14:05 +02:00
efaad3f97c Fix RequestLogCollectorJob sometimes not properly shutting down 2023-05-28 12:31:14 +02:00
624c613bd1 Tests[ListChannelMessagesOfUnsubscribed, ListChannelMessagesOfUnconfirmed1, ListChannelMessagesOfUnconfirmed2, ListChannelMessagesOfRevokedConfirmation] 2023-05-28 03:38:33 +02:00
07b0632c95 Tests[ListChannelSubscriptions] 2023-05-28 03:16:29 +02:00
3d1e6cfa17 Tests[ListSubscribedChannelMessages] 2023-05-28 02:50:55 +02:00
3db636d41a Tests[ListChannelMessages] 2023-05-28 02:40:24 +02:00
2053b8f07f Tests[ListMessages, ListMessagesPaginated, ListMessagesPaginatedInvalid] 2023-05-28 02:27:15 +02:00
b1681b53e4 Tests[TokenKeys, TokenKeysInitial, TokenKeysCreate, TokenKeysUpdate, TokenKeysDelete, TokenKeysDeleteSelf, TokenKeysDowngradeSelf, TokenKeysPermissions] 2023-05-28 01:39:55 +02:00
03f60ff316 Tests[RequestLogSimple] 2023-05-27 23:54:14 +02:00
b2df0a5a02 Send channel as prefix for compat clients 2023-05-27 20:02:16 +02:00
8826cb0312 Save used keytoken in messages 2023-05-27 18:51:20 +02:00
a0c72f5b94 Add keytoken explanation to api_more.html 2023-05-27 18:16:32 +02:00
7d9a58ae54 Fix swagger 2023-05-27 17:51:56 +02:00
fd72b512f8 Directly use pygmentize in Makefile (for scn script in Website) 2023-05-27 17:42:06 +02:00
28c2721036 Use gotestsum for make test 2023-05-27 15:39:07 +02:00
a1788bf75a use swaggo 1.8.12 2023-04-25 20:08:30 +02:00
b1bd278f9b Add KeyToken authorization 2023-04-21 21:45:16 +02:00
16f6ab4861 re-implement ack behaviour from version 1.0 for compat 2023-02-03 22:51:03 +01:00
01934e29b1 todos 2023-02-03 20:12:41 +01:00
d1cefb0150 API versioning ( basePath == /api/v2/* ) 2023-01-27 10:04:06 +01:00
27b189d33a BF 2023-01-24 13:52:11 +01:00
e05d88682a Tests[ListClients] 2023-01-18 21:56:37 +01:00
2a5f1f5f7e Tests[CompatUpdate, CompatUpdateFCM] 2023-01-17 23:10:38 +01:00
e7a45d9a05 Tests[UgradeUserToPro, DowngradeUserToNonPro, FailedUgradeUserToPro, FailToCreateProUser, ReuseProToken] 2023-01-17 22:56:04 +01:00
ec9a326002 Tests[CompatUpgrade] 2023-01-17 22:32:13 +01:00
23c7729fcf Tests[CompatRegisterPro] 2023-01-17 22:03:27 +01:00
7fcd324299 Tests[CompatRequery] 2023-01-17 21:47:53 +01:00
1633449638 Tests[CompatExpand] 2023-01-17 21:30:53 +01:00
57231a1406 Tests[CompatAck] 2023-01-17 21:14:42 +01:00
2eb6292733 improve AssertEqual to handle annoying json.Unmarshal type conversions... 2023-01-17 20:41:45 +01:00
ff24493ff3 Tests[CompatRegister, CompatInfo] 2023-01-17 20:27:20 +01:00
3d602af135 Tests[TestSendCompatMessageByQuery, TestSendCompatMessageByFormData] 2023-01-16 20:29:49 +01:00
590665a5e9 create compat-id for messages && TestCreateCompatUser 2023-01-16 18:53:22 +01:00
89fd0dfed7 create migration script for old data 2023-01-15 06:30:30 +01:00
82bc887767 Move to string-ids for all entities (compat translation for existing data) 2023-01-14 00:48:51 +01:00
acd7de0dee cherry-pick caller logprint fix from psycho-backend 2023-01-13 17:51:55 +01:00
e737cd9d5c requests-log db 2023-01-13 17:17:17 +01:00
0ec7a9d274 add methods for (some) missing test cases 2023-01-13 12:54:19 +01:00
e49d9159e4 Added freely editable description_name field to channel 2023-01-13 12:43:20 +01:00
3343285761 goext 55 2023-01-06 02:03:10 +01:00
14bba38324 migrate to multiple sqlite db files ( primary + requests + logs ) 2023-01-06 00:39:21 +01:00
679277d59e catch panic in gin 2022-12-28 00:32:15 +01:00
cebb2ae2b6 small cleanups 2022-12-23 20:27:21 +01:00
56d9f977ae Tests[ListChannelsDefault, ListChannelsOwned, ListChannelsSubscribedAny, ListChannelsAllAny, ListChannelsSubscribed, ListChannelsAll] 2022-12-22 17:29:59 +01:00
984470b47d Fix sql-preprocessor leading to deadlocks in parallel requests 2022-12-22 16:51:04 +01:00
0112d681ac Fix SQL unmarshalling of optional nested structs (LEFT JOIN) 2022-12-22 12:43:40 +01:00
0cb2a977a0 Save internal_name and display_name in channel 2022-12-22 11:22:36 +01:00
f65c231ba0 Properly shutdown database on SIGTERM 2022-12-22 10:21:10 +01:00
dbc014f819 Added a SQL-Preprocessor - this way we can unmarshal recursive structures (LEFT JOIN etc) 2022-12-21 18:14:13 +01:00
bbf7962e29 move server/* to scnserver/* 2022-12-21 12:35:56 +01:00
2b4d77bab4 Cleaner swagger routes 2022-12-21 11:03:31 +01:00
8582674b44 Refactoring 2022-12-20 13:55:09 +01:00
f7675be834 Use multiple DB connections but retry failed requests 2022-12-20 09:52:33 +01:00
00d77e508d Fix TestSendParallel by using only a single DB connection
see https://github.com/mattn/go-sqlite3/issues/274
see https://github.com/mattn/go-sqlite3/issues/209
see https://stackoverflow.com/questions/32479071/sqlite3-error-database-is-locked-in-golang
2022-12-20 09:22:18 +01:00
e90cfe34e9 switch to new registry image-name 2022-12-16 14:57:17 +01:00
54dfd535a4 Move parseConfOverride() to goext 2022-12-16 01:07:48 +01:00
5a02eb6d18 Prefix all config key with SCN_* 2022-12-14 18:46:26 +01:00
97fc9319d1 Fix wrong env keys in config.go 2022-12-14 18:43:32 +01:00
03b4acd13e Tests[CreateProUser] 2022-12-14 18:38:30 +01:00
86f06a3c6a Tests[CreateChannel, CreateChannelNameTooLong, ChannelNameNormalization] 2022-12-14 18:27:22 +01:00
06e8d2a6e2 Tests[SendLongContentPro] 2022-12-14 18:18:02 +01:00
99f248a8ce Tests[SendParallel] (skipped for now) 2022-12-14 18:08:03 +01:00
c7aaa6ad98 Tests[QuotaExeededPro] 2022-12-14 17:56:14 +01:00
cb5ce66c1a Tests[QuotaExeededNoPro] 2022-12-14 17:56:03 +01:00
0750bf1d8a cleanup test local-url 2022-12-14 17:02:18 +01:00
203360e8b5 Tests[SendToTooLongChannel] 2022-12-14 16:57:08 +01:00
ef1844109f Tests[SendToManualChannel] 2022-12-14 16:38:01 +01:00
de6ad35f60 new Endpoint: CreateChannel(*) 2022-12-14 14:29:59 +01:00
fbb289dedf Added error descriptions to swagger 2022-12-14 14:27:41 +01:00
f1e87170f0 Tests[SendToNewChannel] 2022-12-14 14:30:34 +01:00
66ecad27a7 Only soft-delete messages 2022-12-14 12:29:55 +01:00
98b1e8bd80 move ScanAll/ScanSingle in sq package 2022-12-11 03:14:42 +01:00
26cd1533b4 Tests[SearchMessageFTSSimple] 2022-12-11 02:47:23 +01:00
3692b915f3 Messagefilter (+FTS) [WIP] 2022-12-10 03:38:48 +01:00
06788c3e12 TestData-Factory [WIP] 2022-12-09 00:40:50 +01:00
edfcdd1135 TestData-Factory [WIP] 2022-12-09 00:13:10 +01:00
dd2f3baa0c Properly close db cursors after use 2022-12-08 11:31:52 +01:00
7db70e392b Simplify fts table schema 2022-12-07 23:43:52 +01:00
0cae24a612 Move sq + ParseDurShortString() to goext and change conf values by env 2022-12-07 23:32:58 +01:00
8db0fa37db Move to own sql abstraction on top of jmoiron/sqlx 2022-12-07 22:11:44 +01:00
d27e3d9a91 Made sqlite tables strict (type checked) 2022-12-07 22:11:07 +01:00
fa5a4107a6 Added FTS5 table to schema (full-text-search) 2022-12-07 22:10:46 +01:00
234188c4d4 Tests[SendCompat] 2022-12-01 14:45:31 +01:00
9b700581f3 Tests[SendSimpleMessageAlt1] 2022-12-01 14:30:46 +01:00
12db23d076 Improve test performance (better waiting logic until http server is up) 2022-11-30 23:46:28 +01:00
fd182f0abb Fix timeout in ReadSchema/GetMeta etc method (fixes /health call taking 2 seconds) 2022-11-30 22:59:33 +01:00
7eab74e65c Tests[SendWithTimestamp, SendInvalidTimestamp] 2022-11-30 22:29:12 +01:00
e0ecd4d9ff Tests[SendInvalidPriority] 2022-11-30 21:51:48 +01:00
1ca09c16d3 Tests[SendWithPriority] 2022-11-30 21:39:14 +01:00
a7df476e79 Tests[SendIdempotent] 2022-11-30 21:17:29 +01:00
4e5eac6178 Tests[SendLongContent, LongContent, LongTitle] 2022-11-30 20:59:01 +01:00
91a6808ad2 Tests[SendWithSendername] 2022-11-30 20:47:43 +01:00
11a6517156 Tests[SendContentMessage] 2022-11-30 20:39:04 +01:00
7aa7eb234d Tests[SendSimpleMessageQuery, SendSimpleMessageForm, SendSimpleMessageFormAndQuery, SendSimpleMessageJSONAndQuery] 2022-11-30 20:23:31 +01:00
62d7df9710 Tests[TestSendSimpleMessageJSON] 2022-11-30 17:58:04 +01:00
0ff1188c3d Added swagger themes 2022-11-30 16:46:55 +01:00
b6e8d037a0 Add json tags to query structs (otherwise swag does not get the correct names) 2022-11-30 16:46:14 +01:00
7a11b2c76f Tests[UpdateUsername, RecreateKeys, DeleteUser] 2022-11-30 13:57:55 +01:00
7f56dbdbfa Tests[GetClient, CreateClient, DeleteClient, ReuseFCM] 2022-11-30 12:40:03 +01:00
df4eb15df8 Tests[CreateUser] 2022-11-30 10:35:05 +01:00
ac9ae06cc8 Save SenderName || SenderIP per message 2022-11-29 11:07:15 +01:00
464cf3ec7e Better error message on missing envs 2022-11-26 17:03:26 +01:00
bf0ce5c963 dark-mode 2022-11-26 16:30:30 +01:00
3a0c65a849 Added google androidpublisher/v3 api to verify google purchase tokens 2022-11-25 22:42:21 +01:00
6d80638cf8 CreateUser test 2022-11-24 12:53:27 +01:00
37e09d6532 cleanup swagger 2022-11-23 22:12:47 +01:00
8ea3fdcfef tests (boilerplate) 2022-11-23 20:21:49 +01:00
1bc847cdc9 tags/grouping for API 2022-11-23 19:32:23 +01:00
03c35d6446 update HTML with new methods 2023-06-18 04:07:13 +02:00
d5aea1a828 README 2022-11-21 18:46:55 +01:00
f17ddb4ace switch to debian base-image (no more static linking) 2022-11-20 22:18:48 +01:00
0cc6e27267 Use ID types 2022-11-20 22:18:24 +01:00
ca58aa782d Routes to refresh user and channel keys 2022-11-20 21:35:08 +01:00
e8671e8650 Selector param for ListChannels() 2022-11-20 21:15:06 +01:00
d46601be5c CreateMessage() 2022-11-20 20:34:18 +01:00
d30e2cefc0 firebase via REST (less dependencies) 2023-06-18 04:06:52 +02:00
08a93551e7 DeliveryRetryJob 2022-11-20 15:40:22 +01:00
c2899fd727 swagger doku for compat methods 2022-11-20 13:18:09 +01:00
5ec66e1777 cleanup 2022-11-20 12:59:43 +01:00
516809cd02 Dockerfile, CONF_NS and fix sqlite3 under alpine 2022-11-20 03:41:38 +01:00
0d3526221d replace PHP in html with js & bugfixes 2022-11-20 03:18:23 +01:00
728b12107f compat methods 2022-11-20 01:28:32 +01:00
b56c021356 ListChannelMessages() 2022-11-20 00:30:30 +01:00
80f3b982d2 ListMessages() 2022-11-20 00:21:59 +01:00
0d641b727f CreateSubscription(), UpdateSubscription(), GetMessage(), DeleteMessage() 2022-11-19 23:16:54 +01:00
8278c059ad fix context in methods.go 2022-11-19 17:15:46 +01:00
7af0ff5413 TODO's 2022-11-19 17:09:23 +01:00
5c2877bdb8 ListChannels(), GetChannel(), ListUserSubscriptions(), ListChannelSubscriptions(), GetSubscription(), CancelSubscription() 2022-11-19 17:07:30 +01:00
85bfe79115 SendMessage() 2022-11-19 16:29:14 +01:00
fb37f94c0a firebase implementation 2022-11-19 15:11:36 +01:00
e53f40866e DeleteClient() 2022-11-19 12:59:25 +01:00
650ba20e5d AddClient() 2022-11-19 12:56:44 +01:00
6e01c41c22 GetClient() 2022-11-19 12:50:41 +01:00
f555f0f1cf ListClients() 2022-11-19 12:47:23 +01:00
35ef2175bc UpdateUser() works 2022-11-18 23:33:07 +01:00
55f53deadf GetUser() works 2022-11-18 23:12:37 +01:00
5991631bfa POST:/users works 2022-11-18 21:25:40 +01:00
34a27d9ca4 schema 3.0 2022-11-17 21:26:52 +01:00
1671490485 implement a bit of the register.php call (and the DB schema) 2022-11-13 22:31:28 +01:00
0e58a5c5f0 added template for new golang backend 2022-11-13 19:25:44 +01:00
bfb-vserver-wwwdata
bd11d7973c server BF 2022-10-17 21:35:06 +02:00
f3b5b09ed0 A few code fixes 2020-11-04 10:08:06 +01:00
ce641bf7d2 Update dependencies 2020-11-03 14:41:20 +01:00
f1c7314dca better message for ERR::CONTENT_TOO_LONG 2020-08-03 13:25:49 +02:00
019408dc6d use LONGTEXT for content column 2020-04-21 10:45:03 +02:00
jenkins
cdba3540c2 [Jenkins] Increment version 2020-03-05 15:29:40 +00:00
885d997ff3 fix exceptions in register.php 2020-03-05 16:26:58 +01:00
5118ab3cbf Fix notifications not click-able 2020-03-05 16:20:25 +01:00
jenkins
2812377f5c [Jenkins] Increment version 2020-01-05 22:19:08 +00:00
93b40a9c7f skip version code 20 2020-01-05 23:18:20 +01:00
b95ddcc811 backup/export und import 2020-01-05 22:59:57 +01:00
90ba3c1134 fix api urls 2020-01-05 22:33:58 +01:00
05174958b2 Added php example 2019-06-30 21:46:28 +02:00
jenkins
24be9b2013 [Jenkins] Increment version 2018-12-14 22:09:01 +01:00
29ce4b727c More fixes for paid mode 2018-12-14 22:07:43 +01:00
jenkins
f178019ffe [Jenkins] Increment version 2018-12-14 19:32:22 +01:00
0a1b948042 Stupid bug -.- 2018-12-14 19:23:36 +01:00
jenkins
74cbfb235e [Jenkins] Increment version 2018-12-14 18:38:12 +01:00
63c4104a89 Add recieved messages to ComLog 2018-12-14 18:35:51 +01:00
2597287b1e Fixed wrong pro-mode reset 2018-12-14 18:30:03 +01:00
jenkins
316156f0f0 [Jenkins] Increment version 2018-12-11 13:55:38 +01:00
5286e869cc config for preview-line-count 2018-12-11 13:53:47 +01:00
d6dcf28d89 Share+Delete Button 2018-12-11 13:22:39 +01:00
1d983b9ac0 Collapse message on click-again 2018-12-11 12:31:44 +01:00
e525221010 Show Querylog via hidden shit 2018-12-11 12:25:10 +01:00
4cde4703f2 fixed exception in requery.php 2018-12-11 10:40:30 +01:00
4a07e58c16 fix expand + requery 2018-11-25 21:03:05 +01:00
b12356575a Description text correction 2018-11-25 18:11:42 +01:00
77f571de7d Send timestamp with request 2018-11-25 18:02:25 +01:00
jenkins
f5eef7563b [Jenkins] Increment version 2018-11-23 19:40:31 +01:00
741c09b1e4 fixed TabLayout animation 2018-11-23 19:35:22 +01:00
0c0d7d181f enable light in notification channel (android-oreo) + shorter vibrate 2018-11-23 18:48:46 +01:00
9304da9422 fix NPE in SettingsFrame 2018-11-23 18:42:10 +01:00
jenkins
d7afdd00f2 [Jenkins] Increment version 2018-11-19 18:43:36 +01:00
b780ccea1c wrong notification channel name 2018-11-19 18:41:56 +01:00
jenkins
5d8e871871 [Jenkins] Increment version 2018-11-19 12:31:22 +01:00
4655d688c9 README 2018-11-18 17:19:15 +01:00
8263c0ad95 phone image 2018-11-18 17:12:44 +01:00
a701afd09b OWA tracking 2018-11-18 03:34:13 +01:00
jenkins
1e02d8c01f [Jenkins] Increment version 2018-11-18 03:24:46 +01:00
e4651375aa fix qr url 2018-11-18 03:23:22 +01:00
jenkins
afce4c6391 [Jenkins] Increment version 2018-11-18 00:15:21 +01:00
e95d0cb010 added disable warning 2018-11-18 00:13:36 +01:00
f717355519 use "messages" everywhere instead of "notifications" 2018-11-18 00:11:30 +01:00
3368e514ca red quota icon if OOQ 2018-11-18 00:02:00 +01:00
9dca27177f fixed js logic for errors 2018-11-17 23:59:57 +01:00
c63274f7a9 show subtext on android-o 2018-11-17 23:48:39 +01:00
6c2d2d2345 fixed requery only getting first message 2018-11-17 22:51:02 +01:00
a6fbaa192f config example 2018-11-17 19:28:44 +01:00
jenkins
a01f156535 [Jenkins] Increment version 2018-11-17 19:00:13 +01:00
3b021c09dc notification icons 2018-11-17 18:58:18 +01:00
287176c881 screenshots 2018-11-17 18:21:53 +01:00
84eeb5a002 red high-prio icons 2018-11-17 18:21:25 +01:00
b892532023 send big content over FCM 2018-11-17 17:59:43 +01:00
jenkins
56cdc52bb4 [Jenkins] Increment version 2018-11-17 17:19:59 +01:00
9ef6b1dd91 swipe-to-delete setting 2018-11-17 17:18:15 +01:00
6f7585323b fixed notification being overwritten 2018-11-17 17:06:34 +01:00
92135e64a3 css 2018-11-17 17:00:14 +01:00
28efeea30b message delete problems 2018-11-17 16:45:42 +01:00
a8a074907b fixed update when in message-list 2018-11-17 16:02:31 +01:00
6c29ec9820 max-size = 2048 chars (FCM restrictions) 2018-11-17 15:44:44 +01:00
jenkins
8ec23144ca [Jenkins] Increment version 2018-11-17 14:28:04 +01:00
b703853b28 send.bash example 2018-11-17 14:26:29 +01:00
6f7529fc9b fixed duplicate-recieve 2018-11-17 14:01:51 +01:00
27b45098f0 ups 2018-11-17 13:46:23 +01:00
21f66d99cd fixed timestamp format in requery 2018-11-17 13:44:42 +01:00
1745051e6e fixed info.php return value 2018-11-17 12:51:20 +01:00
jenkins
b395e054e6 [Jenkins] Increment version 2018-11-17 03:25:54 +01:00
90f4270b74 expand on click 2018-11-17 03:24:10 +01:00
f68d75c226 swipe to delete 2018-11-17 03:09:18 +01:00
9cf5133469 get nonack on info-call 2018-11-17 02:09:43 +01:00
75929aad48 rename php files 2018-11-17 01:30:41 +01:00
dca149ec4e move api to sub-dir 2018-11-17 01:29:12 +01:00
75a7b97d24 audio playback on notification stream 2018-11-17 01:21:53 +01:00
eb62873fb6 Android O repeat sound 2018-11-17 00:52:44 +01:00
f8effe7d1e no LED with android O 2018-11-17 00:24:22 +01:00
87a3d34315 scroll to newest message 2018-11-17 00:07:41 +01:00
0f38ad6e5c default_quota=50 2018-11-16 23:53:47 +01:00
0ec6ab71b2 fixed layout wrap in settings 2018-11-16 23:51:46 +01:00
b42204c87d fixed NPE on message recieve if app is not active 2018-11-16 23:28:48 +01:00
53cb259ec1 stupid sound shit 2018-11-13 18:11:17 +01:00
0150cc9e8e force POST in send.php 2018-11-13 16:23:04 +01:00
20214473f1 html styling 2018-11-13 16:06:22 +01:00
jenkins
586ac07ef3 [Jenkins] Increment version 2018-11-12 18:27:08 +01:00
66f82f26d5 channel bullshit 2018-11-12 18:25:41 +01:00
6cb2aa00fb intermed 2018-11-12 17:23:19 +01:00
96a236803e simplify settings layout 2018-11-12 16:29:43 +01:00
9d24044eb3 center pro message 2018-11-12 16:11:31 +01:00
90248bcb54 confirm-dialog 2018-11-12 16:04:59 +01:00
a3b2a1b14f only one account per fcm_token (2) 2018-11-12 16:01:06 +01:00
b21b159b95 fix user_key reset in promode 2018-11-12 15:58:59 +01:00
93ec261dc0 ack messages on recieve 2018-11-12 15:51:06 +01:00
ae246e9219 favicon 2018-11-12 15:28:07 +01:00
3f18fdd35a only one account per fcm_token 2018-11-12 15:22:21 +01:00
faf5207478 fixed wrong sql key 2018-11-12 15:11:26 +01:00
71f003dd66 fix error in send.php 2018-11-12 15:07:48 +01:00
3f85ab514e prevent purchase re-use 2018-11-12 14:57:54 +01:00
9eb5a6b1b9 fix NPE 2018-11-12 14:57:44 +01:00
8e26cd6078 fixed order verify 2018-11-12 14:49:09 +01:00
36b9263730 fix json_decode problems 2018-11-12 14:24:11 +01:00
3d29fecaec gracefully handle user_not_found 2018-11-12 14:23:50 +01:00
jenkins
92ac05f1e3 [Jenkins] Increment version 2018-11-12 13:06:38 +01:00
cae6ff6271 implement usr_msg_id 2018-11-12 13:03:56 +01:00
2163ae4dbf index_more.php 2018-11-12 12:41:59 +01:00
083945852b in-app-purchase: pro-mode 2018-11-12 00:28:57 +01:00
jenkins
e69b1232d7 [Jenkins] Increment version 2018-11-11 19:38:22 +01:00
ac681065fa billing permission 2018-11-11 19:36:58 +01:00
90d8b46179 actually use settings 2018-10-22 16:29:37 +02:00
jenkins
280b5ca4aa [Jenkins] Increment version 2018-10-22 15:29:28 +02:00
54a171bd5e ads 2018-10-22 15:16:39 +02:00
c043367810 icon 2018-10-22 14:37:45 +02:00
5494df1c56 settings kinda fin 2018-10-22 14:24:35 +02:00
f9d04e38a0 ColorPicker 2018-10-22 14:04:50 +02:00
6dfd7e016b setting stuff 2018-10-22 13:53:21 +02:00
96a3cbc40a colors 2018-10-22 01:32:21 +02:00
7261795c99 UltimateMusicPicker 2018-10-22 01:23:25 +02:00
d6becd15c1 switch to androidx 2018-10-22 01:05:05 +02:00
0b22a18088 preferences (1) 2018-10-21 22:58:26 +02:00
39fcddfaf0 small stuff 2018-10-20 19:10:10 +02:00
jenkins
38eaa0c132 [Jenkins] Increment version 2018-10-20 18:59:26 +02:00
338 changed files with 66149 additions and 1462 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"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<Objective-C-extensions> <codeStyleSettings language="XML">
<file> <arrangement>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" /> <rules>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" /> <section>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" /> <rule>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" /> <match>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" /> <AND>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" /> <NAME>xmlns:android</NAME>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" /> <XML_ATTRIBUTE />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" /> <XML_NAMESPACE>^$</XML_NAMESPACE>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" /> </AND>
</file> </match>
<class> </rule>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" /> </section>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" /> <section>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" /> <rule>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" /> <match>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" /> <AND>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" /> <NAME>xmlns:.*</NAME>
</class> <XML_ATTRIBUTE />
<extensions> <XML_NAMESPACE>^$</XML_NAMESPACE>
<pair source="cpp" header="h" fileNamingConvention="NONE" /> </AND>
<pair source="c" header="h" fileNamingConvention="NONE" /> </match>
</extensions> <order>BY_NAME</order>
</Objective-C-extensions> </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> </code_scheme>
</component> </component>

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>

View File

@@ -1,7 +1,7 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 28 compileSdkVersion 30
def versionPropsFile = file('version.properties') def versionPropsFile = file('version.properties')
def vNumber def vNumber
@@ -16,7 +16,7 @@ android {
defaultConfig { defaultConfig {
applicationId "com.blackforestbytes.simplecloudnotifier" applicationId "com.blackforestbytes.simplecloudnotifier"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 28 targetSdkVersion 30
versionCode vNumber versionCode vNumber
versionName vName versionName vName
} }
@@ -35,28 +35,33 @@ android {
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:support-v4:28.0.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.android.support:cardview-v7:28.0.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.android.support:design:28.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'com.takisoft.fix:preference-v7:28.0.0.0' implementation 'com.google.android.material:material:1.2.1'
implementation 'com.takisoft.fix:preference-v7-extras:28.0.0.0' 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.google.firebase:firebase-core:16.0.4' implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.google.firebase:firebase-messaging:17.3.3'
implementation "android.arch.lifecycle:extensions:1.1.1"
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.github.kenglxn.QRGen:android:2.5.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' 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 lastTag = ['git', 'describe', "--abbrev=0", "--tags"].execute().text.trim()
def versionPropsFile = file('version.properties') def versionPropsFile = file('version.properties')
@@ -107,4 +112,5 @@ task updateVersion << {
versionPropsFile.newWriter().withCloseable { w -> versionProps.store(w, null) } versionPropsFile.newWriter().withCloseable { w -> versionProps.store(w, null) }
} }
}
} }

View File

@@ -1,31 +1,68 @@
<?xml version="1.0" encoding="utf-8"?> <?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"> 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 <application
android:name=".SCNApp"
android:allowBackup="false" android:allowBackup="false"
android:name="SCNApp" android:icon="@drawable/icon"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".view.MainActivity"> <activity android:name=".view.MainActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/icon" /> <provider
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/colorAccent" /> 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">
<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> <intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" /> <action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter> </intent-filter>
</service> </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> </application>
</manifest> </manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@@ -1,26 +1,28 @@
package com.blackforestbytes.simplecloudnotifier; package com.blackforestbytes.simplecloudnotifier;
import android.app.Application; import android.app.Application;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleObserver;
import android.arch.lifecycle.OnLifecycleEvent;
import android.arch.lifecycle.ProcessLifecycleOwner;
import android.content.Context; import android.content.Context;
import android.widget.Toast; import android.widget.Toast;
import com.blackforestbytes.simplecloudnotifier.model.CMessageList; import com.android.billingclient.api.BillingClient;
import com.blackforestbytes.simplecloudnotifier.service.NotificationService; import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
import com.blackforestbytes.simplecloudnotifier.view.AccountFragment; import com.blackforestbytes.simplecloudnotifier.view.AccountFragment;
import com.blackforestbytes.simplecloudnotifier.view.MainActivity; import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
import com.blackforestbytes.simplecloudnotifier.view.TabAdapter; import com.blackforestbytes.simplecloudnotifier.view.TabAdapter;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.lifecycle.ProcessLifecycleOwner;
public class SCNApp extends Application implements LifecycleObserver public class SCNApp extends Application implements LifecycleObserver
{ {
private static SCNApp instance; 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"); public static final boolean DEBUG = BuildConfig.DEBUG || !BuildConfig.VERSION_NAME.endsWith(".0");
public static final boolean RELEASE = !DEBUG; public static final boolean RELEASE = !DEBUG;
@@ -37,6 +39,11 @@ public class SCNApp extends Application implements LifecycleObserver
return instance; return instance;
} }
public static MainActivity getMainActivity()
{
return mainActivity.get();
}
public static boolean isBackground() public static boolean isBackground()
{ {
return isBackground; return isBackground;
@@ -92,3 +99,6 @@ public class SCNApp extends Application implements LifecycleObserver
isBackground = false; isBackground = false;
} }
} }
//TODO: Config for collapsed line count
//TODO: Sometimes ads but promode

View File

@@ -0,0 +1,23 @@
package com.blackforestbytes.simplecloudnotifier.lib.android;
import android.util.Log;
public final class ThreadUtils
{
public static void safeSleep(int millisMin, int millisMax)
{
safeSleep(millisMin + (int)(Math.random()*(millisMax-millisMin)));
}
public static void safeSleep(int millis)
{
try
{
Thread.sleep(millis);
}
catch (InterruptedException e)
{
Log.d("ThreadUtils", e.toString());
}
}
}

View File

@@ -0,0 +1,43 @@
package com.blackforestbytes.simplecloudnotifier.lib.collections;
import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func1to1;
import java.util.*;
public final class CollectionHelper
{
public static <T, C> List<T> unique(List<T> input, Func1to1<T, C> mapping)
{
List<T> output = new ArrayList<>(input.size());
HashSet<C> seen = new HashSet<>();
for (T v : input) if (seen.add(mapping.invoke(v))) output.add(v);
return output;
}
public static <T> List<T> sort(List<T> input, Comparator<T> comparator)
{
List<T> output = new ArrayList<>(input);
Collections.sort(output, comparator);
return output;
}
public static <T> void sort_inplace(List<T> input, Comparator<T> comparator)
{
Collections.sort(input, comparator);
}
public static <T, U extends Comparable<U>> List<T> sort(List<T> input, Func1to1<T, U> mapper)
{
return sort(input, mapper, 1);
}
public static <T, U extends Comparable<U>> List<T> sort(List<T> input, Func1to1<T, U> mapper, int sortMod)
{
List<T> output = new ArrayList<>(input);
Collections.sort(output, (o1, o2) -> sortMod * mapper.invoke(o1).compareTo(mapper.invoke(o2)));
return output;
}
}

View File

@@ -0,0 +1,14 @@
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
public class IntRange
{
private int Start;
public int Start() { return Start; }
private int End;
public int End() { return End; }
public IntRange(int s, int e) { Start = s; End = e; }
private IntRange() { }
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
public class Tuple2<T1, T2>
{
public final T1 Item1;
public final T2 Item2;
public Tuple2(T1 i1, T2 i2)
{
Item1 = i1;
Item2 = i2;
}
}

View File

@@ -0,0 +1,15 @@
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
public class Tuple3<T1, T2, T3>
{
public final T1 Item1;
public final T2 Item2;
public final T3 Item3;
public Tuple3(T1 i1, T2 i2, T3 i3)
{
Item1 = i1;
Item2 = i2;
Item3 = i3;
}
}

View File

@@ -0,0 +1,17 @@
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
public class Tuple4<T1, T2, T3, T4>
{
public final T1 Item1;
public final T2 Item2;
public final T3 Item3;
public final T4 Item4;
public Tuple4(T1 i1, T2 i2, T3 i3, T4 i4)
{
Item1 = i1;
Item2 = i2;
Item3 = i3;
Item4 = i4;
}
}

View File

@@ -0,0 +1,19 @@
package com.blackforestbytes.simplecloudnotifier.lib.datatypes;
public class Tuple5<T1, T2, T3, T4, T5>
{
public final T1 Item1;
public final T2 Item2;
public final T3 Item3;
public final T4 Item4;
public final T5 Item5;
public Tuple5(T1 i1, T2 i2, T3 i3, T4 i4, T5 i5)
{
Item1 = i1;
Item2 = i2;
Item3 = i3;
Item4 = i4;
Item5 = i5;
}
}

View File

@@ -0,0 +1,29 @@
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
import android.widget.SeekBar;
public final class FI
{
private FI() throws InstantiationException { throw new InstantiationException(); }
public static SeekBar.OnSeekBarChangeListener SeekBarChanged(Func3to0<SeekBar, Integer, Boolean> action)
{
return new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
action.invoke(seekBar, progress, fromUser);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
};
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
package com.blackforestbytes.simplecloudnotifier.lib.lambda;
@FunctionalInterface
public interface Func5to0<TInput1, TInput2, TInput3, TInput4, TInput5> {
void invoke(TInput1 value1, TInput2 value2, TInput3 value3, TInput4 value4, TInput5 value5);
}

View File

@@ -0,0 +1,231 @@
package com.blackforestbytes.simplecloudnotifier.lib.string;
// from MonoSAMFramework.Portable.DebugTools.CompactJsonFormatter
public class CompactJsonFormatter
{
private static final String INDENT_STRING = " ";
public static String formatJSON(String str, int maxIndent)
{
int indent = 0;
boolean quoted = false;
StringBuilder sb = new StringBuilder();
char last = ' ';
for (int i = 0; i < str.length(); i++)
{
char ch = str.charAt(i);
switch (ch)
{
case '\r':
case '\n':
break;
case '{':
case '[':
sb.append(ch);
last = ch;
if (!quoted)
{
indent++;
if (indent >= maxIndent) break;
sb.append("\n");
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
last = ' ';
}
break;
case '}':
case ']':
if (!quoted)
{
indent--;
if (indent + 1 >= maxIndent) { sb.append(ch); break; }
sb.append("\n");
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
}
sb.append(ch);
last = ch;
break;
case '"':
sb.append(ch);
last = ch;
boolean escaped = false;
int index = i;
while (index > 0 && str.charAt(--index) == '\\')
escaped = !escaped;
if (!escaped)
quoted = !quoted;
break;
case ',':
sb.append(ch);
last = ch;
if (!quoted)
{
if (indent >= maxIndent) { sb.append(' '); last = ' '; break; }
sb.append("\n");
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
}
break;
case ':':
sb.append(ch);
last = ch;
if (!quoted) { sb.append(" "); last = ' '; }
break;
case ' ':
case '\t':
if (quoted)
{
sb.append(ch);
last = ch;
}
else if (last != ' ')
{
sb.append(' ');
last = ' ';
}
break;
default:
sb.append(ch);
last = ch;
break;
}
}
return sb.toString();
}
public static String compressJson(String str, int compressionLevel)
{
int indent = 0;
boolean quoted = false;
StringBuilder sb = new StringBuilder();
char last = ' ';
int compress = 0;
for (int i = 0; i < str.length(); i++)
{
char ch = str.charAt(i);
switch (ch)
{
case '\r':
case '\n':
break;
case '{':
case '[':
sb.append(ch);
last = ch;
if (!quoted)
{
if (compress == 0 && getJsonDepth(str, i) <= compressionLevel)
compress = 1;
else if (compress > 0)
compress++;
indent++;
if (compress > 0) break;
sb.append("\n");
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
last = ' ';
}
break;
case '}':
case ']':
if (!quoted)
{
indent--;
if (compress > 0) { compress--; sb.append(ch); break; }
compress--;
sb.append("\n");
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
}
sb.append(ch);
last = ch;
break;
case '"':
sb.append(ch);
last = ch;
boolean escaped = false;
int index = i;
while (index > 0 && str.charAt(--index) == '\\')
escaped = !escaped;
if (!escaped)
quoted = !quoted;
break;
case ',':
sb.append(ch);
last = ch;
if (!quoted)
{
if (compress > 0) { sb.append(' '); last = ' '; break; }
sb.append("\n");
for (int ix = 0; ix < indent; ix++) sb.append(INDENT_STRING);
}
break;
case ':':
sb.append(ch);
last = ch;
if (!quoted) { sb.append(" "); last = ' '; }
break;
case ' ':
case '\t':
if (quoted)
{
sb.append(ch);
last = ch;
}
else if (last != ' ')
{
sb.append(' ');
last = ' ';
}
break;
default:
sb.append(ch);
last = ch;
break;
}
}
return sb.toString();
}
public static int getJsonDepth(String str, int i)
{
int maxindent = 0;
int indent = 0;
boolean quoted = false;
for (; i < str.length(); i++)
{
char ch = str.charAt(i);
switch (ch)
{
case '{':
case '[':
if (!quoted)
{
indent++;
maxindent = Math.max(indent, maxindent);
}
break;
case '}':
case ']':
if (!quoted)
{
indent--;
if (indent <= 0) return maxindent;
}
break;
case '"':
boolean escaped = false;
int index = i;
while (index > 0 && str.charAt(--index) == '\\')
escaped = !escaped;
if (!escaped)
quoted = !quoted;
break;
default:
break;
}
}
return maxindent;
}
}

View File

@@ -0,0 +1,98 @@
package com.blackforestbytes.simplecloudnotifier.lib.string;
import android.content.Context;
import android.util.Log;
import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func1to1;
import java.text.MessageFormat;
import java.util.List;
public class Str
{
public final static String Empty = "";
public static String format(String fmt, Object... data)
{
return MessageFormat.format(fmt, data);
}
public static String rformat(int fmtResId, Object... data)
{
Context inst = SCNApp.getContext();
if (inst == null)
{
Log.e("StringFormat", "rformat::NoInstance --> inst==null for" + fmtResId);
return "?ERR?";
}
return MessageFormat.format(inst.getResources().getString(fmtResId), data);
}
public static String firstLine(String content)
{
int idx = content.indexOf('\n');
if (idx == -1) return content;
if (idx == 0) return Str.Empty;
if (content.charAt(idx-1) == '\r') return content.substring(0, idx-1);
return content.substring(0, idx);
}
public static boolean isNullOrWhitespace(String str)
{
return str == null || str.length() == 0 || str.trim().length() == 0;
}
public static boolean isNullOrEmpty(String str)
{
return str == null || str.length() == 0;
}
public static boolean equals(String a, String b)
{
if (a == null) return (b == null);
return a.equals(b);
}
public static String join(String sep, List<String> list)
{
StringBuilder b = new StringBuilder();
boolean first = true;
for (String v : list)
{
if (!first) b.append(sep);
b.append(v);
first = false;
}
return b.toString();
}
public static <T> String join(String sep, List<T> list, Func1to1<T, String> map)
{
StringBuilder b = new StringBuilder();
boolean first = true;
for (T v : list)
{
if (!first) b.append(sep);
b.append(map.invoke(v));
first = false;
}
return b.toString();
}
public static Integer tryParseToInt(String s)
{
try
{
return Integer.parseInt(s);
}
catch (Exception e)
{
return null;
}
}
}

View File

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

View File

@@ -3,15 +3,23 @@ package com.blackforestbytes.simplecloudnotifier.model;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; 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.view.MessageAdapter;
import com.blackforestbytes.simplecloudnotifier.SCNApp; import com.blackforestbytes.simplecloudnotifier.SCNApp;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class CMessageList public class CMessageList
{ {
private final Object msg_lock = new Object();
public ArrayList<CMessage> Messages; public ArrayList<CMessage> Messages;
public Set<String> AllAcks;
private ArrayList<WeakReference<MessageAdapter>> _listener = new ArrayList<>(); private ArrayList<WeakReference<MessageAdapter>> _listener = new ArrayList<>();
@@ -27,8 +35,16 @@ public class CMessageList
} }
private CMessageList() private CMessageList()
{
reloadPrefs();
}
public void reloadPrefs()
{
synchronized (msg_lock)
{ {
Messages = new ArrayList<>(); Messages = new ArrayList<>();
AllAcks = new HashSet<>();
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE); SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
int count = sharedPref.getInt("message_count", 0); int count = sharedPref.getInt("message_count", 0);
@@ -38,44 +54,75 @@ public class CMessageList
String title = sharedPref.getString("message["+i+"].title", ""); String title = sharedPref.getString("message["+i+"].title", "");
String content = sharedPref.getString("message["+i+"].content", ""); String content = sharedPref.getString("message["+i+"].content", "");
PriorityEnum prio = PriorityEnum.parseAPI(sharedPref.getInt("message["+i+"].priority", 1)); 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(() -> boolean run = SCNApp.runOnUiThread(() ->
{ {
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE); SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
int count = sharedPref.getInt("message_count", 0); int count = sharedPref.getInt("message_count", 0);
synchronized (msg_lock)
{
Messages.add(msg);
AllAcks.add(Long.toHexString(msg.SCN_ID));
while (Messages.size()>SCNSettings.inst().LocalCacheSize) Messages.remove(0);
}
if (Messages.size()>1 && Messages.get(Messages.size()-2).Timestamp < msg.Timestamp)
{
// quick save
SharedPreferences.Editor e = sharedPref.edit(); SharedPreferences.Editor e = sharedPref.edit();
Messages.add(msg); e.putInt( "message_count", count+1);
e.putInt("message_count", count+1); e.putLong( "message["+count+"].timestamp", time);
e.putLong("message["+count+"].timestamp", time);
e.putString("message["+count+"].title", title); e.putString("message["+count+"].title", title);
e.putString("message["+count+"].content", content); e.putString("message["+count+"].content", content);
e.putInt("message["+count+"].priority", pe.ID); e.putInt( "message["+count+"].priority", pe.ID);
e.putLong( "message["+count+"].scnid", scnid);
e.putStringSet("acks", AllAcks);
e.apply(); e.apply();
}
else
{
// full save
fullSave(); // does sort in here
}
for (WeakReference<MessageAdapter> ref : _listener) for (WeakReference<MessageAdapter> ref : _listener)
{ {
MessageAdapter a = ref.get(); MessageAdapter a = ref.get();
if (a == null) continue; if (a == null) continue;
a.customNotifyItemInserted(count); a.customNotifyDataSetChanged();
a.scrollToTop();
} }
CleanUpListener(); CleanUpListener();
}); });
if (!run) if (!run)
{ {
Messages.add(new CMessage(time, title, content, pe)); synchronized (msg_lock)
fullSave(); {
Messages.add(new CMessage(scnid, time, title, content, pe));
AllAcks.add(Long.toHexString(msg.SCN_ID));
}
fullSave(); // does sort in here
} }
return msg; return msg;
@@ -97,6 +144,10 @@ public class CMessageList
public void fullSave() 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 sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
SharedPreferences.Editor e = sharedPref.edit(); SharedPreferences.Editor e = sharedPref.edit();
@@ -106,25 +157,40 @@ public class CMessageList
for (int i = 0; i < Messages.size(); i++) for (int i = 0; i < Messages.size(); i++)
{ {
e.putLong("message["+i+"].timestamp", Messages.get(i).Timestamp); e.putLong( "message["+i+"].timestamp", Messages.get(i).Timestamp);
e.putString("message["+i+"].title", Messages.get(i).Title); e.putString("message["+i+"].title", Messages.get(i).Title);
e.putString("message["+i+"].content", Messages.get(i).Content); e.putString("message["+i+"].content", Messages.get(i).Content);
e.putInt("message["+i+"].priority", Messages.get(i).Priority.ID); 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(); e.apply();
} }
}
public CMessage tryGet(int pos) public CMessage tryGet(int pos)
{
synchronized (msg_lock)
{ {
if (pos < 0 || pos >= Messages.size()) return null; if (pos < 0 || pos >= Messages.size()) return null;
return Messages.get(pos); return Messages.get(pos);
} }
}
public CMessage tryGetFromBack(int pos)
{
return tryGet(Messages.size() - pos - 1);
}
public int size() public int size()
{
synchronized (msg_lock)
{ {
return Messages.size(); return Messages.size();
} }
}
public void register(MessageAdapter adp) public void register(MessageAdapter adp)
{ {
@@ -139,4 +205,33 @@ public class CMessageList
if (_listener.get(i).get() == null) _listener.remove(i); if (_listener.get(i).get() == null) _listener.remove(i);
} }
} }
public boolean isAck(long id)
{
synchronized (msg_lock)
{
return AllAcks.contains(Long.toHexString(id));
}
}
public CMessage removeFromBack(int pos)
{
CMessage r;
synchronized (msg_lock)
{
int index = Messages.size() - pos - 1;
r = Messages.remove(index);
}
fullSave(); // does sort in here
return r;
}
public void insert(int index, CMessage item)
{
synchronized (msg_lock)
{
Messages.add(index, item);
}
fullSave(); // does sort in here
}
} }

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

@@ -0,0 +1,34 @@
package com.blackforestbytes.simplecloudnotifier.model;
import android.graphics.Color;
import android.media.RingtoneManager;
import android.net.Uri;
public class NotificationSettings
{
public boolean EnableSound;
public String SoundName;
public String SoundSource;
public boolean RepeatSound;
public boolean ForceVolume;
public int ForceVolumeValue;
public boolean EnableLED;
public int LEDColor;
public boolean EnableVibration;
public NotificationSettings(PriorityEnum p)
{
EnableSound = (p == PriorityEnum.HIGH);
SoundName = "Default";
SoundSource = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION).toString();
RepeatSound = false;
EnableLED = (p == PriorityEnum.HIGH) || (p == PriorityEnum.NORMAL);
LEDColor = Color.BLUE;
EnableVibration = (p == PriorityEnum.HIGH) || (p == PriorityEnum.NORMAL);
ForceVolume = false;
ForceVolumeValue = 50;
}
}

View File

@@ -0,0 +1,69 @@
package com.blackforestbytes.simplecloudnotifier.model;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.collections.CollectionHelper;
import java.util.ArrayList;
import java.util.List;
public class QueryLog
{
private final static int MAX_HISTORY_SIZE = 192;
private static QueryLog _instance;
public static QueryLog inst() { if (_instance == null) synchronized (QueryLog.class) { if (_instance == null) _instance = new QueryLog(); } return _instance; }
private QueryLog(){ reloadPrefs(); }
private final List<SingleQuery> history = new ArrayList<>();
public synchronized void add(SingleQuery r)
{
history.add(r);
while (history.size() > MAX_HISTORY_SIZE) history.remove(0);
save();
}
public synchronized List<SingleQuery> get()
{
List<SingleQuery> r = new ArrayList<>(history);
CollectionHelper.sort_inplace(r, (o1, o2) -> (-1) * o1.Timestamp.compareTo(o2.Timestamp));
return r;
}
public synchronized void save()
{
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("QueryLog", Context.MODE_PRIVATE);
SharedPreferences.Editor e = sharedPref.edit();
e.clear();
e.putInt("history_count", history.size());
for (int i = 0; i < history.size(); i++) history.get(i).save(e, "message["+(i+1000)+"]");
e.apply();
}
public synchronized void reloadPrefs()
{
try
{
Context c = SCNApp.getContext();
SharedPreferences sharedPref = c.getSharedPreferences("QueryLog", Context.MODE_PRIVATE);
int count = sharedPref.getInt("history_count", 0);
for (int i=0; i < count; i++) history.add(SingleQuery.load(sharedPref, "message["+(i+1000)+"]"));
CollectionHelper.sort_inplace(history, (o1, o2) -> (-1) * o1.Timestamp.compareTo(o2.Timestamp));
}
catch (Exception e)
{
Log.e("SC:QL:Load", e.toString());
}
}
}

View File

@@ -7,20 +7,34 @@ import android.util.Log;
import android.view.View; import android.view.View;
import com.blackforestbytes.simplecloudnotifier.SCNApp; import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.google.firebase.iid.FirebaseInstanceId; import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple3;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.service.IABService;
import com.google.firebase.installations.FirebaseInstallations;
public class SCNSettings public class SCNSettings
{ {
private final static Object _lock = new Object(); private final static Object _lock = new Object();
private static SCNSettings _inst = null; private static volatile SCNSettings _inst = null;
public static SCNSettings inst() public static SCNSettings inst()
{
SCNSettings local = _inst;
if (local == null)
{ {
synchronized (_lock) synchronized (_lock)
{ {
if (_inst != null) return _inst; local = _inst;
return _inst = new SCNSettings(); if (local == null) _inst = local = new SCNSettings();
} }
} }
return local;
}
// ------------------------------------------------------------
public final static Integer[] CHOOSABLE_CACHE_SIZES = new Integer[]{20, 50, 100, 200, 500, 1000, 2000, 5000};
// ------------------------------------------------------------
public int quota_curr; public int quota_curr;
public int quota_max; public int quota_max;
@@ -30,16 +44,76 @@ public class SCNSettings
public String fcm_token_local; public String fcm_token_local;
public String fcm_token_server; public String fcm_token_server;
public String promode_token;
public boolean promode_local;
public boolean promode_server;
// ------------------------------------------------------------
public boolean Enabled = true;
public int LocalCacheSize = 500;
public boolean EnableDeleteSwipe = false;
public int PreviewLineCount = 6;
public final NotificationSettings PriorityLow = new NotificationSettings(PriorityEnum.LOW);
public final NotificationSettings PriorityNorm = new NotificationSettings(PriorityEnum.NORMAL);
public final NotificationSettings PriorityHigh = new NotificationSettings(PriorityEnum.HIGH);
// ------------------------------------------------------------
public SCNSettings() public SCNSettings()
{
reloadPrefs();
}
public void reloadPrefs()
{ {
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("Config", Context.MODE_PRIVATE); SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("Config", Context.MODE_PRIVATE);
quota_curr = sharedPref.getInt("quota_curr", 0); quota_curr = sharedPref.getInt( "quota_curr", 0);
quota_max = sharedPref.getInt("quota_max", 0); quota_max = sharedPref.getInt( "quota_max", 0);
user_id = sharedPref.getInt("user_id", -1); user_id = sharedPref.getInt( "user_id", -1);
user_key = sharedPref.getString("user_key", ""); user_key = sharedPref.getString("user_key", "");
fcm_token_local = sharedPref.getString("fcm_token_local", ""); fcm_token_local = sharedPref.getString("fcm_token_local", "");
fcm_token_server = sharedPref.getString("fcm_token_server", ""); fcm_token_server = sharedPref.getString("fcm_token_server", "");
promode_local = sharedPref.getBoolean("promode_local", false);
promode_server = sharedPref.getBoolean("promode_server", false);
promode_token = sharedPref.getString("promode_token", "");
Enabled = sharedPref.getBoolean("app_enabled", Enabled);
LocalCacheSize = sharedPref.getInt("local_cache_size", LocalCacheSize);
EnableDeleteSwipe = sharedPref.getBoolean("do_del_swipe", EnableDeleteSwipe);
PreviewLineCount = sharedPref.getInt("preview_line_count", PreviewLineCount);
PriorityLow.EnableLED = sharedPref.getBoolean("priority_low:enabled_led", PriorityLow.EnableLED);
PriorityLow.EnableSound = sharedPref.getBoolean("priority_low:enabled_sound", PriorityLow.EnableSound);
PriorityLow.EnableVibration = sharedPref.getBoolean("priority_low:enabled_vibration", PriorityLow.EnableVibration);
PriorityLow.RepeatSound = sharedPref.getBoolean("priority_low:repeat_sound", PriorityLow.RepeatSound);
PriorityLow.SoundName = sharedPref.getString( "priority_low:sound_name", PriorityLow.SoundName);
PriorityLow.SoundSource = sharedPref.getString( "priority_low:sound_source", PriorityLow.SoundSource);
PriorityLow.LEDColor = sharedPref.getInt( "priority_low:led_color", PriorityLow.LEDColor);
PriorityLow.ForceVolume = sharedPref.getBoolean("priority_low:force_volume", PriorityLow.ForceVolume);
PriorityLow.ForceVolumeValue = sharedPref.getInt( "priority_low:force_volume_value", PriorityLow.ForceVolumeValue);
PriorityNorm.EnableLED = sharedPref.getBoolean("priority_norm:enabled_led", PriorityNorm.EnableLED);
PriorityNorm.EnableSound = sharedPref.getBoolean("priority_norm:enabled_sound", PriorityNorm.EnableSound);
PriorityNorm.EnableVibration = sharedPref.getBoolean("priority_norm:enabled_vibration", PriorityNorm.EnableVibration);
PriorityNorm.RepeatSound = sharedPref.getBoolean("priority_norm:repeat_sound", PriorityNorm.RepeatSound);
PriorityNorm.SoundName = sharedPref.getString( "priority_norm:sound_name", PriorityNorm.SoundName);
PriorityNorm.SoundSource = sharedPref.getString( "priority_norm:sound_source", PriorityNorm.SoundSource);
PriorityNorm.LEDColor = sharedPref.getInt( "priority_norm:led_color", PriorityNorm.LEDColor);
PriorityNorm.ForceVolume = sharedPref.getBoolean("priority_norm:force_volume", PriorityNorm.ForceVolume);
PriorityNorm.ForceVolumeValue = sharedPref.getInt( "priority_norm:force_volume_value", PriorityNorm.ForceVolumeValue);
PriorityHigh.EnableLED = sharedPref.getBoolean("priority_high:enabled_led", PriorityHigh.EnableLED);
PriorityHigh.EnableSound = sharedPref.getBoolean("priority_high:enabled_sound", PriorityHigh.EnableSound);
PriorityHigh.EnableVibration = sharedPref.getBoolean("priority_high:enabled_vibration", PriorityHigh.EnableVibration);
PriorityHigh.RepeatSound = sharedPref.getBoolean("priority_high:repeat_sound", PriorityHigh.RepeatSound);
PriorityHigh.SoundName = sharedPref.getString( "priority_high:sound_name", PriorityHigh.SoundName);
PriorityHigh.SoundSource = sharedPref.getString( "priority_high:sound_source", PriorityHigh.SoundSource);
PriorityHigh.LEDColor = sharedPref.getInt( "priority_high:led_color", PriorityHigh.LEDColor);
PriorityHigh.ForceVolume = sharedPref.getBoolean("priority_high:force_volume", PriorityHigh.ForceVolume);
PriorityHigh.ForceVolumeValue = sharedPref.getInt( "priority_high:force_volume_value", PriorityHigh.ForceVolumeValue);
} }
public void save() public void save()
@@ -47,12 +121,50 @@ public class SCNSettings
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("Config", Context.MODE_PRIVATE); SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("Config", Context.MODE_PRIVATE);
SharedPreferences.Editor e = sharedPref.edit(); SharedPreferences.Editor e = sharedPref.edit();
e.putInt("quota_curr", quota_curr); e.putInt( "quota_curr", quota_curr);
e.putInt("quota_max", quota_max); e.putInt( "quota_max", quota_max);
e.putInt("user_id", user_id); e.putInt( "user_id", user_id);
e.putString("user_key", user_key); e.putString( "user_key", user_key);
e.putString("fcm_token_local", fcm_token_local); e.putString( "fcm_token_local", fcm_token_local);
e.putString("fcm_token_server", fcm_token_server); e.putString( "fcm_token_server", fcm_token_server);
e.putBoolean("promode_local", promode_local);
e.putBoolean("promode_server", promode_server);
e.putString( "promode_token", promode_token);
e.putBoolean("app_enabled", Enabled);
e.putInt( "local_cache_size", LocalCacheSize);
e.putBoolean("do_del_swipe", EnableDeleteSwipe);
e.putInt( "preview_line_count", PreviewLineCount);
e.putBoolean("priority_low:enabled_led", PriorityLow.EnableLED);
e.putBoolean("priority_low:enabled_sound", PriorityLow.EnableSound);
e.putBoolean("priority_low:enabled_vibration", PriorityLow.EnableVibration);
e.putBoolean("priority_low:repeat_sound", PriorityLow.RepeatSound);
e.putString( "priority_low:sound_name", PriorityLow.SoundName);
e.putString( "priority_low:sound_source", PriorityLow.SoundSource);
e.putInt( "priority_low:led_color", PriorityLow.LEDColor);
e.putBoolean("priority_low:force_volume", PriorityLow.ForceVolume);
e.putInt( "priority_low:force_volume_value", PriorityLow.ForceVolumeValue);
e.putBoolean("priority_norm:enabled_led", PriorityNorm.EnableLED);
e.putBoolean("priority_norm:enabled_sound", PriorityNorm.EnableSound);
e.putBoolean("priority_norm:enabled_vibration", PriorityNorm.EnableVibration);
e.putBoolean("priority_norm:repeat_sound", PriorityNorm.RepeatSound);
e.putString( "priority_norm:sound_name", PriorityNorm.SoundName);
e.putString( "priority_norm:sound_source", PriorityNorm.SoundSource);
e.putInt( "priority_norm:led_color", PriorityNorm.LEDColor);
e.putBoolean("priority_norm:force_volume", PriorityNorm.ForceVolume);
e.putInt( "priority_norm:force_volume_value", PriorityNorm.ForceVolumeValue);
e.putBoolean("priority_high:enabled_led", PriorityHigh.EnableLED);
e.putBoolean("priority_high:enabled_sound", PriorityHigh.EnableSound);
e.putBoolean("priority_high:enabled_vibration", PriorityHigh.EnableVibration);
e.putBoolean("priority_high:repeat_sound", PriorityHigh.RepeatSound);
e.putString( "priority_high:sound_name", PriorityHigh.SoundName);
e.putString( "priority_high:sound_source", PriorityHigh.SoundSource);
e.putInt( "priority_high:led_color", PriorityHigh.LEDColor);
e.putBoolean("priority_high:force_volume", PriorityHigh.ForceVolume);
e.putInt( "priority_high:force_volume_value", PriorityHigh.ForceVolumeValue);
e.apply(); e.apply();
} }
@@ -62,65 +174,100 @@ public class SCNSettings
return user_id>=0 && user_key != null && !user_key.isEmpty(); 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"; String base = longurl ? ServerCommunication.PAGE_URL_LONG : ServerCommunication.PAGE_URL_SHORT;
return ServerCommunication.BASE_URL + "index.php?preset_user_id="+user_id+"&preset_user_key="+user_key;
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()) if (isConnected())
{ {
fcm_token_local = token; fcm_token_local = token;
save(); 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 else
{ {
fcm_token_local = token; fcm_token_local = token;
save(); save();
ServerCommunication.register(fcm_token_local, loader); ServerCommunication.register(fcm_token_local, loader, promode_local, promode_token);
updateProState(loader);
} }
} }
public void work(Activity a) // called at app start
public void work(Activity a, boolean force)
{ {
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult -> FirebaseInstallations.getInstance().getId().addOnSuccessListener(a, newToken ->
{ {
String newToken = instanceIdResult.getToken(); Log.d("FB::GetInstanceId", newToken);
Log.e("FB::GetInstanceId", newToken); SCNSettings.inst().setServerToken(newToken, null, force);
SCNSettings.inst().setServerToken(newToken, null);
}).addOnCompleteListener(r -> }).addOnCompleteListener(r ->
{ {
if (isConnected()) ServerCommunication.info(user_id, user_key, null); if (isConnected()) ServerCommunication.info(user_id, user_key, null);
}); });
updateProState(null);
} }
// reset account key
public void reset(View loader) public void reset(View loader)
{ {
if (!isConnected()) return; if (!isConnected()) return;
ServerCommunication.update(user_id, user_key, loader); ServerCommunication.resetSecret(user_id, user_key, loader);
} }
// refresh account data
public void refresh(View loader, Activity a) public void refresh(View loader, Activity a)
{ {
if (isConnected()) if (isConnected())
{ {
ServerCommunication.info(user_id, user_key, loader); ServerCommunication.info(user_id, user_key, loader);
if (promode_server != promode_local) updateProState(loader);
if (!Str.equals(fcm_token_local, fcm_token_server)) work(a, false);
} }
else else
{ {
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult -> // get token then register
FirebaseInstallations.getInstance().getId().addOnSuccessListener(a, newToken ->
{ {
String newToken = instanceIdResult.getToken(); Log.d("FB::GetInstanceId", newToken);
Log.e("FB::GetInstanceId", newToken); SCNSettings.inst().setServerToken(newToken, loader, false); // does register in here
SCNSettings.inst().setServerToken(newToken, loader);
}).addOnCompleteListener(r -> }).addOnCompleteListener(r ->
{ {
if (isConnected()) ServerCommunication.info(user_id, user_key, null); if (isConnected()) ServerCommunication.info(user_id, user_key, null); // info again for safety
}); });
} }
} }
public void updateProState(View loader)
{
Tuple3<Boolean, Boolean, String> state = IABService.inst().getPurchaseCachedExtended(IABService.IAB_PRO_MODE);
if (!state.Item2) return; // not initialized
boolean promode_real = state.Item1;
if (promode_real != promode_local || promode_real != promode_server)
{
promode_local = promode_real;
promode_token = promode_real ? state.Item3 : "";
save();
updateProStateOnServer(loader);
}
}
public void updateProStateOnServer(View loader)
{
if (!isConnected()) return;
ServerCommunication.upgrade(user_id, user_key, loader, promode_local, promode_token);
}
} }

View File

@@ -4,11 +4,18 @@ import android.util.Log;
import android.view.View; import android.view.View;
import com.blackforestbytes.simplecloudnotifier.SCNApp; 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.JSONObject;
import org.json.JSONTokener; import org.json.JSONTokener;
import java.io.IOException; import java.io.IOException;
import java.net.URLEncoder;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.Callback; import okhttp3.Callback;
@@ -19,18 +26,20 @@ import okhttp3.ResponseBody;
public class ServerCommunication public class ServerCommunication
{ {
public static final String BASE_URL = "https://scn.blackforestbytes.com/"; public static final String PAGE_URL_LONG = "https://simplecloudnotifier.blackforestbytes.com/";
public static final String PAGE_URL_SHORT = "https://scn.blackforestbytes.com/";
public static final String BASE_URL = "https://scn.blackforestbytes.com/api/";
private static final OkHttpClient client = new OkHttpClient(); private static final OkHttpClient client = new OkHttpClient();
private ServerCommunication(){ throw new Error("no."); } private ServerCommunication(){ throw new Error("no."); }
public static void register(String token, View loader) public static void register(String token, View loader, boolean pro, String pro_token)
{ {
try try
{ {
Request request = new Request.Builder() Request request = new Request.Builder()
.url(BASE_URL + "register.php?fcm_token="+token) .url(BASE_URL + "register.php?fcm_token=" + token + "&pro=" + pro + "&pro_token=" + URLEncoder.encode(pro_token, "utf-8"))
.build(); .build();
client.newCall(request).enqueue(new Callback() client.newCall(request).enqueue(new Callback()
@@ -38,43 +47,46 @@ public class ServerCommunication
@Override @Override
public void onFailure(Call call, IOException e) public void onFailure(Call call, IOException e)
{ {
e.printStackTrace(); handleError("register", call, null, Str.Empty, true, e);
SCNApp.showToast("Communication with server failed", 4000);
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); }); SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
} }
@Override @Override
public void onResponse(Call call, Response response) public void onResponse(Call call, Response response)
{ {
String r = Str.Empty;
try (ResponseBody responseBody = response.body()) try (ResponseBody responseBody = response.body())
{ {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No response"); if (responseBody == null) throw new IOException("No response");
String r = responseBody.string(); r = responseBody.string();
Log.d("Server::Response", r); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); 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; return;
} }
SCNSettings.inst().user_id = json.getInt("user_id"); SCNSettings.inst().user_id = json_int(json, "user_id");
SCNSettings.inst().user_key = json.getString("user_key"); SCNSettings.inst().user_key = json_str(json, "user_key");
SCNSettings.inst().fcm_token_server = token; SCNSettings.inst().fcm_token_server = token;
SCNSettings.inst().quota_curr = json.getInt("quota"); SCNSettings.inst().quota_curr = json_int(json, "quota");
SCNSettings.inst().quota_max = json.getInt("quota_max"); SCNSettings.inst().quota_max = json_int(json, "quota_max");
SCNSettings.inst().promode_server = json_bool(json, "is_pro");
SCNSettings.inst().save(); SCNSettings.inst().save();
SCNApp.refreshAccountTab(); SCNApp.refreshAccountTab();
handleSuccess("register", call, response, r);
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace(); handleError("register", call, response, r, false, e);
SCNApp.showToast("Communication with server failed", 4000);
} }
finally finally
{ {
@@ -85,12 +97,11 @@ public class ServerCommunication
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace(); handleError("register", null, null, Str.Empty, false, e);
SCNApp.showToast("Communication with server failed", 4000);
} }
} }
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 try
{ {
@@ -103,42 +114,46 @@ public class ServerCommunication
@Override @Override
public void onFailure(Call call, IOException e) public void onFailure(Call call, IOException e)
{ {
e.printStackTrace(); handleError("update<1>", call, null, Str.Empty, true, e);
SCNApp.showToast("Communication with server failed", 4000);
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); }); SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
} }
@Override @Override
public void onResponse(Call call, Response response) public void onResponse(Call call, Response response)
{ {
String r = Str.Empty;
try (ResponseBody responseBody = response.body()) try (ResponseBody responseBody = response.body())
{ {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No response"); if (responseBody == null) throw new IOException("No response");
String r = responseBody.string(); r = responseBody.string();
Log.d("Server::Response", r); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); 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; return;
} }
SCNSettings.inst().user_id = json.getInt("user_id"); SCNSettings.inst().user_id = json_int(json, "user_id");
SCNSettings.inst().user_key = json.getString("user_key"); SCNSettings.inst().user_key = json_str(json, "user_key");
SCNSettings.inst().fcm_token_server = token; SCNSettings.inst().fcm_token_server = token;
SCNSettings.inst().quota_curr = json.getInt("quota"); SCNSettings.inst().quota_curr = json_int(json, "quota");
SCNSettings.inst().quota_max = json.getInt("quota_max"); SCNSettings.inst().quota_max = json_int(json, "quota_max");
SCNSettings.inst().promode_server = json_bool(json, "is_pro");
SCNSettings.inst().save(); SCNSettings.inst().save();
SCNApp.refreshAccountTab(); SCNApp.refreshAccountTab();
handleSuccess("update<1>", call, response, r);
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace(); handleError("update<1>", call, response, r, false, e);
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
} }
finally finally
@@ -150,12 +165,11 @@ public class ServerCommunication
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace(); handleError("update<1>", null, null, Str.Empty, false, e);
SCNApp.showToast("Communication with server failed", 4000);
} }
} }
public static void update(int id, String key, View loader) public static void resetSecret(int id, String key, View loader)
{ {
try try
{ {
@@ -165,39 +179,51 @@ public class ServerCommunication
client.newCall(request).enqueue(new Callback() { client.newCall(request).enqueue(new Callback() {
@Override @Override
public void onFailure(Call call, IOException e) { public void onFailure(Call call, IOException e)
e.printStackTrace(); {
handleError("update<1>", call, null, Str.Empty, true, e);
SCNApp.showToast("Communication with server failed", 4000); SCNApp.showToast("Communication with server failed", 4000);
} }
@Override @Override
public void onResponse(Call call, Response response) { public void onResponse(Call call, Response response)
try (ResponseBody responseBody = response.body()) { {
String r = Str.Empty;
try (ResponseBody responseBody = response.body())
{
if (!response.isSuccessful()) if (!response.isSuccessful())
throw new IOException("Unexpected code " + response); throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No response"); if (responseBody == null) throw new IOException("No response");
String r = responseBody.string(); r = responseBody.string();
Log.d("Server::Response", r); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); 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<2>", call, response, r);
return; return;
} }
SCNSettings.inst().user_id = json.getInt("user_id"); SCNSettings.inst().user_id = json_int(json, "user_id");
SCNSettings.inst().user_key = json.getString("user_key"); SCNSettings.inst().user_key = json_str(json, "user_key");
SCNSettings.inst().quota_curr = json.getInt("quota"); SCNSettings.inst().quota_curr = json_int(json, "quota");
SCNSettings.inst().quota_max = json.getInt("quota_max"); SCNSettings.inst().quota_max = json_int(json, "quota_max");
SCNSettings.inst().promode_server = json_bool(json, "is_pro");
SCNSettings.inst().save(); SCNSettings.inst().save();
SCNApp.refreshAccountTab(); 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); SCNApp.showToast("Communication with server failed", 4000);
} finally { }
finally
{
SCNApp.runOnUiThread(() -> { SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE); if (loader != null) loader.setVisibility(View.GONE);
}); });
@@ -207,8 +233,7 @@ public class ServerCommunication
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace(); handleError("update<2>", null, null, Str.Empty, false, e);
SCNApp.showToast("Communication with server failed", 4000);
} }
} }
@@ -223,40 +248,146 @@ public class ServerCommunication
client.newCall(request).enqueue(new Callback() { client.newCall(request).enqueue(new Callback() {
@Override @Override
public void onFailure(Call call, IOException e) { public void onFailure(Call call, IOException e) {
e.printStackTrace(); handleError("info", call, null, Str.Empty, true, e);
SCNApp.showToast("Communication with server failed", 4000);
SCNApp.runOnUiThread(() -> { SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE); if (loader != null) loader.setVisibility(View.GONE);
}); });
} }
@Override @Override
public void onResponse(Call call, Response response) { public void onResponse(Call call, Response response)
try (ResponseBody responseBody = response.body()) { {
String r = Str.Empty;
try (ResponseBody responseBody = response.body())
{
if (!response.isSuccessful()) if (!response.isSuccessful())
throw new IOException("Unexpected code " + response); throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No response"); if (responseBody == null) throw new IOException("No response");
String r = responseBody.string(); r = responseBody.string();
Log.d("Server::Response", r); Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue(); JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json.getBoolean("success")) { if (!json_bool(json, "success"))
SCNApp.showToast(json.getString("message"), 4000); {
return; SCNApp.showToast(json_str(json, "message"), 4000);
} handleNonSuccess("info", call, response, r);
SCNSettings.inst().user_id = json.getInt("user_id"); int errid = json.optInt("errid", 0);
SCNSettings.inst().quota_curr = json.getInt("quota");
SCNSettings.inst().quota_max = json.getInt("quota_max"); 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(); SCNSettings.inst().save();
SCNApp.refreshAccountTab(); 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); 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(() -> { SCNApp.runOnUiThread(() -> {
if (loader != null) loader.setVisibility(View.GONE); if (loader != null) loader.setVisibility(View.GONE);
}); });
@@ -266,8 +397,290 @@ public class ServerCommunication
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace(); handleError("requery", null, null, Str.Empty, false, e);
}
}
public static void upgrade(int id, String key, View loader, boolean pro, String pro_token)
{
try
{
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
Request request = new Request.Builder()
.url(BASE_URL + "upgrade.php?user_id=" + id + "&user_key=" + key + "&pro=" + pro + "&pro_token=" + URLEncoder.encode(pro_token, "utf-8"))
.build();
client.newCall(request).enqueue(new Callback()
{
@Override
public void onFailure(Call call, IOException e)
{
handleError("upgrade", call, null, Str.Empty, true, e);
}
@Override
public void onResponse(Call call, Response response)
{
String r = Str.Empty;
try (ResponseBody responseBody = response.body())
{
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No response");
r = responseBody.string();
Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json_bool(json, "success")) {
SCNApp.showToast(json_str(json, "message"), 4000);
handleNonSuccess("upgrade", call, response, r);
return;
}
SCNSettings.inst().user_id = json_int(json, "user_id");
SCNSettings.inst().quota_curr = json_int(json, "quota");
SCNSettings.inst().quota_max = json_int(json, "quota_max");
SCNSettings.inst().promode_server = json_bool(json, "is_pro");
SCNSettings.inst().save();
SCNApp.refreshAccountTab();
handleSuccess("upgrade", call, response, r);
}
catch (Exception e)
{
handleError("upgrade", call, response, r, false, e);
}
finally
{
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
}
}
});
}
catch (Exception e)
{
handleError("upgrade", null, null, Str.Empty, false, e);
}
}
public static void ack(int id, String key, long msg_scn_id)
{
try
{
Request request = new Request.Builder()
.url(BASE_URL + "ack.php?user_id=" + id + "&user_key=" + key + "&scn_msg_id=" + msg_scn_id)
.build();
client.newCall(request).enqueue(new Callback()
{
@Override
public void onFailure(Call call, IOException e)
{
handleError("ack", call, null, Str.Empty, true, e);
}
@Override
public void onResponse(Call call, Response response)
{
String r = Str.Empty;
try (ResponseBody responseBody = response.body())
{
if (!response.isSuccessful())
throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No response");
r = responseBody.string();
Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json_bool(json, "success"))
{
SCNApp.showToast(json_str(json, "message"), 4000);
handleNonSuccess("ack", call, response, r);
}
handleSuccess("ack", call, response, r);
}
catch (Exception e)
{
handleError("ack", call, response, r, false, e);
}
}
});
}
catch (Exception e)
{
handleError("ack", null, null, Str.Empty, false, e);
}
}
public static void expand(int id, String key, long scn_msg_id, View loader, Func5to0<String, String, PriorityEnum, Long, Long> okResult)
{
try
{
Request request = new Request.Builder()
.url(BASE_URL + "expand.php?user_id=" + id + "&user_key=" + key + "&scn_msg_id=" + scn_msg_id)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
handleError("expand", call, null, Str.Empty, true, e);
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
}
@Override
public void onResponse(Call call, Response response)
{
String r = Str.Empty;
try (ResponseBody responseBody = response.body())
{
if (!response.isSuccessful())
throw new IOException("Unexpected code " + response);
if (responseBody == null) throw new IOException("No response");
r = responseBody.string();
Log.d("Server::Response", request.url().toString()+"\n"+r);
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
if (!json_bool(json, "success"))
{
SCNApp.showToast(json_str(json, "message"), 4000);
handleNonSuccess("expand", call, response, r);
return;
}
JSONObject o = json.getJSONObject("data");
long time = json_lng(o, "timestamp");
String title = json_str(o, "title");
String content = json_str(o, "body");
PriorityEnum prio = PriorityEnum.parseAPI(json_int(o, "priority"));
long scn_id = json_lng(o, "scn_msg_id");
okResult.invoke(title, content, prio, time, scn_id);
handleSuccess("expand", call, response, r);
}
catch (Exception e)
{
handleError("expand", call, response, r, false, e);
}
finally
{
SCNApp.runOnUiThread(() -> { if (loader != null) loader.setVisibility(View.GONE); });
}
}
});
}
catch (Exception e)
{
handleError("expand", null, null, Str.Empty, false, e);
}
}
private static boolean json_bool(JSONObject o, String key) throws JSONException
{
Object v = o.get(key);
if (v instanceof Integer) return ((int)v) != 0;
if (v instanceof Boolean) return ((boolean)v);
if (v instanceof String) return !Str.equals(((String)v), "0") && !Str.equals(((String)v), "false");
return o.getBoolean(key);
}
private static int json_int(JSONObject o, String key) throws JSONException
{
return o.getInt(key);
}
private static long json_lng(JSONObject o, String key) throws JSONException
{
return o.getLong(key);
}
private static String json_str(JSONObject o, String key) throws JSONException
{
return o.getString(key);
}
private static void handleSuccess(String source, Call call, Response resp, String respBody)
{
Log.d("SC:"+source, respBody);
try
{
Instant i = Instant.now();
String s = source;
String u = call.request().url().toString();
int rc = resp.code();
String r = respBody;
LogLevel l = LogLevel.INFO;
SingleQuery q = new SingleQuery(l, i, s, u, r, rc, "SUCCESS");
QueryLog.inst().add(q);
}
catch (Exception e2)
{
Log.e("SC:HandleSuccess", e2.toString());
}
}
private static void handleNonSuccess(String source, Call call, Response resp, String respBody)
{
Log.d("SC:"+source, respBody);
try
{
Instant i = Instant.now();
String s = source;
String u = call.request().url().toString();
int rc = resp.code();
String r = respBody;
LogLevel l = LogLevel.WARN;
SingleQuery q = new SingleQuery(l, i, s, u, r, rc, "NON-SUCCESS");
QueryLog.inst().add(q);
}
catch (Exception e2)
{
Log.e("SC:HandleSuccess", e2.toString());
}
}
private static void handleError(String source, Call call, Response resp, String respBody, boolean isio, Exception e)
{
Log.e("SC:"+source, e.toString());
if (isio)
{
SCNApp.showToast("Can't connect to server", 3000);
}
else
{
SCNApp.showToast("Communication with server failed", 4000); 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

@@ -1,23 +1,24 @@
package com.blackforestbytes.simplecloudnotifier.service; package com.blackforestbytes.simplecloudnotifier.service;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import com.blackforestbytes.simplecloudnotifier.R;
import com.blackforestbytes.simplecloudnotifier.SCNApp; import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.model.CMessage; import com.blackforestbytes.simplecloudnotifier.model.CMessage;
import com.blackforestbytes.simplecloudnotifier.model.CMessageList; import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
import com.blackforestbytes.simplecloudnotifier.model.LogLevel;
import com.blackforestbytes.simplecloudnotifier.model.PriorityEnum; import com.blackforestbytes.simplecloudnotifier.model.PriorityEnum;
import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings; import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
import com.blackforestbytes.simplecloudnotifier.view.MainActivity; import com.blackforestbytes.simplecloudnotifier.model.ServerCommunication;
import com.blackforestbytes.simplecloudnotifier.model.SingleQuery;
import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage; import com.google.firebase.messaging.RemoteMessage;
import org.joda.time.Instant;
import org.json.JSONObject;
public class FBMService extends FirebaseMessagingService public class FBMService extends FirebaseMessagingService
{ {
@Override @Override
@@ -32,6 +33,8 @@ public class FBMService extends FirebaseMessagingService
{ {
try try
{ {
if (!SCNSettings.inst().Enabled) return;
Log.i("FB::MessageReceived", "From: " + remoteMessage.getFrom()); Log.i("FB::MessageReceived", "From: " + remoteMessage.getFrom());
Log.i("FB::MessageReceived", "Payload: " + remoteMessage.getData()); Log.i("FB::MessageReceived", "Payload: " + remoteMessage.getData());
if (remoteMessage.getNotification() != null) Log.i("FB::MessageReceived", "Notify_Title: " + remoteMessage.getNotification().getTitle()); if (remoteMessage.getNotification() != null) Log.i("FB::MessageReceived", "Notify_Title: " + remoteMessage.getNotification().getTitle());
@@ -41,17 +44,20 @@ public class FBMService extends FirebaseMessagingService
String title = remoteMessage.getData().get("title"); String title = remoteMessage.getData().get("title");
String content = remoteMessage.getData().get("body"); String content = remoteMessage.getData().get("body");
PriorityEnum prio = PriorityEnum.parseAPI(remoteMessage.getData().get("priority")); PriorityEnum prio = PriorityEnum.parseAPI(remoteMessage.getData().get("priority"));
long scn_id = Long.parseLong(remoteMessage.getData().get("scn_msg_id"));
CMessage msg = CMessageList.inst().add(time, title, content, prio); boolean trimmed = Boolean.parseBoolean(remoteMessage.getData().get("trimmed"));
if (SCNApp.isBackground()) SingleQuery q = new SingleQuery(LogLevel.INFO, Instant.now(), "FBM<recieve>", Str.Empty, new JSONObject(remoteMessage.getData()).toString(), 0, "SUCCESS");
QueryLog.inst().add(q);
if (trimmed)
{ {
NotificationService.inst().show(msg); ServerCommunication.expand(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id, null, (i1, i2, i3, i4, i5) -> recieveData(i4, i1, i2, i3, i5, false));
} }
else else
{ {
SCNApp.showToast("Message recieved: " + title, Toast.LENGTH_LONG); recieveData(time, title, content, prio, scn_id, false);
} }
} }
catch (Exception e) catch (Exception e)
@@ -60,4 +66,27 @@ public class FBMService extends FirebaseMessagingService
SCNApp.showToast("Recieved invalid message from server", Toast.LENGTH_LONG); SCNApp.showToast("Recieved invalid message from server", Toast.LENGTH_LONG);
} }
} }
public static void recieveData(long time, String title, String content, PriorityEnum prio, long scn_id, boolean alwaysAck)
{
if (CMessageList.inst().isAck(scn_id))
{
Log.w("FB::MessageReceived", "Recieved ack-ed message: " + scn_id);
if (alwaysAck) ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id);
return;
}
CMessage msg = CMessageList.inst().add(scn_id, time, title, content, prio);
if (SCNApp.isBackground())
{
NotificationService.inst().showBackground(msg);
}
else
{
NotificationService.inst().showForeground(msg);
}
ServerCommunication.ack(SCNSettings.inst().user_id, SCNSettings.inst().user_key, scn_id);
}
} }

View File

@@ -0,0 +1,305 @@
package com.blackforestbytes.simplecloudnotifier.service;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import android.widget.Toast;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple2;
import com.blackforestbytes.simplecloudnotifier.lib.datatypes.Tuple3;
import com.blackforestbytes.simplecloudnotifier.lib.lambda.Func0to0;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import static androidx.constraintlayout.widget.Constraints.TAG;
public class IABService implements PurchasesUpdatedListener
{
public static final String IAB_PRO_MODE = "scn.pro.tier1";
private final static Object _lock = new Object();
private static IABService _inst = null;
public static IABService inst()
{
synchronized (_lock)
{
if (_inst != null) return _inst;
throw new Error("IABService == null");
}
}
public static void startup(MainActivity a)
{
synchronized (_lock)
{
_inst = new IABService(a);
}
}
public enum SimplePurchaseState { YES, NO, UNINITIALIZED }
private BillingClient client;
private boolean isServiceConnected;
private final List<Purchase> purchases = new ArrayList<>();
private boolean _isInitialized = false;
private final Map<String, Boolean> _localCache= new HashMap<>();
public IABService(Context c)
{
_isInitialized = false;
loadCache();
client = BillingClient
.newBuilder(c)
.setListener(this)
.build();
startServiceConnection(this::queryPurchases, false);
startServiceConnection(this::querySkuDetails, false);
}
public void reloadPrefs()
{
loadCache();
}
private void loadCache()
{
_localCache.clear();
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("IAB", Context.MODE_PRIVATE);
int count = sharedPref.getInt("c", 0);
for (int i=0; i < count; i++)
{
String k = sharedPref.getString("["+i+"]->key", null);
boolean v = sharedPref.getBoolean("["+i+"]->value", false);
if (k==null)continue;
_localCache.put(k, v);
}
}
private void saveCache()
{
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("IAB", Context.MODE_PRIVATE);
SharedPreferences.Editor editor= sharedPref.edit();
editor.putInt("c", _localCache.size());
int i = 0;
for (Map.Entry<String, Boolean> e : _localCache.entrySet())
{
editor.putString("["+i+"]->key", e.getKey());
editor.putBoolean("["+i+"]->value", e.getValue());
i++;
}
editor.apply();
}
@SuppressWarnings("ConstantConditions")
private synchronized void updateCache(String k, boolean v)
{
if (_localCache.containsKey(k) && _localCache.get(k)==v) return;
_localCache.put(k, v);
saveCache();
}
public void queryPurchases()
{
Func0to0 queryToExecute = () ->
{
long time = System.currentTimeMillis();
Purchase.PurchasesResult purchasesResult = client.queryPurchases(BillingClient.SkuType.INAPP);
Log.i(TAG, "Querying purchases elapsed time: " + (System.currentTimeMillis() - time) + "ms");
if (purchasesResult.getResponseCode() == BillingClient.BillingResponseCode.OK)
{
for (Purchase p : Objects.requireNonNull(purchasesResult.getPurchasesList()))
{
handlePurchase(p, false);
}
_isInitialized = true;
boolean newProMode = getPurchaseCachedSimple(IAB_PRO_MODE);
if (newProMode != SCNSettings.inst().promode_local)
{
refreshProModeListener();
}
}
else
{
Log.w(TAG, "queryPurchases() got an error response code: " + purchasesResult.getResponseCode());
}
};
executeServiceRequest(queryToExecute, false);
}
public void querySkuDetails() {
}
public void purchase(Activity a, String id)
{
Func0to0 queryRequest = () -> {
// Query the purchase async
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(Collections.singletonList(id)).setType(BillingClient.SkuType.INAPP);
client.querySkuDetailsAsync(params.build(), (billingResult, skuDetailsList) ->
{
if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK || skuDetailsList == null || skuDetailsList.size() != 1)
{
SCNApp.showToast("Could not find product", Toast.LENGTH_SHORT);
return;
}
executeServiceRequest(() ->
{
BillingFlowParams flowParams = BillingFlowParams
.newBuilder()
.setSkuDetails(skuDetailsList.get(0))
.build();
client.launchBillingFlow(a, flowParams);
}, true);
});
};
executeServiceRequest(queryRequest, false);
}
private void executeServiceRequest(Func0to0 runnable, final boolean userRequest)
{
if (isServiceConnected)
{
runnable.invoke();
}
else
{
// If billing service was disconnected, we try to reconnect 1 time.
// (feel free to introduce your retry policy here).
startServiceConnection(runnable, userRequest);
}
}
public void destroy()
{
if (client != null && client.isReady()) {
client.endConnection();
client = null;
isServiceConnected = false;
}
}
@Override
public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> purchases)
{
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null)
{
for (Purchase purchase : purchases)
{
handlePurchase(purchase, true);
}
}
else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED && purchases != null)
{
for (Purchase purchase : purchases)
{
handlePurchase(purchase, true);
}
}
}
private void handlePurchase(Purchase purchase, boolean triggerUpdate)
{
Log.d(TAG, "Got a verified purchase: " + purchase);
purchases.add(purchase);
if (triggerUpdate) refreshProModeListener();
updateCache(purchase.getSku(), true);
}
private void refreshProModeListener()
{
MainActivity ma = SCNApp.getMainActivity();
if (ma != null) ma.adpTabs.tab3.updateProState();
if (ma != null) ma.adpTabs.tab1.updateProState();
SCNSettings.inst().updateProState(null);
}
public void startServiceConnection(final Func0to0 executeOnSuccess, final boolean userRequest)
{
client.startConnection(new BillingClientStateListener()
{
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult)
{
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK)
{
isServiceConnected = true;
if (executeOnSuccess != null) executeOnSuccess.invoke();
}
else
{
if (userRequest) SCNApp.showToast("Could not connect to google services", Toast.LENGTH_SHORT);
}
}
@Override
public void onBillingServiceDisconnected() {
isServiceConnected = false;
}
});
}
public boolean getPurchaseCachedSimple(String id)
{
return getPurchaseCachedExtended(id).Item1;
}
@SuppressWarnings("ConstantConditions")
public Tuple3<Boolean, Boolean, String> getPurchaseCachedExtended(String id)
{
// <state, initialized, token>
if (!_isInitialized)
{
if (_localCache.containsKey(id) && _localCache.get(id)) return new Tuple3<>(true, false, Str.Empty);
}
for (Purchase p : purchases)
{
if (Str.equals(p.getSku(), id))
{
updateCache(id, true);
return new Tuple3<>(true, true, p.getPurchaseToken());
}
}
updateCache(id, false);
return new Tuple3<>(false, true, Str.Empty);
}
}

View File

@@ -1,22 +1,37 @@
package com.blackforestbytes.simplecloudnotifier.service; package com.blackforestbytes.simplecloudnotifier.service;
import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.BitmapFactory;
import android.graphics.Color; import android.graphics.Color;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.support.v4.app.NotificationCompat; import android.os.VibrationEffect;
import android.os.Vibrator;
import android.widget.Toast;
import com.blackforestbytes.simplecloudnotifier.R; import com.blackforestbytes.simplecloudnotifier.R;
import com.blackforestbytes.simplecloudnotifier.SCNApp; import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.model.CMessage; 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 com.blackforestbytes.simplecloudnotifier.view.MainActivity;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
public class NotificationService public class NotificationService
{ {
private final static String CHANNEL_ID = "CHAN_BFB_SCN_MESSAGES"; private final static String CHANNEL_P0_ID = "CHAN_BFB_SCN_MESSAGES_P0";
private final static String CHANNEL_P1_ID = "CHAN_BFB_SCN_MESSAGES_P1";
private final static String CHANNEL_P2_ID = "CHAN_BFB_SCN_MESSAGES_P2";
private final static Object _lock = new Object(); private final static Object _lock = new Object();
private static NotificationService _inst = null; private static NotificationService _inst = null;
@@ -31,39 +46,222 @@ public class NotificationService
private NotificationService() private NotificationService()
{ {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createChannels();
}
private void createChannels()
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
Context ctxt = SCNApp.getContext();
NotificationManager notifman = ctxt.getSystemService(NotificationManager.class);
if (notifman == null) return;
{
NotificationChannel channel0 = notifman.getNotificationChannel(CHANNEL_P0_ID);
if (channel0 == null)
{
channel0 = new NotificationChannel(CHANNEL_P0_ID, "Push notifications (low priority)", NotificationManager.IMPORTANCE_DEFAULT);
channel0.setDescription("Push notifications from the server with low priority.\nGo to the in-app settings to configure ringtone, volume and vibrations");
channel0.setSound(null, null);
channel0.setVibrationPattern(null);
channel0.setLightColor(Color.CYAN);
channel0.enableLights(true);
notifman.createNotificationChannel(channel0);
}
}
{
NotificationChannel channel1 = notifman.getNotificationChannel(CHANNEL_P1_ID);
if (channel1 == null)
{
channel1 = new NotificationChannel(CHANNEL_P1_ID, "Push notifications (normal priority)", NotificationManager.IMPORTANCE_DEFAULT);
channel1.setDescription("Push notifications from the server with low priority.\nGo to the in-app settings to configure ringtone, volume and vibrations");
channel1.setSound(null, null);
channel1.setVibrationPattern(null);
channel1.setLightColor(Color.CYAN);
channel1.enableLights(true);
notifman.createNotificationChannel(channel1);
}
}
{
NotificationChannel channel2 = notifman.getNotificationChannel(CHANNEL_P2_ID);
if (channel2 == null)
{
channel2 = new NotificationChannel(CHANNEL_P2_ID, "Push notifications (high priority)", NotificationManager.IMPORTANCE_DEFAULT);
channel2.setDescription("Push notifications from the server with low priority.\nGo to the in-app settings to configure ringtone, volume and vibrations");
channel2.setSound(null, null);
channel2.setVibrationPattern(null);
channel2.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
channel2.setLightColor(Color.CYAN);
channel2.enableLights(true);
notifman.createNotificationChannel(channel2);
}
}
}
public void showForeground(CMessage msg)
{
SCNApp.showToast("Message recieved: " + msg.Title, Toast.LENGTH_LONG);
try
{
NotificationSettings ns = SCNSettings.inst().PriorityNorm;
switch (msg.Priority)
{
case LOW: ns = SCNSettings.inst().PriorityLow; break;
case NORMAL: ns = SCNSettings.inst().PriorityNorm; break;
case HIGH: ns = SCNSettings.inst().PriorityHigh; break;
}
SoundService.play(ns.EnableSound, ns.SoundSource, ns.ForceVolume, ns.ForceVolumeValue, false);
if (ns.EnableVibration)
{
Vibrator v = (Vibrator) SCNApp.getContext().getSystemService(Context.VIBRATOR_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
v.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE));
} else {
v.vibrate(500);
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void showBackground(CMessage msg)
{ {
Context ctxt = SCNApp.getContext(); Context ctxt = SCNApp.getContext();
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Push notifications", NotificationManager.IMPORTANCE_HIGH); NotificationSettings ns = SCNSettings.inst().PriorityNorm;
channel.setDescription("Messages from the API"); switch (msg.Priority)
channel.setLightColor(Color.rgb(255, 0, 0));
channel.setVibrationPattern(new long[]{200});
channel.enableLights(true);
channel.enableVibration(true);
NotificationManager notificationManager = ctxt.getSystemService(NotificationManager.class);
if (notificationManager != null) notificationManager.createNotificationChannel(channel);
}
}
public void show(CMessage msg)
{ {
Context ctxt = SCNApp.getContext(); case LOW: ns = SCNSettings.inst().PriorityLow; break;
case NORMAL: ns = SCNSettings.inst().PriorityNorm; break;
case HIGH: ns = SCNSettings.inst().PriorityHigh; break;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
{
// old
showBackground_old(msg, ctxt, ns, msg.Priority);
}
else
{
// new
showBackground_new(msg, ctxt, ns, msg.Priority);
}
}
private String getChannel(PriorityEnum p)
{
switch (p)
{
case LOW: return CHANNEL_P0_ID;
case NORMAL: return CHANNEL_P1_ID;
case HIGH: return CHANNEL_P2_ID;
default: return CHANNEL_P0_ID;
}
}
private void showBackground_old(CMessage msg, Context ctxt, NotificationSettings ns, PriorityEnum prio)
{
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, getChannel(prio));
mBuilder.setSmallIcon(R.drawable.ic_notification_white);
mBuilder.setLargeIcon(BitmapFactory.decodeResource(ctxt.getResources(), R.mipmap.ic_notification_full));
mBuilder.setContentTitle(msg.Title);
mBuilder.setContentText(msg.Content);
mBuilder.setShowWhen(true);
mBuilder.setWhen(msg.Timestamp * 1000);
mBuilder.setAutoCancel(true);
mBuilder.setCategory(Notification.CATEGORY_MESSAGE);
mBuilder.setGroup("com.blackforestbytes.simplecloudnotifier.notifications.group."+prio.toString());
if (msg.Priority == PriorityEnum.LOW) mBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
if (msg.Priority == PriorityEnum.NORMAL) mBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT);
if (msg.Priority == PriorityEnum.HIGH) mBuilder.setPriority(NotificationCompat.PRIORITY_HIGH);
if (ns.EnableVibration) mBuilder.setVibrate(new long[]{500});
if (ns.EnableLED) mBuilder.setLights(ns.LEDColor, 500, 500);
if (ns.EnableSound && !ns.SoundSource.isEmpty() && !ns.RepeatSound) mBuilder.setSound(Uri.parse(ns.SoundSource), AudioManager.STREAM_NOTIFICATION);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_bfb)
.setContentTitle(msg.Title)
.setContentText(msg.Content)
.setShowWhen(true)
.setWhen(msg.Timestamp)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true);
Intent intent = new Intent(ctxt, MainActivity.class); Intent intent = new Intent(ctxt, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(ctxt, 0, intent, 0); PendingIntent pi = PendingIntent.getActivity(ctxt, 0, intent, 0);
mBuilder.setContentIntent(pi); mBuilder.setContentIntent(pi);
NotificationManager mNotificationManager = (NotificationManager) ctxt.getSystemService(Context.NOTIFICATION_SERVICE); NotificationManager mNotificationManager = (NotificationManager) ctxt.getSystemService(Context.NOTIFICATION_SERVICE);
if (mNotificationManager != null) mNotificationManager.notify(0, mBuilder.build());
if (ns.EnableSound && !ns.SoundSource.isEmpty() && ns.RepeatSound)
{
Intent intnt_stop = new Intent(SCNApp.getContext(), BroadcastReceiverService.class);
intnt_stop.putExtra(BroadcastReceiverService.ID_KEY, BroadcastReceiverService.NOTIF_STOP_SOUND);
PendingIntent pi_stop = PendingIntent.getBroadcast(SCNApp.getContext().getApplicationContext(), BroadcastReceiverService.NOTIF_STOP_SOUND, intnt_stop, 0);
mBuilder.addAction(new NotificationCompat.Action(-1, "Stop", pi_stop));
mBuilder.setDeleteIntent(pi_stop);
SoundService.play(ns.EnableSound, ns.SoundSource, ns.ForceVolume, ns.ForceVolumeValue, ns.RepeatSound);
} }
Notification n = mBuilder.build();
if (mNotificationManager != null) mNotificationManager.notify((int)msg.SCN_ID, n);
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void showBackground_new(CMessage msg, Context ctxt, NotificationSettings ns, PriorityEnum prio)
{
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, getChannel(prio));
mBuilder.setSmallIcon(R.drawable.ic_notification_white);
mBuilder.setLargeIcon(BitmapFactory.decodeResource(ctxt.getResources(), R.mipmap.ic_notification_full));
mBuilder.setContentTitle(msg.Title);
mBuilder.setContentText(msg.Content);
mBuilder.setShowWhen(true);
mBuilder.setWhen(msg.Timestamp * 1000);
mBuilder.setAutoCancel(true);
mBuilder.setCategory(Notification.CATEGORY_MESSAGE);
mBuilder.setGroup("com.blackforestbytes.simplecloudnotifier.notifications.group."+prio.toString());
if (ns.EnableLED) mBuilder.setLights(ns.LEDColor, 500, 500);
if (msg.Priority == PriorityEnum.LOW) mBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
if (msg.Priority == PriorityEnum.NORMAL) mBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT);
if (msg.Priority == PriorityEnum.HIGH) mBuilder.setPriority(NotificationCompat.PRIORITY_HIGH);
Intent intent = new Intent(ctxt, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(ctxt, 0, intent, 0);
mBuilder.setContentIntent(pi);
NotificationManager mNotificationManager = (NotificationManager) ctxt.getSystemService(Context.NOTIFICATION_SERVICE);
if (mNotificationManager == null) return;
if (ns.EnableSound && !Str.isNullOrWhitespace(ns.SoundSource))
{
if (ns.RepeatSound)
{
Intent intnt_stop = new Intent(SCNApp.getContext(), BroadcastReceiverService.class);
intnt_stop.putExtra(BroadcastReceiverService.ID_KEY, BroadcastReceiverService.NOTIF_STOP_SOUND);
PendingIntent pi_stop = PendingIntent.getBroadcast(ctxt, BroadcastReceiverService.NOTIF_STOP_SOUND, intnt_stop, 0);
mBuilder.addAction(new NotificationCompat.Action(-1, "Stop", pi_stop));
mBuilder.setDeleteIntent(pi_stop);
}
SoundService.play(ns.EnableSound, ns.SoundSource, ns.ForceVolume, ns.ForceVolumeValue, ns.RepeatSound);
}
Notification n = mBuilder.build();
n.flags |= Notification.FLAG_AUTO_CANCEL;
mNotificationManager.notify((int)msg.SCN_ID, n);
if (ns.EnableVibration)
{
Vibrator v = (Vibrator) SCNApp.getContext().getSystemService(Context.VIBRATOR_SERVICE);
v.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE));
}
//if (ns.EnableLED) { } // no LED in Android-O -- configure via Channel
}
} }

View File

@@ -0,0 +1,56 @@
package com.blackforestbytes.simplecloudnotifier.service;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.util.Log;
import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import java.io.IOException;
public class SoundService
{
private static MediaPlayer mpLast = null;
public static void play(boolean enableSound, String soundSource, boolean forceVolume, int forceVolumeValue, boolean loop)
{
if (!enableSound) return;
if (Str.isNullOrWhitespace(soundSource)) return;
stop();
if (forceVolume)
{
AudioManager aman = (AudioManager) SCNApp.getContext().getSystemService(Context.AUDIO_SERVICE);
int maxVolume = aman.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION);
aman.setStreamVolume(AudioManager.STREAM_NOTIFICATION, (int)(maxVolume * (forceVolumeValue / 100.0)), 0);
}
try
{
MediaPlayer player = new MediaPlayer();
player.setAudioAttributes(new AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_NOTIFICATION).build());
player.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
player.setDataSource(SCNApp.getContext(), Uri.parse(soundSource));
player.setLooping(loop);
player.setOnCompletionListener( mp -> { mp.stop(); mp.release(); });
player.setOnSeekCompleteListener(mp -> { mp.stop(); mp.release(); });
player.prepare();
player.start();
mpLast = player;
}
catch (IOException e)
{
Log.e("Sound::play", e.toString());
}
}
public static void stop()
{
if (mpLast != null && mpLast.isPlaying()) { mpLast.stop(); mpLast.release(); mpLast = null; }
}
}

View File

@@ -0,0 +1,56 @@
package com.blackforestbytes.simplecloudnotifier.util;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.ScrollView;
import com.blackforestbytes.simplecloudnotifier.R;
public class MaxHeightScrollView extends ScrollView
{
public int maxHeight = Integer.MAX_VALUE;//dp
public MaxHeightScrollView(Context context)
{
super(context);
}
public MaxHeightScrollView(Context context, AttributeSet attrs)
{
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView, 0, 0);
try {
maxHeight = a.getInteger(R.styleable.MaxHeightScrollView_maxHeightOverride, Integer.MAX_VALUE);
} finally {
a.recycle();
}
}
public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView, 0, 0);
try {
maxHeight = a.getInteger(R.styleable.MaxHeightScrollView_maxHeightOverride, Integer.MAX_VALUE);
} finally {
a.recycle();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
heightMeasureSpec = MeasureSpec.makeMeasureSpec(dpToPx(getResources(), maxHeight), MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private int dpToPx(Resources res, int dp)
{
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, res.getDisplayMetrics());
}
}

View File

@@ -0,0 +1,89 @@
package com.blackforestbytes.simplecloudnotifier.util;
import android.graphics.Canvas;
import android.view.View;
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
import com.blackforestbytes.simplecloudnotifier.view.MessageAdapter;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
public class MessageAdapterTouchHelper extends ItemTouchHelper.SimpleCallback
{
private MessageAdapterTouchHelperListener listener;
private int dir = 0;
public MessageAdapterTouchHelper(int dragDirs, int swipeDirs, MessageAdapterTouchHelperListener listener)
{
super(dragDirs, swipeDirs);
this.dir = swipeDirs;
this.listener = listener;
updateEnabled();
}
public void updateEnabled()
{
int sdir = SCNSettings.inst().EnableDeleteSwipe ? ItemTouchHelper.LEFT : 0;
if (dir == sdir) return;
setDefaultSwipeDirs(dir = sdir);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target)
{
return true;
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState)
{
if (viewHolder != null)
{
final View foregroundView = ((MessageAdapter.MessagePresenter) viewHolder).viewForeground;
getDefaultUIUtil().onSelected(foregroundView);
}
}
@Override
public void onChildDrawOver(@NonNull Canvas c, @NonNull RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)
{
final View foregroundView = ((MessageAdapter.MessagePresenter) viewHolder).viewForeground;
getDefaultUIUtil().onDrawOver(c, recyclerView, foregroundView, dX, dY, actionState, isCurrentlyActive);
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder)
{
final View foregroundView = ((MessageAdapter.MessagePresenter) viewHolder).viewForeground;
getDefaultUIUtil().clearView(foregroundView);
}
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)
{
final View foregroundView = ((MessageAdapter.MessagePresenter) viewHolder).viewForeground;
getDefaultUIUtil().onDraw(c, recyclerView, foregroundView, dX, dY, actionState, isCurrentlyActive);
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction)
{
listener.onSwiped(viewHolder, direction, viewHolder.getAdapterPosition());
}
@Override
public int convertToAbsoluteDirection(int flags, int layoutDirection)
{
return super.convertToAbsoluteDirection(flags, layoutDirection);
}
public interface MessageAdapterTouchHelperListener
{
void onSwiped(RecyclerView.ViewHolder viewHolder, int direction, int position);
}
}

View File

@@ -0,0 +1,25 @@
package com.blackforestbytes.simplecloudnotifier.util;
import android.text.Editable;
import android.text.TextWatcher;
public abstract class TextChangedListener<T> implements TextWatcher {
private T target;
public TextChangedListener(T target) {
this.target = target;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
this.onTextChanged(target, s);
}
public abstract void onTextChanged(T target, Editable s);
}

View File

@@ -1,17 +1,19 @@
package com.blackforestbytes.simplecloudnotifier.view; package com.blackforestbytes.simplecloudnotifier.view;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.blackforestbytes.simplecloudnotifier.R; import com.blackforestbytes.simplecloudnotifier.R;
@@ -22,6 +24,10 @@ import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
import net.glxn.qrgen.android.QRCode; import net.glxn.qrgen.android.QRCode;
import net.glxn.qrgen.core.image.ImageType; 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; import static android.content.Context.CLIPBOARD_SERVICE;
public class AccountFragment extends Fragment public class AccountFragment extends Fragment
@@ -54,20 +60,52 @@ public class AccountFragment extends Fragment
v.findViewById(R.id.btnAccountReset).setOnClickListener(cv -> 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); View lpnl = v.findViewById(R.id.loadingPanel);
lpnl.setVisibility(View.VISIBLE); lpnl.setVisibility(View.VISIBLE);
SCNSettings.inst().reset(lpnl); 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 -> 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(); 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 -> 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); startActivity(browserIntent);
}); });
@@ -93,6 +131,7 @@ public class AccountFragment extends Fragment
TextView tvUserID = v.findViewById(R.id.tvUserID); TextView tvUserID = v.findViewById(R.id.tvUserID);
TextView tvUserKey = v.findViewById(R.id.tvUserKey); TextView tvUserKey = v.findViewById(R.id.tvUserKey);
TextView tvQuota = v.findViewById(R.id.tvQuota); TextView tvQuota = v.findViewById(R.id.tvQuota);
ImageView ivQuota = v.findViewById(R.id.ic_img_quota);
ImageButton btnQR = v.findViewById(R.id.btnQR); ImageButton btnQR = v.findViewById(R.id.btnQR);
SCNSettings s = SCNSettings.inst(); SCNSettings s = SCNSettings.inst();
@@ -102,7 +141,8 @@ public class AccountFragment extends Fragment
tvUserID.setText(String.valueOf(s.user_id)); tvUserID.setText(String.valueOf(s.user_id));
tvUserKey.setText(s.user_key); tvUserKey.setText(s.user_key);
tvQuota.setText(String.format("%d / %d", s.quota_curr, s.quota_max)); 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 else
{ {
@@ -110,6 +150,7 @@ public class AccountFragment extends Fragment
tvUserKey.setText(R.string.str_not_connected); tvUserKey.setText(R.string.str_not_connected);
tvQuota.setText(R.string.str_not_connected); tvQuota.setText(R.string.str_not_connected);
btnQR.setImageResource(R.drawable.qr_default); btnQR.setImageResource(R.drawable.qr_default);
ivQuota.setColorFilter(0x80_80_80);
} }
} }
} }

View File

@@ -1,39 +1,56 @@
package com.blackforestbytes.simplecloudnotifier.view; package com.blackforestbytes.simplecloudnotifier.view;
import android.support.design.widget.TabLayout; import android.content.Context;
import android.support.v4.view.PagerAdapter; import android.content.Intent;
import android.support.v4.view.ViewPager; import android.content.SharedPreferences;
import android.support.v7.app.AppCompatActivity; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.View;
import android.view.MenuItem; import android.widget.RelativeLayout;
import android.widget.Toast;
import com.blackforestbytes.simplecloudnotifier.R; import com.blackforestbytes.simplecloudnotifier.R;
import com.blackforestbytes.simplecloudnotifier.SCNApp; import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.model.CMessageList; import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
import com.blackforestbytes.simplecloudnotifier.model.QueryLog;
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings; import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
import com.blackforestbytes.simplecloudnotifier.model.ServerCommunication; import com.blackforestbytes.simplecloudnotifier.service.IABService;
import com.blackforestbytes.simplecloudnotifier.service.NotificationService; import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
import com.google.firebase.iid.FirebaseInstanceId; import com.blackforestbytes.simplecloudnotifier.view.debug.QueryLogActivity;
import com.google.android.material.tabs.TabLayout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.Map;
import java.util.Set;
public class MainActivity extends AppCompatActivity public class MainActivity extends AppCompatActivity
{ {
public TabAdapter adpTabs; public TabAdapter adpTabs;
public RelativeLayout layoutRoot;
@Override @Override
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState)
{ {
QueryLog.inst();
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
NotificationService.inst(); NotificationService.inst();
CMessageList.inst(); CMessageList.inst();
layoutRoot = findViewById(R.id.layoutRoot);
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setOnClickListener(this::onToolbackClicked);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
ViewPager viewPager = findViewById(R.id.pager); ViewPager viewPager = findViewById(R.id.pager);
@@ -44,9 +61,25 @@ public class MainActivity extends AppCompatActivity
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL); tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.setupWithViewPager(viewPager); tabLayout.setupWithViewPager(viewPager);
SCNApp.register(this); viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { /* */ }
SCNSettings.inst().work(this); @Override
public void onPageSelected(int position)
{
if (position != 2) adpTabs.tab3.onViewpagerHide();
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
SCNApp.register(this);
IABService.startup(this);
SCNSettings.inst().work(this, true);
} }
@Override @Override
@@ -54,6 +87,138 @@ public class MainActivity extends AppCompatActivity
{ {
super.onStop(); super.onStop();
SCNSettings.inst().save();
CMessageList.inst().fullSave(); CMessageList.inst().fullSave();
} }
@Override
protected void onDestroy()
{
super.onDestroy();
CMessageList.inst().fullSave();
IABService.inst().destroy();
}
private int clickCount = 0;
private long lastClick = 0;
private void onToolbackClicked(View v)
{
long now = System.currentTimeMillis();
if (now - lastClick > 200) clickCount=0;
clickCount++;
lastClick = now;
if (clickCount == 4) startActivity(new Intent(this, QueryLogActivity.class));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == 1991 && resultCode == RESULT_OK)
{
Uri uri = data.getData(); //The uri with the location of the file
Context ctxt = this;
try
{
ObjectInputStream stream = new ObjectInputStream(getContentResolver().openInputStream(uri));
Map<String, ?> d1 = (Map<String, ?>)stream.readObject();
Map<String, ?> d2 = (Map<String, ?>)stream.readObject();
Map<String, ?> d3 = (Map<String, ?>)stream.readObject();
Map<String, ?> d4 = (Map<String, ?>)stream.readObject();
stream.close();
runOnUiThread(() ->
{
SharedPreferences.Editor e1 = ctxt.getSharedPreferences("Config", Context.MODE_PRIVATE).edit();
SharedPreferences.Editor e2 = ctxt.getSharedPreferences("IAB", Context.MODE_PRIVATE).edit();
SharedPreferences.Editor e3 = ctxt.getSharedPreferences("CMessageList", Context.MODE_PRIVATE).edit();
SharedPreferences.Editor e4 = ctxt.getSharedPreferences("QueryLog", Context.MODE_PRIVATE).edit();
e1.clear();
for (Map.Entry<String, ?> entry : d1.entrySet())
{
if (entry.getValue() instanceof String) e1.putString(entry.getKey(), (String)entry.getValue());
if (entry.getValue() instanceof Boolean) e1.putBoolean(entry.getKey(), (Boolean)entry.getValue());
if (entry.getValue() instanceof Float) e1.putFloat(entry.getKey(), (Float)entry.getValue());
if (entry.getValue() instanceof Integer) e1.putInt(entry.getKey(), (Integer)entry.getValue());
if (entry.getValue() instanceof Long) e1.putLong(entry.getKey(), (Long)entry.getValue());
if (entry.getValue() instanceof Set<?>) e1.putStringSet(entry.getKey(), (Set<String>)entry.getValue());
}
e2.clear();
for (Map.Entry<String, ?> entry : d2.entrySet())
{
if (entry.getValue() instanceof String) e2.putString(entry.getKey(), (String)entry.getValue());
if (entry.getValue() instanceof Boolean) e2.putBoolean(entry.getKey(), (Boolean)entry.getValue());
if (entry.getValue() instanceof Float) e2.putFloat(entry.getKey(), (Float)entry.getValue());
if (entry.getValue() instanceof Integer) e2.putInt(entry.getKey(), (Integer)entry.getValue());
if (entry.getValue() instanceof Long) e2.putLong(entry.getKey(), (Long)entry.getValue());
if (entry.getValue() instanceof Set<?>) e2.putStringSet(entry.getKey(), (Set<String>)entry.getValue());
}
e2.clear();
for (Map.Entry<String, ?> entry : d3.entrySet())
{
if (entry.getValue() instanceof String) e3.putString(entry.getKey(), (String)entry.getValue());
if (entry.getValue() instanceof Boolean) e3.putBoolean(entry.getKey(), (Boolean)entry.getValue());
if (entry.getValue() instanceof Float) e3.putFloat(entry.getKey(), (Float)entry.getValue());
if (entry.getValue() instanceof Integer) e3.putInt(entry.getKey(), (Integer)entry.getValue());
if (entry.getValue() instanceof Long) e3.putLong(entry.getKey(), (Long)entry.getValue());
if (entry.getValue() instanceof Set<?>) e3.putStringSet(entry.getKey(), (Set<String>)entry.getValue());
}
e4.clear();
for (Map.Entry<String, ?> entry : d4.entrySet())
{
if (entry.getValue() instanceof String) e4.putString(entry.getKey(), (String)entry.getValue());
if (entry.getValue() instanceof Boolean) e4.putBoolean(entry.getKey(), (Boolean)entry.getValue());
if (entry.getValue() instanceof Float) e4.putFloat(entry.getKey(), (Float)entry.getValue());
if (entry.getValue() instanceof Integer) e4.putInt(entry.getKey(), (Integer)entry.getValue());
if (entry.getValue() instanceof Long) e4.putLong(entry.getKey(), (Long)entry.getValue());
if (entry.getValue() instanceof Set<?>) e4.putStringSet(entry.getKey(), (Set<String>)entry.getValue());
}
e1.apply();
e2.apply();
e3.apply();
e4.apply();
SCNSettings.inst().reloadPrefs();
IABService.inst().reloadPrefs();
CMessageList.inst().reloadPrefs();
QueryLog.inst().reloadPrefs();
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ViewPager viewPager = findViewById(R.id.pager);
PagerAdapter adapter = adpTabs = new TabAdapter(getSupportFragmentManager());
viewPager.setAdapter(adapter);
TabLayout tabLayout = findViewById(R.id.tab_layout);
tabLayout.setupWithViewPager(viewPager);
SCNSettings.inst().work(this, true);
SCNApp.showToast("Backup imported", Toast.LENGTH_LONG);
finish();
});
}
catch (Exception e)
{
Log.e("Import:Err", e.toString());
SCNApp.showToast("Import failed", Toast.LENGTH_LONG);
}
}
}
} }

View File

@@ -1,26 +1,44 @@
package com.blackforestbytes.simplecloudnotifier.view; package com.blackforestbytes.simplecloudnotifier.view;
import android.support.annotation.NonNull; import android.content.Intent;
import android.support.v7.widget.RecyclerView; import android.graphics.Color;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import com.blackforestbytes.simplecloudnotifier.R; import com.blackforestbytes.simplecloudnotifier.R;
import com.blackforestbytes.simplecloudnotifier.SCNApp; import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.model.CMessage; import com.blackforestbytes.simplecloudnotifier.model.CMessage;
import com.blackforestbytes.simplecloudnotifier.model.CMessageList; 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 public class MessageAdapter extends RecyclerView.Adapter
{ {
private final View vNoElements; private final View vNoElements;
private final LinearLayoutManager manLayout;
private final RecyclerView viewRecycler;
public MessageAdapter(View noElementsView) private WeakHashMap<MessagePresenter, Boolean> viewHolders = new WeakHashMap<>();
public MessageAdapter(View noElementsView, LinearLayoutManager layout, RecyclerView recycler)
{ {
vNoElements = noElementsView; vNoElements = noElementsView;
manLayout = layout;
viewRecycler = recycler;
CMessageList.inst().register(this); CMessageList.inst().register(this);
vNoElements.setVisibility(getItemCount()>0 ? View.GONE : View.VISIBLE); vNoElements.setVisibility(getItemCount()>0 ? View.GONE : View.VISIBLE);
@@ -37,9 +55,17 @@ public class MessageAdapter extends RecyclerView.Adapter
@Override @Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) 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; 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 @Override
@@ -60,14 +86,39 @@ public class MessageAdapter extends RecyclerView.Adapter
vNoElements.setVisibility(getItemCount()>0 ? View.GONE : View.VISIBLE); 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 tvTimestamp;
private TextView tvTitle; private TextView tvTitle;
private TextView tvMessage; private TextView tvMessage;
private ImageView ivPriority; private ImageView ivPriority;
public RelativeLayout viewForeground;
public RelativeLayout viewBackground;
public MaterialButton btnShare;
public MaterialButton btnDelete;
private CMessage data; private CMessage data;
private int datapos;
MessagePresenter(View itemView) MessagePresenter(View itemView)
{ {
@@ -76,10 +127,33 @@ public class MessageAdapter extends RecyclerView.Adapter
tvTitle = itemView.findViewById(R.id.tvTitle); tvTitle = itemView.findViewById(R.id.tvTitle);
tvMessage = itemView.findViewById(R.id.tvMessage); tvMessage = itemView.findViewById(R.id.tvMessage);
ivPriority = itemView.findViewById(R.id.ivPriority); 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); 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()); tvTimestamp.setText(msg.formatTimestamp());
tvTitle.setText(msg.Title); tvTitle.setText(msg.Title);
@@ -90,23 +164,63 @@ public class MessageAdapter extends RecyclerView.Adapter
case LOW: case LOW:
ivPriority.setVisibility(View.VISIBLE); ivPriority.setVisibility(View.VISIBLE);
ivPriority.setImageResource(R.drawable.priority_low); ivPriority.setImageResource(R.drawable.priority_low);
ivPriority.setColorFilter(Color.rgb(176, 176, 176));
break; break;
case NORMAL: case NORMAL:
ivPriority.setVisibility(View.GONE); ivPriority.setVisibility(View.GONE);
ivPriority.setColorFilter(Color.rgb(176, 176, 176));
break; break;
case HIGH: case HIGH:
ivPriority.setVisibility(View.VISIBLE); ivPriority.setVisibility(View.VISIBLE);
ivPriority.setImageResource(R.drawable.priority_high); ivPriority.setImageResource(R.drawable.priority_high);
ivPriority.setColorFilter(Color.rgb(200, 0, 0));
break; break;
} }
data = msg; 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 @Override
public void onClick(View v) 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,20 +1,34 @@
package com.blackforestbytes.simplecloudnotifier.view; package com.blackforestbytes.simplecloudnotifier.view;
import android.content.Context; import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.blackforestbytes.simplecloudnotifier.R; 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;
public class NotificationsFragment extends Fragment import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class NotificationsFragment extends Fragment implements MessageAdapterTouchHelper.MessageAdapterTouchHelperListener
{ {
private PublisherAdView adView;
private MessageAdapter adpMessages;
public MessageAdapterTouchHelper touchHelper;
public NotificationsFragment() public NotificationsFragment()
{ {
// Required empty public constructor // Required empty public constructor
@@ -26,9 +40,51 @@ public class NotificationsFragment extends Fragment
View v = inflater.inflate(R.layout.fragment_notifications, container, false); View v = inflater.inflate(R.layout.fragment_notifications, container, false);
RecyclerView rvMessages = v.findViewById(R.id.rvMessages); RecyclerView rvMessages = v.findViewById(R.id.rvMessages);
rvMessages.setLayoutManager(new LinearLayoutManager(this.getContext(), RecyclerView.VERTICAL, true)); LinearLayoutManager lman = new LinearLayoutManager(this.getContext(), RecyclerView.VERTICAL, false);
rvMessages.setAdapter(new MessageAdapter(v.findViewById(R.id.tvNoElements))); rvMessages.setLayoutManager(lman);
rvMessages.setAdapter(adpMessages = new MessageAdapter(v.findViewById(R.id.tvNoElements), lman, rvMessages));
ItemTouchHelper.SimpleCallback itemTouchHelperCallback = touchHelper = new MessageAdapterTouchHelper(0, ItemTouchHelper.LEFT, this);
new ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(rvMessages);
adView = v.findViewById(R.id.adBanner);
PublisherAdRequest adRequest = new PublisherAdRequest.Builder().build();
adView.loadAd(adRequest);
adView.setVisibility(SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE);
return v; return v;
} }
public void updateProState()
{
if (adView != null) adView.setVisibility(IABService.inst().getPurchaseCachedSimple(IABService.IAB_PRO_MODE) ? View.GONE : View.VISIBLE);
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction, int position)
{
if (viewHolder instanceof MessageAdapter.MessagePresenter)
{
deleteMessage(viewHolder.getAdapterPosition());
}
}
public void deleteMessage(int pos)
{
final int deletedIndex = pos;
final CMessage deletedItem = adpMessages.removeItem(pos);
String name = deletedItem.Title;
Snackbar snackbar = Snackbar.make(SCNApp.getMainActivity().layoutRoot, name + " removed", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", view -> adpMessages.restoreItem(deletedItem, deletedIndex));
snackbar.setActionTextColor(Color.YELLOW);
snackbar.show();
}
public void updateDeleteSwipeEnabled()
{
if (touchHelper != null) touchHelper.updateEnabled();
}
} }

View File

@@ -1,15 +1,581 @@
package com.blackforestbytes.simplecloudnotifier.view; 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.os.Bundle;
import android.support.v7.preference.PreferenceFragmentCompat; import android.text.Editable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;
import androidx.fragment.app.Fragment;
import com.blackforestbytes.simplecloudnotifier.R; import com.blackforestbytes.simplecloudnotifier.R;
import com.blackforestbytes.simplecloudnotifier.SCNApp;
import com.blackforestbytes.simplecloudnotifier.lib.lambda.FI;
import com.blackforestbytes.simplecloudnotifier.lib.string.Str;
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
import com.blackforestbytes.simplecloudnotifier.service.IABService;
import com.blackforestbytes.simplecloudnotifier.util.TextChangedListener;
public class SettingsFragment extends PreferenceFragmentCompat import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Map;
import top.defaults.colorpicker.ColorPickerPopup;
import xyz.aprildown.ultimatemusicpicker.MusicPickerListener;
import xyz.aprildown.ultimatemusicpicker.UltimateMusicPicker;
public class SettingsFragment extends Fragment implements MusicPickerListener
{ {
@Override private Switch prefAppEnabled;
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) private Spinner prefLocalCacheSize;
private Button prefUpgradeAccount;
private TextView prefUpgradeAccount_msg;
private TextView prefUpgradeAccount_info;
private Switch prefEnableDeleteSwipe;
private EditText prefPreviewLineCount;
private Switch prefMsgLowEnableSound;
private TextView prefMsgLowRingtone_value;
private View prefMsgLowRingtone_container;
private Switch prefMsgLowRepeatSound;
private Switch prefMsgLowEnableLED;
private View prefMsgLowLedColor_container;
private ImageView prefMsgLowLedColor_value;
private Switch prefMsgLowEnableVibrations;
private Switch prefMsgLowForceVolume;
private SeekBar prefMsgLowVolume;
private ImageView prefMsgLowVolumeTest;
private Switch prefMsgNormEnableSound;
private TextView prefMsgNormRingtone_value;
private View prefMsgNormRingtone_container;
private Switch prefMsgNormRepeatSound;
private Switch prefMsgNormEnableLED;
private View prefMsgNormLedColor_container;
private ImageView prefMsgNormLedColor_value;
private Switch prefMsgNormEnableVibrations;
private Switch prefMsgNormForceVolume;
private SeekBar prefMsgNormVolume;
private ImageView prefMsgNormVolumeTest;
private Switch prefMsgHighEnableSound;
private TextView prefMsgHighRingtone_value;
private View prefMsgHighRingtone_container;
private Switch prefMsgHighRepeatSound;
private Switch prefMsgHighEnableLED;
private View prefMsgHighLedColor_container;
private ImageView prefMsgHighLedColor_value;
private Switch prefMsgHighEnableVibrations;
private Switch prefMsgHighForceVolume;
private SeekBar prefMsgHighVolume;
private ImageView prefMsgHighVolumeTest;
private Button prefBtnImport;
private Button prefBtnExport;
private int musicPickerSwitch = -1;
private MediaPlayer[] mPlayers = new MediaPlayer[3];
public SettingsFragment()
{ {
setPreferencesFromResource(R.xml.preferences, rootKey); // Required empty public constructor
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.fragment_settings, container, false);
initFields(v);
updateUI();
initListener();
return v;
}
private void initFields(View v)
{
prefAppEnabled = v.findViewById(R.id.prefAppEnabled);
prefLocalCacheSize = v.findViewById(R.id.prefLocalCacheSize);
prefUpgradeAccount = v.findViewById(R.id.prefUpgradeAccount);
prefUpgradeAccount_msg = v.findViewById(R.id.prefUpgradeAccount2);
prefUpgradeAccount_info = v.findViewById(R.id.prefUpgradeAccount_info);
prefEnableDeleteSwipe = v.findViewById(R.id.prefEnableDeleteSwipe);
prefPreviewLineCount = v.findViewById(R.id.prefPreviewLineCount);
prefMsgLowEnableSound = v.findViewById(R.id.prefMsgLowEnableSound);
prefMsgLowRingtone_value = v.findViewById(R.id.prefMsgLowRingtone_value);
prefMsgLowRingtone_container = v.findViewById(R.id.prefMsgLowRingtone_container);
prefMsgLowRepeatSound = v.findViewById(R.id.prefMsgLowRepeatSound);
prefMsgLowEnableLED = v.findViewById(R.id.prefMsgLowEnableLED);
prefMsgLowLedColor_value = v.findViewById(R.id.prefMsgLowLedColor_value);
prefMsgLowLedColor_container = v.findViewById(R.id.prefMsgLowLedColor_container);
prefMsgLowEnableVibrations = v.findViewById(R.id.prefMsgLowEnableVibrations);
prefMsgLowForceVolume = v.findViewById(R.id.prefMsgLowForceVolume);
prefMsgLowVolume = v.findViewById(R.id.prefMsgLowVolume);
prefMsgLowVolumeTest = v.findViewById(R.id.btnLowVolumeTest);
prefMsgNormEnableSound = v.findViewById(R.id.prefMsgNormEnableSound);
prefMsgNormRingtone_value = v.findViewById(R.id.prefMsgNormRingtone_value);
prefMsgNormRingtone_container = v.findViewById(R.id.prefMsgNormRingtone_container);
prefMsgNormRepeatSound = v.findViewById(R.id.prefMsgNormRepeatSound);
prefMsgNormEnableLED = v.findViewById(R.id.prefMsgNormEnableLED);
prefMsgNormLedColor_value = v.findViewById(R.id.prefMsgNormLedColor_value);
prefMsgNormLedColor_container = v.findViewById(R.id.prefMsgNormLedColor_container);
prefMsgNormEnableVibrations = v.findViewById(R.id.prefMsgNormEnableVibrations);
prefMsgNormForceVolume = v.findViewById(R.id.prefMsgNormForceVolume);
prefMsgNormVolume = v.findViewById(R.id.prefMsgNormVolume);
prefMsgNormVolumeTest = v.findViewById(R.id.btnNormVolumeTest);
prefMsgHighEnableSound = v.findViewById(R.id.prefMsgHighEnableSound);
prefMsgHighRingtone_value = v.findViewById(R.id.prefMsgHighRingtone_value);
prefMsgHighRingtone_container = v.findViewById(R.id.prefMsgHighRingtone_container);
prefMsgHighRepeatSound = v.findViewById(R.id.prefMsgHighRepeatSound);
prefMsgHighEnableLED = v.findViewById(R.id.prefMsgHighEnableLED);
prefMsgHighLedColor_value = v.findViewById(R.id.prefMsgHighLedColor_value);
prefMsgHighLedColor_container = v.findViewById(R.id.prefMsgHighLedColor_container);
prefMsgHighEnableVibrations = v.findViewById(R.id.prefMsgHighEnableVibrations);
prefMsgHighForceVolume = v.findViewById(R.id.prefMsgHighForceVolume);
prefMsgHighVolume = v.findViewById(R.id.prefMsgHighVolume);
prefMsgHighVolumeTest = v.findViewById(R.id.btnHighVolumeTest);
prefBtnExport = v.findViewById(R.id.prefExport);
prefBtnImport = v.findViewById(R.id.prefImport);
ArrayAdapter<Integer> plcsa = new ArrayAdapter<>(v.getContext(), android.R.layout.simple_spinner_item, SCNSettings.CHOOSABLE_CACHE_SIZES);
plcsa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
prefLocalCacheSize.setAdapter(plcsa);
}
@SuppressLint("SetTextI18n")
private void updateUI()
{
SCNSettings s = SCNSettings.inst();
Context c = getContext();
if (c == null) return;
if (prefAppEnabled.isChecked() != s.Enabled) prefAppEnabled.setChecked(s.Enabled);
if (prefEnableDeleteSwipe.isChecked() != s.EnableDeleteSwipe) prefEnableDeleteSwipe.setChecked(s.EnableDeleteSwipe);
if (!prefPreviewLineCount.getText().toString().equals(Integer.toString(s.PreviewLineCount))) prefPreviewLineCount.setText(Integer.toString(s.PreviewLineCount));
prefUpgradeAccount.setVisibility( SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE);
prefUpgradeAccount_info.setVisibility(SCNSettings.inst().promode_local ? View.GONE : View.VISIBLE);
prefUpgradeAccount_msg.setVisibility( SCNSettings.inst().promode_local ? View.VISIBLE : View.GONE );
if (prefLocalCacheSize.getSelectedItemPosition() != getCacheSizeIndex(s.LocalCacheSize)) prefLocalCacheSize.setSelection(getCacheSizeIndex(s.LocalCacheSize));
if (prefMsgLowEnableSound.isChecked() != s.PriorityLow.EnableSound) prefMsgLowEnableSound.setChecked(s.PriorityLow.EnableSound);
if (!prefMsgLowRingtone_value.getText().equals(s.PriorityLow.SoundName)) prefMsgLowRingtone_value.setText(s.PriorityLow.SoundName);
if (prefMsgLowRepeatSound.isChecked() != s.PriorityLow.RepeatSound) prefMsgLowRepeatSound.setChecked(s.PriorityLow.RepeatSound);
if (prefMsgLowEnableLED.isChecked() != s.PriorityLow.EnableLED) prefMsgLowEnableLED.setChecked(s.PriorityLow.EnableLED);
prefMsgLowLedColor_value.setColorFilter(s.PriorityLow.LEDColor);
if (prefMsgLowEnableVibrations.isChecked() != s.PriorityLow.EnableVibration) prefMsgLowEnableVibrations.setChecked(s.PriorityLow.EnableVibration);
if (prefMsgLowForceVolume.isChecked() != s.PriorityLow.ForceVolume) prefMsgLowForceVolume.setChecked(s.PriorityLow.ForceVolume);
if (prefMsgLowVolume.getMax() != 100) prefMsgLowVolume.setMax(100);
if (prefMsgLowVolume.getProgress() != s.PriorityLow.ForceVolumeValue) prefMsgLowVolume.setProgress(s.PriorityLow.ForceVolumeValue);
if (prefMsgLowVolume.isEnabled() != s.PriorityLow.ForceVolume) prefMsgLowVolume.setEnabled(s.PriorityLow.ForceVolume);
if (prefMsgLowVolumeTest.isEnabled() != s.PriorityLow.ForceVolume) prefMsgLowVolumeTest.setEnabled(s.PriorityLow.ForceVolume);
if (s.PriorityLow.ForceVolume) prefMsgLowVolumeTest.setColorFilter(null); else prefMsgLowVolumeTest.setColorFilter(Color.argb(150,200,200,200));
if (prefMsgNormEnableSound.isChecked() != s.PriorityNorm.EnableSound) prefMsgNormEnableSound.setChecked(s.PriorityNorm.EnableSound);
if (!prefMsgNormRingtone_value.getText().equals(s.PriorityNorm.SoundName)) prefMsgNormRingtone_value.setText(s.PriorityNorm.SoundName);
if (prefMsgNormRepeatSound.isChecked() != s.PriorityNorm.RepeatSound) prefMsgNormRepeatSound.setChecked(s.PriorityNorm.RepeatSound);
if (prefMsgNormEnableLED.isChecked() != s.PriorityNorm.EnableLED) prefMsgNormEnableLED.setChecked(s.PriorityNorm.EnableLED);
prefMsgNormLedColor_value.setColorFilter(s.PriorityNorm.LEDColor);
if (prefMsgNormEnableVibrations.isChecked() != s.PriorityNorm.EnableVibration) prefMsgNormEnableVibrations.setChecked(s.PriorityNorm.EnableVibration);
if (prefMsgNormForceVolume.isChecked() != s.PriorityNorm.ForceVolume) prefMsgNormForceVolume.setChecked(s.PriorityNorm.ForceVolume);
if (prefMsgNormVolume.getMax() != 100) prefMsgNormVolume.setMax(100);
if (prefMsgNormVolume.getProgress() != s.PriorityNorm.ForceVolumeValue) prefMsgNormVolume.setProgress(s.PriorityNorm.ForceVolumeValue);
if (prefMsgNormVolume.isEnabled() != s.PriorityNorm.ForceVolume) prefMsgNormVolume.setEnabled(s.PriorityNorm.ForceVolume);
if (prefMsgNormVolumeTest.isEnabled() != s.PriorityNorm.ForceVolume) prefMsgNormVolumeTest.setEnabled(s.PriorityNorm.ForceVolume);
if (s.PriorityNorm.ForceVolume) prefMsgNormVolumeTest.setColorFilter(null); else prefMsgNormVolumeTest.setColorFilter(Color.argb(150,200,200,200));
if (prefMsgHighEnableSound.isChecked() != s.PriorityHigh.EnableSound) prefMsgHighEnableSound.setChecked(s.PriorityHigh.EnableSound);
if (!prefMsgHighRingtone_value.getText().equals(s.PriorityHigh.SoundName)) prefMsgHighRingtone_value.setText(s.PriorityHigh.SoundName);
if (prefMsgHighRepeatSound.isChecked() != s.PriorityHigh.RepeatSound) prefMsgHighRepeatSound.setChecked(s.PriorityHigh.RepeatSound);
if (prefMsgHighEnableLED.isChecked() != s.PriorityHigh.EnableLED) prefMsgHighEnableLED.setChecked(s.PriorityHigh.EnableLED);
prefMsgHighLedColor_value.setColorFilter(s.PriorityHigh.LEDColor);
if (prefMsgHighEnableVibrations.isChecked() != s.PriorityHigh.EnableVibration) prefMsgHighEnableVibrations.setChecked(s.PriorityHigh.EnableVibration);
if (prefMsgHighForceVolume.isChecked() != s.PriorityHigh.ForceVolume) prefMsgHighForceVolume.setChecked(s.PriorityHigh.ForceVolume);
if (prefMsgHighVolume.getMax() != 100) prefMsgHighVolume.setMax(100);
if (prefMsgHighVolume.getProgress() != s.PriorityHigh.ForceVolumeValue) prefMsgHighVolume.setProgress(s.PriorityHigh.ForceVolumeValue);
if (prefMsgHighVolume.isEnabled() != s.PriorityHigh.ForceVolume) prefMsgHighVolume.setEnabled(s.PriorityHigh.ForceVolume);
if (prefMsgHighVolumeTest.isEnabled() != s.PriorityHigh.ForceVolume) prefMsgHighVolumeTest.setEnabled(s.PriorityHigh.ForceVolume);
if (s.PriorityHigh.ForceVolume) prefMsgHighVolumeTest.setColorFilter(null); else prefMsgHighVolumeTest.setColorFilter(Color.argb(150,200,200,200));
}
private void initListener()
{
SCNSettings s = SCNSettings.inst();
prefAppEnabled.setOnCheckedChangeListener((a,b) -> { boolean prev=s.Enabled; s.Enabled=b; saveAndUpdate(); updateEnabled(prev, b); });
prefEnableDeleteSwipe.setOnCheckedChangeListener((a,b) -> { s.EnableDeleteSwipe=b; saveAndUpdate(); });
prefPreviewLineCount.addTextChangedListener(new TextChangedListener<EditText>(prefPreviewLineCount) {
@Override
public void onTextChanged(EditText target, Editable ed) {
if (!ed.toString().isEmpty()) try { s.PreviewLineCount=Integer.parseInt(ed.toString()); saveAndUpdate(); } catch (Exception e) { /* */ }
}
});
prefLocalCacheSize.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
{
@Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
s.LocalCacheSize = prefLocalCacheSize.getSelectedItemPosition()>=0 ? SCNSettings.CHOOSABLE_CACHE_SIZES[prefLocalCacheSize.getSelectedItemPosition()] : 100;
saveAndUpdate();
}
@Override public void onNothingSelected(AdapterView<?> parent) { /* */ }
});
prefUpgradeAccount.setOnClickListener(a -> onUpgradeAccount());
prefBtnExport.setOnClickListener(a -> onExport());
prefBtnImport.setOnClickListener(a -> onImport());
prefMsgLowEnableSound.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.EnableSound=b; saveAndUpdate(); });
prefMsgLowRingtone_container.setOnClickListener(a -> chooseRingtoneLow());
prefMsgLowRepeatSound.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.RepeatSound=b; saveAndUpdate(); });
prefMsgLowEnableLED.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.EnableLED=b; saveAndUpdate(); });
prefMsgLowLedColor_container.setOnClickListener(a -> chooseLEDColorLow());
prefMsgLowEnableVibrations.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.EnableVibration=b; saveAndUpdate(); });
prefMsgLowForceVolume.setOnCheckedChangeListener((a,b) -> { s.PriorityLow.ForceVolume=b; saveAndUpdate(); });
prefMsgLowVolume.setOnSeekBarChangeListener(FI.SeekBarChanged((a,b,c) -> { if (c) { s.PriorityLow.ForceVolumeValue=b; saveAndUpdate(); updateVolume(0, b); } }));
prefMsgLowVolumeTest.setOnClickListener((v) -> { if (s.PriorityLow.ForceVolume) playTestSound(0, prefMsgLowVolumeTest, s.PriorityLow.SoundSource, s.PriorityLow.ForceVolumeValue); });
prefMsgNormEnableSound.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.EnableSound=b; saveAndUpdate(); });
prefMsgNormRingtone_container.setOnClickListener(a -> chooseRingtoneNorm());
prefMsgNormRepeatSound.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.RepeatSound=b; saveAndUpdate(); });
prefMsgNormEnableLED.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.EnableLED=b; saveAndUpdate(); });
prefMsgNormLedColor_container.setOnClickListener(a -> chooseLEDColorNorm());
prefMsgNormEnableVibrations.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.EnableVibration=b; saveAndUpdate(); });
prefMsgNormForceVolume.setOnCheckedChangeListener((a,b) -> { s.PriorityNorm.ForceVolume=b; saveAndUpdate(); });
prefMsgNormVolume.setOnSeekBarChangeListener(FI.SeekBarChanged((a,b,c) -> { if (c) { s.PriorityNorm.ForceVolumeValue=b; saveAndUpdate(); updateVolume(1, b); } }));
prefMsgNormVolumeTest.setOnClickListener((v) -> { if (s.PriorityNorm.ForceVolume) playTestSound(1, prefMsgNormVolumeTest, s.PriorityNorm.SoundSource, s.PriorityNorm.ForceVolumeValue); });
prefMsgHighEnableSound.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.EnableSound=b; saveAndUpdate(); });
prefMsgHighRingtone_container.setOnClickListener(a -> chooseRingtoneHigh());
prefMsgHighRepeatSound.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.RepeatSound=b; saveAndUpdate(); });
prefMsgHighEnableLED.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.EnableLED=b; saveAndUpdate(); });
prefMsgHighLedColor_container.setOnClickListener(a -> chooseLEDColorHigh());
prefMsgHighEnableVibrations.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.EnableVibration=b; saveAndUpdate(); });
prefMsgHighForceVolume.setOnCheckedChangeListener((a,b) -> { s.PriorityHigh.ForceVolume=b; saveAndUpdate(); });
prefMsgHighVolume.setOnSeekBarChangeListener(FI.SeekBarChanged((a,b,c) -> { if (c) { s.PriorityHigh.ForceVolumeValue=b; saveAndUpdate(); updateVolume(2, b); } }));
prefMsgHighVolumeTest.setOnClickListener((v) -> { if (s.PriorityHigh.ForceVolume) playTestSound(2, prefMsgHighVolumeTest, s.PriorityHigh.SoundSource, s.PriorityHigh.ForceVolumeValue); });
}
private void onExport()
{
Context ctxt = getContext();
if (ctxt == null) return;
try
{
File outputDir = ctxt.getCacheDir(); // context being the Activity pointer
File outputFile = File.createTempFile("scn_export_", ".dat", outputDir);
ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream(outputFile));
Map<String, ?> d1 = ctxt.getSharedPreferences("Config", Context.MODE_PRIVATE).getAll();
Map<String, ?> d2 = ctxt.getSharedPreferences("IAB", Context.MODE_PRIVATE).getAll();
Map<String, ?> d3 = ctxt.getSharedPreferences("CMessageList", Context.MODE_PRIVATE).getAll();
Map<String, ?> d4 = ctxt.getSharedPreferences("QueryLog", Context.MODE_PRIVATE).getAll();
output.writeObject(d1);
output.writeObject(d2);
output.writeObject(d3);
output.writeObject(d4);
Intent intent = new Intent(Intent.ACTION_SEND);
Uri uri = FileProvider.getUriForFile(ctxt, "com.blackforestbytes.simplecloudnotifier.fileprovider", outputFile);
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setType("*/*");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(intent, "Export"));
}
catch (IOException e)
{
Log.e("Export:Err", e.toString());
SCNApp.showToast("Export failed", Toast.LENGTH_LONG);
}
}
private void onImport()
{
SCNApp.getMainActivity().setContentView(R.layout.activity_main);
Intent intent = new Intent()
.setType("*/*")
.setAction(Intent.ACTION_GET_CONTENT);
((MainActivity)getActivity()).startActivityForResult(Intent.createChooser(intent, "Select a file"), 1991);
}
private void updateEnabled(boolean prev, boolean now)
{
if (!prev && now)
{
SCNApp.showToast("SimpleCloudNotifier is now enabled", Toast.LENGTH_SHORT);
}
else if (prev && !now)
{
SCNApp.showToast("SimpleCloudNotifier is now disabled\nYou won't recieve new messages.", Toast.LENGTH_LONG);
}
}
private void updateVolume(int idx, int volume)
{
if (mPlayers[idx] != null && mPlayers[idx].isPlaying())
{
AudioManager aman = (AudioManager) SCNApp.getContext().getSystemService(Context.AUDIO_SERVICE);
int maxVolume = aman.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION);
aman.setStreamVolume(AudioManager.STREAM_NOTIFICATION, (int)(maxVolume * (volume / 100.0)), 0);
}
}
private void stopSound(final int idx, final ImageView iv)
{
if (mPlayers[idx] != null && mPlayers[idx].isPlaying())
{
mPlayers[idx].stop();
mPlayers[idx].release();
iv.setImageResource(R.drawable.ic_play);
mPlayers[idx] = null;
}
}
private void playTestSound(final int idx, final ImageView iv, String src, int volume)
{
if (mPlayers[idx] != null && mPlayers[idx].isPlaying())
{
mPlayers[idx].stop();
mPlayers[idx].release();
iv.setImageResource(R.drawable.ic_play);
mPlayers[idx] = null;
return;
}
if (Str.isNullOrWhitespace(src)) return;
if (volume == 0) return;
Context ctxt = getContext();
if (ctxt == null) return;
iv.setImageResource(R.drawable.ic_pause);
AudioManager aman = (AudioManager) SCNApp.getContext().getSystemService(Context.AUDIO_SERVICE);
int maxVolume = aman.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION);
aman.setStreamVolume(AudioManager.STREAM_NOTIFICATION, (int)(maxVolume * (volume / 100.0)), 0);
MediaPlayer player = mPlayers[idx] = new MediaPlayer();
player.setAudioAttributes(new AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_NOTIFICATION).build());
player.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
try
{
player.setDataSource(ctxt, Uri.parse(src));
player.setLooping(false);
player.setOnCompletionListener( mp -> SCNApp.runOnUiThread(() -> { mp.stop(); iv.setImageResource(R.drawable.ic_play); mPlayers[idx]=null; mp.release(); }));
player.setOnSeekCompleteListener(mp -> SCNApp.runOnUiThread(() -> { mp.stop(); iv.setImageResource(R.drawable.ic_play); mPlayers[idx]=null; mp.release(); }));
player.prepare();
player.start();
}
catch (IOException e)
{
Log.e("SFRAG:play", e.toString());
}
}
private void saveAndUpdate()
{
SCNSettings.inst().save();
updateUI();
SCNApp.getMainActivity().adpTabs.tab1.updateDeleteSwipeEnabled();
}
private void onUpgradeAccount()
{
IABService.inst().purchase(getActivity(), IABService.IAB_PRO_MODE);
}
public void updateProState()
{
boolean pmode = IABService.inst().getPurchaseCachedSimple(IABService.IAB_PRO_MODE);
if (prefUpgradeAccount != null) prefUpgradeAccount.setVisibility( pmode ? View.GONE : View.VISIBLE);
if (prefUpgradeAccount_info != null) prefUpgradeAccount_info.setVisibility(pmode ? View.GONE : View.VISIBLE);
if (prefUpgradeAccount_msg != null) prefUpgradeAccount_msg.setVisibility( pmode ? View.VISIBLE : View.GONE );
}
private int getCacheSizeIndex(int value)
{
for (int i = 0; i < SCNSettings.CHOOSABLE_CACHE_SIZES.length; i++)
{
if (SCNSettings.CHOOSABLE_CACHE_SIZES[i] == value) return i;
}
return 2;
}
private void chooseRingtoneLow()
{
musicPickerSwitch = 1;
UltimateMusicPicker ump = new UltimateMusicPicker();
ump.windowTitle("Choose notification sound");
ump.removeSilent();
ump.streamType(AudioManager.STREAM_NOTIFICATION);
ump.ringtone();
ump.notification();
ump.alarm();
ump.music();
if (!SCNSettings.inst().PriorityLow.SoundSource.isEmpty())ump.selectUri(Uri.parse(SCNSettings.inst().PriorityLow.SoundSource));
ump.goWithDialog(getChildFragmentManager());
}
private void chooseRingtoneNorm()
{
musicPickerSwitch = 2;
UltimateMusicPicker ump = new UltimateMusicPicker();
ump.windowTitle("Choose notification sound");
ump.removeSilent();
ump.streamType(AudioManager.STREAM_NOTIFICATION);
ump.ringtone();
ump.notification();
ump.alarm();
ump.music();
if (!SCNSettings.inst().PriorityNorm.SoundSource.isEmpty())ump.defaultUri(Uri.parse(SCNSettings.inst().PriorityNorm.SoundSource));
ump.goWithDialog(getChildFragmentManager());
}
private void chooseRingtoneHigh()
{
musicPickerSwitch = 3;
UltimateMusicPicker ump = new UltimateMusicPicker();
ump.windowTitle("Choose notification sound");
ump.removeSilent();
ump.streamType(AudioManager.STREAM_NOTIFICATION);
ump.ringtone();
ump.notification();
ump.alarm();
ump.music();
if (!SCNSettings.inst().PriorityHigh.SoundSource.isEmpty())ump.defaultUri(Uri.parse(SCNSettings.inst().PriorityHigh.SoundSource));
ump.goWithDialog(getChildFragmentManager());
}
private void chooseLEDColorLow()
{
new ColorPickerPopup.Builder(getContext())
.initialColor(SCNSettings.inst().PriorityLow.LEDColor) // Set initial color
.enableBrightness(true) // Enable brightness slider or not
.okTitle("Choose")
.cancelTitle("Cancel")
.showIndicator(true)
.showValue(false)
.build()
.show(getView(), new ColorPickerPopup.ColorPickerObserver()
{
@Override
public void onColorPicked(int color) {
SCNSettings.inst().PriorityLow.LEDColor = color;
saveAndUpdate();
}
@Override
public void onColor(int color, boolean fromUser) { }
});
}
private void chooseLEDColorNorm()
{
new ColorPickerPopup.Builder(getContext())
.initialColor(SCNSettings.inst().PriorityNorm.LEDColor) // Set initial color
.enableBrightness(true) // Enable brightness slider or not
.okTitle("Choose")
.cancelTitle("Cancel")
.showIndicator(true)
.showValue(false)
.build()
.show(getView(), new ColorPickerPopup.ColorPickerObserver()
{
@Override
public void onColorPicked(int color) {
SCNSettings.inst().PriorityNorm.LEDColor = color;
saveAndUpdate();
}
@Override
public void onColor(int color, boolean fromUser) { }
});
}
private void chooseLEDColorHigh()
{
new ColorPickerPopup.Builder(getContext())
.initialColor(SCNSettings.inst().PriorityHigh.LEDColor) // Set initial color
.enableBrightness(true) // Enable brightness slider or not
.okTitle("Choose")
.cancelTitle("Cancel")
.showIndicator(true)
.showValue(false)
.build()
.show(getView(), new ColorPickerPopup.ColorPickerObserver()
{
@Override
public void onColorPicked(int color) {
SCNSettings.inst().PriorityHigh.LEDColor = color;
saveAndUpdate();
}
@Override
public void onColor(int color, boolean fromUser) { }
});
}
@Override
public void onMusicPick(@NotNull Uri uri, @NotNull String s)
{
if (musicPickerSwitch == 1) { SCNSettings.inst().PriorityLow.SoundSource =uri.toString(); SCNSettings.inst().PriorityLow.SoundName =s; saveAndUpdate(); }
if (musicPickerSwitch == 2) { SCNSettings.inst().PriorityNorm.SoundSource=uri.toString(); SCNSettings.inst().PriorityNorm.SoundName=s; saveAndUpdate(); }
if (musicPickerSwitch == 3) { SCNSettings.inst().PriorityHigh.SoundSource=uri.toString(); SCNSettings.inst().PriorityHigh.SoundName=s; saveAndUpdate(); }
musicPickerSwitch = -1;
}
@Override
public void onPickCanceled()
{
musicPickerSwitch = -1;
}
public void onViewpagerHide()
{
stopSound(0, prefMsgLowVolumeTest);
stopSound(1, prefMsgNormVolumeTest);
stopSound(2, prefMsgHighVolumeTest);
} }
} }

View File

@@ -1,8 +1,8 @@
package com.blackforestbytes.simplecloudnotifier.view; package com.blackforestbytes.simplecloudnotifier.view;
import android.support.v4.app.Fragment; import androidx.fragment.app.Fragment;
import android.support.v4.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter; import androidx.fragment.app.FragmentStatePagerAdapter;
public class TabAdapter extends FragmentStatePagerAdapter { public class TabAdapter extends FragmentStatePagerAdapter {
@@ -35,7 +35,7 @@ public class TabAdapter extends FragmentStatePagerAdapter {
{ {
switch (position) switch (position)
{ {
case 0: return "Notifications"; case 0: return "Messages";
case 1: return "Account"; case 1: return "Account";
case 2: return "Settings"; case 2: return "Settings";
default: return null; 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

@@ -0,0 +1,6 @@
<vector android:height="24dp" android:viewportHeight="1000"
android:viewportWidth="1000" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000"
android:pathData="M500,500m-500,0a500,500 0,1 1,1000 0a500,500 0,1 1,-1000 0"
android:strokeColor="#000000" android:strokeWidth="1"/>
</vector>

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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 114 KiB

View File

@@ -1,32 +1,33 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout <RelativeLayout android:id="@+id/layoutRoot"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:showIn="@layout/activity_main"> xmlns:app="http://schemas.android.com/apk/res-auto">
<android.support.v7.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
app:titleTextColor="@color/colorOnPrimary"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
android:elevation="6dp" android:elevation="6dp"
android:minHeight="?attr/actionBarSize" android:minHeight="?attr/actionBarSize" />
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<android.support.design.widget.TabLayout <com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout" android:id="@+id/tab_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/toolbar" android:layout_below="@+id/toolbar"
app:titleTextColor="@color/colorOnPrimary"
app:tabTextColor="@color/colorOnPrimary"
app:tabSelectedTextColor="@color/colorSecondary"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
android:elevation="6dp" android:elevation="6dp"
android:minHeight="?attr/actionBarSize" android:minHeight="?attr/actionBarSize" />
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
<android.support.v4.view.ViewPager <androidx.viewpager.widget.ViewPager
android:id="@+id/pager" android:id="@+id/pager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"

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

@@ -7,7 +7,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".view.AccountFragment"> tools:context=".view.AccountFragment">
<android.support.constraint.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@@ -198,25 +198,33 @@
app:layout_constraintBottom_toTopOf="@+id/btnAccountReset" app:layout_constraintBottom_toTopOf="@+id/btnAccountReset"
app:layout_constraintTop_toBottomOf="@+id/ic_img_quota" /> app:layout_constraintTop_toBottomOf="@+id/ic_img_quota" />
<Button <com.google.android.material.button.MaterialButton
android:id="@+id/btnAccountReset" android:id="@+id/btnAccountReset"
app:cornerRadius="0dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:backgroundTint="#fa315b"
android:text="@string/str_reset_account" android:text="@string/str_reset_account"
app:layout_constraintBottom_toTopOf="@+id/btnClearLocalStorage" /> app:layout_constraintBottom_toTopOf="@+id/btnClearLocalStorage" />
<Button <com.google.android.material.button.MaterialButton
android:id="@+id/btnClearLocalStorage" android:id="@+id/btnClearLocalStorage"
app:cornerRadius="0dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Clear Messages" android:text="Clear Messages"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:backgroundTint="#607D8B"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
tools:layout_editor_absoluteY="352dp" /> tools:layout_editor_absoluteY="352dp" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/loadingPanel" android:id="@+id/loadingPanel"

View File

@@ -3,9 +3,22 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:ads="http://schemas.android.com/apk/res-auto"
tools:context=".view.NotificationsFragment"> tools:context=".view.NotificationsFragment">
<android.support.v7.widget.RecyclerView <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/pnlMessages"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/adBanner"
android:layout_width="match_parent"
android:layout_height="0dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvMessages" android:id="@+id/rvMessages"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -19,4 +32,20 @@
android:text="@string/no_notifications" android:text="@string/no_notifications"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</RelativeLayout>
<com.google.android.gms.ads.doubleclick.PublisherAdView
android:id="@+id/adBanner"
app:layout_constraintTop_toBottomOf="@+id/pnlMessages"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
ads:adSize="SMART_BANNER"
ads:adUnitId="ca-app-pub-3320562328966175/5524654300" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout> </FrameLayout>

View File

@@ -0,0 +1,828 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.SettingsFragment">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:background="@color/colorHeader"
android:textColor="@color/colorHeaderForeground"
android:textSize="16sp"
android:padding="4dp"
android:text="@string/str_common_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Switch
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:id="@+id/prefAppEnabled"
android:text="@string/str_enabled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp" />
<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">
<TextView
android:id="@+id/tvLocalCacheSize"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/str_localcachesize"
android:textColor="#000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/prefLocalCacheSize"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Spinner
android:minWidth="64dp"
android:id="@+id/prefLocalCacheSize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:spinnerMode="dialog"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<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">
<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" />
<TextView
android:id="@+id/prefUpgradeAccount_info"
android:textAlignment="center"
android:text="@string/str_promode_info"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/prefUpgradeAccount2"
android:textColor="#FF4D00"
android:textStyle="bold"
android:textAlignment="center"
android:text="@string/str_promode"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:background="@color/colorHeader"
android:textColor="@color/colorHeaderForeground"
android:textSize="16sp"
android:padding="4dp"
android:text="@string/str_header_prio0"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Switch
android:id="@+id/prefMsgLowEnableSound"
android:text="@string/str_msg_enablesound"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<LinearLayout
android:id="@+id/prefMsgLowRingtone_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp"
android:gravity="center">
<TextView
android:id="@+id/tvMsgLowRingtone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/str_notificationsound"
android:textColor="#000" />
<TextView
android:id="@+id/prefMsgLowRingtone_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="64dp"
android:spinnerMode="dialog"
android:text="Whatever"
tools:ignore="HardcodedText" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<Switch
android:id="@+id/prefMsgLowRepeatSound"
android:text="@string/str_repeatnotificationsound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<Switch
android:id="@+id/prefMsgLowForceVolume"
android:text="@string/str_forcevolume"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:orientation="horizontal"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp">
<ImageView
android:id="@+id/icnLowVolume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/volume_icon"
android:src="@drawable/ic_volume"
app:layout_constraintStart_toStartOf="parent" />
<SeekBar
android:id="@+id/prefMsgLowVolume"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_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
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<Switch
android:id="@+id/prefMsgLowEnableLED"
android:text="@string/str_enable_led"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<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:id="@+id/prefMsgLowLedColor_container"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/str_ledcolor"
android:textColor="#000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/prefMsgLowLedColor_value"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:src="@drawable/circle"
android:layout_marginEnd="4dp"
android:id="@+id/prefMsgLowLedColor_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:spinnerMode="dialog"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<Switch
android:id="@+id/prefMsgLowEnableVibrations"
android:text="@string/str_enable_vibration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:background="@color/colorHeader"
android:textColor="@color/colorHeaderForeground"
android:textSize="16sp"
android:padding="4dp"
android:text="@string/str_header_prio1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Switch
android:id="@+id/prefMsgNormEnableSound"
android:text="@string/str_msg_enablesound"
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"/>
<LinearLayout
android:id="@+id/prefMsgNormRingtone_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp"
android:gravity="center">
<TextView
android:id="@+id/tvMsgNormRingtone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/str_notificationsound"
android:textColor="#000" />
<TextView
android:id="@+id/prefMsgNormRingtone_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="64dp"
android:spinnerMode="dialog"
android:text="Whatever"
tools:ignore="HardcodedText" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<Switch
android:id="@+id/prefMsgNormForceVolume"
android:text="@string/str_forcevolume"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:orientation="horizontal"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp">
<ImageView
android:id="@+id/icnNormVolume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/volume_icon"
android:src="@drawable/ic_volume"
app:layout_constraintStart_toStartOf="parent" />
<SeekBar
android:id="@+id/prefMsgNormVolume"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_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
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<Switch
android:id="@+id/prefMsgNormRepeatSound"
android:text="@string/str_repeatnotificationsound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<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/prefMsgNormEnableLED"
android:text="@string/str_enable_led"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<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:id="@+id/prefMsgNormLedColor_container"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/str_ledcolor"
android:textColor="#000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/prefMsgNormLedColor_value"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:src="@drawable/circle"
android:layout_marginEnd="4dp"
android:id="@+id/prefMsgNormLedColor_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:spinnerMode="dialog"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<Switch
android:id="@+id/prefMsgNormEnableVibrations"
android:text="@string/str_enable_vibration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:background="@color/colorHeader"
android:textColor="@color/colorHeaderForeground"
android:textSize="16sp"
android:padding="4dp"
android:text="@string/str_header_prio2"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Switch
android:id="@+id/prefMsgHighEnableSound"
android:text="@string/str_msg_enablesound"
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"/>
<LinearLayout
android:id="@+id/prefMsgHighRingtone_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp"
android:orientation="vertical"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tvMsgHighRingtone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/str_notificationsound"
android:textColor="#000" />
<TextView
android:id="@+id/prefMsgHighRingtone_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="64dp"
android:spinnerMode="dialog"
android:text="Whatever"
tools:ignore="HardcodedText" />
</LinearLayout>
<Switch
android:id="@+id/prefMsgHighForceVolume"
android:text="@string/str_forcevolume"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:orientation="horizontal"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp">
<ImageView
android:id="@+id/icnHighVolume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/volume_icon"
android:src="@drawable/ic_volume"
app:layout_constraintStart_toStartOf="parent" />
<SeekBar
android:id="@+id/prefMsgHighVolume"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_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
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<Switch
android:id="@+id/prefMsgHighRepeatSound"
android:text="@string/str_repeatnotificationsound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<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/prefMsgHighEnableLED"
android:text="@string/str_enable_led"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:minHeight="48dp" />
<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:id="@+id/prefMsgHighLedColor_container"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/str_ledcolor"
android:textColor="#000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/prefMsgHighLedColor_value"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:src="@drawable/circle"
android:layout_marginEnd="4dp"
android:id="@+id/prefMsgHighLedColor_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:spinnerMode="dialog"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:background="#c0c0c0"/>
<Switch
android:id="@+id/prefMsgHighEnableVibrations"
android:text="@string/str_enable_vibration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
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,10 +1,44 @@
<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" xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<android.support.v7.widget.CardView <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:id="@+id/card_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -14,7 +48,7 @@
card_view:cardCornerRadius="@dimen/card_album_radius"> card_view:cardCornerRadius="@dimen/card_album_radius">
<android.support.constraint.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:background="#FFFFFFFF" android:background="#FFFFFFFF"
android:layout_margin="3dp" android:layout_margin="3dp"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -55,14 +89,15 @@
app:layout_constraintRight_toLeftOf="@+id/ivPriority" app:layout_constraintRight_toLeftOf="@+id/ivPriority"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
android:layout_margin="4sp" android:layout_margin="4sp"
android:ellipsize="none" android:ellipsize="end"
android:maxLines="32" android:maxLines="6"
android:scrollHorizontally="false" android:scrollHorizontally="false"
android:text="asdasd asdasd asdasd a" /> android:text="asdasd asdasd asdasd a" />
<ImageView <ImageView
android:id="@+id/ivPriority" android:id="@+id/ivPriority"
android:tint="#BBB"
android:visibility="gone" android:visibility="gone"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
@@ -73,8 +108,47 @@
android:paddingTop="3dp" android:paddingTop="3dp"
android:contentDescription="@string/desc_priority_icon" /> android:contentDescription="@string/desc_priority_icon" />
</android.support.constraint.ConstraintLayout> <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" />
</android.support.v7.widget.CardView> <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"/>
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
</FrameLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

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

View File

@@ -3,6 +3,13 @@
<color name="colorPrimary">#3F51B5</color> <color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color> <color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color> <color name="colorAccent">#FF4081</color>
<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="colorBlack">#000</color>
<color name="bg_row_background">#fa315b</color>
</resources> </resources>

View File

@@ -2,4 +2,9 @@
<resources> <resources>
<dimen name="card_margin">5dp</dimen> <dimen name="card_margin">5dp</dimen>
<dimen name="card_album_radius">0dp</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> </resources>

View File

@@ -11,8 +11,32 @@
<string name="ic_img_fuel_desc">Icon Fuel</string> <string name="ic_img_fuel_desc">Icon Fuel</string>
<string name="str_qr_code">QR Code</string> <string name="str_qr_code">QR Code</string>
<string name="str_reset_account">Reset Account</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_not_connected">not connected</string>
<string name="str_reload">reload</string> <string name="str_reload">reload</string>
<string name="desc_priority_icon">Priority icon</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 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> </resources>

View File

@@ -1,13 +1,20 @@
<resources> <resources>
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="@style/PreferenceFixTheme.Light.NoActionBar"> <style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<!-- your app branding color for the app bar --> <!-- your app branding color for the app bar -->
<item name="colorPrimary">#3F51B5</item> <item name="colorPrimary">#3F51B5</item>
<!-- darker variant for the status bar and contextual app bars --> <!-- darker variant for the status bar and contextual app bars -->
<item name="colorPrimaryDark">#303F9F</item> <item name="colorPrimaryDark">#303F9F</item>
<!-- theme UI controls like checkboxes and text fields --> <!-- 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>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.MaterialComponents.Light" />
</resources> </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,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference
android:key="message_count"
android:title="Keep x notifications"
android:summary="Remember tha last x notifications locally"
android:defaultValue="200" />
<PreferenceCategory
android:title="Notifications"
android:key="pref_key_notifications">
<CheckBoxPreference
android:key="notification_enable_sound"
android:title="Notification sound"
android:summary="Play a sound when a notification is recieved"
android:defaultValue="false" />
<CheckBoxPreference
android:key="notification_enable_light"
android:title="Notification light"
android:summary="Turn the notification LED on when a notification is recieved"
android:defaultValue="true" />
<CheckBoxPreference
android:key="notification_enable_vibrate"
android:title="Notification vibration"
android:summary="Vibrate when a notification is recieved"
android:defaultValue="false" />
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -1,3 +1,3 @@
#Sat Oct 20 02:47:36 CEST 2018 #Thu Mar 05 15:29:10 UTC 2020
VERSION_NAME=0.0.2 VERSION_NAME=1.8.0
VERSION_CODE=2 VERSION_CODE=23

View File

@@ -7,10 +7,8 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.2.1' classpath 'com.android.tools.build:gradle:4.1.0'
classpath 'com.google.gms:google-services:4.0.1' classpath 'com.google.gms:google-services:4.3.4'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
} }
} }
@@ -20,6 +18,9 @@ allprojects {
jcenter() jcenter()
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
maven { url "https://dl.bintray.com/gericop/maven" } maven { url "https://dl.bintray.com/gericop/maven" }
maven { url "https://maven.google.com" }
} }
} }

View File

@@ -11,3 +11,6 @@ org.gradle.jvmargs=-Xmx1536m
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true # org.gradle.parallel=true
android.useAndroidX=true
android.enableJetifier=true

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 distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip

58
androidExportReader/.gitignore vendored Normal file
View File

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

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

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

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

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

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