Compare commits

...

219 Commits

Author SHA1 Message Date
d0f0501bf3 Fix some typo in readme 2023-02-23 23:30:03 +00:00
ab9c46f0eb Update 'CHANGELOG.md' 2023-02-21 15:00:37 +00:00
aa799d2ca8 Changelog for v123020523 [CI SKIP] 2023-02-21 14:49:14 +00:00
177c978474 fix: Git changelog. 2023-02-21 14:05:32 +00:00
39b9991413 Changelog for v123020522 [CI SKIP] 2023-02-21 13:25:11 +00:00
b303f110f1 fix: still fixing changelog versions. 2023-02-21 12:59:38 +00:00
f851941a6a Changelog for v123020521 [CI SKIP] 2023-02-21 11:31:18 +00:00
a313552976 fix: change changelog version on release. 2023-02-21 11:20:43 +00:00
6ac97ed3fe Changelog for v123020491 [CI SKIP] 2023-02-20 09:39:40 +00:00
d583b937b7 fix: Fixed acra bug reporting. 2023-02-20 10:29:00 +01:00
15b9a2d935 Changelog for v123010301 [CI SKIP] 2023-02-18 20:01:03 +00:00
5a8ce15961 Chore: acra config. 2023-02-18 20:38:06 +01:00
e1c64cef46 Changelog for v123010281 [CI SKIP] 2023-01-30 19:55:23 +00:00
ee064f3cb4 improvement: Improve right to left support (#130)
Co-authored-by: davidoskky <davidoskky@hidden.hidden>
Co-committed-by: davidoskky <davidoskky@hidden.hidden>
2023-01-29 12:55:28 +00:00
3e46e2ff29 Changelog for v123010261 [CI SKIP] 2023-01-28 10:57:06 +00:00
f28e702549 feat: Handle public instances (#126)
Co-authored-by: davidoskky <davidoskky@hidden.hidden>
Co-committed-by: davidoskky <davidoskky@hidden.hidden>
2023-01-28 10:25:28 +00:00
fc31a4399c ci: Pull request should trigger ci. 2023-01-28 11:23:00 +01:00
9b23053b66 fix: Complete the disconnection before redirecting to the login screen
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/129
2023-01-28 11:20:19 +01:00
389a04d250 Complete the disconnection before redirecting to the login screen 2023-01-27 14:21:33 +01:00
40e1d1478b Changelog for v123010241 [CI SKIP] 2023-01-26 10:55:43 +00:00
2154ff3c33 Merge pull request 'feat: swipe down to close images' (#122) from davidoskky/ReaderForSelfoss-multiplatform:swipe_down into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/122
2023-01-26 10:42:15 +00:00
2245565f95 Remove unnecessary definition 2023-01-25 21:25:32 +01:00
014858f06b Remove unused import 2023-01-25 20:40:20 +01:00
3f1f86a78e Adjust the image closing animation 2023-01-25 10:34:03 +01:00
a549169a7c Add a dark hue to the underlying article when swiping to close images 2023-01-25 10:27:38 +01:00
be7cae365a Rename activity style to avoid interferences 2023-01-25 02:09:41 +01:00
cef3b2e593 Adapt the style of the image activity to the rest of the application 2023-01-25 01:54:35 +01:00
ae927ebc57 Resolve issues when swiping down to close images 2023-01-25 00:46:43 +01:00
90532cf501 Changelog for v123010041 [CI SKIP] 2023-01-24 12:41:52 +00:00
ab0678d61e Merge pull request 'scroll-tag-filters' (#124) from scroll-tag-filters into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/124
2023-01-23 21:42:42 +00:00
a1b7d22d26 fix: added POST_NOTIFICATIONS to fix notifications issues. 2023-01-23 22:32:52 +01:00
29eae4b1f6 fix: scrollable filter sheet. 2023-01-23 21:13:10 +01:00
f5bbc63481 enhancement: Ellipsize chips text. 2023-01-23 21:12:56 +01:00
ddc72d85b0 Close the image fragment only if the image has been dragged down 2023-01-21 16:37:25 +01:00
68bbf5b2d3 Animate swipe down to close images 2023-01-20 16:36:52 +01:00
95e76a55da Cleaning. 2023-01-12 22:01:36 +01:00
2b6659f4ec Swipe down to close images 2023-01-11 22:28:14 +01:00
e0c118a73e Changelog for v122123641 [CI SKIP] 2023-01-04 19:27:56 +00:00
4e61b2aed6 feat: Disable the failing source in the filter sheet. 2023-01-03 21:35:21 +01:00
ba2758c0a3 feat: Display the source error in the sources list. 2023-01-03 21:28:40 +01:00
c718b966a1 Changelog for v122123631 [CI SKIP] 2022-12-30 19:24:48 +00:00
99438e142f build: Added back maven repos (see 1fb9d60dc5 (note_1223925153)) 2022-12-30 20:10:08 +01:00
4d8076c3cf build: Added back maven repos (see 1fb9d60dc5 (note_1223925153)) 2022-12-29 21:34:13 +01:00
db75c5b74a debug: trying to resolve Canvas: trying to use a recycled bitmap. 2022-12-29 20:40:39 +01:00
966a082147 fix: NPE may be caused by the binding or the title that was null. 2022-12-29 20:35:03 +01:00
cd20a5ec29 chore: Skip drone pipeline on changelog push. 2022-12-29 14:11:29 +01:00
cc4c1c9201 Changelog for v122123621 2022-12-29 13:08:23 +00:00
ff021d572c fix: Automatic CHANGELOG generation. 2022-12-29 13:11:23 +01:00
89992967be Merge pull request 'Sources Upsert' (#119) from sources-edit into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/119
2022-12-28 21:31:49 +00:00
3c68bde62b Source update screen. 2022-12-28 22:19:11 +01:00
c38251f5b3 Sources menu. 2022-12-28 21:45:00 +01:00
a01f6d2322 chore: Automatic CHANGELOG generation. 2022-12-28 21:43:56 +01:00
417a33eb25 Merge pull request 'Running migrations.' (#118) from fix-migration into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/118
2022-12-28 14:49:11 +00:00
2e7f7f23b3 No duplicate builds for PRs. 2022-12-28 15:34:22 +01:00
e5e182761e Running migrations. 2022-12-28 15:27:17 +01:00
a094d88799 Merge pull request 'Make the author field nullable' (#117) from davidoskky/ReaderForSelfoss-multiplatform:author into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/117
2022-12-28 14:25:36 +00:00
e51915d1cd Include author field when updating the database 2022-12-28 14:25:56 +01:00
3a654f6ede Migrate the database table 2022-12-28 14:25:34 +01:00
5227751dca Make the author field nullable 2022-12-28 11:02:43 +01:00
27eafe4ff4 Delete sources from DB and reload items on source deletion. 2022-12-26 22:27:28 +01:00
8c83a9408b Drone should work better. 2022-12-26 22:26:28 +01:00
fe2410f719 Handling author field. 2022-12-26 21:49:55 +01:00
a5e86bfb77 Date format issues. 2022-12-26 15:02:19 +01:00
23be633798 Add api version to the reports. 2022-12-25 22:45:12 +01:00
813e0707d8 Date format issue. 2022-12-25 22:41:34 +01:00
9ed9bf07fc Items in repository. 2022-12-23 22:53:16 +01:00
47265c10d0 Trying nexus build. 2022-12-23 14:59:58 +01:00
5cc633246a Debugging images issues. 2022-12-22 20:28:49 +01:00
1f40385786 Context should not be null, but handle the case for now. 2022-12-19 22:08:28 +01:00
eb2876324a This seems to be needed. 2022-12-19 20:47:04 +01:00
633b817d76 Remonving matomo. 2022-12-18 21:07:42 +01:00
2cfaa9b285 Logout fix. 2022-12-18 20:42:13 +01:00
f42ae97326 Explicitly failing for non selfoss rss files. 2022-12-18 20:41:17 +01:00
3b0028164b Glide update + trying requests. 2022-12-17 22:25:23 +01:00
7420adeb5c Do not ignore git version. 2022-12-14 21:42:56 +01:00
316027ca3b Tag for build. 2022-12-14 21:26:33 +01:00
9d58fba5c9 Cleaning. 2022-12-14 21:07:03 +01:00
284c19ef89 More cleaning. 2022-12-14 20:54:48 +01:00
7cfd17231a Cleaning. 2022-12-13 22:22:31 +01:00
527830a5ae Merge branch 'sonar-qube' 2022-12-13 21:53:10 +01:00
c4ed30f594 Fixes #112. 2022-12-13 21:32:48 +01:00
156c1681cf Fixes #111. 2022-12-13 21:19:05 +01:00
3593fbca78 Sonar scanner. 2022-12-13 21:11:38 +01:00
430fc8e8cb Fixes #110. 2022-12-13 20:40:50 +01:00
4fce19bad4 Trying to set code coverage. 2022-12-13 20:29:51 +01:00
49f5848e7b Merge pull request 'Fixes #108.' (#109) from bug/lateinit into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/109
2022-12-12 20:15:44 +00:00
90452100a4 Fixes #108. 2022-12-12 21:11:26 +01:00
bf1196dd0f Translations. 2022-12-09 20:27:38 +01:00
4316dc6516 Removing hidden tags. 2022-12-09 20:23:01 +01:00
9833a66a64 Cleaning tags duplications. 2022-12-08 14:02:30 +01:00
797bf06a9c Retry to fix post login issues. 2022-12-08 13:09:02 +01:00
d98b00533d Merge pull request 'filters' (#107) from filters into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/107
2022-12-07 19:13:46 +00:00
bf8f7d8667 Cleaning. 2022-12-07 12:11:58 +01:00
89c570f34f Fixing tests. 2022-12-06 22:36:15 +01:00
d6a562863a Big cleaning. 2022-12-06 22:18:38 +01:00
a02f06fe2e Big drawer cleaning. 2022-12-06 21:39:41 +01:00
7b088d7bb4 Hidden tags. 2022-12-06 21:39:41 +01:00
477883ed39 Tags sources reset. 2022-12-06 21:39:41 +01:00
748ed41096 Filters working. 2022-12-06 21:39:41 +01:00
86c50d4881 Loading sources and tags. 2022-12-06 21:39:41 +01:00
c4c92e6dd9 No need to login without password and username. 2022-12-06 21:39:32 +01:00
7f0ba193ec Fonts are a pain in the a$$. 2022-12-05 21:04:32 +01:00
87ed5b0fa8 Cleaning, and fixing socket timeout log. 2022-12-04 20:46:43 +01:00
6947743ac0 More details for silent reports. 2022-12-03 21:39:46 +01:00
07e3710d44 Merge pull request 'acra' (#104) from acra into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/104
2022-12-01 21:01:18 +00:00
e68da7764f Settings for acra and analytics. Closes #98. 2022-12-01 21:30:20 +01:00
c3ff894027 Initial integration. 2022-11-30 20:53:11 +01:00
f09f731d30 Merge pull request 'Cookies login and logout.' (#103) from login into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/103
2022-11-30 10:04:13 +00:00
956c4341c7 Cookies login and logout. 2022-11-29 21:38:58 +01:00
7b68264dd7 Cleaning. 2022-11-21 20:20:27 +01:00
cfcf030bf8 Removing gradle props. 2022-11-14 13:38:10 +01:00
0e7d7a5835 Conditionnal siteId 2022-11-14 13:18:20 +01:00
0856ebb889 Removing matomo url from build config. 2022-11-14 13:10:17 +01:00
25bf68cf0c Merge pull request 'Initial Matomo integration.' (#101) from login into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/101
2022-11-13 13:18:49 +00:00
afc6f392c6 Initial Matomo integration. 2022-11-13 13:13:03 +01:00
a0b5e2052b Merge pull request 'Fixed theme reload issues.' (#100) from fix-theme-reload into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/100
2022-11-11 20:29:08 +00:00
87d1ef2bce Fixed theme reload issues. 2022-11-11 20:51:49 +01:00
537a6d3a0b Merge pull request 'Checkerboard background for transparency in zoomed images' (#92) from davidoskky/ReaderForSelfoss-multiplatform:checkerboard into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/92
2022-11-11 19:29:40 +00:00
dbe97f564e Revert imageview changes 2022-11-11 09:40:36 +01:00
3a3bf03114 Bigger checktile. 2022-11-10 21:41:55 +01:00
c09a32e9ad Add checkerboard background to the images in the image view
A checkerboard is drawn beneath the image in the imageview to allow
a simpler viewing of images with transparency
2022-11-09 16:39:00 +01:00
b02a588dff Add a checkerboard background drawable 2022-11-09 16:34:37 +01:00
a4527940b8 Merge pull request 'Mercury issues fixing.' (#96) from mercury-common into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/96
2022-11-08 21:17:23 +00:00
9e8a25ed3e Fixing tests. 2022-11-08 22:02:20 +01:00
8ea46e146b Cleaning. 2022-11-08 21:54:12 +01:00
5ecf3c3f87 Mercury api in common code. 2022-11-08 21:31:40 +01:00
325f103417 Timeout. 2022-11-08 20:49:18 +01:00
ab4b1ae644 Merge pull request 'Theme should automatically change on phone settings change.' (#95) from theme-update into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/95
2022-11-08 07:38:54 +00:00
87ea44754e Font update. 2022-11-07 22:36:20 +01:00
04dec50808 Theme should automatically change on phone settings change. 2022-11-07 22:07:35 +01:00
e36189e2e7 Merge pull request 'About config upgrade.' (#93) from aboutconfig into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/93
2022-11-05 21:20:17 +00:00
d6bdf510a4 About config upgrade. 2022-11-05 22:00:16 +01:00
a464e93370 Merge pull request 'Immediately update bottom badges after reading or starring articles' (#91) from davidoskky/ReaderForSelfoss-multiplatform:badges into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/91
2022-11-02 19:06:48 +00:00
4b63afe62a Update badges tests 2022-11-01 21:51:46 +01:00
ac4c4b9441 Merge branch 'master' into badges 2022-11-01 20:35:13 +00:00
16b10dc1b7 Merge pull request 'Show all sources in the sources list' (#90) from davidoskky/ReaderForSelfoss-multiplatform:sources into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/90
2022-11-01 20:30:30 +00:00
02d734eee8 Do not edit the repository items from outside the repository 2022-11-01 21:29:04 +01:00
c5cdfc0d53 Update bottom bar badges through a state flow 2022-11-01 21:28:14 +01:00
6d610ed61a Fix repeating items in recyclerview 2022-11-01 19:53:22 +01:00
792950be7c Remove unreachable condition 2022-11-01 19:52:43 +01:00
af8969ce4a Merge pull request 'Cleaning.' (#88) from cleaning into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/88
2022-10-31 20:42:20 +00:00
27c55e59a1 Cleaning still. 2022-10-31 21:28:11 +01:00
94a0747947 More cleaning. 2022-10-30 22:02:07 +01:00
d862bfba4f Still cleaning. 2022-10-30 21:38:04 +01:00
b0d1d9c29a No daemon 2022-10-30 21:27:53 +01:00
7b40a31979 Cleaning. 2022-10-30 21:21:43 +01:00
823a8c3692 Date formatter 2022-10-30 21:12:01 +01:00
5494978db8 Cleaning. 2022-10-29 22:58:25 +02:00
6076eb1cee Merge pull request 'chore/mock-multiplatform' (#86) from chore/mock-multiplatform into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/86
2022-10-29 12:10:51 +00:00
131101d2ee Date api issues. 2022-10-29 13:50:00 +02:00
62ad1f45ba Unit tests are on the android side. 2022-10-29 13:37:46 +02:00
402d18b889 Versions update. SettingsKey constants. 2022-10-28 20:32:23 +02:00
e32699c93f Merge pull request 'ios/init' (#85) from ios/init into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/85
2022-10-24 18:18:16 +00:00
059a237b99 Commenting tests for now. 2022-10-23 20:39:09 +02:00
d2bdbae6c8 Launching ios APP. 2022-10-23 20:38:43 +02:00
510fcbe47e Merge pull request 'Prevent crash when logging in' (#81) from davidoskky/ReaderForSelfoss-multiplatform:login_crash into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/81
2022-10-22 19:53:59 +00:00
667e9c1a5d Adjust tests to changes in the repository 2022-10-21 22:56:35 +02:00
53b1d1f8b2 Rework repository initialization 2022-10-21 22:42:32 +02:00
c25e8889a4 Prevent crash when logging in 2022-10-17 19:35:52 +02:00
8b0bbe71c9 Merge pull request 'Allow offline filtering' (#75) from davidoskky/ReaderForSelfoss-multiplatform:offline_filters into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/75
2022-10-14 07:18:42 +00:00
8bfe14c019 Actually filter database items 2022-10-14 00:10:35 +02:00
208babbce3 Correct tests 2022-10-14 00:03:20 +02:00
02098a7aa9 Rearrange filtering steps 2022-10-11 00:52:12 +02:00
d0a982f385 Add tests for offline filtering 2022-10-08 17:15:41 +02:00
1d1c121aab Filter items from database according to tag and source 2022-10-08 17:15:22 +02:00
fe12819163 Correct database source title 2022-10-08 17:14:12 +02:00
023a30c008 Merge pull request 'Simplify sources and tags handling' (#70) from davidoskky/ReaderForSelfoss-multiplatform:drawer_data into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/70
2022-10-04 18:52:39 +00:00
a2862a2587 Merge pull request 'Correct mechanism of mark and unmark snackbars' (#74) from davidoskky/ReaderForSelfoss-multiplatform:snackbar-mark into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/74
2022-10-04 18:43:44 +00:00
054e936657 Merge pull request 'Correct boolean serialization' (#73) from davidoskky/ReaderForSelfoss-multiplatform:swiping into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/73
2022-10-04 18:40:14 +00:00
1d2e5069b8 Avoid double snackbar generation 2022-10-04 16:47:13 +02:00
a147646743 Correct mechanism of mark and unmark snackbars 2022-10-04 16:43:21 +02:00
32e7fc0038 Correct boolean serialization 2022-10-04 15:01:22 +02:00
c15bf44032 Adjust repository tests 2022-10-02 01:01:39 +02:00
0bcd55bd4e Add translated strings 2022-10-01 22:51:09 +02:00
ebef0b3511 Start monitoring connectivity status when the repository is initiated. 2022-10-01 22:43:48 +02:00
713ceb05bf Remove unnecessary data class 2022-09-30 15:07:17 +02:00
dc8381b661 Add missing string 2022-09-30 15:00:25 +02:00
b5b820c64b Remove database access from the Home 2022-09-30 15:00:01 +02:00
f7055626d9 Start monitoring the connectivity before loading the Repository 2022-09-30 14:56:10 +02:00
6ec3e96909 Merge pull request 'Repository Unit Tests' (#50) from davidoskky/ReaderForSelfoss-multiplatform:repository_tests into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/50
2022-09-30 11:31:55 +00:00
22da30eaa8 Remove unnecessary call to api 2022-09-30 13:17:40 +02:00
79fd115f5e Only return new cached items 2022-09-30 13:16:42 +02:00
8dc3d319cd Cleanup 2022-09-30 11:59:08 +02:00
27bb056397 Cleanup 2022-09-30 11:49:31 +02:00
f9ba13dc32 Always cache images in background 2022-09-30 11:23:43 +02:00
6f60ef4346 Remove unnecessary return value 2022-09-30 09:11:55 +02:00
28b950f467 Merge branch 'master' into repository_tests 2022-09-30 07:04:09 +00:00
a9c7ec3dc1 Cache items in background without filtering 2022-09-30 01:19:28 +02:00
920d4ac1ef Correctly implement disabling sources update 2022-09-29 19:37:33 +02:00
0e96d313ec Add tags parameters explicitly 2022-09-29 19:09:09 +02:00
7211fdb1a3 Fix update remote tests 2022-09-29 18:58:00 +02:00
381d6acc82 Analyzing should be with the rest. 2022-09-29 15:34:12 +02:00
d311c2cdeb Fix sources tests 2022-09-28 18:45:21 +02:00
219cae5d74 Fix tags tests 2022-09-28 18:22:06 +02:00
2968aee309 Fix badges tests 2022-09-28 09:43:28 +02:00
6cb4b35c93 Introduce useful assertions in repository instantiation tests 2022-09-28 09:14:47 +02:00
15ec0f2d26 Merge branch 'master' into repository_tests 2022-09-28 06:57:02 +00:00
4781e30da2 Remove unnecessary safe calls 2022-09-27 23:44:42 +02:00
c8759cc035 Fix tags tests 2022-09-27 23:37:30 +02:00
cb4f2f02ef Fix repository.tags() returning null 2022-09-27 23:26:44 +02:00
7517626ab7 Include database return definition within test function 2022-09-27 23:25:47 +02:00
41c951b659 Add test cases for repository instantiation cases 2022-09-27 23:16:30 +02:00
e2afff0b8e Add comment 2022-09-26 23:19:31 +02:00
a382fc89ea Test item caching 2022-09-26 23:11:26 +02:00
3f0a3903ae Test refresh login information 2022-09-26 22:50:55 +02:00
f46f98cef0 Test login 2022-09-26 22:46:37 +02:00
bf6f1a917e Test update remote 2022-09-26 22:42:24 +02:00
71c0a4d340 Test delete source 2022-09-26 22:26:01 +02:00
63c550ead3 Test create source 2022-09-26 22:21:48 +02:00
366b2e10f1 Adjust tests to changes in the data models 2022-09-25 22:02:25 +02:00
d2436bb976 Merge branch 'master' into repository_tests
# Conflicts:
#	.drone.yml
2022-09-25 20:24:46 +02:00
ef994460c1 get sources tests 2022-09-18 18:14:14 +02:00
758708e18d Tags tests 2022-09-17 22:04:24 +02:00
c0381144d1 Add CI test step 2022-09-17 21:29:37 +02:00
cda3ba6cb4 Test badge fetching 2022-09-16 12:04:05 +02:00
a4636cc0c8 Add item fetching tests 2022-09-15 14:07:50 +02:00
60c24fc75a Check that the api is being used rather than the db 2022-09-10 09:37:14 +02:00
5853a19937 Normal items fetch test 2022-09-10 09:08:26 +02:00
99f2c04bf6 Initial testing setup 2022-09-09 13:43:53 +02:00
117 changed files with 4560 additions and 2638 deletions

View File

@ -3,33 +3,35 @@ type: docker
name: test
steps:
- name: Anylyse
image: mingc/android-build-box:latest
failure: ignore
detach: true
commands:
- echo "---------------------------------------------------------"
- echo "Analysing..."
- ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\""
- echo "---------------------------------------------------------"
environment:
SONAR_HOST_URL:
from_secret: sonarScannerHostUrl
SONAR_LOGIN:
from_secret: sonarScannerLogin
- name: BuildAndTest
image: mingc/android-build-box:latest
commands:
- echo "---------------------------------------------------------"
- echo "Configure gradle..."
- mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
- echo "---------------------------------------------------------"
- echo "Building..."
- ./gradlew :androidApp:build -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
- ./gradlew build -x test
- echo "---------------------------------------------------------"
- echo "Testing..."
- echo "---------------------------------------------------------"
- ./gradlew koverMergedXmlReport
environment:
TZ: Europe/Paris
SONAR_HOST_URL:
from_secret: sonarScannerHostUrl
SONAR_LOGIN:
from_secret: sonarScannerLogin
- name: Analyse
image: kytay/sonar-node-plugin
settings:
sonar_host:
from_secret: sonarScannerHostUrl
sonar_token:
from_secret: sonarScannerLogin
use_node_version: 16.18.1
sonar_debug: true
sonar_project_settings: ./sonar-project.properties
trigger:
event:
- push
@ -41,14 +43,23 @@ type: docker
name: Publish
steps:
- name: createTag
- name: createTagAndChangelog
image: ubuntu:latest
commands:
- apt-get update && apt-get install -y git
- git fetch --tags -p
- PREV=$(git describe --tags --abbrev=0)
- ./build.sh --publish --from-ci
- git remote add pushing https://$GITEA_USR:$GITEA_PASS@gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform.git
- VER=$(git describe --tags --abbrev=0)
- CHANGELOG=$(git log $PREV..HEAD --pretty="- %s")
- echo "**$VER**\n\n$CHANGELOG\n\n--------------------------------------------------------------------\n\n$(cat CHANGELOG.md)" > CHANGELOG.md
- git add CHANGELOG.md
- git commit -m "Changelog for $VER [CI SKIP]"
- git push pushing master
- git push pushing --tags
environment:
TZ: Europe/Paris
GITEA_USR:
from_secret: giteaUsr
GITEA_PASS:
@ -74,10 +85,7 @@ steps:
from_secret: privateKey
command_timeout: 2m
script:
- cd /home/ubuntu
- sudo rm -rf /var/www/amine/version.txt
- sudo chown www-data:www-data ./version.txt
- sudo mv version.txt /var/www/amine/
- cd /home/ubuntu && sudo rm -rf /var/www/amine/version.txt && sudo chown www-data:www-data ./version.txt && sudo mv version.txt /var/www/amine/
trigger:
event:
@ -94,8 +102,15 @@ steps:
- name: build
image: mingc/android-build-box:latest
commands:
- echo "---------------------------------------------------------"
- echo "Fetch tags..."
- git fetch --tags
- echo "---------------------------------------------------------"
- echo "Configure gradle..."
- mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=false\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
- echo "---------------------------------------------------------"
- echo "Generate APK"
- ./gradlew :androidApp:assembleGithubConfigRelease -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
- ./gradlew :androidApp:assembleGithubConfigRelease -P pushCache=false
- echo "---------------------------------------------------------"
- echo "Get Key"
- wget https://amine-louveau.fr/key
@ -109,6 +124,7 @@ steps:
- echo "Verify"
- $ANDROID_HOME/build-tools/31.0.0/apksigner verify signed.apk
environment:
TZ: Europe/Paris
YOUR_KEYSTORE_PASSWORD:
from_secret: keyPass
YOUR_KEY_ALIAS:

View File

@ -46,28 +46,3 @@ Always check if the web version of your instance is working.
I won't provide any selfoss instance url. If you want to help, but to not have one, you'll have to install one, and use it.
All the details to need are [here](https://selfoss.aditu.de/).
# Build the project
You can directly import this project into IntellIJ/Android Studio.
You'll have to:
- Define some parameters either in `~/.gradle/gradle.properties` or as gradle parameters (see the examples)
- appLoginUrl, appLoginUsername and appLoginPassword: url, username and password of a selfoss instance. **These are only used for tests. They can be empty if you don't test API calls.**
### Examples:
#### Inside ~/.gradle/gradle.properties
```
appLoginUrl="URL" # It can be empty.
appLoginUsername="LOGIN" # It can be empty.
appLoginPassword="PASS" # It can be empty.
```
#### As gradle parameters
```
./gradlew .... -P appLoginUrl="URL" -P appLoginUsername="LOGIN" -P appLoginPassword="PASS"
```

View File

@ -1,3 +1,89 @@
**v123020523**
- fix: Git changelog.
--------------------------------------------------------------------
**v123020491**
- fix: Fixed acra bug reporting.
--------------------------------------------------------------------
**v123010301**
- Chore: acra config.
--------------------------------------------------------------------
**v123010281**
- improvement: Improve right to left support (#130) Co-authored-by: davidoskky <davidoskky@hidden.hidden> Co-committed-by: davidoskky <davidoskky@hidden.hidden>
--------------------------------------------------------------------
**v123010261**
- feat: Handle public instances (#126) Co-authored-by: davidoskky <davidoskky@hidden.hidden> Co-committed-by: davidoskky <davidoskky@hidden.hidden>
- ci: Pull request should trigger ci.
- fix: Complete the disconnection before redirecting to the login screen
- Complete the disconnection before redirecting to the login screen
--------------------------------------------------------------------
**v123010241**
- Merge pull request 'feat: swipe down to close images' (#122) from davidoskky/ReaderForSelfoss-multiplatform:swipe_down into master
- Remove unnecessary definition
- Remove unused import
- Adjust the image closing animation
- Add a dark hue to the underlying article when swiping to close images
- Rename activity style to avoid interferences
- Adapt the style of the image activity to the rest of the application
- Resolve issues when swiping down to close images
- Close the image fragment only if the image has been dragged down
- Animate swipe down to close images
- Swipe down to close images
--------------------------------------------------------------------
**v123010041**
- Merge pull request 'scroll-tag-filters' (#124) from scroll-tag-filters into master
- fix: added POST_NOTIFICATIONS to fix notifications issues.
- fix: scrollable filter sheet.
- enhancement: Ellipsize chips text.
- Cleaning.
--------------------------------------------------------------------
**v122123641**
- feat: Disable the failing source in the filter sheet.
- feat: Display the source error in the sources list.
--------------------------------------------------------------------
**v122123631**
- build: Added back maven repos (see https://gitlab.com/fdroid/fdroiddata/-/commit/1fb9d60dc58511abc2bb4eb321977922a0682c8b#note_1223925153)
- build: Added back maven repos (see https://gitlab.com/fdroid/fdroiddata/-/commit/1fb9d60dc58511abc2bb4eb321977922a0682c8b#note_1223925153)
- debug: trying to resolve `Canvas: trying to use a recycled bitmap`.
- fix: NPE may be caused by the binding or the title that was null.
- chore: Skip drone pipeline on changelog push.
--------------------------------------------------------------------
**v122123621**
- fix: Automatic CHANGELOG generation.
- Merge pull request 'Sources Upsert' (#119) from sources-edit into master
- Source update screen.
- Sources menu.
- chore: Automatic CHANGELOG generation.
--------------------------------------------------------------------
# V2/Multiplatform rewrite
**v1**

View File

@ -2,11 +2,11 @@
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/readerforselfoss/localized.svg)](https://crowdin.com/project/readerforselfoss)
It's an RSS Reader for Android and IOS, that **only** works with [Selfoss](https://selfoss.aditu.de/)
**ReaderForSelfoss** is a RSS Reader for Android and IOS, that **only** works with [Selfoss](https://selfoss.aditu.de/)
If you are a developer, don't hesitate to help with PRs.
If you are a user, you can still create new issues. I'll fix them when I can.
If you are a user, fell free to create issues. I'll fix them when I can.
<a href="https://f-droid.org/packages/bou.amine.apps.readerforselfossv2.android"><img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="100"></a>
@ -14,13 +14,13 @@ If you are a user, you can still create new issues. I'll fix them when I can.
<img src="res//fr-card.png?raw=true" alt="card view" width="400"/> <img src="res//fr-list.png?raw=true" alt="list view" width="400"/>
## Like my app ?
## Like my app?
<a href="https://www.buymeacoffee.com/aminecmi" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/lato-orange.png" alt="Buy Me A Coffee" style="height: 51px !important;width: 217px !important;" ></a>
## Want to help ?
## Want to help?
1. **You'll have to have a Selfoss instance running.** You'll find everything you need to install it [here](https://selfoss.aditu.de/).
1. **You need a Selfoss instance running.** Find everything you need to install it [here](https://selfoss.aditu.de/).
2. Check the [Contribution guide](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md).
@ -30,8 +30,8 @@ If you are a user, you can still create new issues. I'll fix them when I can.
- [Check what changed](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/CHANGELOG.md)
- [See what I'm doing](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/projects/1)
- [Create an issue, or request a new feature](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/issues)
- [Help translation the app](https://crowdin.com/project/readerforselfoss)
- [Create an issue or request a new feature](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/issues)
- [Help translating the app](https://crowdin.com/project/readerforselfoss)
## Contributors (V1) (Alphabetical order) ❤️

View File

@ -1,11 +1,14 @@
import java.io.ByteArrayOutputStream
val ignoreGitVersion: String by project
val acraVersion = "5.9.7"
plugins {
id("com.android.application")
kotlin("android")
kotlin("kapt")
id("com.mikepenz.aboutlibraries.plugin")
id("org.jetbrains.kotlinx.kover") version "0.6.1"
}
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
@ -54,19 +57,23 @@ fun versionNameFromGit(): String {
android {
compileOptions {
// Flag to enable support for the new language APIs
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
compileSdk = 31
buildToolsVersion = "31.0.0"
// For Kotlin projects
kotlinOptions {
jvmTarget = "11"
}
compileSdk = 33
buildToolsVersion = "33.0.0"
buildFeatures {
viewBinding = true
}
defaultConfig {
applicationId = "bou.amine.apps.readerforselfossv2.android"
minSdk = 21
targetSdk = 31
targetSdk = 33
versionCode = versionCodeFromGit()
versionName = versionNameFromGit()
@ -79,6 +86,11 @@ android {
// tests
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
packagingOptions {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
@ -86,9 +98,6 @@ android {
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
}
getByName("debug") {
buildConfigField("String", "LOGIN_URL", properties["appLoginUrl"] as String)
buildConfigField("String", "LOGIN_PASSWORD", properties["appLoginPassword"] as String)
buildConfigField("String", "LOGIN_USERNAME", properties["appLoginUsername"] as String)
}
}
flavorDimensions.add("build")
@ -98,9 +107,6 @@ android {
dimension = "build"
}
}
kotlinOptions {
jvmTarget = "1.8"
}
namespace = "bou.amine.apps.readerforselfossv2.android"
}
@ -109,18 +115,10 @@ dependencies {
implementation(project(":shared"))
implementation("com.google.android.material:material:1.5.0")
implementation("androidx.appcompat:appcompat:1.4.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.3")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
implementation("androidx.preference:preference-ktx:1.1.1")
// Testing
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0-alpha02")
androidTestImplementation("androidx.test:runner:1.3.1-alpha02")
// Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
androidTestImplementation("androidx.test.espresso:espresso-contrib:3.4.0-alpha02")
// Espresso-intents for validation and stubbing of Intents
androidTestImplementation("androidx.test.espresso:espresso-intents:3.4.0-alpha02")
implementation(fileTree(mapOf("include" to listOf("*.jar"), "dir" to "libs")))
// Android Support
@ -132,38 +130,23 @@ dependencies {
implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.annotation:annotation:1.3.0")
implementation("androidx.work:work-runtime-ktx:2.7.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.3")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("org.jsoup:jsoup:1.14.3")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
//multidex
implementation("androidx.multidex:multidex:2.0.1")
// About
implementation("com.mikepenz:aboutlibraries-core:8.9.4")
implementation("com.mikepenz:aboutlibraries:8.9.4")
implementation("com.mikepenz:aboutlibraries-definitions:8.9.4")
// Async
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
// Retrofit + http logging + okhttp
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.3")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.burgstaller:okhttp-digest:2.5")
implementation("com.mikepenz:aboutlibraries-core:10.5.1")
implementation("com.mikepenz:aboutlibraries:10.5.1")
// Material-ish things
implementation("com.ashokvarma.android:bottom-navigation-bar:2.2.0")
implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1")
// glide
kapt("com.github.bumptech.glide:compiler:4.11.0")
implementation("com.github.bumptech.glide:okhttp3-integration:4.1.1")
// Drawer
implementation("com.mikepenz:materialdrawer:8.4.5")
kapt("com.github.bumptech.glide:compiler:4.14.2")
implementation("com.github.bumptech.glide:okhttp3-integration:4.14.2")
// Themes
implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
@ -188,14 +171,44 @@ dependencies {
implementation("androidx.core:core-ktx:1.8.0")
// implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
// implementation("androidx.lifecycle:lifecycle-common-java8:2.5.1")
// implementation("androidx.lifecycle:lifecycle-runtime:2.5.1")
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
// Network information
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
// SQLDELIGHT
implementation("com.squareup.sqldelight:android-driver:1.5.3")
implementation("com.squareup.sqldelight:android-driver:1.5.4")
//test
testImplementation("junit:junit:4.13.2")
testImplementation("io.mockk:mockk:1.12.0")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
implementation("ch.acra:acra-http:$acraVersion")
implementation("ch.acra:acra-toast:$acraVersion")
}
tasks.withType<Test> {
outputs.upToDateWhen { false }
useJUnit()
testLogging {
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
events = setOf(
org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED,
org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED,
org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR
)
showStandardStreams = true
}
}
aboutLibraries {
offlineMode = true
fetchRemoteLicense = false
fetchRemoteFunding = false
includePlatform = false
strictMode = com.mikepenz.aboutlibraries.plugin.StrictMode.FAIL
duplicationMode = com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE
duplicationRule = com.mikepenz.aboutlibraries.plugin.DuplicateRule.GROUP
}

View File

@ -30,15 +30,8 @@
<fields>;
}
-dontwarn okio.**
-dontwarn retrofit2.Platform$Java8
-keep class retrofit.** { *; }
-keepclasseswithmembers class * {
@retrofit.http.* <methods>;
}
-keepattributes *Annotation*,Signature
-keepattributes Exceptions
-dontwarn okio.**
-dontwarn javax.annotation.Nullable
-dontwarn javax.annotation.ParametersAreNonnullByDefault
@ -90,3 +83,5 @@
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
-dontwarn io.mockk.**
-keep class io.mockk.** { *; }

View File

@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@ -15,7 +16,8 @@
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/NoBar"
android:dataExtractionRules="@xml/data_extraction_rules">
android:dataExtractionRules="@xml/data_extraction_rules"
android:configChanges="uiMode">
<activity
android:name=".MainActivity"
android:theme="@style/SplashTheme"
@ -51,7 +53,7 @@
android:value=".HomeActivity" />
</activity>
<activity
android:name=".AddSourceActivity"
android:name=".UpsertSourceActivity"
android:parentActivityName=".SourcesActivity"
android:exported="true">
<meta-data
@ -68,7 +70,8 @@
android:name=".ReaderActivity">
</activity>
<activity
android:name=".ImageActivity">
android:name=".ImageActivity"
android:theme="@style/Theme.AppCompat.ImageActivity">
</activity>
<meta-data android:name="android.webkit.WebView.MetricsOptOut"
@ -78,8 +81,5 @@
android:value="true" />
<meta-data android:name="android.max_aspect" android:value="2.1" />
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
</application>
</manifest>

View File

@ -0,0 +1,9 @@
package bou.amine.apps.readerforselfossv2.android
import org.acra.ACRA
import org.acra.ktx.sendSilentlyWithAcra
fun Throwable.sendSilentlyWithAcraWithName(name: String) {
ACRA.errorReporter.putCustomData("error_source", name)
this.sendSilentlyWithAcra()
}

View File

@ -1,26 +1,17 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ImageView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
import androidx.appcompat.widget.SearchView
import androidx.core.view.doOnNextLayout
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.*
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
@ -31,46 +22,30 @@ import bou.amine.apps.readerforselfossv2.android.adapters.ItemListAdapter
import bou.amine.apps.readerforselfossv2.android.adapters.ItemsAdapter
import bou.amine.apps.readerforselfossv2.android.background.LoadingWorker
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityHomeBinding
import bou.amine.apps.readerforselfossv2.android.fragments.FilterSheetFragment
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.*
import bou.amine.apps.readerforselfossv2.utils.ItemType
import com.ashokvarma.bottomnavigation.BottomNavigationBar
import com.ashokvarma.bottomnavigation.BottomNavigationItem
import com.ashokvarma.bottomnavigation.TextBadgeItem
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.mikepenz.aboutlibraries.LibsBuilder
import com.mikepenz.materialdrawer.holder.BadgeStyle
import com.mikepenz.materialdrawer.holder.ColorHolder
import com.mikepenz.materialdrawer.holder.StringHolder
import com.mikepenz.materialdrawer.model.DividerDrawerItem
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem
import com.mikepenz.materialdrawer.model.SecondaryDrawerItem
import com.mikepenz.materialdrawer.model.interfaces.*
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader
import com.mikepenz.materialdrawer.util.addStickyFooterItem
import com.mikepenz.materialdrawer.util.updateBadge
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
import java.security.MessageDigest
import java.util.concurrent.TimeUnit
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAware {
private val DRAWER_ID_TAGS = 100101L
private val DRAWER_ID_HIDDEN_TAGS = 101100L
private val DRAWER_ID_SOURCES = 100110L
private val DRAWER_ID_FILTERS = 100111L
private var items: ArrayList<SelfossModel.Item> = ArrayList()
private var elementsShown: ItemType = ItemType.UNREAD
@ -88,8 +63,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private var fromTabShortcut: Boolean = false
private lateinit var tagsBadge: Map<Long, Int>
private val settingsLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
appSettingsService.refreshUserSettings()
}
@ -98,7 +71,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private val repository : Repository by instance()
private val appSettingsService : AppSettingsService by instance()
data class DrawerData(val tags: List<SelfossModel.Tag>?, val sources: List<SelfossModel.Source>?)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -115,14 +87,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
setContentView(view)
setSupportActionBar(binding.toolBar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeButtonEnabled(true)
val mDrawerToggle = ActionBarDrawerToggle(this, binding.drawerContainer, binding.toolBar, R.string.material_drawer_open, R.string.material_drawer_close)
binding.drawerContainer.addDrawerListener(mDrawerToggle)
mDrawerToggle.syncState()
handleBottomBar()
initDrawer()
handleSwipeRefreshLayout()
@ -143,17 +109,22 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
binding.swipeRefreshLayout.setOnRefreshListener {
repository.offlineOverride = false
lastFetchDone = false
handleDrawerItems()
CoroutineScope(Dispatchers.Main).launch {
getElementsAccordingToTab()
binding.swipeRefreshLayout.isRefreshing = false
}
}
val swipeDirs = if (appSettingsService.getPublicAccess()) {
0
} else {
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
}
val simpleItemTouchCallback =
object : ItemTouchHelper.SimpleCallback(
0,
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
swipeDirs
) {
override fun getSwipeDirs(
recyclerView: RecyclerView,
@ -183,18 +154,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
adapter.handleItemAtIndex(position)
reloadBadgeContent()
val tagHashes = i.tags.map { it.longHash() }
tagsBadge = tagsBadge.map {
if (tagHashes.contains(it.key)) {
(it.key to (it.value - 1))
} else {
(it.key to it.value)
}
}.toMap()
reloadTagsBadges()
// Just load everythin
if (items.size <= 0) {
getElementsAccordingToTab()
@ -212,6 +171,16 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
ItemTouchHelper(simpleItemTouchCallback).attachToRecyclerView(binding.recyclerView)
}
private fun updateBottomBarBadgeCount(badge: TextBadgeItem, count: Int) {
if (count > 0) {
badge
.setText(count.toString())
.maybeShow()
} else {
badge.removeBadge()
}
}
private fun handleBottomBar() {
tabNewBadge = TextBadgeItem()
@ -224,6 +193,28 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
.setText("")
.setHideOnSelect(false).hide(false)
if (appSettingsService.isDisplayUnreadCountEnabled()) {
lifecycleScope.launch {
repository.badgeUnread.collect {
updateBottomBarBadgeCount(tabNewBadge, it)
}
}
}
if (appSettingsService.isDisplayAllCountEnabled()) {
lifecycleScope.launch {
repository.badgeAll.collect {
updateBottomBarBadgeCount(tabArchiveBadge, it)
}
}
lifecycleScope.launch {
repository.badgeStarred.collect {
updateBottomBarBadgeCount(tabStarredBadge, it)
}
}
}
val tabNew =
BottomNavigationItem(
R.drawable.ic_tab_fiber_new_black_24dp,
@ -259,8 +250,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
override fun onResume() {
super.onResume()
handleDrawerItems()
reloadLayoutManager()
if (appSettingsService.isInfiniteLoadingEnabled()) {
@ -271,6 +260,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
handleBottomBarActions()
handleGDPRDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
handleRecurringTask()
CoroutineScope(Dispatchers.Main).launch {
@ -280,257 +271,22 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
getElementsAccordingToTab()
}
private fun initDrawer() {
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) {
Glide.with(this@HomeActivity)
.asBitmap()
.load(uri)
.apply(RequestOptions()
.placeholder(R.mipmap.ic_launcher)
.fallback(R.mipmap.ic_launcher)
.fitCenter())
.into(imageView)
private fun handleGDPRDialog(GDPRShown: Boolean) {
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
if (!GDPRShown) {
val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle(getString(R.string.gdpr_dialog_title))
alertDialog.setMessage(getString(R.string.gdpr_dialog_message))
alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL,
"OK"
) { dialog, _ ->
appSettingsService.settings.putBoolean("GDPR_shown", true)
dialog.dismiss()
}
override fun cancel(imageView: ImageView) {
Glide.with(this@HomeActivity).clear(imageView)
}
})
val drawerListener = object : DrawerLayout.DrawerListener {
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
// We do nothing
}
override fun onDrawerOpened(drawerView: View) {
binding.bottomBar.hide()
}
override fun onDrawerClosed(drawerView: View) {
binding.bottomBar.show()
}
override fun onDrawerStateChanged(newState: Int) {
// We do nothing
}
}
binding.drawerContainer.addDrawerListener(drawerListener)
// Sticky items
addStickyPrimaryItem(R.string.drawer_report_bug, R.drawable.ic_bug_report_black_24dp) { _, _, _ ->
val browserIntent =
Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.trackerUrl))
startActivity(browserIntent)
false
}
addStickyPrimaryItem(R.string.title_activity_settings, R.drawable.ic_settings_black_24dp) { _, _, _ ->
settingsLauncher.launch(Intent(this, SettingsActivity::class.java))
false
}
}
private fun addStickyPrimaryItem(name: Int, icon: Int, clickListener: ((v: View?, item: IDrawerItem<*>, position: Int) -> Boolean)?) {
binding.mainDrawer.addStickyFooterItem(
PrimaryDrawerItem().apply {
nameRes = name
iconRes = icon
isIconTinted = true
onDrawerItemClickListener = clickListener
})
}
private fun handleDrawerItems() {
tagsBadge = emptyMap()
binding.mainDrawer.itemAdapter.add(
PrimaryDrawerItem().apply {
nameRes = R.string.drawer_loading
isSelectable = false
}
)
CoroutineScope(Dispatchers.IO).launch {
val drawerData = DrawerData(repository.getDBTags().map { it.toView() },
repository.getDBSources().map { it.toView() })
runOnUiThread {
// Only refresh if there is no data in the DB, or if the `UpdateSources` setting is enabled
if (drawerData.sources?.isEmpty() == true || appSettingsService.isUpdateSourcesEnabled()) {
drawerApiCalls(drawerData)
} else {
handleDrawerData(drawerData, loadedFromCache = true)
}
}
}
}
private fun drawerApiCalls(drawerData: DrawerData) {
CoroutineScope(Dispatchers.Main).launch {
val apiDrawerData = DrawerData(repository.getTags(), repository.getSources())
handleDrawerData(if (drawerData != apiDrawerData) apiDrawerData else drawerData)
}
}
private fun handleDrawerData(drawerData: DrawerData, loadedFromCache: Boolean = false) {
binding.mainDrawer.itemAdapter.clear()
// Filters title with clear action
secondaryItem(withDivider = false, R.string.drawer_item_filters, DRAWER_ID_FILTERS, R.string.drawer_action_clear) { _,_,_ ->
repository.sourceFilter = null
repository.tagFilter = null
binding.mainDrawer.setSelectionAtPosition(-1)
getElementsAccordingToTab()
fetchOnEmptyList()
false
}
// Hidden tags
if (drawerData.tags != null && drawerData.tags.isNotEmpty() && appSettingsService.getHiddenTags().isNotEmpty()) {
secondaryItem(
withDivider = true,
R.string.drawer_item_hidden_tags,
DRAWER_ID_HIDDEN_TAGS
)
handleHiddenTags(drawerData.tags)
}
// Tags
secondaryItem(withDivider = true, R.string.drawer_item_tags, DRAWER_ID_TAGS)
if (drawerData.tags == null && !loadedFromCache) {
binding.mainDrawer.itemAdapter.add(
SecondaryDrawerItem()
.apply { nameRes = R.string.drawer_error_loading_tags; isSelectable = false }
)
} else {
handleTags(drawerData.tags!!)
}
// Sources
secondaryItem(withDivider = true, R.string.drawer_item_sources, DRAWER_ID_SOURCES, R.string.drawer_action_edit) { v, _, _ ->
startActivity(Intent(v!!.context, SourcesActivity::class.java))
false
}
if (drawerData.sources == null && !loadedFromCache) {
binding.mainDrawer.itemAdapter.add(
SecondaryDrawerItem().apply {
nameRes = R.string.drawer_error_loading_tags
isSelectable = false
}
)
} else {
handleSources(drawerData.sources!!)
}
// About action
binding.mainDrawer.itemAdapter.add(
DividerDrawerItem(),
PrimaryDrawerItem().apply {
nameRes = R.string.action_about
isSelectable = false
iconRes = R.drawable.ic_info_outline_white_24dp
isIconTinted = true
onDrawerItemClickListener = { _,_,_ ->
LibsBuilder()
.withAboutIconShown(true)
.withAboutVersionShown(true)
.start(this@HomeActivity)
false
}
}
)
}
private fun secondaryItem(withDivider: Boolean, name: Int, id: Long, badgeId: Int? = null, clickListener: ((v: View?, item: IDrawerItem<*>, position: Int) -> Boolean)? = null) {
if (withDivider) {
binding.mainDrawer.itemAdapter.add(DividerDrawerItem())
}
binding.mainDrawer.itemAdapter.add(
SecondaryDrawerItem().apply {
nameRes = name
identifier = id
isSelectable = false
if (badgeId != null) {
badgeRes = badgeId
}
onDrawerItemClickListener = clickListener
}
)
}
private fun createDrawerItem(it: SelfossModel.Tag) {
val gd = GradientDrawable()
val gdColor = try {
Color.parseColor(it.color)
} catch (e: IllegalArgumentException) {
resources.getColor(R.color.colorPrimary)
}
gd.setColor(gdColor)
gd.shape = GradientDrawable.RECTANGLE
gd.setSize(30, 30)
gd.cornerRadius = 30F
val drawerItem = PrimaryDrawerItem()
.apply {
nameText = it.tag.getHtmlDecoded()
identifier = it.tag.longHash()
iconDrawable = gd
badgeStyle = BadgeStyle().apply {
textColor = ColorHolder.fromColor(Color.WHITE)
color = ColorHolder.fromColor(resources.getColor(R.color.colorAccent))
}
onDrawerItemClickListener = { _, _, _ ->
repository.tagFilter = it
repository.sourceFilter = null
getElementsAccordingToTab()
fetchOnEmptyList()
false
}
}
if (it.unread > 0) {
drawerItem.badgeText = it.unread.toString()
}
binding.mainDrawer.itemAdapter.add(drawerItem)
}
private fun handleTags(tags: List<SelfossModel.Tag>) {
val filteredTags = tags
.filterNot { appSettingsService.getHiddenTags().contains(it.tag) }
.sortedBy { it.tag }
createTagItems(filteredTags)
}
private fun handleHiddenTags(tags: List<SelfossModel.Tag>) {
val filteredHiddenTags: List<SelfossModel.Tag> =
tags.filter { appSettingsService.getHiddenTags().contains(it.tag) }
createTagItems(filteredHiddenTags)
}
private fun createTagItems(tags: List<SelfossModel.Tag>) {
tagsBadge = tags.associate {
createDrawerItem(it)
(it.tag.longHash() to it.unread)
}
}
private fun handleSources(sources: List<SelfossModel.Source>) {
for (source in sources) {
val item = PrimaryDrawerItem().apply {
nameText = source.title.getHtmlDecoded()
identifier = source.id.toLong()
iconUrl = source.getIcon(repository.baseUrl)
onDrawerItemClickListener = { _,_,_ ->
repository.sourceFilter = source
repository.tagFilter = null
getElementsAccordingToTab()
fetchOnEmptyList()
false
}
}
binding.mainDrawer.itemAdapter.add(item)
alertDialog.show()
}
}
@ -615,9 +371,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
})
}
private fun fetchOnEmptyList() {
fun fetchOnEmptyList() {
binding.recyclerView.doOnNextLayout {
// TODO: do if last element (or is empty ?)
getElementsAccordingToTab(true)
}
}
@ -656,7 +411,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
binding.emptyText.visibility = View.GONE
}
private fun getElementsAccordingToTab(
fun getElementsAccordingToTab(
appendResults: Boolean = false
) {
offset = if (appendResults && items.size > 0) {
@ -731,36 +486,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun reloadBadges() {
if (appSettingsService.isDisplayUnreadCountEnabled() || appSettingsService.isDisplayAllCountEnabled()) {
CoroutineScope(Dispatchers.Main).launch {
CoroutineScope(Dispatchers.IO).launch {
repository.reloadBadges()
reloadBadgeContent()
}
}
}
private fun reloadBadgeContent() {
if (appSettingsService.isDisplayUnreadCountEnabled()) {
tabNewBadge
.setText(repository.badgeUnread.toString())
.maybeShow()
}
if (appSettingsService.isDisplayAllCountEnabled()) {
tabArchiveBadge
.setText(repository.badgeAll.toString())
.maybeShow()
tabStarredBadge
.setText(repository.badgeStarred.toString())
.maybeShow()
}
}
private fun reloadTagsBadges() {
tagsBadge.forEach {
binding.mainDrawer.updateBadge(it.key, StringHolder(it.value.toString()))
}
binding.mainDrawer.resetDrawerContent()
}
private fun calculateNoOfColumns(): Int {
val displayMetrics = resources.displayMetrics
val dpWidth = displayMetrics.widthPixels / displayMetrics.density
@ -786,6 +517,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.home_menu, menu)
if (appSettingsService.getPublicAccess()) {
menu.removeItem(R.id.readAll)
menu.removeItem(R.id.action_sources)
}
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.getActionView() as SearchView
@ -806,14 +541,17 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_filter -> {
val filterSheetFragment = FilterSheetFragment()
filterSheetFragment.show(supportFragmentManager, FilterSheetFragment.TAG)
return true
}
R.id.refresh -> {
needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
// TODO: Use Dispatchers.IO
CoroutineScope(Dispatchers.Main).launch {
val updatedRemote = repository.updateRemote()
if (updatedRemote) {
// TODO: Send toast messages from the repository
Toast.makeText(
this@HomeActivity,
R.string.refresh_success_response, Toast.LENGTH_LONG
@ -845,8 +583,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
).show()
tabNewBadge.removeBadge()
handleDrawerItems()
getElementsAccordingToTab()
} else {
Toast.makeText(
@ -863,10 +599,20 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
return true
}
R.id.action_disconnect -> {
appSettingsService.clearAll()
runBlocking {
repository.logout()
}
val intent = Intent(this, LoginActivity::class.java)
this.startActivity(intent)
this@HomeActivity.finish()
finish()
return true
}
R.id.action_settings -> {
settingsLauncher.launch(Intent(this, SettingsActivity::class.java))
return true
}
R.id.action_sources -> {
startActivity(Intent(this, SourcesActivity::class.java))
return true
}
else -> return super.onOptionsItemSelected(item)
@ -875,10 +621,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun maxItemNumber(): Int =
when (elementsShown) {
ItemType.UNREAD -> repository.badgeUnread
ItemType.ALL -> repository.badgeAll
ItemType.STARRED -> repository.badgeStarred
else -> repository.badgeUnread // if !elementsShown then unread are fetched.
ItemType.UNREAD -> repository.badgeUnread.value
ItemType.ALL -> repository.badgeAll.value
ItemType.STARRED -> repository.badgeStarred.value
else -> repository.badgeUnread.value // if !elementsShown then unread are fetched.
}
private fun updateItems(adapterItems: ArrayList<SelfossModel.Item>) {

View File

@ -3,6 +3,7 @@ package bou.amine.apps.readerforselfossv2.android
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
@ -23,7 +24,6 @@ class ImageActivity : AppCompatActivity() {
setContentView(view)
setSupportActionBar(binding.toolBar)
supportActionBar?.setDisplayShowTitleEnabled(false)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
allImages = intent.getStringArrayListExtra("allImages") as ArrayList<String>
@ -31,12 +31,35 @@ class ImageActivity : AppCompatActivity() {
binding.pager.adapter = ScreenSlidePagerAdapter(this)
binding.pager.setCurrentItem(position, false)
val transitionListener = object : MotionLayout.TransitionListener {
override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {
// Nothing
}
override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float
) {
// Nothing
}
override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
if (motionLayout?.currentState == binding.root.endState) {
onBackPressedDispatcher.onBackPressed()
overridePendingTransition(0, 0)
}
}
override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) {
// Nothing
}
}
binding.root.setTransitionListener(transitionListener)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
onBackPressed()
onBackPressedDispatcher.onBackPressed()
return true
}
}

View File

@ -2,6 +2,7 @@ package bou.amine.apps.readerforselfossv2.android
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.text.TextUtils
@ -10,6 +11,7 @@ import android.view.MenuItem
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
@ -21,10 +23,12 @@ import com.mikepenz.aboutlibraries.LibsBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.acra.ACRA
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
class LoginActivity : AppCompatActivity(), DIAware {
private var inValidCount: Int = 0
@ -33,12 +37,14 @@ class LoginActivity : AppCompatActivity(), DIAware {
private lateinit var binding: ActivityLoginBinding
override val di by closestDI()
private val repository : Repository by instance()
private val appSettingsService : AppSettingsService by instance()
private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
handleTheme()
binding = ActivityLoginBinding.inflate(layoutInflater)
val view = binding.root
@ -50,12 +56,40 @@ class LoginActivity : AppCompatActivity(), DIAware {
handleBaseUrlFail()
if (appSettingsService.getBaseUrl().isNotEmpty()) {
goToMain()
showProgress(true)
// This should be reverted when "old" users connected with a non-selfoss rss
// are handled. Revert to "simple" way.
CoroutineScope(Dispatchers.Main).launch {
try {
val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance()
if (!errorFetching && !displaySelfossOnly) {
goToMain()
} else {
showProgress(false)
if (displaySelfossOnly) {
Toast.makeText(
applicationContext,
R.string.application_selfoss_only,
Toast.LENGTH_LONG
).show()
}
repository.logout()
}
} catch (e: Throwable) {
repository.logout()
showProgress(false)
}
}
}
handleActions()
}
@SuppressLint("WrongConstant") // Constant is fetched from the settings
private fun handleTheme() {
AppCompatDelegate.setDefaultNightMode(appSettingsService.getCurrentTheme())
}
private fun handleActions() {
binding.passwordView.setOnEditorActionListener(
@ -93,12 +127,16 @@ class LoginActivity : AppCompatActivity(), DIAware {
}
private fun goToMain() {
CoroutineScope(Dispatchers.Main).launch {
repository.updateApiInformation()
ACRA.errorReporter.putCustomData("SELFOSS_API_VERSION", appSettingsService.getApiVersion().toString())
}
val intent = Intent(this, HomeActivity::class.java)
startActivity(intent)
finish()
}
private fun preferenceError(t: Throwable) {
private fun preferenceError() {
appSettingsService.resetLoginInformation()
binding.urlView.error = getString(R.string.wrong_infos)
@ -118,13 +156,64 @@ class LoginActivity : AppCompatActivity(), DIAware {
val login = binding.loginView.text.toString().trim()
val password = binding.passwordView.text.toString().trim()
var cancel = false
var focusView: View? = null
failInvalidUrl(url)
failLoginDetails(password, login)
showProgress(true)
repository.refreshLoginInformation(url, login, password)
CoroutineScope(Dispatchers.Main).launch {
val result = repository.login()
if (result) {
val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance()
if (!errorFetching && !displaySelfossOnly) {
goToMain()
} else {
if (displaySelfossOnly) {
Toast.makeText(
applicationContext,
R.string.application_selfoss_only,
Toast.LENGTH_LONG
).show()
}
preferenceError()
}
} else {
preferenceError()
}
showProgress(false)
}
}
private fun failLoginDetails(
password: String,
login: String
) {
var lastFocusedView: View? = null
var cancel = false
if (isWithLogin) {
if (TextUtils.isEmpty(password)) {
binding.passwordView.error = getString(R.string.error_invalid_password)
lastFocusedView = binding.passwordView
cancel = true
}
if (TextUtils.isEmpty(login)) {
binding.loginView.error = getString(R.string.error_field_required)
lastFocusedView = binding.loginView
cancel = true
}
}
maybeCancelAndFocusView(cancel, lastFocusedView)
}
private fun failInvalidUrl(url: String) {
val focusView = binding.urlView
var cancel = false
if (url.isBaseUrlInvalid()) {
binding.urlView.error = getString(R.string.login_url_problem)
focusView = binding.urlView
cancel = true
binding.urlView.error = getString(R.string.login_url_problem)
inValidCount++
if (inValidCount == 3) {
val alertDialog = AlertDialog.Builder(this).create()
@ -138,40 +227,12 @@ class LoginActivity : AppCompatActivity(), DIAware {
inValidCount = 0
}
}
maybeCancelAndFocusView(cancel, focusView)
}
if (isWithLogin) {
if (TextUtils.isEmpty(password)) {
binding.passwordView.error = getString(R.string.error_invalid_password)
focusView = binding.passwordView
cancel = true
}
if (TextUtils.isEmpty(login)) {
binding.loginView.error = getString(R.string.error_field_required)
focusView = binding.loginView
cancel = true
}
}
private fun maybeCancelAndFocusView(cancel: Boolean, focusView: View?) {
if (cancel) {
focusView?.requestFocus()
} else {
showProgress(true)
repository.refreshLoginInformation(url, login, password)
CoroutineScope(Dispatchers.IO).launch {
val result = repository.login()
if (result) {
repository.updateApiVersion()
goToMain()
} else {
CoroutineScope(Dispatchers.Main).launch {
preferenceError(Exception("Not success"))
}
}
}
showProgress(false)
}
}
@ -185,11 +246,11 @@ class LoginActivity : AppCompatActivity(), DIAware {
.alpha(
if (show) 0F else 1F
).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE
override fun onAnimationEnd(animation: Animator) {
binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE
}
}
}
)
)
binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
binding.loginProgress
@ -198,11 +259,11 @@ class LoginActivity : AppCompatActivity(), DIAware {
.alpha(
if (show) 1F else 0F
).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
override fun onAnimationEnd(animation: Animator) {
binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
}
}
}
)
)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {

View File

@ -3,41 +3,49 @@ package bou.amine.apps.readerforselfossv2.android
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.widget.ImageView
import android.widget.Toast
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.multidex.MultiDexApplication
import androidx.preference.PreferenceManager
import bou.amine.apps.readerforselfossv2.DI.networkModule
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.github.ln_12.library.ConnectivityStatus
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader
import io.github.aakira.napier.DebugAntilog
import io.github.aakira.napier.Napier
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import org.acra.ACRA
import org.acra.ReportField
import org.acra.config.httpSender
import org.acra.config.toast
import org.acra.data.StringFormat
import org.acra.ktx.initAcra
import org.acra.sender.HttpSender
import org.kodein.di.*
class MyApp : MultiDexApplication(), DIAware {
override val di by DI.lazy {
bind<AppSettingsService>() with singleton { AppSettingsService(ACRA.isACRASenderServiceProcess()) }
import(networkModule)
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
bind<Repository>() with singleton { Repository(instance(), instance(), connectivityStatus, instance()) }
bind<Repository>() with singleton {
Repository(
instance(),
instance(),
isConnectionAvailable,
instance()
)
}
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
}
@ -47,35 +55,87 @@ class MyApp : MultiDexApplication(), DIAware {
private val connectivityStatus: ConnectivityStatus by instance()
private val driverFactory: DriverFactory by instance()
// TODO: handle with the "previous" way
private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true)
override fun onCreate() {
super.onCreate()
Napier.base(DebugAntilog())
initDrawerImageLoader()
if (!ACRA.isACRASenderServiceProcess()) {
tryToHandleBug()
tryToHandleBug()
handleNotificationChannels()
handleNotificationChannels()
ProcessLifecycleOwner.get().lifecycle.addObserver(
AppLifeCycleObserver(
connectivityStatus,
repository
)
)
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(connectivityStatus, repository))
CoroutineScope(Dispatchers.Main).launch {
viewModel.networkAvailableProvider.collect { networkAvailable ->
val toastMessage = if (networkAvailable) {
repository.handleDBActions()
R.string.network_connectivity_retrieved
} else {
R.string.network_connectivity_lost
}
CoroutineScope(Dispatchers.Main).launch {
viewModel.networkAvailableProvider.collect { networkAvailable ->
val toastMessage = if (networkAvailable) {
repository.handleDBActions()
R.string.network_connectivity_retrieved
} else {
R.string.network_connectivity_lost
Toast.makeText(
applicationContext,
toastMessage,
Toast.LENGTH_SHORT
).show()
}
Toast.makeText(
applicationContext,
toastMessage,
Toast.LENGTH_SHORT
).show()
}
}
repository.migrate(driverFactory)
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
initAcra {
reportFormat = StringFormat.JSON
reportContent = listOf(
ReportField.REPORT_ID,
ReportField.INSTALLATION_ID,
ReportField.APP_VERSION_CODE,
ReportField.APP_VERSION_NAME,
ReportField.BUILD,
ReportField.ANDROID_VERSION,
ReportField.BRAND,
ReportField.PHONE_MODEL,
ReportField.AVAILABLE_MEM_SIZE,
ReportField.TOTAL_MEM_SIZE,
ReportField.STACK_TRACE,
ReportField.APPLICATION_LOG,
ReportField.LOGCAT,
ReportField.INITIAL_CONFIGURATION,
ReportField.CRASH_CONFIGURATION,
ReportField.IS_SILENT,
ReportField.USER_APP_START_DATE,
ReportField.USER_COMMENT,
ReportField.USER_CRASH_DATE,
ReportField.USER_EMAIL,
ReportField.CUSTOM_DATA
)
toast {
//required
text = getString(R.string.crash_toast_text)
length = Toast.LENGTH_SHORT
}
httpSender {
uri =
"https://bugs.amine-louveau.fr/report" /*best guess, you may need to adjust this*/
basicAuthLogin = "qMEscjj89Gwt6cPR"
basicAuthPassword = "Yo58QFlGzFaWlBzP"
httpMethod = HttpSender.Method.POST
}
}
}
private fun handleNotificationChannels() {
@ -88,47 +148,35 @@ class MyApp : MultiDexApplication(), DIAware {
val newItemsChannelname = getString(R.string.new_items_channel_sync)
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
val newItemsChannelmChannel = NotificationChannel(AppSettingsService.newItemsChannelId, newItemsChannelname, newItemsChannelimportance)
val newItemsChannelmChannel = NotificationChannel(
AppSettingsService.newItemsChannelId,
newItemsChannelname,
newItemsChannelimportance
)
notificationManager.createNotificationChannel(mChannel)
notificationManager.createNotificationChannel(newItemsChannelmChannel)
}
}
private fun initDrawerImageLoader() {
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) {
Glide.with(imageView.context)
.load(uri.toString())
.apply(RequestOptions.fitCenterTransform().placeholder(placeholder))
.into(imageView)
}
override fun cancel(imageView: ImageView) {
Glide.with(imageView.context).clear(imageView)
}
override fun placeholder(ctx: Context, tag: String?): Drawable {
return baseContext.resources.getDrawable(R.mipmap.ic_launcher)
}
})
}
private fun tryToHandleBug() {
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { thread, e ->
if (e is java.lang.NoClassDefFoundError && e.stackTrace.asList().any {
if (e is NoClassDefFoundError && e.stackTrace.asList().any {
it.toString().contains("android.view.ViewDebug")
}) {
Unit
// Nothing
} else {
oldHandler.uncaughtException(thread, e)
}
}
}
class AppLifeCycleObserver(val connectivityStatus: ConnectivityStatus, val repository: Repository) : DefaultLifecycleObserver {
class AppLifeCycleObserver(
val connectivityStatus: ConnectivityStatus,
val repository: Repository
) : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)

View File

@ -30,15 +30,17 @@ class ReaderActivity : AppCompatActivity(), DIAware {
private lateinit var binding: ActivityReaderBinding
private var allItems: ArrayList<SelfossModel.Item> = ArrayList()
override val di by closestDI()
private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
private fun showMenuItem(willAddToFavorite: Boolean) {
if (willAddToFavorite) {
toolbarMenu.findItem(R.id.star).icon.setTint(Color.WHITE)
toolbarMenu.findItem(R.id.star).icon?.setTint(Color.WHITE)
} else {
toolbarMenu.findItem(R.id.star).icon.setTint(Color.RED)
toolbarMenu.findItem(R.id.star).icon?.setTint(Color.RED)
}
}
@ -61,12 +63,14 @@ class ReaderActivity : AppCompatActivity(), DIAware {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
if (allItems.isEmpty()) {
currentItem = intent.getIntExtra("currentItem", 0)
allItems = repository.getReaderItems()
if (allItems.isEmpty() || currentItem > allItems.size) {
finish()
}
currentItem = intent.getIntExtra("currentItem", 0)
readItem(allItems[currentItem])
binding.pager.adapter = ScreenSlidePagerAdapter(this)
@ -80,7 +84,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
}
private fun readItem(item: SelfossModel.Item) {
if (appSettingsService.isMarkOnScrollEnabled()) {
if (appSettingsService.isMarkOnScrollEnabled() && !appSettingsService.getPublicAccess()) {
CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(item)
}
@ -133,28 +137,34 @@ class ReaderActivity : AppCompatActivity(), DIAware {
inflater.inflate(R.menu.reader_menu, menu)
toolbarMenu = menu
if (allItems.isNotEmpty() && allItems[currentItem].starred) {
canRemoveFromFavorite()
} else {
canFavorite()
}
alignmentMenu()
binding.pager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
if (allItems[position].starred) {
canRemoveFromFavorite()
} else {
canFavorite()
}
readItem(allItems[position])
}
if (appSettingsService.getPublicAccess()) {
menu.removeItem(R.id.star)
} else {
if (allItems.isNotEmpty() && allItems[currentItem].starred) {
canRemoveFromFavorite()
} else {
canFavorite()
}
)
binding.pager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
if (allItems[position].starred) {
canRemoveFromFavorite()
} else {
canFavorite()
}
readItem(allItems[position])
}
}
)
}
return true
}
@ -173,20 +183,18 @@ class ReaderActivity : AppCompatActivity(), DIAware {
when (item.itemId) {
android.R.id.home -> {
onBackPressed()
onBackPressedDispatcher.onBackPressed()
return true
}
R.id.star -> {
if (allItems[binding.pager.currentItem].starred) {
CoroutineScope(Dispatchers.IO).launch {
repository.unstarr(allItems[binding.pager.currentItem])
// TODO: Handle failure
}
afterUnsave()
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.starr(allItems[binding.pager.currentItem])
// TODO: Handle failure
}
afterSave()
}
@ -214,8 +222,4 @@ class ReaderActivity : AppCompatActivity(), DIAware {
startActivity(intent)
overridePendingTransition(0, 0)
}
companion object {
var allItems: ArrayList<SelfossModel.Item> = ArrayList()
}
}

View File

@ -56,20 +56,13 @@ class SourcesActivity : AppCompatActivity(), DIAware {
CoroutineScope(Dispatchers.Main).launch {
val response = repository.getSources()
if (response != null) {
if (response.isNotEmpty()) {
items = response
val mAdapter = SourcesListAdapter(
this@SourcesActivity, items
)
binding.recyclerView.adapter = mAdapter
mAdapter.notifyDataSetChanged()
if (items.isEmpty()) {
Toast.makeText(
this@SourcesActivity,
R.string.nothing_here,
Toast.LENGTH_SHORT
).show()
}
} else {
Toast.makeText(
this@SourcesActivity,
@ -80,7 +73,7 @@ class SourcesActivity : AppCompatActivity(), DIAware {
}
binding.fab.setOnClickListener {
startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java))
startActivity(Intent(this@SourcesActivity, UpsertSourceActivity::class.java))
}
}
}

View File

@ -3,12 +3,15 @@ package bou.amine.apps.readerforselfossv2.android
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.*
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityUpsertSourceBinding
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import kotlinx.coroutines.CoroutineScope
@ -19,38 +22,59 @@ import org.kodein.di.android.closestDI
import org.kodein.di.instance
class AddSourceActivity : AppCompatActivity(), DIAware {
class UpsertSourceActivity : AppCompatActivity(), DIAware {
private var existingSource: SelfossModel.Source? = null
private var mSpoutsValue: String? = null
private lateinit var binding: ActivityAddSourceBinding
private lateinit var binding: ActivityUpsertSourceBinding
override val di by closestDI()
private val repository : Repository by instance()
private val appSettingsService : AppSettingsService by instance()
private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAddSourceBinding.inflate(layoutInflater)
binding = ActivityUpsertSourceBinding.inflate(layoutInflater)
val view = binding.root
existingSource = repository.getSelectedSource()
if (existingSource != null) {
binding.formContainer.visibility = View.GONE
binding.progress.visibility = View.VISIBLE
}
val title = if (existingSource == null) R.string.add_source else R.string.update_source
supportFragmentManager.addOnBackStackChangedListener {
if (supportFragmentManager.backStackEntryCount == 0) {
setTitle(title)
}
}
setContentView(view)
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
supportActionBar?.title = resources.getString(title)
maybeGetDetailsFromIntentSharing(intent, binding.sourceUri, binding.nameInput)
maybeGetDetailsFromIntentSharing(intent)
binding.saveBtn.setOnClickListener {
handleSaveSource(
binding.tags,
binding.nameInput.text.toString(),
binding.sourceUri.text.toString()
)
handleSaveSource()
}
}
private fun initFields(items: Map<String, SelfossModel.Spout>) {
binding.nameInput.setText(existingSource!!.title)
binding.tags.setText(existingSource!!.tags.joinToString(", "))
binding.sourceUri.setText(existingSource!!.params?.url)
binding.spoutsSpinner.setSelection(items.keys.indexOf(existingSource!!.spout))
binding.progress.visibility = View.GONE
binding.formContainer.visibility = View.VISIBLE
}
override fun onResume() {
super.onResume()
@ -58,17 +82,13 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
if (baseUrl.isEmpty() || baseUrl.isBaseUrlInvalid()) {
mustLoginToAddSource()
} else {
handleSpoutsSpinner(binding.spoutsSpinner, binding.progress, binding.formContainer)
handleSpoutsSpinner()
}
}
private fun handleSpoutsSpinner(
spoutsSpinner: Spinner,
mProgress: ProgressBar,
formContainer: ConstraintLayout
) {
private fun handleSpoutsSpinner() {
val spoutsKV = HashMap<String, String>()
spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
binding.spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(adapterView: AdapterView<*>, view: View?, i: Int, l: Long) {
if (view != null) {
val spoutName = (view as TextView).text.toString()
@ -84,33 +104,37 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
fun handleSpoutFailure(networkIssue: Boolean = false) {
Toast.makeText(
this@AddSourceActivity,
this@UpsertSourceActivity,
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
Toast.LENGTH_SHORT
).show()
mProgress.visibility = View.GONE
binding.progress.visibility = View.GONE
}
CoroutineScope(Dispatchers.Main).launch {
try {
val items = repository.getSpouts()
if (items != null) {
if (items.isNotEmpty()) {
val itemsStrings = items.map { it.value.name }
for ((key, value) in items) {
spoutsKV[value.name] = key
}
mProgress.visibility = View.GONE
formContainer.visibility = View.VISIBLE
binding.progress.visibility = View.GONE
binding.formContainer.visibility = View.VISIBLE
val spinnerArrayAdapter =
ArrayAdapter(
this@AddSourceActivity,
this@UpsertSourceActivity,
android.R.layout.simple_spinner_item,
itemsStrings
)
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spoutsSpinner.adapter = spinnerArrayAdapter
binding.spoutsSpinner.adapter = spinnerArrayAdapter
if (existingSource != null) {
initFields(items)
}
} else {
handleSpoutFailure()
}
@ -121,13 +145,11 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
}
private fun maybeGetDetailsFromIntentSharing(
intent: Intent,
sourceUri: EditText,
nameInput: EditText
intent: Intent
) {
if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
binding.sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
binding.nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
}
}
@ -138,7 +160,8 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
finish()
}
private fun handleSaveSource(tags: EditText, title: String, url: String) {
private fun handleSaveSource() {
val url = binding.sourceUri.text.toString()
val sourceDetailsUnavailable =
title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()
@ -149,18 +172,27 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
}
else -> {
CoroutineScope(Dispatchers.Main).launch {
val successfullyAddedSource = repository.createSource(
title,
url,
mSpoutsValue!!,
tags.text.toString(),
"",
)
val successfullyAddedSource = if (existingSource != null) {
repository.updateSource(
existingSource!!.id,
binding.nameInput.text.toString(),
url,
mSpoutsValue!!,
binding.tags.text.toString()
)
} else {
repository.createSource(
binding.nameInput.text.toString(),
url,
mSpoutsValue!!,
binding.tags.text.toString(),
)
}
if (successfullyAddedSource) {
finish()
} else {
Toast.makeText(
this@AddSourceActivity,
this@UpsertSourceActivity,
R.string.cant_create_source,
Toast.LENGTH_SHORT
).show()
@ -169,4 +201,9 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
}
}
}
override fun onDestroy() {
super.onDestroy()
repository.unsetSelectedSource()
}
}

View File

@ -10,9 +10,12 @@ import androidx.recyclerview.widget.RecyclerView
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.CardItemBinding
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
import bou.amine.apps.readerforselfossv2.android.utils.*
import bou.amine.apps.readerforselfossv2.android.utils.LinkOnTouchListener
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapCenterCrop
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
@ -53,13 +56,17 @@ class ItemCardAdapter(
val itm = items[position]
binding.favButton.isSelected = itm.starred
if (appSettingsService.getPublicAccess()) {
binding.favButton.visibility = View.GONE
}
binding.title.text = itm.title.getHtmlDecoded()
binding.title.setOnTouchListener(LinkOnTouchListener())
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = itm.sourceAndDateText(repository.dateUtils)
binding.sourceTitleAndDate.text = itm.sourceAuthorAndDate()
if (!appSettingsService.isFullHeightCardsEnabled()) {
binding.itemImage.maxHeight = imageMaxHeight
@ -108,13 +115,11 @@ class ItemCardAdapter(
CoroutineScope(Dispatchers.IO).launch {
repository.unstarr(item)
}
item.starred = false
binding.favButton.isSelected = false
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.starr(item)
}
item.starred = true
binding.favButton.isSelected = true
}
}
@ -131,8 +136,8 @@ class ItemCardAdapter(
private fun handleLinkOpening() {
binding.root.setOnClickListener {
repository.setReaderItems(items)
c.openItemUrl(
items,
bindingAdapterPosition,
items[bindingAdapterPosition].getLinkDecoded(),
appSettingsService.isArticleViewerEnabled(),

View File

@ -51,7 +51,7 @@ class ItemListAdapter(
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = itm.sourceAndDateText(repository.dateUtils)
binding.sourceTitleAndDate.text = itm.sourceAuthorAndDate()
if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
@ -84,8 +84,8 @@ class ItemListAdapter(
private fun handleLinkOpening() {
binding.root.setOnClickListener {
repository.setReaderItems(items)
c.openItemUrl(
items,
bindingAdapterPosition,
items[bindingAdapterPosition].getLinkDecoded(),
appSettingsService.isArticleViewerEnabled(),

View File

@ -28,7 +28,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
updateItems(this.items)
}
private fun unmarkSnackbar(position: Int) {
private fun unmarkSnackbar(item: SelfossModel.Item, position: Int) {
val s = Snackbar
.make(
app.findViewById(R.id.coordLayout),
@ -36,9 +36,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
Snackbar.LENGTH_LONG
)
.setAction(R.string.undo_string) {
CoroutineScope(Dispatchers.IO).launch {
unreadItemAtIndex(position, false)
}
unreadItemAtIndex(item, position, false)
}
val view = s.view
@ -47,7 +45,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
s.show()
}
private fun markSnackbar(position: Int) {
private fun markSnackbar(item: SelfossModel.Item, position: Int) {
val s = Snackbar
.make(
app.findViewById(R.id.coordLayout),
@ -55,7 +53,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
Snackbar.LENGTH_LONG
)
.setAction(R.string.undo_string) {
readItemAtIndex(position)
readItemAtIndex(item, position, false)
}
val view = s.view
@ -66,37 +64,36 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
fun handleItemAtIndex(position: Int) {
if (items[position].unread) {
readItemAtIndex(position)
readItemAtIndex(items[position], position)
} else {
unreadItemAtIndex(position)
unreadItemAtIndex(items[position], position)
}
}
private fun readItemAtIndex(position: Int, showSnackbar: Boolean = true) {
val i = items[position]
private fun readItemAtIndex(item: SelfossModel.Item, position: Int, showSnackbar: Boolean = true) {
CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(i)
repository.markAsRead(item)
}
if (repository.displayedItems == ItemType.UNREAD) {
items.remove(i)
items.remove(item)
notifyItemRemoved(position)
updateItems(items)
} else {
notifyItemChanged(position)
}
if (showSnackbar) {
unmarkSnackbar(position)
unmarkSnackbar(item, position)
}
}
private fun unreadItemAtIndex(position: Int, showSnackbar: Boolean = true) {
private fun unreadItemAtIndex(item: SelfossModel.Item, position: Int, showSnackbar: Boolean = true) {
CoroutineScope(Dispatchers.IO).launch {
repository.unmarkAsRead(items[position])
repository.unmarkAsRead(item)
}
notifyItemChanged(position)
if (showSnackbar) {
markSnackbar(position)
markSnackbar(item, position)
}
}

View File

@ -2,12 +2,15 @@ package bou.amine.apps.readerforselfossv2.android.adapters
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.Toast
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import bou.amine.apps.readerforselfossv2.android.UpsertSourceActivity
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
@ -58,12 +61,23 @@ class SourcesListAdapter(
c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
}
if (itm.error.isNotBlank()) {
binding.errorText.visibility = View.VISIBLE
binding.errorText.text = itm.error
} else {
binding.errorText.visibility = View.GONE
}
binding.sourceTitle.text = itm.title.getHtmlDecoded()
}
override fun getItemId(position: Int) = position.toLong()
override fun getItemViewType(position: Int) = position
override fun getItemCount(): Int = items.size
inner class ViewHolder(internal val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
inner class ViewHolder(private val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
init {
handleClickListeners()
@ -74,13 +88,13 @@ class SourcesListAdapter(
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
deleteBtn.setOnClickListener {
val (id) = items[adapterPosition]
val (id, title) = items[bindingAdapterPosition]
CoroutineScope(Dispatchers.IO).launch {
val successfullyDeletedSource = repository.deleteSource(id)
val successfullyDeletedSource = repository.deleteSource(id, title)
if (successfullyDeletedSource) {
items.removeAt(adapterPosition)
notifyItemRemoved(adapterPosition)
notifyItemRangeChanged(adapterPosition, itemCount)
items.removeAt(bindingAdapterPosition)
notifyItemRemoved(bindingAdapterPosition)
notifyItemRangeChanged(bindingAdapterPosition, itemCount)
} else {
Toast.makeText(
app,
@ -90,6 +104,14 @@ class SourcesListAdapter(
}
}
}
mView.setOnClickListener {
val source = items[bindingAdapterPosition]
repository.setSelectedSource(source)
app.startActivity(Intent(app, UpsertSourceActivity::class.java))
}
}
}
}

View File

@ -1,35 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.api.mercury
import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class MercuryApi() {
private val service: MercuryService
init {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.NONE
val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
val gson = GsonBuilder()
.setLenient()
.create()
val retrofit =
Retrofit
.Builder()
.baseUrl("https://www.amine-louveau.fr")
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
service = retrofit.create(MercuryService::class.java)
}
fun parseUrl(url: String): Call<ParsedContent> {
return service.parseUrl(url)
}
}

View File

@ -1,59 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.api.mercury
import android.os.Parcel
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
class ParsedContent(
@SerializedName("title") val title: String,
@SerializedName("content") val content: String?,
@SerializedName("date_published") val date_published: String,
@SerializedName("lead_image_url") val lead_image_url: String?,
@SerializedName("dek") val dek: String,
@SerializedName("url") val url: String,
@SerializedName("domain") val domain: String,
@SerializedName("excerpt") val excerpt: String,
@SerializedName("total_pages") val total_pages: Int,
@SerializedName("rendered_pages") val rendered_pages: Int,
@SerializedName("next_page_url") val next_page_url: String
) : Parcelable {
companion object {
@JvmField
val CREATOR: Parcelable.Creator<ParsedContent> =
object : Parcelable.Creator<ParsedContent> {
override fun createFromParcel(source: Parcel): ParsedContent = ParsedContent(source)
override fun newArray(size: Int): Array<ParsedContent?> = arrayOfNulls(size)
}
}
constructor(source: Parcel) : this(
title = source.readString().orEmpty(),
content = source.readString(),
date_published = source.readString().orEmpty(),
lead_image_url = source.readString(),
dek = source.readString().orEmpty(),
url = source.readString().orEmpty(),
domain = source.readString().orEmpty(),
excerpt = source.readString().orEmpty(),
total_pages = source.readInt(),
rendered_pages = source.readInt(),
next_page_url = source.readString().orEmpty()
)
override fun describeContents() = 0
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(title)
dest.writeString(content)
dest.writeString(date_published)
dest.writeString(lead_image_url)
dest.writeString(dek)
dest.writeString(url)
dest.writeString(domain)
dest.writeString(excerpt)
dest.writeInt(total_pages)
dest.writeInt(rendered_pages)
dest.writeString(next_page_url)
}
}

View File

@ -1,10 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.api.mercury
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query
interface MercuryService {
@GET("parser.php")
fun parseUrl(@Query("link") link: String): Call<ParsedContent>
}

View File

@ -26,83 +26,89 @@ import org.kodein.di.instance
import java.util.*
import kotlin.concurrent.schedule
class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params), DIAware {
class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params),
DIAware {
override val di by lazy { (applicationContext as MyApp).di }
private val repository : Repository by instance()
private val appSettingsService : AppSettingsService by instance()
private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
override fun doWork(): Result {
if (appSettingsService.isPeriodicRefreshEnabled() && isNetworkAccessible(context)) {
override fun doWork(): Result {
if (appSettingsService.isPeriodicRefreshEnabled() && isNetworkAccessible(context)) {
CoroutineScope(Dispatchers.IO).launch {
val notificationManager =
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
CoroutineScope(Dispatchers.IO).launch {
val notificationManager =
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notification =
NotificationCompat.Builder(applicationContext, AppSettingsService.syncChannelId)
.setContentTitle(context.getString(R.string.loading_notification_title))
.setContentText(context.getString(R.string.loading_notification_text))
.setOngoing(true)
.setPriority(PRIORITY_LOW)
.setChannelId(AppSettingsService.syncChannelId)
.setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
val notification =
NotificationCompat.Builder(applicationContext, AppSettingsService.syncChannelId)
.setContentTitle(context.getString(R.string.loading_notification_title))
.setContentText(context.getString(R.string.loading_notification_text))
.setOngoing(true)
.setPriority(PRIORITY_LOW)
.setChannelId(AppSettingsService.syncChannelId)
.setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
notificationManager.notify(1, notification.build())
notificationManager.notify(1, notification.build())
repository.handleDBActions()
repository.handleDBActions()
if (appSettingsService.isNotifyNewItemsEnabled()) {
launch {
handleNewItemsNotification(repository.tryToCacheItemsAndGetNewOnes(), notificationManager)
val apiItems = repository.tryToCacheItemsAndGetNewOnes()
if (appSettingsService.isNotifyNewItemsEnabled()) {
launch {
handleNewItemsNotification(apiItems, notificationManager)
}
}
apiItems.map { it.preloadImages(context) }
}
}
return Result.success()
}
return Result.success()
}
private fun handleNewItemsNotification(
newItems: List<SelfossModel.Item>?,
notificationManager: NotificationManager
) {
CoroutineScope(Dispatchers.IO).launch {
val apiItems = newItems.orEmpty()
val apiItems = newItems.orEmpty()
val newSize = apiItems.filter { it.unread }.size
if (newSize > 0) {
val newSize = apiItems.filter { it.unread }.size
if (newSize > 0) {
val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pflags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE
} else {
0
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, pflags)
val newItemsNotification =
NotificationCompat.Builder(applicationContext, AppSettingsService.newItemsChannelId)
.setContentTitle(context.getString(R.string.new_items_notification_title))
.setContentText(
context.getString(
R.string.new_items_notification_text,
newSize
)
)
.setPriority(PRIORITY_DEFAULT)
.setChannelId(AppSettingsService.newItemsChannelId)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
Timer("", false).schedule(4000) {
notificationManager.notify(2, newItemsNotification.build())
}
val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
apiItems.map { it.preloadImages(context) }
val pflags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE
} else {
0
}
val pendingIntent: PendingIntent =
PendingIntent.getActivity(context, 0, intent, pflags)
val newItemsNotification =
NotificationCompat.Builder(
applicationContext,
AppSettingsService.newItemsChannelId
)
.setContentTitle(context.getString(R.string.new_items_notification_title))
.setContentText(
context.getString(
R.string.new_items_notification_text,
newSize
)
)
.setPriority(PRIORITY_DEFAULT)
.setChannelId(AppSettingsService.newItemsChannelId)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
Timer("", false).schedule(4000) {
notificationManager.notify(2, newItemsNotification.build())
}
}
Timer("", false).schedule(4000) {
notificationManager.cancel(1)
}

View File

@ -16,22 +16,23 @@ import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.content.res.ResourcesCompat
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment
import bou.amine.apps.readerforselfossv2.android.ImageActivity
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.api.mercury.MercuryApi
import bou.amine.apps.readerforselfossv2.android.api.mercury.ParsedContent
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentArticleBinding
import bou.amine.apps.readerforselfossv2.android.model.ParecelableItem
import bou.amine.apps.readerforselfossv2.android.model.toModel
import bou.amine.apps.readerforselfossv2.android.model.toParcelable
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
import bou.amine.apps.readerforselfossv2.model.MercuryModel
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.model.StatusAndData
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.MercuryApi
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getImages
@ -49,15 +50,15 @@ import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.android.x.closestDI
import org.kodein.di.instance
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.net.MalformedURLException
import java.net.SocketTimeoutException
import java.net.URL
import java.util.*
import java.util.concurrent.ExecutionException
private const val IMAGE_JPG = "image/jpg"
class ArticleFragment : Fragment(), DIAware {
private var fontSize: Int = 16
private lateinit var item: SelfossModel.Item
@ -66,13 +67,12 @@ class ArticleFragment : Fragment(), DIAware {
private lateinit var contentSource: String
private lateinit var contentImage: String
private lateinit var contentTitle: String
private lateinit var allImages : ArrayList<String>
private lateinit var allImages: ArrayList<String>
private lateinit var fab: FloatingActionButton
private lateinit var textAlignment: String
private var _binding: FragmentArticleBinding? = null
private val binding get() = _binding!!
private lateinit var binding: FragmentArticleBinding
override val di : DI by closestDI()
override val di: DI by closestDI()
private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
@ -81,6 +81,9 @@ class ArticleFragment : Fragment(), DIAware {
private var font = ""
private var staticBar = false
private val mercuryApi: MercuryApi by instance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -95,29 +98,19 @@ class ArticleFragment : Fragment(), DIAware {
savedInstanceState: Bundle?
): View {
try {
_binding = FragmentArticleBinding.inflate(inflater, container, false)
binding = FragmentArticleBinding.inflate(inflater, container, false)
url = item.getLinkDecoded()
contentText = item.content
contentTitle = item.title.getHtmlDecoded()
contentImage = item.getThumbnail(repository.baseUrl)
contentSource = item.sourceAndDateText(repository.dateUtils)
contentSource = item.sourceAuthorAndDate()
allImages = item.getImages()
fontSize = appSettingsService.getFontSize()
staticBar = appSettingsService.isStaticBarEnabled()
font = appSettingsService.getFont()
if (font.isNotEmpty()) {
resId = requireContext().resources.getIdentifier(font, "font", requireContext().packageName)
typeface = try {
ResourcesCompat.getFont(requireContext(), resId)!!
} catch (e: java.lang.Exception) {
// Just to be sure
null
}
}
refreshAlignment()
fab = binding.fab
@ -126,48 +119,7 @@ class ArticleFragment : Fragment(), DIAware {
fab.rippleColor = resources.getColor(R.color.colorAccentDark)
val floatingToolbar: FloatingToolbar = binding.floatingToolbar
floatingToolbar.attachFab(fab)
floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
floatingToolbar.setClickListener(
object : FloatingToolbar.ItemClickListener {
override fun onItemClick(item: MenuItem) {
when (item.itemId) {
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
R.id.open_action -> requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
R.id.unread_action -> if (context != null) {
if (this@ArticleFragment.item.unread) {
CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = false
Toast.makeText(
context,
R.string.marked_as_read,
Toast.LENGTH_LONG
).show()
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.unmarkAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = true
Toast.makeText(
context,
R.string.marked_as_unread,
Toast.LENGTH_LONG
).show()
}
}
else -> Unit
}
}
override fun onItemLongClick(item: MenuItem?) {
}
}
)
val floatingToolbar: FloatingToolbar = handleFloatingToolbar()
if (staticBar) {
fab.hide()
@ -179,28 +131,7 @@ class ArticleFragment : Fragment(), DIAware {
binding.source.typeface = typeface
}
if (contentText.isEmptyOrNullOrNullString()) {
getContentFromMercury()
} else {
binding.titleView.text = contentTitle
if (typeface != null) {
binding.titleView.typeface = typeface
}
htmlToWebview()
if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
binding.imageView.visibility = View.VISIBLE
Glide
.with(requireContext())
.asBitmap()
.load(contentImage)
.apply(RequestOptions.fitCenterTransform())
.into(binding.imageView)
} else {
binding.imageView.visibility = View.GONE
}
}
handleContent()
binding.nestedScrollView.setOnScrollChangeListener(
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
@ -218,10 +149,12 @@ class ArticleFragment : Fragment(), DIAware {
)
} catch (e: InflateException) {
e.sendSilentlyWithAcraWithName("webview not available")
AlertDialog.Builder(requireContext())
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
.setPositiveButton(android.R.string.ok
.setPositiveButton(
android.R.string.ok
) { _, _ ->
appSettingsService.disableArticleViewer()
requireActivity().finish()
@ -233,9 +166,81 @@ class ArticleFragment : Fragment(), DIAware {
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
private fun handleContent() {
if (contentText.isEmptyOrNullOrNullString()) {
if (repository.isNetworkAvailable()) {
getContentFromMercury()
}
} else {
binding.titleView.text = contentTitle
if (typeface != null) {
binding.titleView.typeface = typeface
}
htmlToWebview()
if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
binding.imageView.visibility = View.VISIBLE
Glide
.with(requireContext())
.asBitmap()
.load(contentImage)
.apply(RequestOptions.fitCenterTransform())
.into(binding.imageView)
} else {
binding.imageView.visibility = View.GONE
}
}
}
private fun handleFloatingToolbar(): FloatingToolbar {
val floatingToolbar: FloatingToolbar = binding.floatingToolbar
if (appSettingsService.getPublicAccess()) {
floatingToolbar.setMenu(R.menu.reader_toolbar_no_read)
}
floatingToolbar.attachFab(fab)
floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
floatingToolbar.setClickListener(
object : FloatingToolbar.ItemClickListener {
override fun onItemClick(item: MenuItem) {
when (item.itemId) {
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
R.id.open_action -> requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
R.id.unread_action -> if (context != null) {
if (this@ArticleFragment.item.unread) {
CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = false
Toast.makeText(
context,
R.string.marked_as_read,
Toast.LENGTH_LONG
).show()
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.unmarkAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = true
Toast.makeText(
context,
R.string.marked_as_unread,
Toast.LENGTH_LONG
).show()
}
}
else -> Unit
}
}
override fun onItemLongClick(item: MenuItem?) {
// We do nothing
}
}
)
return floatingToolbar
}
private fun refreshAlignment() {
@ -247,90 +252,109 @@ class ArticleFragment : Fragment(), DIAware {
}
private fun getContentFromMercury() {
if (repository.isNetworkAvailable()) {
binding.progressBar.visibility = View.VISIBLE
val parser = MercuryApi()
binding.progressBar.visibility = View.VISIBLE
parser.parseUrl(url).enqueue(
object : Callback<ParsedContent> {
override fun onResponse(
call: Call<ParsedContent>,
response: Response<ParsedContent>
) {
// TODO: clean all the following after finding the mercury content issue
try {
if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) {
try {
binding.titleView.text = response.body()!!.title
if (typeface != null) {
binding.titleView.typeface = typeface
}
try {
// Note: Mercury may return relative urls... If it does the url val will not be changed.
URL(response.body()!!.url)
url = response.body()!!.url
} catch (e: MalformedURLException) {
// Mercury returned a relative url. We do nothing.
}
} catch (e: Exception) {
}
try {
contentText = response.body()!!.content.orEmpty()
htmlToWebview()
} catch (e: Exception) {
}
try {
if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isNullOrEmpty() && context != null) {
binding.imageView.visibility = View.VISIBLE
try {
Glide
.with(requireContext())
.asBitmap()
.load(
response.body()!!.lead_image_url.orEmpty()
)
.apply(RequestOptions.fitCenterTransform())
.into(binding.imageView)
} catch (e: Exception) {
}
} else {
binding.imageView.visibility = View.GONE
}
} catch (e: Exception) {
if (context != null) {
}
}
try {
binding.nestedScrollView.scrollTo(0, 0)
binding.progressBar.visibility = View.GONE
} catch (e: Exception) {
if (context != null) {
}
}
} else {
try {
openInBrowserAfterFailing()
} catch (e: Exception) {
if (context != null) {
}
}
}
} catch (e: Exception) {
if (context != null) {
}
}
CoroutineScope(Dispatchers.Main).launch {
try {
val response = mercuryApi.query(url)
if (response.success && response.data != null && !response.data?.content.isNullOrEmpty()) {
binding.titleView.text = response.data!!.title.orEmpty()
if (typeface != null) {
binding.titleView.typeface = typeface
}
URL(response.data!!.url)
url = response.data!!.url
override fun onFailure(
call: Call<ParsedContent>,
t: Throwable
) = openInBrowserAfterFailing()
contentText = response.data!!.content.orEmpty()
htmlToWebview()
handleLeadImage(response)
binding.nestedScrollView.scrollTo(0, 0)
binding.progressBar.visibility = View.GONE
} else {
openInBrowserAfterFailing()
}
)
} catch (e: SocketTimeoutException) {
openInBrowserAfterFailing()
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("getContentFromMercury > $url")
openInBrowserAfterFailing()
}
}
}
private fun handleLeadImage(response: StatusAndData<MercuryModel.ParsedContent>) {
if (!response.data?.lead_image_url.isNullOrEmpty() && context != null) {
binding.imageView.visibility = View.VISIBLE
Glide
.with(requireContext())
.asBitmap()
.load(
response.data!!.lead_image_url.orEmpty()
)
.apply(RequestOptions.fitCenterTransform())
.into(binding.imageView)
} else {
binding.imageView.visibility = View.GONE
}
}
private fun handleImageLoading() {
binding.webcontent.webViewClient = object : WebViewClient() {
@Deprecated("Deprecated in Java")
override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
}
return true
}
@Deprecated("Deprecated in Java")
override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
if (url.lowercase(Locale.US).contains(".jpg") || url.lowercase(Locale.US)
.contains(".jpeg")
) {
try {
val image =
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse(
IMAGE_JPG,
"UTF-8",
getBitmapInputStream(image, Bitmap.CompressFormat.JPEG)
)
} catch (e: ExecutionException) {
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > jpeg > $url")
}
} else if (url.lowercase(Locale.US).contains(".png")) {
try {
val image =
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse(
IMAGE_JPG,
"UTF-8",
getBitmapInputStream(image, Bitmap.CompressFormat.PNG)
)
} catch (e: ExecutionException) {
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > png > $url")
}
} else if (url.lowercase(Locale.US).contains(".webp")) {
try {
val image =
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse(
IMAGE_JPG,
"UTF-8",
getBitmapInputStream(image, Bitmap.CompressFormat.WEBP)
)
} catch (e: ExecutionException) {
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > webp > $url")
}
}
return super.shouldInterceptRequest(view, url)
}
}
}
@ -353,49 +377,19 @@ class ArticleFragment : Fragment(), DIAware {
binding.webcontent.settings.loadWithOverviewMode = true
binding.webcontent.settings.javaScriptEnabled = false
binding.webcontent.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean {
if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
}
return true
}
handleImageLoading()
override fun shouldInterceptRequest(view: WebView?, url: String): WebResourceResponse? {
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
if (url.lowercase(Locale.US).contains(".jpg") || url.lowercase(Locale.US).contains(".jpeg")) {
try {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.JPEG))
}catch ( e : ExecutionException) {}
}
else if (url.lowercase(Locale.US).contains(".png")) {
try {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.PNG))
}catch ( e : ExecutionException) {}
}
else if (url.lowercase(Locale.US).contains(".webp")) {
try {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.WEBP))
}catch ( e : ExecutionException) {}
val gestureDetector =
GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent): Boolean {
return performClick()
}
})
return super.shouldInterceptRequest(view, url)
}
}
val gestureDetector = GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent?): Boolean {
return performClick()
}
})
binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event)}
binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
binding.webcontent.settings.layoutAlgorithm =
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
var baseUrl: String? = null
@ -403,16 +397,23 @@ class ArticleFragment : Fragment(), DIAware {
val itemUrl = URL(url)
baseUrl = itemUrl.protocol + "://" + itemUrl.host
} catch (e: MalformedURLException) {
e.sendSilentlyWithAcraWithName("htmlToWebview > item url")
}
val fontName = when (font) {
val fontName = when (font) {
getString(R.string.open_sans_font_id) -> "Open Sans"
getString(R.string.roboto_font_id) -> "Roboto"
getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
else -> ""
}
val fontLinkAndStyle = if (font.isNotEmpty()) {
"""<link href="https://fonts.googleapis.com/css?family=${fontName.replace(" ", "+")}" rel="stylesheet">
"""<link href="https://fonts.googleapis.com/css?family=${
fontName.replace(
" ",
"+"
)
}" rel="stylesheet">
|<style>
| * {
| font-family: '$fontName';
@ -436,7 +437,12 @@ class ArticleFragment : Fragment(), DIAware {
| max-width: 100%;
| }
| a {
| color: ${String.format("#%06X", 0xFFFFFF and resources.getColor(R.color.colorAccent))} !important;
| color: ${
String.format(
"#%06X",
0xFFFFFF and resources.getColor(R.color.colorAccent)
)
} !important;
| }
| *:not(a) {
| color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)};
@ -447,11 +453,26 @@ class ArticleFragment : Fragment(), DIAware {
| word-break: break-word;
| overflow:hidden;
| line-height: 1.5em;
| background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)};
| background-color: ${
String.format(
"#%06X",
0xFFFFFF and colorSurface.data
)
};
| }
| body, html {
| background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)} !important;
| border-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)} !important;
| background-color: ${
String.format(
"#%06X",
0xFFFFFF and colorSurface.data
)
} !important;
| border-color: ${
String.format(
"#%06X",
0xFFFFFF and colorSurface.data
)
} !important;
| padding: 0 !important;
| margin: 0 !important;
| }
@ -461,7 +482,12 @@ class ArticleFragment : Fragment(), DIAware {
| pre, code {
| white-space: pre-wrap;
| width:100%;
| background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)};
| background-color: ${
String.format(
"#%06X",
0xFFFFFF and colorSurface.data
)
};
| }
| </style>
| $fontLinkAndStyle
@ -477,12 +503,12 @@ class ArticleFragment : Fragment(), DIAware {
fun scrollDown() {
val height = binding.nestedScrollView.measuredHeight
binding.nestedScrollView.smoothScrollBy(0, height/2)
binding.nestedScrollView.smoothScrollBy(0, height / 2)
}
fun scrollUp() {
val height = binding.nestedScrollView.measuredHeight
binding.nestedScrollView.smoothScrollBy(0, -height/2)
binding.nestedScrollView.smoothScrollBy(0, -height / 2)
}
private fun openInBrowserAfterFailing() {
@ -494,7 +520,7 @@ class ArticleFragment : Fragment(), DIAware {
private const val ARG_ITEMS = "items"
fun newInstance(
item: SelfossModel.Item
item: SelfossModel.Item
): ArticleFragment {
val fragment = ArticleFragment()
val args = Bundle()
@ -506,9 +532,10 @@ class ArticleFragment : Fragment(), DIAware {
fun performClick(): Boolean {
if (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
) {
val position : Int = allImages.indexOf(binding.webcontent.hitTestResult.extra)
val position: Int = allImages.indexOf(binding.webcontent.hitTestResult.extra)
val intent = Intent(activity, ImageActivity::class.java)
intent.putExtra("allImages", allImages)

View File

@ -0,0 +1,214 @@
package bou.amine.apps.readerforselfossv2.android.fragments
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import bou.amine.apps.readerforselfossv2.android.HomeActivity
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getIcon
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.chip.Chip
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.android.x.closestDI
import org.kodein.di.instance
class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
private lateinit var binding: FilterFragmentBinding
override val di: DI by closestDI()
private val repository: Repository by instance()
private var selectedChip: Chip? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding =
FilterFragmentBinding.inflate(
inflater,
container,
false
)
val context: Context? = context
if (context == null) {
dismiss()
Exception("FilterSheetFragment context is null").sendSilentlyWithAcraWithName("FilterSheetFragment > onCreateView")
} else {
CoroutineScope(Dispatchers.Main).launch {
handleTagChips(context)
handleSourceChips(context)
binding.progressBar2.visibility = GONE
binding.filterView.visibility = VISIBLE
}
}
binding.floatingActionButton2.setOnClickListener {
(activity as HomeActivity).getElementsAccordingToTab()
(activity as HomeActivity).fetchOnEmptyList()
dismiss()
}
return binding.root
}
private suspend fun handleSourceChips(
context: Context
) {
val sourceGroup = binding.sourcesGroup
repository.getSources().forEach { source ->
val c = Chip(context)
c.ellipsize = TextUtils.TruncateAt.END
Glide.with(context)
.load(source.getIcon(repository.baseUrl))
.listener(object : RequestListener<Drawable?> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable?>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable?>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
try {
c.chipIcon = resource
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("sources > onResourceReady")
}
return false
}
}).preload()
c.text = source.title.getHtmlDecoded()
c.setOnCloseIconClickListener {
(it as Chip).isCloseIconVisible = false
selectedChip = null
repository.setSourceFilter(null)
}
c.setOnClickListener {
if (selectedChip != null) {
selectedChip!!.isCloseIconVisible = false
}
(it as Chip).isCloseIconVisible = true
selectedChip = it
repository.setSourceFilter(source)
repository.setTagFilter(null)
}
if (repository.sourceFilter.value?.equals(source) == true) {
c.isCloseIconVisible = true
selectedChip = c
}
c.isEnabled = source.error.isBlank()
if (source.error.isNotBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
c.tooltipText = source.error
}
sourceGroup.addView(c)
}
}
private suspend fun handleTagChips(
context: Context,
) {
val tagGroup = binding.tagsGroup
val tags = repository.getTags()
tags.forEach { tag ->
val c = Chip(context)
c.ellipsize = TextUtils.TruncateAt.END
c.text = tag.tag
try {
val gd = GradientDrawable()
val gdColor = try {
Color.parseColor(tag.color)
} catch (e: IllegalArgumentException) {
e.sendSilentlyWithAcraWithName("color issue " + tag.color)
resources.getColor(R.color.colorPrimary)
}
gd.setColor(gdColor)
gd.shape = GradientDrawable.RECTANGLE
gd.setSize(30, 30)
gd.cornerRadius = 30F
c.chipIcon = gd
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("tags > GradientDrawable")
}
c.setOnCloseIconClickListener {
(it as Chip).isCloseIconVisible = false
selectedChip = null
repository.setTagFilter(null)
}
c.setOnClickListener {
if (selectedChip != null) {
selectedChip!!.isCloseIconVisible = false
}
(it as Chip).isCloseIconVisible = true
selectedChip = it
repository.setTagFilter(tag)
repository.setSourceFilter(null)
}
if (repository.tagFilter.value?.equals(tag) == true) {
c.isCloseIconVisible = true
selectedChip = c
}
tagGroup.addView(c)
}
}
companion object {
const val TAG = "FilterModalBottomSheet"
}
}

View File

@ -28,7 +28,7 @@ class ImageFragment : Fragment() {
val view = binding?.root
binding!!.photoView.visibility = View.VISIBLE
Glide.with(activity)
Glide.with(requireActivity())
.asBitmap()
.apply(glideOptions)
.load(imageUrl)

View File

@ -2,6 +2,7 @@ package bou.amine.apps.readerforselfossv2.android.model
import android.content.Context
import android.webkit.URLUtil
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.utils.getImages
import com.bumptech.glide.Glide
@ -23,6 +24,7 @@ fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
}
}
} catch (e : Error) {
e.sendSilentlyWithAcraWithName("preloadImages")
return false
}
@ -35,7 +37,7 @@ fun String.toTextDrawableString(): String {
try {
textDrawable.append(s[0])
} catch (e: StringIndexOutOfBoundsException) {
// We do nothing
e.sendSilentlyWithAcraWithName("toTextDrawableString")
}
}
return textDrawable.toString()

View File

@ -3,7 +3,6 @@ package bou.amine.apps.readerforselfossv2.android.model
import android.os.Parcel
import android.os.Parcelable
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import com.google.gson.annotations.SerializedName
fun SelfossModel.Item.toParcelable() : ParecelableItem =
ParecelableItem(
@ -17,7 +16,8 @@ fun SelfossModel.Item.toParcelable() : ParecelableItem =
this.icon,
this.link,
this.sourcetitle,
this.tags.joinToString(",")
this.tags.joinToString(","),
this.author
)
fun ParecelableItem.toModel() : SelfossModel.Item =
SelfossModel.Item(
@ -31,20 +31,22 @@ fun ParecelableItem.toModel() : SelfossModel.Item =
this.icon,
this.link,
this.sourcetitle,
this.tags.split(",")
this.tags.split(","),
this.author
)
data class ParecelableItem(
@SerializedName("id") val id: Int,
@SerializedName("datetime") val datetime: String,
@SerializedName("title") val title: String,
@SerializedName("content") val content: String,
@SerializedName("unread") var unread: Boolean,
@SerializedName("starred") var starred: Boolean,
@SerializedName("thumbnail") val thumbnail: String?,
@SerializedName("icon") val icon: String?,
@SerializedName("link") val link: String,
@SerializedName("sourcetitle") val sourcetitle: String,
@SerializedName("tags") val tags: String
val id: Int,
val datetime: String,
val title: String,
val content: String,
var unread: Boolean,
var starred: Boolean,
val thumbnail: String?,
val icon: String?,
val link: String,
val sourcetitle: String,
val tags: String,
val author: String?
) : Parcelable {
companion object {
@ -66,7 +68,8 @@ data class ParecelableItem(
icon = source.readString(),
link = source.readString().orEmpty(),
sourcetitle = source.readString().orEmpty(),
tags = source.readString().orEmpty()
tags = source.readString().orEmpty(),
author = source.readString().orEmpty()
)
override fun describeContents() = 0
@ -83,5 +86,6 @@ data class ParecelableItem(
dest.writeString(link)
dest.writeString(sourcetitle)
dest.writeString(tags)
dest.writeString(author)
}
}

View File

@ -16,12 +16,17 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySettingsBinding
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.mikepenz.aboutlibraries.LibsBuilder
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
private const val TITLE_TAG = "settingsActivityTitle"
class SettingsActivity : AppCompatActivity(),
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback, DIAware {
override val di by closestDI()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -91,6 +96,21 @@ class SettingsActivity : AppCompatActivity(),
class MainPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_main, rootKey)
preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt()) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯
true
}
preferenceManager.findPreference<Preference>("action_about")?.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ ->
context?.let {
LibsBuilder()
.withAboutIconShown(true)
.withAboutVersionShown(true)
.start(it)
}
true
}
}
}
@ -107,6 +127,7 @@ class SettingsActivity : AppCompatActivity(),
val input: Int = (dest.toString() + source.toString()).toInt()
if (input in 1..200) return@InputFilter null
} catch (nfe: NumberFormatException) {
nfe.sendSilentlyWithAcraWithName("GeneralPreferenceFragment")
Toast.makeText(activity, R.string.items_number_should_be_number, Toast.LENGTH_LONG).show()
}
""
@ -124,12 +145,17 @@ class SettingsActivity : AppCompatActivity(),
fontSize?.setOnBindEditTextListener { editText ->
editText.inputType = InputType.TYPE_CLASS_NUMBER
editText.addTextChangedListener { object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
// We do nothing
}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
// We do nothing
}
override fun afterTextChanged(editable: Editable) {
try {
editText.textSize = editable.toString().toInt().toFloat()
} catch (e: NumberFormatException) {
e.sendSilentlyWithAcraWithName("ArticleViewerPreferenceFragment > afterTextChanged")
}
}
} }
@ -139,6 +165,7 @@ class SettingsActivity : AppCompatActivity(),
val input = (dest.toString() + source.toString()).toInt()
if (input > 0) return@InputFilter null
} catch (nfe: NumberFormatException) {
nfe.sendSilentlyWithAcraWithName("ArticleViewerPreferenceFragment > filters")
}
""
}

View File

@ -1,13 +1,9 @@
package bou.amine.apps.readerforselfossv2.android.utils
import android.app.Activity
import android.app.PendingIntent
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.text.Spannable
import android.text.style.ClickableSpan
import android.util.Patterns
@ -22,7 +18,6 @@ import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
fun Context.openItemUrl(
allItems: ArrayList<SelfossModel.Item>,
currentItem: Int,
linkDecoded: String,
articleViewer: Boolean,
@ -37,7 +32,6 @@ fun Context.openItemUrl(
).show()
} else {
if (articleViewer) {
ReaderActivity.allItems = allItems
val intent = Intent(this, ReaderActivity::class.java)
intent.putExtra("currentItem", currentItem)
app.startActivity(intent)

View File

@ -1,14 +0,0 @@
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomBaseViewHolder.java */
package bou.amine.apps.readerforselfossv2.android.utils.drawer
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import bou.amine.apps.readerforselfossv2.android.R
open class CustomBaseViewHolder(var view: View) : RecyclerView.ViewHolder(view) {
var icon: ImageView = view.findViewById(R.id.material_drawer_icon)
var name: TextView = view.findViewById(R.id.material_drawer_name)
var description: TextView = view.findViewById(R.id.material_drawer_description)
}

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="100%p" android:toXDelta="0"
android:duration="@android:integer/config_mediumAnimTime"/>
</set>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0" android:toXDelta="-100%p"
android:duration="@android:integer/config_mediumAnimTime"/>
</set>

View File

@ -0,0 +1,5 @@
<bitmap
xmlns:android="http://schemas.android.com/apk/res/android"
android:dither="true"
android:src="@drawable/checktile"
android:tileMode="repeat"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M4.25,5.61C6.27,8.2 10,13 10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-6c0,0 3.72,-4.8 5.74,-7.39C20.25,4.95 19.78,4 18.95,4H5.04C4.21,4 3.74,4.95 4.25,5.61z"/>
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
</vector>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="Open Sans"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="Roboto"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View File

@ -1,13 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.drawerlayout.widget.DrawerLayout 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:id="@+id/drawerContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="bou.amine.apps.readerforselfossv2.android.HomeActivity"
android:fitsSystemWindows="true"
xmlns:app="http://schemas.android.com/apk/res-auto">
tools:context="bou.amine.apps.readerforselfossv2.android.HomeActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordLayout"
@ -28,12 +27,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar app:popupTheme="?attr/toolbarPopupTheme" android:theme="@style/ToolBarStyle"
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme"
/>
/>
</com.google.android.material.appbar.AppBarLayout>
@ -45,19 +46,19 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="?android:attr/windowBackground">
android:background="?android:attr/windowBackground"
android:orientation="vertical">
<TextView
android:id="@+id/emptyText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:gravity="center_horizontal"
android:paddingTop="100dp"
android:text="@string/nothing_here"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
android:background="@android:color/transparent"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
@ -69,7 +70,7 @@
android:paddingBottom="60dp"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/list_item"/>
tools:listitem="@layout/list_item" />
</LinearLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
@ -77,6 +78,7 @@
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.ashokvarma.bottomnavigation.BottomNavigationBar
android:id="@+id/bottomBar"
android:layout_width="match_parent"
@ -85,11 +87,4 @@
app:bnbActiveColor="@color/colorAccent"
app:bnbBackgroundColor="?attr/bottomBarBackground" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.mikepenz.materialdrawer.widget.MaterialDrawerSliderView
android:id="@+id/mainDrawer"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true" />
</androidx.drawerlayout.widget.DrawerLayout>

View File

@ -1,33 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
app:layoutDescription="@xml/image_close_scene">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
<androidx.appcompat.widget.Toolbar android:theme="@style/ToolBarStyle"
android:id="@+id/toolBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
/>
/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appBarLayout" />
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_constraintTop_toBottomOf="@+id/appBarLayout">
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.motion.widget.MotionLayout>

View File

@ -1,31 +1,30 @@
<LinearLayout 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"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:gravity="center_horizontal"
android:orientation="vertical"
tools:context="bou.amine.apps.readerforselfossv2.android.LoginActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar app:popupTheme="?attr/toolbarPopupTheme" android:theme="@style/ToolBarStyle"
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
/>
android:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
android:padding="@dimen/activity_horizontal_margin">
<!-- Login progress -->
<ProgressBar
android:id="@+id/loginProgress"
@ -33,67 +32,65 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:visibility="gone"/>
android:visibility="gone" />
<ScrollView
<LinearLayout
android:id="@+id/loginForm"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
<EditText
android:id="@+id/urlView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:hint="@string/prompt_url"
android:imeOptions="actionUnspecified"
android:importantForAutofill="no"
android:inputType="textUri"
android:maxLines="1"
android:minHeight="48dp" />
<EditText
android:id="@+id/urlView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_url"
android:imeOptions="actionUnspecified"
android:importantForAutofill="no"
android:inputType="textUri"
android:maxLines="1" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/withLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/withLoginSwitch"
android:textAlignment="viewStart" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:text="@string/withLoginSwitch"
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/withLogin"
android:layout_weight="1"/>
<EditText
android:id="@+id/loginView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="username"
android:hint="@string/prompt_login"
android:inputType="text"
android:maxLines="1"
android:minHeight="48dp"
android:visibility="gone" />
<EditText
android:id="@+id/loginView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="username"
android:hint="@string/prompt_login"
android:inputType="text"
android:maxLines="1"
android:visibility="gone" />
<EditText
android:id="@+id/passwordView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="password"
android:hint="@string/prompt_password"
android:inputType="textPassword"
android:maxLines="1"
android:minHeight="48dp"
android:visibility="gone" />
<EditText
android:id="@+id/passwordView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="password"
android:hint="@string/prompt_password"
android:inputType="textPassword"
android:maxLines="1"
android:visibility="gone" />
<Button
android:id="@+id/signInButton"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:text="@string/action_sign_in"
android:textStyle="bold" />
<Button
android:id="@+id/signInButton"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:text="@string/action_sign_in"
android:textStyle="bold" />
</LinearLayout>
</ScrollView>
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -24,7 +24,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/source_list_item">
</androidx.recyclerview.widget.RecyclerView>
<com.google.android.material.floatingactionbutton.FloatingActionButton

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="bou.amine.apps.readerforselfossv2.android.AddSourceActivity">
tools:context="bou.amine.apps.readerforselfossv2.android.UpsertSourceActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -17,116 +17,83 @@
<androidx.appcompat.widget.Toolbar app:popupTheme="?attr/toolbarPopupTheme" android:theme="@style/ToolBarStyle"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
/>
android:layout_height="?attr/actionBarSize" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:layout_height="match_parent"
android:layout_width="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/formContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:visibility="gone"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintVertical_bias="0.0">
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:text="@string/add_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView2"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textAlignment="center"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="16dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginLeft="16dp"
android:gravity="center_horizontal" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:id="@+id/nameInput"
android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="@+id/textView2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginTop="16dp"
android:autofillHints="false"
android:hint="@string/add_source_hint_name"
android:textColorHint="?android:textColorPrimary"
android:autofillHints="false" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textUri"
android:ems="10"
android:id="@+id/sourceUri"
android:hint="@string/add_source_hint_url"
android:textColorHint="?android:textColorPrimary"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/nameInput"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:autofillHints="false" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:id="@+id/tags"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/sourceUri"
android:hint="@string/add_source_hint_tags"
android:textColorHint="?android:textColorPrimary"
android:inputType="text"
android:autofillHints="false" />
android:textColorHint="?android:textColorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/sourceUri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginTop="16dp"
android:autofillHints="false"
android:hint="@string/add_source_hint_url"
android:inputType="textUri"
android:textColorHint="?android:textColorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/nameInput" />
<EditText
android:id="@+id/tags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginTop="16dp"
android:autofillHints="false"
android:hint="@string/add_source_hint_tags"
android:inputType="text"
android:textColorHint="?android:textColorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sourceUri" />
<Spinner
android:layout_width="match_parent"
android:id="@+id/spoutsSpinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/tags"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_height="40dp"/>
app:layout_constraintTop_toBottomOf="@+id/tags" />
<Button
android:text="@string/add_source_save"
android:id="@+id/saveBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/saveBtn"
android:elevation="5dp"
android:textColor="?android:textColorPrimary"
android:layout_marginEnd="16dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/spoutsSpinner"
android:elevation="5dp"
android:text="@string/add_source_save"
android:textColor="?android:textColorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="16dp"
app:layout_constraintVertical_bias="0.0"/>
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/spoutsSpinner" />
</androidx.constraintlayout.widget.ConstraintLayout>
@ -135,8 +102,6 @@
style="?android:attr/progressBarStyleLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"

View File

@ -1,18 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
app:layout_constraintHorizontal_bias="0.62"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_margin="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
card_view:cardElevation="2dp"
card_view:cardUseCompatPadding="true"
@ -28,8 +24,8 @@
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:cropToPadding="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/background_splash"
card_view:layout_constraintBottom_toTopOf="@+id/constraintLayout" />
@ -39,18 +35,17 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/itemImage">
<ImageView
android:id="@+id/sourceImage"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/background_splash" />
@ -58,70 +53,58 @@
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:gravity="start"
android:layout_margin="8dp"
android:textAlignment="viewStart"
android:textStyle="bold"
android:textColor="?android:textColorPrimary"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toRightOf="@+id/sourceImage"
app:layout_constraintRight_toRightOf="parent"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/sourceImage"
app:layout_constraintTop_toTopOf="@+id/sourceImage"
tools:text="Titre" />
<TextView
android:id="@+id/sourceTitleAndDate"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="start"
android:textAlignment="viewStart"
android:textSize="14sp"
android:textColor="?android:textColorPrimary"
app:layout_constraintLeft_toLeftOf="@+id/title"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/title"
app:layout_constraintTop_toBottomOf="@+id/title"
tools:text="Google Actualité Il y a 5h" />
<RelativeLayout
android:layout_width="0dp"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sourceTitleAndDate">
<ImageButton
android:id="@+id/favButton"
android:id="@+id/browserBtn"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:adjustViewBounds="true"
android:background="@android:color/transparent"
android:contentDescription="@string/reader_action_open"
android:elevation="5dp"
android:padding="4dp"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_menu_heart_60dp"
app:tint="@color/ic_menu_heart_color" />
app:srcCompat="@drawable/ic_open_in_browser_black_24dp"
app:tint="?android:attr/textColorPrimary" />
<ImageButton
android:id="@+id/shareBtn"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_centerVertical="true"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_toLeftOf="@+id/favButton"
android:layout_toStartOf="@+id/favButton"
android:layout_marginStart="16dp"
android:adjustViewBounds="true"
android:background="@android:color/transparent"
android:contentDescription="@string/share"
android:elevation="5dp"
android:padding="4dp"
android:scaleType="centerCrop"
@ -129,23 +112,21 @@
app:tint="?android:attr/textColorPrimary" />
<ImageButton
android:id="@+id/browserBtn"
android:id="@+id/favButton"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_centerVertical="true"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_toLeftOf="@+id/shareBtn"
android:layout_toStartOf="@+id/shareBtn"
android:layout_marginStart="16dp"
android:adjustViewBounds="true"
android:background="@android:color/transparent"
android:contentDescription="@string/add_to_favs_reader"
android:elevation="5dp"
android:padding="4dp"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_open_in_browser_black_24dp"
app:tint="?android:attr/textColorPrimary" />
app:srcCompat="@drawable/ic_menu_heart_60dp"
app:tint="@color/ic_menu_heart_color" />
</RelativeLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">
<ProgressBar
android:id="@+id/progressBar2"
style="?android:attr/progressBarStyle"
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/filterView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/filterTagsTitle"
style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:text="@string/filter_item_tags"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/filterSourcesTitle"
style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:text="@string/filter_item_sources"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tagsGroup" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/tagsGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/filterTagsTitle"
app:singleSelection="true">
</com.google.android.material.chip.ChipGroup>
<com.google.android.material.chip.ChipGroup
android:id="@+id/sourcesGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/filterSourcesTitle">
</com.google.android.material.chip.ChipGroup>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingActionButton2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:clickable="true"
android:contentDescription="@string/menu_home_search"
android:focusable="true"
app:backgroundTint="@color/colorAccent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:rippleColor="@color/colorAccentDark"
app:srcCompat="@drawable/ic_menu_search_white_24dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,5 +1,4 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
@ -22,10 +21,22 @@
android:layout_width="0dp"
android:layout_height="200dp"
android:scaleType="centerCrop"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/titleView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="8dp"
android:layout_marginRight="16dp"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />
<TextView
android:id="@+id/source"
@ -36,40 +47,23 @@
android:layout_marginRight="16dp"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/titleView" />
<TextView
android:id="@+id/titleView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
android:textStyle="bold"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />
<WebView
android:id="@+id/webcontent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="24dp"
android:layout_marginRight="16dp"
android:background="?attr/webviewBackground"
android:paddingBottom="48dp"
android:textColorLink="?attr/colorAccent"
android:visibility="gone"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="24dp"
android:paddingBottom="48dp"
android:background="?attr/webviewBackground"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/source"
tools:visibility="visible" />
@ -80,10 +74,10 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|bottom|end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_gravity="end|bottom|right">
app:layout_constraintStart_toStartOf="parent">
<com.github.rubensousa.floatingtoolbar.FloatingToolbar
android:id="@+id/floatingToolbar"
@ -96,12 +90,11 @@
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom|right"
android:layout_marginBottom="16dp"
android:layout_gravity="end|bottom"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:paddingBottom="@dimen/activity_vertical_margin"
android:layout_marginBottom="16dp"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:src="@drawable/ic_add_white_24dp"
app:backgroundTint="?attr/colorAccent"
app:fabSize="mini"
@ -112,11 +105,11 @@
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:animateLayoutChanges="true"
android:alpha="0.8"
android:animateLayoutChanges="true"
android:background="@color/black"
android:clickable="false">
android:clickable="false"
android:visibility="gone">
<ProgressBar
style="?android:attr/progressBarStyleLarge"

View File

@ -1,16 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout 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">
<com.github.chrisbanes.photoview.PhotoView
android:id="@+id/photoView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:background="@android:color/black"
app:srcCompat="@android:drawable/screen_background_dark" />
<com.github.chrisbanes.photoview.PhotoView
android:id="@+id/photoView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:adjustViewBounds="true"
android:background="@drawable/checkerboard"
app:srcCompat="@android:drawable/screen_background_dark" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -3,17 +3,16 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="88dp">
android:layout_height="wrap_content">
<ImageView
android:id="@+id/itemImage"
android:layout_width="46dp"
android:layout_height="46dp"
android:layout_marginStart="8dp"
android:layout_marginTop="21dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="8dp" />
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/title"
@ -24,39 +23,30 @@
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:fontFamily="sans-serif"
android:gravity="start"
android:maxLines="3"
android:textAlignment="viewStart"
android:textAllCaps="false"
android:textColor="?android:textColorPrimary"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="?android:textColorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/itemImage"
app:layout_constraintTop_toTopOf="parent"
tools:text="Titre"
android:layout_marginLeft="8dp"
android:layout_marginRight="16dp" />
tools:text="Titre" />
<TextView
android:id="@+id/sourceTitleAndDate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="66dp"
android:layout_marginEnd="16dp"
android:gravity="start"
android:maxLines="1"
android:textAlignment="viewStart"
android:textSize="14sp"
android:textColor="?android:textColorPrimary"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/itemImage"
app:layout_constraintTop_toTopOf="parent"
tools:text="Google Actualité Il y a 5h"
android:layout_marginLeft="8dp"
android:layout_marginRight="16dp" />
app:layout_constraintTop_toBottomOf="@+id/itemImage"
tools:text="Google Actualité Il y a 5h" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -3,48 +3,74 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="@+id/deleteBtn"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:background="@drawable/ic_remove_circle_outline_black_24dp"
android:backgroundTint="?android:textColorSecondary"
android:contentDescription="@string/remove_source"
android:elevation="4dp"
app:iconSize="34dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<ImageView
android:id="@+id/itemImage"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:importantForAccessibility="no"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<TextView
android:id="@+id/sourceTitle"
android:layout_width="0dp"
android:layout_height="17dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:gravity="start"
android:maxLines="1"
android:textAlignment="textStart"
android:textSize="13sp"
android:textAlignment="viewStart"
android:textColor="?android:textColorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/errorText"
app:layout_constraintEnd_toStartOf="@+id/deleteBtn"
app:layout_constraintStart_toEndOf="@+id/itemImage"
app:layout_constraintTop_toTopOf="parent"
tools:text="source title" />
tools:text="Source title" />
<Button
android:id="@+id/deleteBtn"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="34dp"
android:layout_height="34dp"
android:layout_marginEnd="8dp"
android:background="@drawable/ic_remove_circle_outline_black_24dp"
android:backgroundTint="?android:textColorSecondary"
android:elevation="4dp"
android:contentDescription="@string/remove_source"
<TextView
android:id="@+id/errorText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10sp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/red"
android:textStyle="italic"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintEnd_toStartOf="@+id/deleteBtn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/itemImage"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -8,16 +8,32 @@
app:showAsAction="ifRoom|collapseActionView"
app:actionViewClass="androidx.appcompat.widget.SearchView" />
<item android:id="@+id/action_filter"
android:title="@string/menu_home_filter"
android:icon="@drawable/ic_baseline_filter_alt_24"
android:orderInCategory="1"
app:showAsAction="always" />
<item android:id="@+id/readAll"
android:icon="@drawable/ic_menu_done_all_white_24dp"
android:title="@string/readAll"
android:orderInCategory="1"
app:showAsAction="always"/>
android:orderInCategory="2"
app:showAsAction="ifRoom"/>
<item android:id="@+id/action_sources"
android:title="@string/menu_home_sources"
android:orderInCategory="97"
app:showAsAction="never"/>
<item android:id="@+id/action_settings"
android:title="@string/title_activity_settings"
android:orderInCategory="98"
app:showAsAction="never"/>
<item
android:id="@+id/refresh"
android:icon="@drawable/ic_menu_refresh_white_24dp"
android:orderInCategory="99"
app:showAsAction="never"
android:orderInCategory="101"
android:title="@string/menu_home_refresh" />
<item android:id="@+id/action_disconnect"

View File

@ -0,0 +1,16 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/open_action"
android:icon="@drawable/ic_open_in_browser_white_24dp"
android:title="@string/reader_action_open"
app:showAsAction="ifRoom" />
<item
android:id="@+id/share_action"
android:icon="@drawable/ic_share_white_24dp"
android:title="@string/reader_action_share"
app:showAsAction="ifRoom" />
</menu>

View File

@ -10,7 +10,6 @@
<string name="withLoginSwitch">"Autenticació (si és necessària)"</string>
<string name="login_url_problem">"Pot ser que falti una \"/\" al final de l'url."</string>
<string name="prompt_login">"Nom d'usuari"</string>
<string name="label_share">"Comparteix"</string>
<string name="readAll">"Llegeix-ho tot"</string>
<string name="action_disconnect">"Desconnecta't"</string>
<string name="title_activity_settings">"Configuració"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">L\'alçada de les targetes s\'ajustarà al seu contingut</string>
<string name="card_height_off">L\'alçada de les targetes serà fixa</string>
<string name="source_code">Codi font</string>
<string name="drawer_error_loading_tags">S\'ha produït un error en carregar les etiquetes</string>
<string name="drawer_item_filters">Filtres</string>
<string name="drawer_action_clear">Esborra</string>
<string name="drawer_item_tags">Etiquetes</string>
<string name="drawer_item_sources">Fonts</string>
<string name="drawer_action_edit">Edita</string>
<string name="drawer_loading">S\'està carregant…</string>
<string name="filter_item_tags">Etiquetes</string>
<string name="filter_item_sources">Fonts</string>
<string name="menu_home_search">Cerca</string>
<string name="can_delete_source">No es pot suprimir la font</string>
<string name="base_url_error">S\'ha produït un error en comunicar-se amb la instància de Selfoss. Si el problema persisteix, posa\'t en contacte amb mi.</string>
<string name="pref_header_theme">Temes</string>
<string name="default_theme">Predeterminat</string>
<string name="default_dark_theme">Predeterminat/Fosc</string>
<string name="pref_selfoss_category">API de Selfoss</string>
<string name="pref_api_items_number_title">Nombre d\'elements carregats</string>
<string name="pref_hidden_tags">Etiquetes ocultes</string>
<string name="pref_general_infinite_loading_title">Carrega articles en desplaçar</string>
<string name="translation">Traducció</string>
<string name="cant_open_invalid_url">L\'element URL no és vàlid. Estic intentant solucionar aquest problema perquè l\'aplicació no falli.</string>
<string name="drawer_report_bug">Informa d\'un error</string>
<string name="items_number_should_be_number">El nombre d\'elements ha de ser enter.</string>
<string name="reader_action_more">Més informació</string>
<string name="reader_action_open">Obre al navegador</string>
<string name="reader_action_share">Comparteix</string>
<string name="pref_switch_actions_pager_scroll_on">Es marcaran els articles com a llegits en lliscar el dit d\'un article a l\'altre.</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">Aquesta acció marcarà els elements com a llegits.</string>
<string name="pref_switch_actions_pager_scroll">Marca com a llegit en lliscar el dit</string>
<string name="pref_switch_actions_pager_scroll_off">No es marcaran els articles com a llegits en lliscar el dit d\'un article a l\'altre.</string>
<string name="drawer_item_hidden_tags">Etiquetes ocultes</string>
<string name="unmark">Marca com no llegit</string>
<string name="pref_header_offline">Sense connexió i memòria clau</string>
<string name="pref_switch_items_caching_off">Els articles no es guardaran a la memòria del dispositiu i l\'aplicació no es podrà utilitzar sense connexió.</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Els articles se sincronitzaran periòdicament</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Interval de sincronització ( >= 15 minuts)]]></string>
<string name="pref_switch_refresh_when_charging">Sincronitza només quan el telèfon s\'està carregant</string>
<string name="loading_notification_title">S\'està carregant...</string>
<string name="loading_notification_title">S\'està carregant</string>
<string name="loading_notification_text">Selfoss està sincronitzant els articles</string>
<string name="notification_channel_sync">Notificació de sincronització</string>
<string name="new_items_channel_sync">Notificació d\'elements nous</string>
@ -132,4 +120,12 @@
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -10,7 +10,6 @@
<string name="withLoginSwitch">"Anmeldung erforderlich?"</string>
<string name="login_url_problem">"Ups. Du musst eventuell ein \"/\" am Ende der URL anhängen."</string>
<string name="prompt_login">"Benutzername"</string>
<string name="label_share">"Teilen"</string>
<string name="readAll">"Alle gelesen"</string>
<string name="action_disconnect">"Verbindung trennen"</string>
<string name="title_activity_settings">"Einstellungen"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">Kartenhöhe passt sich Inhalt an</string>
<string name="card_height_off">Kartenhöhe ist fix</string>
<string name="source_code">Quellcode</string>
<string name="drawer_error_loading_tags">Fehler beim Laden der Tags</string>
<string name="drawer_item_filters">Filter</string>
<string name="drawer_action_clear">leeren</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Quellen</string>
<string name="drawer_action_edit">bearbeiten</string>
<string name="drawer_loading">Lade…</string>
<string name="filter_item_tags">Tags</string>
<string name="filter_item_sources">Quellen</string>
<string name="menu_home_search">Suche</string>
<string name="can_delete_source">Can\'t delete the source…</string>
<string name="base_url_error">Beim Versuch deine Selfoss-Instanz zu erreichen ist ein Fehler aufgetreten. Solltet dieser Fehler bestehen bleiben, trete bitte mit mir in Kontakt.</string>
<string name="pref_header_theme">Designs</string>
<string name="default_theme">Standard</string>
<string name="default_dark_theme">Standard (Dunkel)</string>
<string name="pref_selfoss_category">selfoss API</string>
<string name="pref_api_items_number_title">Loaded items number</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Übersetzung</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Melde einen Fehler</string>
<string name="items_number_should_be_number">The items number should be an integer.</string>
<string name="reader_action_more">Read more</string>
<string name="reader_action_open">Im Browser öffnen</string>
<string name="reader_action_share">Teilen</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">Dies wird alle Elemente als gelesen markieren.</string>
<string name="pref_switch_actions_pager_scroll">Beim Wischen als gelesen markieren</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Eintrag als ungelesen markieren</string>
<string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Die Artikel werden regelmäßig synchronisiert</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Nur aktualisieren, wenn das Telefon aufgeladen wird</string>
<string name="loading_notification_title">Lädt...</string>
<string name="loading_notification_title">Lädt</string>
<string name="loading_notification_text">Selfoss synchronisiert Ihre Artikel</string>
<string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string>
@ -132,4 +120,12 @@
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -10,7 +10,6 @@
<string name="withLoginSwitch">"Inicio de sesión requerido ?"</string>
<string name="login_url_problem">"Oops. Puede que necesite añadir un \"/\" al final de la url."</string>
<string name="prompt_login">"Nombre de usuario"</string>
<string name="label_share">"Compartir"</string>
<string name="readAll">"Leer todo"</string>
<string name="action_disconnect">"Desconectar"</string>
<string name="title_activity_settings">"Configuración"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">Altura de tarjetas se ajustará a su contenido</string>
<string name="card_height_off">Se fijará la altura de la tarjeta</string>
<string name="source_code">Código fuente</string>
<string name="drawer_error_loading_tags">Error al cargar etiquetas</string>
<string name="drawer_item_filters">Filtros</string>
<string name="drawer_action_clear">limpiar</string>
<string name="drawer_item_tags">Etiquetas</string>
<string name="drawer_item_sources">Fuentes</string>
<string name="drawer_action_edit">editar</string>
<string name="drawer_loading">Cargando…</string>
<string name="filter_item_tags">Etiquetas</string>
<string name="filter_item_sources">Fuentes</string>
<string name="menu_home_search">Buscar</string>
<string name="can_delete_source">No se puede eliminar la fuente…</string>
<string name="base_url_error">Hubo un problema al intentar comunicarse con su instancia de Selfoss. Si el problema persiste, póngase en contacto conmigo.</string>
<string name="pref_header_theme">Temas</string>
<string name="default_theme">Predeterminado</string>
<string name="default_dark_theme">Predeterminado/Oscuro</string>
<string name="pref_selfoss_category">Api de Selfoss</string>
<string name="pref_api_items_number_title">Número de artículos cargados</string>
<string name="pref_hidden_tags">Etiquetas ocultas</string>
<string name="pref_general_infinite_loading_title">Cargar más artículos en desplazamiento</string>
<string name="translation">Traducción</string>
<string name="cant_open_invalid_url">La url del elemento no es válida. Estoy buscando resolver este problema para que la aplicación no colapse.</string>
<string name="drawer_report_bug">Reportar un error</string>
<string name="items_number_should_be_number">El número de artículos debe ser un número entero.</string>
<string name="reader_action_more">Leer más</string>
<string name="reader_action_open">Abrir en el navegador</string>
<string name="reader_action_share">Compartir</string>
<string name="pref_switch_actions_pager_scroll_on">Marcar artículos como leidos al desplazarse entre ellos.</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">Esto marcará todos los artículos como leídos.</string>
<string name="pref_switch_actions_pager_scroll">Marcar artículos como leídos al deslizar con el dedo hacia los lados</string>
<string name="pref_switch_actions_pager_scroll_off">No marcar artículos como leídos al deslizar con el dedo hacia los lados.</string>
<string name="drawer_item_hidden_tags">Etiquetas ocultas</string>
<string name="unmark">Marcar artículo como no leído</string>
<string name="pref_header_offline">Sin conexión y caché</string>
<string name="pref_switch_items_caching_off">Los artículos no se guardarán en la memoria del dispositivo y la aplicación no se podrá utilizar sin conexión.</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Los artículos se sincronizarán periódicamente</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Intervalo de sincronización (>= 15 minutos)]]></string>
<string name="pref_switch_refresh_when_charging">Sólo refrescar cuando el teléfono está cargando</string>
<string name="loading_notification_title">Cargando...</string>
<string name="loading_notification_title">Cargando</string>
<string name="loading_notification_text">Selfoss está sincronizando tus artículos</string>
<string name="notification_channel_sync">Notificación de sincronización</string>
<string name="new_items_channel_sync">Notificación de elementos nuevos</string>
@ -132,4 +120,12 @@
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -10,7 +10,6 @@
<string name="withLoginSwitch">"Login required ?"</string>
<string name="login_url_problem">"Oops. You may need to add a \"/\" at the end of the url."</string>
<string name="prompt_login">"Username"</string>
<string name="label_share">"Share"</string>
<string name="readAll">"Read all"</string>
<string name="action_disconnect">"Disconnect"</string>
<string name="title_activity_settings">"Settings"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">Cards height will adjust to its content</string>
<string name="card_height_off">Card height will be fixed</string>
<string name="source_code">Source code</string>
<string name="drawer_error_loading_tags">Error loading tags</string>
<string name="drawer_item_filters">Filters</string>
<string name="drawer_action_clear">clear</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Sources</string>
<string name="drawer_action_edit">edit</string>
<string name="drawer_loading">Loading …</string>
<string name="filter_item_tags">Tags</string>
<string name="filter_item_sources">Sources</string>
<string name="menu_home_search">Search</string>
<string name="can_delete_source">Can\'t delete the source…</string>
<string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string>
<string name="pref_header_theme">Themes</string>
<string name="default_theme">Default</string>
<string name="default_dark_theme">Default/Dark</string>
<string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Loaded items number</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string>
<string name="items_number_should_be_number">The items number should be an integer.</string>
<string name="reader_action_more">Read more</string>
<string name="reader_action_open">Open in browser</string>
<string name="reader_action_share">Share</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string>
@ -132,4 +120,12 @@
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -10,7 +10,6 @@
<string name="withLoginSwitch">"Avec login ?"</string>
<string name="login_url_problem">"Petit souci. Il manque peut-être un / à la fin ?"</string>
<string name="prompt_login">"Utilisateur"</string>
<string name="label_share">"Partager"</string>
<string name="readAll">"Tout lire"</string>
<string name="action_disconnect">"Déconnecter"</string>
<string name="title_activity_settings">"Paramètres"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">La taille de la carte s\'adaptera au contenu</string>
<string name="card_height_off">La taille de la carte sera fixe</string>
<string name="source_code">Code source</string>
<string name="drawer_error_loading_tags">Erreur lors du chargement des tags</string>
<string name="drawer_item_filters">Filtres</string>
<string name="drawer_action_clear">raz</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Sources</string>
<string name="drawer_action_edit">éditer</string>
<string name="drawer_loading">Chargement …</string>
<string name="filter_item_tags">Tags</string>
<string name="filter_item_sources">Sources</string>
<string name="menu_home_search">Rechercher</string>
<string name="can_delete_source">Impossible de supprimer la source…</string>
<string name="base_url_error">Il y a eu un souci lors de la communication avec votre instance Selfoss. Si le problèmes persiste, contactez-moi pour trouver une solution.</string>
<string name="pref_header_theme">Thèmes</string>
<string name="default_theme">Par défaut</string>
<string name="default_dark_theme">Par défaut/Foncé</string>
<string name="pref_selfoss_category">Api Selfoss</string>
<string name="pref_api_items_number_title">Nombre d\'articles chargés</string>
<string name="pref_hidden_tags">Tags Cachés</string>
<string name="pref_general_infinite_loading_title">Charger plus d\'articles au scroll</string>
<string name="translation">Traduction</string>
<string name="cant_open_invalid_url">Lurl de lélément nest pas valide. En attendant la résolution du problème, le lien ne s\'ouvrira pas.</string>
<string name="drawer_report_bug">Signaler un bug</string>
<string name="items_number_should_be_number">Le nombre d\'articles doit être un entier.</string>
<string name="reader_action_more">Lire plus</string>
<string name="reader_action_open">Ouvrir</string>
<string name="reader_action_share">Partager</string>
<string name="pref_switch_actions_pager_scroll_on">Marquer les articles comme lus à la navigation dans le lecteur d\'article.</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">Marquer tous les éléments comme lus ?</string>
<string name="pref_switch_actions_pager_scroll">Marquer comme lu à la navigation.</string>
<string name="pref_switch_actions_pager_scroll_off">Ne pas marquer les articles comme lus à la navigation.</string>
<string name="drawer_item_hidden_tags">Tags Cachés</string>
<string name="unmark">Marquer l\'article comme non lu</string>
<string name="pref_header_offline">Hors ligne et cache</string>
<string name="pref_switch_items_caching_off">Les articles ne seront pas enregistrés et l\'application ne sera pas utilisable hors ligne.</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles seront périodiquement synchronisées</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Interval de synchronisation ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Synchroniser uniquement lorsque le téléphone est en charge</string>
<string name="loading_notification_title">Chargement ...</string>
<string name="loading_notification_title">Chargement </string>
<string name="loading_notification_text">Selfoss synchronise vos articles</string>
<string name="notification_channel_sync">Notification de synchronisation</string>
<string name="new_items_channel_sync">Notification de nouveaux articles</string>
@ -132,4 +120,12 @@
<string name="mode_dark">Thème sombre</string>
<string name="mode_system">Utiliser les paramètres système</string>
<string name="mode_light">Thème clair</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -10,7 +10,6 @@
<string name="withLoginSwitch">"É preciso iniciar sesión?"</string>
<string name="login_url_problem">"Ups! Pode que precises engadir un \"/\" o final da URL."</string>
<string name="prompt_login">"Nome de usuario"</string>
<string name="label_share">"Compartir"</string>
<string name="readAll">"Ler todos"</string>
<string name="action_disconnect">"Desconectar"</string>
<string name="title_activity_settings">"Axustes"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">A altura das tarxetas axustarase ao seu contido</string>
<string name="card_height_off">A altura das tarxetas será fixa</string>
<string name="source_code">Código fonte</string>
<string name="drawer_error_loading_tags">Produciuse un erro ao cargar as etiquetas</string>
<string name="drawer_item_filters">Filtros</string>
<string name="drawer_action_clear">limpar</string>
<string name="drawer_item_tags">Etiquetas</string>
<string name="drawer_item_sources">Fontes</string>
<string name="drawer_action_edit">editar</string>
<string name="drawer_loading">Cargando…</string>
<string name="filter_item_tags">Etiquetas</string>
<string name="filter_item_sources">Fontes</string>
<string name="menu_home_search">Procurar</string>
<string name="can_delete_source">Non se puido eliminar a fonte…</string>
<string name="base_url_error">Houno unha incidencia ao tratar de comunicarse coa túa instancia de Selfoss. Se o problema persiste, prégolle que se poña en contacto conmigo.</string>
<string name="pref_header_theme">Temas</string>
<string name="default_theme">Predeterminado</string>
<string name="default_dark_theme">Predeterminado/Escuro</string>
<string name="pref_selfoss_category">API de Selfoss</string>
<string name="pref_api_items_number_title">Número de elementos cargados</string>
<string name="pref_hidden_tags">Etiquetas ocultas</string>
<string name="pref_general_infinite_loading_title">Cargar máis artigos ao desprazarse</string>
<string name="translation">Traducción</string>
<string name="cant_open_invalid_url">A URL do elemento non é válida. Estou tratando de solucionar isto pra que a aplicación non falle.</string>
<string name="drawer_report_bug">Informar dun erro</string>
<string name="items_number_should_be_number">O número de elementos debería ser un enteiro.</string>
<string name="reader_action_more">Ler máis</string>
<string name="reader_action_open">Abrir no navegador</string>
<string name="reader_action_share">Compartir</string>
<string name="pref_switch_actions_pager_scroll_on">Marcar artigos como lidos cando se desliza o dedo dun a outro.</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">Isto marcara todos os elementos como lidos.</string>
<string name="pref_switch_actions_pager_scroll">Marcar artigos como lidos ao deslizar co dedo cara os lados</string>
<string name="pref_switch_actions_pager_scroll_off">Non marcar artigos como lidos ao deslizar co dedo cara os lados.</string>
<string name="drawer_item_hidden_tags">Etiquetas ocultas</string>
<string name="unmark">Marcar artículo como non lido</string>
<string name="pref_header_offline">Sen conexión e caché</string>
<string name="pref_switch_items_caching_off">Os artigos non se gardaran na memoria do dispositivo e non se poderá utilizar a aplicación sen conexión.</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Os artigos sincronizaranse periódicamente</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Intervalo de sincronización (>= 15 minutos)]]></string>
<string name="pref_switch_refresh_when_charging">Só refrescar cando o teléfono se está a cargar</string>
<string name="loading_notification_title">Cargando...</string>
<string name="loading_notification_title">Cargando</string>
<string name="loading_notification_text">Selfoss está sincronizando os teus ar tigos</string>
<string name="notification_channel_sync">Notificación de sincronización</string>
<string name="new_items_channel_sync">Notificación de actualizacións</string>
@ -132,4 +120,12 @@
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -10,7 +10,6 @@
<string name="withLoginSwitch">"Harus masuk?"</string>
<string name="login_url_problem">"Ups. Anda mungkin harus menambahkan \"/\" di akhir url."</string>
<string name="prompt_login">"Nama pengguna"</string>
<string name="label_share">"Bagikan"</string>
<string name="readAll">"Baca semua"</string>
<string name="action_disconnect">"Putuskan sambungan"</string>
<string name="title_activity_settings">"Pengaturan"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">Tinggi kartu akan disesuaikan dengan konten</string>
<string name="card_height_off">Ukuran kartu akan tetap</string>
<string name="source_code">Kode sumber</string>
<string name="drawer_error_loading_tags">Kesalahan saat memuat tag</string>
<string name="drawer_item_filters">Filter</string>
<string name="drawer_action_clear">kosongkan</string>
<string name="drawer_item_tags">Tag</string>
<string name="drawer_item_sources">Sumber</string>
<string name="drawer_action_edit">suntung</string>
<string name="drawer_loading">Memuat …</string>
<string name="filter_item_tags">Tag</string>
<string name="filter_item_sources">Sumber</string>
<string name="menu_home_search">Cari</string>
<string name="can_delete_source">Tidak dapat menghapus sumber…</string>
<string name="base_url_error">Ada masalah saat berkomunikasi dengan Selfoss Anda. Jika masalah berlanjut, tolong hubungi saya.</string>
<string name="pref_header_theme">Tema</string>
<string name="default_theme">Bawaan</string>
<string name="default_dark_theme">Bawaan/Gelap</string>
<string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Item nomor dimuat</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Muat lebih banyak artikel saat membalik halaman</string>
<string name="translation">Terjemahan</string>
<string name="cant_open_invalid_url">Alamat tautan proyek tidak valid. Saya mencoba memecahkan masalah ini untuk menghindari aplikasi berhenti.</string>
<string name="drawer_report_bug">Laporkan bug</string>
<string name="items_number_should_be_number">Jumlah item harus berupa bilangan bulat.</string>
<string name="reader_action_more">Baca lebih lanjut</string>
<string name="reader_action_open">Buka di peramban</string>
<string name="reader_action_share">Bagikan</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string>
@ -132,4 +120,12 @@
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -10,7 +10,6 @@
<string name="withLoginSwitch">"È richiesto l'accesso?"</string>
<string name="login_url_problem">"Oops. Potrebbe essere necessario aggiungere un \"/\" alla fine dell'url."</string>
<string name="prompt_login">"Nome utente"</string>
<string name="label_share">"Condividi"</string>
<string name="readAll">"Segna tutte come lette"</string>
<string name="action_disconnect">"Scollegati"</string>
<string name="title_activity_settings">"Impostazioni"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">Cards height will adjust to its content</string>
<string name="card_height_off">Card height will be fixed</string>
<string name="source_code">Codice sorgente</string>
<string name="drawer_error_loading_tags">Errore nel caricamento dei tag</string>
<string name="drawer_item_filters">Filtri</string>
<string name="drawer_action_clear">cancella</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Fonti</string>
<string name="drawer_action_edit">modifica</string>
<string name="drawer_loading">Caricamento…</string>
<string name="filter_item_tags">Tags</string>
<string name="filter_item_sources">Fonti</string>
<string name="menu_home_search">Cerca</string>
<string name="can_delete_source">Non è possibile eliminare la fonte…</string>
<string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string>
<string name="pref_header_theme">Temi</string>
<string name="default_theme">Predefinito</string>
<string name="default_dark_theme">Predefinito (Scuro)</string>
<string name="pref_selfoss_category">Api di Selfoss</string>
<string name="pref_api_items_number_title">Numero di elementi caricati</string>
<string name="pref_hidden_tags">Tag nascosti</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Traduzioni</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Segnala un bug</string>
<string name="items_number_should_be_number">The items number should be an integer.</string>
<string name="reader_action_more">Read more</string>
<string name="reader_action_open">Open in browser</string>
<string name="reader_action_share">Share</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Segna come non letto</string>
<string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string>
@ -132,4 +120,12 @@
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -10,7 +10,6 @@
<string name="withLoginSwitch">"로그인이 필요합니까?"</string>
<string name="login_url_problem">"죄송합니다. Url의 끝에 \"/\"를 추가할 필요가 있습니다."</string>
<string name="prompt_login">"사용자 이름"</string>
<string name="label_share">"공유"</string>
<string name="readAll">"모두 읽기"</string>
<string name="action_disconnect">"연결 해제"</string>
<string name="title_activity_settings">"설정"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">Cards height will adjust to its content</string>
<string name="card_height_off">Card height will be fixed</string>
<string name="source_code">Source code</string>
<string name="drawer_error_loading_tags">Error loading tags</string>
<string name="drawer_item_filters">Filters</string>
<string name="drawer_action_clear">clear</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Sources</string>
<string name="drawer_action_edit">edit</string>
<string name="drawer_loading">Loading …</string>
<string name="filter_item_tags">Tags</string>
<string name="filter_item_sources">Sources</string>
<string name="menu_home_search">Search</string>
<string name="can_delete_source">Can\'t delete the source…</string>
<string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string>
<string name="pref_header_theme">Themes</string>
<string name="default_theme">Default</string>
<string name="default_dark_theme">Default/Dark</string>
<string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Loaded items number</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string>
<string name="items_number_should_be_number">The items number should be an integer.</string>
<string name="reader_action_more">Read more</string>
<string name="reader_action_open">Open in browser</string>
<string name="reader_action_share">Share</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string>
@ -132,4 +120,12 @@
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -10,7 +10,6 @@
<string name="withLoginSwitch">"Authenticatie vereist?"</string>
<string name="login_url_problem">"Oeps, ben je soms de \"/\" vergeten aan het eind?"</string>
<string name="prompt_login">"Gebruikersnaam"</string>
<string name="label_share">"Delen"</string>
<string name="readAll">"Alles lezen"</string>
<string name="action_disconnect">"Verbinding verbreken"</string>
<string name="title_activity_settings">"Instellingen"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">Hoogte aanpassen aan de hand van kaartinhoud</string>
<string name="card_height_off">Vaste hoogte</string>
<string name="source_code">Broncode</string>
<string name="drawer_error_loading_tags">Fout bij het laden van tags</string>
<string name="drawer_item_filters">Filters</string>
<string name="drawer_action_clear">wissen</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Bronnen</string>
<string name="drawer_action_edit">bewerken</string>
<string name="drawer_loading">Bezig met laden …</string>
<string name="filter_item_tags">Tags</string>
<string name="filter_item_sources">Bronnen</string>
<string name="menu_home_search">Zoeken</string>
<string name="can_delete_source">Kan de bron niet verwijderen…</string>
<string name="base_url_error">Er was een probleem bij het communiceren met uw Selfoss Instance. Als het probleem blijft, neem dan contact met mij op.</string>
<string name="pref_header_theme">Thema \'s</string>
<string name="default_theme">Standaard</string>
<string name="default_dark_theme">Standaard/Donker</string>
<string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Geladen items nummer</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Laad meer artikelen door te bladeren</string>
<string name="translation">Vertaling</string>
<string name="cant_open_invalid_url">De URL is ongeldig. Ik probeer dit probleem op te lossen, zodat de toepassing niet wordt afgesloten.</string>
<string name="drawer_report_bug">Een fout melden</string>
<string name="items_number_should_be_number">Het aantal items moet een geheel getal zijn.</string>
<string name="reader_action_more">Lees meer</string>
<string name="reader_action_open">Openen in browser</string>
<string name="reader_action_share">Delen</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string>
@ -132,4 +120,12 @@
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -10,7 +10,6 @@
<string name="withLoginSwitch">"É necessário o login ?"</string>
<string name="login_url_problem">"Oops. Talvez você precise adicionar uma \"/\" no final da url."</string>
<string name="prompt_login">"Usuário"</string>
<string name="label_share">"Compartilhar"</string>
<string name="readAll">"Ler todos"</string>
<string name="action_disconnect">"Desconectar"</string>
<string name="title_activity_settings">"Configurações"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">Cards com altura ajustáveis de acordo com o conteúdo</string>
<string name="card_height_off">Cards com altura de tamanho fixo</string>
<string name="source_code">Código fonte</string>
<string name="drawer_error_loading_tags">Erro ao carregar as tags</string>
<string name="drawer_item_filters">Filtros</string>
<string name="drawer_action_clear">limpar</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Fontes</string>
<string name="drawer_action_edit">editar</string>
<string name="drawer_loading">Carregando …</string>
<string name="filter_item_tags">Tags</string>
<string name="filter_item_sources">Fontes</string>
<string name="menu_home_search">Procurar</string>
<string name="can_delete_source">Não foi possível apagar a fonte…</string>
<string name="base_url_error">Houve um problema ao tentar se comunicar com o seu Selfoss. Se o problema persistir, entre em contato comigo.</string>
<string name="pref_header_theme">Temas</string>
<string name="default_theme">Padrão</string>
<string name="default_dark_theme">Padrão/Escuro</string>
<string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Quantidade de itens carregados</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Carregar mais artigos ao realizar o scroll</string>
<string name="translation">Traduções</string>
<string name="cant_open_invalid_url">A url está inválida. Estou tentando resolver esse problema para que o aplicativo não encerre.</string>
<string name="drawer_report_bug">Reportar erro</string>
<string name="items_number_should_be_number">O número dos itens deve ser um número inteiro.</string>
<string name="reader_action_more">Leia mais</string>
<string name="reader_action_open">Abrir no navegador</string>
<string name="reader_action_share">Compartilhar</string>
<string name="pref_switch_actions_pager_scroll_on">Se esta configuração estiver ativada, os artigos serão marcados como lidos ao deslizar para a esquerda e para a direita no leitor do artigo.</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">Isso marcará todos os itens como lidos.</string>
<string name="pref_switch_actions_pager_scroll">Marcar Como Lida ao Abrir</string>
<string name="pref_switch_actions_pager_scroll_off">Não marca artigos como lido quando abrir.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string>
@ -132,4 +120,12 @@
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -10,7 +10,6 @@
<string name="withLoginSwitch">"É necessário fazer login?"</string>
<string name="login_url_problem">"Uups. Você pode precisar adicionar uma \"/\" no final da url."</string>
<string name="prompt_login">"Nome do usuário"</string>
<string name="label_share">"Compartilhar"</string>
<string name="readAll">"Ler tudo"</string>
<string name="action_disconnect">"Desligar"</string>
<string name="title_activity_settings">"Configurações"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">Altura de cartas irá ajustar ao seu conteúdo</string>
<string name="card_height_off">Altura do cartão será corrigida</string>
<string name="source_code">Código fonte</string>
<string name="drawer_error_loading_tags">Erro ao carregar etiquetas</string>
<string name="drawer_item_filters">Filtros</string>
<string name="drawer_action_clear">limpar</string>
<string name="drawer_item_tags">Etiquetas</string>
<string name="drawer_item_sources">Fontes</string>
<string name="drawer_action_edit">editar</string>
<string name="drawer_loading">A carregar…</string>
<string name="filter_item_tags">Etiquetas</string>
<string name="filter_item_sources">Fontes</string>
<string name="menu_home_search">Buscar</string>
<string name="can_delete_source">Não é possível excluir a fonte…</string>
<string name="base_url_error">Houve um problema ao tentar se comunicar com sua instância de Selfoss. Se o problema persistir, por favor entre em contato comigo.</string>
<string name="pref_header_theme">Temas</string>
<string name="default_theme">Predefinição</string>
<string name="default_dark_theme">Padrão/escuro</string>
<string name="pref_selfoss_category">Api de Selfoss</string>
<string name="pref_api_items_number_title">Número de itens carregados</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Carregar mais artigos no pergaminho</string>
<string name="translation">Tradução</string>
<string name="cant_open_invalid_url">A url do item é inválido. Eu estou olhando para resolver esta questão, para que o app não vai falhar.</string>
<string name="drawer_report_bug">Reportar falha</string>
<string name="items_number_should_be_number">O número de itens deve ser um número inteiro.</string>
<string name="reader_action_more">Ler mais</string>
<string name="reader_action_open">Abrir no browser</string>
<string name="reader_action_share">Compartilhar</string>
<string name="pref_switch_actions_pager_scroll_on">Artigos de marca como lida quando passar entre artigos.</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string>
@ -132,4 +120,12 @@
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -10,7 +10,6 @@
<string name="withLoginSwitch">"Login required ?"</string>
<string name="login_url_problem">"Oops. You may need to add a \"/\" at the end of the url."</string>
<string name="prompt_login">"පරිශීලක නාමය"</string>
<string name="label_share">"Share"</string>
<string name="readAll">"Read all"</string>
<string name="action_disconnect">"Disconnect"</string>
<string name="title_activity_settings">"සැකසුම්"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">Cards height will adjust to its content</string>
<string name="card_height_off">Card height will be fixed</string>
<string name="source_code">Source code</string>
<string name="drawer_error_loading_tags">Error loading tags</string>
<string name="drawer_item_filters">Filters</string>
<string name="drawer_action_clear">clear</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Sources</string>
<string name="drawer_action_edit">edit</string>
<string name="drawer_loading">Loading …</string>
<string name="filter_item_tags">Tags</string>
<string name="filter_item_sources">Sources</string>
<string name="menu_home_search">Search</string>
<string name="can_delete_source">Can\'t delete the source…</string>
<string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string>
<string name="pref_header_theme">Themes</string>
<string name="default_theme">Default</string>
<string name="default_dark_theme">Default/Dark</string>
<string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Loaded items number</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string>
<string name="items_number_should_be_number">The items number should be an integer.</string>
<string name="reader_action_more">Read more</string>
<string name="reader_action_open">Open in browser</string>
<string name="reader_action_share">Share</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string>
@ -132,4 +120,12 @@
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -10,7 +10,6 @@
<string name="withLoginSwitch">"Kullanıcı Girişi Gerekli?"</string>
<string name="login_url_problem">"Oops. Url'nin sonuna \"/\" eklemek gerekebilir."</string>
<string name="prompt_login">"Kullanıcı adı"</string>
<string name="label_share">"Paylaş"</string>
<string name="readAll">"Tümünü oku"</string>
<string name="action_disconnect">"Bağlantıyı kes"</string>
<string name="title_activity_settings">"Ayarlar"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">Kartların yüksekliği içeriğine göre ayarlanır</string>
<string name="card_height_off">Kart yüksekliği sabit olacak</string>
<string name="source_code">Kaynak kodu</string>
<string name="drawer_error_loading_tags">Etiketler yükleme hatası</string>
<string name="drawer_item_filters">Filtreler</string>
<string name="drawer_action_clear">temizle</string>
<string name="drawer_item_tags">Etiketler</string>
<string name="drawer_item_sources">Kaynaklar</string>
<string name="drawer_action_edit">düzenle</string>
<string name="drawer_loading">Yükleniyor…</string>
<string name="filter_item_tags">Etiketler</string>
<string name="filter_item_sources">Kaynaklar</string>
<string name="menu_home_search">Ara</string>
<string name="can_delete_source">Kaynak silinemiyor…</string>
<string name="base_url_error">Selfoss Örneğinizle iletişim kurmaya çalışırken bir sorun oluştu. Sorun devam ederse, lütfen benimle iletişime geçin.</string>
<string name="pref_header_theme">Temalar</string>
<string name="default_theme">Varsayılan</string>
<string name="default_dark_theme">Varsayılan/koyu</string>
<string name="pref_selfoss_category">Selfoss Uygulaması</string>
<string name="pref_api_items_number_title">Yüklenen öğe numarası</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Kaydırma üzerine daha fazla makale yükleyin</string>
<string name="translation">Çeviri</string>
<string name="cant_open_invalid_url">Öğe url geçersiz. Uygulama çökmeyeceği için bu sorunu çözmeye çalışıyorum.</string>
<string name="drawer_report_bug">Hata bildir</string>
<string name="items_number_should_be_number">Öğe sayısı bir tamsayı olmalıdır.</string>
<string name="reader_action_more">Daha fazlasını görüntüle</string>
<string name="reader_action_open">Tarayıcıda aç</string>
<string name="reader_action_share">Paylaş</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string>
@ -132,4 +120,12 @@
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -10,7 +10,6 @@
<string name="withLoginSwitch">"需要登录?"</string>
<string name="login_url_problem">"哎呀。您可能需要在网址的末尾添加一个 \"/\"。"</string>
<string name="prompt_login">"用户名"</string>
<string name="label_share">"分享"</string>
<string name="readAll">"全部阅读"</string>
<string name="action_disconnect">"断开连接"</string>
<string name="title_activity_settings">"设置"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">卡片高度将根据内容调整</string>
<string name="card_height_off">卡片高度将被固定</string>
<string name="source_code">源代码</string>
<string name="drawer_error_loading_tags">加载标记时出错..。</string>
<string name="drawer_item_filters">搜索条件</string>
<string name="drawer_action_clear">清空</string>
<string name="drawer_item_tags">标签</string>
<string name="drawer_item_sources">来源</string>
<string name="drawer_action_edit">编辑</string>
<string name="drawer_loading">正在载入…</string>
<string name="filter_item_tags">标签</string>
<string name="filter_item_sources">来源</string>
<string name="menu_home_search">搜索</string>
<string name="can_delete_source">无法删除数据源…</string>
<string name="base_url_error">与您的 Selfoss 通信时出现问题。如果问题一直存在,请与我联系。</string>
<string name="pref_header_theme">主题</string>
<string name="default_theme">默认​​​​​</string>
<string name="default_dark_theme">默认值/暗</string>
<string name="pref_selfoss_category">塞尔福斯 Api</string>
<string name="pref_api_items_number_title">已加载项目编号</string>
<string name="pref_hidden_tags">隐藏标签</string>
<string name="pref_general_infinite_loading_title">翻页时载入更多文章</string>
<string name="translation">翻译</string>
<string name="cant_open_invalid_url">项目链接地址无效。我正在设法解决这个问题,以避免应用程序崩溃。</string>
<string name="drawer_report_bug">报告错误</string>
<string name="items_number_should_be_number">项目数应为整数。</string>
<string name="reader_action_more">阅读更多</string>
<string name="reader_action_open">在浏览器中打开</string>
<string name="reader_action_share">分享</string>
<string name="pref_switch_actions_pager_scroll_on">切换文章时将文章标记为已读。</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">这将标记所有项目为已读。</string>
<string name="pref_switch_actions_pager_scroll">滑动时标为已读</string>
<string name="pref_switch_actions_pager_scroll_off">滑动时不标记文章为已读</string>
<string name="drawer_item_hidden_tags">隐藏标签</string>
<string name="unmark">标记条目为未读</string>
<string name="pref_header_offline">离线和缓存</string>
<string name="pref_switch_items_caching_off">文章不会被保存到设备内存,应用程序在离线时将无法阅读它们</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">将定期同步文章</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[同步间隔 (>= 15分钟)]]></string>
<string name="pref_switch_refresh_when_charging">仅在手机充电时刷新</string>
<string name="loading_notification_title">加载中...</string>
<string name="loading_notification_title">加载中</string>
<string name="loading_notification_text">Selfoss 正在同步您的文章</string>
<string name="notification_channel_sync">同步通知</string>
<string name="new_items_channel_sync">新条目通知</string>
@ -132,4 +120,12 @@
<string name="mode_dark">深色模式</string>
<string name="mode_system">遵循系统设置</string>
<string name="mode_light">浅色模式</string>
<string name="gdpr_dialog_title">该应用不分享任何关于您的个人数据。</string>
<string name="gdpr_dialog_message"><![CDATA[崩溃报告发送现已启用。 可以从设置页面禁用它。 请记住,崩溃报告对于应用程序开发是必需的。]]></string>
<string name="crash_toast_text">发生崩溃。请将细节发送给开发人员。</string>
<string name="pref_switch_disable_acra">"禁用自动错误报告 "</string>
<string name="menu_home_filter">筛选器</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -10,7 +10,6 @@
<string name="withLoginSwitch">"需要登入?"</string>
<string name="login_url_problem">"哎呀。您可能需要在网址的末尾添加一个 \"/\"。"</string>
<string name="prompt_login">"使用者名稱"</string>
<string name="label_share">"分享"</string>
<string name="readAll">"全部阅读"</string>
<string name="action_disconnect">"断开连接"</string>
<string name="title_activity_settings">"设置"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">卡片高度将根据内容调整</string>
<string name="card_height_off">卡片高度将被固定</string>
<string name="source_code">源代码</string>
<string name="drawer_error_loading_tags">加载标记时出错..。</string>
<string name="drawer_item_filters">搜索条件</string>
<string name="drawer_action_clear">清空</string>
<string name="drawer_item_tags">标签</string>
<string name="drawer_item_sources">来源</string>
<string name="drawer_action_edit">编辑</string>
<string name="drawer_loading">正在载入…</string>
<string name="filter_item_tags">标签</string>
<string name="filter_item_sources">来源</string>
<string name="menu_home_search">搜索</string>
<string name="can_delete_source">无法删除数据源…</string>
<string name="base_url_error">与您的 Selfoss 通信时出现问题。如果问题一直存在,请与我联系。</string>
<string name="pref_header_theme">主题</string>
<string name="default_theme">默认​​​​​</string>
<string name="default_dark_theme">默认值/暗</string>
<string name="pref_selfoss_category">塞尔福斯 Api</string>
<string name="pref_api_items_number_title">已加载项目编号</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">翻页时载入更多文章</string>
<string name="translation">翻译</string>
<string name="cant_open_invalid_url">项目链接地址无效。我正在设法解决这个问题,以避免应用程序崩溃。</string>
<string name="drawer_report_bug">报告错误</string>
<string name="items_number_should_be_number">项目数应为整数。</string>
<string name="reader_action_more">阅读更多</string>
<string name="reader_action_open">在浏览器中打开</string>
<string name="reader_action_share">分享</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string>
@ -132,4 +120,12 @@
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -12,4 +12,5 @@
<color name="refresh_progress_2">@color/colorAccent</color>
<color name="refresh_progress_3">@color/pink</color>
<color name="dark">#FF282828</color>
<color name="transparent_dark_background">#33000000</color>
</resources>

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="com_google_android_gms_fonts_certs">
<item>@array/com_google_android_gms_fonts_certs_dev</item>
<item>@array/com_google_android_gms_fonts_certs_prod</item>
</array>
<string-array name="com_google_android_gms_fonts_certs_dev">
<item>
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
</item>
</string-array>
<string-array name="com_google_android_gms_fonts_certs_prod">
<item>
MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
</item>
</string-array>
</resources>

View File

@ -9,24 +9,6 @@
<string-array name="ModeValues">
<item>1</item> <!--MODE_NIGHT_NO-->
<item>2</item> <!--MODE_NIGHT_YES-->
<item>0</item> <!--MODE_NIGHT_AUTO_TIME-->
</string-array>
<string-array name="Voice">
<item>Male</item>
<item>Female</item>
</string-array>
<string-array name="VoiceAlias">
<item>"usenglishmale"</item>
<item>"usenglishfemale"</item>
<item>"ukenglishmale"</item>
<item>"ukenglishfemale"</item>
<item>"eurfrenchmale"</item>
<item>"eurfrenchfemale"</item>
<item>"eurspanishmale"</item>
<item>"eurspanishfemale"</item>
<item>"euritalianmale"</item>
<item>"euritalianfemale"</item>
<item>-1</item> <!--MODE_NIGHT_FOLLOW_SYSTEM-->
</string-array>
</resources>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="preloaded_fonts" translatable="false">
<item>@font/open_sans</item>
<item>@font/roboto</item>
</array>
</resources>

View File

@ -4,5 +4,6 @@
<item></item>
<item>@string/open_sans_font_id</item>
<item>@string/roboto_font_id</item>
<item>@string/source_code_pro_font_id</item>
</array>
</resources>

View File

@ -4,5 +4,6 @@
<item>Systems</item>
<item>Open Sans</item>
<item>Roboto</item>
<item>Source Code Pro</item>
</array>
</resources>

View File

@ -9,7 +9,6 @@
<string name="withLoginSwitch">"Login required ?"</string>
<string name="login_url_problem">"Oops. You may need to add a \"/\" at the end of the url."</string>
<string name="prompt_login">"Username"</string>
<string name="label_share">"Share"</string>
<string name="readAll">"Read all"</string>
<string name="action_disconnect">"Disconnect"</string>
<string name="title_activity_settings">"Settings"</string>
@ -62,28 +61,18 @@
<string name="card_height_on">Cards height will adjust to its content</string>
<string name="card_height_off">Card height will be fixed</string>
<string name="source_code">Source code</string>
<string name="drawer_error_loading_tags">Error loading tags</string>
<string name="drawer_item_filters">Filters</string>
<string name="drawer_action_clear">clear</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Sources</string>
<string name="drawer_action_edit">edit</string>
<string name="drawer_loading">Loading …</string>
<string name="filter_item_tags">Tags</string>
<string name="filter_item_sources">Sources</string>
<string name="menu_home_search">Search</string>
<string name="can_delete_source">Can\'t delete the source…</string>
<string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string>
<string name="pref_header_theme">Themes</string>
<string name="default_theme">Default</string>
<string name="default_dark_theme">Default/Dark</string>
<string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Loaded items number</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string>
<string name="items_number_should_be_number">The items number should be an integer.</string>
<string name="reader_action_more">Read more</string>
<string name="reader_action_open">Open in browser</string>
<string name="reader_action_share">Share</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -94,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -109,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string>
@ -117,13 +105,14 @@
<string name="new_items_notification_text">%1$d new items loaded.</string>
<string name="pref_switch_notify_new_items">Notify on new items synced.</string>
<string name="shortcut_offline">Offline</string>
<string name="pref_api_timeout">Api Timeout</string>
<string name="pref_api_timeout">Api Timeout (seconds)</string>
<string name="pref_header_experimental">Experimental</string>
<string name="webview_dialog_issue_message">Webview not available. Disabling the article viewer to avoid any future crashes. Will load articles inside of your browser from now on.</string>
<string name="webview_dialog_issue_title">Webview issue</string>
<string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string>
<string name="source_code_pro_font_id" translatable="false">source_code_pro_medium</string>
<string name="open_sans_font_id" translatable="false">open_sans</string>
<string name="roboto_font_id" translatable="false">roboto</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
@ -134,4 +123,12 @@
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources>

View File

@ -25,7 +25,11 @@
<item name="android:textColorPrimary">@color/white</item>
<item name="android:textColorSecondary">@color/white</item>
<item name="actionMenuTextColor">@color/white</item>
<!--<item name="actionOverflowButtonStyle">@style/ActionButtonOverflowStyle</item>
<item name="drawerArrowStyle">@style/DrawerArrowStyle</item>-->
</style>
<style name="Theme.AppCompat.ImageActivity" parent="NoBar">
<item name="android:windowBackground">@color/transparent_dark_background</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
</style>
</resources>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto"
android:id="@+id/motionLayout">
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="500"
motion:motionInterpolator="linear">
<OnSwipe
motion:touchAnchorId="@+id/scrollView"
motion:touchAnchorSide="top"
motion:onTouchUp="autoCompleteToStart" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@id/scrollView"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintTop_toBottomOf="@+id/appBarLayout"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent">
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/scrollView"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginBottom="0dp"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent">
</Constraint>
</ConstraintSet>
</MotionScene>

View File

@ -1,5 +1,6 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<EditTextPreference
android:inputType="number"
android:key="api_timeout"

View File

@ -14,15 +14,6 @@
android:title="@string/pref_api_items_number_title"
app:iconSpaceReserved="false"/>
<EditTextPreference
android:defaultValue=""
android:hint="@string/add_source_hint_tags"
android:key="hidden_tags"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="@string/pref_hidden_tags"
app:iconSpaceReserved="false"/>
<SwitchPreference
android:defaultValue="false"
android:key="infinite_loading"

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/title_activity_settings">
<Preference
@ -17,9 +18,13 @@
android:title="@string/pref_header_offline"
android:icon="@drawable/ic_signal_wifi_off_black_24dp" />
<Preference
android:fragment="bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity$ThemePreferenceFragment"
<ListPreference
android:defaultValue="0"
android:entries="@array/ModeTitles"
android:entryValues="@array/ModeValues"
android:key="currentMode"
android:title="@string/pref_header_theme"
app:useSimpleSummaryProvider="false"
android:icon="@drawable/ic_color_lens_black_24dp" />
<Preference
@ -32,4 +37,18 @@
android:title="@string/pref_header_experimental"
android:icon="@drawable/ic_widgets_black_24dp" />
<SwitchPreference
android:defaultValue="false"
android:key="acra.disable"
android:title="@string/pref_switch_disable_acra"
android:icon="@drawable/ic_baseline_bug_report_24"/>
<Preference
android:key="action_about"
android:title="@string/action_about"
android:icon="@drawable/ic_info_outline_white_24dp" />
</PreferenceScreen>

View File

@ -0,0 +1,46 @@
package bou.amine.apps.readerforselfossv2.repository
import bou.amine.apps.readerforselfossv2.utils.DateUtils
import junit.framework.TestCase.assertEquals
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
import org.junit.Test
class DatesTest {
private val v3Date = "2013-04-07T13:43:00+01:00"
private val v4Date = "2013-04-07 13:43:00"
private val bug1Date = "2022-12-24T17:00:08+00"
@Test
fun v3_date_should_be_parsed() {
val date = DateUtils.parseDate(v3Date)
val expected =
LocalDateTime(2013, 4, 7, 14, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(date, expected)
}
@Test
fun v4_date_should_be_parsed() {
val date = DateUtils.parseDate(v4Date)
val expected =
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(date, expected)
}
@Test
fun bug1_date_should_be_parsed() {
val date = DateUtils.parseDate(bug1Date)
val expected =
LocalDateTime(2022, 12, 24, 18, 0, 8, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(date, expected)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,60 @@
package bou.amine.apps.readerforselfossv2.repository
import bou.amine.apps.readerforselfossv2.dao.ITEM
import bou.amine.apps.readerforselfossv2.model.SelfossModel
fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<ITEM> {
return listOf(
ITEM(
id = item.id,
datetime = item.datetime,
title = item.title,
content = item.content,
unread = item.unread,
starred = item.starred,
thumbnail = item.thumbnail,
icon = item.icon,
link = item.link,
sourcetitle = item.sourcetitle,
tags = item.tags,
author = item.author
)
)
}
fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List<SelfossModel.Item> {
return listOf(
SelfossModel.Item(
id = item.id.toInt(),
datetime = item.datetime,
title = item.title,
content = item.content,
unread = item.unread,
starred = item.starred,
thumbnail = item.thumbnail,
icon = item.icon,
link = item.link,
sourcetitle = item.sourcetitle,
tags = item.tags.split(','),
author = item.author
)
)
}
class FakeItemParameters {
var id = "20"
var datetime = "2022-09-09T03:32:01-04:00"
val title = "Etica della ricerca sotto i riflettori."
val content =
"<p><strong>Luigi Campanella, già Presidente SCI</strong></p>\n<p>Letica della scienza è di certo ambito di cui continuiamo a scoprire nuovi aspetti e risvolti.</p>\n<p>Lultimo è quello delle intelligenze artificiali capaci di creare opere complesse basate su immagini e parole memorizzate con il rischio di fake news e di contenuti disturbanti.</p>\n<p>Per evitare che ciò accada si sta procedendo filtrando secondo criteri di autocensura i dati da cui lintelligenza artificiale parte.</p>\n<p>Comincia ad intravedersi un futuro prossimo di competizione fra autori umani ed artificiali nel quale sarà importante, quando i loro prodotti saranno indistinguibili, dichiararne lorigine.</p>\n<p>Come si comprende, si conferma che gli aspetti etici dellinnovazione e della ricerca si diversificato sempre di più.</p>\n<p>La biologia molecolare e la genetica già in passato hanno posto allattenzione comune aspetti di etica della scienza che hanno indotto a nuove riflessioni circa i limiti delle ricerche.</p>\n<p>Largomento, sempre attuale, torna sulle prime pagine a seguito della pubblicazione di una ricerca della Università di Cambridge che ha sviluppato una struttura cellulare di un topo con un cuore che batte regolarmente.</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image002-1.png?w=481\" alt=\"\" width=\"697\" height=\"430\" /><img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image003-1.png?w=906\" alt=\"\" /><p>Magdalena Zernicka-Goetz</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image004.jpg?w=474\" alt=\"\" width=\"622\" height=\"465\" /><p>Gianluca Amadei</p>\n<p>Del gruppo fa parte anche uno scienziato italiano Gianluca Amadei,che dinnanzi alle obiezioni di natura etica sulla realizzazione della vita artificiale si è affrettato a sostenere che non è creare nuove vite il fine primario della ricerca, ma quello di salvare quelle esistenti, di dare contributi essenziali alla medicina citando il caso del fallimento tuttora non interpretato di alcune gravidanze e di superare la sperimentazione animale, così contribuendo positivamente alla soluzione di un altro dilemma etico.</p>\n<p>Lembrione sintetico ha ovviamente come primo traguardo il contributo ai trapianti oggi drammaticamente carenti nellofferta rispetto alla domanda, con attese fino a 4 anni per i trapianti di cuore ed a 2 anni per quelli di fegato. Il lavoro dovrebbe adesso continuare presso lAteneo di Padova per creare nuovi organi e nuovi farmaci.</p>"
var unread = true
var starred = true
val thumbnail = null
val icon = "ba79e238383ce83c23a169929c8906ef.png"
val link =
"https://ilblogdellasci.wordpress.com/2022/09/09/etica-della-ricerca-sotto-i-riflettori/"
var sourcetitle = "La Chimica e la Società"
var tags = "Chimica, Testing"
var author = "Someone important"
}

View File

@ -1,25 +1,23 @@
buildscript {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
classpath("com.android.tools.build:gradle:7.3.0")
// sonarquve
classpath("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513")
// SqlDelight
classpath("com.squareup.sqldelight:gradle-plugin:1.5.3")
classpath("com.squareup.sqldelight:gradle-plugin:1.5.4")
}
}
apply(plugin = "org.sonarqube")
plugins {
//trick: for the same plugin versions in all sub-modules
id("com.android.application").version("7.4.0").apply(false)
id("com.android.library").version("7.4.0").apply(false)
kotlin("android").version("1.7.20").apply(false)
kotlin("multiplatform").version("1.7.20").apply(false)
id("com.mikepenz.aboutlibraries.plugin").version("10.5.1").apply(false)
id("org.jetbrains.kotlinx.kover") version "0.6.1"
}
allprojects {
repositories {
// maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
google()
mavenCentral()
jcenter()
@ -27,6 +25,11 @@ allprojects {
}
}
tasks.register("clean", Delete::class) {
delete(rootProject.buildDir)
}
koverMerged {
enable()
}

View File

@ -11,14 +11,26 @@
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#Tue Mar 22 16:50:00 CET 2022
#Gradle
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
#Kotlin
kotlin.code.style=official
kotlin.mpp.enableCInteropCommonization=true
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
kotlin.native.enableDependencyPropagation=false
#Android
android.useAndroidX=true
kotlin.native.enableDependencyPropagation=false
#android.nonTransitiveRClass=true
android.enableJetifier=true
#MPP
kotlin.mpp.enableCInteropCommonization=true
kotlin.mpp.enableGranularSourceSetsMetadata=true
org.gradle.parallel=true
org.gradle.caching=true
ignoreGitVersion=false
kotlin.native.cacheKind.iosX64=none
pushCache=true

View File

@ -1,6 +1,6 @@
#Wed Feb 09 17:05:19 CET 2022
#Mon Jan 23 20:47:46 CET 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@ -1,383 +1,383 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXBuildFile section */
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
7555FFB4242A642300829871 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXCopyFilesBuildPhase section */
7555FFB4242A642300829871 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
7555FF7B242A565900829871 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileReference section */
058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
7555FF7B242A565900829871 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
7555FF78242A565900829871 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXFrameworksBuildPhase section */
7555FF78242A565900829871 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
058557D7273AAEEB004C7B11 /* Preview Content */ = {
isa = PBXGroup;
children = (
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
7555FF72242A565900829871 = {
isa = PBXGroup;
children = (
7555FF7D242A565900829871 /* iosApp */,
7555FF7C242A565900829871 /* Products */,
7555FFB0242A642200829871 /* Frameworks */,
);
sourceTree = "<group>";
};
7555FF7C242A565900829871 /* Products */ = {
isa = PBXGroup;
children = (
7555FF7B242A565900829871 /* iosApp.app */,
);
name = Products;
sourceTree = "<group>";
};
7555FF7D242A565900829871 /* iosApp */ = {
isa = PBXGroup;
children = (
058557BA273AAA24004C7B11 /* Assets.xcassets */,
7555FF82242A565900829871 /* ContentView.swift */,
7555FF8C242A565B00829871 /* Info.plist */,
2152FB032600AC8F00CF470E /* iOSApp.swift */,
058557D7273AAEEB004C7B11 /* Preview Content */,
);
path = iosApp;
sourceTree = "<group>";
};
7555FFB0242A642200829871 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXGroup section */
058557D7273AAEEB004C7B11 /* Preview Content */ = {
isa = PBXGroup;
children = (
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
7555FF72242A565900829871 = {
isa = PBXGroup;
children = (
7555FF7D242A565900829871 /* iosApp */,
7555FF7C242A565900829871 /* Products */,
7555FFB0242A642200829871 /* Frameworks */,
);
sourceTree = "<group>";
};
7555FF7C242A565900829871 /* Products */ = {
isa = PBXGroup;
children = (
7555FF7B242A565900829871 /* iosApp.app */,
);
name = Products;
sourceTree = "<group>";
};
7555FF7D242A565900829871 /* iosApp */ = {
isa = PBXGroup;
children = (
058557BA273AAA24004C7B11 /* Assets.xcassets */,
7555FF82242A565900829871 /* ContentView.swift */,
7555FF8C242A565B00829871 /* Info.plist */,
2152FB032600AC8F00CF470E /* iOSApp.swift */,
058557D7273AAEEB004C7B11 /* Preview Content */,
);
path = iosApp;
sourceTree = "<group>";
};
7555FFB0242A642200829871 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
7555FF7A242A565900829871 /* iosApp */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */;
buildPhases = (
7555FFB5242A651A00829871 /* ShellScript */,
7555FF77242A565900829871 /* Sources */,
7555FF78242A565900829871 /* Frameworks */,
7555FF79242A565900829871 /* Resources */,
7555FFB4242A642300829871 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = iosApp;
productName = iosApp;
productReference = 7555FF7B242A565900829871 /* iosApp.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXNativeTarget section */
7555FF7A242A565900829871 /* iosApp */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */;
buildPhases = (
7555FFB5242A651A00829871 /* ShellScript */,
7555FF77242A565900829871 /* Sources */,
7555FF78242A565900829871 /* Frameworks */,
7555FF79242A565900829871 /* Resources */,
7555FFB4242A642300829871 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = iosApp;
productName = iosApp;
productReference = 7555FF7B242A565900829871 /* iosApp.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
7555FF73242A565900829871 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1130;
LastUpgradeCheck = 1130;
ORGANIZATIONNAME = orgName;
TargetAttributes = {
7555FF7A242A565900829871 = {
CreatedOnToolsVersion = 11.3.1;
};
};
};
buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 7555FF72242A565900829871;
productRefGroup = 7555FF7C242A565900829871 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
7555FF7A242A565900829871 /* iosApp */,
);
};
/* End PBXProject section */
/* Begin PBXProject section */
7555FF73242A565900829871 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1130;
LastUpgradeCheck = 1130;
ORGANIZATIONNAME = orgName;
TargetAttributes = {
7555FF7A242A565900829871 = {
CreatedOnToolsVersion = 11.3.1;
};
};
};
buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 7555FF72242A565900829871;
productRefGroup = 7555FF7C242A565900829871 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
7555FF7A242A565900829871 /* iosApp */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
7555FF79242A565900829871 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */,
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXResourcesBuildPhase section */
7555FF79242A565900829871 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */,
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
7555FFB5242A651A00829871 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "cd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
7555FFB5242A651A00829871 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "cd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
7555FF77242A565900829871 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
7555FF83242A565900829871 /* ContentView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
7555FF77242A565900829871 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
7555FF83242A565900829871 /* ContentView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
7555FFA3242A565B00829871 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
7555FFA4242A565B00829871 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
7555FFA6242A565B00829871 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
ENABLE_PREVIEWS = YES;
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
INFOPLIST_FILE = iosApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
shared,
);
PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
7555FFA7242A565B00829871 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
ENABLE_PREVIEWS = YES;
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
INFOPLIST_FILE = iosApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
shared,
);
PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCBuildConfiguration section */
7555FFA3242A565B00829871 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
7555FFA4242A565B00829871 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
7555FFA6242A565B00829871 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
ENABLE_PREVIEWS = YES;
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
INFOPLIST_FILE = iosApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
shared,
);
PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
7555FFA7242A565B00829871 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
ENABLE_PREVIEWS = YES;
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
INFOPLIST_FILE = iosApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-framework",
shared,
);
PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7555FFA3242A565B00829871 /* Debug */,
7555FFA4242A565B00829871 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7555FFA6242A565B00829871 /* Debug */,
7555FFA7242A565B00829871 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 7555FF73242A565900829871 /* Project object */;
}
/* Begin XCConfigurationList section */
7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7555FFA3242A565B00829871 /* Debug */,
7555FFA4242A565B00829871 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7555FFA6242A565B00829871 /* Debug */,
7555FFA7242A565B00829871 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 7555FF73242A565900829871 /* Project object */;
}

View File

@ -2,12 +2,9 @@ import SwiftUI
import shared
struct ContentView: View {
let greet = Greeting().greeting()
let toto = SelfossApi().getItems()
var body: some View {
Text(greet)
Text("ototot")
}
}

View File

@ -2,12 +2,22 @@ val pushCache: String by settings
pluginManagement {
repositories {
// maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
google()
gradlePluginPortal()
mavenCentral()
}
}
dependencyResolutionManagement {
repositories {
// maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
google()
mavenCentral()
}
}
buildCache {
remote<HttpBuildCache> {
url = uri("http://18.0.0.7:3071/cache/")

View File

@ -1,7 +1,7 @@
object SqlDelight {
const val runtime = "com.squareup.sqldelight:runtime:1.5.3"
const val android = "com.squareup.sqldelight:android-driver:1.5.3"
const val native = "com.squareup.sqldelight:native-driver:1.5.3"
const val runtime = "com.squareup.sqldelight:runtime:1.5.4"
const val android = "com.squareup.sqldelight:android-driver:1.5.4"
const val native = "com.squareup.sqldelight:native-driver:1.5.4"
}
@ -10,6 +10,7 @@ plugins {
id("com.android.library")
id("com.squareup.sqldelight")
kotlin("plugin.serialization") version "1.4.10"
id("org.jetbrains.kotlinx.kover") version "0.6.1"
}
kotlin {
@ -18,7 +19,7 @@ kotlin {
listOf(
iosX64(),
iosArm64(),
//iosSimulatorArm64() sure all ios dependencies support this target
// iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "shared"
@ -40,14 +41,11 @@ kotlin {
implementation("org.kodein.di:kodein-di:7.12.0")
//Settings
implementation("com.russhwolf:multiplatform-settings-no-arg:0.9")
implementation("com.russhwolf:multiplatform-settings-no-arg:1.0.0-RC")
//Logging
implementation("io.github.aakira:napier:2.6.1")
// Network information
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
// Sql
implementation(SqlDelight.runtime)
}
@ -61,6 +59,7 @@ kotlin {
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-okhttp:2.1.1")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
// Sql
implementation(SqlDelight.android)
@ -74,39 +73,36 @@ kotlin {
}
val iosX64Main by getting
val iosArm64Main by getting
//val iosSimulatorArm64Main by getting
// val iosSimulatorArm64Main by getting
val iosMain by creating {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
//iosSimulatorArm64Main.dependsOn(this)
// iosSimulatorArm64Main.dependsOn(this)
// Sql
dependencies {
implementation(SqlDelight.native)
implementation("io.ktor:ktor-client-ios:2.1.1")
}
}
val iosX64Test by getting
val iosArm64Test by getting
//val iosSimulatorArm64Test by getting
// val iosSimulatorArm64Test by getting
val iosTest by creating {
dependsOn(commonTest)
iosX64Test.dependsOn(this)
iosArm64Test.dependsOn(this)
dependencies {
implementation("io.ktor:ktor-client-ios:2.1.1")
}
//iosSimulatorArm64Test.dependsOn(this)
// iosSimulatorArm64Test.dependsOn(this)
}
}
}
android {
compileSdk = 31
compileSdk = 32
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 21
targetSdk = 31
targetSdk = 32
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
@ -115,10 +111,11 @@ android {
namespace = "bou.amine.apps.readerforselfossv2"
}
sqldelight {
database("ReaderForSelfossDB") {
packageName = "bou.amine.apps.readerforselfossv2.dao"
sourceFolders = listOf("sqldelight")
}
}
}

View File

@ -1,36 +1,33 @@
package bou.amine.apps.readerforselfossv2.utils
import android.text.format.DateUtils
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import java.time.Instant
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import kotlinx.datetime.*
actual class DateUtils actual constructor(actual val appSettingsService: AppSettingsService) {
actual fun parseDate(dateString: String): Long {
actual class DateUtils {
actual companion object {
actual fun parseDate(dateString: String): Long {
return try {
Instant.parse(dateString).toEpochMilliseconds()
} catch (e: Exception) {
var str = dateString.replace(" ", "T")
if (str.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+\\d{2}".toRegex())) {
str = str.split("+")[0]
}
LocalDateTime.parse(str).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
}
}
val FORMATTERV1 = "yyyy-MM-dd HH:mm:ss"
actual fun parseRelativeDate(dateString: String): String {
return if (appSettingsService.getApiVersion() >= 4) {
OffsetDateTime.parse(dateString).toInstant().toEpochMilli()
} else {
LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(FORMATTERV1)).toInstant(
ZoneOffset.UTC).toEpochMilli()
val date = parseDate(dateString)
return " " + DateUtils.getRelativeTimeSpanString(
date,
Clock.System.now().toEpochMilliseconds(),
DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
)
}
}
actual fun parseRelativeDate(dateString: String): String {
val date = parseDate(dateString)
return " " + DateUtils.getRelativeTimeSpanString(
date,
Instant.now().toEpochMilli(),
DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
)
}
}

View File

@ -1,5 +1,6 @@
package bou.amine.apps.readerforselfossv2.DI
import bou.amine.apps.readerforselfossv2.rest.MercuryApi
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import org.kodein.di.DI
@ -8,6 +9,6 @@ import org.kodein.di.instance
import org.kodein.di.singleton
val networkModule by DI.Module {
bind<AppSettingsService>() with singleton { AppSettingsService() }
bind<SelfossApi>() with singleton { SelfossApi(instance()) }
bind<MercuryApi>() with singleton { MercuryApi() }
}

View File

@ -0,0 +1,14 @@
package bou.amine.apps.readerforselfossv2.model
import kotlinx.serialization.Serializable
class MercuryModel {
@Serializable
class ParsedContent(
val title: String?,
val content: String?,
val lead_image_url: String?, // NOSONAR
val url: String
)
}

View File

@ -0,0 +1,21 @@
package bou.amine.apps.readerforselfossv2.model
import kotlinx.serialization.Serializable
@Serializable
class SuccessResponse(val success: Boolean) {
val isSuccess: Boolean
get() = success
}
class StatusAndData<T>(val success: Boolean, val data: T? = null) {
companion object {
fun <T> succes(d: T): StatusAndData<T> {
return StatusAndData(true, d)
}
fun <T> error(): StatusAndData<T> {
return StatusAndData(false)
}
}
}

View File

@ -9,6 +9,7 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.encodeCollection
import kotlinx.serialization.json.*
class SelfossModel {
@ -20,12 +21,6 @@ class SelfossModel {
val unread: Int
)
@Serializable
class SuccessResponse(val success: Boolean) {
val isSuccess: Boolean
get() = success
}
@Serializable
class Stats(
val total: Int,
@ -40,17 +35,32 @@ class SelfossModel {
)
@Serializable
data class ApiVersion(
data class ApiInformation(
val version: String?,
val apiversion: String?
val apiversion: String?,
val configuration: ApiConfiguration?
) {
fun getApiMajorVersion() : Int {
fun getApiMajorVersion(): Int {
var versionNumber = 0
if (apiversion != null) {
versionNumber = apiversion.substringBefore(".").toInt()
}
return versionNumber
}
fun getApiConfiguration() = configuration ?: ApiConfiguration(null, null)
}
@Serializable
data class ApiConfiguration(
@Serializable(with = BooleanSerializer::class)
val publicMode: Boolean?,
@Serializable(with = BooleanSerializer::class)
val authEnabled: Boolean?
) {
fun isAuthEnabled() = authEnabled ?: true
fun isPublicModeEnabled() = publicMode ?: false
}
@Serializable
@ -61,9 +71,13 @@ class SelfossModel {
val tags: List<String>,
val spout: String,
val error: String,
val icon: String?
val icon: String?,
val params: SourceParams?
)
@Serializable
data class SourceParams(
val url: String
)
@Serializable
data class Item(
val id: Int,
@ -79,21 +93,16 @@ class SelfossModel {
val link: String,
val sourcetitle: String,
@Serializable(with = TagsListSerializer::class)
val tags: List<String>
val tags: List<String>,
val author: String?
) {
// TODO: maybe find a better way to handle these kind of urls
fun getLinkDecoded(): String {
var stringUrl: String
stringUrl =
if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
if (link.contains("&amp;url=")) {
link.substringAfter("&amp;url=")
} else {
this.link.replace("&amp;", "&")
}
} else {
this.link.replace("&amp;", "&")
}
stringUrl = if (link.contains("//news.google.com/news/") && link.contains("&amp;url=")) {
link.substringAfter("&amp;url=")
} else {
this.link.replace("&amp;", "&")
}
// handle :443 => https
if (stringUrl.contains(":443")) {
@ -108,8 +117,14 @@ class SelfossModel {
return stringUrl
}
fun sourceAndDateText(dateUtils: DateUtils): String =
this.sourcetitle.getHtmlDecoded() + dateUtils.parseRelativeDate(this.datetime)
fun sourceAuthorAndDate(): String {
var txt = this.sourcetitle.getHtmlDecoded()
if (!this.author.isNullOrBlank()) {
txt += " (by ${this.author}) "
}
txt += DateUtils.parseRelativeDate(this.datetime)
return txt
}
fun toggleStar(): Item {
this.starred = !this.starred
@ -117,11 +132,12 @@ class SelfossModel {
}
}
// TODO: this seems to be super slow.
object TagsListSerializer : KSerializer<List<String>> {
override fun deserialize(decoder: Decoder): List<String> {
return when(val json = ((decoder as JsonDecoder).decodeJsonElement())) {
is JsonArray -> json.toList().map { it.toString() }
is JsonArray -> json.toList().map { it.toString().replace("^\"|\"$".toRegex(), "") }
else -> json.toString().split(",")
}
@ -131,14 +147,18 @@ class SelfossModel {
get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: List<String>) {
TODO("Not yet implemented")
encoder.encodeCollection(PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING), value.size) { this.toString() }
}
}
object BooleanSerializer : KSerializer<Boolean> {
override fun deserialize(decoder: Decoder): Boolean {
val json = ((decoder as JsonDecoder).decodeJsonElement()).jsonPrimitive
return json.booleanOrNull ?: json.int == 1
return if (json.booleanOrNull != null) {
json.boolean
} else {
json.int == 1
}
}
override val descriptor: SerialDescriptor
@ -148,16 +168,4 @@ class SelfossModel {
TODO("Not yet implemented")
}
}
class StatusAndData<T>(val success: Boolean, val data: T? = null) {
companion object {
fun <T> succes(d: T): StatusAndData<T> {
return StatusAndData(true, d)
}
fun <T> error(): StatusAndData<T> {
return StatusAndData(false)
}
}
}
}

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